11import type {
22 HeartbeatHookContext ,
33 PluginRegistration ,
4- DispatchOptions ,
5- DispatchResult ,
64} from "@sentry/junior-plugin-api" ;
75import {
86 bindEventTaskCredentialSubject ,
@@ -27,10 +25,6 @@ import {
2725
2826const MAX_DISPATCHES_PER_HEARTBEAT = 25 ;
2927
30- type EventTaskDispatchResult =
31- | DispatchResult
32- | ( DispatchResult & { deliveryError : unknown } ) ;
33-
3428function bindDispatchCredentialSubject (
3529 options : SlackDispatchOptions ,
3630 plugin : string ,
@@ -49,16 +43,11 @@ function bindDispatchCredentialSubject(
4943 plugin,
5044 subject : credentialSubject ,
5145 } )
52- : credentialSubject . allowedWhen === "event-task"
53- ? bindEventTaskCredentialSubject ( {
54- plugin,
55- subject : credentialSubject ,
56- } )
57- : bindSlackDirectCredentialSubject ( {
58- channelId : options . destination . channelId ,
59- teamId : options . destination . teamId ,
60- subject : credentialSubject ,
61- } ) ;
46+ : bindSlackDirectCredentialSubject ( {
47+ channelId : options . destination . channelId ,
48+ teamId : options . destination . teamId ,
49+ subject : credentialSubject ,
50+ } ) ;
6251 if ( ! boundSubject ) {
6352 throw new Error ( "Dispatch credentialSubject is not valid for this action" ) ;
6453 }
@@ -69,6 +58,30 @@ function bindDispatchCredentialSubject(
6958 } ;
7059}
7160
61+ async function dispatch ( args : {
62+ conversationWorkQueue : ConversationWorkQueue ;
63+ nowMs : number ;
64+ options : BoundDispatchOptions ;
65+ plugin : string ;
66+ } ) {
67+ await verifyDispatchCredentialSubjectAccess ( args . options , args . plugin ) ;
68+ const result = await createOrGetDispatch ( {
69+ plugin : args . plugin ,
70+ options : args . options ,
71+ nowMs : args . nowMs ,
72+ } ) ;
73+ if ( ! isTerminalDispatchStatus ( result . record . status ) ) {
74+ await enqueueAgentDispatch ( result . record , {
75+ queue : args . conversationWorkQueue ,
76+ nowMs : args . nowMs ,
77+ } ) ;
78+ }
79+ return {
80+ id : result . record . id ,
81+ status : result . status ,
82+ } ;
83+ }
84+
7285/** Build the plugin-scoped heartbeat context that gates durable dispatch access. */
7386export function createHeartbeatContext ( args : {
7487 conversationWorkQueue : ConversationWorkQueue ;
@@ -94,26 +107,14 @@ export function createHeartbeatContext(args: {
94107 if ( dispatchCount >= MAX_DISPATCHES_PER_HEARTBEAT ) {
95108 throw new Error ( "Plugin heartbeat exceeded the dispatch limit" ) ;
96109 }
97- await verifyDispatchCredentialSubjectAccess (
98- dispatchOptions ,
99- pluginName ,
100- ) ;
101- const result = await createOrGetDispatch ( {
102- plugin : pluginName ,
103- options : dispatchOptions ,
110+ const result = await dispatch ( {
111+ conversationWorkQueue : args . conversationWorkQueue ,
104112 nowMs : args . nowMs ,
113+ options : dispatchOptions ,
114+ plugin : pluginName ,
105115 } ) ;
106116 dispatchCount += 1 ;
107- if ( ! isTerminalDispatchStatus ( result . record . status ) ) {
108- await enqueueAgentDispatch ( result . record , {
109- queue : args . conversationWorkQueue ,
110- nowMs : args . nowMs ,
111- } ) ;
112- }
113- return {
114- id : result . record . id ,
115- status : result . status ,
116- } ;
117+ return result ;
117118 } ,
118119 async get ( id ) {
119120 return await getPluginDispatchProjection ( {
@@ -125,37 +126,39 @@ export function createHeartbeatContext(args: {
125126 } ;
126127}
127128
128- /** Create one core-owned dispatch and preserve queue failure after persistence . */
129+ /** Create and enqueue one core-owned event task dispatch . */
129130export async function dispatchEventTask ( args : {
130131 conversationWorkQueue : ConversationWorkQueue ;
131132 nowMs : number ;
132- options : DispatchOptions ;
133- } ) : Promise < EventTaskDispatchResult > {
133+ options : Omit < SlackDispatchOptions , "credentialSubject" > & {
134+ credentialSubject ?: {
135+ allowedWhen : "event-task" ;
136+ taskId : string ;
137+ type : "user" ;
138+ userId : string ;
139+ } ;
140+ } ;
141+ } ) {
134142 const plugin = "junior" ;
135- validateDispatchOptions ( args . options ) ;
136- const options = bindDispatchCredentialSubject ( args . options , plugin ) ;
137- await verifyDispatchCredentialSubjectAccess ( options , plugin ) ;
138- const result = await createOrGetDispatch ( {
139- plugin,
140- options,
141- nowMs : args . nowMs ,
142- } ) ;
143- if ( ! isTerminalDispatchStatus ( result . record . status ) ) {
144- try {
145- await enqueueAgentDispatch ( result . record , {
146- queue : args . conversationWorkQueue ,
147- nowMs : args . nowMs ,
148- } ) ;
149- } catch ( deliveryError ) {
150- return {
151- deliveryError,
152- id : result . record . id ,
153- status : result . status ,
154- } ;
155- }
143+ const { credentialSubject, ...unboundOptions } = args . options ;
144+ validateDispatchOptions ( { ...unboundOptions } ) ;
145+ const boundSubject = credentialSubject
146+ ? bindEventTaskCredentialSubject ( {
147+ plugin,
148+ subject : credentialSubject ,
149+ } )
150+ : undefined ;
151+ if ( credentialSubject && ! boundSubject ) {
152+ throw new Error ( "Dispatch credentialSubject is not valid for this action" ) ;
156153 }
157- return {
158- id : result . record . id ,
159- status : result . status ,
154+ const options : BoundDispatchOptions = {
155+ ... unboundOptions ,
156+ ... ( boundSubject ? { credentialSubject : boundSubject } : { } ) ,
160157 } ;
158+ return await dispatch ( {
159+ conversationWorkQueue : args . conversationWorkQueue ,
160+ nowMs : args . nowMs ,
161+ options,
162+ plugin,
163+ } ) ;
161164}
0 commit comments