@sentry/junior 0.182.0 → 0.184.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 (98) hide show
  1. package/dist/{agent-hooks-3S25UIHO.js → agent-hooks-XTZVALGQ.js} +11 -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 +97 -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 +35 -0
  12. package/dist/api/code/routes.d.ts +4 -0
  13. package/dist/api/schema/code.d.ts +75 -0
  14. package/dist/api/schema.d.ts +2 -0
  15. package/dist/api/schema.js +9 -1
  16. package/dist/api.js +258 -134
  17. package/dist/app.d.ts +0 -4
  18. package/dist/app.js +2171 -220
  19. package/dist/{catalog-runtime-ESLMBQBV.js → catalog-runtime-5357XR4F.js} +4 -4
  20. package/dist/chat/agent-invocations/store.d.ts +4 -2
  21. package/dist/chat/agent-invocations/work.d.ts +1 -3
  22. package/dist/chat/app/factory.d.ts +3 -3
  23. package/dist/chat/app/services.d.ts +3 -3
  24. package/dist/chat/code/publisher.d.ts +3 -0
  25. package/dist/chat/code/store.d.ts +9 -0
  26. package/dist/chat/event-tasks/store.d.ts +10 -0
  27. package/dist/chat/ingress/slack-webhook.d.ts +1 -1
  28. package/dist/chat/plugins/agent-hooks.d.ts +5 -0
  29. package/dist/chat/{runtime/slack-runtime.d.ts → providers/slack/runtime.d.ts} +2 -2
  30. package/dist/chat/{runtime/reply-executor.d.ts → providers/slack/turn.d.ts} +10 -9
  31. package/dist/chat/resource-events/store.d.ts +12 -0
  32. package/dist/chat/runtime/turn-execution.d.ts +30 -0
  33. package/dist/chat/slack/dispatch-turn.d.ts +12 -14
  34. package/dist/chat/task-execution/slack-work.d.ts +1 -1
  35. package/dist/{chunk-SXOZIG5X.js → chunk-2ZQPVX7C.js} +6 -6
  36. package/dist/{chunk-UQP54CXZ.js → chunk-3S7ULUWW.js} +1 -1
  37. package/dist/{chunk-2CEOXDG2.js → chunk-4CNDGK25.js} +174 -6
  38. package/dist/{chunk-3THPTNN6.js → chunk-4RONS3WR.js} +4 -4
  39. package/dist/{chunk-LHQK5XCU.js → chunk-6UDPINOX.js} +2 -2
  40. package/dist/{chunk-BZD4ADQH.js → chunk-6VOYP4M6.js} +2 -3
  41. package/dist/{chunk-QHALFHZA.js → chunk-6WGC3SO4.js} +9 -9
  42. package/dist/{chunk-BDDWVY32.js → chunk-ACPDVEJO.js} +22 -20
  43. package/dist/{chunk-44BQ3TSK.js → chunk-BCHYQS4B.js} +233 -173
  44. package/dist/{chunk-SVFSYQMT.js → chunk-BHYRYYLY.js} +1 -1
  45. package/dist/{chunk-LT4QYGKM.js → chunk-BT53RIGJ.js} +160 -97
  46. package/dist/{chunk-Z7ALSQS3.js → chunk-DUKIDPF6.js} +1 -1
  47. package/dist/{chunk-PQES5LB5.js → chunk-E57WPXWY.js} +5 -5
  48. package/dist/{chunk-GGYA5NPN.js → chunk-FKEZZMXS.js} +8 -8
  49. package/dist/{chunk-LB4FWMCV.js → chunk-HIPUIPJM.js} +183 -155
  50. package/dist/{chunk-3NFHI2NA.js → chunk-HUXFMDBZ.js} +1 -1
  51. package/dist/{chunk-6TSWXBTQ.js → chunk-IZDYYYGP.js} +3 -3
  52. package/dist/{chunk-YHL2L2ZZ.js → chunk-JA43DV4Y.js} +1 -1
  53. package/dist/{chunk-V5UZCCNW.js → chunk-JM2COLGC.js} +6 -6
  54. package/dist/{chunk-A7THOQ6N.js → chunk-JNO2GUDS.js} +21 -21
  55. package/dist/{chunk-KGMHUDVF.js → chunk-K7VDC2QW.js} +6 -6
  56. package/dist/{chunk-CVBIM4Z3.js → chunk-LCP4EDKR.js} +1 -1
  57. package/dist/{chunk-4DZ62L36.js → chunk-LHXIZLFF.js} +1 -1
  58. package/dist/{chunk-D6OHGAIO.js → chunk-M5YWNMTE.js} +5 -5
  59. package/dist/{chunk-3ENUMZNG.js → chunk-MAPJFUMV.js} +3 -3
  60. package/dist/{chunk-UUMBHORH.js → chunk-MLSQX2EX.js} +1 -1
  61. package/dist/{chunk-WOIALVN2.js → chunk-PJPRXO2T.js} +1 -1
  62. package/dist/{chunk-VHMAFMHB.js → chunk-QABCY33S.js} +1 -1
  63. package/dist/{chunk-UKIHNBBD.js → chunk-SHJK75Z2.js} +3 -3
  64. package/dist/{chunk-S35OZMSL.js → chunk-UBOIHNTH.js} +13 -13
  65. package/dist/{chunk-DZR3L74W.js → chunk-UF6BUA5W.js} +3 -3
  66. package/dist/{chunk-JIOKUIAS.js → chunk-WQFEWULF.js} +2 -2
  67. package/dist/chunk-WVIA7V3M.js +975 -0
  68. package/dist/{chunk-MJISSOWB.js → chunk-YKX6MTLU.js} +1 -1
  69. package/dist/{chunk-TEFNHMO2.js → chunk-Z3QAXJR7.js} +4 -4
  70. package/dist/cli/chat.js +33 -35
  71. package/dist/cli/check.js +5 -5
  72. package/dist/cli/plugins.js +12 -12
  73. package/dist/cli/snapshot-create.js +9 -9
  74. package/dist/cli/upgrade.js +6 -6
  75. package/dist/db/schema/code.d.ts +367 -0
  76. package/dist/db/schema.d.ts +367 -1
  77. package/dist/{db-NYIWZ4L5.js → db-MA52H62H.js} +6 -6
  78. package/dist/instrumentation.js +3 -3
  79. package/dist/nitro.js +6 -6
  80. package/dist/{oauth-callback-server-A2YTPVVX.js → oauth-callback-server-DXGHKN6S.js} +25 -25
  81. package/dist/{runner-VKAEU52J.js → runner-4IXUAQNK.js} +18 -18
  82. package/dist/{skills-52GMXUTY.js → skills-X3DY7AXO.js} +5 -5
  83. package/dist/spawn-43WGS6CH.js +37 -0
  84. package/dist/task-runner-VDRKITPV.js +32 -0
  85. package/dist/{validation-VD2YQX2W.js → validation-Z6JXSTJS.js} +5 -5
  86. package/dist/version.js +1 -1
  87. package/dist/{work-ZWBTGEQP.js → work-OKWRFBDW.js} +18 -18
  88. package/dist/worker-B236AE72.js +22 -0
  89. package/migrations/0036_complex_rage.sql +32 -0
  90. package/migrations/meta/0036_snapshot.json +3186 -0
  91. package/migrations/meta/_journal.json +8 -1
  92. package/package.json +6 -7
  93. package/dist/app-adapter.d.ts +0 -85
  94. package/dist/chat/api-turns/adapter-conversations.d.ts +0 -15
  95. package/dist/chunk-CZYNWPWD.js +0 -930
  96. package/dist/spawn-GSS4EBLR.js +0 -37
  97. package/dist/task-runner-DUGDWF5O.js +0 -32
  98. package/dist/worker-XUWD5WS7.js +0 -22
package/dist/app.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import {
2
2
  processConversationWork
3
- } from "./chunk-3THPTNN6.js";
3
+ } from "./chunk-4RONS3WR.js";
4
4
  import {
5
5
  bindSpawnAgent
6
- } from "./chunk-VHMAFMHB.js";
6
+ } from "./chunk-QABCY33S.js";
7
7
  import {
8
8
  createAgentInvocationWorker,
9
9
  enqueueAgentInvocation,
10
10
  listPendingAgentInvocationMailboxAppends,
11
11
  routeAgentInvocationWork
12
- } from "./chunk-LT4QYGKM.js";
12
+ } from "./chunk-BT53RIGJ.js";
13
13
  import {
14
14
  GET as GET2,
15
15
  GET2 as GET3,
@@ -36,7 +36,7 @@ import {
36
36
  startSlackProcessingReaction,
37
37
  stripLeadingBotMention,
38
38
  wakePausedTurn
39
- } from "./chunk-BDDWVY32.js";
39
+ } from "./chunk-ACPDVEJO.js";
40
40
  import "./chunk-FA6POGLH.js";
41
41
  import {
42
42
  GET,
@@ -53,8 +53,8 @@ import {
53
53
  resolveSlackConversationContext,
54
54
  routeApiTurnWork,
55
55
  webActorFromEmail
56
- } from "./chunk-S35OZMSL.js";
57
- import "./chunk-CVBIM4Z3.js";
56
+ } from "./chunk-UBOIHNTH.js";
57
+ import "./chunk-LCP4EDKR.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-D6OHGAIO.js";
68
+ } from "./chunk-M5YWNMTE.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-QHALFHZA.js";
88
+ } from "./chunk-6WGC3SO4.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-PQES5LB5.js";
100
+ } from "./chunk-E57WPXWY.js";
101
101
  import "./chunk-UTL3MHPC.js";
102
102
  import {
103
103
  createVercelPluginTaskCallback,
104
104
  queueCallback,
105
105
  registerVercelPluginTaskDevConsumer
106
- } from "./chunk-DZR3L74W.js";
106
+ } from "./chunk-UF6BUA5W.js";
107
107
  import {
108
108
  validatePluginEgressCredentialHooks,
109
109
  validatePluginRegistrations
110
- } from "./chunk-3NFHI2NA.js";
110
+ } from "./chunk-HUXFMDBZ.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-A7THOQ6N.js";
158
+ } from "./chunk-JNO2GUDS.js";
159
159
  import {
160
160
  AuthorizationFlowDisabledError,
161
161
  PluginCredentialFailureError
162
- } from "./chunk-KGMHUDVF.js";
162
+ } from "./chunk-K7VDC2QW.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-GGYA5NPN.js";
171
+ } from "./chunk-FKEZZMXS.js";
172
172
  import {
173
173
  RetryableDeliveryError
174
174
  } from "./chunk-RF6J3DIO.js";
@@ -176,7 +176,7 @@ import {
176
176
  formatProviderLabel,
177
177
  postSlackMessage,
178
178
  startOAuthFlow
179
- } from "./chunk-V5UZCCNW.js";
179
+ } from "./chunk-JM2COLGC.js";
180
180
  import {
181
181
  getExperimentalFeatures,
182
182
  isExperimentalFeatureEnabled,
@@ -184,6 +184,7 @@ import {
184
184
  } from "./chunk-2LYHWMR4.js";
185
185
  import {
186
186
  collectAttachmentGarbage,
187
+ collectEventTaskMatchKeys,
187
188
  findMatchingEventTasks,
188
189
  getResourceEventCatalog,
189
190
  listScheduledTasks,
@@ -196,10 +197,10 @@ import {
196
197
  scheduledRunId,
197
198
  scheduledRunSchema,
198
199
  withScheduledTaskLock
199
- } from "./chunk-LB4FWMCV.js";
200
+ } from "./chunk-HIPUIPJM.js";
200
201
  import {
201
202
  parseSlackThreadId
202
- } from "./chunk-MJISSOWB.js";
203
+ } from "./chunk-YKX6MTLU.js";
203
204
  import {
204
205
  CONVERSATION_WORK_STALE_ENQUEUE_MS,
205
206
  appendAndEnqueueInboundMessage,
@@ -214,7 +215,7 @@ import {
214
215
  removeActiveConversation,
215
216
  requestConversationWork,
216
217
  stopConversationWork
217
- } from "./chunk-TEFNHMO2.js";
218
+ } from "./chunk-Z3QAXJR7.js";
218
219
  import {
219
220
  ConversationTurnBoundaryError,
220
221
  CooperativeTurnYieldError,
@@ -232,7 +233,7 @@ import {
232
233
  markTurnCompleted,
233
234
  markTurnFailed,
234
235
  startActiveTurn
235
- } from "./chunk-YHL2L2ZZ.js";
236
+ } from "./chunk-JA43DV4Y.js";
236
237
  import {
237
238
  abandonTurnRecord,
238
239
  appendThreadContextMessages,
@@ -268,11 +269,11 @@ import {
268
269
  trimTrailingAssistantMessages,
269
270
  turnHasReply,
270
271
  upsertConversationMessage
271
- } from "./chunk-SXOZIG5X.js";
272
+ } from "./chunk-2ZQPVX7C.js";
272
273
  import {
273
274
  getSandboxResourceConfig,
274
275
  setSandboxResourceConfig
275
- } from "./chunk-3ENUMZNG.js";
276
+ } from "./chunk-MAPJFUMV.js";
276
277
  import "./chunk-PDO5BLNM.js";
277
278
  import "./chunk-UIE3R5XU.js";
278
279
  import {
@@ -291,13 +292,14 @@ import {
291
292
  import "./chunk-7H2VZTWN.js";
292
293
  import {
293
294
  getTurnRequestDeadline
294
- } from "./chunk-UUMBHORH.js";
295
+ } from "./chunk-MLSQX2EX.js";
295
296
  import {
296
297
  SlackActionError,
297
298
  bindEventTaskCredentialSubject,
298
299
  bindScheduledTaskCredentialSubject,
299
300
  bindSlackDirectCredentialSubject,
300
301
  cancelSubscriptions,
302
+ collectResourceEventMatchKeys,
301
303
  createPluginLogger,
302
304
  createPluginState,
303
305
  createResourceEventTeamIdResolver,
@@ -316,7 +318,7 @@ import {
316
318
  verifyEventTaskCredentialSubject,
317
319
  verifyScheduledTaskCredentialSubject,
318
320
  verifySlackDirectCredentialSubject
319
- } from "./chunk-2CEOXDG2.js";
321
+ } from "./chunk-4CNDGK25.js";
320
322
  import "./chunk-3Y47VMHV.js";
321
323
  import {
322
324
  contextProvenance,
@@ -329,7 +331,7 @@ import {
329
331
  requireSlackDestination,
330
332
  withConversationEventLock,
331
333
  withConversationMutationLock
332
- } from "./chunk-6TSWXBTQ.js";
334
+ } from "./chunk-IZDYYYGP.js";
333
335
  import {
334
336
  juniorAgentBindings,
335
337
  juniorAgentInvocations,
@@ -337,13 +339,13 @@ import {
337
339
  juniorConversations,
338
340
  juniorSchedulerRuns,
339
341
  juniorSchedulerTasks
340
- } from "./chunk-44BQ3TSK.js";
341
- import "./chunk-UKIHNBBD.js";
342
+ } from "./chunk-BCHYQS4B.js";
343
+ import "./chunk-SHJK75Z2.js";
342
344
  import {
343
345
  acquireActiveLock,
344
346
  getStateAdapter,
345
347
  withActiveLock
346
- } from "./chunk-Z7ALSQS3.js";
348
+ } from "./chunk-DUKIDPF6.js";
347
349
  import {
348
350
  juniorDestinations
349
351
  } from "./chunk-VFUK3X5B.js";
