@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.
- package/dist/{agent-hooks-3S25UIHO.js → agent-hooks-XTZVALGQ.js} +11 -11
- package/dist/api/acp/auth.d.ts +46 -0
- package/dist/api/acp/authorization-page.d.ts +11 -0
- package/dist/api/acp/conversations.d.ts +70 -0
- package/dist/api/acp/errors.d.ts +6 -0
- package/dist/api/acp/route.d.ts +16 -0
- package/dist/api/acp/schema.d.ts +97 -0
- package/dist/api/acp/sleep.d.ts +2 -0
- package/dist/api/acp/state.d.ts +22 -0
- package/dist/api/acp/transport.d.ts +113 -0
- package/dist/api/code/overview.d.ts +35 -0
- package/dist/api/code/routes.d.ts +4 -0
- package/dist/api/schema/code.d.ts +75 -0
- package/dist/api/schema.d.ts +2 -0
- package/dist/api/schema.js +9 -1
- package/dist/api.js +258 -134
- package/dist/app.d.ts +0 -4
- package/dist/app.js +2171 -220
- package/dist/{catalog-runtime-ESLMBQBV.js → catalog-runtime-5357XR4F.js} +4 -4
- package/dist/chat/agent-invocations/store.d.ts +4 -2
- package/dist/chat/agent-invocations/work.d.ts +1 -3
- package/dist/chat/app/factory.d.ts +3 -3
- package/dist/chat/app/services.d.ts +3 -3
- package/dist/chat/code/publisher.d.ts +3 -0
- package/dist/chat/code/store.d.ts +9 -0
- package/dist/chat/event-tasks/store.d.ts +10 -0
- package/dist/chat/ingress/slack-webhook.d.ts +1 -1
- package/dist/chat/plugins/agent-hooks.d.ts +5 -0
- package/dist/chat/{runtime/slack-runtime.d.ts → providers/slack/runtime.d.ts} +2 -2
- package/dist/chat/{runtime/reply-executor.d.ts → providers/slack/turn.d.ts} +10 -9
- package/dist/chat/resource-events/store.d.ts +12 -0
- package/dist/chat/runtime/turn-execution.d.ts +30 -0
- package/dist/chat/slack/dispatch-turn.d.ts +12 -14
- package/dist/chat/task-execution/slack-work.d.ts +1 -1
- package/dist/{chunk-SXOZIG5X.js → chunk-2ZQPVX7C.js} +6 -6
- package/dist/{chunk-UQP54CXZ.js → chunk-3S7ULUWW.js} +1 -1
- package/dist/{chunk-2CEOXDG2.js → chunk-4CNDGK25.js} +174 -6
- package/dist/{chunk-3THPTNN6.js → chunk-4RONS3WR.js} +4 -4
- package/dist/{chunk-LHQK5XCU.js → chunk-6UDPINOX.js} +2 -2
- package/dist/{chunk-BZD4ADQH.js → chunk-6VOYP4M6.js} +2 -3
- package/dist/{chunk-QHALFHZA.js → chunk-6WGC3SO4.js} +9 -9
- package/dist/{chunk-BDDWVY32.js → chunk-ACPDVEJO.js} +22 -20
- package/dist/{chunk-44BQ3TSK.js → chunk-BCHYQS4B.js} +233 -173
- package/dist/{chunk-SVFSYQMT.js → chunk-BHYRYYLY.js} +1 -1
- package/dist/{chunk-LT4QYGKM.js → chunk-BT53RIGJ.js} +160 -97
- package/dist/{chunk-Z7ALSQS3.js → chunk-DUKIDPF6.js} +1 -1
- package/dist/{chunk-PQES5LB5.js → chunk-E57WPXWY.js} +5 -5
- package/dist/{chunk-GGYA5NPN.js → chunk-FKEZZMXS.js} +8 -8
- package/dist/{chunk-LB4FWMCV.js → chunk-HIPUIPJM.js} +183 -155
- package/dist/{chunk-3NFHI2NA.js → chunk-HUXFMDBZ.js} +1 -1
- package/dist/{chunk-6TSWXBTQ.js → chunk-IZDYYYGP.js} +3 -3
- package/dist/{chunk-YHL2L2ZZ.js → chunk-JA43DV4Y.js} +1 -1
- package/dist/{chunk-V5UZCCNW.js → chunk-JM2COLGC.js} +6 -6
- package/dist/{chunk-A7THOQ6N.js → chunk-JNO2GUDS.js} +21 -21
- package/dist/{chunk-KGMHUDVF.js → chunk-K7VDC2QW.js} +6 -6
- package/dist/{chunk-CVBIM4Z3.js → chunk-LCP4EDKR.js} +1 -1
- package/dist/{chunk-4DZ62L36.js → chunk-LHXIZLFF.js} +1 -1
- package/dist/{chunk-D6OHGAIO.js → chunk-M5YWNMTE.js} +5 -5
- package/dist/{chunk-3ENUMZNG.js → chunk-MAPJFUMV.js} +3 -3
- package/dist/{chunk-UUMBHORH.js → chunk-MLSQX2EX.js} +1 -1
- package/dist/{chunk-WOIALVN2.js → chunk-PJPRXO2T.js} +1 -1
- package/dist/{chunk-VHMAFMHB.js → chunk-QABCY33S.js} +1 -1
- package/dist/{chunk-UKIHNBBD.js → chunk-SHJK75Z2.js} +3 -3
- package/dist/{chunk-S35OZMSL.js → chunk-UBOIHNTH.js} +13 -13
- package/dist/{chunk-DZR3L74W.js → chunk-UF6BUA5W.js} +3 -3
- package/dist/{chunk-JIOKUIAS.js → chunk-WQFEWULF.js} +2 -2
- package/dist/chunk-WVIA7V3M.js +975 -0
- package/dist/{chunk-MJISSOWB.js → chunk-YKX6MTLU.js} +1 -1
- package/dist/{chunk-TEFNHMO2.js → chunk-Z3QAXJR7.js} +4 -4
- package/dist/cli/chat.js +33 -35
- package/dist/cli/check.js +5 -5
- package/dist/cli/plugins.js +12 -12
- package/dist/cli/snapshot-create.js +9 -9
- package/dist/cli/upgrade.js +6 -6
- package/dist/db/schema/code.d.ts +367 -0
- package/dist/db/schema.d.ts +367 -1
- package/dist/{db-NYIWZ4L5.js → db-MA52H62H.js} +6 -6
- package/dist/instrumentation.js +3 -3
- package/dist/nitro.js +6 -6
- package/dist/{oauth-callback-server-A2YTPVVX.js → oauth-callback-server-DXGHKN6S.js} +25 -25
- package/dist/{runner-VKAEU52J.js → runner-4IXUAQNK.js} +18 -18
- package/dist/{skills-52GMXUTY.js → skills-X3DY7AXO.js} +5 -5
- package/dist/spawn-43WGS6CH.js +37 -0
- package/dist/task-runner-VDRKITPV.js +32 -0
- package/dist/{validation-VD2YQX2W.js → validation-Z6JXSTJS.js} +5 -5
- package/dist/version.js +1 -1
- package/dist/{work-ZWBTGEQP.js → work-OKWRFBDW.js} +18 -18
- package/dist/worker-B236AE72.js +22 -0
- package/migrations/0036_complex_rage.sql +32 -0
- package/migrations/meta/0036_snapshot.json +3186 -0
- package/migrations/meta/_journal.json +8 -1
- package/package.json +6 -7
- package/dist/app-adapter.d.ts +0 -85
- package/dist/chat/api-turns/adapter-conversations.d.ts +0 -15
- package/dist/chunk-CZYNWPWD.js +0 -930
- package/dist/spawn-GSS4EBLR.js +0 -37
- package/dist/task-runner-DUGDWF5O.js +0 -32
- 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-
|
|
3
|
+
} from "./chunk-4RONS3WR.js";
|
|
4
4
|
import {
|
|
5
5
|
bindSpawnAgent
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-QABCY33S.js";
|
|
7
7
|
import {
|
|
8
8
|
createAgentInvocationWorker,
|
|
9
9
|
enqueueAgentInvocation,
|
|
10
10
|
listPendingAgentInvocationMailboxAppends,
|
|
11
11
|
routeAgentInvocationWork
|
|
12
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
57
|
-
import "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
106
|
+
} from "./chunk-UF6BUA5W.js";
|
|
107
107
|
import {
|
|
108
108
|
validatePluginEgressCredentialHooks,
|
|
109
109
|
validatePluginRegistrations
|
|
110
|
-
} from "./chunk-
|
|
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-
|
|
158
|
+
} from "./chunk-JNO2GUDS.js";
|
|
159
159
|
import {
|
|
160
160
|
AuthorizationFlowDisabledError,
|
|
161
161
|
PluginCredentialFailureError
|
|
162
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
200
|
+
} from "./chunk-HIPUIPJM.js";
|
|
200
201
|
import {
|
|
201
202
|
parseSlackThreadId
|
|
202
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
272
|
+
} from "./chunk-2ZQPVX7C.js";
|
|
272
273
|
import {
|
|
273
274
|
getSandboxResourceConfig,
|
|
274
275
|
setSandboxResourceConfig
|
|
275
|
-
} from "./chunk-
|
|
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-
|
|
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-
|
|
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-
|
|
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-
|
|
341
|
-
import "./chunk-
|
|
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-
|
|
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-
|
|
368
|
-
import "./chunk-
|
|
369
|
-
import "./chunk-
|
|
369
|
+
} from "./chunk-6UDPINOX.js";
|
|
370
|
+
import "./chunk-BHYRYYLY.js";
|
|
371
|
+
import "./chunk-WQFEWULF.js";
|
|
370
372
|
import {
|
|
371
373
|
pluginCatalogRuntime
|
|
372
|
-
} from "./chunk-
|
|
373
|
-
import "./chunk-
|
|
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-
|
|
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-
|
|
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
|
|
701
|
+
const errorMessage3 = storedSession?.errorMessage;
|
|
700
702
|
if (dispatchOutcome) {
|
|
701
703
|
return {
|
|
702
|
-
...
|
|
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
|
|
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" ?
|
|
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/
|
|
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.
|
|
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.
|
|
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/
|
|
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/
|
|
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
|
|
5743
|
-
return async function
|
|
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
|
|
7150
|
+
let errorMessage3;
|
|
7149
7151
|
let resultMessageTs;
|
|
7150
7152
|
const routing = buildDispatchRoutingContext(dispatch2);
|
|
7151
|
-
await options.
|
|
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
|
-
|
|
7176
|
+
errorMessage3 = result.errorMessage;
|
|
7175
7177
|
},
|
|
7176
7178
|
shouldYield: hooks.shouldYield,
|
|
7177
7179
|
skipBackfill: true
|
|
7178
7180
|
});
|
|
7179
7181
|
return {
|
|
7180
|
-
...
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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,
|
|
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/
|
|
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
|
|
8205
|
-
const
|
|
8206
|
-
|
|
8207
|
-
|
|
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
|
-
|
|
8215
|
-
|
|
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
|
|
8227
|
-
const
|
|
8228
|
-
|
|
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
|
|
8231
|
-
|
|
8232
|
-
|
|
8233
|
-
|
|
8234
|
-
|
|
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
|
-
|
|
8237
|
-
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
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
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
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
|
-
|
|
8268
|
-
{
|
|
8269
|
-
|
|
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
|
-
|
|
8282
|
-
|
|
8283
|
-
|
|
8284
|
-
|
|
8285
|
-
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
|
|
8290
|
-
|
|
8291
|
-
|
|
8292
|
-
|
|
8293
|
-
|
|
8294
|
-
|
|
8295
|
-
|
|
8296
|
-
|
|
8297
|
-
|
|
8298
|
-
|
|
8299
|
-
|
|
8300
|
-
|
|
8301
|
-
|
|
8302
|
-
|
|
8303
|
-
|
|
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
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
)
|
|
8318
|
-
|
|
8319
|
-
|
|
8320
|
-
|
|
8321
|
-
|
|
8322
|
-
|
|
8323
|
-
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
|
|
8329
|
-
|
|
8330
|
-
|
|
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
|
-
|
|
8337
|
-
|
|
8338
|
-
|
|
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("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'");
|
|
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
|
|
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
|
|
8654
|
-
const explicitMethods =
|
|
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 (
|
|
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
|
|
8782
|
-
|
|
8783
|
-
|
|
8784
|
-
const
|
|
8785
|
-
|
|
8786
|
-
|
|
8787
|
-
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
|
|
8791
|
-
|
|
8792
|
-
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
|
|
8802
|
-
|
|
8803
|
-
|
|
8804
|
-
|
|
8805
|
-
(
|
|
8806
|
-
|
|
8807
|
-
|
|
8808
|
-
|
|
8809
|
-
|
|
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({
|