@@ -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,49 @@ func (r *RootCmd) mcpServer() *serpent.Command {
388
388
return & serpent.Command {
389
389
Use : "server" ,
390
390
Handler : func (inv * serpent.Invocation ) error {
391
+ // lastMessageID is the ID of the last *user* message that we saw. A user
392
+ // message only happens when interacting via the API (as opposed to
393
+ // interacting with the terminal directly).
394
+ var lastMessageID 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 new
399
+ // user message, and the status is "working" and not self-reported (meaning it
400
+ // came from the screen watcher), then it means one of two things:
401
+ // 1. The LLM is still working, in which case our last status will already
402
+ // have been "working", so there is nothing to do.
403
+ // 2. The user has interacted with the terminal directly. For now, we are
404
+ // ignoring these updates. This risks missing cases where the user
405
+ // manually submits a new prompt and the LLM becomes active and does not
406
+ // update itself, but it avoids spamming useless status updates as the user
407
+ // is typing, so the tradeoff is worth it. In the future, if we can
408
+ // reliably distinguish between user and LLM activity, we can change this.
409
+ if report .messageID > lastMessageID {
410
+ report .state = codersdk .WorkspaceAppStatusStateWorking
411
+ } else if report .state == codersdk .WorkspaceAppStatusStateWorking && ! report .selfReported {
412
+ return report , false
413
+ }
414
+ // Preserve previous message and URI if there was no message.
415
+ if report .summary == "" {
416
+ report .summary = lastReport .summary
417
+ if report .link == "" {
418
+ report .link = lastReport .link
419
+ }
420
+ }
421
+ // Avoid queueing duplicate updates.
422
+ if report .state == lastReport .state &&
423
+ report .link == lastReport .link &&
424
+ report .summary == lastReport .summary {
425
+ return report , false
426
+ }
427
+ lastReport = report
428
+ return report , true
429
+ })
430
+
391
431
srv := & mcpServer {
392
432
appStatusSlug : appStatusSlug ,
393
- queue : cliutil. NewQueue [ reportTask ]( 100 ) ,
433
+ queue : queue ,
394
434
}
395
435
396
436
// Display client URL separately from authentication status.
@@ -505,35 +545,6 @@ func (r *RootCmd) mcpServer() *serpent.Command {
505
545
}
506
546
507
547
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
548
go func () {
538
549
for {
539
550
// TODO: Even with the queue, there is still the potential that a message
@@ -545,45 +556,15 @@ func (s *mcpServer) startReporter(ctx context.Context, inv *serpent.Invocation)
545
556
return
546
557
}
547
558
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 {
559
+ err := s .agentClient .PatchAppStatus (ctx , agentsdk.PatchAppStatus {
558
560
AppSlug : s .appStatusSlug ,
559
561
Message : item .summary ,
560
562
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 )
563
+ State : item .state ,
564
+ })
580
565
if err != nil && ! errors .Is (err , context .Canceled ) {
581
566
cliui .Warnf (inv .Stderr , "Failed to report task status: %s" , err )
582
567
}
583
-
584
- if err == nil {
585
- lastPayload = payload
586
- }
587
568
}
588
569
}()
589
570
}
@@ -607,7 +588,7 @@ func (s *mcpServer) startWatcher(ctx context.Context, inv *serpent.Invocation) {
607
588
if ev .Status == agentapi .StatusStable {
608
589
state = codersdk .WorkspaceAppStatusStateComplete
609
590
}
610
- err := s .queue .Push (reportTask {
591
+ err := s .queue .Push (taskReport {
611
592
state : state ,
612
593
})
613
594
if err != nil {
@@ -616,7 +597,7 @@ func (s *mcpServer) startWatcher(ctx context.Context, inv *serpent.Invocation) {
616
597
}
617
598
case agentapi.EventMessageUpdate :
618
599
if ev .Role == agentapi .RoleUser {
619
- err := s .queue .Push (reportTask {
600
+ err := s .queue .Push (taskReport {
620
601
messageID : ev .Id ,
621
602
})
622
603
if err != nil {
@@ -667,7 +648,7 @@ func (s *mcpServer) startServer(ctx context.Context, inv *serpent.Invocation, in
667
648
// Add tool dependencies.
668
649
toolOpts := []func (* toolsdk.Deps ){
669
650
toolsdk .WithTaskReporter (func (args toolsdk.ReportTaskArgs ) error {
670
- return s .queue .Push (reportTask {
651
+ return s .queue .Push (taskReport {
671
652
link : args .Link ,
672
653
selfReported : true ,
673
654
state : codersdk .WorkspaceAppStatusState (args .State ),
0 commit comments