@@ -364,13 +366,13 @@ import {
364
366
  modelIdForProfile,
365
367
  setProfiles,
366
368
  setSlackReactionConfig
367
- } from "./chunk-LHQK5XCU.js";
368
- import "./chunk-SVFSYQMT.js";
369
- import "./chunk-JIOKUIAS.js";
369
+ } from "./chunk-6UDPINOX.js";
370
+ import "./chunk-BHYRYYLY.js";
371
+ import "./chunk-WQFEWULF.js";
370
372
  import {
371
373
  pluginCatalogRuntime
372
- } from "./chunk-WOIALVN2.js";
373
- import "./chunk-4DZ62L36.js";
374
+ } from "./chunk-PJPRXO2T.js";
375
+ import "./chunk-LHXIZLFF.js";
374
376
  import {
375
377
  credentialContextForActor
376
378
  } from "./chunk-CEMMLV5L.js";
@@ -405,13 +407,13 @@ import {
405
407
  withContext,
406
408
  withLogContext,
407
409
  withSpan
408
- } from "./chunk-UQP54CXZ.js";
410
+ } from "./chunk-3S7ULUWW.js";
409
411
  import {
410
412
  continueTrace
411
413
  } from "./chunk-PEQXYSSE.js";
412
414
  import {
413
415
  JUNIOR_VERSION
414
- } from "./chunk-BZD4ADQH.js";
416
+ } from "./chunk-6VOYP4M6.js";
415
417
 
416
418
  // src/app.ts
417
419
  import { createRequire } from "module";
@@ -696,10 +698,10 @@ async function readDispatchTurnResult(dispatch2) {
696
698
  const session = storedSession ?? summary;
697
699
  const dispatchOutcome = summary?.dispatchOutcome ?? storedSession?.dispatchOutcome;
698
700
  const resultMessageTs = summary?.resultMessageId ?? storedSession?.resultMessageId;
699
- const errorMessage = storedSession?.errorMessage;
701
+ const errorMessage3 = storedSession?.errorMessage;
700
702
  if (dispatchOutcome) {
701
703
  return {
702
- ...errorMessage ? { errorMessage } : void 0,
704
+ ...errorMessage3 ? { errorMessage: errorMessage3 } : void 0,
703
705
  outcome: dispatchOutcome,
704
706
  ...resultMessageTs ? { resultMessageTs } : void 0
705
707
  };
@@ -1272,13 +1274,13 @@ async function skipMissedRun(db, args) {
1272
1274
  return void 0;
1273
1275
  }
1274
1276
  const duplicateOf = await findStaleRecoveryCanonicalTask(db, current);
1275
- 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.";
1277
+ 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.";
1276
1278
  const skipped = {
1277
1279
  id: scheduledRunId(current.id, args.scheduledForMs),
1278
1280
  attempt: 1,
1279
1281
  claimedAtMs: args.nowMs,
1280
1282
  completedAtMs: args.nowMs,
1281
- errorMessage,
1283
+ errorMessage: errorMessage3,
1282
1284
  scheduledForMs: args.scheduledForMs,
1283
1285
  status: "skipped",
1284
1286
  taskId: current.id
@@ -1297,12 +1299,12 @@ async function skipMissedRun(db, args) {
1297
1299
  nextRunAtMs,
1298
1300
  runNowAtMs: isRunNow ? void 0 : current.runNowAtMs,
1299
1301
  status,
1300
- statusReason: status === "deleted" ? errorMessage : void 0,
1302
+ statusReason: status === "deleted" ? errorMessage3 : void 0,
1301
1303
  updatedAtMs: args.nowMs
1302
1304
  },
1303
1305
  current
1304
1306
  );
1305
- return { errorMessage, run: skipped, task: current };
1307
+ return { errorMessage: errorMessage3, run: skipped, task: current };
1306
1308
  }
1307
1309
  async function claimDueScheduledRun(db, args) {
1308
1310
  const skippedRuns = [];
@@ -4208,7 +4210,7 @@ function combineTurnText(queuedMessages, latestText) {
4208
4210
  };
4209
4211
  }
4210
4212
 
4211
- // src/chat/runtime/slack-runtime.ts
4213
+ // src/chat/providers/slack/runtime.ts
4212
4214
  var THREAD_OPTOUT_ACK = "Understood. I'll stay out of this thread unless someone @mentions me again.";
4213
4215
  function shouldRethrowTurnControlError(error) {
4214
4216
  return isCooperativeTurnYieldError(error) || isTurnInputCommitLostError(error) || isTurnInputDeferredError(error) || isProviderRetryError(error);
@@ -4587,7 +4589,7 @@ function createSlackTurnRuntime(deps) {
4587
4589
  }),
4588
4590
  stripLeadingBotMention: deps.stripLeadingBotMention
4589
4591
  });
