Give injected agent submit Enter a longer settle delay
The trailing CR that submits orchestrator-injected input was written only 15ms after the body, inside TUI agents' paste-coalescing window, so codex (and other paste-detecting agents) intermittently swallowed it as a newline and left the message composed but unsent. Centralize the per-piece timing in a pure pieceWriteDelay helper: keep 15ms between body lines but give the final lone Enter a 100ms settle gap so the agent closes the preceding burst and registers the CR as submit. Covers send_input, send_message, timers, and the spawn initial prompt (all go through writeInput). Resolves the codex composer-submit TODO item.
This commit is contained in:
@@ -16,6 +16,9 @@ loosely follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|||||||
over MCP.
|
over MCP.
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
- Injected agent input now sends the submit Enter as a separated,
|
||||||
|
settled keystroke so messages reliably submit instead of sometimes
|
||||||
|
sitting unsent in the composer.
|
||||||
- Codex agents are no longer reported idle while a turn is still
|
- Codex agents are no longer reported idle while a turn is still
|
||||||
running.
|
running.
|
||||||
- Slow MCP tool calls such as `wait_for_pattern` no longer block later
|
- Slow MCP tool calls such as `wait_for_pattern` no longer block later
|
||||||
|
|||||||
5
TODO.md
5
TODO.md
@@ -1,6 +1 @@
|
|||||||
- [ ] When opening a codex sub agent, the message gets input to the field, but the message is never submitted.
|
|
||||||
- This appears to be inconsistent. Sometimes it works, sometimes it doesn't. Might be because of popups on codex sub agents?
|
|
||||||
- Question: when it fails, is a Codex startup popup visible (trust/workspace, auth/model selection, permissions), or is the normal composer focused?
|
|
||||||
- Question: if the message is sitting in the composer, does pressing Enter once manually submit it, or does something else need to be dismissed first?
|
|
||||||
- Question: does this happen with short one-line prompts as well as long/multiline sub-agent instructions?
|
|
||||||
- [ ] The per-tab agent summary text should display below the tab always, not just when the tab is focused.
|
- [ ] The per-tab agent summary text should display below the tab always, not just when the tab is focused.
|
||||||
|
|||||||
@@ -26,6 +26,11 @@ import (
|
|||||||
// false positives (timestamps, exit codes, etc.).
|
// false positives (timestamps, exit codes, etc.).
|
||||||
var portRegex = regexp.MustCompile(`https?://[^\s:/]+:(\d{2,5})(?:/[^\s]*)?`)
|
var portRegex = regexp.MustCompile(`https?://[^\s:/]+:(\d{2,5})(?:/[^\s]*)?`)
|
||||||
|
|
||||||
|
const (
|
||||||
|
agentInterPieceDelay = 15 * time.Millisecond
|
||||||
|
agentSubmitSettleDelay = 100 * time.Millisecond
|
||||||
|
)
|
||||||
|
|
||||||
type ChildStatus string
|
type ChildStatus string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -642,8 +647,8 @@ func (c *Child) writeInput(b []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for i, piece := range pieces {
|
for i, piece := range pieces {
|
||||||
if i > 0 {
|
if delay := pieceWriteDelay(i, len(pieces), piece); delay > 0 {
|
||||||
time.Sleep(15 * time.Millisecond)
|
time.Sleep(delay)
|
||||||
}
|
}
|
||||||
if _, err := pty.Write(piece); err != nil {
|
if _, err := pty.Write(piece); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -659,6 +664,20 @@ func inputWritePieces(kind ChildKind, b []byte) [][]byte {
|
|||||||
return splitOnEnter(b)
|
return splitOnEnter(b)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func pieceWriteDelay(index, total int, piece []byte) time.Duration {
|
||||||
|
if index == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
if index == total-1 && isLoneEnter(piece) {
|
||||||
|
return agentSubmitSettleDelay
|
||||||
|
}
|
||||||
|
return agentInterPieceDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
func isLoneEnter(piece []byte) bool {
|
||||||
|
return len(piece) == 1 && (piece[0] == '\r' || piece[0] == '\n')
|
||||||
|
}
|
||||||
|
|
||||||
func mintIdentity() string {
|
func mintIdentity() string {
|
||||||
var buf [12]byte
|
var buf [12]byte
|
||||||
_, _ = rand.Read(buf[:])
|
_, _ = rand.Read(buf[:])
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package app
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestInputWritePiecesOnlySplitAgentEnters(t *testing.T) {
|
func TestInputWritePiecesOnlySplitAgentEnters(t *testing.T) {
|
||||||
@@ -27,3 +28,63 @@ func TestInputWritePiecesOnlySplitAgentEnters(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPieceWriteDelay(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
index int
|
||||||
|
total int
|
||||||
|
piece []byte
|
||||||
|
want time.Duration
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "first piece",
|
||||||
|
index: 0,
|
||||||
|
total: 3,
|
||||||
|
piece: []byte("body"),
|
||||||
|
want: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "middle body piece",
|
||||||
|
index: 1,
|
||||||
|
total: 3,
|
||||||
|
piece: []byte("body"),
|
||||||
|
want: agentInterPieceDelay,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "final carriage return submit",
|
||||||
|
index: 1,
|
||||||
|
total: 2,
|
||||||
|
piece: []byte("\r"),
|
||||||
|
want: agentSubmitSettleDelay,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "final newline submit",
|
||||||
|
index: 1,
|
||||||
|
total: 2,
|
||||||
|
piece: []byte("\n"),
|
||||||
|
want: agentSubmitSettleDelay,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "final non-enter piece",
|
||||||
|
index: 2,
|
||||||
|
total: 3,
|
||||||
|
piece: []byte("tail"),
|
||||||
|
want: agentInterPieceDelay,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "standalone enter fast path",
|
||||||
|
index: 0,
|
||||||
|
total: 1,
|
||||||
|
piece: []byte("\r"),
|
||||||
|
want: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tc := range cases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
if got := pieceWriteDelay(tc.index, tc.total, tc.piece); got != tc.want {
|
||||||
|
t.Fatalf("pieceWriteDelay(%d, %d, %q) = %s, want %s", tc.index, tc.total, tc.piece, got, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user