@sentry/junior 0.181.1 → 0.182.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 (81) hide show
  1. package/dist/{agent-hooks-KO56HJ74.js → agent-hooks-3S25UIHO.js} +11 -11
  2. package/dist/api.js +125 -51
  3. package/dist/app-adapter.d.ts +85 -0
  4. package/dist/app.d.ts +6 -1
  5. package/dist/app.js +343 -487
  6. package/dist/{catalog-runtime-CJTFLCK3.js → catalog-runtime-ESLMBQBV.js} +4 -4
  7. package/dist/chat/api-turns/adapter-conversations.d.ts +15 -0
  8. package/dist/chat/api-turns/stop.d.ts +12 -0
  9. package/dist/chat/api-turns/work.d.ts +9 -1
  10. package/dist/chat/app/conversation-work.d.ts +1 -1
  11. package/dist/chat/event-tasks/tool-support.d.ts +7 -1
  12. package/dist/chat/event-tasks/types.d.ts +1 -0
  13. package/dist/chat/experimental.d.ts +1 -1
  14. package/dist/chat/resource-events/catalog.d.ts +11 -1
  15. package/dist/chat/resource-events/store.d.ts +5 -1
  16. package/dist/chat/services/subscribed-decision.d.ts +2 -1
  17. package/dist/chat/tools/create-event-task.d.ts +3 -0
  18. package/dist/chat/tools/delete-event-task.d.ts +2 -0
  19. package/dist/chat/tools/list-event-tasks.d.ts +2 -0
  20. package/dist/chat/tools/list-resource-event-subscriptions.d.ts +2 -0
  21. package/dist/chat/tools/search-resource-event-types.d.ts +10 -0
  22. package/dist/chat/tools/update-event-task.d.ts +3 -0
  23. package/dist/chat/tools/watch-resource-events.d.ts +3 -0
  24. package/dist/{chunk-WDNA4LML.js → chunk-2CEOXDG2.js} +20 -11
  25. package/dist/{chunk-VRS6WDK5.js → chunk-2LYHWMR4.js} +1 -1
  26. package/dist/{chunk-6TB7JN63.js → chunk-3ENUMZNG.js} +3 -3
  27. package/dist/{chunk-4WYHL4EH.js → chunk-3NFHI2NA.js} +1 -1
  28. package/dist/{chunk-MR36BRPE.js → chunk-3THPTNN6.js} +4 -4
  29. package/dist/{chunk-ULM7P4EQ.js → chunk-44BQ3TSK.js} +1 -1
  30. package/dist/{chunk-7UF37LNL.js → chunk-4DZ62L36.js} +1 -1
  31. package/dist/{chunk-YCW6RZGX.js → chunk-6TSWXBTQ.js} +3 -3
  32. package/dist/{chunk-IWRPJ3J5.js → chunk-A7THOQ6N.js} +58 -32
  33. package/dist/{chunk-J4ID4ZI6.js → chunk-BDDWVY32.js} +19 -19
  34. package/dist/{chunk-S5X2VQOD.js → chunk-BZD4ADQH.js} +3 -2
  35. package/dist/{chunk-CJRUBOJI.js → chunk-CVBIM4Z3.js} +1 -1
  36. package/dist/{chunk-HPFHC7YR.js → chunk-D6OHGAIO.js} +5 -5
  37. package/dist/{chunk-NRF23QZ4.js → chunk-DZR3L74W.js} +3 -3
  38. package/dist/{chunk-RULGB5NX.js → chunk-GGYA5NPN.js} +9 -9
  39. package/dist/{chunk-KH2SL2HT.js → chunk-JIOKUIAS.js} +2 -2
  40. package/dist/{chunk-XQ56HNH5.js → chunk-KGMHUDVF.js} +6 -6
  41. package/dist/{chunk-YFJDRORU.js → chunk-LB4FWMCV.js} +92 -8
  42. package/dist/{chunk-S63LNPIV.js → chunk-LHQK5XCU.js} +2 -2
  43. package/dist/{chunk-DINF4LMR.js → chunk-LT4QYGKM.js} +7 -7
  44. package/dist/{chunk-4OSVTPYQ.js → chunk-MJISSOWB.js} +1 -1
  45. package/dist/{chunk-PJ7EGFME.js → chunk-PQES5LB5.js} +5 -5
  46. package/dist/{chunk-XL3C4NAR.js → chunk-QHALFHZA.js} +9 -9
  47. package/dist/{chunk-HG6HNOTQ.js → chunk-S35OZMSL.js} +130 -200
  48. package/dist/{chunk-Z2UG4FBT.js → chunk-SVFSYQMT.js} +1 -1
  49. package/dist/{chunk-DTRCZOHE.js → chunk-SXOZIG5X.js} +6 -6
  50. package/dist/{chunk-4GOZBB22.js → chunk-TEFNHMO2.js} +63 -7
  51. package/dist/{chunk-QA6RE6IB.js → chunk-UKIHNBBD.js} +3 -3
  52. package/dist/{chunk-2CZ63QCL.js → chunk-UQP54CXZ.js} +1 -1
  53. package/dist/{chunk-HRTW7FFX.js → chunk-UUMBHORH.js} +1 -1
  54. package/dist/{chunk-ZQQWJMHE.js → chunk-V5UZCCNW.js} +6 -6
  55. package/dist/{chunk-GITIZ6TZ.js → chunk-VHMAFMHB.js} +1 -1
  56. package/dist/{chunk-DXK3VTVP.js → chunk-WOIALVN2.js} +1 -1
  57. package/dist/{chunk-VHSBLFNT.js → chunk-YHL2L2ZZ.js} +1 -1
  58. package/dist/{chunk-H6MPFFBE.js → chunk-Z7ALSQS3.js} +1 -1
  59. package/dist/cli/chat.js +34 -34
  60. package/dist/cli/check.js +5 -5
  61. package/dist/cli/plugins.js +12 -12
  62. package/dist/cli/snapshot-create.js +9 -9
  63. package/dist/cli/upgrade.js +6 -6
  64. package/dist/{db-4PXTD3OH.js → db-NYIWZ4L5.js} +6 -6
  65. package/dist/{experimental-SIJSRDJD.js → experimental-P5FZM4CU.js} +1 -1
  66. package/dist/instrumentation.js +3 -3
  67. package/dist/nitro.js +6 -6
  68. package/dist/{oauth-callback-server-TQWNEVR6.js → oauth-callback-server-A2YTPVVX.js} +26 -26
  69. package/dist/{runner-G5HLQA43.js → runner-VKAEU52J.js} +18 -18
  70. package/dist/{skills-BNNNRYNT.js → skills-52GMXUTY.js} +5 -5
  71. package/dist/spawn-GSS4EBLR.js +37 -0
  72. package/dist/task-runner-DUGDWF5O.js +32 -0
  73. package/dist/{validation-IJSBB6JR.js → validation-VD2YQX2W.js} +5 -5
  74. package/dist/version.js +1 -1
  75. package/dist/{work-5FQI4P2A.js → work-ZWBTGEQP.js} +18 -18
  76. package/dist/worker-XUWD5WS7.js +22 -0
  77. package/package.json +7 -6
  78. package/dist/api/acp/route.d.ts +0 -13
  79. package/dist/spawn-U2RKOFP2.js +0 -37
  80. package/dist/task-runner-CDZJBEXP.js +0 -32
  81. package/dist/worker-KJ362GVB.js +0 -22
