From 3ffe823486a259a3eb0c2d629b225fcb47d29622 Mon Sep 17 00:00:00 2001 From: Stefan Haubold Date: Wed, 25 Mar 2026 17:15:46 +0100 Subject: [PATCH 1/2] interactive midturn commit e2e test Entire-Checkpoint: d6fc700cce4c --- e2e/tests/interactive_test.go | 43 +++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/e2e/tests/interactive_test.go b/e2e/tests/interactive_test.go index 9f604ccdd..ffec0ad80 100644 --- a/e2e/tests/interactive_test.go +++ b/e2e/tests/interactive_test.go @@ -10,6 +10,49 @@ import ( "github.com/entireio/cli/e2e/testutil" ) +// TestInteractiveMidTurnCommit: agent creates a file and commits it within a +// single interactive turn. This exercises the TUI event path for turn-end +// detection — unlike RunPrompt (which uses non-interactive mode where process +// exit forces cleanup), the interactive session stays alive and must receive +// the idle event to trigger turn-end and transcript export. +// +// This is a regression test for OpenCode where session.status idle events +// were not reaching the plugin in TUI mode, causing turn-end to never fire, +// transcript export to never run, and condensation to fail with +// "transcript not found". +func TestInteractiveMidTurnCommit(t *testing.T) { + testutil.ForEachAgent(t, 3*time.Minute, func(t *testing.T, s *testutil.RepoState, ctx context.Context) { + prompt := s.Agent.PromptPattern() + + // External agents (roger-roger) use different hook mechanisms and have + // WaitFor settle issues with short interactive output — skip them. + if s.IsExternalAgent() { + t.Skipf("skipping interactive mid-turn commit for external agent %s", s.Agent.Name()) + } + + session := s.StartSession(t, ctx) + if session == nil { + t.Skipf("agent %s does not support interactive mode", s.Agent.Name()) + } + + s.WaitFor(t, session, prompt, 30*time.Second) + + // Single turn: create a file and commit it. The agent commits mid-turn, + // then finishes (gives summary). The turn-end hook must fire after the + // agent goes idle — in TUI mode this depends on the plugin receiving + // the idle event, not on process exit. + s.Send(t, session, "create a markdown file at docs/red.md with a paragraph about the colour red, then commit it. Do not ask for confirmation, just make the change.") + s.WaitFor(t, session, prompt, 90*time.Second) + + testutil.AssertFileExists(t, s.Dir, "docs/*.md") + testutil.AssertNewCommits(t, s, 1) + + testutil.WaitForCheckpoint(t, s, 15*time.Second) + testutil.AssertCommitLinkedToCheckpoint(t, s.Dir, "HEAD") + testutil.AssertNoShadowBranches(t, s.Dir) + }) +} + func TestInteractiveMultiStep(t *testing.T) { testutil.ForEachAgent(t, 3*time.Minute, func(t *testing.T, s *testutil.RepoState, ctx context.Context) { prompt := s.Agent.PromptPattern() From d4efe52b95909901ec3c17deea98d7effb8b2138 Mon Sep 17 00:00:00 2001 From: Stefan Haubold Date: Wed, 25 Mar 2026 18:10:31 +0100 Subject: [PATCH 2/2] address PR comment Entire-Checkpoint: a5d85be30f10 --- e2e/tests/interactive_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/tests/interactive_test.go b/e2e/tests/interactive_test.go index ffec0ad80..7aee2fc93 100644 --- a/e2e/tests/interactive_test.go +++ b/e2e/tests/interactive_test.go @@ -44,7 +44,7 @@ func TestInteractiveMidTurnCommit(t *testing.T) { s.Send(t, session, "create a markdown file at docs/red.md with a paragraph about the colour red, then commit it. Do not ask for confirmation, just make the change.") s.WaitFor(t, session, prompt, 90*time.Second) - testutil.AssertFileExists(t, s.Dir, "docs/*.md") + testutil.WaitForFileExists(t, s.Dir, "docs/red.md", 30*time.Second) testutil.AssertNewCommits(t, s, 1) testutil.WaitForCheckpoint(t, s, 15*time.Second)