@@ -190,6 +190,43 @@ describe("commitment extraction runtime", () => {
190190 expect ( store . commitments [ 0 ] ) . not . toHaveProperty ( "sourceAssistantText" ) ;
191191 } ) ;
192192
193+ it ( "partitions extraction batches by agent" , async ( ) => {
194+ const cfg = await createConfig ( ) ;
195+ const extractBatch = vi . fn ( async ( _params : { items : CommitmentExtractionItem [ ] } ) => ( {
196+ candidates : [ ] ,
197+ } ) ) ;
198+ configureCommitmentExtractionRuntime ( {
199+ forceInTests : true ,
200+ extractBatch,
201+ setTimer : ( ) => ( { unref ( ) { } } ) as ReturnType < typeof setTimeout > ,
202+ clearTimer : ( ) => undefined ,
203+ } ) ;
204+
205+ for ( const [ index , agentId ] of [ "alpha" , "beta" , "alpha" , "beta" ] . entries ( ) ) {
206+ expect (
207+ enqueueCommitmentExtraction ( {
208+ cfg,
209+ nowMs : nowMs + index ,
210+ agentId,
211+ sessionKey : `agent:${ agentId } :telegram:user-1` ,
212+ channel : "telegram" ,
213+ sourceMessageId : `m${ index } ` ,
214+ userText : `Commitment candidate ${ index } ` ,
215+ assistantText : "I will follow up." ,
216+ } ) ,
217+ ) . toBe ( true ) ;
218+ }
219+
220+ await expect ( drainCommitmentExtractionQueue ( ) ) . resolves . toBe ( 4 ) ;
221+ expect ( extractBatch ) . toHaveBeenCalledTimes ( 2 ) ;
222+ expect (
223+ extractBatch . mock . calls . map ( ( [ params ] ) => params . items . map ( ( item ) => item . agentId ) ) ,
224+ ) . toEqual ( [
225+ [ "alpha" , "alpha" ] ,
226+ [ "beta" , "beta" ] ,
227+ ] ) ;
228+ } ) ;
229+
193230 it ( "uses the configured agent model for the hidden extractor run" , async ( ) => {
194231 const cfg = await createConfig ( ) ;
195232 cfg . agents = {
@@ -568,6 +605,58 @@ describe("commitment extraction runtime", () => {
568605 expect ( extractBatch ) . toHaveBeenCalledTimes ( 1 ) ;
569606 } ) ;
570607
608+ it ( "keeps other agents queued after a terminal extraction failure" , async ( ) => {
609+ const cfg = await createConfig ( ) ;
610+ const scheduled : Array < ( ) => void > = [ ] ;
611+ const extractBatch = vi . fn ( async ( { items } : { items : CommitmentExtractionItem [ ] } ) => {
612+ if ( items [ 0 ] ?. agentId === "alpha" ) {
613+ throw new Error ( 'No API key found for provider "openai".' ) ;
614+ }
615+ return { candidates : [ ] } ;
616+ } ) ;
617+ configureCommitmentExtractionRuntime ( {
618+ forceInTests : true ,
619+ extractBatch,
620+ setTimer : ( callback ) => {
621+ scheduled . push ( callback ) ;
622+ return { unref ( ) { } } as ReturnType < typeof setTimeout > ;
623+ } ,
624+ clearTimer : ( ) => undefined ,
625+ } ) ;
626+
627+ for ( const agentId of [ "alpha" , "beta" ] ) {
628+ expect (
629+ enqueueCommitmentExtraction ( {
630+ cfg,
631+ nowMs,
632+ agentId,
633+ sessionKey : `agent:${ agentId } :telegram:user-1` ,
634+ channel : "telegram" ,
635+ userText : "I have an interview tomorrow." ,
636+ assistantText : "Good luck." ,
637+ } ) ,
638+ ) . toBe ( true ) ;
639+ }
640+
641+ expect ( scheduled ) . toHaveLength ( 1 ) ;
642+ scheduled [ 0 ] ?.( ) ;
643+ await vi . waitFor ( ( ) => {
644+ expect ( extractBatch ) . toHaveBeenCalledTimes ( 1 ) ;
645+ } ) ;
646+ await vi . waitFor ( ( ) => {
647+ expect ( scheduled ) . toHaveLength ( 2 ) ;
648+ } ) ;
649+
650+ scheduled [ 1 ] ?.( ) ;
651+ await vi . waitFor ( ( ) => {
652+ expect ( extractBatch ) . toHaveBeenCalledTimes ( 2 ) ;
653+ } ) ;
654+ expect ( extractBatch . mock . calls . map ( ( [ params ] ) => params . items [ 0 ] ?. agentId ) ) . toEqual ( [
655+ "alpha" ,
656+ "beta" ,
657+ ] ) ;
658+ } ) ;
659+
571660 it ( "schedules a retry when a non-terminal failure leaves the queue full" , async ( ) => {
572661 const cfg = await createConfig ( ) ;
573662 const scheduled : Array < ( ) => void > = [ ] ;
0 commit comments