package/dist/app.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  processConversationWork
3
- } from "./chunk-MR36BRPE.js";
3
+ } from "./chunk-3THPTNN6.js";
4
4
  import {
5
5
  bindSpawnAgent
6
- } from "./chunk-GITIZ6TZ.js";
6
+ } from "./chunk-VHMAFMHB.js";
7
7
  import {
8
8
  createAgentInvocationWorker,
9
9
  enqueueAgentInvocation,
10
10
  listPendingAgentInvocationMailboxAppends,
11
11
  routeAgentInvocationWork
12
- } from "./chunk-DINF4LMR.js";
12
+ } from "./chunk-LT4QYGKM.js";
13
13
  import {
14
14
  GET as GET2,
15
15
  GET2 as GET3,
@@ -36,25 +36,25 @@ import {
36
36
  startSlackProcessingReaction,
37
37
  stripLeadingBotMention,
38
38
  wakePausedTurn
39
- } from "./chunk-J4ID4ZI6.js";
39
+ } from "./chunk-BDDWVY32.js";
40
40
  import "./chunk-FA6POGLH.js";
41
41
  import {
42
42
  GET,
43
43
  apiTurnIdForMessage,
44
44
  appendAndEnqueueApiConversationMessage,
45
- authenticatePersonalToken,
46
45
  conversationVisibilityFromSlackChannelType,
47
46
  createApiTurnCancellation,
48
47
  createApiTurnWorker,
49
48
  createVercelAttachmentStorage,
49
+ getAuthPausedApiTurnId,
50
50
  readConversationAccessFromSql,
51
51
  recordApiConversationActivity,
52
52
  resolveSlackChannelTypeFromMessage,
53
53
  resolveSlackConversationContext,
54
54
  routeApiTurnWork,
55
55
  webActorFromEmail
56
- } from "./chunk-HG6HNOTQ.js";
57
- import "./chunk-CJRUBOJI.js";
56
+ } from "./chunk-S35OZMSL.js";
57
+ import "./chunk-CVBIM4Z3.js";
58
58
  import {
59
59
  buildDeliveredTurnStatePatch,
60
60
  completeAuthPauseTurn,
@@ -65,7 +65,7 @@ import {
65
65
  getTurnUserSlackMessageTs,
66
66
  persistAuthPauseTurnState,
67
67
  requireTurnFailureEventId
68
- } from "./chunk-HPFHC7YR.js";
68
+ } from "./chunk-D6OHGAIO.js";
69
69
  import {
70
70
  claimDispatchMailboxAppend,
71
71
  confirmDispatchMailboxAppend,
@@ -85,7 +85,7 @@ import {
85
85
  recordTaskExecution,
86
86
  resolveTurnSessionRouting,
87
87
  scheduleSessionCompletedPluginTasks
88
- } from "./chunk-XL3C4NAR.js";
88
+ } from "./chunk-QHALFHZA.js";
89
89
  import {
90
90
  ConversationTurnLifecycleService
91
91
  } from "./chunk-M3QYTBQ6.js";
@@ -97,17 +97,17 @@ import {
97
97
  persistThreadRuntimeState,
98
98
  persistThreadState,
99
99
  persistThreadStateById
100
- } from "./chunk-PJ7EGFME.js";
100
+ } from "./chunk-PQES5LB5.js";
101
101
  import "./chunk-UTL3MHPC.js";
102
102
  import {
103
103
  createVercelPluginTaskCallback,
104
104
  queueCallback,
105
105
  registerVercelPluginTaskDevConsumer
106
- } from "./chunk-NRF23QZ4.js";
106
+ } from "./chunk-DZR3L74W.js";
107
107
  import {
108
108
  validatePluginEgressCredentialHooks,
109
109
  validatePluginRegistrations
110
- } from "./chunk-4WYHL4EH.js";
110
+ } from "./chunk-3NFHI2NA.js";
111
111
  import {
112
112
  defineJuniorPlugins,
113
113
  pluginCatalogConfigFromEnv,
@@ -155,11 +155,11 @@ import {
155
155
  unpublishedArtifactHeaders,
156
156
  verifySlackSignature,
157
157
  withNormalizedThreadId
158
- } from "./chunk-IWRPJ3J5.js";
158
+ } from "./chunk-A7THOQ6N.js";
159
159
  import {
160
160
  AuthorizationFlowDisabledError,
161
161
  PluginCredentialFailureError
162
- } from "./chunk-XQ56HNH5.js";
162
+ } from "./chunk-KGMHUDVF.js";
163
163
  import "./chunk-6Y5B5PEX.js";
164
164
  import "./chunk-DIB4GX3G.js";
165
165
  import {
@@ -168,7 +168,7 @@ import {
168
168
  deleteMcpAuthSessionsForUserProvider,
169
169
  deleteMcpServerSessionId,
170
170
  deleteMcpStoredOAuthCredentials
171
- } from "./chunk-RULGB5NX.js";
171
+ } from "./chunk-GGYA5NPN.js";
172
172
  import {
173
173
  RetryableDeliveryError
174
174
  } from "./chunk-RF6J3DIO.js";
@@ -176,12 +176,12 @@ import {
176
176
  formatProviderLabel,
177
177
  postSlackMessage,
178
178
  startOAuthFlow
179
- } from "./chunk-ZQQWJMHE.js";
179
+ } from "./chunk-V5UZCCNW.js";
180
180
  import {
181
181
  getExperimentalFeatures,
182
182
  isExperimentalFeatureEnabled,
183
183
  setExperimentalFeatures
184
- } from "./chunk-VRS6WDK5.js";
184
+ } from "./chunk-2LYHWMR4.js";
185
185
  import {
186
186
  collectAttachmentGarbage,
187
187
  findMatchingEventTasks,
@@ -196,21 +196,25 @@ import {
196
196
  scheduledRunId,
197
197
  scheduledRunSchema,
198
198
  withScheduledTaskLock
199
- } from "./chunk-YFJDRORU.js";
199
+ } from "./chunk-LB4FWMCV.js";
200
200
  import {
201
201
  parseSlackThreadId
202
- } from "./chunk-4OSVTPYQ.js";
202
+ } from "./chunk-MJISSOWB.js";
203
203
  import {
204
204
  CONVERSATION_WORK_STALE_ENQUEUE_MS,
205
205
  appendAndEnqueueInboundMessage,
206
206
  clearExpiredConversationLease,
207
207
  ensureConversationWake,
208
+ getConversation,
208
209
  getConversationWorkState,
209
210
  hasRunnableConversationWork,
211
+ hasRunnableConversationWork2,
210
212
  isInvalidConversationRecordError,
211
213
  listActiveConversationIds,
212
- removeActiveConversation
213
- } from "./chunk-4GOZBB22.js";
214
+ removeActiveConversation,
215
+ requestConversationWork,
216
+ stopConversationWork
217
+ } from "./chunk-TEFNHMO2.js";
214
218
  import {
215
219
  ConversationTurnBoundaryError,
216
220
  CooperativeTurnYieldError,
@@ -228,7 +232,7 @@ import {
228
232
  markTurnCompleted,
229
233
  markTurnFailed,
230
234
  startActiveTurn
231
- } from "./chunk-VHSBLFNT.js";
235
+ } from "./chunk-YHL2L2ZZ.js";
232
236
  import {
233
237
  abandonTurnRecord,
234
238
  appendThreadContextMessages,
@@ -264,11 +268,11 @@ import {
264
268
  trimTrailingAssistantMessages,
265
269
  turnHasReply,
266
270
  upsertConversationMessage
267
- } from "./chunk-DTRCZOHE.js";
271
+ } from "./chunk-SXOZIG5X.js";
268
272
  import {
269
273
  getSandboxResourceConfig,
270
274
  setSandboxResourceConfig
271
- } from "./chunk-6TB7JN63.js";
275
+ } from "./chunk-3ENUMZNG.js";
272
276
  import "./chunk-PDO5BLNM.js";
