@@ -362,11 +362,19 @@ func (*RootCmd) mcpConfigureCursor() *serpent.Command {
362
362
}
363
363
364
364
type taskReport struct {
365
- link string
366
- messageID int64
365
+ // link is optional.
366
+ link string
367
+ // messageID must be set if this update is from a *user* message. A user
368
+ // message only happens when interacting via the AI AgentAPI (as opposed to
369
+ // interacting with the terminal directly).
370
+ messageID * int64
371
+ // selfReported must be set if the update is directly from the AI agent
372
+ // (as opposed to the screen watcher).
367
373
selfReported bool
368
- state codersdk.WorkspaceAppStatusState
369
- summary string
374
+ // state must always be set.
375
+ state codersdk.WorkspaceAppStatusState
376
+ // summary is optional.
377
+ summary string
370
378
}
371
379
372
380
type mcpServer struct {
@@ -388,17 +396,22 @@ func (r *RootCmd) mcpServer() *serpent.Command {
388
396
return & serpent.Command {
389
397
Use :"server" ,
390
398
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 AI AgentAPI (as
393
- // opposed to interacting with the terminal directly).
394
- var lastUserMessageID int64
395
399
var lastReport taskReport
396
400
// Create a queue that skips duplicates and preserves summaries.
397
401
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
+ // Avoid queuing empty statuses (this would probably indicate a
403
+ // developer error)
404
+ if report .state == "" {
405
+ return report ,false
406
+ }
407
+ // If this is a user message, discard if it is not new.
408
+ if report .messageID != nil && lastReport .messageID != nil &&
409
+ * lastReport .messageID >= * report .messageID {
410
+ return report ,false
411
+ }
412
+ // If this is not a user message, and the status is "working" and not
413
+ // self-reported (meaning it came from the screen watcher), then it
414
+ // means one of two things:
402
415
//
403
416
// 1. The AI agent is not working; the user is interacting with the
404
417
// terminal directly.
@@ -415,11 +428,16 @@ func (r *RootCmd) mcpServer() *serpent.Command {
415
428
// user manually submits a new prompt and the AI agent becomes active
416
429
// (and does not update itself), but it avoids spamming useless status
417
430
// updates as the user is typing, so the tradeoff is worth it.
418
- if report .messageID > lastUserMessageID {
419
- report .state = codersdk .WorkspaceAppStatusStateWorking
420
- } else if report . state == codersdk . WorkspaceAppStatusStateWorking && ! report .selfReported && lastReport .state != "" {
431
+ if report .messageID == nil &&
432
+ report .state == codersdk .WorkspaceAppStatusStateWorking &&
433
+ ! report .selfReported && lastReport .state != "" {
421
434
return report ,false
422
435
}
436
+ // Keep track of the last message ID so we can tell when a message is
437
+ // new or if it has been re-emitted.
438
+ if report .messageID == nil {
439
+ report .messageID = lastReport .messageID
440
+ }
423
441
// Preserve previous message and URI if there was no message.
424
442
if report .summary == "" {
425
443
report .summary = lastReport .summary
@@ -607,7 +625,8 @@ func (s *mcpServer) startWatcher(ctx context.Context, inv *serpent.Invocation) {
607
625
case agentapi.EventMessageUpdate :
608
626
if ev .Role == agentapi .RoleUser {
609
627
err := s .queue .Push (taskReport {
610
- messageID :ev .Id ,
628
+ messageID :& ev .Id ,
629
+ state :codersdk .WorkspaceAppStatusStateWorking ,
611
630
})
612
631
if err != nil {
613
632
cliui .Warnf (inv .Stderr ,"Failed to queue update: %s" ,err )