4590
- await deps.replyToThread(thread, message, {
4592
+ await deps.executeSlackTurn(thread, message, {
4591
4593
  explicitMention: true,
4592
4594
  beforeFirstResponsePost: hooks.beforeFirstResponsePost,
4593
4595
  conversationId: hooks.conversationId,
@@ -4877,7 +4879,7 @@ function createSlackTurnRuntime(deps) {
4877
4879
  processingReaction,
4878
4880
  hooks
4879
4881
  );
4880
- await deps.replyToThread(thread, message, {
4882
+ await deps.executeSlackTurn(thread, message, {
4881
4883
  explicitMention: Boolean(message.isMention),
4882
4884
  conversationId: hooks.conversationId,
4883
4885
  destination: hooks.destination,
@@ -5516,7 +5518,7 @@ function createJuniorRuntimeServices(overrides = {}) {
5516
5518
  };
5517
5519
  }
5518
5520
 
5519
- // src/chat/runtime/reply-executor.ts
5521
+ // src/chat/providers/slack/turn.ts
5520
5522
  import { createSlackSource as createSlackSource2 } from "@sentry/junior-plugin-api";
5521
5523
 
5522
5524
  // src/chat/slack/message/identity.ts
@@ -5614,7 +5616,7 @@ async function maybeApplyProviderDefaultConfigRequest(args) {
5614
5616
  };
5615
5617
  }
5616
5618
 
5617
- // src/chat/runtime/reply-executor.ts
5619
+ // src/chat/providers/slack/turn.ts
5618
5620
  async function persistThreadRuntimeStateWithRetry(thread, patch) {
5619
5621
  await persistWithRetry(() => persistThreadRuntimeState(thread, patch));
5620
5622
  }
@@ -5739,8 +5741,8 @@ async function loadPiMessagesForTurn(args) {
5739
5741
  function shouldPublishExternally2(publishExternally) {
5740
5742
  return publishExternally !== false;
5741
5743
  }
5742
- function createReplyToThread(deps) {
5743
- return async function replyToThread(thread, message, options) {
5744
+ function createSlackTurn(deps) {
5745
+ return async function executeSlackTurn(thread, message, options) {
5744
5746
  if (message.author.isMe) {
5745
5747
  return;
5746
5748
  }
@@ -7145,10 +7147,10 @@ function createSlackDispatchTurnRunner(options) {
7145
7147
  isSubscribedContext: true
7146
7148
  });
7147
7149
  let outcome;
7148
- let errorMessage;
7150
+ let errorMessage3;
7149
7151
  let resultMessageTs;
7150
7152
  const routing = buildDispatchRoutingContext(dispatch2);
7151
- await options.replyToThread(thread, message, {
7153
+ await options.executeSlackTurn(thread, message, {
7152
7154
  ack: hooks.ack,
7153
7155
  conversationId,
7154
7156
  destination: dispatch2.destination,
@@ -7171,13 +7173,13 @@ function createSlackDispatchTurnRunner(options) {
7171
7173
  },
7172
7174
  onTurnOutcome: (result) => {
7173
7175
  outcome = result.outcome;
7174
- errorMessage = result.errorMessage;
7176
+ errorMessage3 = result.errorMessage;
7175
7177
  },
7176
7178
  shouldYield: hooks.shouldYield,
7177
7179
  skipBackfill: true
7178
7180
  });
7179
7181
  return {
7180
- ...errorMessage ? { errorMessage } : void 0,
7182
+ ...errorMessage3 ? { errorMessage: errorMessage3 } : void 0,
7181
7183
  ...outcome ? { outcome } : void 0,
7182
7184
  ...resultMessageTs ? { resultMessageTs } : void 0
7183
7185
  };
@@ -7228,7 +7230,7 @@ function createSlackRuntime(options) {
7228
7230
  );
7229
7231
  }
7230
7232
  });
7231
- const replyToThread = createReplyToThread({
7233
+ const executeSlackTurn = createSlackTurn({
7232
7234
  getSlackAdapter: options.getSlackAdapter,
7233
7235
  prepareTurnState,
7234
7236
  resolveUserAttachments: services.visionContext.resolveUserAttachments,
@@ -7327,7 +7329,7 @@ function createSlackRuntime(options) {
7327
7329
  conversation: preparedState.conversation
7328
7330
  });
7329
7331
  },
7330
- replyToThread,
7332
+ executeSlackTurn,
7331
7333
  initializeAssistantThread: async ({
7332
7334
  channelId,
7333
7335
  threadTs,
@@ -7358,7 +7360,7 @@ function createSlackRuntime(options) {
7358
7360
  runDispatchTurn: createSlackDispatchTurnRunner({
7359
7361
  getLocationConfiguration: getLocationConfigurationService,
7360
7362
  getSlackAdapter: options.getSlackAdapter,
7361
- replyToThread
7363
+ executeSlackTurn
7362
7364
  })
7363
7365
  };
7364
7366
  }
@@ -7409,7 +7411,7 @@ async function failTurnBestEffort(args) {
7409
7411
  );
7410
7412
  }
7411
7413
  }
7412
- async function persistFailedReplyState(turn, errorMessage = "Paused agent run failed while continuing") {
7414
+ async function persistFailedReplyState(turn, errorMessage3 = "Paused agent run failed while continuing") {
7413
7415
  const currentState = await getPersistedThreadState(turn.conversationId);
7414
7416
  const conversation = coerceThreadConversationState(currentState);
7415
7417
  await hydrateConversationMessages({
@@ -7426,7 +7428,7 @@ async function persistFailedReplyState(turn, errorMessage = "Paused agent run fa
7426
7428
  });
7427
7429
  await failTurnBestEffort({
7428
7430
  turn,
7429
- errorMessage
7431
+ errorMessage: errorMessage3
7430
7432
  });
7431
7433
  await persistThreadStateById(turn.conversationId, {
7432
7434
  conversation
@@ -7951,9 +7953,7 @@ function createConversationWork(options) {
7951
7953
  turnLifecycle: services.replyExecutor?.turnLifecycle
7952
7954
  }),
7953
7955
  fallbackWorker: routeAgentInvocationWork({
7954
- invocationWorker: createAgentInvocationWorker({
7955
- agentRunner: options.agentRunner
7956
- }),
7956
+ invocationWorker: createAgentInvocationWorker(options.agentRunner),
7957
7957
  fallbackWorker: providerWorker
7958
7958
  })
7959
7959
  }),
@@ -8189,7 +8189,7 @@ async function stopApiConversationTurn(args) {
8189
8189
  return result;
8190
8190
  }
8191
8191
 
8192
- // src/chat/api-turns/adapter-conversations.ts
8192
+ // src/api/acp/conversations.ts
8193
8193
  var EVENT_PAGE_SIZE = 50;
8194
8194
  function actorFromUser(user) {
8195
8195
  return webActorFromEmail(
@@ -8197,151 +8197,2071 @@ function actorFromUser(user) {
8197
8197
  user.displayName ? { fullName: user.displayName } : void 0
8198
8198
  );
8199
8199
  }
8200
- async function hasConversationAccess(conversationId, user) {
8201
- const access = (await readConversationAccessFromSql(getDb(), [conversationId], user)).get(conversationId);
8202
- return Boolean(access?.isParticipant);
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;
8213
+ }
8214
+ if (!conversation || !hasRunnableConversationWork(conversation)) {
8215
+ return true;
8216
+ }
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
+ );
8225
+ }
8226
+ async function latestEventCursor(eventStore, conversationId) {
8227
+ const page = await eventStore.query(conversationId, { limit: 1 });
8228
+ return page.events.at(-1)?.seq ?? 0;
8229
+ }
8230
+ function createAcpConversations(options) {
8231
+ return {
8232
+ async cancel({ conversationId, user }) {
8233
+ if (!await hasConversationAccess(conversationId, user)) {
8234
+ return "not_found";
8235
+ }
8236
+ await stopApiConversationTurn({
8237
+ conversationId,
8238
+ conversationStore: options.conversationStore,
8239
+ queue: options.queue,
8240
+ state: options.state
8241
+ });
8242
+ return "cancelled";
8243
+ },
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" };
8259
+ }
8260
+ const currentCursor = await latestEventCursor(
8261
+ options.eventStore,
8262
+ conversationId
8263
+ );
8264
+ if (await getAuthPausedApiTurnId(conversationId)) {
8265
+ return { status: "active" };
8266
+ }
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,
8277
+ queue: options.queue,
8278
+ state: options.state
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
+ }
8335
+ }
8336
+ const turnPage = { cursor, messages };
8337
+ if (terminal) turnPage.terminal = terminal;
8338
+ return turnPage;
8339
+ }
8340
+ };
8341
+ }
8342
+
8343
+ // src/api/acp/route.ts
8344
+ import { createHash as createHash4 } from "crypto";
8345
+ import * as acp4 from "@agentclientprotocol/sdk";
8346
+
8347
+ // src/api/acp/auth.ts
8348
+ import {
8349
+ createHash as createHash3,
8350
+ randomBytes,
8351
+ randomUUID as randomUUID2,
8352
+ timingSafeEqual as timingSafeEqual3
8353
+ } from "crypto";
8354
+ import * as acp3 from "@agentclientprotocol/sdk";
8355
+ import { userSchema as userSchema2 } from "@sentry/junior-plugin-api";
8356
+ import { z as z7 } from "zod";
8357
+
8358
+ // src/api/acp/state.ts
8359
+ import { setTimeout as wait } from "timers/promises";
8360
+ var MUTATION_LOCK_TTL_MS = 1e4;
8361
+ async function withLock(state, key, run, options = {}) {
8362
+ const ttlMs = options.ttlMs ?? MUTATION_LOCK_TTL_MS;
8363
+ const startedAtMs = Date.now();
8364
+ let lock;
8365
+ while (true) {
8366
+ lock = await state.acquireLock(key, ttlMs);
8367
+ if (lock) break;
8368
+ if (Date.now() - startedAtMs >= (options.waitMs ?? 0)) {
8369
+ return { acquired: false };
8370
+ }
8371
+ await wait(options.retryMs ?? 25, void 0, { ref: false });
8372
+ }
8373
+ const heartbeat = options.keepAlive ? setInterval(
8374
+ () => void state.extendLock(lock, ttlMs).catch(() => void 0),
8375
+ Math.floor(ttlMs / 3)
8376
+ ) : void 0;
8377
+ heartbeat?.unref();
8378
+ try {
8379
+ return { acquired: true, value: await run(lock) };
8380
+ } finally {
8381
+ if (heartbeat) clearInterval(heartbeat);
8382
+ await state.releaseLock(lock);
8383
+ }
8384
+ }
8385
+ async function fenceLock(state, lock, ttlMs = MUTATION_LOCK_TTL_MS) {
8386
+ if (!await state.extendLock(lock, ttlMs)) {
8387
+ throw new Error(
8388
+ `ACP lock ownership was lost before write for ${lock.threadId}`
8389
+ );
8390
+ }
8391
+ }
8392
+
8393
+ // src/api/acp/transport.ts
8394
+ import { createHash as createHash2, randomUUID } from "crypto";
8395
+ import * as acp2 from "@agentclientprotocol/sdk";
8396
+ import { userSchema } from "@sentry/junior-plugin-api";
8397
+ import { z as z6 } from "zod";
8398
+
8399
+ // src/api/acp/schema.ts
8400
+ import * as acp from "@agentclientprotocol/sdk";
8401
+ import { z as z5 } from "zod";
8402
+ var ACP_SESSION_ID_PATTERN = /^local:acp:[a-f0-9]{32}$/;
8403
+ var metaSchema = z5.record(z5.string(), z5.unknown()).nullable();
8404
+ var jsonRpcIdSchema = z5.union([z5.string(), z5.number().finite(), z5.null()]);
8405
+ var SESSION_HEADER_METHODS = /* @__PURE__ */ new Set([
8406
+ acp.methods.agent.session.cancel,
8407
+ acp.methods.agent.session.close,
8408
+ acp.methods.agent.session.load,
8409
+ acp.methods.agent.session.prompt,
8410
+ acp.methods.agent.session.resume,
8411
+ acp.methods.agent.session.setConfigOption,
8412
+ acp.methods.agent.session.setMode
8413
+ ]);
8414
+ var acpConnectionIdSchema = z5.string().uuid();
8415
+ var acpSessionIdSchema = z5.string().regex(ACP_SESSION_ID_PATTERN);
8416
+ var acpCallSchema = z5.object({
8417
+ id: jsonRpcIdSchema.optional(),
8418
+ jsonrpc: z5.literal("2.0"),
8419
+ method: z5.string().min(1),
8420
+ params: z5.unknown().optional()
8421
+ }).strict();
8422
+ var jsonRpcErrorSchema = z5.object({
8423
+ code: z5.number().int(),
8424
+ data: z5.unknown().optional(),
8425
+ message: z5.string()
8426
+ }).strict();
8427
+ var acpResponseSchema = z5.union([
8428
+ z5.object({
8429
+ id: jsonRpcIdSchema,
8430
+ jsonrpc: z5.literal("2.0"),
8431
+ result: z5.unknown()
8432
+ }).strict(),
8433
+ z5.object({
8434
+ error: jsonRpcErrorSchema,
8435
+ id: jsonRpcIdSchema,
8436
+ jsonrpc: z5.literal("2.0")
8437
+ }).strict()
8438
+ ]);
8439
+ var acpInboundMessageSchema = z5.union([
8440
+ acpCallSchema,
8441
+ acpResponseSchema
8442
+ ]);
8443
+ var implementationSchema = z5.object({
8444
+ _meta: metaSchema.optional(),
8445
+ name: z5.string().min(1),
8446
+ title: z5.string().nullable().optional(),
8447
+ version: z5.string()
8448
+ }).strict();
8449
+ var initializeParamsSchema = z5.object({
8450
+ _meta: metaSchema.optional(),
8451
+ clientCapabilities: z5.record(z5.string(), z5.unknown()).optional(),
8452
+ clientInfo: implementationSchema.nullable().optional(),
8453
+ protocolVersion: z5.number().int().min(0).max(65535)
8454
+ }).strict();
8455
+ var authenticateParamsSchema = z5.object({
8456
+ _meta: metaSchema.optional(),
8457
+ methodId: z5.string().min(1)
8458
+ }).strict();
8459
+ var newSessionParamsSchema = z5.object({
8460
+ _meta: metaSchema.optional(),
8461
+ additionalDirectories: z5.array(z5.string()).optional(),
8462
+ cwd: z5.string().min(1),
8463
+ mcpServers: z5.array(z5.unknown())
8464
+ }).strict();
8465
+ var loadSessionParamsSchema = newSessionParamsSchema.extend({
8466
+ sessionId: acpSessionIdSchema
8467
+ });
8468
+ var annotationsSchema = z5.object({
8469
+ _meta: metaSchema.optional(),
8470
+ audience: z5.array(z5.enum(["assistant", "user"])).nullable().optional(),
8471
+ lastModified: z5.string().nullable().optional(),
8472
+ priority: z5.number().finite().nullable().optional()
8473
+ }).strict();
8474
+ var textContentBlockSchema = z5.object({
8475
+ _meta: metaSchema.optional(),
8476
+ annotations: annotationsSchema.nullable().optional(),
8477
+ text: z5.string(),
8478
+ type: z5.literal("text")
8479
+ }).strict();
8480
+ var promptParamsSchema = z5.object({
8481
+ _meta: metaSchema.optional(),
8482
+ prompt: z5.array(textContentBlockSchema),
8483
+ sessionId: acpSessionIdSchema
8484
+ }).strict();
8485
+ var cancelParamsSchema = z5.object({
8486
+ _meta: metaSchema.optional(),
8487
+ sessionId: acpSessionIdSchema
8488
+ }).strict();
8489
+
8490
+ // src/api/acp/sleep.ts
8491
+ function sleep(ms, signal) {
8492
+ return new Promise((resolve, reject) => {
8493
+ if (signal.aborted) {
8494
+ reject(signal.reason);
8495
+ return;
8496
+ }
8497
+ const timeoutId = setTimeout(() => {
8498
+ signal.removeEventListener("abort", onAbort);
8499
+ resolve();
8500
+ }, ms);
8501
+ const onAbort = () => {
8502
+ clearTimeout(timeoutId);
8503
+ reject(signal.reason);
8504
+ };
8505
+ signal.addEventListener("abort", onAbort, { once: true });
8506
+ });
8507
+ }
8508
+
8509
+ // src/api/acp/transport.ts
8510
+ var ACP_STATE_TTL_MS = 24 * 60 * 60 * 1e3;
8511
+ var ACP_SSE_LEASE_TTL_MS = 6e4;
8512
+ var ACP_SSE_LEASE_RENEW_INTERVAL_MS = 2e4;
8513
+ var EVENT_POLL_INTERVAL_MS = 100;
8514
+ var LOCK_WAIT_MS = 5e3;
8515
+ var MAX_STREAM_ITEMS = 1024;
8516
+ var MUTATION_LOCK_OPTIONS = {
8517
+ keepAlive: true,
8518
+ ttlMs: MUTATION_LOCK_TTL_MS,
8519
+ waitMs: LOCK_WAIT_MS
8520
+ };
8521
+ var SSE_KEEP_ALIVE_MS = 15e3;
8522
+ var SSE_MAINTENANCE_INTERVAL_MS = 1e3;
8523
+ var jsonRpcIdSchema2 = z6.union([z6.string(), z6.number().finite(), z6.null()]);
8524
+ var acpConnectionSchema = z6.object({
8525
+ credentialHash: z6.string().length(64),
8526
+ nonce: z6.string().min(1),
8527
+ user: userSchema.optional()
8528
+ }).strict();
8529
+ var acpStreamRouteSchema = z6.object({
8530
+ connectionId: z6.string().min(1),
8531
+ sessionId: z6.string().min(1).optional()
8532
+ }).strict();
8533
+ var acpPromptStreamOutputSchema = z6.object({
8534
+ afterSeq: z6.number().int().nonnegative(),
8535
+ kind: z6.literal("prompt"),
8536
+ messageId: z6.string().min(1),
8537
+ requestId: jsonRpcIdSchema2,
8538
+ turnId: z6.string().min(1)
8539
+ }).strict();
8540
+ var acpStreamOutputSchema = z6.discriminatedUnion("kind", [
8541
+ z6.object({
8542
+ kind: z6.literal("message"),
8543
+ message: acpInboundMessageSchema
8544
+ }).strict(),
8545
+ z6.object({ kind: z6.literal("replay") }).strict(),
8546
+ acpPromptStreamOutputSchema
8547
+ ]);
8548
+ var storedAcpStreamItemSchema = z6.object({
8549
+ id: z6.string().min(1),
8550
+ output: acpStreamOutputSchema
8551
+ }).strict();
8552
+ var acpRequestReceiptSchema = z6.object({
8553
+ deliveryKey: z6.string().min(1).optional(),
8554
+ outputs: z6.array(acpStreamOutputSchema),
8555
+ sessionId: z6.string().min(1).optional()
8556
+ }).strict();
8557
+ var acpStreamCursorSchema = z6.object({ itemId: z6.string().min(1) }).strict();
8558
+ function stableHex(value) {
8559
+ return createHash2("sha256").update(value).digest("hex").slice(0, 32);
8560
+ }
8561
+ function connectionKey(connectionId) {
8562
+ return `junior:acp:v1:connection:${connectionId}`;
8563
+ }
8564
+ function receiptKey(connectionId, requestKey2) {
8565
+ return `${connectionKey(connectionId)}:request:${requestKey2}`;
8566
+ }
8567
+ function receiptLockKey(connectionId, requestKey2) {
8568
+ return `${receiptKey(connectionId, requestKey2)}:lock`;
8569
+ }
8570
+ function connectionLockKey(connectionId) {
8571
+ return `${connectionKey(connectionId)}:lock`;
8572
+ }
8573
+ function streamKey(route) {
8574
+ const suffix = route.sessionId ? `session:${stableHex(route.sessionId)}` : "connection";
8575
+ return `${connectionKey(route.connectionId)}:stream:${suffix}`;
8576
+ }
8577
+ function streamListKey(route) {
8578
+ return `${streamKey(route)}:items`;
8579
+ }
8580
+ function streamCursorKey(route) {
8581
+ return `${streamKey(route)}:cursor`;
8582
+ }
8583
+ function streamLockKey(route) {
8584
+ return `${streamKey(route)}:subscriber`;
8585
+ }
8586
+ function streamAppendLockKey(route) {
8587
+ return `${streamKey(route)}:append`;
8588
+ }
8589
+ function requestStreamItemId(requestKey2, index) {
8590
+ return `acp-item:${stableHex(`${requestKey2}:${index}`)}`;
8591
+ }
8592
+ function parseStreamItem(value) {
8593
+ return storedAcpStreamItemSchema.parse(value);
8594
+ }
8595
+ function parseReceipt(value) {
8596
+ return acpRequestReceiptSchema.parse(value);
8597
+ }
8598
+ async function readStreamItems(state, route) {
8599
+ return (await state.getList(streamListKey(route))).map(parseStreamItem);
8600
+ }
8601
+ async function readStreamCursor(state, route) {
8602
+ const value = await state.get(streamCursorKey(route));
8603
+ return value === null || value === void 0 ? void 0 : acpStreamCursorSchema.parse(value).itemId;
8604
+ }
8605
+ async function connectionIsLive(state, connectionId) {
8606
+ return Boolean(await readAcpConnection(state, connectionId));
8607
+ }
8608
+ async function createAcpConnection(state, credentialHash2) {
8609
+ const connectionId = randomUUID();
8610
+ const connection = acpConnectionSchema.parse({
8611
+ credentialHash: credentialHash2,
8612
+ nonce: randomUUID()
8613
+ });
8614
+ await state.set(connectionKey(connectionId), connection, ACP_STATE_TTL_MS);
8615
+ return { connection, connectionId };
8616
+ }
8617
+ async function readAcpConnection(state, connectionId) {
8618
+ const value = await state.get(connectionKey(connectionId));
8619
+ return value === null || value === void 0 ? void 0 : acpConnectionSchema.parse(value);
8620
+ }
8621
+ async function deleteAcpConnection(state, connectionId) {
8622
+ const result = await withLock(
8623
+ state,
8624
+ connectionLockKey(connectionId),
8625
+ async (lock) => {
8626
+ await fenceLock(state, lock, MUTATION_LOCK_TTL_MS);
8627
+ await state.delete(connectionKey(connectionId));
8628
+ await fenceLock(state, lock, MUTATION_LOCK_TTL_MS);
8629
+ await state.delete(streamCursorKey({ connectionId }));
8630
+ },
8631
+ MUTATION_LOCK_OPTIONS
8632
+ );
8633
+ if (!result.acquired) {
8634
+ throw new Error("Timed out acquiring ACP connection control");
8635
+ }
8636
+ }
8637
+ async function bindAcpConnectionUser(args) {
8638
+ const result = await withLock(
8639
+ args.state,
8640
+ connectionLockKey(args.connectionId),
8641
+ async (lock) => {
8642
+ const connection = await readAcpConnection(args.state, args.connectionId);
8643
+ if (!connection || connection.credentialHash !== args.credentialHash) {
8644
+ return "expired";
8645
+ }
8646
+ if (connection.user && connection.user.id !== args.user.id) {
8647
+ return "conflict";
8648
+ }
8649
+ await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
8650
+ await args.state.set(
8651
+ connectionKey(args.connectionId),
8652
+ acpConnectionSchema.parse({ ...connection, user: args.user }),
8653
+ ACP_STATE_TTL_MS
8654
+ );
8655
+ return "completed";
8656
+ },
8657
+ MUTATION_LOCK_OPTIONS
8658
+ );
8659
+ if (!result.acquired) {
8660
+ throw new Error("Timed out acquiring ACP connection control");
8661
+ }
8662
+ return result.value;
8663
+ }
8664
+ async function prepareStreamAppend(args) {
8665
+ const existing = await readStreamItems(args.state, args.route);
8666
+ const cursor = await readStreamCursor(args.state, args.route);
8667
+ const cursorIndex = cursor ? existing.findIndex((item) => item.id === cursor) : -1;
8668
+ const pending = existing.slice(cursorIndex >= 0 ? cursorIndex + 1 : 0);
8669
+ const presentIds = /* @__PURE__ */ new Set();
8670
+ const pendingIds = /* @__PURE__ */ new Set();
8671
+ for (const id of args.ids) {
8672
+ const matches = (item) => item.id === id || item.id.startsWith(`${id}:delivery:`);
8673
+ if (existing.some(matches)) presentIds.add(id);
8674
+ if (pending.some(matches)) pendingIds.add(id);
8675
+ }
8676
+ const retainedIds = pendingIds.size === 0 ? /* @__PURE__ */ new Set() : presentIds;
8677
+ const missingCount = args.ids.length - retainedIds.size;
8678
+ if (missingCount > MAX_STREAM_ITEMS) throw new AcpStreamFullError();
8679
+ if (missingCount === 0 || existing.length + missingCount <= MAX_STREAM_ITEMS) {
8680
+ return {
8681
+ existingIds: new Set(existing.map((item) => item.id)),
8682
+ retainedIds
8683
+ };
8684
+ }
8685
+ if (existing.length === 0 || cursorIndex !== existing.length - 1) {
8686
+ throw new AcpStreamFullError();
8687
+ }
8688
+ await fenceLock(args.state, args.lock, MUTATION_LOCK_TTL_MS);
8689
+ await args.state.delete(streamListKey(args.route));
8690
+ await args.state.delete(streamCursorKey(args.route));
8691
+ return { existingIds: /* @__PURE__ */ new Set(), retainedIds: /* @__PURE__ */ new Set() };
8692
+ }
8693
+ async function appendStreamOutputWithLock(args) {
8694
+ const route = acpStreamRouteSchema.parse(args.route);
8695
+ const output = acpStreamOutputSchema.parse(args.output);
8696
+ let id = args.id;
8697
+ for (let delivery = 1; args.existingIds.has(id); delivery += 1) {
8698
+ id = `${args.id}:delivery:${delivery}`;
8699
+ }
8700
+ await fenceLock(args.state, args.lock, MUTATION_LOCK_TTL_MS);
8701
+ if (output.kind !== "message" && !route.sessionId) {
8702
+ throw new Error(`ACP ${output.kind} output requires a session stream`);
8703
+ }
8704
+ await args.state.appendToList(
8705
+ streamListKey(route),
8706
+ { id, output },
8707
+ { ttlMs: ACP_STATE_TTL_MS }
8708
+ );
8709
+ args.existingIds.add(id);
8710
+ }
8711
+ var AcpStreamFullError = class extends Error {
8712
+ constructor() {
8713
+ super("ACP stream has too much undelivered output");
8714
+ this.name = "AcpStreamFullError";
8715
+ }
8716
+ };
8717
+ async function streamSessionReplay(args) {
8718
+ const messages = await args.conversations.readMessages(args.sessionId);
8719
+ for (const message of messages) {
8720
+ if (args.signal.aborted) return false;
8721
+ const emitted = await args.emit({
8722
+ jsonrpc: "2.0",
8723
+ method: acp2.methods.client.session.update,
8724
+ params: {
8725
+ sessionId: args.sessionId,
8726
+ update: {
8727
+ sessionUpdate: message.role === "user" ? "user_message_chunk" : "agent_message_chunk",
8728
+ content: { type: "text", text: message.text },
8729
+ messageId: message.id
8730
+ }
8731
+ }
8732
+ });
8733
+ if (!emitted) return false;
8734
+ }
8735
+ return true;
8736
+ }
8737
+ async function queueReceipt(args) {
8738
+ const receipt = parseReceipt(args.receipt);
8739
+ const deliveryKey = receipt.deliveryKey ?? args.requestKey;
8740
+ const route = { connectionId: args.connectionId };
8741
+ if (receipt.sessionId) route.sessionId = receipt.sessionId;
8742
+ const ids = receipt.outputs.map(
8743
+ (_, index) => requestStreamItemId(deliveryKey, index)
8744
+ );
8745
+ const appendAll = async (lock) => {
8746
+ const prepared = await prepareStreamAppend({
8747
+ ids,
8748
+ lock,
8749
+ route,
8750
+ state: args.state
8751
+ });
8752
+ for (const [index, output] of receipt.outputs.entries()) {
8753
+ const id = ids[index];
8754
+ if (!id || prepared.retainedIds.has(id)) continue;
8755
+ await appendStreamOutputWithLock({
8756
+ existingIds: prepared.existingIds,
8757
+ id,
8758
+ lock,
8759
+ output,
8760
+ route,
8761
+ state: args.state
8762
+ });
8763
+ }
8764
+ };
8765
+ if (args.stream) {
8766
+ if (streamAppendLockKey(args.stream.route) !== streamAppendLockKey(route)) {
8767
+ throw new Error("Reserved ACP output changed streams");
8768
+ }
8769
+ await appendAll(args.stream.lock);
8770
+ return;
8771
+ }
8772
+ const result = await withLock(
8773
+ args.state,
8774
+ streamAppendLockKey(route),
8775
+ appendAll,
8776
+ MUTATION_LOCK_OPTIONS
8777
+ );
8778
+ if (!result.acquired) {
8779
+ throw new Error("Timed out acquiring ACP stream append control");
8780
+ }
8781
+ }
8782
+ async function completeAcpRequest(args) {
8783
+ try {
8784
+ const result = await withLock(
8785
+ args.state,
8786
+ receiptLockKey(args.connectionId, args.requestKey),
8787
+ async (lock) => {
8788
+ if (!await readAcpConnection(args.state, args.connectionId)) {
8789
+ return "expired";
8790
+ }
8791
+ const receipt = parseReceipt(args.receipt);
8792
+ await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
8793
+ await args.state.set(
8794
+ receiptKey(args.connectionId, args.requestKey),
8795
+ receipt,
8796
+ ACP_STATE_TTL_MS
8797
+ );
8798
+ await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
8799
+ await queueReceipt({
8800
+ connectionId: args.connectionId,
8801
+ receipt,
8802
+ requestKey: args.requestKey,
8803
+ state: args.state
8804
+ });
8805
+ return "completed";
8806
+ },
8807
+ MUTATION_LOCK_OPTIONS
8808
+ );
8809
+ if (!result.acquired) {
8810
+ throw new Error("Timed out acquiring ACP request completion control");
8811
+ }
8812
+ return result.value;
8813
+ } catch (error) {
8814
+ if (error instanceof AcpStreamFullError) return "full";
8815
+ throw error;
8816
+ }
8817
+ }
8818
+ async function acceptAcpRequest(args) {
8819
+ try {
8820
+ const result = await withLock(
8821
+ args.state,
8822
+ receiptLockKey(args.connectionId, args.requestKey),
8823
+ async (lock) => {
8824
+ if (!await readAcpConnection(args.state, args.connectionId)) {
8825
+ return "expired";
8826
+ }
8827
+ const stored = await args.state.get(
8828
+ receiptKey(args.connectionId, args.requestKey)
8829
+ );
8830
+ const receipt = stored === null || stored === void 0 ? void 0 : parseReceipt(stored);
8831
+ if (!receipt) {
8832
+ const createAndQueue = async (stream2) => {
8833
+ const created = parseReceipt(await args.createReceipt());
8834
+ await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
8835
+ await args.state.set(
8836
+ receiptKey(args.connectionId, args.requestKey),
8837
+ created,
8838
+ ACP_STATE_TTL_MS
8839
+ );
8840
+ await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
8841
+ const queue = {
8842
+ connectionId: args.connectionId,
8843
+ receipt: created,
8844
+ requestKey: args.requestKey,
8845
+ state: args.state
8846
+ };
8847
+ if (stream2) queue.stream = stream2;
8848
+ await queueReceipt(queue);
8849
+ };
8850
+ if (!args.reserveRoute) {
8851
+ if (!await readAcpConnection(args.state, args.connectionId)) {
8852
+ return "expired";
8853
+ }
8854
+ await createAndQueue();
8855
+ return "accepted";
8856
+ }
8857
+ const route = acpStreamRouteSchema.parse(args.reserveRoute);
8858
+ const stream = await withLock(
8859
+ args.state,
8860
+ streamAppendLockKey(route),
8861
+ async (streamLock) => {
8862
+ await prepareStreamAppend({
8863
+ ids: [requestStreamItemId(args.requestKey, 0)],
8864
+ lock: streamLock,
8865
+ route,
8866
+ state: args.state
8867
+ });
8868
+ if (!await readAcpConnection(args.state, args.connectionId)) {
8869
+ return "expired";
8870
+ }
8871
+ await createAndQueue({ lock: streamLock, route });
8872
+ return "accepted";
8873
+ },
8874
+ MUTATION_LOCK_OPTIONS
8875
+ );
8876
+ if (!stream.acquired) {
8877
+ throw new Error("Timed out acquiring ACP stream append control");
8878
+ }
8879
+ return stream.value;
8880
+ }
8881
+ await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
8882
+ await queueReceipt({
8883
+ connectionId: args.connectionId,
8884
+ receipt,
8885
+ requestKey: args.requestKey,
8886
+ state: args.state
8887
+ });
8888
+ return "accepted";
8889
+ },
8890
+ MUTATION_LOCK_OPTIONS
8891
+ );
8892
+ return result.acquired ? result.value : "busy";
8893
+ } catch (error) {
8894
+ if (error instanceof AcpStreamFullError) return "full";
8895
+ throw error;
8896
+ }
8897
+ }
8898
+ function resultMessage(requestId, result) {
8899
+ return { jsonrpc: "2.0", id: requestId, result };
8900
+ }
8901
+ function errorMessage(requestId, error) {
8902
+ return {
8903
+ jsonrpc: "2.0",
8904
+ id: requestId,
8905
+ error: error.toErrorResponse()
8906
+ };
8907
+ }
8908
+ async function streamPrompt(args) {
8909
+ let cursor = args.output.afterSeq;
8910
+ const sentMessageIds = /* @__PURE__ */ new Set();
8911
+ while (!args.signal.aborted) {
8912
+ if (!await connectionIsLive(args.state, args.connectionId)) return false;
8913
+ const page = await args.conversations.readTurn({
8914
+ afterCursor: cursor,
8915
+ conversationId: args.sessionId,
8916
+ messageId: args.output.messageId,
8917
+ turnId: args.output.turnId
8918
+ });
8919
+ cursor = page.cursor;
8920
+ if (page.messages.length === 0 && !page.terminal) {
8921
+ await sleep(EVENT_POLL_INTERVAL_MS, args.signal);
8922
+ continue;
8923
+ }
8924
+ for (const message of page.messages) {
8925
+ if (sentMessageIds.has(message.id)) continue;
8926
+ sentMessageIds.add(message.id);
8927
+ if (!await args.emit({
8928
+ jsonrpc: "2.0",
8929
+ method: acp2.methods.client.session.update,
8930
+ params: {
8931
+ sessionId: args.sessionId,
8932
+ update: {
8933
+ sessionUpdate: "agent_message_chunk",
8934
+ content: { type: "text", text: message.text },
8935
+ messageId: message.id
8936
+ }
8937
+ }
8938
+ })) {
8939
+ return false;
8940
+ }
8941
+ }
8942
+ if (page.terminal?.status === "completed") {
8943
+ return await args.emit(
8944
+ resultMessage(args.output.requestId, {
8945
+ stopReason: page.terminal.outcome === "cancelled" ? "cancelled" : "end_turn"
8946
+ })
8947
+ );
8948
+ }
8949
+ if (page.terminal?.status === "failed") {
8950
+ return await args.emit(
8951
+ errorMessage(
8952
+ args.output.requestId,
8953
+ acp2.RequestError.internalError(
8954
+ { failureCode: page.terminal.failureCode },
8955
+ "Junior Turn failed"
8956
+ )
8957
+ )
8958
+ );
8959
+ }
8960
+ }
8961
+ return false;
8962
+ }
8963
+ function serializeSseMessage(message) {
8964
+ return new TextEncoder().encode(`data: ${JSON.stringify(message)}
8965
+
8966
+ `);
8967
+ }
8968
+ function serializeSseKeepAlive() {
8969
+ return new TextEncoder().encode(":\n\n");
8970
+ }
8971
+ async function openAcpSse(args) {
8972
+ const route = acpStreamRouteSchema.parse(args.route);
8973
+ const lock = await args.state.acquireLock(
8974
+ streamLockKey(route),
8975
+ ACP_SSE_LEASE_TTL_MS
8976
+ );
8977
+ if (!lock) {
8978
+ return new Response("ACP SSE stream already connected", { status: 409 });
8979
+ }
8980
+ return new Response(createSseBody({ ...args, lock, route }), {
8981
+ status: 200,
8982
+ headers: {
8983
+ "Cache-Control": "no-cache",
8984
+ Connection: "keep-alive",
8985
+ "Content-Type": "text/event-stream"
8986
+ }
8987
+ });
8988
+ }
8989
+ function createSseBody(args) {
8990
+ const abort = new AbortController();
8991
+ let cleaned = false;
8992
+ let heartbeat;
8993
+ let keepAlive;
8994
+ let leaseExpiresAt = args.lock.expiresAt;
8995
+ let nextMaintenanceAtMs = 0;
8996
+ let retainingLease;
8997
+ let closeBody;
8998
+ const correlation = {
8999
+ connectionId: args.route.connectionId
9000
+ };
9001
+ if (args.route.sessionId) correlation.conversationId = args.route.sessionId;
9002
+ if (args.userId) correlation.userId = args.userId;
9003
+ const cleanup = async () => {
9004
+ if (cleaned) return;
9005
+ cleaned = true;
9006
+ abort.abort();
9007
+ closeBody?.();
9008
+ if (keepAlive) clearInterval(keepAlive);
9009
+ if (heartbeat) clearInterval(heartbeat);
9010
+ args.requestSignal.removeEventListener("abort", onRequestAbort);
9011
+ try {
9012
+ await args.state.releaseLock(args.lock);
9013
+ } catch (error) {
9014
+ args.onError?.(error, "acp.sse.lock_cleanup.exception", correlation);
9015
+ }
9016
+ };
9017
+ const onRequestAbort = () => void cleanup();
9018
+ const retainLease = async () => {
9019
+ if (retainingLease) return await retainingLease;
9020
+ retainingLease = (async () => {
9021
+ let extended;
9022
+ try {
9023
+ extended = await args.state.extendLock(args.lock, ACP_SSE_LEASE_TTL_MS);
9024
+ } catch (error) {
9025
+ if (Date.now() < leaseExpiresAt) return;
9026
+ throw new Error("ACP SSE stream lease could not be renewed", {
9027
+ cause: error
9028
+ });
9029
+ }
9030
+ if (!extended) {
9031
+ throw new Error("ACP SSE stream lease expired");
9032
+ }
9033
+ leaseExpiresAt = Date.now() + ACP_SSE_LEASE_TTL_MS;
9034
+ })();
9035
+ try {
9036
+ await retainingLease;
9037
+ } finally {
9038
+ retainingLease = void 0;
9039
+ }
9040
+ };
9041
+ return new ReadableStream({
9042
+ start(controller) {
9043
+ let failureCaptured = false;
9044
+ closeBody = () => {
9045
+ try {
9046
+ controller.close();
9047
+ } catch {
9048
+ }
9049
+ };
9050
+ const fail = (error) => {
9051
+ if (cleaned) return;
9052
+ const failure = error instanceof Error ? error : new Error("ACP SSE stream failed", { cause: error });
9053
+ if (!failureCaptured) {
9054
+ failureCaptured = true;
9055
+ args.onError?.(failure, "acp.sse.exception", correlation);
9056
+ }
9057
+ abort.abort(failure);
9058
+ try {
9059
+ controller.error(failure);
9060
+ } catch {
9061
+ }
9062
+ void cleanup();
9063
+ };
9064
+ args.requestSignal.addEventListener("abort", onRequestAbort, {
9065
+ once: true
9066
+ });
9067
+ if (args.requestSignal.aborted) {
9068
+ void cleanup();
9069
+ return;
9070
+ }
9071
+ keepAlive = setInterval(() => {
9072
+ if (cleaned || (controller.desiredSize ?? 0) <= 0) return;
9073
+ try {
9074
+ controller.enqueue(serializeSseKeepAlive());
9075
+ } catch (error) {
9076
+ fail(error);
9077
+ }
9078
+ }, SSE_KEEP_ALIVE_MS);
9079
+ keepAlive.unref?.();
9080
+ heartbeat = setInterval(() => {
9081
+ if (cleaned) return;
9082
+ void retainLease().catch(fail);
9083
+ }, ACP_SSE_LEASE_RENEW_INTERVAL_MS);
9084
+ heartbeat.unref?.();
9085
+ const emit = async (message) => {
9086
+ while (!abort.signal.aborted && (controller.desiredSize ?? 0) <= 0) {
9087
+ await sleep(EVENT_POLL_INTERVAL_MS, abort.signal);
9088
+ }
9089
+ if (abort.signal.aborted || !await connectionIsLive(args.state, args.route.connectionId)) {
9090
+ abort.abort();
9091
+ return false;
9092
+ }
9093
+ await retainLease();
9094
+ controller.enqueue(serializeSseMessage(message));
9095
+ return true;
9096
+ };
9097
+ const pump = async () => {
9098
+ let cursor = await readStreamCursor(args.state, args.route);
9099
+ while (!abort.signal.aborted) {
9100
+ if (!await connectionIsLive(args.state, args.route.connectionId)) {
9101
+ return;
9102
+ }
9103
+ if (args.maintain && Date.now() >= nextMaintenanceAtMs) {
9104
+ nextMaintenanceAtMs = Date.now() + SSE_MAINTENANCE_INTERVAL_MS;
9105
+ await args.maintain();
9106
+ }
9107
+ const items = await readStreamItems(args.state, args.route);
9108
+ const cursorIndex = cursor ? items.findIndex((item) => item.id === cursor) : -1;
9109
+ const pending = items.slice(cursorIndex >= 0 ? cursorIndex + 1 : 0);
9110
+ if (pending.length === 0) {
9111
+ await sleep(EVENT_POLL_INTERVAL_MS, abort.signal);
9112
+ continue;
9113
+ }
9114
+ for (const item of pending) {
9115
+ if (abort.signal.aborted) return;
9116
+ await retainLease();
9117
+ await args.state.set(
9118
+ streamCursorKey(args.route),
9119
+ acpStreamCursorSchema.parse({ itemId: item.id }),
9120
+ ACP_STATE_TTL_MS
9121
+ );
9122
+ cursor = item.id;
9123
+ let delivered;
9124
+ if (item.output.kind === "message") {
9125
+ delivered = await emit(item.output.message);
9126
+ } else {
9127
+ const sessionId = args.route.sessionId;
9128
+ if (!sessionId) {
9129
+ throw new Error(
9130
+ `ACP ${item.output.kind} output requires a session stream`
9131
+ );
9132
+ }
9133
+ delivered = item.output.kind === "replay" ? await streamSessionReplay({
9134
+ conversations: args.conversations,
9135
+ emit,
9136
+ sessionId,
9137
+ signal: abort.signal
9138
+ }) : await streamPrompt({
9139
+ conversations: args.conversations,
9140
+ connectionId: args.route.connectionId,
9141
+ emit,
9142
+ output: item.output,
9143
+ sessionId,
9144
+ signal: abort.signal,
9145
+ state: args.state
9146
+ });
9147
+ }
9148
+ if (!delivered || abort.signal.aborted) return;
9149
+ }
9150
+ }
9151
+ };
9152
+ void pump().catch((error) => {
9153
+ if (!cleaned && !args.requestSignal.aborted) fail(error);
9154
+ }).finally(cleanup);
9155
+ },
9156
+ async cancel() {
9157
+ await cleanup();
9158
+ }
9159
+ });
9160
+ }
9161
+
9162
+ // src/api/acp/auth.ts
9163
+ var ACP_AUTHORIZATION_TTL_MS = 10 * 60 * 1e3;
9164
+ var ACP_CONNECTION_COOKIE = "junior_acp_connection";
9165
+ var LOCK_WAIT_MS2 = 5e3;
9166
+ var jsonRpcIdSchema3 = z7.union([z7.string(), z7.number().finite(), z7.null()]);
9167
+ var acpAuthorizationSchema = z7.object({
9168
+ connectionId: z7.string().uuid(),
9169
+ credentialHash: z7.string().length(64),
9170
+ elicitationId: z7.string().uuid(),
9171
+ expiresAtMs: z7.number().int().positive(),
9172
+ requestId: jsonRpcIdSchema3,
9173
+ requestKey: z7.string().min(1),
9174
+ userCodeHash: z7.string().length(64)
9175
+ }).strict();
9176
+ var acpAuthorizationPointerSchema = z7.object({
9177
+ expiresAtMs: z7.number().int().positive(),
9178
+ transactionId: z7.string().uuid()
9179
+ }).strict();
9180
+ var ACP_AUTH_METHOD_ID = "junior";
9181
+ var ACP_AUTH_METHOD = {
9182
+ id: ACP_AUTH_METHOD_ID,
9183
+ name: "Sign in to Junior",
9184
+ description: "Sign in with the Google account allowed by this Junior app."
9185
+ };
9186
+ function authorizationKey(transactionId) {
9187
+ return `junior:acp:v1:authorization:${transactionId}`;
9188
+ }
9189
+ function authorizationLockKey(transactionId) {
9190
+ return `${authorizationKey(transactionId)}:lock`;
9191
+ }
9192
+ function authorizationPointerKey(connectionId) {
9193
+ return `junior:acp:v1:connection:${connectionId}:authorization`;
9194
+ }
9195
+ function authorizationPointerLockKey(connectionId) {
9196
+ return `${authorizationPointerKey(connectionId)}:lock`;
9197
+ }
9198
+ function credentialHash(value) {
9199
+ return createHash3("sha256").update(value).digest("hex");
9200
+ }
9201
+ function createUserCode() {
9202
+ const value = randomBytes(6).toString("hex").toUpperCase();
9203
+ return `${value.slice(0, 4)}-${value.slice(4, 8)}-${value.slice(8, 12)}`;
9204
+ }
9205
+ function userCodeHash(value) {
9206
+ return credentialHash(value.trim().replaceAll("-", "").toUpperCase());
9207
+ }
9208
+ function userCodeMatches(value, expectedHash) {
9209
+ const actual = Buffer.from(userCodeHash(value), "hex");
9210
+ const expected = Buffer.from(expectedHash, "hex");
9211
+ return actual.length === expected.length && timingSafeEqual3(actual, expected);
9212
+ }
9213
+ function cookieValue(request) {
9214
+ const header = request.headers.get("cookie");
9215
+ if (!header) return void 0;
9216
+ for (const part of header.split(";")) {
9217
+ const separator = part.indexOf("=");
9218
+ if (separator <= 0) continue;
9219
+ if (part.slice(0, separator).trim() !== ACP_CONNECTION_COOKIE) continue;
9220
+ const value = part.slice(separator + 1).trim();
9221
+ if (value) return value;
9222
+ }
9223
+ return void 0;
9224
+ }
9225
+ function cookieAttributes(request) {
9226
+ return [
9227
+ "Path=/api/acp",
9228
+ "HttpOnly",
9229
+ "SameSite=Strict",
9230
+ ...new URL(request.url).protocol === "https:" ? ["Secure"] : []
9231
+ ].join("; ");
9232
+ }
9233
+ function createAcpConnectionCredential(request) {
9234
+ const credential = randomBytes(32).toString("base64url");
9235
+ return {
9236
+ cookie: `${ACP_CONNECTION_COOKIE}=${credential}; ${cookieAttributes(request)}; Max-Age=86400`,
9237
+ credentialHash: credentialHash(credential)
9238
+ };
9239
+ }
9240
+ function hasAcpConnectionCredential(request, expectedHash) {
9241
+ const credential = cookieValue(request);
9242
+ if (!credential) return false;
9243
+ const actual = Buffer.from(credentialHash(credential), "hex");
9244
+ const expected = Buffer.from(expectedHash, "hex");
9245
+ return actual.length === expected.length && timingSafeEqual3(actual, expected);
9246
+ }
9247
+ function authorizationBaseURL(request, configured) {
9248
+ const value = configured?.trim();
9249
+ let origin;
9250
+ if (value) {
9251
+ origin = /^https?:\/\//.test(value) ? value : `https://${value}`;
9252
+ } else {
9253
+ const requestURL = new URL(request.url);
9254
+ const hostname = requestURL.hostname.replace(/^\[|\]$/g, "");
9255
+ if (!["localhost", "127.0.0.1", "::1"].includes(hostname)) {
9256
+ throw acp3.RequestError.internalError(
9257
+ void 0,
9258
+ "Junior ACP sign-in requires a configured public base URL"
9259
+ );
9260
+ }
9261
+ origin = requestURL.origin;
9262
+ }
9263
+ const url = new URL(origin);
9264
+ url.pathname = "/";
9265
+ url.search = "";
9266
+ url.hash = "";
9267
+ return url;
9268
+ }
9269
+ async function readAuthorization(state, transactionId) {
9270
+ const value = await state.get(authorizationKey(transactionId));
9271
+ return value === null || value === void 0 ? void 0 : acpAuthorizationSchema.parse(value);
9272
+ }
9273
+ async function readAuthorizationPointer(state, connectionId) {
9274
+ const value = await state.get(authorizationPointerKey(connectionId));
9275
+ return value === null || value === void 0 ? void 0 : acpAuthorizationPointerSchema.parse(value);
9276
+ }
9277
+ async function deleteAuthorizationPointer(state, connectionId, transactionId) {
9278
+ const result = await withLock(
9279
+ state,
9280
+ authorizationPointerLockKey(connectionId),
9281
+ async (lock) => {
9282
+ const pointer = await readAuthorizationPointer(state, connectionId);
9283
+ if (pointer?.transactionId !== transactionId) return;
9284
+ await fenceLock(state, lock, MUTATION_LOCK_TTL_MS);
9285
+ await state.delete(authorizationPointerKey(connectionId));
9286
+ },
9287
+ {
9288
+ keepAlive: true,
9289
+ ttlMs: MUTATION_LOCK_TTL_MS,
9290
+ waitMs: LOCK_WAIT_MS2
9291
+ }
9292
+ );
9293
+ if (!result.acquired) {
9294
+ throw new Error("Timed out acquiring ACP authorization pointer control");
9295
+ }
9296
+ }
9297
+ async function deleteAuthorization(state, authorization, transactionId) {
9298
+ await deleteAuthorizationPointer(
9299
+ state,
9300
+ authorization.connectionId,
9301
+ transactionId
9302
+ );
9303
+ await state.delete(authorizationKey(transactionId));
9304
+ }
9305
+ function completionReceipt(authorization, response) {
9306
+ return {
9307
+ deliveryKey: `${authorization.requestKey}:authorization-complete`,
9308
+ outputs: [
9309
+ {
9310
+ kind: "message",
9311
+ message: {
9312
+ jsonrpc: "2.0",
9313
+ method: acp3.methods.client.elicitation.complete,
9314
+ params: { elicitationId: authorization.elicitationId }
9315
+ }
9316
+ },
9317
+ { kind: "message", message: response }
9318
+ ]
9319
+ };
9320
+ }
9321
+ function resultMessage2(requestId) {
9322
+ return { jsonrpc: "2.0", id: requestId, result: {} };
9323
+ }
9324
+ function cancelledMessage(requestId, message = "Junior sign-in was cancelled") {
9325
+ return {
9326
+ jsonrpc: "2.0",
9327
+ id: requestId,
9328
+ error: acp3.RequestError.requestCancelled(
9329
+ void 0,
9330
+ message
9331
+ ).toErrorResponse()
9332
+ };
9333
+ }
9334
+ async function beginAcpAuthorization(args) {
9335
+ const url = authorizationBaseURL(args.request, args.baseURL);
9336
+ const result = await withLock(
9337
+ args.state,
9338
+ authorizationPointerLockKey(args.connectionId),
9339
+ async (lock) => {
9340
+ const pointer = await readAuthorizationPointer(
9341
+ args.state,
9342
+ args.connectionId
9343
+ );
9344
+ if (pointer) {
9345
+ const pending = await readAuthorization(
9346
+ args.state,
9347
+ pointer.transactionId
9348
+ );
9349
+ if (pending?.connectionId === args.connectionId) {
9350
+ throw acp3.RequestError.invalidRequest(
9351
+ void 0,
9352
+ "Junior sign-in is already in progress"
9353
+ );
9354
+ }
9355
+ await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
9356
+ await args.state.delete(authorizationPointerKey(args.connectionId));
9357
+ }
9358
+ const transactionId2 = randomUUID2();
9359
+ const userCode2 = createUserCode();
9360
+ const authorization = acpAuthorizationSchema.parse({
9361
+ connectionId: args.connectionId,
9362
+ credentialHash: args.credentialHash,
9363
+ elicitationId: transactionId2,
9364
+ expiresAtMs: Date.now() + ACP_AUTHORIZATION_TTL_MS,
9365
+ requestId: args.requestId,
9366
+ requestKey: args.requestKey,
9367
+ userCodeHash: userCodeHash(userCode2)
9368
+ });
9369
+ await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
9370
+ await args.state.set(
9371
+ authorizationKey(transactionId2),
9372
+ authorization,
9373
+ ACP_STATE_TTL_MS
9374
+ );
9375
+ await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
9376
+ await args.state.set(
9377
+ authorizationPointerKey(args.connectionId),
9378
+ { expiresAtMs: authorization.expiresAtMs, transactionId: transactionId2 },
9379
+ ACP_STATE_TTL_MS
9380
+ );
9381
+ return { transactionId: transactionId2, userCode: userCode2 };
9382
+ },
9383
+ {
9384
+ keepAlive: true,
9385
+ ttlMs: MUTATION_LOCK_TTL_MS,
9386
+ waitMs: LOCK_WAIT_MS2
9387
+ }
9388
+ );
9389
+ if (!result.acquired) {
9390
+ throw new Error("Timed out acquiring ACP authorization pointer control");
9391
+ }
9392
+ const { transactionId, userCode } = result.value;
9393
+ url.pathname = `/_junior/acp/auth/${transactionId}`;
9394
+ const params = {
9395
+ mode: "url",
9396
+ message: `Sign in to Junior in your browser. Enter verification code ${userCode}.`,
9397
+ elicitationId: transactionId,
9398
+ requestId: args.requestId,
9399
+ url: url.toString()
9400
+ };
9401
+ return {
9402
+ deliveryKey: `${args.requestKey}:authorization-start`,
9403
+ outputs: [
9404
+ {
9405
+ kind: "message",
9406
+ message: {
9407
+ jsonrpc: "2.0",
9408
+ id: transactionId,
9409
+ method: acp3.methods.client.elicitation.create,
9410
+ params
9411
+ }
9412
+ }
9413
+ ]
9414
+ };
9415
+ }
9416
+ async function finishAuthorization(args) {
9417
+ const result = await completeAcpRequest({
9418
+ connectionId: args.authorization.connectionId,
9419
+ receipt: completionReceipt(args.authorization, args.response),
9420
+ requestKey: args.authorization.requestKey,
9421
+ state: args.state
9422
+ });
9423
+ return result === "full" ? "busy" : result;
9424
+ }
9425
+ async function completeAcpAuthorization(args) {
9426
+ const result = await withLock(
9427
+ args.state,
9428
+ authorizationLockKey(args.transactionId),
9429
+ async (lock) => {
9430
+ const authorization = await readAuthorization(
9431
+ args.state,
9432
+ args.transactionId
9433
+ );
9434
+ if (!authorization) {
9435
+ return "expired";
9436
+ }
9437
+ if (authorization.expiresAtMs <= Date.now()) {
9438
+ const completion2 = await finishAuthorization({
9439
+ authorization,
9440
+ response: cancelledMessage(
9441
+ authorization.requestId,
9442
+ "Junior sign-in request expired"
9443
+ ),
9444
+ state: args.state
9445
+ });
9446
+ if (completion2 === "busy") return completion2;
9447
+ await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
9448
+ await deleteAuthorization(
9449
+ args.state,
9450
+ authorization,
9451
+ args.transactionId
9452
+ );
9453
+ return "expired";
9454
+ }
9455
+ if (!userCodeMatches(args.userCode, authorization.userCodeHash)) {
9456
+ return "invalid";
9457
+ }
9458
+ const binding = await bindAcpConnectionUser({
9459
+ connectionId: authorization.connectionId,
9460
+ credentialHash: authorization.credentialHash,
9461
+ state: args.state,
9462
+ user: userSchema2.parse(args.user)
9463
+ });
9464
+ if (binding !== "completed") {
9465
+ const completion2 = await finishAuthorization({
9466
+ authorization,
9467
+ response: cancelledMessage(
9468
+ authorization.requestId,
9469
+ "Junior sign-in could not bind this connection"
9470
+ ),
9471
+ state: args.state
9472
+ });
9473
+ if (completion2 !== "completed") return completion2;
9474
+ await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
9475
+ await deleteAuthorization(
9476
+ args.state,
9477
+ authorization,
9478
+ args.transactionId
9479
+ );
9480
+ return binding;
9481
+ }
9482
+ const completion = await finishAuthorization({
9483
+ authorization,
9484
+ response: resultMessage2(authorization.requestId),
9485
+ state: args.state
9486
+ });
9487
+ if (completion !== "completed") return completion;
9488
+ await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
9489
+ await deleteAuthorization(args.state, authorization, args.transactionId);
9490
+ return "completed";
9491
+ },
9492
+ {
9493
+ keepAlive: true,
9494
+ ttlMs: MUTATION_LOCK_TTL_MS,
9495
+ waitMs: LOCK_WAIT_MS2
9496
+ }
9497
+ );
9498
+ if (!result.acquired) {
9499
+ throw new Error("Timed out acquiring ACP authorization control");
9500
+ }
9501
+ return result.value;
9502
+ }
9503
+ async function handleAcpAuthorizationResponse(args) {
9504
+ if (typeof args.response.id !== "string") return "ignored";
9505
+ const parsedId = z7.string().uuid().safeParse(args.response.id);
9506
+ if (!parsedId.success) return "ignored";
9507
+ const authorization = await readAuthorization(args.state, parsedId.data);
9508
+ if (!authorization || authorization.connectionId !== args.connectionId) {
9509
+ return "ignored";
9510
+ }
9511
+ 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;
9512
+ if (action === "accept") return "accepted";
9513
+ const result = await withLock(
9514
+ args.state,
9515
+ authorizationLockKey(parsedId.data),
9516
+ async (lock) => {
9517
+ const current = await readAuthorization(args.state, parsedId.data);
9518
+ if (!current || current.connectionId !== args.connectionId) return false;
9519
+ const completion = await finishAuthorization({
9520
+ authorization: current,
9521
+ response: cancelledMessage(current.requestId),
9522
+ state: args.state
9523
+ });
9524
+ if (completion === "busy") return false;
9525
+ await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
9526
+ await deleteAuthorization(args.state, current, parsedId.data);
9527
+ return true;
9528
+ },
9529
+ {
9530
+ keepAlive: true,
9531
+ ttlMs: MUTATION_LOCK_TTL_MS,
9532
+ waitMs: LOCK_WAIT_MS2
9533
+ }
9534
+ );
9535
+ if (!result.acquired) {
9536
+ throw new Error("Timed out acquiring ACP authorization control");
9537
+ }
9538
+ return result.value ? "cancelled" : "ignored";
9539
+ }
9540
+ async function expireAcpAuthorization(args) {
9541
+ const pointer = await readAuthorizationPointer(args.state, args.connectionId);
9542
+ if (!pointer || pointer.expiresAtMs > Date.now()) return false;
9543
+ const result = await withLock(
9544
+ args.state,
9545
+ authorizationLockKey(pointer.transactionId),
9546
+ async (lock) => {
9547
+ const authorization = await readAuthorization(
9548
+ args.state,
9549
+ pointer.transactionId
9550
+ );
9551
+ if (!authorization || authorization.connectionId !== args.connectionId) {
9552
+ await deleteAuthorizationPointer(
9553
+ args.state,
9554
+ args.connectionId,
9555
+ pointer.transactionId
9556
+ );
9557
+ return false;
9558
+ }
9559
+ if (authorization.expiresAtMs > Date.now()) return false;
9560
+ const completion = await finishAuthorization({
9561
+ authorization,
9562
+ response: cancelledMessage(
9563
+ authorization.requestId,
9564
+ "Junior sign-in request expired"
9565
+ ),
9566
+ state: args.state
9567
+ });
9568
+ if (completion === "busy") return false;
9569
+ await fenceLock(args.state, lock, MUTATION_LOCK_TTL_MS);
9570
+ await deleteAuthorization(
9571
+ args.state,
9572
+ authorization,
9573
+ pointer.transactionId
9574
+ );
9575
+ return true;
9576
+ },
9577
+ {
9578
+ keepAlive: true,
9579
+ ttlMs: MUTATION_LOCK_TTL_MS,
9580
+ waitMs: LOCK_WAIT_MS2
9581
+ }
9582
+ );
9583
+ if (!result.acquired) return false;
9584
+ return result.value;
9585
+ }
9586
+
9587
+ // src/api/acp/route.ts
9588
+ var ACP_CONNECTION_ID_HEADER = "Acp-Connection-Id";
9589
+ var ACP_SESSION_ID_HEADER = "Acp-Session-Id";
9590
+ var ACP_CONVERSATION_PREFIX = "local:acp:";
9591
+ var ACP_EVENT_STREAM_MIME_TYPE = "text/event-stream";
9592
+ var ACP_JSON_MIME_TYPE = "application/json";
9593
+ var MAX_PROMPT_TEXT_LENGTH = 32e3;
9594
+ function isRecord2(value) {
9595
+ return typeof value === "object" && value !== null && !Array.isArray(value);
9596
+ }
9597
+ function authenticatedAcpUser(connection) {
9598
+ return connection.user;
9599
+ }
9600
+ function supportsUrlElicitation(params) {
9601
+ const parsed = initializeParamsSchema.safeParse(params);
9602
+ if (!parsed.success) return false;
9603
+ const elicitation = parsed.data.clientCapabilities?.elicitation;
9604
+ return isRecord2(elicitation) && isRecord2(elicitation.url);
9605
+ }
9606
+ function parseParams(schema, params) {
9607
+ const parsed = schema.safeParse(params);
9608
+ if (parsed.success) return parsed.data;
9609
+ const firstPath = parsed.error.issues[0]?.path[0];
9610
+ throw acp4.RequestError.invalidParams({
9611
+ field: typeof firstPath === "string" || typeof firstPath === "number" ? String(firstPath) : "params"
9612
+ });
9613
+ }
9614
+ function rejectUnsupportedMcpServers(mcpServers) {
9615
+ if (mcpServers.length > 0) {
9616
+ throw acp4.RequestError.invalidParams(
9617
+ { field: "mcpServers" },
9618
+ "Junior does not accept client MCP servers"
9619
+ );
9620
+ }
9621
+ }
9622
+ function promptText(prompt) {
9623
+ if (prompt.length === 0) {
9624
+ throw acp4.RequestError.invalidParams(
9625
+ { field: "prompt" },
9626
+ "Junior accepts one or more text blocks only"
9627
+ );
9628
+ }
9629
+ const blocks = [];
9630
+ for (const block of prompt) {
9631
+ if (!block.text.trim()) {
9632
+ throw acp4.RequestError.invalidParams(
9633
+ { field: "prompt" },
9634
+ "Junior accepts non-empty text blocks only"
9635
+ );
9636
+ }
9637
+ blocks.push(block.text);
9638
+ }
9639
+ const text = blocks.join("\n");
9640
+ if (text.length > MAX_PROMPT_TEXT_LENGTH) {
9641
+ throw acp4.RequestError.invalidParams(
9642
+ { field: "prompt" },
9643
+ `Junior accepts at most ${MAX_PROMPT_TEXT_LENGTH} prompt characters`
9644
+ );
9645
+ }
9646
+ return text;
9647
+ }
9648
+ function stableHex2(value, length = 32) {
9649
+ return createHash4("sha256").update(value).digest("hex").slice(0, length);
9650
+ }
9651
+ function canonicalJson(value) {
9652
+ if (Array.isArray(value)) return value.map(canonicalJson);
9653
+ if (!isRecord2(value)) return value;
9654
+ return Object.fromEntries(
9655
+ Object.entries(value).sort(([left], [right]) => left < right ? -1 : left > right ? 1 : 0).map(([key, child]) => [key, canonicalJson(child)])
9656
+ );
9657
+ }
9658
+ function requestKey(call) {
9659
+ const id = call.id === null ? "null" : `${typeof call.id}:${stableHex2(String(call.id))}`;
9660
+ const payload = JSON.stringify(
9661
+ canonicalJson({ method: call.method, params: call.params ?? null })
9662
+ );
9663
+ return `${id}:${stableHex2(payload)}`;
9664
+ }
9665
+ function sessionIdFromCall(call) {
9666
+ if (!call) return void 0;
9667
+ 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;
9668
+ if (!schema) return void 0;
9669
+ const parsed = schema.safeParse(call.params);
9670
+ return parsed.success ? parsed.data.sessionId : void 0;
9671
+ }
9672
+ function determineRoute(connectionId, call, headerSessionId) {
9673
+ const paramsSessionId = sessionIdFromCall(call);
9674
+ if (SESSION_HEADER_METHODS.has(call.method) && !headerSessionId) {
9675
+ return textResponse("Missing Acp-Session-Id", 400);
9676
+ }
9677
+ if (headerSessionId !== void 0 && paramsSessionId !== void 0 && headerSessionId !== paramsSessionId) {
9678
+ return textResponse("Mismatched Acp-Session-Id", 400);
9679
+ }
9680
+ return {
9681
+ connectionId,
9682
+ ...headerSessionId ? { sessionId: headerSessionId } : paramsSessionId ? { sessionId: paramsSessionId } : {}
9683
+ };
9684
+ }
9685
+ function resultMessage3(requestId, result) {
9686
+ return { jsonrpc: "2.0", id: requestId, result };
9687
+ }
9688
+ function errorMessage2(requestId, error) {
9689
+ return {
9690
+ jsonrpc: "2.0",
9691
+ id: requestId,
9692
+ error: error.toErrorResponse()
9693
+ };
9694
+ }
9695
+ function textResponse(body, status) {
9696
+ return new Response(body, {
9697
+ status,
9698
+ headers: { "Content-Type": "text/plain" }
9699
+ });
9700
+ }
9701
+ function jsonResponse(value, status, headers) {
9702
+ return new Response(JSON.stringify(value), {
9703
+ status,
9704
+ headers: { "Content-Type": ACP_JSON_MIME_TYPE, ...headers }
9705
+ });
9706
+ }
9707
+ function emptyResponse(status) {
9708
+ return new Response(null, { status });
9709
+ }
9710
+ async function createSession(args) {
9711
+ const params = parseParams(
9712
+ newSessionParamsSchema,
9713
+ args.call.params
9714
+ );
9715
+ rejectUnsupportedMcpServers(params.mcpServers);
9716
+ const sessionId = `${ACP_CONVERSATION_PREFIX}${stableHex2(
9717
+ `${args.connection.nonce}:${args.requestKey}`
9718
+ )}`;
9719
+ await args.conversations.create({
9720
+ conversationId: sessionId,
9721
+ user: args.user
9722
+ });
9723
+ return sessionId;
9724
+ }
9725
+ async function acceptPrompt(args) {
9726
+ const params = parseParams(
9727
+ promptParamsSchema,
9728
+ args.call.params
9729
+ );
9730
+ const admission = await args.conversations.prompt({
9731
+ conversationId: params.sessionId,
9732
+ idempotencyKey: `${args.connection.nonce}:${args.requestKey}`,
9733
+ text: promptText(params.prompt),
9734
+ user: args.user
9735
+ });
9736
+ if (admission.status === "not_found") {
9737
+ throw acp4.RequestError.resourceNotFound(params.sessionId);
9738
+ }
9739
+ if (admission.status === "active") {
9740
+ throw acp4.RequestError.invalidParams(
9741
+ { field: "sessionId" },
9742
+ "This ACP session already has an active prompt"
9743
+ );
9744
+ }
9745
+ return {
9746
+ output: {
9747
+ afterSeq: admission.afterCursor,
9748
+ kind: "prompt",
9749
+ messageId: admission.messageId,
9750
+ requestId: args.requestId,
9751
+ turnId: admission.turnId
9752
+ },
9753
+ sessionId: params.sessionId
9754
+ };
9755
+ }
9756
+ async function createRequestReceipt(args) {
9757
+ if (args.call.method === acp4.methods.agent.session.new) {
9758
+ const sessionId = await createSession(args);
9759
+ return {
9760
+ outputs: [
9761
+ {
9762
+ kind: "message",
9763
+ message: resultMessage3(args.call.id, { sessionId })
9764
+ }
9765
+ ]
9766
+ };
9767
+ }
9768
+ if (args.call.method === acp4.methods.agent.session.load) {
9769
+ const params = parseParams(
9770
+ loadSessionParamsSchema,
9771
+ args.call.params
9772
+ );
9773
+ rejectUnsupportedMcpServers(params.mcpServers);
9774
+ if (!await args.conversations.hasAccess({
9775
+ conversationId: params.sessionId,
9776
+ user: args.user
9777
+ })) {
9778
+ throw acp4.RequestError.resourceNotFound(params.sessionId);
9779
+ }
9780
+ return {
9781
+ sessionId: params.sessionId,
9782
+ outputs: [
9783
+ { kind: "replay" },
9784
+ { kind: "message", message: resultMessage3(args.call.id, {}) }
9785
+ ]
9786
+ };
9787
+ }
9788
+ if (args.call.method === acp4.methods.agent.session.prompt) {
9789
+ const prompt = await acceptPrompt({
9790
+ ...args,
9791
+ requestId: args.call.id
9792
+ });
9793
+ return {
9794
+ outputs: [prompt.output],
9795
+ sessionId: prompt.sessionId
9796
+ };
9797
+ }
9798
+ throw acp4.RequestError.methodNotFound(args.call.method);
9799
+ }
9800
+ async function handleCancelNotification(args) {
9801
+ const params = parseParams(
9802
+ cancelParamsSchema,
9803
+ args.call.params
9804
+ );
9805
+ const result = await args.conversations.cancel({
9806
+ conversationId: params.sessionId,
9807
+ user: args.user
9808
+ });
9809
+ if (result === "not_found") {
9810
+ throw acp4.RequestError.resourceNotFound(params.sessionId);
9811
+ }
9812
+ }
9813
+ async function requireConnection(args) {
9814
+ const value = await readAcpConnection(args.state, args.connectionId);
9815
+ if (!value || !hasAcpConnectionCredential(args.request, value.credentialHash)) {
9816
+ return void 0;
9817
+ }
9818
+ return value;
9819
+ }
9820
+ function isJsonContentType(contentType) {
9821
+ return contentType?.split(";", 1)[0]?.trim().toLowerCase() === ACP_JSON_MIME_TYPE;
8203
9822
  }
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;
9823
+ async function handleInitialize(args) {
9824
+ const requestId = args.call.id;
9825
+ if (requestId === void 0) {
9826
+ return textResponse("Initialize request must include an ID", 400);
8213
9827
  }
8214
- if (!conversation || !hasRunnableConversationWork(conversation)) {
8215
- return true;
9828
+ try {
9829
+ parseParams(initializeParamsSchema, args.call.params);
9830
+ if (!supportsUrlElicitation(args.call.params)) {
9831
+ throw acp4.RequestError.invalidParams(
9832
+ { field: "clientCapabilities.elicitation.url" },
9833
+ "Remote Junior authentication requires URL elicitation support"
9834
+ );
9835
+ }
9836
+ const credential = createAcpConnectionCredential(args.request);
9837
+ const result = {
9838
+ protocolVersion: acp4.PROTOCOL_VERSION,
9839
+ agentCapabilities: {
9840
+ loadSession: true,
9841
+ promptCapabilities: {
9842
+ image: false,
9843
+ audio: false,
9844
+ embeddedContext: false
9845
+ }
9846
+ },
9847
+ authMethods: args.browserAuthAvailable ? [ACP_AUTH_METHOD] : [],
9848
+ agentInfo: { name: "junior", version: args.version }
9849
+ };
9850
+ const connection = await createAcpConnection(
9851
+ args.state,
9852
+ credential.credentialHash
9853
+ );
9854
+ return jsonResponse(resultMessage3(requestId, result), 200, {
9855
+ [ACP_CONNECTION_ID_HEADER]: connection.connectionId,
9856
+ "Set-Cookie": credential.cookie
9857
+ });
9858
+ } catch (error) {
9859
+ if (!(error instanceof acp4.RequestError)) throw error;
9860
+ return jsonResponse(errorMessage2(requestId, error), 200);
8216
9861
  }
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
- );
8225
9862
  }
8226
- async function latestEventCursor(eventStore, conversationId) {
8227
- const page = await eventStore.query(conversationId, { limit: 1 });
8228
- return page.events.at(-1)?.seq ?? 0;
9863
+ async function handleConnectedPost(args) {
9864
+ const requestId = args.call.id;
9865
+ if (requestId === void 0) {
9866
+ if (args.authenticated && args.call.method === acp4.methods.agent.session.cancel) {
9867
+ try {
9868
+ await handleCancelNotification({
9869
+ call: args.call,
9870
+ conversations: args.options.conversations,
9871
+ user: args.authenticated
9872
+ });
9873
+ } catch (error) {
9874
+ if (!(error instanceof acp4.RequestError)) throw error;
9875
+ }
9876
+ }
9877
+ return emptyResponse(202);
9878
+ }
9879
+ const identifiedCall = { ...args.call, id: requestId };
9880
+ const identifiedRequestKey = requestKey(identifiedCall);
9881
+ const acceptance = {
9882
+ connectionId: args.connectionId,
9883
+ requestKey: identifiedRequestKey,
9884
+ state: args.state,
9885
+ createReceipt: async () => {
9886
+ try {
9887
+ if (identifiedCall.method === acp4.methods.agent.authenticate) {
9888
+ const params = parseParams(
9889
+ authenticateParamsSchema,
9890
+ identifiedCall.params
9891
+ );
9892
+ if (params.methodId !== ACP_AUTH_METHOD_ID) {
9893
+ throw acp4.RequestError.invalidParams(
9894
+ { field: "methodId" },
9895
+ "Junior did not advertise this authentication method"
9896
+ );
9897
+ }
9898
+ if (args.authenticated) {
9899
+ return {
9900
+ outputs: [
9901
+ {
9902
+ kind: "message",
9903
+ message: resultMessage3(requestId, {})
9904
+ }
9905
+ ]
9906
+ };
9907
+ }
9908
+ if (!args.options.browserAuth) {
9909
+ throw acp4.RequestError.internalError(
9910
+ void 0,
9911
+ "Junior ACP authentication requires an enabled dashboard"
9912
+ );
9913
+ }
9914
+ return await beginAcpAuthorization({
9915
+ baseURL: args.options.browserAuth.baseURL,
9916
+ connectionId: args.connectionId,
9917
+ credentialHash: args.connection.credentialHash,
9918
+ request: args.request,
9919
+ requestId,
9920
+ requestKey: identifiedRequestKey,
9921
+ state: args.state
9922
+ });
9923
+ }
9924
+ if (!args.authenticated) {
9925
+ throw acp4.RequestError.authRequired();
9926
+ }
9927
+ return await createRequestReceipt({
9928
+ call: identifiedCall,
9929
+ connection: args.connection,
9930
+ conversations: args.options.conversations,
9931
+ requestKey: identifiedRequestKey,
9932
+ user: args.authenticated
9933
+ });
9934
+ } catch (error) {
9935
+ if (!(error instanceof acp4.RequestError)) throw error;
9936
+ const receipt = {
9937
+ outputs: [
9938
+ {
9939
+ kind: "message",
9940
+ message: errorMessage2(requestId, error)
9941
+ }
9942
+ ]
9943
+ };
9944
+ if (args.route.sessionId) receipt.sessionId = args.route.sessionId;
9945
+ return receipt;
9946
+ }
9947
+ }
9948
+ };
9949
+ if (identifiedCall.method === acp4.methods.agent.session.prompt) {
9950
+ acceptance.reserveRoute = args.route;
9951
+ }
9952
+ const status = await acceptAcpRequest(acceptance);
9953
+ if (status === "busy") {
9954
+ return textResponse("ACP request is already being accepted", 503);
9955
+ }
9956
+ if (status === "expired") {
9957
+ return textResponse("Unknown Acp-Connection-Id", 404);
9958
+ }
9959
+ if (status === "full") {
9960
+ return textResponse("ACP stream has too much undelivered output", 503);
9961
+ }
9962
+ return emptyResponse(202);
8229
9963
  }
8230
- function createAdapterConversations(options) {
8231
- return {
8232
- async cancel({ conversationId, user }) {
8233
- if (!await hasConversationAccess(conversationId, user)) {
8234
- return "not_found";
9964
+ async function handleConnectedResponse(args) {
9965
+ await handleAcpAuthorizationResponse({
9966
+ connectionId: args.connectionId,
9967
+ response: args.response,
9968
+ state: args.state
9969
+ });
9970
+ return emptyResponse(202);
9971
+ }
9972
+ function createAcpHttpHandler(options) {
9973
+ const state = options.state;
9974
+ return async (request) => {
9975
+ let authenticated;
9976
+ let call;
9977
+ let connectionId;
9978
+ let sessionId;
9979
+ try {
9980
+ if (request.headers.has("Authorization")) {
9981
+ return new Response("Unauthorized", { status: 401 });
8235
9982
  }
8236
- await stopApiConversationTurn({
8237
- conversationId,
8238
- conversationStore: options.conversationStore,
8239
- queue: options.queue,
8240
- state: options.state
8241
- });
8242
- return "cancelled";
8243
- },
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" };
9983
+ const connectionHeader = request.headers.get(ACP_CONNECTION_ID_HEADER);
9984
+ if (connectionHeader !== null) {
9985
+ const parsed = acpConnectionIdSchema.safeParse(connectionHeader);
9986
+ if (!parsed.success) {
9987
+ return textResponse("Invalid Acp-Connection-Id", 400);
9988
+ }
9989
+ connectionId = parsed.data;
8259
9990
  }
8260
- const currentCursor = await latestEventCursor(
8261
- options.eventStore,
8262
- conversationId
8263
- );
8264
- if (await getAuthPausedApiTurnId(conversationId)) {
8265
- return { status: "active" };
9991
+ const sessionHeader = request.headers.get(ACP_SESSION_ID_HEADER);
9992
+ if (sessionHeader !== null) {
9993
+ const parsed = acpSessionIdSchema.safeParse(sessionHeader);
9994
+ if (!parsed.success) {
9995
+ return textResponse("Invalid Acp-Session-Id", 400);
9996
+ }
9997
+ sessionId = parsed.data;
8266
9998
  }
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,
8277
- queue: options.queue,
8278
- state: options.state
9999
+ if (request.method === "POST") {
10000
+ if (!isJsonContentType(request.headers.get("Content-Type"))) {
10001
+ return textResponse("Unsupported Media Type", 415);
8279
10002
  }
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
10003
+ let value;
10004
+ try {
10005
+ value = await request.json();
10006
+ } catch {
10007
+ return textResponse("Invalid JSON", 400);
10008
+ }
10009
+ if (Array.isArray(value)) {
10010
+ return textResponse(
10011
+ "Batch JSON-RPC requests are not implemented",
10012
+ 501
10013
+ );
10014
+ }
10015
+ const parsedMessage = acpInboundMessageSchema.safeParse(value);
10016
+ if (!parsedMessage.success) {
10017
+ return textResponse("Invalid JSON-RPC message", 400);
10018
+ }
10019
+ const message = parsedMessage.data;
10020
+ if ("method" in message && message.method === acp4.methods.agent.initialize) {
10021
+ call = message;
10022
+ if (connectionId) {
10023
+ return textResponse(
10024
+ "Initialize not allowed on existing connection",
10025
+ 400
10026
+ );
8310
10027
  }
8311
- ] : [];
10028
+ return await handleInitialize({
10029
+ browserAuthAvailable: options.browserAuth !== void 0,
10030
+ call,
10031
+ request,
10032
+ state,
10033
+ version: options.version
10034
+ });
10035
+ }
10036
+ if (!connectionId) {
10037
+ return textResponse("Missing Acp-Connection-Id", 400);
10038
+ }
10039
+ const connection2 = await requireConnection({
10040
+ connectionId,
10041
+ request,
10042
+ state
10043
+ });
10044
+ if (!connection2) {
10045
+ return textResponse("Unauthorized", 401);
10046
+ }
10047
+ authenticated = authenticatedAcpUser(connection2);
10048
+ if (!("method" in message)) {
10049
+ return await handleConnectedResponse({
10050
+ connectionId,
10051
+ response: message,
10052
+ state
10053
+ });
10054
+ }
10055
+ call = message;
10056
+ const route = determineRoute(connectionId, call, sessionId);
10057
+ if (route instanceof Response) return route;
10058
+ return await handleConnectedPost({
10059
+ authenticated,
10060
+ call,
10061
+ connection: connection2,
10062
+ connectionId,
10063
+ options,
10064
+ request,
10065
+ route,
10066
+ state
10067
+ });
10068
+ }
10069
+ if (!connectionId) {
10070
+ return textResponse("Missing Acp-Connection-Id", 400);
10071
+ }
10072
+ const connection = await requireConnection({
10073
+ connectionId,
10074
+ request,
10075
+ state
8312
10076
  });
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"
10077
+ if (!connection) {
10078
+ return textResponse("Unauthorized", 401);
10079
+ }
10080
+ authenticated = authenticatedAcpUser(connection);
10081
+ if (request.method === "GET") {
10082
+ if (request.headers.get("Upgrade")?.toLowerCase() === "websocket") {
10083
+ return textResponse("WebSocket upgrade is not implemented", 426);
10084
+ }
10085
+ const accept = request.headers.get("Accept")?.toLowerCase();
10086
+ if (!accept?.includes(ACP_EVENT_STREAM_MIME_TYPE)) {
10087
+ return textResponse("Not Acceptable", 406);
10088
+ }
10089
+ const route = { connectionId };
10090
+ if (sessionId) route.sessionId = sessionId;
10091
+ const stream = {
10092
+ conversations: options.conversations,
10093
+ onError: options.onError,
10094
+ requestSignal: request.signal,
10095
+ route,
10096
+ state
10097
+ };
10098
+ if (!sessionId) {
10099
+ stream.maintain = async () => {
10100
+ await expireAcpAuthorization({
10101
+ connectionId: route.connectionId,
10102
+ state
10103
+ });
8331
10104
  };
8332
- } else if (data.type === "turn_failed" && data.turnId === turnId) {
8333
- terminal = { failureCode: data.failureCode, status: "failed" };
8334
10105
  }
10106
+ if (authenticated) stream.userId = authenticated.id;
10107
+ return await openAcpSse(stream);
8335
10108
  }
8336
- const turnPage = { cursor, messages };
8337
- if (terminal) turnPage.terminal = terminal;
8338
- return turnPage;
10109
+ if (request.method === "DELETE") {
10110
+ await deleteAcpConnection(state, connectionId);
10111
+ return emptyResponse(202);
10112
+ }
10113
+ return textResponse("Method Not Allowed", 405);
10114
+ } catch (error) {
10115
+ const conversationId = sessionIdFromCall(call) ?? sessionId;
10116
+ const context = {};
10117
+ if (authenticated) context.userId = authenticated.id;
10118
+ if (connectionId) context.connectionId = connectionId;
10119
+ if (conversationId) context.conversationId = conversationId;
10120
+ options.onError?.(error, "acp.transport.exception", context);
10121
+ return textResponse("ACP transport failed", 500);
8339
10122
  }
8340
10123
  };
8341
10124
  }
8342
10125
 
10126
+ // src/api/acp/authorization-page.ts
10127
+ var ACP_AUTHORIZATION_PATH = "/_junior/acp/auth/:transactionId";
10128
+ 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;
10129
+ function escapeHtml(value) {
10130
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&#039;");
10131
+ }
10132
+ function renderResult(agentName, result) {
10133
+ const content = {
10134
+ blocked: {
10135
+ status: 403,
10136
+ title: "Request blocked",
10137
+ message: "Open the sign-in link again and confirm the connection."
10138
+ },
10139
+ busy: {
10140
+ status: 503,
10141
+ title: "Your ACP client has pending output",
10142
+ message: "Reconnect the client, then confirm this connection again."
10143
+ },
10144
+ completed: {
10145
+ status: 200,
10146
+ title: `${agentName} is connected`,
10147
+ message: "You can close this window and return to your ACP client."
10148
+ },
10149
+ conflict: {
10150
+ status: 409,
10151
+ title: "This connection uses another account",
10152
+ message: "Start a new connection in your ACP client and sign in again."
10153
+ },
10154
+ expired: {
10155
+ status: 410,
10156
+ title: "This sign-in request expired",
10157
+ message: "Return to your ACP client and start sign-in again."
10158
+ },
10159
+ invalid: {
10160
+ status: 403,
10161
+ title: "Verification code did not match",
10162
+ message: "Return to your ACP client and enter the code shown there."
10163
+ }
10164
+ }[result];
10165
+ return new Response(
10166
+ `<!DOCTYPE html>
10167
+ <html lang="en">
10168
+ <head>
10169
+ <meta charset="utf-8" />
10170
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
10171
+ <title>${escapeHtml(content.title)}</title>
10172
+ </head>
10173
+ <body style="margin:0;background:#000;color:#fff;font-family:ui-sans-serif,system-ui,sans-serif">
10174
+ <main style="min-height:100vh;display:grid;place-items:center;padding:2rem">
10175
+ <section style="max-width:32rem;border-left:4px solid #a78bfa;padding-left:1rem">
10176
+ <h1 style="margin:0 0 .75rem;font-size:2rem">${escapeHtml(content.title)}</h1>
10177
+ <p style="margin:0;color:#b8b8b8;line-height:1.5">${escapeHtml(content.message)}</p>
10178
+ </section>
10179
+ </main>
10180
+ </body>
10181
+ </html>`,
10182
+ {
10183
+ status: content.status,
10184
+ headers: {
10185
+ "cache-control": "no-store",
10186
+ "content-security-policy": "frame-ancestors 'none'; form-action 'self'",
10187
+ "content-type": "text/html; charset=utf-8",
10188
+ "x-frame-options": "DENY"
10189
+ }
10190
+ }
10191
+ );
10192
+ }
10193
+ function renderConfirmation(agentName) {
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>Connect ${escapeHtml(agentName)}</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">Connect ${escapeHtml(agentName)}</h1>
10206
+ <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>
10207
+ <form method="post">
10208
+ <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>
10209
+ <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>
10210
+ </form>
10211
+ </section>
10212
+ </main>
10213
+ </body>
10214
+ </html>`,
10215
+ {
10216
+ headers: {
10217
+ "cache-control": "no-store",
10218
+ "content-security-policy": "frame-ancestors 'none'; form-action 'self'",
10219
+ "content-type": "text/html; charset=utf-8",
10220
+ "x-frame-options": "DENY"
10221
+ }
10222
+ }
10223
+ );
10224
+ }
10225
+ function requestHasSameOrigin(request) {
10226
+ const origin = request.headers.get("origin");
10227
+ if (!origin) return false;
10228
+ try {
10229
+ return new URL(origin).origin === new URL(request.url).origin;
10230
+ } catch {
10231
+ return false;
10232
+ }
10233
+ }
10234
+ function transactionIdFromRequest(request) {
10235
+ const transactionId = new URL(request.url).pathname.split("/").at(-1);
10236
+ return transactionId && UUID_PATTERN.test(transactionId) ? transactionId : void 0;
10237
+ }
10238
+ async function handleAcpAuthorizationPage(args) {
10239
+ const transactionId = transactionIdFromRequest(args.request);
10240
+ if (!transactionId) return renderResult(args.agentName, "expired");
10241
+ if (args.request.method === "GET") {
10242
+ return renderConfirmation(args.agentName);
10243
+ }
10244
+ if (args.request.method !== "POST" || !requestHasSameOrigin(args.request)) {
10245
+ return renderResult(args.agentName, "blocked");
10246
+ }
10247
+ let userCode;
10248
+ try {
10249
+ const value = (await args.request.formData()).get("code");
10250
+ userCode = typeof value === "string" ? value : void 0;
10251
+ } catch {
10252
+ }
10253
+ if (!userCode?.trim()) return renderResult(args.agentName, "invalid");
10254
+ const result = await completeAcpAuthorization({
10255
+ state: args.state,
10256
+ transactionId,
10257
+ user: args.user,
10258
+ userCode
10259
+ });
10260
+ return renderResult(args.agentName, result);
10261
+ }
10262
+
8343
10263
  // src/app.ts
8344
- function resolveAdapterBaseURL(dashboard) {
10264
+ function resolveAcpBaseURL(dashboard) {
8345
10265
  const configured = dashboard?.baseURL?.trim() || process.env.JUNIOR_BASE_URL?.trim();
8346
10266
  if (configured) return configured;
8347
10267
  const vercelURL = process.env.VERCEL_PROJECT_PRODUCTION_URL?.trim() || process.env.VERCEL_URL?.trim();
@@ -8501,6 +10421,7 @@ function dashboardHostRoutePaths(dashboard, authenticatedRoutes = []) {
8501
10421
  const peoplePath = pagePath("people");
8502
10422
  const pagePaths = [
8503
10423
  basePath,
10424
+ pagePath("code"),
8504
10425
  conversationsPath,
8505
10426
  `${conversationsPath}/*`,
8506
10427
  pagePath("locations"),
@@ -8540,6 +10461,7 @@ function dashboardHostRoutePaths(dashboard, authenticatedRoutes = []) {
8540
10461
  "/api/tasks",
8541
10462
  "/api/tasks/*",
8542
10463
  "/api/skills",
10464
+ "/api/code",
8543
10465
  "/api/stats",
8544
10466
  "/api/conversations",
8545
10467
  "/api/conversations/*",
@@ -8650,11 +10572,11 @@ function validateDashboardRouteOwnership(args) {
8650
10572
  function mountRoutes(app, routes) {
8651
10573
  for (const route of routes) {
8652
10574
  const handler = (c) => route.handler(c.req.raw);
8653
- const methods = typeof route.method === "string" ? [route.method] : route.method ?? ["ALL"];
8654
- const explicitMethods = methods.filter(
10575
+ const methods5 = typeof route.method === "string" ? [route.method] : route.method ?? ["ALL"];
10576
+ const explicitMethods = methods5.filter(
8655
10577
  (method) => method !== "ALL"
8656
10578
  );
8657
- if (methods.includes("ALL")) {
10579
+ if (methods5.includes("ALL")) {
8658
10580
  app.all(route.path, handler);
8659
10581
  } else if (explicitMethods.length > 0) {
8660
10582
  app.on(explicitMethods, route.path, handler);
@@ -8703,6 +10625,27 @@ async function createApp(options) {
8703
10625
  let pluginApiRoutes = [];
8704
10626
  const resolveResourceEventTeamId = createResourceEventTeamIdResolver();
8705
10627
  const resourceEvents = {
10628
+ async neededMatchKeys(input) {
10629
+ const teamId = await resolveResourceEventTeamId();
10630
+ if (!teamId) return [];
10631
+ const conversationWork = getConversationWorkOptions();
10632
+ const [watchKeys, taskKeys] = await Promise.all([
10633
+ collectResourceEventMatchKeys({
10634
+ eventTypes: input.eventTypes,
10635
+ identifiers: input.identifiers,
10636
+ namespace: input.namespace,
10637
+ state: conversationWork.state,
10638
+ teamId
10639
+ }),
10640
+ collectEventTaskMatchKeys(getDb(), {
10641
+ eventTypes: input.eventTypes,
10642
+ identifiers: input.identifiers,
10643
+ namespace: input.namespace,
10644
+ teamId
10645
+ })
10646
+ ]);
10647
+ return [.../* @__PURE__ */ new Set([...watchKeys, ...taskKeys])].sort();
10648
+ },
8706
10649
  async publish(event) {
8707
10650
  const teamId = await resolveResourceEventTeamId();
8708
10651
  if (!teamId) {
@@ -8778,37 +10721,41 @@ async function createApp(options) {
8778
10721
  });
8779
10722
  return conversationWorkOptions;
8780
10723
  };
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
- );
10724
+ let acpRuntime;
10725
+ const getAcpRuntime = () => {
10726
+ if (acpRuntime) return acpRuntime;
10727
+ const work = getConversationWorkOptions();
10728
+ const state = work.state ?? getStateAdapter();
10729
+ acpRuntime = {
10730
+ handleRequest: createAcpHttpHandler({
10731
+ ...dashboard && !dashboard.disabled ? { browserAuth: { baseURL: resolveAcpBaseURL(dashboard) } } : void 0,
10732
+ conversations: createAcpConversations({
10733
+ conversationStore: work.conversationStore,
10734
+ eventStore: getConversationEventStore(),
10735
+ queue: work.queue ?? getVercelConversationWorkQueue(),
10736
+ state
10737
+ }),
10738
+ onError: (error, event, attributes) => void logException(error, event, { ...attributes, platform: "acp" }),
10739
+ state,
10740
+ version: JUNIOR_VERSION
10741
+ }),
10742
+ state
10743
+ };
10744
+ return acpRuntime;
10745
+ };
10746
+ const authenticatedRoutes = dashboard && !dashboard.disabled ? [
10747
+ {
10748
+ handler: (request, user) => handleAcpAuthorizationPage({
10749
+ agentName: botConfig.userName,
10750
+ request,
10751
+ state: getAcpRuntime().state,
10752
+ user
10753
+ }),
10754
+ method: ["GET", "POST"],
10755
+ path: ACP_AUTHORIZATION_PATH
8811
10756
  }
10757
+ ] : [];
10758
+ try {
8812
10759
  validateDashboardRouteOwnership({
8813
10760
  authenticatedRoutes,
8814
10761
  dashboard,
@@ -8833,8 +10780,12 @@ async function createApp(options) {
8833
10780
  next
8834
10781
  );
8835
10782
  });
10783
+ app.on(
10784
+ ["GET", "POST", "DELETE"],
10785
+ "/api/acp",
10786
+ (c) => getAcpRuntime().handleRequest(c.req.raw)
10787
+ );
8836
10788
  mountRoutes(app, pluginRoutes);
8837
- mountRoutes(app, adapterRoutes);
8838
10789
  mountRoutes(
8839
10790
  app,
8840
10791
  await createDashboardRouteRegistrations({