273
277
  import "./chunk-UIE3R5XU.js";
274
278
  import {
@@ -287,7 +291,7 @@ import {
287
291
  import "./chunk-7H2VZTWN.js";
288
292
  import {
289
293
  getTurnRequestDeadline
290
- } from "./chunk-HRTW7FFX.js";
294
+ } from "./chunk-UUMBHORH.js";
291
295
  import {
292
296
  SlackActionError,
293
297
  bindEventTaskCredentialSubject,
@@ -305,7 +309,6 @@ import {
305
309
  getWorkspaceTeamId,
306
310
  isDmChannel,
307
311
  normalizeSlackConversationId,
308
- resolveViewerUser,
309
312
  runWithWorkspaceTeamId,
310
313
  setDashboardConversationLinkOptions,
311
314
  setPlugins,
@@ -313,7 +316,7 @@ import {
313
316
  verifyEventTaskCredentialSubject,
314
317
  verifyScheduledTaskCredentialSubject,
315
318
  verifySlackDirectCredentialSubject
316
- } from "./chunk-WDNA4LML.js";
319
+ } from "./chunk-2CEOXDG2.js";
317
320
  import "./chunk-3Y47VMHV.js";
318
321
  import {
319
322
  contextProvenance,
@@ -326,7 +329,7 @@ import {
326
329
  requireSlackDestination,
327
330
  withConversationEventLock,
328
331
  withConversationMutationLock
329
- } from "./chunk-YCW6RZGX.js";
332
+ } from "./chunk-6TSWXBTQ.js";
330
333
  import {
331
334
  juniorAgentBindings,
332
335
  juniorAgentInvocations,
@@ -334,15 +337,13 @@ import {
334
337
  juniorConversations,
335
338
  juniorSchedulerRuns,
336
339
  juniorSchedulerTasks
337
- } from "./chunk-ULM7P4EQ.js";
338
- import {
339
- sleep
340
- } from "./chunk-QA6RE6IB.js";
340
+ } from "./chunk-44BQ3TSK.js";
341
+ import "./chunk-UKIHNBBD.js";
341
342
  import {
342
343
  acquireActiveLock,
343
344
  getStateAdapter,
344
345
  withActiveLock
345
- } from "./chunk-H6MPFFBE.js";
346
+ } from "./chunk-Z7ALSQS3.js";
346
347
  import {
347
348
  juniorDestinations
348
349
  } from "./chunk-VFUK3X5B.js";
@@ -363,13 +364,13 @@ import {
363
364
  modelIdForProfile,
364
365
  setProfiles,
365
366
  setSlackReactionConfig
366
- } from "./chunk-S63LNPIV.js";
367
- import "./chunk-Z2UG4FBT.js";
368
- import "./chunk-KH2SL2HT.js";
367
+ } from "./chunk-LHQK5XCU.js";
368
+ import "./chunk-SVFSYQMT.js";
369
+ import "./chunk-JIOKUIAS.js";
369
370
  import {
370
371
  pluginCatalogRuntime
371
- } from "./chunk-DXK3VTVP.js";
372
- import "./chunk-7UF37LNL.js";
372
+ } from "./chunk-WOIALVN2.js";
373
+ import "./chunk-4DZ62L36.js";
373
374
  import {
374
375
  credentialContextForActor
375
376
  } from "./chunk-CEMMLV5L.js";
@@ -404,13 +405,13 @@ import {
404
405
  withContext,
405
406
  withLogContext,
406
407
  withSpan
407
- } from "./chunk-2CZ63QCL.js";
408
+ } from "./chunk-UQP54CXZ.js";
408
409
  import {
409
410
  continueTrace
410
411
  } from "./chunk-PEQXYSSE.js";
411
412
  import {
412
413
  JUNIOR_VERSION
413
- } from "./chunk-S5X2VQOD.js";
414
+ } from "./chunk-BZD4ADQH.js";
414
415
 
415
416
  // src/app.ts
416
417
  import { createRequire } from "module";
@@ -514,7 +515,7 @@ async function recoverConversationWork(args) {
514
515
  }
515
516
  return;
516
517
  }
