MCPcopy Index your code
hub / github.com/coder/agentapi / writeStabilize

Method writeStabilize

lib/screentracker/pty_conversation.go:448–526  ·  view source on GitHub ↗

writeStabilize writes messageParts to the PTY and waits for the agent to process them. It operates in two phases: Phase 1 (echo detection): writes the message text and waits for the screen to change and stabilize. This detects agents that echo typed input. If the screen doesn't change within writeS

(ctx context.Context, messageParts ...MessagePart)

Source from the content-addressed store, hash-verified

446// started processing. This phase is fatal on timeout: if the
447// agent doesn't react to Enter, it's unresponsive.
448func (c *PTYConversation) writeStabilize(ctx context.Context, messageParts ...MessagePart) error {
449 screenBeforeMessage := c.cfg.AgentIO.ReadScreen()
450 for _, part := range messageParts {
451 if err := part.Do(c.cfg.AgentIO); err != nil {
452 return xerrors.Errorf("failed to write message part: %w", err)
453 }
454 }
455 // Phase 1: wait for the screen to stabilize after the
456 // message is written (echo detection).
457 if err := util.WaitFor(ctx, util.WaitTimeout{
458 Timeout: writeStabilizeEchoTimeout,
459 MinInterval: 50 * time.Millisecond,
460 InitialWait: true,
461 Clock: c.cfg.Clock,
462 }, func() (bool, error) {
463 screen := c.cfg.AgentIO.ReadScreen()
464 if screen != screenBeforeMessage {
465 stabilityTimer := c.cfg.Clock.NewTimer(1 * time.Second)
466 select {
467 case <-ctx.Done():
468 stabilityTimer.Stop()
469 return false, ctx.Err()
470 case <-stabilityTimer.C:
471 }
472 stabilityTimer.Stop()
473 newScreen := c.cfg.AgentIO.ReadScreen()
474 return newScreen == screen, nil
475 }
476 return false, nil
477 }); err != nil {
478 if !errors.Is(err, util.WaitTimedOut) {
479 // Context cancellation or condition errors are fatal.
480 return xerrors.Errorf("failed to wait for screen to stabilize: %w", err)
481 }
482 // Phase 1 timeout is non-fatal: the agent may not echo
483 // input (e.g. TUI agents buffer bracketed-paste content
484 // internally). Proceed to Phase 2 to send the carriage
485 // return.
486 c.cfg.Logger.Info(
487 "echo detection timed out, sending carriage return",
488 "timeout", writeStabilizeEchoTimeout,
489 )
490 }
491
492 // Phase 2: wait for the screen to change after the
493 // carriage return is written (processing detection).
494 screenBeforeCarriageReturn := c.cfg.AgentIO.ReadScreen()
495 lastCarriageReturnTime := time.Time{}
496 if err := util.WaitFor(ctx, util.WaitTimeout{
497 Timeout: writeStabilizeProcessTimeout,
498 MinInterval: 25 * time.Millisecond,
499 Clock: c.cfg.Clock,
500 }, func() (bool, error) {
501 // we don't want to spam additional carriage returns because the agent may process them
502 // (aider does this), but we do want to retry sending one if nothing's
503 // happening for a while
504 if c.cfg.Clock.Since(lastCarriageReturnTime) >= 3*time.Second {
505 lastCarriageReturnTime = c.cfg.Clock.Now()

Callers 1

sendMessageMethod · 0.95

Calls 5

WaitForFunction · 0.92
ReadScreenMethod · 0.65
DoMethod · 0.65
WriteMethod · 0.65
StopMethod · 0.45

Tested by

no test coverage detected