From 5e0aff953eac38ab0297d30d35dfe7272237a5a0 Mon Sep 17 00:00:00 2001 From: Hugo Dutka Date: Mon, 26 May 2025 13:28:08 +0200 Subject: [PATCH 1/3] fix(conversation): send fewer carriage returns after a user message --- .vscode/settings.json | 5 ++++- lib/screentracker/conversation.go | 21 +++++++++++++++++---- lib/screentracker/conversation_test.go | 4 ++++ lib/termexec/termexec.go | 5 +++++ 4 files changed, 30 insertions(+), 5 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 89d1965..14cb7f6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,7 @@ { "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode" + "editor.defaultFormatter": "esbenp.prettier-vscode", + "[go]": { + "editor.defaultFormatter": "golang.go" + } } \ No newline at end of file diff --git a/lib/screentracker/conversation.go b/lib/screentracker/conversation.go index 4311897..4fd4331 100644 --- a/lib/screentracker/conversation.go +++ b/lib/screentracker/conversation.go @@ -21,6 +21,7 @@ type screenSnapshot struct { type AgentIO interface { Write(data []byte) (int, error) ReadScreen() string + Cursor() (int, int) } type ConversationConfig struct { @@ -289,16 +290,28 @@ func (c *Conversation) writeMessageWithConfirmation(ctx context.Context, message // wait for the screen to change after the carriage return is written screenBeforeCarriageReturn := c.cfg.AgentIO.ReadScreen() + cursorBeforeCarriageReturnX, cursorBeforeCarriageReturnY := c.cfg.AgentIO.Cursor() + lastCarriageReturnTime := time.Time{} if err := util.WaitFor(ctx, util.WaitTimeout{ Timeout: 15 * time.Second, MinInterval: 25 * time.Millisecond, }, func() (bool, error) { - if _, err := c.cfg.AgentIO.Write([]byte("\r")); err != nil { - return false, xerrors.Errorf("failed to write carriage return: %w", err) + // we don't want to spam additional carriage returns because the agent may process them + // (aider does this), but we do want to retry sending one if nothing's + // happening for a while + if time.Since(lastCarriageReturnTime) >= 3*time.Second { + lastCarriageReturnTime = time.Now() + if _, err := c.cfg.AgentIO.Write([]byte("\r")); err != nil { + return false, xerrors.Errorf("failed to write carriage return: %w", err) + } } - time.Sleep(25 * time.Millisecond) + time.Sleep(1 * time.Millisecond) screen := c.cfg.AgentIO.ReadScreen() - return screen != screenBeforeCarriageReturn, nil + cursorX, cursorY := c.cfg.AgentIO.Cursor() + + return screen != screenBeforeCarriageReturn || + cursorX != cursorBeforeCarriageReturnX || + cursorY != cursorBeforeCarriageReturnY, nil }); err != nil { return xerrors.Errorf("failed to wait for processing to start: %w", err) } diff --git a/lib/screentracker/conversation_test.go b/lib/screentracker/conversation_test.go index 1b797aa..8eacfc5 100644 --- a/lib/screentracker/conversation_test.go +++ b/lib/screentracker/conversation_test.go @@ -31,6 +31,10 @@ func (a *testAgent) ReadScreen() string { return a.screen } +func (a *testAgent) Cursor() (int, int) { + return 0, 0 +} + func (a *testAgent) Write(data []byte) (int, error) { return 0, nil } diff --git a/lib/termexec/termexec.go b/lib/termexec/termexec.go index 6c04813..460faff 100644 --- a/lib/termexec/termexec.go +++ b/lib/termexec/termexec.go @@ -70,6 +70,11 @@ func (p *Process) ReadScreen() string { return p.xp.State.String() } +// Cursor returns the current cursor position. +func (p *Process) Cursor() (int, int) { + return p.xp.State.Cursor() +} + // Write sends input to the process via the pseudo terminal. func (p *Process) Write(data []byte) (int, error) { return p.xp.TerminalInPipe().Write(data) From 2a37165d3a79a4113585679225edf0cdaba17e20 Mon Sep 17 00:00:00 2001 From: Hugo Dutka Date: Mon, 26 May 2025 13:31:49 +0200 Subject: [PATCH 2/3] adjust sleep duration --- lib/screentracker/conversation.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/screentracker/conversation.go b/lib/screentracker/conversation.go index 4fd4331..88efa86 100644 --- a/lib/screentracker/conversation.go +++ b/lib/screentracker/conversation.go @@ -305,7 +305,7 @@ func (c *Conversation) writeMessageWithConfirmation(ctx context.Context, message return false, xerrors.Errorf("failed to write carriage return: %w", err) } } - time.Sleep(1 * time.Millisecond) + time.Sleep(25 * time.Millisecond) screen := c.cfg.AgentIO.ReadScreen() cursorX, cursorY := c.cfg.AgentIO.Cursor() From 5ced40e4fdeeb0cf2eff14b44d1fee3264416c9a Mon Sep 17 00:00:00 2001 From: Hugo Dutka Date: Mon, 26 May 2025 13:39:24 +0200 Subject: [PATCH 3/3] remove cursor checks - I'm not sure if cursor position changes can reliably indicate that the agent started processing a message --- lib/screentracker/conversation.go | 7 +------ lib/screentracker/conversation_test.go | 4 ---- lib/termexec/termexec.go | 5 ----- 3 files changed, 1 insertion(+), 15 deletions(-) diff --git a/lib/screentracker/conversation.go b/lib/screentracker/conversation.go index 88efa86..8e55e57 100644 --- a/lib/screentracker/conversation.go +++ b/lib/screentracker/conversation.go @@ -21,7 +21,6 @@ type screenSnapshot struct { type AgentIO interface { Write(data []byte) (int, error) ReadScreen() string - Cursor() (int, int) } type ConversationConfig struct { @@ -290,7 +289,6 @@ func (c *Conversation) writeMessageWithConfirmation(ctx context.Context, message // wait for the screen to change after the carriage return is written screenBeforeCarriageReturn := c.cfg.AgentIO.ReadScreen() - cursorBeforeCarriageReturnX, cursorBeforeCarriageReturnY := c.cfg.AgentIO.Cursor() lastCarriageReturnTime := time.Time{} if err := util.WaitFor(ctx, util.WaitTimeout{ Timeout: 15 * time.Second, @@ -307,11 +305,8 @@ func (c *Conversation) writeMessageWithConfirmation(ctx context.Context, message } time.Sleep(25 * time.Millisecond) screen := c.cfg.AgentIO.ReadScreen() - cursorX, cursorY := c.cfg.AgentIO.Cursor() - return screen != screenBeforeCarriageReturn || - cursorX != cursorBeforeCarriageReturnX || - cursorY != cursorBeforeCarriageReturnY, nil + return screen != screenBeforeCarriageReturn, nil }); err != nil { return xerrors.Errorf("failed to wait for processing to start: %w", err) } diff --git a/lib/screentracker/conversation_test.go b/lib/screentracker/conversation_test.go index 8eacfc5..1b797aa 100644 --- a/lib/screentracker/conversation_test.go +++ b/lib/screentracker/conversation_test.go @@ -31,10 +31,6 @@ func (a *testAgent) ReadScreen() string { return a.screen } -func (a *testAgent) Cursor() (int, int) { - return 0, 0 -} - func (a *testAgent) Write(data []byte) (int, error) { return 0, nil } diff --git a/lib/termexec/termexec.go b/lib/termexec/termexec.go index 460faff..6c04813 100644 --- a/lib/termexec/termexec.go +++ b/lib/termexec/termexec.go @@ -70,11 +70,6 @@ func (p *Process) ReadScreen() string { return p.xp.State.String() } -// Cursor returns the current cursor position. -func (p *Process) Cursor() (int, int) { - return p.xp.State.Cursor() -} - // Write sends input to the process via the pseudo terminal. func (p *Process) Write(data []byte) (int, error) { return p.xp.TerminalInPipe().Write(data)