Confirm every write Orchestra sends, and count none of them as progress
F20. Only the launch confirmed its submit. A decision notice at a turn boundary, and /clear or @HANDOFF.md during a context reset, were fire-and-forget through the same transport that loses an Enter often enough that the launch needed three resubmits. A lost Enter on the context-reset path is the worst of them: it strands the session mid-rollover and nothing retries it. LaunchConfirmer is therefore InputConfirmer, ConfirmLaunch is ConfirmInput, and sendPrompt and sendLine both go through it. Orchestra does not try to guarantee delivery of input it did not originate. But it must never read that input as work, which is the F16 half. Burn-in run 3 stalled with an unexplained "go ahead and implement it" in the editor, and the renewal check hashed the whole capture, so those keystrokes read as progress and the lease kept renewing around an idle agent. PaneProgress drops input lines from the capture, which the -J join makes exact: a wrapped input block is one line beginning with the prompt marker. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+23
-3
@@ -27,7 +27,7 @@ type TmuxBackend struct {
|
||||
Command string
|
||||
// Binary is test/packaging override for tmux itself.
|
||||
Binary string
|
||||
// LaunchConfirmTimeout and LaunchConfirmPoll bound ConfirmLaunch. They are
|
||||
// LaunchConfirmTimeout and LaunchConfirmPoll bound ConfirmInput. They are
|
||||
// tunable because how fast a terminal harness visibly reacts is a property
|
||||
// of the host, not of this code. Zero values mean 10s and 250ms.
|
||||
LaunchConfirmTimeout time.Duration
|
||||
@@ -428,12 +428,32 @@ func (b *TmuxBackend) inputState(ctx context.Context, s Session) (InputState, er
|
||||
return InputState{Text: strings.TrimSpace(strings.Join(parts, " ")), Active: true}, nil
|
||||
}
|
||||
|
||||
// PaneProgress hashes what the harness produced, not what someone typed at it.
|
||||
// The capture joins wrapped lines, so an input block is one line beginning
|
||||
// with the prompt marker and dropping those lines removes it whole. Found live
|
||||
// during burn-in run 3: unexplained keystrokes in a pane kept a stalled lease
|
||||
// renewing, because the renewal check hashed the whole capture.
|
||||
func (b *TmuxBackend) PaneProgress(ctx context.Context, s Session) (string, error) {
|
||||
text, err := b.PaneCapture(ctx, s, "recent")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
var kept []string
|
||||
for _, line := range strings.Split(text, "\n") {
|
||||
if promptLine.MatchString(line) {
|
||||
continue
|
||||
}
|
||||
kept = append(kept, line)
|
||||
}
|
||||
return strings.Join(kept, "\n"), nil
|
||||
}
|
||||
|
||||
// launchResubmitLimit bounds the intervention independently of the observation
|
||||
// deadline. A slow TUI must not receive a fortieth Enter after it accepted the
|
||||
// first: three exact-editor resubmits, then observation only.
|
||||
const launchResubmitLimit = 3
|
||||
|
||||
// ConfirmLaunch drives the submit to a decision instead of assuming one Enter
|
||||
// ConfirmInput drives the submit to a decision instead of assuming one Enter
|
||||
// landed. Burn-in run 3 proved the submit is not deterministic: the text
|
||||
// reached the editor on all three attempts and the following Enter never took
|
||||
// effect. So this resends Enter while the live editor still holds exactly what
|
||||
@@ -442,7 +462,7 @@ const launchResubmitLimit = 3
|
||||
//
|
||||
// The returned evidence records how many submits it took, which is the only
|
||||
// way to tell a harness that needs a second Enter from one that needed none.
|
||||
func (b *TmuxBackend) ConfirmLaunch(ctx context.Context, s Session, submitted string) (string, error) {
|
||||
func (b *TmuxBackend) ConfirmInput(ctx context.Context, s Session, submitted string) (string, error) {
|
||||
timeout, poll := b.LaunchConfirmTimeout, b.LaunchConfirmPoll
|
||||
if timeout <= 0 {
|
||||
timeout = 10 * time.Second
|
||||
|
||||
Reference in New Issue
Block a user