517
- if (work.execution.lease || !hasRunnableConversationWork(work)) {
518
+ if (work.execution.lease || !hasRunnableConversationWork2(work)) {
518
519
  return;
519
520
  }
520
521
  const wake = await ensureConversationWake({
@@ -4044,17 +4045,9 @@ async function decideSubscribedThreadReply(args) {
4044
4045
  if (preflightDecision) {
4045
4046
  return preflightDecision;
4046
4047
  }
4047
- const evidence = buildRouterEvidence(args.input);
4048
4048
  if (!text && !args.input.hasAttachments) {
4049
4049
  return { shouldReply: false, reason: "empty_message" /* EmptyMessage */ };
4050
4050
  }
4051
- if (!args.input.isExplicitMention && !args.input.hasAttachments && isAcknowledgmentOnly(text)) {
4052
- return {
4053
- shouldReply: false,
4054
- reason: "side_conversation" /* SideConversation */,
4055
- reasonDetail: "acknowledgment"
4056
- };
4057
- }
4058
4051
  if (args.input.isExplicitMention) {
4059
4052
  if (isThreadOptOutInstruction(rawText, text)) {
4060
4053
  return {
@@ -4069,6 +4062,21 @@ async function decideSubscribedThreadReply(args) {
4069
4062
  reason: "explicit_mention" /* ExplicitMention */
4070
4063
  };
4071
4064
  }
4065
+ if (!isExperimentalFeatureEnabled("passive-routing")) {
4066
+ return {
4067
+ shouldReply: false,
4068
+ reason: "passive_disabled" /* PassiveDisabled */,
4069
+ reasonDetail: "passive-routing"
4070
+ };
4071
+ }
4072
+ if (!args.input.hasAttachments && isAcknowledgmentOnly(text)) {
4073
+ return {
4074
+ shouldReply: false,
4075
+ reason: "side_conversation" /* SideConversation */,
4076
+ reasonDetail: "acknowledgment"
4077
+ };
4078
+ }
4079
+ const evidence = buildRouterEvidence(args.input);
4072
4080
  if (evidence.assistantWasLastSpeaker && evidence.humanMessagesSinceLastAssistant === 0 && !evidence.currentMessageHasAttachments && (evidence.currentMessageHasDirectedFollowUpCue || evidence.currentMessageIsTerseClarification)) {
4073
4081
  return {
4074
4082
  shouldReply: true,
@@ -4745,6 +4753,31 @@ function createSlackTurnRuntime(deps) {
4745
4753
  });
4746
4754
  return;
4747
4755
  }
4756
+ if (!isResourceEventNotification && !turnIsExplicitMention && !isExperimentalFeatureEnabled("passive-routing")) {
4757
+ const decision2 = await deps.decideSubscribedReply({
4758
+ rawText: combinedText.rawText,
4759
+ text: combinedText.userText,
4760
+ hasAttachments: content.hasAttachments || queuedMessages.some(
4761
+ (queued) => queued.message.attachments.length > 0
4762
+ ),
4763
+ isExplicitMention: false,
4764
+ context: threadContext
4765
+ });
4766
+ await maybeHandleThreadOptOutDecision({
4767
+ thread,
4768
+ decision: decision2,
4769
+ beforeFirstResponsePost: hooks.beforeFirstResponsePost
4770
+ });
4771
+ await skipSubscribedMessage({
4772
+ thread,
4773
+ message,
4774
+ decision: decision2,
4775
+ context: threadContext,
4776
+ ack: hooks.ack,
4777
+ text: combinedText
4778
+ });
4779
+ return;
4780
+ }
4748
4781
  const preparedState = await deps.prepareTurnState({
4749
4782
  thread,
4750
4783
  message,
@@ -8118,416 +8151,203 @@ async function ingestEventTasks(input, options) {
8118
8151
  return { dispatched };
8119
8152
  }
8120
8153
 
8121
- // src/api/acp/route.ts
8122
- import { randomUUID } from "crypto";
8123
- import * as acp from "@agentclientprotocol/sdk";
8124
- import { AcpServer } from "@agentclientprotocol/sdk/experimental/server";
8125
- var ACP_CONNECTION_ID_HEADER = "Acp-Connection-Id";
8126
- var ACP_CONVERSATION_PREFIX = "local:acp:";
8127
- var EVENT_PAGE_SIZE = 50;
8128
- var EVENT_POLL_INTERVAL_MS = 25;
8129
- var MAX_PROMPT_TEXT_LENGTH = 32e3;
8130
- function bearerToken(request) {
8131
- const authorization = request.headers.get("Authorization");
8132
- const match = authorization?.match(/^Bearer ([^\s]+)$/);
8133
- return match?.[1];
8134
- }
8135
- async function authenticateRequest(request) {
8136
- const token = bearerToken(request);
8137
- if (!token) return void 0;
8138
- const email = await authenticatePersonalToken(token);
8139
- if (!email) return void 0;
8140
- const user = await resolveViewerUser(email);
8141
- if (!user) return void 0;
8142
- return {
8143
- actor: webActorFromEmail(
8144
- user.email ?? email,
8145
- user.displayName ? { fullName: user.displayName } : void 0
8146
- ),
8147
- user
8148
- };
8149
- }
8150
- function rejectUnsupportedMcpServers(mcpServers) {
8151
- if (mcpServers.length > 0) {
8152
- throw acp.RequestError.invalidParams(
8153
- { field: "mcpServers" },
8154
- "Junior does not accept client MCP servers"
8155
- );
8156
- }
8157
- }
8158
- async function requireOwnedSession(sessionId, user) {
8159
- if (!sessionId.startsWith(ACP_CONVERSATION_PREFIX)) {
8160
- throw acp.RequestError.resourceNotFound(sessionId);
8154
+ // src/chat/api-turns/stop.ts
8155
+ async function stopApiConversationTurn(args) {
8156
+ const nowMs = args.nowMs ?? Date.now();
8157
+ const stop = async () => await stopConversationWork({
8158
+ conversationId: args.conversationId,
8159
+ conversationStore: args.conversationStore,
8160
+ nowMs,
8161
+ state: args.state
8162
+ });
8163
+ let result = await stop();
8164
+ if (result.status === "no_work" && await getAuthPausedApiTurnId(args.conversationId)) {
8165
+ const conversation = await getConversation({
8166
+ conversationId: args.conversationId,
8167
+ state: args.state
8168
+ });
8169
+ await requestConversationWork({
8170
+ conversationId: args.conversationId,
8171
+ conversationStore: args.conversationStore,
8172
+ destination: conversation?.destination,
8173
+ nowMs,
8174
+ state: args.state
8175
+ });
8176
+ result = await stop();
8161
8177
  }
8162
- const access = (await readConversationAccessFromSql(getDb(), [sessionId], user)).get(sessionId);
8163
- if (!access?.isParticipant) {
8164
- throw acp.RequestError.resourceNotFound(sessionId);
8178
+ if (result.status === "requested") {
8179
+ await ensureConversationWake({
8180
+ conversationId: args.conversationId,
8181
+ conversationStore: args.conversationStore,
8182
+ idempotencyKey: `api-stop:${args.conversationId}:${result.runId}`,
8183
+ nowMs,
8184
+ queue: args.queue,
8185
+ replaceExistingWake: true,
8186
+ state: args.state
8187
+ });
8165
8188
  }
8189
+ return result;
8166
8190
  }
8167
- function promptText(prompt) {
8168
- if (prompt.length === 0) {
8169
- throw acp.RequestError.invalidParams(
8170
- { field: "prompt" },
8171
- "Junior accepts one or more text blocks only"
8172
- );
8173
- }
8174
- const blocks = [];
8175
- for (const block of prompt) {
8176
- if (block.type !== "text") {
8177
- throw acp.RequestError.invalidParams(
8178
- { field: "prompt" },
8179
- "Junior accepts one or more text blocks only"
8180
- );
8181
- }
8182
- if (!block.text.trim()) {
8183
- throw acp.RequestError.invalidParams(
8184
- { field: "prompt" },
8185
- "Junior accepts non-empty text blocks only"
8186
- );
8187
- }
8188
- blocks.push(block.text);
8191
+
8192
+ // src/chat/api-turns/adapter-conversations.ts
8193
+ var EVENT_PAGE_SIZE = 50;
8194
+ function actorFromUser(user) {
8195
+ return webActorFromEmail(
8196
+ user.email,
8197
+ user.displayName ? { fullName: user.displayName } : void 0
8198
+ );
8199
+ }
8200
+ async function hasConversationAccess(conversationId, user) {
8201
+ const access = (await readConversationAccessFromSql(getDb(), [conversationId], user)).get(conversationId);
8202
+ return Boolean(access?.isParticipant);
8203
+ }
8204
+ async function turnTerminalIsReady(args) {
8205
+ const conversation = await getConversation({
8206
+ conversationId: args.conversationId,
8207
+ state: args.state
8208
+ });
8209
+ if (conversation?.execution.pendingMessages.some(
8210
+ (message) => message.inboundMessageId === args.messageId
8211
+ )) {
8212
+ return false;
8189
8213
  }
8190
- const text = blocks.join("\n");
8191
- if (text.length > MAX_PROMPT_TEXT_LENGTH) {
8192
- throw acp.RequestError.invalidParams(
8193
- { field: "prompt" },
8194
- `Junior accepts at most ${MAX_PROMPT_TEXT_LENGTH} prompt characters`
8195
- );
8214
+ if (!conversation || !hasRunnableConversationWork(conversation)) {
8215
+ return true;
8196
8216
  }
8197
- return text;
8198
- }
8199
- function requestIdKey(requestId) {
8200
- if (requestId === null) return "null";
8201
- return `${typeof requestId}:${requestId}`;
8217
+ const later = await args.eventStore.query(args.conversationId, {
8218
+ afterSeq: args.terminalSeq,
8219
+ limit: 1,
8220
+ types: ["turn_started"]
8221
+ });
8222
+ return later.events.some(
8223
+ (event) => event.data.type === "turn_started" && event.data.turnId !== args.turnId
8224
+ );
8202
8225
  }
8203
- async function latestEventSeq(eventStore, conversationId) {
8226
+ async function latestEventCursor(eventStore, conversationId) {
8204
8227
  const page = await eventStore.query(conversationId, { limit: 1 });
8205
8228
  return page.events.at(-1)?.seq ?? 0;
8206
8229
  }
8207
- async function replaySession(eventStore, sessionId, client) {
8208
- const history = await eventStore.loadMessageHistory(sessionId);
8209
- for (const message of projectConversationMessages(history)) {
8210
- if (message.role === "system") continue;
8211
- await client.notify(acp.methods.client.session.update, {
8212
- sessionId,
8213
- update: {
8214
- sessionUpdate: message.role === "user" ? "user_message_chunk" : "agent_message_chunk",
8215
- content: { type: "text", text: message.text },
8216
- messageId: message.id
8217
- }
8218
- });
8219
- }
8220
- }
8221
- async function waitForTurn(args) {
8222
- let cursor = args.afterSeq;
8223
- const assistantPrefix = `${args.turnId}:assistant:`;
8224
- const sentMessageIds = /* @__PURE__ */ new Set();
8225
- while (true) {
8226
- if (args.signal.aborted) {
8227
- throw acp.RequestError.requestCancelled();
8228
- }
8229
- const page = await args.eventStore.query(args.sessionId, {
8230
- afterSeq: cursor,
8231
- limit: EVENT_PAGE_SIZE,
8232
- types: ["message", "turn_completed", "turn_failed"]
8233
- });
8234
- if (page.events.length === 0) {
8235
- try {
8236
- await sleep(EVENT_POLL_INTERVAL_MS, args.signal);
8237
- } catch (error) {
8238
- if (args.signal.aborted) {
8239
- throw acp.RequestError.requestCancelled();
8240
- }
8241
- throw error;
8242
- }
8243
- continue;
8244
- }
8245
- for (const event of page.events) {
8246
- cursor = event.seq;
8247
- const data = event.data;
8248
- if (data.type === "message" && data.role === "assistant" && data.messageId.startsWith(assistantPrefix) && !sentMessageIds.has(data.messageId)) {
8249
- sentMessageIds.add(data.messageId);
8250
- await args.client.notify(acp.methods.client.session.update, {
8251
- sessionId: args.sessionId,
8252
- update: {
8253
- sessionUpdate: "agent_message_chunk",
8254
- content: { type: "text", text: data.text },
8255
- messageId: data.messageId
8256
- }
8257
- });
8258
- continue;
8259
- }
8260
- if (data.type === "turn_completed" && data.turnId === args.turnId) {
8261
- args.cancellation.finish(args.sessionId, args.cancellationSignal);
8262
- return {
8263
- stopReason: data.outcome === "cancelled" ? "cancelled" : "end_turn"
8264
- };
8265
- }
8266
- if (data.type === "turn_failed" && data.turnId === args.turnId) {
8267
- args.cancellation.finish(args.sessionId, args.cancellationSignal);
8268
- throw acp.RequestError.internalError(
8269
- { failureCode: data.failureCode },
8270
- "Junior Turn failed"
8271
- );
8230
+ function createAdapterConversations(options) {
8231
+ return {
8232
+ async cancel({ conversationId, user }) {
8233
+ if (!await hasConversationAccess(conversationId, user)) {
8234
+ return "not_found";
8272
8235
  }
8273
- }
8274
- }
8275
- }
8276
- async function runAcpOperation(authenticated, operation, conversationId, callback) {
8277
- const name = `acp.${operation}`;
8278
- return await withSpan(
8279
- name,
8280
- name,
8281
- {
8282
- actorId: authenticated.actor.userId,
8283
- conversationId,
8284
- platform: "acp",
8285
- userId: authenticated.actor.userId
8236
+ await stopApiConversationTurn({
8237
+ conversationId,
8238
+ conversationStore: options.conversationStore,
8239
+ queue: options.queue,
8240
+ state: options.state
8241
+ });
8242
+ return "cancelled";
8286
8243
  },
8287
- async () => {
8288
- try {
8289
- return await callback();
8290
- } catch (error) {
8291
- if (!(error instanceof acp.RequestError)) {
8292
- logException(error, `${name}.failed`);
8293
- }
8294
- throw error;
8295
- }
8296
- }
8297
- );
8298
- }
8299
- function createActorAgent(authenticated, connectionNonce, options) {
8300
- const conversationStore = options.conversationStore ?? getConversationStore();
8301
- const eventStore = getConversationEventStore();
8302
- return acp.agent({ name: "junior" }).onRequest(
8303
- acp.methods.agent.initialize,
8304
- () => runAcpOperation(authenticated, "initialize", void 0, () => ({
8305
- protocolVersion: acp.PROTOCOL_VERSION,
8306
- agentCapabilities: {
8307
- loadSession: true,
8308
- promptCapabilities: {
8309
- image: false,
8310
- audio: false,
8311
- embeddedContext: false
8312
- }
8313
- },
8314
- authMethods: [],
8315
- agentInfo: { name: "junior", version: JUNIOR_VERSION }
8316
- }))
8317
- ).onRequest(acp.methods.agent.session.new, async (context) => {
8318
- return await runAcpOperation(
8319
- authenticated,
8320
- "session_new",
8321
- void 0,
8322
- async () => {
8323
- rejectUnsupportedMcpServers(context.params.mcpServers);
8324
- const sessionId = `${ACP_CONVERSATION_PREFIX}${randomUUID()}`;
8325
- await recordApiConversationActivity({
8326
- actor: authenticated.actor,
8327
- conversationId: sessionId,
8328
- conversationStore,
8329
- nowMs: Date.now(),
8330
- rootVisibility: "private"
8331
- });
8332
- return { sessionId };
8333
- }
8334
- );
8335
- }).onRequest(acp.methods.agent.session.load, async (context) => {
8336
- return await runAcpOperation(
8337
- authenticated,
8338
- "session_load",
8339
- context.params.sessionId,
8340
- async () => {
8341
- rejectUnsupportedMcpServers(context.params.mcpServers);
8342
- await requireOwnedSession(
8343
- context.params.sessionId,
8344
- authenticated.user
8345
- );
8346
- await replaySession(
8347
- eventStore,
8348
- context.params.sessionId,
8349
- context.client
8350
- );
8351
- return {};
8244
+ async create({ conversationId, user }) {
8245
+ await recordApiConversationActivity({
8246
+ actor: actorFromUser(user),
8247
+ conversationId,
8248
+ conversationStore: options.conversationStore,
8249
+ nowMs: Date.now(),
8250
+ rootVisibility: "private"
8251
+ });
8252
+ },
8253
+ async hasAccess({ conversationId, user }) {
8254
+ return await hasConversationAccess(conversationId, user);
8255
+ },
8256
+ async prompt({ conversationId, idempotencyKey, text, user }) {
8257
+ if (!await hasConversationAccess(conversationId, user)) {
8258
+ return { status: "not_found" };
8352
8259
  }
8353
- );
8354
- }).onRequest(acp.methods.agent.session.prompt, async (context) => {
8355
- return await runAcpOperation(
8356
- authenticated,
8357
- "session_prompt",
8358
- context.params.sessionId,
8359
- async () => {
8360
- await requireOwnedSession(
8361
- context.params.sessionId,
8362
- authenticated.user
8363
- );
8364
- const text = promptText(context.params.prompt);
8365
- const currentSeq = await latestEventSeq(
8366
- eventStore,
8367
- context.params.sessionId
8368
- );
8369
- const cancellationSignal = options.cancellation.begin(
8370
- context.params.sessionId
8371
- );
8372
- if (!cancellationSignal) {
8373
- throw acp.RequestError.invalidParams(
8374
- { field: "sessionId" },
8375
- "This ACP session already has an active prompt"
8376
- );
8377
- }
8378
- const recordDisconnect = () => options.cancellation.disconnect(
8379
- context.params.sessionId,
8380
- cancellationSignal
8381
- );
8382
- if (context.signal.aborted) {
8383
- recordDisconnect();
8384
- } else {
8385
- context.signal.addEventListener("abort", recordDisconnect, {
8386
- once: true
8387
- });
8388
- }
8389
- let accepted;
8390
- try {
8391
- accepted = await appendAndEnqueueApiConversationMessage(
8392
- {
8393
- actor: authenticated.actor,
8394
- conversationId: context.params.sessionId,
8395
- idempotencyKey: `${connectionNonce}:${requestIdKey(context.requestId)}`,
8396
- message: text
8397
- },
8398
- {
8399
- conversationStore,
8400
- queue: options.queue,
8401
- state: options.state
8402
- }
8403
- );
8404
- } catch (error) {
8405
- options.cancellation.finish(
8406
- context.params.sessionId,
8407
- cancellationSignal
8408
- );
8409
- throw error;
8410
- }
8411
- const afterSeq = accepted.status === "duplicate" ? 0 : currentSeq;
8412
- return await waitForTurn({
8413
- afterSeq,
8414
- cancellation: options.cancellation,
8415
- cancellationSignal,
8416
- client: context.client,
8417
- eventStore,
8418
- sessionId: context.params.sessionId,
8419
- signal: context.signal,
8420
- turnId: apiTurnIdForMessage(accepted.messageId)
8421
- });
8260
+ const currentCursor = await latestEventCursor(
8261
+ options.eventStore,
8262
+ conversationId
8263
+ );
8264
+ if (await getAuthPausedApiTurnId(conversationId)) {
8265
+ return { status: "active" };
8422
8266
  }
8423
- );
8424
- }).onNotification(acp.methods.agent.session.cancel, async (context) => {
8425
- await runAcpOperation(
8426
- authenticated,
8427
- "session_cancel",
8428
- context.params.sessionId,
8429
- async () => {
8430
- await requireOwnedSession(
8431
- context.params.sessionId,
8432
- authenticated.user
8433
- );
8434
- if (!options.cancellation.cancel(context.params.sessionId)) {
8435
- return;
8436
- }
8437
- const nowMs = Date.now();
8438
- await ensureConversationWake({
8439
- conversationId: context.params.sessionId,
8440
- conversationStore,
8441
- idempotencyKey: `acp-cancel:${context.params.sessionId}:${nowMs}`,
8442
- nowMs,
8267
+ const admission = await appendAndEnqueueApiConversationMessage(
8268
+ {
8269
+ actor: actorFromUser(user),
8270
+ conversationId,
8271
+ idempotencyKey,
8272
+ message: text
8273
+ },
8274
+ {
8275
+ conversationStore: options.conversationStore,
8276
+ exclusive: true,
8443
8277
  queue: options.queue,
8444
- replaceExistingWake: true,
8445
8278
  state: options.state
8446
- });
8447
- }
8448
- );
8449
- });
8450
- }
8451
- function isJsonRpcId(value) {
8452
- return value === null || typeof value === "string" || typeof value === "number" && Number.isFinite(value);
8453
- }
8454
- async function hasSafeAcpEnvelope(request) {
8455
- if (request.method !== "POST") return true;
8456
- let value;
8457
- try {
8458
- value = await request.clone().json();
8459
- } catch {
8460
- return true;
8461
- }
8462
- if (typeof value !== "object" || value === null || Array.isArray(value)) {
8463
- return true;
8464
- }
8465
- if (!("jsonrpc" in value) || value.jsonrpc !== "2.0") {
8466
- return false;
8467
- }
8468
- if (!("method" in value) || typeof value.method !== "string") {
8469
- return false;
8470
- }
8471
- return !("id" in value) || isJsonRpcId(value.id);
8472
- }
8473
- async function initializationSucceeded(response) {
8474
- try {
8475
- const value = await response.clone().json();
8476
- return typeof value === "object" && value !== null && "result" in value;
8477
- } catch {
8478
- return false;
8479
- }
8480
- }
8481
- function createAcpHttpHandler(options) {
8482
- const server = new AcpServer({ agent: acp.agent({ name: "junior" }) });
8483
- const connectionActors = /* @__PURE__ */ new Map();
8484
- return async (request) => {
8485
- const authenticated = await authenticateRequest(request);
8486
- if (!authenticated) {
8487
- return new Response("Unauthorized", { status: 401 });
8488
- }
8489
- const connectionId = request.headers.get(ACP_CONNECTION_ID_HEADER);
8490
- const connectionActorId = connectionId ? connectionActors.get(connectionId) : void 0;
8491
- if (connectionId && connectionActorId === void 0) {
8492
- return new Response("Unknown Acp-Connection-Id", { status: 404 });
8493
- }
8494
- if (connectionActorId !== void 0 && connectionActorId !== authenticated.actor.userId) {
8495
- return new Response("Unknown Acp-Connection-Id", { status: 404 });
8496
- }
8497
- if (!await hasSafeAcpEnvelope(request)) {
8498
- return new Response("Invalid JSON-RPC message", { status: 400 });
8499
- }
8500
- const response = await server.handleRequest(request, {
8501
- agent: createActorAgent(authenticated, randomUUID(), options)
8502
- });
8503
- const responseConnectionId = response.headers.get(ACP_CONNECTION_ID_HEADER);
8504
- if (response.ok && responseConnectionId && !connectionId) {
8505
- if (await initializationSucceeded(response)) {
8506
- connectionActors.set(responseConnectionId, authenticated.actor.userId);
8507
- } else {
8508
- await server.handleRequest(
8509
- new Request(request.url, {
8510
- method: "DELETE",
8511
- headers: { [ACP_CONNECTION_ID_HEADER]: responseConnectionId }
8512
- })
8513
- );
8514
- const headers = new Headers(response.headers);
8515
- headers.delete(ACP_CONNECTION_ID_HEADER);
8516
- return new Response(response.body, {
8517
- headers,
8518
- status: response.status,
8519
- statusText: response.statusText
8520
- });
8279
+ }
8280
+ );
8281
+ if (admission.status === "active") return { status: "active" };
8282
+ return {
8283
+ afterCursor: admission.status === "duplicate" ? 0 : currentCursor,
8284
+ messageId: admission.messageId,
8285
+ status: "accepted",
8286
+ turnId: apiTurnIdForMessage(admission.messageId)
8287
+ };
8288
+ },
8289
+ async readMessages(conversationId) {
8290
+ const history = await options.eventStore.loadMessageHistory(conversationId);
8291
+ return projectConversationMessages(history).filter(
8292
+ (message) => message.role !== "system"
8293
+ ).map(({ id, role, text }) => ({ id, role, text }));
8294
+ },
8295
+ async readTurn({ afterCursor, conversationId, messageId, turnId }) {
8296
+ const page = await options.eventStore.query(conversationId, {
8297
+ afterSeq: afterCursor,
8298
+ limit: EVENT_PAGE_SIZE,
8299
+ types: ["message", "turn_completed", "turn_failed"]
8300
+ });
8301
+ const cursor = page.events.at(-1)?.seq ?? afterCursor;
8302
+ const assistantPrefix = `${turnId}:assistant:`;
8303
+ const messages = page.events.flatMap((event) => {
8304
+ const data = event.data;
8305
+ return data.type === "message" && data.role === "assistant" && data.messageId.startsWith(assistantPrefix) ? [
8306
+ {
8307
+ id: data.messageId,
8308
+ role: "assistant",
8309
+ text: data.text
8310
+ }
8311
+ ] : [];
8312
+ });
8313
+ let terminal;
8314
+ const terminalEvent = await options.eventStore.loadByIdempotencyKey(
8315
+ conversationId,
8316
+ `turn:${turnId}:terminal`
8317
+ );
8318
+ if (terminalEvent && terminalEvent.seq <= cursor && await turnTerminalIsReady({
8319
+ conversationId,
8320
+ eventStore: options.eventStore,
8321
+ messageId,
8322
+ state: options.state,
8323
+ terminalSeq: terminalEvent.seq,
8324
+ turnId
8325
+ })) {
8326
+ const data = terminalEvent.data;
8327
+ if (data.type === "turn_completed" && data.turnId === turnId) {
8328
+ terminal = {
8329
+ outcome: data.outcome === "cancelled" ? "cancelled" : "completed",
8330
+ status: "completed"
8331
+ };
8332
+ } else if (data.type === "turn_failed" && data.turnId === turnId) {
8333
+ terminal = { failureCode: data.failureCode, status: "failed" };
8334
+ }
8521
8335
  }
8336
+ const turnPage = { cursor, messages };
8337
+ if (terminal) turnPage.terminal = terminal;
8338
+ return turnPage;
8522
8339
  }
8523
- if (request.method === "DELETE" && response.ok && connectionId) {
8524
- connectionActors.delete(connectionId);
8525
- }
8526
- return response;
8527
8340
  };
8528
8341
  }
8529
8342
 
8530
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
+ }
8531
8351
  var DASHBOARD_PACKAGE_NAME = "@sentry/junior-dashboard";
8532
8352
  async function defaultWaitUntil() {
8533
8353
  try {
@@ -8625,6 +8445,7 @@ async function createDashboardRouteRegistrations(args) {
8625
8445
  }
8626
8446
  const createDashboardApp = args.createDashboardApp ?? await loadDashboardAppFactory();
8627
8447
  return dashboardRouteRegistrations({
8448
+ authenticatedRoutes: args.authenticatedRoutes,
8628
8449
  dashboard: args.dashboard,
8629
8450
  createDashboardApp,
8630
8451
  pluginRoutes: args.pluginRoutes
@@ -8672,7 +8493,7 @@ function normalizeDashboardPath(path, fallback) {
8672
8493
  const withSlash = value.startsWith("/") ? value : `/${value}`;
8673
8494
  return stripTrailingSlashes(withSlash);
8674
8495
  }
8675
- function dashboardHostRoutePaths(dashboard) {
8496
+ function dashboardHostRoutePaths(dashboard, authenticatedRoutes = []) {
8676
8497
  const basePath = normalizeDashboardPath(dashboard.basePath, "/");
8677
8498
  const authPath = normalizeDashboardPath(dashboard.authPath, "/api/auth");
8678
8499
  const pagePath = (suffix) => basePath === "/" ? `/${suffix}` : `${basePath}/${suffix}`;
@@ -8732,6 +8553,7 @@ function dashboardHostRoutePaths(dashboard) {
8732
8553
  "/api/workspaces/*",
8733
8554
  "/api/config",
8734
8555
  "/api/me",
8556
+ ...authenticatedRoutes.map((route) => route.path),
8735
8557
  authPath,
8736
8558
  `${authPath}/*`
8737
8559
  ];
@@ -8782,35 +8604,43 @@ function routePatternOverlaps(ownedPath, routePath) {
8782
8604
  (example) => routePatternMatchesConcretePath(routePath, example)
8783
8605
  );
8784
8606
  }
8785
- function dashboardOwnedRoutePath(routePath, dashboard) {
8786
- return dashboardHostRoutePaths(dashboard).some(
8607
+ function dashboardOwnedRoutePath(routePath, dashboard, authenticatedRoutes = []) {
8608
+ return dashboardHostRoutePaths(dashboard, authenticatedRoutes).some(
8787
8609
  (path) => routePatternOverlaps(path, routePath)
8788
8610
  );
8789
8611
  }
8790
8612
  function dashboardRouteRegistrations(args) {
8791
8613
  let app;
8792
8614
  const fetch2 = (request) => {
8793
- app ??= args.createDashboardApp({
8615
+ const dashboardOptions = {
8794
8616
  ...args.dashboard,
8795
8617
  agentName: botConfig.userName,
8618
+ authenticatedRoutes: args.authenticatedRoutes,
8796
8619
  pluginRoutes: args.pluginRoutes
8797
- });
8620
+ };
8621
+ app ??= args.createDashboardApp(dashboardOptions);
8798
8622
  if (!app || typeof app.fetch !== "function") {
8799
8623
  throw new Error("createDashboardApp() must return an app with fetch()");
8800
8624
  }
8801
8625
  return app.fetch(request);
8802
8626
  };
8803
- return dashboardHostRoutePaths(args.dashboard).map((path) => ({
8804
- handler: fetch2,
8805
- path
8806
- }));
8627
+ return dashboardHostRoutePaths(args.dashboard, args.authenticatedRoutes).map(
8628
+ (path) => ({
8629
+ handler: fetch2,
8630
+ path
8631
+ })
8632
+ );
8807
8633
  }
8808
8634
  function validateDashboardRouteOwnership(args) {
8809
8635
  if (!args.dashboard || args.dashboard.disabled) {
8810
8636
  return;
8811
8637
  }
8812
8638
  for (const route of args.routes) {
8813
- if (dashboardOwnedRoutePath(route.path, args.dashboard)) {
8639
+ if (dashboardOwnedRoutePath(
8640
+ route.path,
8641
+ args.dashboard,
8642
+ args.authenticatedRoutes
8643
+ )) {
8814
8644
  throw new Error(
8815
8645
  `Plugin "${route.pluginName}" route "${route.path}" conflicts with core dashboard routes`
8816
8646
  );
@@ -8820,11 +8650,11 @@ function validateDashboardRouteOwnership(args) {
8820
8650
  function mountRoutes(app, routes) {
8821
8651
  for (const route of routes) {
8822
8652
  const handler = (c) => route.handler(c.req.raw);
8823
- const methods2 = Array.isArray(route.method) ? route.method : [route.method ?? "ALL"];
8824
- const explicitMethods = methods2.filter(
8653
+ const methods = typeof route.method === "string" ? [route.method] : route.method ?? ["ALL"];
8654
+ const explicitMethods = methods.filter(
8825
8655
  (method) => method !== "ALL"
8826
8656
  );
8827
- if (methods2.includes("ALL")) {
8657
+ if (methods.includes("ALL")) {
8828
8658
  app.all(route.path, handler);
8829
8659
  } else if (explicitMethods.length > 0) {
8830
8660
  app.on(explicitMethods, route.path, handler);
@@ -8858,6 +8688,17 @@ async function createApp(options) {
8858
8688
  const previousSandboxResources = getSandboxResourceConfig();
8859
8689
  const previousExperimentalFeatures = getExperimentalFeatures();
8860
8690
  const previousDashboardLinkOptions = setDashboardConversationLinkOptions(dashboard);
8691
+ const restoreRuntimeConfig = () => {
8692
+ pluginCatalogRuntime.setConfig(previousPluginCatalogConfig);
8693
+ setPlugins(previousPlugins);
8694
+ setConfigDefaults(previousConfigDefaults);
8695
+ botConfig.defaultProfile = previousDefaultProfile;
8696
+ botConfig.profiles = previousModelProfiles;
8697
+ setSlackReactionConfig(previousSlackReactionConfig);
8698
+ setSandboxResourceConfig(previousSandboxResources);
8699
+ setExperimentalFeatures(previousExperimentalFeatures);
8700
+ setDashboardConversationLinkOptions(previousDashboardLinkOptions);
8701
+ };
8861
8702
  let pluginRoutes = [];
8862
8703
  let pluginApiRoutes = [];
8863
8704
  const resolveResourceEventTeamId = createResourceEventTeamIdResolver();
@@ -8909,20 +8750,11 @@ async function createApp(options) {
8909
8750
  );
8910
8751
  }
8911
8752
  pluginRoutes = getPluginRoutes({ resourceEvents });
8912
- validateDashboardRouteOwnership({ dashboard, routes: pluginRoutes });
8913
8753
  if (dashboard && !dashboard.disabled) {
8914
8754
  pluginApiRoutes = getPluginApiRoutes();
8915
8755
  }
8916
8756
  } catch (error) {
8917
- pluginCatalogRuntime.setConfig(previousPluginCatalogConfig);
8918
- setPlugins(previousPlugins);
8919
- setConfigDefaults(previousConfigDefaults);
8920
- botConfig.defaultProfile = previousDefaultProfile;
8921
- botConfig.profiles = previousModelProfiles;
8922
- setSlackReactionConfig(previousSlackReactionConfig);
8923
- setSandboxResourceConfig(previousSandboxResources);
8924
- setExperimentalFeatures(previousExperimentalFeatures);
8925
- setDashboardConversationLinkOptions(previousDashboardLinkOptions);
8757
+ restoreRuntimeConfig();
8926
8758
  throw error;
8927
8759
  }
8928
8760
  const waitUntil = options?.waitUntil ?? await defaultWaitUntil();
@@ -8938,6 +8770,54 @@ async function createApp(options) {
8938
8770
  replyExecutor: { agentRunner },
8939
8771
  sandbox: { tracePropagation }
8940
8772
  };
8773
+ let conversationWorkOptions;
8774
+ const getConversationWorkOptions = () => {
8775
+ conversationWorkOptions ??= options?.conversationWork ?? createProductionConversationWorkOptions({
8776
+ agentRunner,
8777
+ services: runtimeServiceOverrides
8778
+ });
8779
+ return conversationWorkOptions;
8780
+ };
8781
+ let adapterRoutes = [];
8782
+ let authenticatedRoutes = [];
8783
+ try {
8784
+ const adapterOutputs = await Promise.all(
8785
+ (options?.adapters ?? []).map(async (adapter) => {
8786
+ const work = getConversationWorkOptions();
8787
+ const state = work.state ?? getStateAdapter();
8788
+ return await adapter({
8789
+ agentName: botConfig.userName,
8790
+ baseURL: resolveAdapterBaseURL(dashboard),
8791
+ conversations: createAdapterConversations({
8792
+ conversationStore: work.conversationStore,
8793
+ eventStore: getConversationEventStore(),
8794
+ queue: work.queue ?? getVercelConversationWorkQueue(),
8795
+ state
8796
+ }),
8797
+ reportError: (error, event, attributes) => void logException(error, event, attributes),
8798
+ state,
8799
+ version: JUNIOR_VERSION
8800
+ });
8801
+ })
8802
+ );
8803
+ adapterRoutes = adapterOutputs.flatMap((output) => output.routes ?? []);
8804
+ authenticatedRoutes = adapterOutputs.flatMap(
8805
+ (output) => output.authenticatedRoutes ?? []
8806
+ );
8807
+ if (authenticatedRoutes.length > 0 && (!dashboard || dashboard.disabled)) {
8808
+ throw new Error(
8809
+ "createApp() adapters with authenticated routes require an enabled dashboard"
8810
+ );
8811
+ }
8812
+ validateDashboardRouteOwnership({
8813
+ authenticatedRoutes,
8814
+ dashboard,
8815
+ routes: pluginRoutes
8816
+ });
8817
+ } catch (error) {
8818
+ restoreRuntimeConfig();
8819
+ throw error;
8820
+ }
8941
8821
  const slackWebhookServices = createProductionSlackWebhookServices({
8942
8822
  services: runtimeServiceOverrides
8943
8823
  });
@@ -8954,9 +8834,11 @@ async function createApp(options) {
8954
8834
  );
8955
8835
  });
8956
8836
  mountRoutes(app, pluginRoutes);
8837
+ mountRoutes(app, adapterRoutes);
8957
8838
  mountRoutes(
8958
8839
  app,
8959
8840
  await createDashboardRouteRegistrations({
8841
+ authenticatedRoutes,
8960
8842
  dashboard,
8961
8843
  createDashboardApp: virtualConfig?.createDashboardApp,
8962
8844
  pluginRoutes: pluginApiRoutes
@@ -8994,32 +8876,6 @@ async function createApp(options) {
8994
8876
  });
8995
8877
  let agentContinuePOST;
8996
8878
  let pluginTaskPOST;
8997
- let conversationWorkOptions;
8998
- const getConversationWorkOptions = () => {
8999
- conversationWorkOptions ??= options?.conversationWork ?? createProductionConversationWorkOptions({
9000
- agentRunner,
9001
- services: runtimeServiceOverrides
9002
- });
9003
- return conversationWorkOptions;
9004
- };
9005
- if (isExperimentalFeatureEnabled("acp")) {
9006
- const work = getConversationWorkOptions();
9007
- const cancellation = work.apiTurnCancellation;
9008
- if (!cancellation) {
9009
- throw new Error("Experimental ACP requires API Turn cancellation wiring");
9010
- }
9011
- const handleAcpRequest = createAcpHttpHandler({
9012
- cancellation,
9013
- conversationStore: work.conversationStore,
9014
- queue: work.queue ?? getVercelConversationWorkQueue(),
9015
- state: work.state
9016
- });
9017
- app.on(
9018
- ["GET", "POST", "DELETE"],
9019
- "/api/acp",
9020
- (c) => handleAcpRequest(c.req.raw)
9021
- );
9022
- }
9023
8879
  if (process.env.NODE_ENV === "development") {
9024
8880
  registerVercelConversationWorkDevConsumer(getConversationWorkOptions());
9025
8881
  registerVercelPluginTaskDevConsumer();