@@ -361,7 +361,7 @@ func (*RootCmd) mcpConfigureCursor() *serpent.Command {
361
361
return cmd
362
362
}
363
363
364
- type reportTask struct {
364
+ type taskReport struct {
365
365
link string
366
366
messageID int64
367
367
selfReported bool
@@ -374,7 +374,7 @@ type mcpServer struct {
374
374
appStatusSlug string
375
375
client * codersdk.Client
376
376
llmClient * agentapi.Client
377
- queue * cliutil.Queue [reportTask ]
377
+ queue * cliutil.Queue [taskReport ]
378
378
}
379
379
380
380
func (r * RootCmd ) mcpServer () * serpent.Command {
@@ -388,9 +388,50 @@ func (r *RootCmd) mcpServer() *serpent.Command {
388
388
return & serpent.Command {
389
389
Use : "server" ,
390
390
Handler : func (inv * serpent.Invocation ) error {
391
+ // lastUserMessageID is the ID of the last *user* message that we saw. A
392
+ // user message only happens when interacting via the LLM agent API (as
393
+ // opposed to interacting with the terminal directly).
394
+ var lastUserMessageID int64
395
+ var lastReport taskReport
396
+ // Create a queue that skips duplicates and preserves summaries.
397
+ queue := cliutil.NewQueue [taskReport ](512 ).WithPredicate (func (report taskReport ) (taskReport , bool ) {
398
+ // Use "working" status if this is a new user message. If this is not a
399
+ // new user message, and the status is "working" and not self-reported
400
+ // (meaning it came from the screen watcher), then it means one of two
401
+ // things:
402
+ // 1. The LLM is still working, so there is nothing to update.
403
+ // 2. The LLM stopped working, then the user has interacted with the
404
+ // terminal directly. For now, we are ignoring these updates. This
405
+ // risks missing cases where the user manually submits a new prompt
406
+ // and the LLM becomes active and does not update itself, but it
407
+ // avoids spamming useless status updates as the user is typing, so
408
+ // the tradeoff is worth it. In the future, if we can reliably
409
+ // distinguish between user and LLM activity, we can change this.
410
+ if report .messageID > lastUserMessageID {
411
+ report .state = codersdk .WorkspaceAppStatusStateWorking
412
+ } else if report .state == codersdk .WorkspaceAppStatusStateWorking && ! report .selfReported {
413
+ return report , false
414
+ }
415
+ // Preserve previous message and URI if there was no message.
416
+ if report .summary == "" {
417
+ report .summary = lastReport .summary
418
+ if report .link == "" {
419
+ report .link = lastReport .link
420
+ }
421
+ }
422
+ // Avoid queueing duplicate updates.
423
+ if report .state == lastReport .state &&
424
+ report .link == lastReport .link &&
425
+ report .summary == lastReport .summary {
426
+ return report , false
427
+ }
428
+ lastReport = report
429
+ return report , true
430
+ })
431
+
391
432
srv := & mcpServer {
392
433
appStatusSlug : appStatusSlug ,
393
- queue : cliutil. NewQueue [ reportTask ]( 100 ) ,
434
+ queue : queue ,
394
435
}
395
436
396
437
// Display client URL separately from authentication status.
@@ -505,35 +546,6 @@ func (r *RootCmd) mcpServer() *serpent.Command {
505
546
}
506
547
507
548
func (s * mcpServer ) startReporter (ctx context.Context , inv * serpent.Invocation ) {
508
- // lastMessageID is the ID of the last *user* message that we saw. A user
509
- // message only happens when interacting via the API (as opposed to
510
- // interacting with the terminal directly).
511
- var lastMessageID int64
512
- shouldUpdate := func (item reportTask ) codersdk.WorkspaceAppStatusState {
513
- // Always send self-reported updates.
514
- if item .selfReported {
515
- return item .state
516
- }
517
- // Always send completed states.
518
- switch item .state {
519
- case codersdk .WorkspaceAppStatusStateComplete ,
520
- codersdk .WorkspaceAppStatusStateFailure :
521
- return item .state
522
- }
523
- // Always send "working" when there is a new user message, since we know the
524
- // LLM will begin work soon if it has not already.
525
- if item .messageID > lastMessageID {
526
- return codersdk .WorkspaceAppStatusStateWorking
527
- }
528
- // Otherwise, if the state is "working" and there have been no new user
529
- // messages, it means either that the LLM is still working or it means the
530
- // user has interacted with the terminal directly. For now, we are ignoring
531
- // these updates. This risks missing cases where the user manually submits
532
- // a new prompt and the LLM becomes active and does not update itself, but
533
- // it avoids spamming useless status updates.
534
- return ""
535
- }
536
- var lastPayload agentsdk.PatchAppStatus
537
549
go func () {
538
550
for {
539
551
// TODO: Even with the queue, there is still the potential that a message
@@ -545,45 +557,15 @@ func (s *mcpServer) startReporter(ctx context.Context, inv *serpent.Invocation)
545
557
return
546
558
}
547
559
548
- state := shouldUpdate (item )
549
- if state == "" {
550
- continue
551
- }
552
-
553
- if item .messageID != 0 {
554
- lastMessageID = item .messageID
555
- }
556
-
557
- payload := agentsdk.PatchAppStatus {
560
+ err := s .agentClient .PatchAppStatus (ctx , agentsdk.PatchAppStatus {
558
561
AppSlug : s .appStatusSlug ,
559
562
Message : item .summary ,
560
563
URI : item .link ,
561
- State : state ,
562
- }
563
-
564
- // Preserve previous message and URI if there was no message.
565
- if payload .Message == "" {
566
- payload .Message = lastPayload .Message
567
- if payload .URI == "" {
568
- payload .URI = lastPayload .URI
569
- }
570
- }
571
-
572
- // Avoid sending duplicate updates.
573
- if lastPayload .State == payload .State &&
574
- lastPayload .URI == payload .URI &&
575
- lastPayload .Message == payload .Message {
576
- continue
577
- }
578
-
579
- err := s .agentClient .PatchAppStatus (ctx , payload )
564
+ State : item .state ,
565
+ })
580
566
if err != nil && ! errors .Is (err , context .Canceled ) {
581
567
cliui .Warnf (inv .Stderr , "Failed to report task status: %s" , err )
582
568
}
583
-
584
- if err == nil {
585
- lastPayload = payload
586
- }
587
569
}
588
570
}()
589
571
}
@@ -607,7 +589,7 @@ func (s *mcpServer) startWatcher(ctx context.Context, inv *serpent.Invocation) {
607
589
if ev .Status == agentapi .StatusStable {
608
590
state = codersdk .WorkspaceAppStatusStateComplete
609
591
}
610
- err := s .queue .Push (reportTask {
592
+ err := s .queue .Push (taskReport {
611
593
state : state ,
612
594
})
613
595
if err != nil {
@@ -616,7 +598,7 @@ func (s *mcpServer) startWatcher(ctx context.Context, inv *serpent.Invocation) {
616
598
}
617
599
case agentapi.EventMessageUpdate :
618
600
if ev .Role == agentapi .RoleUser {
619
- err := s .queue .Push (reportTask {
601
+ err := s .queue .Push (taskReport {
620
602
messageID : ev .Id ,
621
603
})
622
604
if err != nil {
@@ -667,7 +649,7 @@ func (s *mcpServer) startServer(ctx context.Context, inv *serpent.Invocation, in
667
649
// Add tool dependencies.
668
650
toolOpts := []func (* toolsdk.Deps ){
669
651
toolsdk .WithTaskReporter (func (args toolsdk.ReportTaskArgs ) error {
670
- return s .queue .Push (reportTask {
652
+ return s .queue .Push (taskReport {
671
653
link : args .Link ,
672
654
selfReported : true ,
673
655
state : codersdk .WorkspaceAppStatusState (args .State ),
0 commit comments