package agent

import (
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/http/httptest"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"sync"
	"sync/atomic"
	"testing"
	"time"

	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agent/drivercontract"
	"github.com/tidbcloud/AgentTeam/desktop/internal/agent/runtimeconfig"
	"github.com/tidbcloud/AgentTeam/desktop/internal/consts"
	"github.com/tidbcloud/AgentTeam/desktop/internal/promptcompiler"
	"github.com/tidbcloud/AgentTeam/desktop/internal/provider"
	"github.com/tidbcloud/AgentTeam/desktop/internal/types"
	"github.com/tidbcloud/AgentTeam/internal/core/rewindcaps"
)

func TestResolveOpenCodeLaunchBinaryUsesExternalOpenCodeDefault(t *testing.T) {
	managedPath := writeOpenCodeTestBinary(t)
	t.Setenv("AGENTTEAM_MANAGED_OPENCODE_PATH", managedPath)

	got, err := resolveOpenCodeLaunchBinary(drivercontract.LaunchConfig{
		RuntimeType: runtimeconfig.TypeOpenCode,
	})

	require.NoError(t, err)
	require.Equal(t, "opencode", got)
}

func TestResolveOpenCodeLaunchBinaryUsesManagedOpenCodeForBuiltin(t *testing.T) {
	managedPath := writeOpenCodeTestBinary(t)
	t.Setenv("AGENTTEAM_MANAGED_OPENCODE_PATH", managedPath)

	externalPath := filepath.Join(t.TempDir(), "external-opencode")
	require.NoError(t, os.WriteFile(externalPath, []byte("#!/bin/sh\nexit 0\n"), 0o755))

	got, err := resolveOpenCodeLaunchBinary(drivercontract.LaunchConfig{
		RuntimeType: runtimeconfig.TypeBuiltin,
		Binary:      externalPath,
	})

	require.NoError(t, err)
	require.Equal(t, managedPath, got)
}

func TestOpenCodeSpawnReportsServeExitDuringHealthCheck(t *testing.T) {
	workDir := t.TempDir()
	binary := writeFailingOpenCodeServeBinary(t)
	t.Setenv("HOME", t.TempDir())

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()

	startedAt := time.Now()
	_, err := (&OpenCodeDriver{}).Spawn(ctx, drivercontract.LaunchConfig{
		AgentID: "agent-1",
		Binary:  binary,
		WorkDir: workDir,
	})

	require.Error(t, err)
	require.Contains(t, err.Error(), "opencode: health check: serve process exited before health check passed")
	require.Contains(t, err.Error(), "fake opencode startup failure")
	require.Less(t, time.Since(startedAt), 4*time.Second)
}

func TestOpenCodeServerStartupGateSerializesAndHonorsCancellation(t *testing.T) {
	gate := make(openCodeServerStartupGate, 1)
	releaseFirst, err := gate.acquire(context.Background())
	require.NoError(t, err)

	blockedCtx, cancel := context.WithCancel(context.Background())
	cancel()
	secondStarted := false
	err = gate.run(blockedCtx, func() error {
		secondStarted = true
		return nil
	})
	require.ErrorIs(t, err, context.Canceled)
	require.False(t, secondStarted)

	releaseFirst()
	require.NoError(t, gate.run(context.Background(), func() error {
		secondStarted = true
		return nil
	}))
	require.True(t, secondStarted)
}

func TestOpenCodeLaunchWaitsForStartupGateBeforeAllocatingPort(t *testing.T) {
	tests := []struct {
		name   string
		launch func(context.Context, *OpenCodeDriver, drivercontract.LaunchConfig) error
	}{
		{
			name: "agent",
			launch: func(ctx context.Context, driver *OpenCodeDriver, cfg drivercontract.LaunchConfig) error {
				_, err := driver.Spawn(ctx, cfg)
				return err
			},
		},
		{
			name: "detached",
			launch: func(ctx context.Context, driver *OpenCodeDriver, cfg drivercontract.LaunchConfig) error {
				cleanup, err := driver.startTemporaryServer(ctx, cfg)
				if cleanup != nil {
					cleanup()
				}
				return err
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			releaseGate, err := processOpenCodeServerStartupGate.acquire(context.Background())
			require.NoError(t, err)
			t.Cleanup(releaseGate)

			ctx, cancel := context.WithCancel(context.Background())
			cancel()

			driver := &OpenCodeDriver{}
			err = tt.launch(ctx, driver, drivercontract.LaunchConfig{
				AgentID:        "agent-queued",
				Binary:         writeFailingOpenCodeServeBinary(t),
				WorkDir:        t.TempDir(),
				PermissionMode: types.AgentPermissionModeFullAccess,
			})
			require.ErrorIs(t, err, context.Canceled)

			driver.mu.Lock()
			serverURL := driver.serverURL
			password := driver.password
			serverCancel := driver.serverCancel
			driver.mu.Unlock()
			require.Empty(t, serverURL)
			require.Empty(t, password)
			require.Nil(t, serverCancel)
		})
	}
}

func TestOpenCodeLaunchFailureClearsServerState(t *testing.T) {
	tests := []struct {
		name   string
		binary func(*testing.T) string
		launch func(context.Context, *OpenCodeDriver, drivercontract.LaunchConfig) error
	}{
		{
			name: "agent start failure",
			binary: func(t *testing.T) string {
				return filepath.Join(t.TempDir(), "missing-opencode")
			},
			launch: func(ctx context.Context, driver *OpenCodeDriver, cfg drivercontract.LaunchConfig) error {
				_, err := driver.Spawn(ctx, cfg)
				return err
			},
		},
		{
			name:   "agent health failure",
			binary: writeFailingOpenCodeServeBinary,
			launch: func(ctx context.Context, driver *OpenCodeDriver, cfg drivercontract.LaunchConfig) error {
				_, err := driver.Spawn(ctx, cfg)
				return err
			},
		},
		{
			name: "detached start failure",
			binary: func(t *testing.T) string {
				return filepath.Join(t.TempDir(), "missing-opencode")
			},
			launch: func(ctx context.Context, driver *OpenCodeDriver, cfg drivercontract.LaunchConfig) error {
				_, err := driver.startTemporaryServer(ctx, cfg)
				return err
			},
		},
		{
			name:   "detached health failure",
			binary: writeFailingOpenCodeServeBinary,
			launch: func(ctx context.Context, driver *OpenCodeDriver, cfg drivercontract.LaunchConfig) error {
				_, err := driver.startTemporaryServer(ctx, cfg)
				return err
			},
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			driver := &OpenCodeDriver{}
			err := tt.launch(context.Background(), driver, drivercontract.LaunchConfig{
				AgentID:        "agent-startup-failure",
				Binary:         tt.binary(t),
				WorkDir:        t.TempDir(),
				PermissionMode: types.AgentPermissionModeFullAccess,
			})
			require.Error(t, err)

			driver.mu.Lock()
			serverURL := driver.serverURL
			password := driver.password
			serverCancel := driver.serverCancel
			driver.mu.Unlock()
			require.Empty(t, serverURL)
			require.Empty(t, password)
			require.Nil(t, serverCancel)
		})
	}
}

func TestStopOpenCodeServerProcessCleansUpOnce(t *testing.T) {
	var cancelCalls atomic.Int32
	exited := make(chan struct{})
	close(exited)
	process := &openCodeServeProcess{
		cancel: func() { cancelCalls.Add(1) },
		exited: exited,
	}

	stopOpenCodeServerProcess(process)
	stopOpenCodeServerProcess(process)

	require.EqualValues(t, 1, cancelCalls.Load())
}

func TestClearOpenCodeServerStateDoesNotClearNewLaunchAtReusedURL(t *testing.T) {
	const reusedURL = "http://127.0.0.1:43210"
	oldServer := openCodeStartedServer{
		process: &openCodeServeProcess{},
		url:     reusedURL,
	}
	newCancel := func() {}
	driver := &OpenCodeDriver{
		serverURL:    reusedURL,
		password:     "new-password",
		serverCancel: newCancel,
	}

	driver.clearOpenCodeServerState(oldServer, "old-password")

	driver.mu.Lock()
	serverURL := driver.serverURL
	password := driver.password
	serverCancel := driver.serverCancel
	driver.mu.Unlock()
	require.Equal(t, reusedURL, serverURL)
	require.Equal(t, "new-password", password)
	require.NotNil(t, serverCancel)
}

func TestOpenCodeServeAllocatesPortOnlyAfterStartupGate(t *testing.T) {
	releaseGate, err := processOpenCodeServerStartupGate.acquire(context.Background())
	require.NoError(t, err)

	var allocatorCalls atomic.Int32
	attempted := make(chan struct{})
	done := make(chan error, 1)
	binary := filepath.Join(t.TempDir(), "missing-opencode")
	workDir := t.TempDir()
	go func() {
		close(attempted)
		_, err := (&OpenCodeDriver{}).startOpenCodeServe(context.Background(), openCodeServeStart{
			cfg: drivercontract.LaunchConfig{
				AgentID: "allocator-order",
				Binary:  binary,
				WorkDir: workDir,
			},
			logLevel: "WARN",
			stderr:   newCapturingPrefixWriter("[allocator] ", openCodeCapturedOutputMaxBytes),
			stdout:   newCapturingPrefixWriter("[allocator-stdout] ", openCodeCapturedOutputMaxBytes),
			allocatePort: func() (int, error) {
				allocatorCalls.Add(1)
				return 43210, nil
			},
		})
		done <- err
	}()
	<-attempted
	require.Never(t, func() bool { return allocatorCalls.Load() != 0 }, 50*time.Millisecond, 5*time.Millisecond)

	releaseGate()
	require.Error(t, <-done)
	require.EqualValues(t, 1, allocatorCalls.Load())
}

func TestOpenCodeServeHealthFailureStopsLiveProcessOnlyOnce(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("test helper uses a POSIX launcher")
	}
	recordDir := t.TempDir()
	t.Setenv("AGENTTEAM_TEST_FAKE_OPENCODE_RECORD_DIR", recordDir)
	t.Setenv("AGENTTEAM_TEST_FAKE_OPENCODE_HEALTH_FAILURE", "1")
	driver := &OpenCodeDriver{httpClient: &http.Client{Timeout: time.Second}}
	ctx, cancel := context.WithTimeout(context.Background(), 500*time.Millisecond)
	defer cancel()
	var cancelCalls atomic.Int32

	started, err := driver.startOpenCodeServe(ctx, openCodeServeStart{
		cfg: drivercontract.LaunchConfig{
			AgentID: "live-health-failure",
			Binary:  writeFakeOpenCodeServeBinary(t),
			WorkDir: t.TempDir(),
		},
		env:         os.Environ(),
		logLevel:    "WARN",
		stderr:      newCapturingPrefixWriter("[health-failure] ", openCodeCapturedOutputMaxBytes),
		stdout:      newCapturingPrefixWriter("[health-failure-stdout] ", openCodeCapturedOutputMaxBytes),
		diagnostics: openCodeLaunchDiagnostic{},
		onProcessStarted: func(server openCodeStartedServer) {
			originalCancel := server.process.cancel
			server.process.cancel = func() {
				cancelCalls.Add(1)
				originalCancel()
			}
		},
	})

	require.Error(t, err)
	require.ErrorIs(t, err, context.DeadlineExceeded)
	require.NotNil(t, started.process)
	require.EqualValues(t, 1, cancelCalls.Load())
	select {
	case <-started.process.exited:
	default:
		t.Fatal("health failure left the serve process running")
	}
	stopOpenCodeServerProcess(started.process)
	require.EqualValues(t, 1, cancelCalls.Load())
}

func writeOpenCodeTestBinary(t *testing.T) string {
	t.Helper()

	oldVersion := runtimeconfig.ManagedOpenCodeVersion
	runtimeconfig.ManagedOpenCodeVersion = ""
	t.Cleanup(func() { runtimeconfig.ManagedOpenCodeVersion = oldVersion })

	path := filepath.Join(t.TempDir(), "opencode")
	require.NoError(t, os.WriteFile(path, []byte("#!/bin/sh\nexit 0\n"), 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(filepath.Dir(path), "models.json"), []byte(`{"local":{"id":"local","name":"Local","env":[],"models":{}}}`+"\n"), 0o644))
	return path
}

func writeFailingOpenCodeServeBinary(t *testing.T) string {
	t.Helper()

	dir := t.TempDir()
	if runtime.GOOS == "windows" {
		path := filepath.Join(dir, "opencode.cmd")
		script := "@echo off\r\necho fake opencode startup failure 1>&2\r\nexit /b 42\r\n"
		require.NoError(t, os.WriteFile(path, []byte(script), 0o755))
		return path
	}

	path := filepath.Join(dir, "opencode")
	script := "#!/bin/sh\necho fake opencode startup failure >&2\nexit 42\n"
	require.NoError(t, os.WriteFile(path, []byte(script), 0o755))
	return path
}

func TestNormalizeOpenCodeModelForProvider(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name       string
		model      string
		providerID string
		want       string
	}{
		{
			name:       "strips matching provider prefix",
			model:      "agentteam/gpt-5.5",
			providerID: "agentteam",
			want:       "gpt-5.5",
		},
		{
			name:       "strips one foreign provider layer when nested model remains namespaced",
			model:      "anthropic/zhipu/glm-5-turbo",
			providerID: "agentteam",
			want:       "zhipu/glm-5-turbo",
		},
		{
			name:       "keeps bare namespaced model ids",
			model:      "zhipu/glm-5-turbo",
			providerID: "agentteam",
			want:       "zhipu/glm-5-turbo",
		},
		{
			name:       "keeps empty provider passthrough",
			model:      "anthropic/zhipu/glm-5-turbo",
			providerID: "",
			want:       "anthropic/zhipu/glm-5-turbo",
		},
	}

	for _, tc := range tests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			require.Equal(t, tc.want, normalizeOpenCodeModelForProvider(tc.model, tc.providerID))
		})
	}
}

func TestParseOpenCodeModel(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name       string
		model      string
		providerID string
		want       map[string]string
	}{
		{
			name:       "explicit provider wins",
			model:      "gpt-5",
			providerID: "openai",
			want: map[string]string{
				"providerID": "openai",
				"modelID":    "gpt-5",
			},
		},
		{
			name:  "slash separated model",
			model: "openai/gpt-5",
			want: map[string]string{
				"providerID": "openai",
				"modelID":    "gpt-5",
			},
		},
		{
			name:  "colon separated model",
			model: "openai:gpt-5",
			want: map[string]string{
				"providerID": "openai",
				"modelID":    "gpt-5",
			},
		},
		{
			name:  "model without provider returns nil",
			model: "gpt-5",
			want:  nil,
		},
	}

	for _, tc := range tests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			got := parseOpenCodeModel(tc.model, tc.providerID)
			if len(got) != len(tc.want) {
				t.Fatalf("len(parseOpenCodeModel(%q, %q))=%d, want %d", tc.model, tc.providerID, len(got), len(tc.want))
			}
			for key, wantValue := range tc.want {
				if got[key] != wantValue {
					t.Fatalf("parseOpenCodeModel(%q, %q)[%q]=%q, want %q", tc.model, tc.providerID, key, got[key], wantValue)
				}
			}
		})
	}
}

func TestOpenCodeContextWindowErrorQueuesAndStartsSummarize(t *testing.T) {
	t.Parallel()

	type summarizeRequest struct {
		method    string
		path      string
		directory string
		body      string
	}
	requests := make(chan summarizeRequest, 1)
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		body, _ := io.ReadAll(r.Body)
		requests <- summarizeRequest{
			method:    r.Method,
			path:      r.URL.Path,
			directory: r.Header.Get("x-opencode-directory"),
			body:      string(body),
		}
		_, _ = w.Write([]byte("true"))
	}))
	t.Cleanup(server.Close)

	driver := &OpenCodeDriver{
		serverURL:       server.URL,
		password:        "pw",
		workDir:         "/tmp/workspace",
		httpClient:      server.Client(),
		sessionID:       "sess-1",
		sessionStatus:   "idle",
		providerID:      "agentteam",
		launchModel:     "anthropic/zhipu/glm-5-turbo",
		translatedLines: make(chan string, 4),
	}
	queued := driver.QueueContextCompaction(drivercontract.ParsedEvent{
		Kind:    consts.ParsedEventKindError,
		Message: "API Error: The model has reached its context window limit.",
	})
	require.True(t, queued)

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
	defer cancel()
	started, err := driver.StartIdleCompaction(ctx, "sess-1")
	require.NoError(t, err)
	require.True(t, started)

	var request summarizeRequest
	select {
	case request = <-requests:
	case <-ctx.Done():
		t.Fatalf("summarize request was not received: %v", ctx.Err())
	}
	require.Equal(t, http.MethodPost, request.method)
	require.Equal(t, "/session/sess-1/summarize", request.path)
	require.Equal(t, "/tmp/workspace", request.directory)
	require.JSONEq(t, `{"providerID":"agentteam","modelID":"zhipu/glm-5-turbo"}`, request.body)
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))
	driver.handleSSEEvent(openCodeCompactionPartEvent(t, "sess-1", "msg-compaction-manual", false))
	driver.handleSSEEvent(openCodeSummaryAssistantEvent(t, "sess-1", "msg-summary-manual", "msg-compaction-manual"))
	driver.handleSSEEvent(openCodeSessionCompactedEvent(t, "sess-1"))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	var line string
	select {
	case line = <-driver.translatedLines:
	case <-ctx.Done():
		t.Fatalf("summarize completion event was not received: %v", ctx.Err())
	}
	compacted := driver.ParseLine(line)
	require.Equal(t, []drivercontract.ParsedEvent{{
		Kind:      consts.ParsedEventKindRuntimeCompacted,
		SessionID: "sess-1",
		Message:   "Context compacted.",
	}}, compacted)
	idle := driver.ParseLine(<-driver.translatedLines)
	require.Equal(t, []drivercontract.ParsedEvent{{
		Kind:      consts.ParsedEventKindRuntimeIdle,
		SessionID: "sess-1",
	}}, idle)
}

func TestOpenCodeSummarizeFailureRetiresGeneration(t *testing.T) {
	t.Parallel()

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, http.MethodPost, r.Method)
		require.Equal(t, "/session/sess-1/summarize", r.URL.Path)
		http.Error(w, "summarize unavailable\n"+strings.Repeat("x", 8192), http.StatusInternalServerError)
	}))
	t.Cleanup(server.Close)

	retired := make(chan struct{})
	driver := &OpenCodeDriver{
		serverURL:       server.URL,
		password:        "pw",
		httpClient:      server.Client(),
		sessionID:       "sess-1",
		sessionStatus:   "idle",
		translatedLines: make(chan string, 4),
		serverCancel:    sync.OnceFunc(func() { close(retired) }),
	}
	require.True(t, driver.QueueContextCompaction(drivercontract.ParsedEvent{
		Kind:    consts.ParsedEventKindError,
		Message: "context window exceeded",
	}))

	started, err := driver.StartIdleCompaction(context.Background(), "sess-1")
	require.NoError(t, err)
	require.True(t, started)

	select {
	case <-retired:
	case <-time.After(time.Second):
		t.Fatal("summarize failure did not retire the runtime generation")
	}
	var parsed []drivercontract.ParsedEvent
	require.Eventually(t, func() bool {
		for {
			select {
			case line := <-driver.translatedLines:
				parsed = append(parsed, driver.ParseLine(line)...)
			default:
				return len(parsed) >= 2
			}
		}
	}, time.Second, 10*time.Millisecond)
	require.Len(t, parsed, 2)
	require.Equal(t, consts.ParsedEventKindError, parsed[0].Kind)
	require.True(t, parsedBoolMetadata(parsed[0].Metadata, contextCompactionFailedMetadataKey))
	require.Contains(t, parsed[0].Message, "summarize unavailable")
	require.Less(t, len(parsed[0].Message), 4200, "summarize diagnostic must remain bounded")
	require.Equal(t, consts.ParsedEventKindTurnEnd, parsed[1].Kind)
	require.True(t, parsedBoolMetadata(parsed[1].Metadata, contextCompactionFailedMetadataKey))
	require.True(t, driver.generationRetiring)
	require.True(t, driver.sseDeliveryBlocked)
}

func TestOpenCodeProactiveCompactionStartsAboveThreshold(t *testing.T) {
	t.Parallel()

	requests := make(chan string, 1)
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests <- r.URL.Path
		require.Equal(t, http.MethodPost, r.Method)
		require.Equal(t, "/session/sess-1/summarize", r.URL.Path)
		_, _ = w.Write([]byte("true"))
	}))
	t.Cleanup(server.Close)

	driver := &OpenCodeDriver{
		serverURL:                    server.URL,
		password:                     "pw",
		httpClient:                   server.Client(),
		sessionID:                    "sess-1",
		sessionStatus:                "idle",
		translatedLines:              make(chan string, 4),
		proactiveCompactionThreshold: 100000,
		lastInputContextTokens:       150000,
	}

	started, err := driver.StartProactiveIdleCompaction(context.Background(), "sess-1")
	require.NoError(t, err)
	require.True(t, started)

	select {
	case gotPath := <-requests:
		require.Equal(t, "/session/sess-1/summarize", gotPath)
	case <-time.After(time.Second):
		t.Fatal("proactive summarize request was not received")
	}
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))
	driver.handleSSEEvent(openCodeCompactionPartEvent(t, "sess-1", "msg-compaction-proactive", false))
	driver.handleSSEEvent(openCodeSummaryAssistantEvent(t, "sess-1", "msg-summary-proactive", "msg-compaction-proactive"))
	driver.handleSSEEvent(openCodeSessionCompactedEvent(t, "sess-1"))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	compacted := driver.ParseLine(<-driver.translatedLines)
	require.Equal(t, []drivercontract.ParsedEvent{{
		Kind:      consts.ParsedEventKindRuntimeCompacted,
		SessionID: "sess-1",
		Message:   "Context compacted.",
	}}, compacted)
	idle := driver.ParseLine(<-driver.translatedLines)
	require.Equal(t, []drivercontract.ParsedEvent{{
		Kind:      consts.ParsedEventKindRuntimeIdle,
		SessionID: "sess-1",
	}}, idle)
}

func TestOpenCodeProactiveCompactionSkippedBelowThreshold(t *testing.T) {
	t.Parallel()

	var called bool
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))
	t.Cleanup(server.Close)

	driver := &OpenCodeDriver{
		serverURL:                    server.URL,
		password:                     "pw",
		httpClient:                   server.Client(),
		sessionID:                    "sess-1",
		sessionStatus:                "idle",
		translatedLines:              make(chan string, 4),
		proactiveCompactionThreshold: 100000,
		lastInputContextTokens:       50000,
	}

	started, err := driver.StartProactiveIdleCompaction(context.Background(), "sess-1")
	require.NoError(t, err)
	require.False(t, started)
	require.False(t, called, "summarize must not run below the proactive threshold")
}

func TestOpenCodeProactiveCompactionDisabledWhenThresholdZero(t *testing.T) {
	t.Parallel()

	var called bool
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))
	t.Cleanup(server.Close)

	driver := &OpenCodeDriver{
		serverURL:                    server.URL,
		password:                     "pw",
		httpClient:                   server.Client(),
		sessionID:                    "sess-1",
		sessionStatus:                "idle",
		translatedLines:              make(chan string, 4),
		proactiveCompactionThreshold: 0,
		lastInputContextTokens:       999999,
	}

	started, err := driver.StartProactiveIdleCompaction(context.Background(), "sess-1")
	require.NoError(t, err)
	require.False(t, started)
	require.False(t, called, "a zero threshold must disable proactive compaction")
}

func TestOpenCodeProactiveCompactionYieldsToQueuedOverflow(t *testing.T) {
	t.Parallel()

	var called bool
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))
	t.Cleanup(server.Close)

	driver := &OpenCodeDriver{
		serverURL:                    server.URL,
		password:                     "pw",
		httpClient:                   server.Client(),
		sessionID:                    "sess-1",
		sessionStatus:                "idle",
		translatedLines:              make(chan string, 4),
		proactiveCompactionThreshold: 100000,
		lastInputContextTokens:       150000,
	}
	require.True(t, driver.QueueContextCompaction(drivercontract.ParsedEvent{
		Kind:    consts.ParsedEventKindError,
		Message: "context window exceeded",
	}))

	started, err := driver.StartProactiveIdleCompaction(context.Background(), "sess-1")
	require.NoError(t, err)
	require.False(t, started, "proactive compaction must yield to a queued overflow compaction")
	require.False(t, called)
}

func TestOpenCodeProactiveCompactionAmbiguousFailureRetiresGeneration(t *testing.T) {
	t.Parallel()

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, http.MethodPost, r.Method)
		require.Equal(t, "/session/sess-1/summarize", r.URL.Path)
		_, _ = w.Write([]byte("true"))
	}))
	t.Cleanup(server.Close)

	retired := make(chan struct{})
	driver := &OpenCodeDriver{
		serverURL:                    server.URL,
		password:                     "pw",
		httpClient:                   server.Client(),
		sessionID:                    "sess-1",
		sessionStatus:                "idle",
		translatedLines:              make(chan string, 4),
		proactiveCompactionThreshold: 100000,
		lastInputContextTokens:       150000,
		summarizeIdleTimeout:         20 * time.Millisecond,
		serverCancel:                 sync.OnceFunc(func() { close(retired) }),
	}

	started, err := driver.StartProactiveIdleCompaction(context.Background(), "sess-1")
	require.NoError(t, err)
	require.True(t, started)

	select {
	case <-retired:
	case <-time.After(time.Second):
		t.Fatal("proactive summarize failure did not retire the runtime generation")
	}
	var parsed []drivercontract.ParsedEvent
	require.Eventually(t, func() bool {
		for {
			select {
			case line := <-driver.translatedLines:
				parsed = append(parsed, driver.ParseLine(line)...)
			default:
				return len(parsed) >= 2
			}
		}
	}, time.Second, 10*time.Millisecond)
	require.Len(t, parsed, 2)
	require.Equal(t, consts.ParsedEventKindError, parsed[0].Kind)
	require.True(t, parsedBoolMetadata(parsed[0].Metadata, contextCompactionFailedMetadataKey))
	require.True(t, parsedBoolMetadata(parsed[0].Metadata, contextCompactionMaintenanceMetadataKey))
	require.Equal(t, consts.ParsedEventKindTurnEnd, parsed[1].Kind)
	require.True(t, parsedBoolMetadata(parsed[1].Metadata, contextCompactionFailedMetadataKey))
	require.True(t, parsedBoolMetadata(parsed[1].Metadata, contextCompactionMaintenanceMetadataKey))
	require.True(t, driver.generationRetiring)
	require.True(t, driver.sseDeliveryBlocked)
}

func TestOpenCodeProactiveCompactionCooldown(t *testing.T) {
	t.Parallel()

	var called bool
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		called = true
	}))
	t.Cleanup(server.Close)

	driver := &OpenCodeDriver{
		serverURL:                    server.URL,
		password:                     "pw",
		httpClient:                   server.Client(),
		sessionID:                    "sess-1",
		sessionStatus:                "idle",
		translatedLines:              make(chan string, 4),
		proactiveCompactionThreshold: 100000,
		lastInputContextTokens:       150000,
		lastSummarizeCompletedAt:     time.Now(),
	}

	started, err := driver.StartProactiveIdleCompaction(context.Background(), "sess-1")
	require.NoError(t, err)
	require.False(t, started, "proactive compaction must not re-fire within the cooldown")
	require.False(t, called)
}

func TestResolveProactiveIdleCompactionThreshold(t *testing.T) {
	t.Setenv("AGENTTEAM_PROACTIVE_COMPACTION_TOKENS", "")

	require.Equal(t, proactiveIdleCompactionDefaultThreshold,
		resolveProactiveIdleCompactionThreshold(drivercontract.LaunchConfig{}))
	require.Equal(t, uint64(80000),
		resolveProactiveIdleCompactionThreshold(drivercontract.LaunchConfig{
			Env: map[string]string{"AGENTTEAM_PROACTIVE_COMPACTION_TOKENS": "80000"},
		}))
	require.Equal(t, uint64(0),
		resolveProactiveIdleCompactionThreshold(drivercontract.LaunchConfig{
			Env: map[string]string{"AGENTTEAM_PROACTIVE_COMPACTION_TOKENS": "0"},
		}))
	require.Equal(t, proactiveIdleCompactionDefaultThreshold,
		resolveProactiveIdleCompactionThreshold(drivercontract.LaunchConfig{
			Env: map[string]string{"AGENTTEAM_PROACTIVE_COMPACTION_TOKENS": "not-a-number"},
		}))
}

func TestOpenCodeSummarizeSuppressesInternalSessionIdle(t *testing.T) {
	t.Parallel()

	// Block the summarize HTTP call so summarizeInProgress stays true while we
	// simulate the internal session.status idle that summarize's prompt.loop
	// publishes before the call returns.
	proceed := make(chan struct{})
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		<-proceed
		_, _ = w.Write([]byte("true"))
	}))
	t.Cleanup(server.Close)

	driver := &OpenCodeDriver{
		serverURL:       server.URL,
		password:        "pw",
		httpClient:      server.Client(),
		sessionID:       "sess-1",
		sessionStatus:   "idle",
		translatedLines: make(chan string, 8),
	}
	require.True(t, driver.QueueContextCompaction(drivercontract.ParsedEvent{
		Kind:    consts.ParsedEventKindError,
		Message: "context window exceeded",
	}))
	started, err := driver.StartIdleCompaction(context.Background(), "sess-1")
	require.NoError(t, err)
	require.True(t, started)
	require.Eventually(t, func() bool {
		driver.mu.Lock()
		defer driver.mu.Unlock()
		return driver.summarizeInProgress
	}, time.Second, 5*time.Millisecond)

	// A duplicate idle from the previous turn may still be queued when Process
	// starts summarize. It must neither become turn_end nor satisfy the
	// summarize fence before this invocation's own busy is observed.
	statusIdle, _ := json.Marshal(map[string]any{
		"sessionID": "sess-1",
		"status":    map[string]any{"type": "idle"},
	})
	driver.handleSessionStatus(statusIdle, "sess-1")

	select {
	case line := <-driver.translatedLines:
		t.Fatalf("internal session idle during summarize must not emit an event, got %s", line)
	case <-time.After(50 * time.Millisecond):
	}

	// Even HTTP success is insufficient while only the stale idle was seen.
	close(proceed)
	require.Eventually(t, func() bool {
		driver.mu.Lock()
		defer driver.mu.Unlock()
		return driver.summarizeFence != nil && driver.summarizeFence.httpSucceeded
	}, time.Second, 5*time.Millisecond)
	select {
	case line := <-driver.translatedLines:
		t.Fatalf("HTTP success plus stale idle completed summarize: %s", line)
	case <-time.After(50 * time.Millisecond):
	}

	// This summarize's own compaction part, compacted success, and busy/idle
	// complete the source-derived SSE half of the fence.
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))
	driver.handleSSEEvent(openCodeCompactionPartEvent(t, "sess-1", "msg-compaction-fenced", false))
	driver.handleSSEEvent(openCodeSummaryAssistantEvent(t, "sess-1", "msg-summary-fenced", "msg-compaction-fenced"))
	driver.handleSSEEvent(openCodeSessionCompactedEvent(t, "sess-1"))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	var got []drivercontract.ParsedEvent
	require.Eventually(t, func() bool {
		select {
		case line := <-driver.translatedLines:
			events := driver.ParseLine(line)
			if len(events) == 1 {
				got = append(got, events[0])
				return events[0].Kind == consts.ParsedEventKindRuntimeIdle
			}
		default:
		}
		return false
	}, time.Second, 5*time.Millisecond)
	require.Equal(t, []string{
		consts.ParsedEventKindRuntimeCompacted,
		consts.ParsedEventKindRuntimeIdle,
	}, parsedEventKinds(got))
}

func TestOpenCodeSummarizeHTTPWithoutSSEIdleRetiresGeneration(t *testing.T) {
	t.Parallel()

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_, _ = w.Write([]byte("true"))
	}))
	t.Cleanup(server.Close)

	retired := make(chan struct{})
	driver := &OpenCodeDriver{
		serverURL:            server.URL,
		password:             "pw",
		httpClient:           server.Client(),
		sessionID:            "sess-1",
		sessionStatus:        "idle",
		translatedLines:      make(chan string, 4),
		summarizeIdleTimeout: 20 * time.Millisecond,
		serverCancel:         sync.OnceFunc(func() { close(retired) }),
	}
	require.True(t, driver.QueueContextCompaction(drivercontract.ParsedEvent{
		Kind:    consts.ParsedEventKindError,
		Message: "context window exceeded",
	}))
	started, err := driver.StartIdleCompaction(context.Background(), "sess-1")
	require.NoError(t, err)
	require.True(t, started)

	select {
	case <-retired:
	case <-time.After(time.Second):
		t.Fatal("missing summarize SSE boundary did not retire the runtime generation")
	}
	errorEvents := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, errorEvents, 1)
	require.Equal(t, consts.ParsedEventKindError, errorEvents[0].Kind)
	boundaryEvents := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, boundaryEvents, 1)
	require.Equal(t, consts.ParsedEventKindTurnEnd, boundaryEvents[0].Kind)
	require.True(t, parsedBoolMetadata(boundaryEvents[0].Metadata, contextCompactionFailedMetadataKey))
	require.Empty(t, driver.translatedLines, "missing SSE idle must not fabricate runtime_idle or duplicate turn_end")
}

func TestOpenCodeSummarizeHTTPAndIdleWithoutCompactedRetiresGeneration(t *testing.T) {
	t.Parallel()

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_, _ = w.Write([]byte("true"))
	}))
	t.Cleanup(server.Close)

	retired := make(chan struct{})
	driver := &OpenCodeDriver{
		serverURL:            server.URL,
		password:             "pw",
		httpClient:           server.Client(),
		sessionID:            "sess-1",
		sessionStatus:        "idle",
		translatedLines:      make(chan string, 4),
		summarizeIdleTimeout: 30 * time.Millisecond,
		serverCancel:         sync.OnceFunc(func() { close(retired) }),
	}
	require.True(t, driver.QueueContextCompaction(drivercontract.ParsedEvent{
		Kind:    consts.ParsedEventKindError,
		Message: "context window exceeded",
	}))
	started, err := driver.StartIdleCompaction(context.Background(), "sess-1")
	require.NoError(t, err)
	require.True(t, started)

	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))
	driver.handleSSEEvent(openCodeCompactionPartEvent(t, "sess-1", "msg-compaction-no-success", false))
	driver.handleSSEEvent(openCodeSummaryAssistantEvent(t, "sess-1", "msg-summary-no-success", "msg-compaction-no-success"))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))

	select {
	case <-retired:
	case <-time.After(time.Second):
		t.Fatal("HTTP 200 plus busy/idle without session.compacted did not retire")
	}
	errorEvents := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, errorEvents, 1)
	require.Equal(t, consts.ParsedEventKindError, errorEvents[0].Kind)
	require.True(t, parsedBoolMetadata(errorEvents[0].Metadata, contextCompactionFailedMetadataKey))
	boundaryEvents := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, boundaryEvents, 1)
	require.Equal(t, consts.ParsedEventKindTurnEnd, boundaryEvents[0].Kind)
	require.True(t, parsedBoolMetadata(boundaryEvents[0].Metadata, contextCompactionFailedMetadataKey))
	require.Empty(t, driver.translatedLines)
}

func TestOpenCodeSummarizeSessionErrorRetiresDespiteHTTP200(t *testing.T) {
	t.Parallel()

	releaseResponse := make(chan struct{})
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		<-releaseResponse
		_, _ = w.Write([]byte("true"))
	}))
	t.Cleanup(server.Close)

	retired := make(chan struct{})
	driver := &OpenCodeDriver{
		serverURL:       server.URL,
		password:        "pw",
		httpClient:      server.Client(),
		sessionID:       "sess-1",
		sessionStatus:   "idle",
		translatedLines: make(chan string, 8),
		serverCancel:    sync.OnceFunc(func() { close(retired) }),
	}
	require.True(t, driver.QueueContextCompaction(drivercontract.ParsedEvent{
		Kind:    consts.ParsedEventKindError,
		Message: "context window exceeded",
	}))
	started, err := driver.StartIdleCompaction(context.Background(), "sess-1")
	require.NoError(t, err)
	require.True(t, started)
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))
	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"ProviderError","message":"compaction provider failed"}}}`)

	select {
	case <-retired:
	case <-time.After(time.Second):
		t.Fatal("summarize session.error did not retire the runtime generation")
	}
	events := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, events, 1)
	require.Equal(t, consts.ParsedEventKindError, events[0].Kind)
	require.True(t, parsedBoolMetadata(events[0].Metadata, contextCompactionFailedMetadataKey))
	require.False(t, parsedBoolMetadata(events[0].Metadata, contextCompactionMaintenanceMetadataKey))
	require.False(t, parsedBoolMetadata(events[0].Metadata, runtimeErrorNonterminalMetadataKey))
	boundary := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, boundary, 1)
	require.Equal(t, consts.ParsedEventKindTurnEnd, boundary[0].Kind)
	require.True(t, parsedBoolMetadata(boundary[0].Metadata, contextCompactionFailedMetadataKey))
	require.False(t, parsedBoolMetadata(boundary[0].Metadata, contextCompactionMaintenanceMetadataKey))

	// Upstream can still return HTTP 200 and publish cleanup assistant/idle
	// events after the error. None may revive or complete this generation.
	close(releaseResponse)
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	driver.handleSSEEvent(`{"type":"message.updated","properties":{"sessionID":"sess-1","info":{"id":"msg_100000000002BBBBBBBBBBBBBB","parentID":"msg_100000000001AAAAAAAAAAAAAA","sessionID":"sess-1","role":"assistant"}}}`)
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	require.Never(t, func() bool {
		select {
		case <-driver.translatedLines:
			return true
		default:
			return false
		}
	}, 100*time.Millisecond, 5*time.Millisecond)
	driver.mu.Lock()
	require.True(t, driver.generationRetiring)
	require.Equal(t, "error", driver.sessionStatus)
	require.True(t, driver.turnActive)
	driver.mu.Unlock()
}

func TestOpenCodeProactiveSummarizeSessionErrorCarriesMaintenancePurpose(t *testing.T) {
	t.Parallel()

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_, _ = w.Write([]byte("true"))
	}))
	t.Cleanup(server.Close)

	retired := make(chan struct{})
	driver := &OpenCodeDriver{
		serverURL:                    server.URL,
		password:                     "pw",
		httpClient:                   server.Client(),
		sessionID:                    "sess-1",
		sessionStatus:                "idle",
		translatedLines:              make(chan string, 8),
		proactiveCompactionThreshold: 100000,
		lastInputContextTokens:       150000,
		serverCancel:                 sync.OnceFunc(func() { close(retired) }),
	}
	started, err := driver.StartProactiveIdleCompaction(context.Background(), "sess-1")
	require.NoError(t, err)
	require.True(t, started)
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))
	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"ProviderError","message":"compaction provider failed"}}}`)

	select {
	case <-retired:
	case <-time.After(time.Second):
		t.Fatal("proactive summarize session.error did not retire the runtime generation")
	}
	events := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, events, 1)
	require.Equal(t, consts.ParsedEventKindError, events[0].Kind)
	require.True(t, parsedBoolMetadata(events[0].Metadata, contextCompactionFailedMetadataKey))
	require.True(t, parsedBoolMetadata(events[0].Metadata, contextCompactionMaintenanceMetadataKey))
	boundary := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, boundary, 1)
	require.Equal(t, consts.ParsedEventKindTurnEnd, boundary[0].Kind)
	require.True(t, parsedBoolMetadata(boundary[0].Metadata, contextCompactionFailedMetadataKey))
	require.True(t, parsedBoolMetadata(boundary[0].Metadata, contextCompactionMaintenanceMetadataKey))
}

func TestOpenCodeProactiveSummarizePreservesDelayedPriorTurnFailure(t *testing.T) {
	t.Parallel()

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_, _ = w.Write([]byte("true"))
	}))
	t.Cleanup(server.Close)

	retired := make(chan struct{})
	driver := &OpenCodeDriver{
		serverURL:                    server.URL,
		password:                     "pw",
		httpClient:                   server.Client(),
		sessionID:                    "sess-1",
		sessionStatus:                "busy",
		turnActive:                   true,
		translatedLines:              make(chan string, 8),
		proactiveCompactionThreshold: 100000,
		lastInputContextTokens:       150000,
		serverCancel:                 sync.OnceFunc(func() { close(retired) }),
	}
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	ordinaryBoundary := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, ordinaryBoundary, 1)
	require.Equal(t, consts.ParsedEventKindTurnEnd, ordinaryBoundary[0].Kind)
	require.True(t, driver.postBoundaryAwaitFreshBusy)

	started, err := driver.StartProactiveIdleCompaction(context.Background(), "sess-1")
	require.NoError(t, err)
	require.True(t, started)
	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"ContextOverflowError","message":"late prior-turn overflow"}}}`)

	select {
	case <-retired:
	case <-time.After(time.Second):
		t.Fatal("delayed prior-turn error did not retire the runtime generation")
	}
	events := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, events, 1)
	require.Equal(t, consts.ParsedEventKindError, events[0].Kind)
	require.True(t, parsedBoolMetadata(events[0].Metadata, contextCompactionFailedMetadataKey))
	require.False(t, parsedBoolMetadata(events[0].Metadata, contextCompactionMaintenanceMetadataKey))
	boundary := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, boundary, 1)
	require.Equal(t, consts.ParsedEventKindTurnEnd, boundary[0].Kind)
	require.True(t, parsedBoolMetadata(boundary[0].Metadata, contextCompactionFailedMetadataKey))
	require.False(t, parsedBoolMetadata(boundary[0].Metadata, contextCompactionMaintenanceMetadataKey))
}

func TestOpenCodeProactiveSummaryAssistantFailureCarriesMaintenancePurpose(t *testing.T) {
	t.Parallel()

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		_, _ = w.Write([]byte("true"))
	}))
	t.Cleanup(server.Close)

	driver := &OpenCodeDriver{
		serverURL:                    server.URL,
		password:                     "pw",
		httpClient:                   server.Client(),
		sessionID:                    "sess-1",
		sessionStatus:                "idle",
		translatedLines:              make(chan string, 8),
		proactiveCompactionThreshold: 100000,
		lastInputContextTokens:       150000,
	}
	started, err := driver.StartProactiveIdleCompaction(context.Background(), "sess-1")
	require.NoError(t, err)
	require.True(t, started)
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))
	driver.handleSSEEvent(openCodeCompactionPartEvent(t, "sess-1", "msg-compaction-proactive", false))
	driver.handleSSEEvent(openCodeSummaryAssistantEvent(
		t,
		"sess-1",
		"msg-summary-proactive",
		"msg-compaction-proactive",
	))
	driver.handleSSEEvent(`{"type":"message.updated","properties":{"sessionID":"sess-1","info":{"id":"msg-summary-proactive","parentID":"msg-compaction-proactive","sessionID":"sess-1","role":"assistant","summary":true,"error":{"name":"ProviderError","message":"summary provider failed"},"time":{"completed":200}}}}`)

	driver.mu.Lock()
	terminalError := driver.pendingSessionError
	fence := driver.summarizeFence
	serverCancel := driver.beginOpenCodeGenerationRetirementLocked()
	driver.mu.Unlock()
	if serverCancel != nil {
		serverCancel()
	}
	require.NotNil(t, terminalError)
	require.True(t, parsedBoolMetadata(terminalError.Metadata, contextCompactionFailedMetadataKey))
	require.True(t, parsedBoolMetadata(terminalError.Metadata, contextCompactionMaintenanceMetadataKey))
	select {
	case <-fence.workerDone:
	case <-time.After(time.Second):
		t.Fatal("proactive summarize worker did not stop after generation retirement")
	}
}

func TestOpenCodeStaleSummarizeCompletionCannotMutateReplacementGeneration(t *testing.T) {
	t.Parallel()

	requestStarted := make(chan struct{})
	proceed := make(chan struct{})
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		close(requestStarted)
		<-proceed
		_, _ = w.Write([]byte("true"))
	}))
	t.Cleanup(server.Close)

	driver := &OpenCodeDriver{
		serverURL:         server.URL,
		password:          "pw",
		httpClient:        server.Client(),
		sessionID:         "sess-old",
		sessionStatus:     "idle",
		sessionGeneration: 1,
		translatedLines:   make(chan string, 4),
	}
	require.True(t, driver.QueueContextCompaction(drivercontract.ParsedEvent{
		Kind:    consts.ParsedEventKindError,
		Message: "context window exceeded",
	}))
	started, err := driver.StartIdleCompaction(context.Background(), "sess-old")
	require.NoError(t, err)
	require.True(t, started)
	select {
	case <-requestStarted:
	case <-time.After(time.Second):
		t.Fatal("summarize request did not start")
	}

	// Model the state reset performed by Spawn for a replacement server
	// generation while the old HTTP response is still outstanding.
	driver.mu.Lock()
	driver.beginOpenCodeGenerationRetirementLocked()
	driver.sessionID = "sess-new"
	driver.summarizeFence = nil
	driver.summarizeInProgress = false
	driver.turnActive = false
	driver.sessionStatus = "idle"
	driver.generationRetiring = false
	driver.sseDeliveryBlocked = false
	driver.mu.Unlock()
	close(proceed)

	require.Never(t, func() bool {
		select {
		case <-driver.translatedLines:
			return true
		default:
			return false
		}
	}, 100*time.Millisecond, 5*time.Millisecond)
	driver.mu.Lock()
	require.Equal(t, "sess-new", driver.sessionID)
	require.Equal(t, "idle", driver.sessionStatus)
	require.False(t, driver.turnActive)
	require.False(t, driver.summarizeInProgress)
	driver.mu.Unlock()
}

func TestOpenCodeDeliverBusyStdinMessageReturnsPromptError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, "/session/sess-1/prompt_async", r.URL.Path)
		http.Error(w, "prompt failed", http.StatusBadGateway)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:     server.URL,
		password:      "pw",
		sessionID:     "sess-1",
		sessionStatus: "idle",
		httpClient:    server.Client(),
		workDir:       t.TempDir(),
	}

	err := driver.DeliverBusyStdinInput(context.Background(), testRuntimeInput("hello"), "sess-1")
	require.Error(t, err)
	require.Contains(t, err.Error(), "status 502")
}

func TestOpenCodeRuntimeDebugWritesDiagnosticsWithoutActivityBreadcrumbs(t *testing.T) {
	driver := &OpenCodeDriver{
		sessionID:       "sess-1",
		translatedLines: make(chan string, 1),
	}
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))
	select {
	case line := <-driver.translatedLines:
		t.Fatalf("runtime debug disabled emitted activity: %s", line)
	default:
	}

	driver = &OpenCodeDriver{
		sessionID:           "sess-1",
		translatedLines:     make(chan string, 2),
		runtimeDebugEnabled: true,
	}
	debugLogPath := attachOpenCodeRuntimeDebugLogForTest(t, driver, t.TempDir())
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))

	select {
	case line := <-driver.translatedLines:
		t.Fatalf("runtime diagnostic polluted activity stream: %s", line)
	default:
	}

	entries := readOpenCodeRuntimeDebugEntries(t, debugLogPath)
	require.Len(t, entries, 2)
	require.Equal(t, 1, entries[0].SchemaVersion)
	require.Equal(t, "opencode", entries[0].Runtime)
	require.NotEmpty(t, entries[0].LaunchID)
	require.Equal(t, "sess-1", entries[0].SessionID)
	require.Equal(t, "opencode sse event received", entries[0].Event)
	require.Equal(t, "opencode session.status", entries[1].Event)
	require.Equal(t, "busy", entries[1].Metadata["status"])
	require.False(t, entries[1].Timestamp.IsZero())
}

func TestOpenCodeRuntimeDebugScrubsSensitiveMetadata(t *testing.T) {
	driver := &OpenCodeDriver{
		sessionID:           "sess-1",
		translatedLines:     make(chan string, 1),
		runtimeDebugEnabled: true,
	}
	debugLogPath := attachOpenCodeRuntimeDebugLogForTest(t, driver, t.TempDir())
	driver.emitRuntimeDebug("opencode debug", map[string]any{
		"apiKey":        "sk-secret",
		"authorization": "Bearer secret",
		"url":           "https://user:pass@example.test/path?token=secret#frag",
		"error":         fmt.Errorf("request failed: Authorization: Bearer nested-secret"),
		"nested": map[string]any{
			"password": "secret",
			"value":    "visible",
		},
	})

	entries := readOpenCodeRuntimeDebugEntries(t, debugLogPath)
	require.Len(t, entries, 1)
	metadata := entries[0].Metadata
	require.Equal(t, "<redacted>", metadata["apiKey"])
	require.Equal(t, "<redacted>", metadata["authorization"])
	require.Equal(t, "https://example.test/path", metadata["url"])
	require.Equal(t, "request failed: Authorization: <redacted>", metadata["error"])
	require.Equal(t, map[string]any{
		"password": "<redacted>",
		"value":    "visible",
	}, metadata["nested"])
	joined, err := json.Marshal(metadata)
	require.NoError(t, err)
	require.NotContains(t, string(joined), "sk-secret")
	require.NotContains(t, string(joined), "Bearer secret")
	require.NotContains(t, string(joined), "nested-secret")
	require.NotContains(t, string(joined), "token=secret")
	require.Empty(t, driver.translatedLines)
}

func TestOpenCodeRuntimeDebugLogLivesBesideServeLog(t *testing.T) {
	workDir := t.TempDir()
	driver := &OpenCodeDriver{
		sessionID:           "sess-1",
		translatedLines:     make(chan string, 1),
		runtimeDebugEnabled: true,
		serveLogPath:        openCodeServeLogPath(workDir),
	}
	debugLogPath := attachOpenCodeRuntimeDebugLogForTest(t, driver, workDir)

	driver.emitRuntimeDebug("opencode debug", map[string]any{"url": "http://127.0.0.1:1234?token=secret"})

	require.Equal(t, filepath.Join(workDir, "diagnostics", "opencode-runtime-debug.jsonl"), debugLogPath)
	require.Equal(t, filepath.Dir(openCodeServeLogPath(workDir)), filepath.Dir(debugLogPath))
	info, err := os.Stat(debugLogPath)
	require.NoError(t, err)
	require.Equal(t, os.FileMode(0o600), info.Mode().Perm())
	entries := readOpenCodeRuntimeDebugEntries(t, debugLogPath)
	require.Len(t, entries, 1)
	require.Equal(t, "http://127.0.0.1:1234", entries[0].Metadata["url"])
	require.Empty(t, driver.translatedLines)
}

func TestOpenCodeDiagnosticLogsAttachRuntimeDebugWhenEnabled(t *testing.T) {
	workDir := t.TempDir()
	driver := &OpenCodeDriver{
		sessionID:           "sess-1",
		translatedLines:     make(chan string, 1),
		runtimeDebugEnabled: true,
	}
	diagnosticLogs, err := driver.attachOpenCodeDiagnosticLogs(drivercontract.LaunchConfig{
		AgentID:             "agt-debug",
		WorkDir:             workDir,
		RuntimeDebugEnabled: true,
	}, nil, nil)
	require.NoError(t, err)
	t.Cleanup(func() {
		require.NoError(t, diagnosticLogs.Close())
	})

	require.Equal(t, openCodeServeLogPath(workDir), diagnosticLogs.serve.Path())
	require.Equal(t, openCodeRuntimeDebugLogPath(workDir), diagnosticLogs.runtimeDebug.Path())
	driver.emitRuntimeDebug("opencode debug", map[string]any{"status": "busy"})

	entries := readOpenCodeRuntimeDebugEntries(t, diagnosticLogs.runtimeDebug.Path())
	require.Len(t, entries, 1)
	require.Equal(t, "agt-debug", entries[0].AgentID)
	require.NotEmpty(t, entries[0].LaunchID)
	require.Equal(t, "sess-1", entries[0].SessionID)
	require.Equal(t, "busy", entries[0].Metadata["status"])
	require.Empty(t, driver.translatedLines)
}

func TestOpenCodeServeLogFileTeeScrubsAndRotates(t *testing.T) {
	workDir := t.TempDir()

	stderr := newCapturingPrefixWriter("[opencode] ", openCodeCapturedOutputMaxBytes)
	stdout := newCapturingPrefixWriter("[opencode-stdout] ", openCodeCapturedOutputMaxBytes)
	driver := &OpenCodeDriver{}
	diagnosticLogs, err := driver.attachOpenCodeDiagnosticLogs(drivercontract.LaunchConfig{WorkDir: workDir}, stderr, stdout)
	require.NoError(t, err)
	defer func() {
		require.NoError(t, diagnosticLogs.Close())
	}()
	serveLog := diagnosticLogs.serve
	serveLog.maxBytes = 512
	require.Equal(t, filepath.Join(workDir, "diagnostics", "opencode-serve.log"), driver.serveLogPath)
	require.Empty(t, diagnosticLogs.runtimeDebug.Path())
	require.NoFileExists(t, openCodeRuntimeDebugLogPath(workDir))

	_, err = stdout.Write([]byte("stdout hello\n"))
	require.NoError(t, err)
	_, err = stderr.Write([]byte("OPENCODE_SERVER_PASSWORD=secret Authorization=Bearer abc apiKey=secret token=secret AGENTTEAM_EXEC_TOKEN_FILE=/private/exec-token\n"))
	require.NoError(t, err)
	_, err = stderr.Write([]byte(`error="Failed query: insert into part values (?)\nparams: secret prompt body"` + "\n"))
	require.NoError(t, err)
	_, err = stdout.Write([]byte(strings.Repeat("x", 300) + "\n"))
	require.NoError(t, err)
	require.NoError(t, serveLog.Close())

	current, err := os.ReadFile(serveLog.Path())
	require.NoError(t, err)
	rotated, err := os.ReadFile(serveLog.Path() + ".1")
	require.NoError(t, err)
	allLogs := string(rotated) + string(current)
	require.Contains(t, allLogs, "[stdout] stdout hello")
	require.Contains(t, allLogs, "[launch:ocl_")
	require.Contains(t, allLogs, "[stderr] OPENCODE_SERVER_PASSWORD=<redacted>")
	require.Contains(t, allLogs, "Authorization=<redacted>")
	require.Contains(t, allLogs, "apiKey=<redacted>")
	require.Contains(t, allLogs, "token=<redacted>")
	require.Contains(t, allLogs, "AGENTTEAM_EXEC_TOKEN_FILE=<redacted>")
	require.NotContains(t, allLogs, "/private/exec-token")
	require.NotContains(t, allLogs, "secret")
	require.NotContains(t, allLogs, "Bearer abc")
	require.NotContains(t, allLogs, "secret prompt body")
	require.Contains(t, allLogs, `\nparams: <redacted>`)
}

func TestOpenCodeRuntimeErrorDiagnosticKeepsCodesWithoutRawMessage(t *testing.T) {
	raw := json.RawMessage(`{
		"name":"UnknownError",
		"message":"Failed query: insert into part values (?) - database is locked - private tool input",
		"cause":{"code":"SQLITE_BUSY","errno":5,"sqlite_errorname":"SQLITE_BUSY"}
	}`)

	diagnostic := openCodeRuntimeErrorDiagnostic(raw, runtimeErrorMessageFromRaw(raw))

	require.Equal(t, "UnknownError", diagnostic["error_name"])
	require.Equal(t, "SQLITE_BUSY", diagnostic["cause_code"])
	require.Equal(t, json.Number("5"), diagnostic["cause_errno"])
	require.Equal(t, "SQLITE_BUSY", diagnostic["sqlite_error_name"])
	require.Equal(t, openCodeSessionDatabaseBusyCode, diagnostic["runtime_error_code"])
	require.NotZero(t, diagnostic["error_message_bytes"])
	require.NotEmpty(t, diagnostic["error_message_fingerprint"])
	encoded, err := json.Marshal(diagnostic)
	require.NoError(t, err)
	require.NotContains(t, string(encoded), "private tool input")
	require.NotContains(t, string(encoded), "insert into part")
}

func TestOpenCodePromptAsyncDebugDoesNotIncludePromptText(t *testing.T) {
	var requestBody string
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, "/session/sess-1/prompt_async", r.URL.Path)
		body, err := io.ReadAll(r.Body)
		require.NoError(t, err)
		requestBody = string(body)
		w.WriteHeader(http.StatusNoContent)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:           server.URL,
		password:            "pw",
		sessionID:           "sess-1",
		providerID:          "agentteam",
		httpClient:          server.Client(),
		workDir:             t.TempDir(),
		translatedLines:     make(chan string, 4),
		runtimeDebugEnabled: true,
	}
	debugLogPath := attachOpenCodeRuntimeDebugLogForTest(t, driver, driver.workDir)

	const prompt = "secret prompt body should not appear in debug"
	err := driver.sendPromptAsync(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{}, prompt), "agentteam/deepseek-v4-pro", "msg-debug")
	require.NoError(t, err)
	require.Contains(t, requestBody, prompt)

	debugEvents := readOpenCodeRuntimeDebugEntries(t, debugLogPath)
	require.NotEmpty(t, debugEvents)
	hasRequestBodySize := false
	for _, event := range debugEvents {
		require.NotContains(t, event.Event, prompt)
		encoded, marshalErr := json.Marshal(event.Metadata)
		require.NoError(t, marshalErr)
		require.NotContains(t, string(encoded), prompt)
		if _, ok := event.Metadata["request_body_size"]; ok {
			hasRequestBodySize = true
		}
	}
	require.True(t, hasRequestBodySize)
	require.Empty(t, driver.translatedLines)
}

func attachOpenCodeRuntimeDebugLogForTest(t *testing.T, driver *OpenCodeDriver, workDir string) string {
	t.Helper()
	debugLog, err := openOpenCodeDiagnosticLogFile(openCodeRuntimeDebugLogPath(workDir), openCodeRuntimeDebugLogMaxBytes)
	require.NoError(t, err)
	driver.mu.Lock()
	driver.runtimeDebugLog = debugLog
	driver.runtimeDebugAgentID = "agt-debug"
	driver.runtimeDebugLaunchID = "ocl_test"
	driver.mu.Unlock()
	t.Cleanup(func() {
		require.NoError(t, debugLog.Close())
	})
	return debugLog.Path()
}

func readOpenCodeRuntimeDebugEntries(t *testing.T, path string) []openCodeRuntimeDebugEntry {
	t.Helper()
	data, err := os.ReadFile(path)
	require.NoError(t, err)
	lines := strings.Split(strings.TrimSpace(string(data)), "\n")
	if len(lines) == 1 && lines[0] == "" {
		return nil
	}
	entries := make([]openCodeRuntimeDebugEntry, 0, len(lines))
	for _, line := range lines {
		var entry openCodeRuntimeDebugEntry
		require.NoError(t, json.Unmarshal([]byte(line), &entry))
		entries = append(entries, entry)
	}
	return entries
}

func TestOpenCodeSendPromptAsyncNormalizesPrefixedModel(t *testing.T) {
	var body struct {
		Agent string            `json:"agent"`
		Model map[string]string `json:"model"`
	}
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, "/session/sess-1/prompt_async", r.URL.Path)
		require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
		w.WriteHeader(http.StatusNoContent)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:  server.URL,
		password:   "pw",
		sessionID:  "sess-1",
		providerID: "agentteam",
		httpClient: server.Client(),
		workDir:    t.TempDir(),
	}

	err := driver.sendPromptAsync(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{}, "hello"), "anthropic/zhipu/glm-5-turbo", "")
	require.NoError(t, err)
	require.Equal(t, openCodeDefaultAgent, body.Agent)
	require.Equal(t, map[string]string{
		"providerID": "agentteam",
		"modelID":    "zhipu/glm-5-turbo",
	}, body.Model)
}

func TestOpenCodeAckedBusyRuntimeInputIsRejectedBeforeMutation(t *testing.T) {
	runtimeInputID := "agentteam-message-1"
	requests := 0
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests++
		http.Error(w, "must not be called", http.StatusInternalServerError)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:       server.URL,
		password:        "pw",
		sessionID:       "sess-1",
		httpClient:      server.Client(),
		workDir:         t.TempDir(),
		translatedLines: make(chan string, 2),
	}
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))

	err := driver.DeliverAckedBusyRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: runtimeInputID}, "deliver while busy"), "sess-1")
	require.ErrorIs(t, err, errRuntimeInputMutationNotAttempted)
	require.False(t, driver.SupportsAckedBusyRuntimeInputDelivery())
	require.Zero(t, requests)
	require.Nil(t, driver.pendingRuntimeInputs)
}

func TestOpenCodeDeliverRuntimeInputAtIdleWaitsForExactAssistantParent(t *testing.T) {
	runtimeInputID := "agentteam-message-1"
	var body struct {
		Agent     string           `json:"agent"`
		MessageID string           `json:"messageID"`
		Parts     []map[string]any `json:"parts"`
	}
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, "/session/sess-1/prompt_async", r.URL.Path)
		require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
		w.WriteHeader(http.StatusNoContent)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:       server.URL,
		password:        "pw",
		sessionID:       "sess-1",
		sessionStatus:   "idle",
		httpClient:      server.Client(),
		workDir:         t.TempDir(),
		translatedLines: make(chan string, 2),
	}
	require.NoError(t, driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: runtimeInputID}, "deliver after idle"), "sess-1"))
	require.Equal(t, openCodeDefaultAgent, body.Agent)
	requireOpenCodeRuntimeMessageID(t, body.MessageID)
	require.Len(t, body.Parts, 1)
	require.Equal(t, "text", body.Parts[0]["type"])
	require.Equal(t, "deliver after idle", body.Parts[0]["text"])
	metadata := body.Parts[0]["metadata"].(map[string]any)
	agentteamMetadata := metadata["agentteam"].(map[string]any)
	require.Equal(t, runtimeInputID, agentteamMetadata["runtime_input_id"])

	driver.handleSSEEvent(openCodeUserMessageUpdatedEvent(t, "sess-1", body.MessageID))
	select {
	case line := <-driver.translatedLines:
		t.Fatalf("persistence alone acked runtime input: %s", line)
	default:
	}

	driver.handleSSEEvent(openCodeAssistantMessageUpdatedEvent(t, "sess-1", "msg-assistant-1", body.MessageID))
	require.Equal(t, []drivercontract.ParsedEvent{{
		Kind:           consts.ParsedEventKindRuntimeInputAcked,
		RuntimeInputID: runtimeInputID,
		SessionID:      "sess-1",
	}}, driver.ParseLine(<-driver.translatedLines))
}

func TestOpenCodePromptAsyncMarksBusyBeforeTransportResponse(t *testing.T) {
	entered := make(chan struct{})
	release := make(chan struct{})
	client := &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
		close(entered)
		<-release
		return &http.Response{
			StatusCode: http.StatusNoContent,
			Body:       io.NopCloser(strings.NewReader("")),
			Header:     make(http.Header),
		}, nil
	})}
	driver := &OpenCodeDriver{
		serverURL:     "http://opencode.test",
		password:      "pw",
		sessionID:     "sess-1",
		sessionStatus: "idle",
		httpClient:    client,
		workDir:       t.TempDir(),
	}

	done := make(chan error, 1)
	go func() {
		done <- driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: "agentteam-message-1"}, "hello"), "sess-1")
	}()
	<-entered
	driver.mu.Lock()
	require.True(t, driver.turnActive)
	require.Equal(t, "busy", driver.sessionStatus)
	require.NotNil(t, driver.promptAwaitingRunner)
	driver.mu.Unlock()

	// Positive Runner evidence may race ahead of the short HTTP response. It
	// clears only the admission reservation, not the active-turn state.
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))
	driver.mu.Lock()
	require.Nil(t, driver.promptAwaitingRunner)
	require.True(t, driver.turnActive)
	require.Equal(t, "busy", driver.sessionStatus)
	driver.mu.Unlock()
	close(release)
	require.NoError(t, <-done)
}

func TestOpenCodeAmbiguousPromptTransportRetainsExactCorrelation(t *testing.T) {
	requests := 0
	client := &http.Client{Transport: roundTripFunc(func(*http.Request) (*http.Response, error) {
		requests++
		return nil, io.ErrUnexpectedEOF
	})}
	driver := &OpenCodeDriver{
		serverURL:       "http://opencode.test",
		password:        "pw",
		sessionID:       "sess-1",
		sessionStatus:   "idle",
		httpClient:      client,
		workDir:         t.TempDir(),
		translatedLines: make(chan string, 1),
	}
	err := driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: "agentteam-message-1"}, "hello"), "sess-1")
	require.ErrorIs(t, err, errOpenCodePromptTransportAmbiguous)
	require.Equal(t, 1, requests)

	driver.mu.Lock()
	require.Len(t, driver.pendingRuntimeInputs, 1)
	require.NotNil(t, driver.promptAwaitingRunner)
	var messageID string
	for id := range driver.pendingRuntimeInputs {
		messageID = id
	}
	driver.mu.Unlock()

	// A second body cannot overtake the response-less first mutation.
	err = driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: "agentteam-message-2"}, "later"), "sess-1")
	require.ErrorIs(t, err, errRuntimeInputMutationNotAttempted)
	require.Equal(t, 1, requests)

	// If the original request did reach OpenCode, its exact assistant parent
	// settles the retained correlation without replaying the body.
	driver.handleSSEEvent(openCodeAssistantMessageUpdatedEvent(t, "sess-1", "msg-assistant-1", messageID))
	require.Equal(t, []drivercontract.ParsedEvent{{
		Kind:           consts.ParsedEventKindRuntimeInputAcked,
		RuntimeInputID: "agentteam-message-1",
		SessionID:      "sess-1",
	}}, driver.ParseLine(<-driver.translatedLines))
}

func TestOpenCodePromptAwaitingRunnerSuppressesEarlyIdleWithoutRuntimeInputID(t *testing.T) {
	var requests int
	var firstMessageID string
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests++
		require.Equal(t, "/session/sess-1/prompt_async", r.URL.Path)
		var body struct {
			MessageID string `json:"messageID"`
		}
		require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
		if requests == 1 {
			firstMessageID = body.MessageID
		}
		w.WriteHeader(http.StatusNoContent)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:       server.URL,
		password:        "pw",
		sessionID:       "sess-1",
		sessionStatus:   "idle",
		httpClient:      server.Client(),
		workDir:         t.TempDir(),
		translatedLines: make(chan string, 2),
	}
	require.NoError(t, driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{}, "first prompt without a AgentTeam runtime input id"), "sess-1"))
	require.Equal(t, 1, requests)
	requireOpenCodeRuntimeMessageID(t, firstMessageID)
	require.Nil(t, driver.pendingRuntimeInputs)
	require.NotNil(t, driver.promptAwaitingRunner)

	// OpenCode /abort publishes idle if the prompt_async fiber has not entered
	// Runner yet, but that fiber remains live. The early idle therefore cannot
	// end the turn or admit a second prompt, even without a correlated pending
	// RuntimeInput to serve as a guard.
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	select {
	case line := <-driver.translatedLines:
		t.Fatalf("pre-Runner idle emitted a terminal event: %s", line)
	default:
	}
	require.True(t, driver.turnActive)
	require.Equal(t, "busy", driver.sessionStatus)
	require.NotNil(t, driver.promptAwaitingRunner)

	err := driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: "agentteam-message-2"}, "must not overlap"), "sess-1")
	require.ErrorIs(t, err, errRuntimeInputMutationNotAttempted)
	require.Equal(t, 1, requests)

	// Positive Runner evidence clears the admission reservation. The assistant
	// then runs normally, and only its subsequent real idle ends the turn.
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))
	require.Nil(t, driver.promptAwaitingRunner)
	driver.handleSSEEvent(openCodeAssistantMessageUpdatedEvent(t, "sess-1", "msg-assistant-1", firstMessageID))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	require.Equal(t, []drivercontract.ParsedEvent{{
		Kind:      consts.ParsedEventKindTurnEnd,
		SessionID: "sess-1",
	}}, driver.ParseLine(<-driver.translatedLines))

	require.NoError(t, driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: "agentteam-message-2"}, "safe next prompt"), "sess-1"))
	require.Equal(t, 2, requests)
}

func TestOpenCodeRuntimeInputAckedDoesNotEmitOnUserPersistenceOnly(t *testing.T) {
	runtimeInputID := "agentteam-message-1"
	messageID := openCodeMessageIDFromParts(1_700_000_000_000, 1, "AAAAAAAAAAAAAA")
	driver := &OpenCodeDriver{
		sessionID:       "sess-1",
		translatedLines: make(chan string, 1),
	}
	driver.rememberPendingRuntimeInput(messageID, runtimeInputID, "sess-1")

	driver.handleSSEEvent(openCodeUserMessageUpdatedEvent(t, "sess-1", messageID))

	select {
	case line := <-driver.translatedLines:
		t.Fatalf("opencode user persistence ack acked runtime input: %s", line)
	default:
	}
}

func TestOpenCodeRuntimeInputAckedAcknowledgesOnlyCurrentReservationParent(t *testing.T) {
	runtimeInputID := "agentteam-message-current"
	messageID := openCodeMessageIDFromParts(1_700_000_000_000, 2, "BBBBBBBBBBBBBB")
	unrelatedMessageID := openCodeMessageIDFromParts(1_700_000_000_000, 1, "AAAAAAAAAAAAAA")
	driver := &OpenCodeDriver{
		sessionID:       "sess-1",
		translatedLines: make(chan string, 3),
	}
	driver.rememberPendingRuntimeInput(messageID, runtimeInputID, "sess-1")
	reservation, err := driver.reservePromptAwaitingRunnerWithCompaction("sess-1", messageID, nil)
	require.NoError(t, err)
	require.NotNil(t, reservation)
	driver.handleSSEEvent(openCodeUserMessageUpdatedEvent(t, "sess-1", messageID))

	driver.handleSSEEvent(openCodeAssistantMessageUpdatedEvent(t, "sess-1", "msg-assistant-unrelated", unrelatedMessageID))
	select {
	case line := <-driver.translatedLines:
		t.Fatalf("opencode acked an input for an unrelated assistant parent: %s", line)
	default:
	}
	require.Same(t, reservation, driver.promptAwaitingRunner)

	driver.handleSSEEvent(openCodeAssistantMessageUpdatedEvent(t, "sess-1", "msg-assistant-current", messageID))
	events := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, events, 1)
	require.Equal(t, consts.ParsedEventKindRuntimeInputAcked, events[0].Kind)
	require.Equal(t, "sess-1", events[0].SessionID)
	require.Equal(t, runtimeInputID, events[0].RuntimeInputID)
	require.Nil(t, driver.promptAwaitingRunner)
	require.Empty(t, driver.pendingRuntimeInputs)
}

func TestOpenCodeDeliverRuntimeInputWhileBusyDoesNotMutate(t *testing.T) {
	requests := 0
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests++
		http.Error(w, "must not be called", http.StatusInternalServerError)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:     server.URL,
		password:      "pw",
		sessionID:     "sess-1",
		sessionStatus: "busy",
		turnActive:    true,
		httpClient:    server.Client(),
		workDir:       t.TempDir(),
	}
	err := driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: "agentteam-message-1"}, "deliver while busy"), "sess-1")
	require.ErrorIs(t, err, errRuntimeInputMutationNotAttempted)
	require.Zero(t, requests)
	require.Nil(t, driver.pendingRuntimeInputs)
}

func TestOpenCodeDeliverRuntimeInputWhileSSEDisconnectedDoesNotMutate(t *testing.T) {
	requests := 0
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests++
		http.Error(w, "must not be called", http.StatusInternalServerError)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:          server.URL,
		password:           "pw",
		sessionID:          "sess-1",
		sessionStatus:      "idle",
		sseDeliveryBlocked: true,
		httpClient:         server.Client(),
		workDir:            t.TempDir(),
	}
	err := driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: "agentteam-message-1"}, "must wait for SSE acknowledgement stream"), "sess-1")
	require.ErrorIs(t, err, errRuntimeInputMutationNotAttempted)
	require.Contains(t, err.Error(), "connected SSE")
	require.Zero(t, requests)
	require.Nil(t, driver.pendingRuntimeInputs)
	require.Nil(t, driver.promptAwaitingRunner)
}

func TestOpenCodePromptAsyncPrimitiveRejectsWhileSSEDisconnected(t *testing.T) {
	requests := 0
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests++
		http.Error(w, "must not be called", http.StatusInternalServerError)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:          server.URL,
		password:           "pw",
		sessionID:          "sess-1",
		sessionStatus:      "idle",
		sseDeliveryBlocked: true,
		httpClient:         server.Client(),
		workDir:            t.TempDir(),
	}
	err := driver.sendPromptAsync(
		context.Background(),
		testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: "agentteam-message-1"}, "initial or resume prompt"),
		"",
		"",
	)
	require.ErrorIs(t, err, errRuntimeInputMutationNotAttempted)
	require.Contains(t, err.Error(), "connected SSE")
	require.Zero(t, requests)
	require.Nil(t, driver.promptAwaitingRunner)
}

func TestOpenCodeDriverSuppressesInitialAndRepeatedIdleStatus(t *testing.T) {
	driver := &OpenCodeDriver{
		sessionID:       "sess-1",
		translatedLines: make(chan string, 2),
	}

	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	select {
	case line := <-driver.translatedLines:
		t.Fatalf("initial idle emitted translated event: %s", line)
	default:
	}

	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	require.Equal(t, []drivercontract.ParsedEvent{{
		Kind:      consts.ParsedEventKindTurnEnd,
		SessionID: "sess-1",
	}}, driver.ParseLine(<-driver.translatedLines))

	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	select {
	case line := <-driver.translatedLines:
		t.Fatalf("repeated idle emitted translated event: %s", line)
	default:
	}
}

func TestOpenCodeDeliverRuntimeInputForgetsPendingOnDefinitivePromptError(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		http.Error(w, "prompt failed", http.StatusBadGateway)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:       server.URL,
		password:        "pw",
		sessionID:       "sess-1",
		sessionStatus:   "idle",
		httpClient:      server.Client(),
		workDir:         t.TempDir(),
		translatedLines: make(chan string, 1),
	}

	err := driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: "agentteam-message-1"}, "deliver after idle"), "sess-1")
	require.Error(t, err)
	require.ErrorIs(t, err, errRuntimeInputMutationNotAttempted)

	driver.mu.Lock()
	pendingLen := len(driver.pendingRuntimeInputs)
	awaitingRunner := driver.promptAwaitingRunner
	turnActive := driver.turnActive
	sessionStatus := driver.sessionStatus
	driver.mu.Unlock()
	require.Zero(t, pendingLen)
	require.Nil(t, awaitingRunner)
	require.False(t, turnActive)
	require.Equal(t, "idle", sessionStatus)
}

func TestOpenCodeRuntimeInputMessageIDIsOpenCodeCompatibleAndMonotonic(t *testing.T) {
	driver := &OpenCodeDriver{}

	first, err := driver.nextRuntimeInputMessageID()
	require.NoError(t, err)
	second, err := driver.nextRuntimeInputMessageID()
	require.NoError(t, err)

	requireOpenCodeRuntimeMessageID(t, first)
	requireOpenCodeRuntimeMessageID(t, second)
	require.Less(t, first, second)
	require.NotContains(t, first, "msg_agentteam_")
	require.NotContains(t, second, "msg_agentteam_")
}

func requireOpenCodeRuntimeMessageID(t *testing.T, id string) {
	t.Helper()

	require.True(t, strings.HasPrefix(id, "msg_"))
	require.Len(t, strings.TrimPrefix(id, "msg_"), 26)
	timeHex := strings.TrimPrefix(id, "msg_")[:12]
	for _, ch := range timeHex {
		require.Contains(t, "0123456789abcdef", string(ch))
	}
	suffix := strings.TrimPrefix(id, "msg_")[12:]
	for _, ch := range suffix {
		require.Contains(t, openCodeMessageIDBase62, string(ch))
	}
}

func TestBuildOpenCodeServeConfigContentNormalizesPrefixedModelForInjectedProvider(t *testing.T) {
	content, err := buildOpenCodeServeConfigContent(drivercontract.LaunchConfig{
		Model:      "anthropic/zhipu/glm-5-turbo",
		SmallModel: "anthropic/zhipu/glm-4.7",
		Provider: &drivercontract.ProviderConfig{
			ID:       "agentteam",
			Name:     "AgentTeam",
			BaseURL:  "https://proxy.example.com/v1",
			APIKey:   "sk-proxy",
			Headers:  map[string]string{"X-AgentTeam-Route-Workspace-Id": "wsp_1"},
			WireAPIs: []string{"openai"},
		},
	})
	require.NoError(t, err)

	var config openCodeServeConfig
	require.NoError(t, json.Unmarshal([]byte(content), &config))
	require.Equal(t, "agentteam/zhipu/glm-5-turbo", config.Model)
	require.Equal(t, "agentteam/zhipu/glm-4.7", config.SmallModel)
	require.Contains(t, config.Provider, "agentteam")
	require.Equal(t, map[string]string{"X-AgentTeam-Route-Workspace-Id": "wsp_1"}, config.Provider["agentteam"].Options.Headers)
	require.Contains(t, config.Provider["agentteam"].Models, "zhipu/glm-5-turbo")
	require.Contains(t, config.Provider["agentteam"].Models, "zhipu/glm-4.7")
	require.NotContains(t, config.Provider["agentteam"].Models, "anthropic/zhipu/glm-5-turbo")
}

func TestBuildOpenCodeServeConfigContentSelectsProviderNPMForWireAPI(t *testing.T) {
	testCases := []struct {
		name    string
		wireAPI string
		wantNPM string
	}{
		{name: "openai compatible", wireAPI: provider.WireAIOpenAI, wantNPM: "@ai-sdk/openai-compatible"},
		{name: "openai responses", wireAPI: provider.WireAPIResponses, wantNPM: "@ai-sdk/openai"},
		{name: "anthropic", wireAPI: provider.WireAPIAnthropic, wantNPM: "@ai-sdk/anthropic"},
	}

	for _, testCase := range testCases {
		t.Run(testCase.name, func(t *testing.T) {
			content, err := buildOpenCodeServeConfigContent(drivercontract.LaunchConfig{
				Model: "gpt-4o",
				Provider: &drivercontract.ProviderConfig{
					ID:       "agentteam",
					Name:     "AgentTeam",
					BaseURL:  "https://proxy.example.com/v1",
					APIKey:   "sk-proxy",
					WireAPIs: []string{testCase.wireAPI},
				},
			})
			require.NoError(t, err)

			var config openCodeServeConfig
			require.NoError(t, json.Unmarshal([]byte(content), &config))
			require.Contains(t, config.Provider, "agentteam")
			require.Equal(t, testCase.wantNPM, config.Provider["agentteam"].NPM)
		})
	}
}

func TestOpenCodeDeliverRuntimeInputUsesManifestOnlyForScreenshotAttachments(t *testing.T) {
	var body struct {
		Parts []map[string]any `json:"parts"`
	}
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, "/session/sess-1/prompt_async", r.URL.Path)
		require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
		w.WriteHeader(http.StatusNoContent)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:     server.URL,
		password:      "pw",
		sessionID:     "sess-1",
		sessionStatus: "idle",
		httpClient:    server.Client(),
		workDir:       t.TempDir(),
	}

	err := driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{Attachments: []types.RuntimeAttachment{
		{
			Filename: "screenshot.png",
			MIME:     "image/png",
			URI:      "file:///tmp/screenshot.png",
		},
	}}, "describe it\n\nAttached files have been prepared locally.\n\nFiles:\n1. screenshot.png\n   - path: /tmp/screenshot.png"), "sess-1")

	require.NoError(t, err)
	require.Len(t, body.Parts, 1)
	require.Equal(t, "text", body.Parts[0]["type"])
	require.Contains(t, body.Parts[0]["text"], "screenshot.png")
	require.Contains(t, body.Parts[0]["text"], "/tmp/screenshot.png")
}

func TestOpenCodeDeliverRuntimeInputSkipsPDFFileParts(t *testing.T) {
	var body struct {
		Parts []map[string]any `json:"parts"`
	}
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, "/session/sess-1/prompt_async", r.URL.Path)
		require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
		w.WriteHeader(http.StatusNoContent)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:     server.URL,
		password:      "pw",
		sessionID:     "sess-1",
		sessionStatus: "idle",
		httpClient:    server.Client(),
		workDir:       t.TempDir(),
	}

	err := driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{Attachments: []types.RuntimeAttachment{
		{
			Filename: "manual.pdf",
			MIME:     "application/pdf",
			URI:      "file:///tmp/manual.pdf",
		},
	}}, "please inspect\n\nAttached files have been prepared locally.\n\nFiles:\n1. manual.pdf\n   - path: /tmp/manual.pdf"), "sess-1")

	require.NoError(t, err)
	require.Len(t, body.Parts, 1)
	require.Equal(t, "text", body.Parts[0]["type"])
	require.Contains(t, body.Parts[0]["text"], "manual.pdf")
	require.Contains(t, body.Parts[0]["text"], "/tmp/manual.pdf")
}

func TestOpenCodeDeliverRuntimeInputRejectsAttachmentsWithoutManifestText(t *testing.T) {
	requests := 0
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests++
		w.WriteHeader(http.StatusNoContent)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:       server.URL,
		password:        "pw",
		sessionID:       "sess-1",
		sessionStatus:   "idle",
		httpClient:      server.Client(),
		workDir:         t.TempDir(),
		translatedLines: make(chan string, 1),
	}

	err := driver.DeliverRuntimeInput(context.Background(), drivercontract.RuntimeInput{
		ID: "agentteam-message-attachment-only",
		Attachments: []types.RuntimeAttachment{{
			Filename: "manual.pdf",
			MIME:     "application/pdf",
			URI:      "file:///tmp/manual.pdf",
		}},
	}, "sess-1")

	require.ErrorIs(t, err, errRuntimeInputMutationNotAttempted)
	require.Contains(t, err.Error(), "no supported parts")
	require.Zero(t, requests)
	require.Empty(t, driver.pendingRuntimeInputs)
	require.Nil(t, driver.promptAwaitingRunner)
	require.False(t, driver.turnActive)
	require.Equal(t, "idle", driver.sessionStatus)
}

func TestOpenCodePrepareSpawnRecoveryPointUsesBoundMessageID(t *testing.T) {
	driver := &OpenCodeDriver{sessionID: "sess-initial"}

	point := prepareSpawnRecoveryPoint(driver, "msg-initial", "sess-initial")

	require.NotNil(t, point)
	require.Equal(t, "msg-initial", point.MessageID)
	require.Equal(t, "sess-initial", point.ThreadID)
	require.Equal(t, rewindcaps.StrategyOpenCodeSessionRevert, point.Strategy)

	ref, err := rewindcaps.DecodeOpenCodeSessionRevertRecoveryRef(point.RecoveryRef)
	require.NoError(t, err)
	require.Equal(t, "sess-initial", ref.SessionID)
	require.True(t, strings.HasPrefix(ref.MessageID, "msg_"))
}

func TestOpenCodeRecoveryMessageIDBindingIsStable(t *testing.T) {
	driver := &OpenCodeDriver{sessionID: "sess-initial"}
	input := testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: "msg-initial"}, "hello")
	messageID, err := driver.recoveryMessageIDForRuntimeInputID(input.ID)
	require.NoError(t, err)
	require.True(t, strings.HasPrefix(messageID, "msg_"))

	second, err := driver.recoveryMessageIDForRuntimeInputID(input.ID)
	require.NoError(t, err)
	require.Equal(t, messageID, second)
}

func TestOpenCodeRestoreRecoveryPointRejectsWrongStrategy(t *testing.T) {
	ref, err := rewindcaps.EncodeOpenCodeSessionRevertRecoveryRef("sess-initial", "msg_1")
	require.NoError(t, err)

	err = (&OpenCodeDriver{}).RestoreRecoveryPoint(context.Background(), "unknown_strategy", ref)

	require.Error(t, err)
	require.Contains(t, err.Error(), "unsupported recovery strategy")
}

func TestOpenCodeInterruptActiveRunCallsAbort(t *testing.T) {
	var gotPath string
	abortReceived := make(chan struct{})
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		gotPath = r.URL.Path
		require.Equal(t, http.MethodPost, r.Method)
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`true`))
		close(abortReceived)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:       server.URL,
		password:        "pw",
		httpClient:      server.Client(),
		sessionID:       "sess-initial",
		workDir:         t.TempDir(),
		translatedLines: make(chan string, 1),
		turnActive:      true,
		sessionStatus:   "busy",
	}

	interruptDone := make(chan error, 1)
	go func() {
		interruptDone <- driver.InterruptActiveRun(context.Background(), "sess-initial")
	}()
	select {
	case <-abortReceived:
	case <-time.After(time.Second):
		t.Fatal("abort request was not received")
	}
	require.Equal(t, "/session/sess-initial/abort", gotPath)
	require.True(t, driver.turnActive)
	require.Equal(t, "busy", driver.sessionStatus)

	// The real session.status idle is consumed by the interrupt fence before
	// InterruptActiveRun returns, so it cannot arrive after a subsequent live
	// restore as a stale ordinary TurnEnd.
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-initial", "idle"))
	require.NoError(t, <-interruptDone)
	require.False(t, driver.turnActive)
	require.Equal(t, "idle", driver.sessionStatus)
	select {
	case line := <-driver.translatedLines:
		t.Fatalf("interrupt fence leaked a stale turn boundary: %s", line)
	default:
	}
}

func TestOpenCodeInterruptTreatsFenceConsumedIdleAsSuccessWhenContextCancels(t *testing.T) {
	requestReceived := make(chan struct{})
	releaseResponse := make(chan struct{})
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		close(requestReceived)
		<-releaseResponse
		_, _ = w.Write([]byte(`true`))
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:       server.URL,
		password:        "pw",
		httpClient:      server.Client(),
		sessionID:       "sess-initial",
		workDir:         t.TempDir(),
		translatedLines: make(chan string, 1),
		turnActive:      true,
		sessionStatus:   "busy",
	}
	ctx, cancel := context.WithCancel(context.Background())
	interruptDone := make(chan error, 1)
	go func() {
		interruptDone <- driver.InterruptActiveRun(ctx, "sess-initial")
	}()
	select {
	case <-requestReceived:
	case <-time.After(time.Second):
		t.Fatal("abort request was not received")
	}

	// Make both the consumed idle fence and the caller cancellation observable
	// before the HTTP request can complete. Whichever branch wins must preserve
	// the authoritative idle as a successful interrupt.
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-initial", "idle"))
	cancel()
	close(releaseResponse)
	require.NoError(t, <-interruptDone)
	require.False(t, driver.turnActive)
	require.Equal(t, "idle", driver.sessionStatus)
}

func TestOpenCodeInterruptClearsAutoCompactionBindingBeforeFreshPrompt(t *testing.T) {
	abortReceived := make(chan struct{})
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		switch {
		case strings.HasSuffix(r.URL.Path, "/abort"):
			close(abortReceived)
			_, _ = w.Write([]byte(`true`))
		case strings.HasSuffix(r.URL.Path, "/prompt_async"):
			w.WriteHeader(http.StatusNoContent)
		default:
			http.NotFound(w, r)
		}
	}))
	defer server.Close()

	retired := make(chan struct{})
	driver := &OpenCodeDriver{
		serverURL:                        server.URL,
		password:                         "pw",
		httpClient:                       server.Client(),
		sessionID:                        "sess-initial",
		workDir:                          t.TempDir(),
		translatedLines:                  make(chan string, 8),
		turnActive:                       true,
		sessionStatus:                    "busy",
		autoCompactionUserMessageID:      "msg-old-compaction",
		autoCompactionAssistantMessageID: "msg-old-summary",
		runnerUserMessageIDs:             map[string]struct{}{"msg-old-compaction": {}},
		runnerAssistantMessageIDs:        map[string]struct{}{"msg-old-summary": {}},
		runnerSummaryAssistantMessageIDs: map[string]struct{}{"msg-old-summary": {}},
		promptRunnerAdmissionTimeout:     time.Second,
		serverCancel:                     sync.OnceFunc(func() { close(retired) }),
	}

	interruptDone := make(chan error, 1)
	go func() {
		interruptDone <- driver.InterruptActiveRun(context.Background(), "sess-initial")
	}()
	select {
	case <-abortReceived:
	case <-time.After(time.Second):
		t.Fatal("abort request was not received")
	}
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-initial", "idle"))
	require.NoError(t, <-interruptDone)
	require.Empty(t, driver.autoCompactionUserMessageID)
	require.Empty(t, driver.autoCompactionAssistantMessageID)

	// A fresh uncorrelated prompt's ordinary provider error must remain an
	// ordinary fatal error. A stale auto-summary binding would instead make the
	// first idle enter context double-idle drain and reopen delivery too early.
	require.NoError(t, driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{}, "fresh prompt after interrupt"), "sess-initial"))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-initial", "busy"))
	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-initial","error":{"name":"ProviderError","message":"fresh provider failure"}}}`)
	diagnostic := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, diagnostic, 1)
	require.True(t, parsedBoolMetadata(diagnostic[0].Metadata, runtimeErrorNonterminalMetadataKey))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-initial", "idle"))
	select {
	case <-retired:
	case <-time.After(time.Second):
		t.Fatal("fresh provider error inherited stale auto-compaction lifecycle")
	}
	terminal := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, terminal, 1)
	require.Equal(t, consts.ParsedEventKindError, terminal[0].Kind)
	require.False(t, driver.contextTerminalDrain)
}

func TestOpenCodeInterruptDrainsCleanupAcrossHTTPRaceAndRestore(t *testing.T) {
	for _, cleanupBeforeResponse := range []bool{true, false} {
		name := "cleanup_after_http_response"
		if cleanupBeforeResponse {
			name = "cleanup_before_http_response"
		}
		t.Run(name, func(t *testing.T) {
			abortReceived := make(chan struct{})
			releaseAbort := make(chan struct{})
			if !cleanupBeforeResponse {
				close(releaseAbort)
			}
			promptMessageIDs := make(chan string, 1)
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				switch {
				case strings.HasSuffix(r.URL.Path, "/abort"):
					close(abortReceived)
					<-releaseAbort
					_, _ = w.Write([]byte(`true`))
				case strings.HasSuffix(r.URL.Path, "/revert"):
					_, _ = w.Write([]byte(`{"id":"sess-initial"}`))
				case strings.HasSuffix(r.URL.Path, "/prompt_async"):
					var body struct {
						MessageID string `json:"messageID"`
					}
					require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
					promptMessageIDs <- body.MessageID
					w.WriteHeader(http.StatusNoContent)
				default:
					http.NotFound(w, r)
				}
			}))
			defer server.Close()

			driver := &OpenCodeDriver{
				serverURL:       server.URL,
				password:        "pw",
				httpClient:      server.Client(),
				sessionID:       "sess-initial",
				workDir:         t.TempDir(),
				translatedLines: make(chan string, 16),
				turnActive:      true,
				sessionStatus:   "busy",
				countedStepFinish: map[string]struct{}{
					"old-step": {},
				},
			}
			driver.textBuf.WriteString("OLD")

			interruptDone := make(chan error, 1)
			go func() {
				interruptDone <- driver.InterruptActiveRun(context.Background(), "sess-initial")
			}()
			select {
			case <-abortReceived:
			case <-time.After(time.Second):
				t.Fatal("abort request was not received")
			}

			// This is the exact OpenCode 1.17.11 abort sequence. All cleanup and
			// both idles can cross the independent HTTP/SSE transport boundary.
			driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-initial","error":{"name":"MessageAbortedError","data":{"message":"aborted"}}}}`)
			driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-initial", "idle"))
			if cleanupBeforeResponse {
				driver.handleSSEEvent(`{"type":"message.part.delta","properties":{"sessionID":"sess-initial","messageID":"msg-old-assistant","partID":"part-old","field":"text","delta":"STALE"}}`)
				driver.handleSSEEvent(`{"type":"message.updated","properties":{"sessionID":"sess-initial","info":{"id":"msg-old-assistant","parentID":"msg-old-user","sessionID":"sess-initial","role":"assistant","error":{"name":"MessageAbortedError"},"time":{"completed":100}}}}`)
				driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-initial", "idle"))
				close(releaseAbort)
			}
			require.NoError(t, <-interruptDone)
			require.True(t, driver.interruptCleanupDrain)

			ref, err := rewindcaps.EncodeOpenCodeSessionRevertRecoveryRef("sess-initial", "msg-revert-target")
			require.NoError(t, err)
			require.NoError(t, driver.RestoreRecoveryPoint(context.Background(), rewindcaps.StrategyOpenCodeSessionRevert, ref))
			require.True(t, driver.interruptCleanupDrain, "live revert must preserve the abort cleanup drain")
			require.Empty(t, driver.countedStepFinish)

			if !cleanupBeforeResponse {
				// These frames were already published server-side before abort
				// HTTP 200, but remained in the SSE socket backlog until after
				// AgentTeam completed its live revert.
				driver.handleSSEEvent(`{"type":"message.part.delta","properties":{"sessionID":"sess-initial","messageID":"msg-old-assistant","partID":"part-old","field":"text","delta":"STALE"}}`)
				driver.handleSSEEvent(`{"type":"message.updated","properties":{"sessionID":"sess-initial","info":{"id":"msg-old-assistant","parentID":"msg-old-user","sessionID":"sess-initial","role":"assistant","error":{"name":"MessageAbortedError"},"time":{"completed":100}}}}`)
				driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-initial", "idle"))
			}
			require.Empty(t, driver.translatedLines, "aborted cleanup must not leak Error/Text/TurnEnd")

			require.NoError(t, driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: "agentteam-new-input"}, "new request"), "sess-initial"))
			var promptMessageID string
			select {
			case promptMessageID = <-promptMessageIDs:
			case <-time.After(time.Second):
				t.Fatal("new prompt was not submitted")
			}
			driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-initial", "busy"))
			require.False(t, driver.interruptCleanupDrain, "fresh Runner busy opens the new event generation")
			driver.handleSSEEvent(openCodeAssistantMessageUpdatedEvent(t, "sess-initial", "msg-new-assistant", promptMessageID))
			driver.handleSSEEvent(`{"type":"message.part.delta","properties":{"sessionID":"sess-initial","messageID":"msg-new-assistant","partID":"part-new","field":"text","delta":"NEW"}}`)
			driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-initial", "idle"))

			var events []drivercontract.ParsedEvent
			for range 3 {
				events = append(events, driver.ParseLine(<-driver.translatedLines)...)
			}
			require.Equal(t, []drivercontract.ParsedEvent{
				{
					Kind:           consts.ParsedEventKindRuntimeInputAcked,
					SessionID:      "sess-initial",
					RuntimeInputID: "agentteam-new-input",
				},
				{Kind: consts.ParsedEventKindText, Text: "NEW"},
				{Kind: consts.ParsedEventKindTurnEnd, SessionID: "sess-initial"},
			}, events)
		})
	}
}

func TestOpenCodeInterruptActiveRunRejectsBeforeRunnerAdmission(t *testing.T) {
	requests := 0
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests++
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:     server.URL,
		password:      "pw",
		httpClient:    server.Client(),
		sessionID:     "sess-initial",
		workDir:       t.TempDir(),
		turnActive:    true,
		sessionStatus: "busy",
		promptAwaitingRunner: &openCodePromptRunnerReservation{
			SessionID: "sess-initial",
			MessageID: "msg-prompt",
		},
	}

	err := driver.InterruptActiveRun(context.Background(), "sess-initial")
	require.Error(t, err)
	require.Contains(t, err.Error(), "before Runner admission")
	require.Zero(t, requests)
	require.NotNil(t, driver.promptAwaitingRunner)
	require.True(t, driver.turnActive)
	require.Equal(t, "busy", driver.sessionStatus)
}

func TestOpenCodeInterruptActiveRunRejectsWhileSSEDisconnected(t *testing.T) {
	requests := 0
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests++
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:          server.URL,
		password:           "pw",
		httpClient:         server.Client(),
		sessionID:          "sess-initial",
		workDir:            t.TempDir(),
		turnActive:         true,
		sessionStatus:      "busy",
		sseDeliveryBlocked: true,
	}

	err := driver.InterruptActiveRun(context.Background(), "sess-initial")
	require.ErrorIs(t, err, ErrAgentBusy)
	require.Contains(t, err.Error(), "connected SSE")
	require.Zero(t, requests)
	require.True(t, driver.turnActive)
	require.Equal(t, "busy", driver.sessionStatus)
}

func TestOpenCodeInterruptActiveRunRejectsPendingAssistantReceipt(t *testing.T) {
	requests := 0
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests++
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:     server.URL,
		password:      "pw",
		httpClient:    server.Client(),
		sessionID:     "sess-initial",
		workDir:       t.TempDir(),
		turnActive:    true,
		sessionStatus: "busy",
		pendingRuntimeInputs: map[string]openCodePendingRuntimeInput{
			"msg-prompt": {
				RuntimeInputID: "agentteam-message-1",
				SessionID:      "sess-initial",
			},
		},
	}

	err := driver.InterruptActiveRun(context.Background(), "sess-initial")
	require.Error(t, err)
	require.Contains(t, err.Error(), "await assistant receipt")
	require.Zero(t, requests)
	require.Len(t, driver.pendingRuntimeInputs, 1)
	require.True(t, driver.turnActive)
	require.Equal(t, "busy", driver.sessionStatus)
}

func TestOpenCodeRestoreRecoveryPointCallsRevert(t *testing.T) {
	var got struct {
		Path string
		Body struct {
			MessageID string `json:"messageID"`
		}
	}
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		got.Path = r.URL.Path
		require.Equal(t, http.MethodPost, r.Method)
		require.NoError(t, json.NewDecoder(r.Body).Decode(&got.Body))
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"id":"sess-initial"}`))
	}))
	defer server.Close()

	ref, err := rewindcaps.EncodeOpenCodeSessionRevertRecoveryRef("sess-initial", "msg_1")
	require.NoError(t, err)
	driver := &OpenCodeDriver{
		serverURL:  server.URL,
		password:   "pw",
		httpClient: server.Client(),
		sessionID:  "sess-initial",
	}

	err = driver.RestoreRecoveryPoint(context.Background(), rewindcaps.StrategyOpenCodeSessionRevert, ref)

	require.NoError(t, err)
	require.Equal(t, "/session/sess-initial/revert", got.Path)
	require.Equal(t, "msg_1", got.Body.MessageID)
}

func TestOpenCodeRestoreRecoveryPointRejectsInFlightSummarize(t *testing.T) {
	requests := 0
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		requests++
		w.WriteHeader(http.StatusOK)
	}))
	defer server.Close()

	ref, err := rewindcaps.EncodeOpenCodeSessionRevertRecoveryRef("sess-initial", "msg_1")
	require.NoError(t, err)
	driver := &OpenCodeDriver{
		serverURL:           server.URL,
		password:            "pw",
		httpClient:          server.Client(),
		sessionID:           "sess-initial",
		summarizeInProgress: true,
	}

	err = driver.RestoreRecoveryPoint(context.Background(), rewindcaps.StrategyOpenCodeSessionRevert, ref)
	require.ErrorIs(t, err, ErrAgentBusy)
	require.Contains(t, err.Error(), "summarize")
	require.Zero(t, requests)
}

func TestOpenCodeDetachedRecoveryBatchReusesOneServer(t *testing.T) {
	if runtime.GOOS == "windows" {
		t.Skip("test helper uses a POSIX launcher")
	}
	if _, err := exec.LookPath("python3"); err != nil {
		t.Skip("python3 is required for the detached server fixture")
	}

	recordPath := filepath.Join(t.TempDir(), "requests.log")
	binaryPath := filepath.Join(t.TempDir(), "opencode")
	script := `#!/bin/sh
port=""
while [ "$#" -gt 0 ]; do
  if [ "$1" = "--port" ]; then
    port="$2"
    shift 2
    continue
  fi
  shift
done
exec python3 - "$port" "$OPENCODE_TEST_RECORD" <<'PY'
import http.server
import sys

port = int(sys.argv[1])
record = sys.argv[2]
with open(record, "a", encoding="utf-8") as f:
    f.write("started\n")
    f.flush()

class Handler(http.server.BaseHTTPRequestHandler):
    def do_GET(self):
        if self.path == "/global/health":
            self.send_response(200)
            self.end_headers()
            self.wfile.write(b'{"healthy":true}')
            return
        self.send_response(404)
        self.end_headers()

    def do_POST(self):
        size = int(self.headers.get("Content-Length", "0"))
        self.rfile.read(size)
        with open(record, "a", encoding="utf-8") as f:
            f.write(self.path + "\n")
        self.send_response(200)
        self.end_headers()
        self.wfile.write(b'{}')

    def log_message(self, format, *args):
        return

http.server.ThreadingHTTPServer(("127.0.0.1", port), Handler).serve_forever()
PY
`
	require.NoError(t, os.WriteFile(binaryPath, []byte(script), 0o755))

	ref1, err := rewindcaps.EncodeOpenCodeSessionRevertRecoveryRef("sess-1", "msg_1")
	require.NoError(t, err)
	ref2, err := rewindcaps.EncodeOpenCodeSessionRevertRecoveryRef("sess-2", "msg_2")
	require.NoError(t, err)

	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
	defer cancel()
	driver := &OpenCodeDriver{}
	err = driver.restoreRecoveryPointsDetached(ctx, drivercontract.LaunchConfig{
		AgentID:     "agt-batch",
		RuntimeType: runtimeconfig.TypeOpenCode,
		Binary:      binaryPath,
		WorkDir:     t.TempDir(),
		Env: map[string]string{
			"OPENCODE_TEST_RECORD": recordPath,
		},
	}, rewindcaps.StrategyOpenCodeSessionRevert, []string{ref1, ref2, ref1})
	require.NoError(t, err)

	driver.mu.Lock()
	serverURL := driver.serverURL
	password := driver.password
	serverCancel := driver.serverCancel
	driver.mu.Unlock()
	require.Empty(t, serverURL)
	require.Empty(t, password)
	require.Nil(t, serverCancel)

	data, err := os.ReadFile(recordPath)
	require.NoError(t, err)
	lines := strings.Fields(string(data))
	require.Equal(t, []string{
		"started",
		"/session/sess-1/revert",
		"/session/sess-2/revert",
	}, lines)
}

func TestOpenCodeDriverParseTranslatedLines(t *testing.T) {
	t.Parallel()

	driver := &OpenCodeDriver{}
	tests := []struct {
		name string
		line string
		want []drivercontract.ParsedEvent
	}{
		{
			name: "ignores non json",
			line: "debug log",
		},
		{
			name: "ignores invalid json",
			line: "{",
		},
		{
			name: "ignores empty session init",
			line: `{"kind":"session_init","sessionId":" "}`,
		},
		{
			name: "session init",
			line: `{"kind":"session_init","sessionId":"sess-1"}`,
			want: []drivercontract.ParsedEvent{{
				Kind:      consts.ParsedEventKindSessionInit,
				SessionID: "sess-1",
			}},
		},
		{
			name: "thinking",
			line: `{"kind":"thinking","text":"planning","sessionId":"sess-1"}`,
			want: []drivercontract.ParsedEvent{{
				Kind: consts.ParsedEventKindThinking,
				Text: "planning",
			}},
		},
		{
			name: "ignores empty thinking",
			line: `{"kind":"thinking","text":" "}`,
		},
		{
			name: "text",
			line: `{"kind":"text","text":"hello","sessionId":"sess-1"}`,
			want: []drivercontract.ParsedEvent{{
				Kind: consts.ParsedEventKindText,
				Text: "hello",
			}},
		},
		{
			name: "tool call with string input",
			line: `{"kind":"tool_call","toolName":"mcp_agentteam_message_send","toolInput":"{\"target\":\"#agentteam\"}","sessionId":"sess-1"}`,
			want: []drivercontract.ParsedEvent{{
				Kind:      consts.ParsedEventKindToolCall,
				ToolName:  "mcp_agentteam_message_send",
				ToolInput: `{"target":"#agentteam"}`,
			}},
		},
		{
			name: "tool call with object input",
			line: `{"kind":"tool_call","toolName":"read_file","toolInput":{"path":"README.md"}}`,
			want: []drivercontract.ParsedEvent{{
				Kind:      consts.ParsedEventKindToolCall,
				ToolName:  "read_file",
				ToolInput: `{"path":"README.md"}`,
			}},
		},
		{
			name: "tool call defaults empty input",
			line: `{"kind":"tool_call","toolName":"list_files"}`,
			want: []drivercontract.ParsedEvent{{
				Kind:      consts.ParsedEventKindToolCall,
				ToolName:  "list_files",
				ToolInput: `{}`,
			}},
		},
		{
			name: "turn end",
			line: `{"kind":"turn_end","sessionId":"sess-1"}`,
			want: []drivercontract.ParsedEvent{{
				Kind:      consts.ParsedEventKindTurnEnd,
				SessionID: "sess-1",
			}},
		},
		{
			name: "runtime input acked",
			line: `{"kind":"runtime_input_surfaced","runtimeInputId":"msg-agentteam-1","sessionId":"sess-1"}`,
			want: []drivercontract.ParsedEvent{{
				Kind:           consts.ParsedEventKindRuntimeInputAcked,
				RuntimeInputID: "msg-agentteam-1",
				SessionID:      "sess-1",
			}},
		},
		{
			name: "usage",
			line: `{"kind":"usage","model":"gpt-5","inputTokens":1000,"cachedInputTokens":400,"outputTokens":300}`,
			want: []drivercontract.ParsedEvent{{
				Kind:              consts.ParsedEventKindUsage,
				Model:             "gpt-5",
				InputTokens:       1000,
				CachedInputTokens: 400,
				OutputTokens:      300,
			}},
		},
		{
			name: "error",
			line: `{"kind":"error","message":"boom","sessionId":"sess-1"}`,
			want: []drivercontract.ParsedEvent{{
				Kind:    consts.ParsedEventKindError,
				Message: "boom",
			}},
		},
		{
			name: "ignores empty error",
			line: `{"kind":"error","message":" "}`,
		},
	}

	for _, tc := range tests {
		tc := tc
		t.Run(tc.name, func(t *testing.T) {
			t.Parallel()
			require.Equal(t, tc.want, driver.ParseLine(tc.line))
		})
	}
}

func TestOpenCodeDriverCoalescesToolPartLifecycleByCallID(t *testing.T) {
	driver := &OpenCodeDriver{
		sessionID:                 "sess-1",
		translatedLines:           make(chan string, 4),
		sessionStatus:             "busy",
		turnActive:                true,
		runnerAssistantMessageIDs: map[string]struct{}{"msg-assistant-current": {}},
	}

	for _, state := range []string{
		`"status":"pending","input":{"query":"agentteam"}`,
		`"status":"running","input":{"query":"agentteam"}`,
		`"status":"completed","input":{"query":"agentteam"},"output":{"hits":[],"total":0}`,
	} {
		driver.handleSSEEvent(fmt.Sprintf(
			`{"type":"message.part.updated","properties":{"sessionID":"sess-1","part":{"id":"part-1","messageID":"msg-assistant-current","type":"tool","tool":"mcp__agentteam__memory_search","callID":"call-1","state":{%s}}}}`,
			state,
		))
	}

	var got []drivercontract.ParsedEvent
	for range 3 {
		got = append(got, driver.ParseLine(<-driver.translatedLines)...)
	}
	require.Equal(t, []drivercontract.ParsedEvent{
		{
			Kind:       consts.ParsedEventKindToolCall,
			ToolName:   "mcp__agentteam__memory_search",
			ToolCallID: "call-1",
			ToolInput:  `{"query":"agentteam"}`,
		},
		{
			Kind:       consts.ParsedEventKindToolCall,
			ToolName:   "mcp__agentteam__memory_search",
			ToolCallID: "call-1",
			ToolInput:  `{"query":"agentteam"}`,
		},
		{
			Kind:       consts.ParsedEventKindToolOutput,
			ToolName:   "mcp__agentteam__memory_search",
			ToolCallID: "call-1",
			Text:       `{"hits":[],"total":0}`,
		},
	}, got)
}

func TestOpenCodeDriverPreservesToolFailureDetail(t *testing.T) {
	driver := &OpenCodeDriver{
		sessionID:                 "sess-1",
		translatedLines:           make(chan string, 1),
		sessionStatus:             "busy",
		turnActive:                true,
		runnerAssistantMessageIDs: map[string]struct{}{"msg-assistant-current": {}},
	}

	driver.handleSSEEvent(`{"type":"message.part.updated","properties":{"sessionID":"sess-1","part":{"id":"part-1","messageID":"msg-assistant-current","type":"tool","tool":"mcp__agentteam__memory_search","callID":"call-1","state":{"status":"error","input":{"path":"missing.md"},"error":"upstream failed"}}}}`)

	require.Equal(t, []drivercontract.ParsedEvent{{
		Kind:       consts.ParsedEventKindToolOutput,
		Text:       "upstream failed",
		ToolName:   "mcp__agentteam__memory_search",
		ToolCallID: "call-1",
		Metadata:   map[string]any{"toolError": true},
	}}, driver.ParseLine(<-driver.translatedLines))
}

func TestOpenCodeDriverSSESessionErrorIsDiagnosticUntilIdle(t *testing.T) {
	driver := &OpenCodeDriver{
		sessionID:       "sess-1",
		translatedLines: make(chan string, 4),
		sessionStatus:   "busy",
		turnActive:      true,
	}

	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"UnknownError","message":"failed to process PDF"}}}`)
	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"UnknownError","message":"failed again"}}}`)

	require.Len(t, driver.translatedLines, 2)
	var got []drivercontract.ParsedEvent
	for range 2 {
		got = append(got, driver.ParseLine(<-driver.translatedLines)...)
	}
	require.Equal(t, []drivercontract.ParsedEvent{
		{
			Kind:     consts.ParsedEventKindError,
			Message:  "failed to process PDF",
			Metadata: map[string]any{runtimeErrorNonterminalMetadataKey: true},
		},
		{
			Kind:     consts.ParsedEventKindError,
			Message:  "failed again",
			Metadata: map[string]any{runtimeErrorNonterminalMetadataKey: true},
		},
	}, got)
	require.True(t, driver.turnActive)
	require.Equal(t, "busy", driver.sessionStatus)
	err := driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: "agentteam-message-2"}, "must wait for the authoritative idle"), "sess-1")
	require.ErrorIs(t, err, errRuntimeInputMutationNotAttempted)

	// Positive Runner activity after the preprocessing diagnostics proves that
	// OpenCode recovered and continued the same prompt.
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	require.Equal(t, []drivercontract.ParsedEvent{{
		Kind:      consts.ParsedEventKindTurnEnd,
		SessionID: "sess-1",
	}}, driver.ParseLine(<-driver.translatedLines))
	require.False(t, driver.turnActive)
	require.Equal(t, "idle", driver.sessionStatus)
}

func TestOpenCodeDriverSSEFatalSessionErrorRetiresAtIdle(t *testing.T) {
	retired := make(chan struct{})
	driver := &OpenCodeDriver{
		sessionID:       "sess-1",
		translatedLines: make(chan string, 4),
		sessionStatus:   "busy",
		turnActive:      true,
		serverCancel:    sync.OnceFunc(func() { close(retired) }),
	}

	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"ProviderError","message":"fatal provider rejection"}}}`)
	diagnostic := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, diagnostic, 1)
	require.Equal(t, consts.ParsedEventKindError, diagnostic[0].Kind)
	require.Equal(t, true, diagnostic[0].Metadata[runtimeErrorNonterminalMetadataKey])

	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	select {
	case <-retired:
	case <-time.After(time.Second):
		t.Fatal("fatal session error did not retire the runtime generation")
	}
	terminal := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, terminal, 1)
	require.Equal(t, consts.ParsedEventKindError, terminal[0].Kind)
	require.Equal(t, "fatal provider rejection", terminal[0].Message)
	require.False(t, parsedBoolMetadata(terminal[0].Metadata, runtimeErrorNonterminalMetadataKey))
	require.True(t, driver.generationRetiring)
	require.True(t, driver.sseDeliveryBlocked)
	require.Equal(t, "error", driver.sessionStatus)
	require.True(t, driver.turnActive)

	// prompt_async's outer catchCause may publish a wrapping error after
	// Runner.finishRun has already published the idle above. The generation is
	// already retired, so this and any late status must be ignored.
	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"UnknownError","message":"wrapped fatal provider rejection"}}}`)
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	driver.mu.Lock()
	require.Equal(t, "error", driver.sessionStatus)
	require.True(t, driver.turnActive)
	driver.mu.Unlock()
	select {
	case line := <-driver.translatedLines:
		t.Fatalf("retiring generation leaked a late SSE event or turn boundary: %s", line)
	default:
	}
}

func TestOpenCodeContextWindowErrorEndsTurnForNativeSummarize(t *testing.T) {
	driver := &OpenCodeDriver{
		sessionID:             "sess-1",
		activePromptMessageID: "msg-user-current",
		translatedLines:       make(chan string, 4),
		sessionStatus:         "busy",
		turnActive:            true,
	}

	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"ContextOverflowError","data":{"message":"request entity too large"}}}}`)
	diagnostic := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, diagnostic, 1)
	require.True(t, parsedBoolMetadata(diagnostic[0].Metadata, runtimeErrorNonterminalMetadataKey))
	require.True(t, driver.IsContextWindowExceeded(diagnostic[0]), "structured error.name must identify context overflow")

	// SessionProcessor publishes an early idle before it persists the failed
	// assistant cleanup. The Runner is still Running, so no terminal boundary
	// or summarize eligibility may be exposed here.
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	select {
	case line := <-driver.translatedLines:
		t.Fatalf("processor idle leaked a terminal boundary: %s", line)
	default:
	}
	require.True(t, driver.contextTerminalDrain)
	require.True(t, driver.turnActive)
	require.Equal(t, "busy", driver.sessionStatus)

	driver.handleSSEEvent(`{"type":"message.updated","properties":{"sessionID":"sess-1","info":{"id":"msg-assistant-current","parentID":"msg-user-current","sessionID":"sess-1","role":"assistant","finish":"error","error":{"name":"ContextOverflowError","data":{"message":"request entity too large"}},"time":{"completed":1234}}}}`)
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	terminal := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, terminal, 1)
	require.Equal(t, consts.ParsedEventKindError, terminal[0].Kind)
	require.True(t, driver.IsContextWindowExceeded(terminal[0]))
	require.Equal(t, consts.ParsedEventKindTurnEnd, driver.ParseLine(<-driver.translatedLines)[0].Kind)
	require.False(t, driver.generationRetiring)
	require.False(t, driver.sseDeliveryBlocked)
	require.Equal(t, "idle", driver.sessionStatus)
	require.False(t, driver.turnActive)
	require.False(t, driver.contextTerminalDrain)
}

func TestOpenCodeContextTerminalDrainsPostIdleCleanupBeforeSummarize(t *testing.T) {
	requestStarted := make(chan struct{}, 1)
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, "/session/sess-1/summarize", r.URL.Path)
		requestStarted <- struct{}{}
		_, _ = w.Write([]byte("true"))
	}))
	t.Cleanup(server.Close)

	driver := &OpenCodeDriver{
		serverURL:             server.URL,
		password:              "pw",
		httpClient:            server.Client(),
		sessionID:             "sess-1",
		activePromptMessageID: "msg-user-current",
		sessionStatus:         "busy",
		turnActive:            true,
		translatedLines:       make(chan string, 16),
	}

	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"ContextOverflowError","message":"Input exceeds context window of this model"}}}`)
	diagnostic := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, diagnostic, 1)
	require.True(t, parsedBoolMetadata(diagnostic[0].Metadata, runtimeErrorNonterminalMetadataKey))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	select {
	case line := <-driver.translatedLines:
		t.Fatalf("first processor idle leaked a terminal boundary: %s", line)
	default:
	}
	require.True(t, driver.contextTerminalDrain)
	require.True(t, driver.turnActive)
	require.Equal(t, "busy", driver.sessionStatus)
	started, err := driver.StartIdleCompaction(context.Background(), "sess-1")
	require.NoError(t, err)
	require.False(t, started, "summarize must not enter an already-running Runner after the first idle")

	// SessionProcessor.halt publishes cleanup after its first idle and
	// Runner.finishRun then publishes another idle. Part cleanup is suppressed,
	// while the exact completed assistant update advances the lifecycle fence.
	driver.handleSSEEvent(`{"type":"message.part.updated","properties":{"sessionID":"sess-1","part":{"id":"part-cleanup","type":"tool","tool":"cleanup","callID":"call-cleanup","state":{"status":"error","input":{},"error":"overflow"}}}}`)
	driver.handleSSEEvent(`{"type":"message.part.delta","properties":{"sessionID":"sess-1","messageID":"msg_100000000002BBBBBBBBBBBBBB","partID":"part-text","field":"text","delta":"late cleanup"}}`)
	driver.handleSSEEvent(`{"type":"message.updated","properties":{"sessionID":"sess-1","info":{"id":"msg-assistant-current","parentID":"msg-user-current","sessionID":"sess-1","role":"assistant","finish":"error","error":{"name":"ContextOverflowError","data":{"message":"Input exceeds context window of this model"}},"time":{"completed":1234}}}}`)
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	terminal := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, terminal, 1)
	require.True(t, driver.IsContextWindowExceeded(terminal[0]))
	require.Equal(t, consts.ParsedEventKindTurnEnd, driver.ParseLine(<-driver.translatedLines)[0].Kind)
	require.True(t, driver.QueueContextCompaction(terminal[0]))
	require.Equal(t, "idle", driver.sessionStatus)
	require.False(t, driver.turnActive)
	require.False(t, driver.contextTerminalDrain)

	started, err = driver.StartIdleCompaction(context.Background(), "sess-1")
	require.NoError(t, err)
	require.True(t, started)
	select {
	case <-requestStarted:
	case <-time.After(time.Second):
		t.Fatal("summarize request was not received")
	}

	// The summarize's fresh busy opens the new activity generation; its matching
	// compacted event and idle complete the HTTP+SSE success fence.
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))
	driver.handleSSEEvent(openCodeCompactionPartEvent(t, "sess-1", "msg-manual-compaction", false))
	driver.handleSSEEvent(openCodeSummaryAssistantEvent(t, "sess-1", "msg-summary-manual", "msg-manual-compaction"))
	driver.handleSSEEvent(openCodeCompactionPartEvent(t, "sess-1", "msg-manual-compaction", false))
	driver.handleSSEEvent(openCodeSessionCompactedEvent(t, "sess-1"))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	var completion []drivercontract.ParsedEvent
	require.Eventually(t, func() bool {
		select {
		case line := <-driver.translatedLines:
			events := driver.ParseLine(line)
			if len(events) == 1 {
				completion = append(completion, events[0])
				return events[0].Kind == consts.ParsedEventKindRuntimeIdle
			}
		default:
		}
		return false
	}, time.Second, 5*time.Millisecond)
	require.Equal(t, []string{
		consts.ParsedEventKindRuntimeCompacted,
		consts.ParsedEventKindRuntimeIdle,
	}, parsedEventKinds(completion))
	require.False(t, driver.contextTerminalDrain)
}

func TestOpenCodeManualSummarizeSuppressesInternalSummaryText(t *testing.T) {
	fence := &openCodeSummarizeBoundaryFence{
		sessionID:           "sess-1",
		sessionGeneration:   7,
		summarizeGeneration: 3,
	}
	driver := &OpenCodeDriver{
		sessionID:           "sess-1",
		sessionGeneration:   7,
		summarizeGeneration: 3,
		summarizeInProgress: true,
		summarizeFence:      fence,
		translatedLines:     make(chan string, 4),
		sessionStatus:       "busy",
		turnActive:          true,
	}

	driver.handleSSEEvent(openCodeCompactionPartEvent(t, "sess-1", "msg-compaction-manual", false))
	driver.handleSSEEvent(openCodeSummaryAssistantEvent(t, "sess-1", "msg-summary-manual", "msg-compaction-manual"))
	driver.handleSSEEvent(`{"type":"message.part.delta","properties":{"sessionID":"sess-1","messageID":"msg-summary-manual","partID":"part-summary-text","field":"text","delta":"INTERNAL SUMMARY"}}`)
	// processCompaction updates the same compaction part with tail_start_id
	// after the summary finishes. That idempotent update must not erase the
	// already-bound summary assistant from the success fence.
	driver.handleSSEEvent(openCodeCompactionPartEvent(t, "sess-1", "msg-compaction-manual", false))

	require.Empty(t, driver.translatedLines)
	driver.mu.Lock()
	require.Equal(t, "msg-summary-manual", fence.summaryAssistantID)
	require.Empty(t, driver.textBuf.String())
	driver.mu.Unlock()
}

func TestOpenCodeSubtaskCommandSyntheticContinuationRemainsVisible(t *testing.T) {
	rootUserID := openCodeMessageIDFromParts(2_000, 1, "AAAAAAAAAAAAAA")
	subtaskAssistantID := openCodeMessageIDFromParts(2_000, 2, "BBBBBBBBBBBBBB")
	// OpenCode resets its ascending-id counter whenever Date.now() changes; a
	// clock rollback can therefore make legitimate same-Runner continuation
	// ids smaller than the root prompt.
	summaryUserID := openCodeMessageIDFromParts(1_000, 1, "CCCCCCCCCCCCCC")
	continuationAssistantID := openCodeMessageIDFromParts(1_000, 2, "DDDDDDDDDDDDDD")
	driver := &OpenCodeDriver{
		sessionID:                 "sess-1",
		activePromptMessageID:     rootUserID,
		translatedLines:           make(chan string, 4),
		sessionStatus:             "busy",
		turnActive:                true,
		runnerUserMessageIDs:      map[string]struct{}{rootUserID: {}},
		runnerAssistantMessageIDs: map[string]struct{}{subtaskAssistantID: {}},
	}

	// handleSubtask(command=...) creates a synthetic user row and fixed text
	// part before the next agentteam iteration creates its continuation assistant.
	driver.handleSSEEvent(openCodeUserMessageUpdatedEvent(t, "sess-1", summaryUserID))
	driver.handleSSEEvent(fmt.Sprintf(
		`{"type":"message.part.updated","properties":{"sessionID":"sess-1","part":{"id":"part-subtask-summary","messageID":%q,"type":"text","synthetic":true,"text":%q}}}`,
		summaryUserID,
		promptcompiler.OpenCodeSubtaskSummaryPrompt(),
	))
	driver.handleSSEEvent(openCodeAssistantMessageUpdatedEvent(
		t,
		"sess-1",
		continuationAssistantID,
		summaryUserID,
	))
	driver.handleSSEEvent(fmt.Sprintf(
		`{"type":"message.part.delta","properties":{"sessionID":"sess-1","messageID":%q,"partID":"part-continuation-text","field":"text","delta":"VISIBLE AFTER SUBTASK"}}`,
		continuationAssistantID,
	))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))

	require.Equal(t, []drivercontract.ParsedEvent{{
		Kind: consts.ParsedEventKindText,
		Text: "VISIBLE AFTER SUBTASK",
	}}, driver.ParseLine(<-driver.translatedLines))
	require.Equal(t, consts.ParsedEventKindTurnEnd, driver.ParseLine(<-driver.translatedLines)[0].Kind)
	require.Empty(t, driver.translatedLines)
}

func TestOpenCodeAutomaticCompactionSuccessContinuesWithoutAgentTeamSummarize(t *testing.T) {
	rootUserID := openCodeMessageIDFromParts(5_000, 1, "AAAAAAAAAAAAAA")
	originalAssistantID := openCodeMessageIDFromParts(5_000, 2, "BBBBBBBBBBBBBB")
	compactionUserID := openCodeMessageIDFromParts(4_000, 1, "CCCCCCCCCCCCCC")
	summaryAssistantID := openCodeMessageIDFromParts(3_000, 1, "DDDDDDDDDDDDDD")
	continuationUserID := openCodeMessageIDFromParts(2_000, 1, "EEEEEEEEEEEEEE")
	continuationAssistantID := openCodeMessageIDFromParts(1_000, 1, "FFFFFFFFFFFFFF")
	driver := &OpenCodeDriver{
		sessionID:             "sess-1",
		activePromptMessageID: rootUserID,
		translatedLines:       make(chan string, 16),
		sessionStatus:         "busy",
		turnActive:            true,
		runnerUserMessageIDs:  map[string]struct{}{rootUserID: {}},
	}

	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"ContextOverflowError","data":{"message":"request entity too large"}}}}`)
	diagnostic := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, diagnostic, 1)
	require.True(t, parsedBoolMetadata(diagnostic[0].Metadata, runtimeErrorNonterminalMetadataKey))

	// OpenCode auto-compacts inside the same Runner: ordinary assistant cleanup,
	// an auto compaction part, a summary assistant, session.compacted, and an
	// optional continuation all precede the one Runner idle. The valid ids move
	// backward to prove current-Runner parent/source correlation, not numeric
	// ordering, owns these internal messages.
	driver.handleSSEEvent(openCodeAssistantMessageUpdatedEvent(t, "sess-1", originalAssistantID, rootUserID))
	driver.handleSSEEvent(openCodeCompactionPartEvent(t, "sess-1", compactionUserID, true))
	driver.handleSSEEvent(openCodeSummaryAssistantEvent(t, "sess-1", summaryAssistantID, compactionUserID))
	driver.handleSSEEvent(fmt.Sprintf(
		`{"type":"message.part.delta","properties":{"sessionID":"sess-1","messageID":%q,"partID":"part-summary-text","field":"text","delta":"INTERNAL SUMMARY"}}`,
		summaryAssistantID,
	))
	// The same compaction part is republished with tail_start_id before replay
	// continuation creation. Preserve the summary binding across that update.
	driver.handleSSEEvent(openCodeCompactionPartEvent(t, "sess-1", compactionUserID, true))
	driver.handleSSEEvent(openCodeUserMessageUpdatedEvent(t, "sess-1", continuationUserID))
	driver.handleSSEEvent(openCodeSessionCompactedEvent(t, "sess-1"))
	driver.handleSSEEvent(openCodeAssistantMessageUpdatedEvent(
		t,
		"sess-1",
		continuationAssistantID,
		continuationUserID,
	))
	driver.handleSSEEvent(fmt.Sprintf(
		`{"type":"message.part.delta","properties":{"sessionID":"sess-1","messageID":%q,"partID":"part-continuation-text","field":"text","delta":"VISIBLE CONTINUATION"}}`,
		continuationAssistantID,
	))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))

	textEvent := driver.ParseLine(<-driver.translatedLines)
	require.Equal(t, []drivercontract.ParsedEvent{{
		Kind: consts.ParsedEventKindText,
		Text: "VISIBLE CONTINUATION",
	}}, textEvent)
	boundary := driver.ParseLine(<-driver.translatedLines)
	require.Equal(t, []drivercontract.ParsedEvent{{
		Kind:      consts.ParsedEventKindTurnEnd,
		SessionID: "sess-1",
	}}, boundary)
	require.Empty(t, driver.translatedLines)
	require.False(t, driver.generationRetiring)
	require.False(t, driver.compactQueued)
	require.False(t, driver.turnActive)
	require.Equal(t, "idle", driver.sessionStatus)
}

func TestOpenCodeAutomaticCompactionSummaryAcknowledgesPendingRootInput(t *testing.T) {
	rootUserID := openCodeMessageIDFromParts(5_000, 1, "AAAAAAAAAAAAAA")
	compactionUserID := openCodeMessageIDFromParts(4_000, 1, "BBBBBBBBBBBBBB")
	summaryAssistantID := openCodeMessageIDFromParts(3_000, 1, "CCCCCCCCCCCCCC")
	runtimeInputID := "agentteam-message-auto-compaction-root"
	driver := &OpenCodeDriver{
		sessionID:             "sess-1",
		activePromptMessageID: rootUserID,
		translatedLines:       make(chan string, 4),
		sessionStatus:         "busy",
		turnActive:            true,
		runnerUserMessageIDs:  map[string]struct{}{rootUserID: {}},
		pendingRuntimeInputs: map[string]openCodePendingRuntimeInput{
			rootUserID: {
				RuntimeInputID: runtimeInputID,
				Persisted:      true,
				SessionID:      "sess-1",
			},
		},
	}

	// runAgentTeam can detect overflow from the previous finished assistant before
	// creating any assistant parented by this newly admitted root user. The
	// source-owned auto-compaction row carries the pending receipt to its
	// summary assistant, preserving exact-parent acknowledgement.
	driver.handleSSEEvent(openCodeCompactionPartEvent(t, "sess-1", compactionUserID, true))
	require.NotContains(t, driver.pendingRuntimeInputs, rootUserID)
	require.Equal(t, runtimeInputID, driver.pendingRuntimeInputs[compactionUserID].RuntimeInputID)
	require.Empty(t, driver.translatedLines)

	driver.handleSSEEvent(openCodeSummaryAssistantEvent(
		t,
		"sess-1",
		summaryAssistantID,
		compactionUserID,
	))
	acked := driver.ParseLine(<-driver.translatedLines)
	require.Equal(t, []drivercontract.ParsedEvent{{
		Kind:           consts.ParsedEventKindRuntimeInputAcked,
		SessionID:      "sess-1",
		RuntimeInputID: runtimeInputID,
	}}, acked)
	require.Empty(t, driver.pendingRuntimeInputs)

	// A plugin may disable auto-continuation; the summary receipt is sufficient
	// to settle ownership even when Runner ends immediately afterward.
	driver.handleSSEEvent(openCodeSessionCompactedEvent(t, "sess-1"))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	require.Equal(t, consts.ParsedEventKindTurnEnd, driver.ParseLine(<-driver.translatedLines)[0].Kind)
	require.False(t, driver.generationRetiring)
}

func TestOpenCodeAutomaticCompactionProviderFailureWaitsForRunnerIdle(t *testing.T) {
	retired := make(chan struct{})
	driver := &OpenCodeDriver{
		sessionID:             "sess-1",
		activePromptMessageID: "msg-user-current",
		translatedLines:       make(chan string, 16),
		sessionStatus:         "busy",
		turnActive:            true,
		runnerUserMessageIDs:  map[string]struct{}{"msg-user-current": {}},
		serverCancel:          sync.OnceFunc(func() { close(retired) }),
	}

	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"ContextOverflowError","data":{"message":"request entity too large"}}}}`)
	require.True(t, parsedBoolMetadata(driver.ParseLine(<-driver.translatedLines)[0].Metadata, runtimeErrorNonterminalMetadataKey))
	driver.handleSSEEvent(openCodeCompactionPartEvent(t, "sess-1", "msg-compaction-auto", true))
	driver.handleSSEEvent(`{"type":"message.updated","properties":{"sessionID":"sess-1","info":{"id":"msg-summary","parentID":"msg-compaction-auto","sessionID":"sess-1","role":"assistant","summary":true}}}`)

	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"ProviderError","data":{"message":"summary provider failed"}}}}`)
	summaryDiagnostic := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, summaryDiagnostic, 1)
	require.True(t, parsedBoolMetadata(summaryDiagnostic[0].Metadata, runtimeErrorNonterminalMetadataKey))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	select {
	case line := <-driver.translatedLines:
		t.Fatalf("summary processor idle leaked a terminal boundary: %s", line)
	default:
	}
	require.True(t, driver.contextTerminalDrain)

	driver.handleSSEEvent(`{"type":"message.updated","properties":{"sessionID":"sess-1","info":{"id":"msg-summary","parentID":"msg-compaction-auto","sessionID":"sess-1","role":"assistant","summary":true,"error":{"name":"ProviderError","data":{"message":"summary provider failed"}},"time":{"completed":200}}}}`)
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	select {
	case <-retired:
	case <-time.After(time.Second):
		t.Fatal("failed automatic compaction did not retire the runtime generation")
	}
	terminal := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, terminal, 1)
	require.Equal(t, consts.ParsedEventKindError, terminal[0].Kind)
	require.True(t, parsedBoolMetadata(terminal[0].Metadata, contextCompactionFailedMetadataKey))
	require.False(t, parsedBoolMetadata(terminal[0].Metadata, contextCompactionMaintenanceMetadataKey))
	boundary := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, boundary, 1)
	require.Equal(t, consts.ParsedEventKindTurnEnd, boundary[0].Kind)
	require.True(t, parsedBoolMetadata(boundary[0].Metadata, contextCompactionFailedMetadataKey))
	require.False(t, parsedBoolMetadata(boundary[0].Metadata, contextCompactionMaintenanceMetadataKey))
	require.True(t, driver.generationRetiring)
}

func TestOpenCodeSyntheticContinuationAssistantErrorIsTerminal(t *testing.T) {
	rootUserID := openCodeMessageIDFromParts(2_000, 1, "AAAAAAAAAAAAAA")
	rootAssistantID := openCodeMessageIDFromParts(2_000, 2, "BBBBBBBBBBBBBB")
	continuationUserID := openCodeMessageIDFromParts(1_000, 1, "CCCCCCCCCCCCCC")
	continuationAssistantID := openCodeMessageIDFromParts(1_000, 2, "DDDDDDDDDDDDDD")
	retired := make(chan struct{})
	driver := &OpenCodeDriver{
		sessionID:                 "sess-1",
		activePromptMessageID:     rootUserID,
		translatedLines:           make(chan string, 4),
		sessionStatus:             "busy",
		turnActive:                true,
		runnerUserMessageIDs:      map[string]struct{}{rootUserID: {}},
		runnerAssistantMessageIDs: map[string]struct{}{rootAssistantID: {}},
		serverCancel:              sync.OnceFunc(func() { close(retired) }),
	}

	driver.handleSSEEvent(fmt.Sprintf(
		`{"type":"message.part.updated","properties":{"sessionID":"sess-1","part":{"id":"part-subtask-summary","messageID":%q,"type":"text","synthetic":true,"text":%q}}}`,
		continuationUserID,
		promptcompiler.OpenCodeSubtaskSummaryPrompt(),
	))
	driver.handleSSEEvent(fmt.Sprintf(
		`{"type":"message.updated","properties":{"sessionID":"sess-1","info":{"id":%q,"parentID":%q,"sessionID":"sess-1","role":"assistant","finish":"error","error":{"name":"ProviderError","data":{"message":"continuation failed"}},"time":{"completed":200}}}}`,
		continuationAssistantID,
		continuationUserID,
	))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))

	select {
	case <-retired:
	case <-time.After(time.Second):
		t.Fatal("synthetic continuation assistant failure did not retire the runtime generation")
	}
	terminal := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, terminal, 1)
	require.Equal(t, consts.ParsedEventKindError, terminal[0].Kind)
	require.Contains(t, terminal[0].Message, "continuation failed")
	require.True(t, driver.generationRetiring)
}

func TestOpenCodePostBoundarySessionErrorRetiresInsteadOfReopeningBusy(t *testing.T) {
	retired := make(chan struct{})
	driver := &OpenCodeDriver{
		sessionID:       "sess-1",
		translatedLines: make(chan string, 4),
		sessionStatus:   "busy",
		turnActive:      true,
		serverCancel:    sync.OnceFunc(func() { close(retired) }),
	}

	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	require.Equal(t, consts.ParsedEventKindTurnEnd, driver.ParseLine(<-driver.translatedLines)[0].Kind)
	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"UnknownError","message":"late catchCause wrapper"}}}`)
	select {
	case <-retired:
	case <-time.After(time.Second):
		t.Fatal("post-boundary session.error did not retire the runtime generation")
	}
	events := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, events, 1)
	require.Equal(t, consts.ParsedEventKindError, events[0].Kind)
	require.False(t, parsedBoolMetadata(events[0].Metadata, runtimeErrorNonterminalMetadataKey))
	require.True(t, driver.generationRetiring)
	require.True(t, driver.sseDeliveryBlocked)
}

func TestOpenCodeLatePruneActivityAfterIdleDoesNotReopenTurn(t *testing.T) {
	retired := make(chan struct{})
	driver := &OpenCodeDriver{
		sessionID:       "sess-1",
		translatedLines: make(chan string, 8),
		sessionStatus:   "busy",
		turnActive:      true,
		serverCancel:    sync.OnceFunc(func() { close(retired) }),
	}

	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	require.Equal(t, consts.ParsedEventKindTurnEnd, driver.ParseLine(<-driver.translatedLines)[0].Kind)

	// compaction.prune is forked while Runner is still active and can publish
	// old message parts after Runner's authoritative idle. Those background
	// frames must not reopen the logical turn or erase the late-error fence.
	driver.handleSSEEvent(`{"type":"message.part.updated","properties":{"sessionID":"sess-1","part":{"id":"old-tool-part","messageID":"old-assistant","type":"tool","tool":"read","callID":"old-call","state":{"status":"completed","input":{},"output":"old"}}}}`)
	driver.handleSSEEvent(`{"type":"message.part.delta","properties":{"sessionID":"sess-1","messageID":"old-assistant","partID":"old-text-part","field":"text","delta":"stale"}}`)
	driver.handleSSEEvent(`{"type":"message.updated","properties":{"sessionID":"sess-1","info":{"id":"old-assistant","parentID":"old-user","sessionID":"sess-1","role":"assistant"}}}`)
	require.Empty(t, driver.translatedLines)

	driver.mu.Lock()
	require.False(t, driver.turnActive)
	require.Equal(t, "idle", driver.sessionStatus)
	require.True(t, driver.postBoundaryAwaitFreshBusy)
	require.Empty(t, driver.textBuf.String())
	driver.mu.Unlock()

	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"UnknownError","message":"late catchCause wrapper"}}}`)
	select {
	case <-retired:
	case <-time.After(time.Second):
		t.Fatal("stale prune activity erased the post-boundary retirement fence")
	}
	events := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, events, 1)
	require.Equal(t, consts.ParsedEventKindError, events[0].Kind)
	require.False(t, parsedBoolMetadata(events[0].Metadata, runtimeErrorNonterminalMetadataKey))
	require.True(t, driver.generationRetiring)
}

func TestOpenCodeNewPromptPreprocessingErrorDoesNotInheritOldPostBoundaryFence(t *testing.T) {
	promptMessageIDs := make(chan string, 1)
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		var body struct {
			MessageID string `json:"messageID"`
		}
		require.NoError(t, json.NewDecoder(r.Body).Decode(&body))
		promptMessageIDs <- body.MessageID
		w.WriteHeader(http.StatusNoContent)
	}))
	defer server.Close()

	retired := make(chan struct{})
	driver := &OpenCodeDriver{
		serverURL:       server.URL,
		password:        "pw",
		httpClient:      server.Client(),
		sessionID:       "sess-1",
		workDir:         t.TempDir(),
		translatedLines: make(chan string, 16),
		sessionStatus:   "busy",
		turnActive:      true,
		serverCancel:    sync.OnceFunc(func() { close(retired) }),
	}

	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	require.Equal(t, consts.ParsedEventKindTurnEnd, driver.ParseLine(<-driver.translatedLines)[0].Kind)
	require.True(t, driver.postBoundaryAwaitFreshBusy)

	require.NoError(t, driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: "agentteam-new"}, "new prompt"), "sess-1"))
	var promptMessageID string
	select {
	case promptMessageID = <-promptMessageIDs:
	case <-time.After(time.Second):
		t.Fatal("new prompt was not submitted")
	}

	// createUserMessage reports local file/directory preprocessing failures
	// before runAgentTeam busy, then converts them to synthetic text and continues.
	// With a fresh reservation this is provisional, not the previous prompt's
	// late catchCause wrapper.
	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"UnknownError","data":{"message":"failed to read directory"}}}}`)
	diagnostic := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, diagnostic, 1)
	require.True(t, parsedBoolMetadata(diagnostic[0].Metadata, runtimeErrorNonterminalMetadataKey))
	select {
	case <-retired:
		t.Fatal("recoverable preprocessing error retired the fresh prompt")
	default:
	}
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))
	driver.handleSSEEvent(openCodeAssistantMessageUpdatedEvent(t, "sess-1", "msg-new-assistant", promptMessageID))
	acked := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, acked, 1)
	require.Equal(t, consts.ParsedEventKindRuntimeInputAcked, acked[0].Kind)
	require.Equal(t, "agentteam-new", acked[0].RuntimeInputID)
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	require.Equal(t, consts.ParsedEventKindTurnEnd, driver.ParseLine(<-driver.translatedLines)[0].Kind)
	require.False(t, driver.generationRetiring)
	require.Nil(t, driver.promptAwaitingRunner)
	require.Empty(t, driver.pendingRuntimeInputs)
	require.Equal(t, "idle", driver.sessionStatus)
}

func TestOpenCodeTerminalErrorWithoutAssistantParentRetiresCorrelatedInput(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusNoContent)
	}))
	defer server.Close()

	retired := make(chan struct{})
	driver := &OpenCodeDriver{
		serverURL:       server.URL,
		password:        "pw",
		httpClient:      server.Client(),
		sessionID:       "sess-1",
		sessionStatus:   "idle",
		workDir:         t.TempDir(),
		translatedLines: make(chan string, 4),
		serverCancel:    sync.OnceFunc(func() { close(retired) }),
	}
	require.NoError(t, driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: "agentteam-correlated"}, "model selection will fail before assistant creation"), "sess-1"))
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))
	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"ModelNotFoundError","message":"model not found"}}}`)
	require.Equal(t, true, driver.ParseLine(<-driver.translatedLines)[0].Metadata[runtimeErrorNonterminalMetadataKey])
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))

	select {
	case <-retired:
	case <-time.After(time.Second):
		t.Fatal("unacknowledged terminal correlated input did not retire the generation")
	}
	terminal := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, terminal, 1)
	require.Equal(t, consts.ParsedEventKindError, terminal[0].Kind)
	driver.mu.Lock()
	require.Len(t, driver.pendingRuntimeInputs, 1, "ownership correlation must remain fail-closed until Process retirement")
	require.True(t, driver.generationRetiring)
	require.True(t, driver.sseDeliveryBlocked)
	driver.mu.Unlock()
}

func TestOpenCodeExactParentTerminalAssistantStillAdmitsRunner(t *testing.T) {
	retired := make(chan struct{})
	reservation := &openCodePromptRunnerReservation{
		MessageID: "msg-user-current",
		SessionID: "sess-1",
		done:      make(chan struct{}),
	}
	driver := &OpenCodeDriver{
		sessionID:             "sess-1",
		activePromptMessageID: "msg-user-current",
		sessionStatus:         "busy",
		turnActive:            true,
		promptAwaitingRunner:  reservation,
		pendingRuntimeInputs: map[string]openCodePendingRuntimeInput{
			"msg-user-current": {
				RuntimeInputID: "agentteam-current",
				SessionID:      "sess-1",
			},
		},
		translatedLines: make(chan string, 8),
		serverCancel:    sync.OnceFunc(func() { close(retired) }),
	}

	// An assistant can be created and fail before a busy frame is observed.
	// Exact parenthood is still Runner admission and a acked ownership ACK,
	// even though the same update is terminal.
	driver.handleSSEEvent(`{"type":"message.updated","properties":{"sessionID":"sess-1","info":{"id":"msg-assistant-current","parentID":"msg-user-current","sessionID":"sess-1","role":"assistant","finish":"error","error":{"name":"ProviderError","data":{"message":"provider rejected"}},"time":{"completed":100}}}}`)
	require.Nil(t, driver.promptAwaitingRunner)
	acked := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, acked, 1)
	require.Equal(t, consts.ParsedEventKindRuntimeInputAcked, acked[0].Kind)
	require.Equal(t, "agentteam-current", acked[0].RuntimeInputID)

	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	select {
	case <-retired:
	case <-time.After(time.Second):
		t.Fatal("terminal exact-parent assistant did not retire the generation")
	}
	terminal := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, terminal, 1)
	require.Equal(t, consts.ParsedEventKindError, terminal[0].Kind)
	require.Contains(t, terminal[0].Message, "provider rejected")
}

func TestOpenCodeAbandonedQueuedCompactionDoesNotBlockNextPrompt(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(http.StatusNoContent)
	}))
	defer server.Close()

	driver := &OpenCodeDriver{
		serverURL:       server.URL,
		password:        "pw",
		httpClient:      server.Client(),
		sessionID:       "sess-1",
		sessionStatus:   "idle",
		workDir:         t.TempDir(),
		translatedLines: make(chan string, 2),
		compactQueued:   true,
	}
	require.NoError(t, driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: "agentteam-after-abandoned-compaction"}, "continue without the abandoned compaction"), "sess-1"))
	driver.mu.Lock()
	require.False(t, driver.compactQueued)
	require.NotNil(t, driver.promptAwaitingRunner)
	driver.mu.Unlock()
}

func TestOpenCodePromptRunnerAdmissionWatchdogRetiresStuckGeneration(t *testing.T) {
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		require.Equal(t, "/session/sess-1/prompt_async", r.URL.Path)
		w.WriteHeader(http.StatusNoContent)
	}))
	defer server.Close()

	retired := make(chan struct{})
	driver := &OpenCodeDriver{
		serverURL:                    server.URL,
		password:                     "pw",
		httpClient:                   server.Client(),
		sessionID:                    "sess-1",
		sessionStatus:                "idle",
		workDir:                      t.TempDir(),
		translatedLines:              make(chan string, 2),
		promptRunnerAdmissionTimeout: 20 * time.Millisecond,
		serverCancel:                 sync.OnceFunc(func() { close(retired) }),
	}

	require.NoError(t, driver.DeliverRuntimeInput(context.Background(), testRuntimeInputWithConfig(drivercontract.RuntimeInput{ID: "agentteam-message-stuck"}, "accepted but createUserMessage will fail before Runner"), "sess-1"))
	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"UnknownError","message":"model resolution failed"}}}`)

	select {
	case <-retired:
	case <-time.After(time.Second):
		t.Fatal("pre-Runner terminal path did not retire the runtime generation")
	}
	driver.mu.Lock()
	require.NotNil(t, driver.promptAwaitingRunner)
	require.True(t, driver.sseDeliveryBlocked)
	driver.mu.Unlock()
}

func TestOpenCodeStalePriorTurnActivityCannotAdmitFreshPromptRunner(t *testing.T) {
	staleEvents := map[string]string{
		"tool_part":  `{"type":"message.part.updated","properties":{"sessionID":"sess-1","part":{"id":"old-part","messageID":"old-assistant","type":"tool","tool":"read","callID":"old-call","state":{"status":"completed","input":{},"output":"old"}}}}`,
		"text_delta": `{"type":"message.part.delta","properties":{"sessionID":"sess-1","messageID":"old-assistant","partID":"old-text","field":"text","delta":"old"}}`,
		"assistant":  `{"type":"message.updated","properties":{"sessionID":"sess-1","info":{"id":"old-assistant","parentID":"old-user","sessionID":"sess-1","role":"assistant"}}}`,
	}
	for name, staleEvent := range staleEvents {
		t.Run(name, func(t *testing.T) {
			retired := make(chan struct{})
			reservation := &openCodePromptRunnerReservation{
				MessageID: "msg-user-fresh",
				SessionID: "sess-1",
				done:      make(chan struct{}),
			}
			driver := &OpenCodeDriver{
				sessionID:             "sess-1",
				sessionStatus:         "busy",
				turnActive:            true,
				activePromptMessageID: "msg-user-fresh",
				promptAwaitingRunner:  reservation,
				pendingRuntimeInputs: map[string]openCodePendingRuntimeInput{
					"msg-user-fresh": {
						RuntimeInputID: "agentteam-fresh",
						SessionID:      "sess-1",
					},
				},
				translatedLines:              make(chan string, 8),
				promptRunnerAdmissionTimeout: 20 * time.Millisecond,
				serverCancel:                 sync.OnceFunc(func() { close(retired) }),
			}
			driver.startPromptRunnerAdmissionWatchdog(reservation)

			// OpenCode can fork compaction.prune before the old Runner idle and
			// publish old part updates afterward. No uncorrelated same-session
			// frame may close the new prompt's admission watchdog.
			driver.handleSSEEvent(staleEvent)
			require.Same(t, reservation, driver.promptAwaitingRunner)
			driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"UnknownError","message":"fresh prompt failed before Runner"}}}`)
			select {
			case <-retired:
			case <-time.After(time.Second):
				t.Fatal("fresh prompt admission watchdog was disabled by stale activity")
			}
		})
	}
}

func TestOpenCodeStalePruneCannotEnterFreshAdmittedRunner(t *testing.T) {
	oldUserID := openCodeMessageIDFromParts(1_000, 1, "AAAAAAAAAAAAAA")
	oldAssistantID := openCodeMessageIDFromParts(1_000, 2, "BBBBBBBBBBBBBB")
	freshUserID := openCodeMessageIDFromParts(2_000, 1, "CCCCCCCCCCCCCC")
	freshAssistantID := openCodeMessageIDFromParts(2_000, 2, "DDDDDDDDDDDDDD")
	driver := &OpenCodeDriver{
		sessionID:                    "sess-1",
		activePromptMessageID:        oldUserID,
		sessionStatus:                "busy",
		turnActive:                   true,
		translatedLines:              make(chan string, 8),
		runnerUserMessageIDs:         map[string]struct{}{oldUserID: {}},
		runnerAssistantMessageIDs:    map[string]struct{}{oldAssistantID: {}},
		messageIDLastEncoded:         1,
		promptRunnerAdmissionTimeout: time.Second,
	}

	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "idle"))
	require.Equal(t, consts.ParsedEventKindTurnEnd, driver.ParseLine(<-driver.translatedLines)[0].Kind)
	reservation, err := driver.reservePromptAwaitingRunnerWithCompaction("sess-1", freshUserID, nil)
	require.NoError(t, err)
	require.NotNil(t, reservation)
	driver.handleSSEEvent(openCodeSessionStatusEvent(t, "sess-1", "busy"))
	require.Nil(t, driver.promptAwaitingRunner)

	oldAssistantUpdate := fmt.Sprintf(
		`{"type":"message.updated","properties":{"sessionID":"sess-1","info":{"id":%q,"parentID":%q,"sessionID":"sess-1","role":"assistant"}}}`,
		oldAssistantID,
		oldUserID,
	)
	oldToolPart := fmt.Sprintf(
		`{"type":"message.part.updated","properties":{"sessionID":"sess-1","part":{"id":"old-tool-part","messageID":%q,"type":"tool","tool":"read","callID":"old-call","state":{"status":"completed","input":{},"output":"old"}}}}`,
		oldAssistantID,
	)

	// The old assistant update itself must not gain admission merely because
	// the fresh Runner has published busy.
	driver.handleSSEEvent(oldAssistantUpdate)
	driver.handleSSEEvent(oldToolPart)
	require.Empty(t, driver.translatedLines)

	driver.handleSSEEvent(openCodeAssistantMessageUpdatedEvent(t, "sess-1", freshAssistantID, freshUserID))
	driver.handleSSEEvent(oldToolPart)
	require.Empty(t, driver.translatedLines, "old prune part entered the fresh Runner after its assistant was admitted")

	driver.handleSSEEvent(fmt.Sprintf(
		`{"type":"message.part.updated","properties":{"sessionID":"sess-1","part":{"id":"fresh-tool-part","messageID":%q,"type":"tool","tool":"read","callID":"fresh-call","state":{"status":"completed","input":{},"output":"fresh"}}}}`,
		freshAssistantID,
	))
	events := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, events, 1)
	require.Equal(t, consts.ParsedEventKindToolOutput, events[0].Kind)
	require.Equal(t, "fresh-call", events[0].ToolCallID)
	require.Equal(t, "fresh", events[0].Text)
}

func TestOpenCodeDriverSSESessionErrorUsesNestedDetail(t *testing.T) {
	driver := &OpenCodeDriver{
		sessionID:       "sess-1",
		translatedLines: make(chan string, 2),
	}

	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"UnknownError","data":{"message":"failed to process PDF: corrupt trailer"}}}}`)

	events := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, events, 1)
	require.Equal(t, consts.ParsedEventKindError, events[0].Kind)
	require.Equal(t, "UnknownError: failed to process PDF: corrupt trailer", events[0].Message)
	require.Equal(t, true, events[0].Metadata[runtimeErrorNonterminalMetadataKey])
}

func TestOpenCodeDriverSSESessionErrorMarksTransient529(t *testing.T) {
	driver := &OpenCodeDriver{
		sessionID:       "sess-1",
		translatedLines: make(chan string, 2),
	}

	driver.handleSSEEvent(`{"type":"session.error","properties":{"sessionID":"sess-1","error":{"name":"ProviderError","message":"API Error: 529 [1305][该模型当前访问量过大，请您稍后再试]"}}}`)

	events := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, events, 1)
	require.Equal(t, consts.ParsedEventKindError, events[0].Kind)
	require.Equal(t, true, events[0].Metadata["transient_runtime_error"])
	require.Equal(t, "overloaded", events[0].Metadata["transient_runtime_error_reason"])
	require.Equal(t, float64(529), events[0].Metadata["http_status"])
	require.Equal(t, true, events[0].Metadata[runtimeErrorNonterminalMetadataKey])
}

func TestOpenCodeDriverSSESessionRetryEmitsRetryableError(t *testing.T) {
	driver := &OpenCodeDriver{
		sessionID:       "sess-1",
		translatedLines: make(chan string, 2),
	}

	driver.handleSSEEvent(openCodeSessionRetryStatusEvent(t, "sess-1", 11, "hosted model family is not prepaid-admissible", 1781063753266))
	driver.handleSSEEvent(openCodeSessionRetryStatusEvent(t, "sess-1", 11, "hosted model family is not prepaid-admissible", 1781063753266))

	events := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, events, 1)
	require.Equal(t, consts.ParsedEventKindError, events[0].Kind)
	require.Equal(t, "hosted model family is not prepaid-admissible", events[0].Message)
	require.Equal(t, true, events[0].Metadata["will_retry"])
	require.Equal(t, float64(11), events[0].Metadata["retry_attempt"])
	require.Equal(t, float64(1781063753266), events[0].Metadata["retry_next"])

	select {
	case line := <-driver.translatedLines:
		t.Fatalf("duplicate retry status emitted another error: %s", line)
	default:
	}
}

func TestOpenCodeDriverSSEMessageUpdatedDoesNotEmitUsage(t *testing.T) {
	t.Parallel()

	// message.updated carries only the last step's replacement token value, so it
	// must not be emitted as a usage event (otherwise multi-step turns would be
	// miscounted). Usage is accumulated from step-finish parts instead.
	driver := &OpenCodeDriver{
		sessionID:       "sess-1",
		translatedLines: make(chan string, 1),
	}

	driver.handleSSEEvent(openCodeMessageUpdatedEvent(t, "sess-1", "msg-1", "deepseek-v4-pro", 34, 5, 8))

	select {
	case line := <-driver.translatedLines:
		t.Fatalf("message.updated should not emit usage: %s", line)
	default:
	}
}

func TestOpenCodeDriverSSEStepFinishEmitsNormalizedUsage(t *testing.T) {
	t.Parallel()

	// Each step-finish part is one model invocation. input folds in cache read
	// and cache write, output folds in reasoning, and cached is the cache-read
	// subset of input — so total = input + output matches the provider total.
	// Model is left empty (attribution falls back to the process launch model);
	// see TestOpenCodeDriverSSEStepFinishLeavesModelEmpty.
	driver := &OpenCodeDriver{
		sessionID:                 "sess-1",
		translatedLines:           make(chan string, 1),
		sessionStatus:             "busy",
		turnActive:                true,
		runnerAssistantMessageIDs: map[string]struct{}{"msg-assistant-current": {}},
	}

	driver.handleSSEEvent(openCodeStepFinishEvent(t, "sess-1", "part-1", 100, 400, 50, 200, 30))

	line := <-driver.translatedLines
	require.Equal(t, []drivercontract.ParsedEvent{{
		Kind:                    consts.ParsedEventKindUsage,
		SessionID:               "sess-1",
		ModelInvocationID:       "part-1",
		ContextExposureKnown:    true,
		ContextExposureExcluded: false,
		ContextCompacted:        false,
		InputTokens:             550, // 100 + 400 (cache read) + 50 (cache write)
		CachedInputTokens:       400,
		OutputTokens:            230, // 200 + 30 (reasoning)
	}}, driver.ParseLine(line))
}

func TestOpenCodeDriverExcludesCompactionSummaryInvocationFromRootExposure(t *testing.T) {
	t.Parallel()

	driver := &OpenCodeDriver{
		sessionID:                        "sess-1",
		translatedLines:                  make(chan string, 1),
		sessionStatus:                    "busy",
		turnActive:                       true,
		runnerAssistantMessageIDs:        map[string]struct{}{"msg-assistant-current": {}},
		runnerSummaryAssistantMessageIDs: map[string]struct{}{"msg-assistant-current": {}},
	}

	driver.handleSSEEvent(openCodeStepFinishEvent(t, "sess-1", "part-summary", 100, 0, 0, 20, 0))

	usage := driver.ParseLine(<-driver.translatedLines)
	require.Len(t, usage, 1)
	require.False(t, usage[0].ContextExposureKnown)
	require.True(t, usage[0].ContextExposureExcluded)
	require.False(t, usage[0].ContextCompacted)
}

func TestOpenCodeDriverSSEStepFinishAccumulatesAndDeduplicates(t *testing.T) {
	t.Parallel()

	driver := &OpenCodeDriver{
		sessionID:                 "sess-1",
		translatedLines:           make(chan string, 4),
		sessionStatus:             "busy",
		turnActive:                true,
		runnerAssistantMessageIDs: map[string]struct{}{"msg-assistant-current": {}},
	}

	// Two distinct model invocations (e.g. a tool-call turn) produce two
	// step-finish parts; both must be counted.
	driver.handleSSEEvent(openCodeStepFinishEvent(t, "sess-1", "part-1", 100, 400, 50, 200, 30))
	line1 := <-driver.translatedLines
	driver.handleSSEEvent(openCodeStepFinishEvent(t, "sess-1", "part-2", 80, 20, 0, 60, 10))
	line2 := <-driver.translatedLines

	// Republishing the same step-finish part id must not double-count.
	driver.handleSSEEvent(openCodeStepFinishEvent(t, "sess-1", "part-1", 100, 400, 50, 200, 30))
	select {
	case line := <-driver.translatedLines:
		t.Fatalf("republished step-finish emitted duplicate usage: %s", line)
	default:
	}

	events := append(driver.ParseLine(line1), driver.ParseLine(line2)...)
	require.Equal(t, []string{"part-1", "part-2"}, []string{
		events[0].ModelInvocationID,
		events[1].ModelInvocationID,
	})
	var totalInput, totalCached, totalOutput uint64
	for _, ev := range events {
		totalInput += ev.InputTokens
		totalCached += ev.CachedInputTokens
		totalOutput += ev.OutputTokens
	}
	require.Equal(t, uint64(650), totalInput)  // 550 + (80+20+0)
	require.Equal(t, uint64(420), totalCached) // 400 + 20
	require.Equal(t, uint64(300), totalOutput) // 230 + (60+10)
}

func TestOpenCodeDriverMarksFirstPostCompactionRootInvocation(t *testing.T) {
	driver := &OpenCodeDriver{
		sessionID:                 "sess-1",
		translatedLines:           make(chan string, 2),
		sessionStatus:             "busy",
		turnActive:                true,
		runnerAssistantMessageIDs: map[string]struct{}{"msg-assistant-current": {}},
	}
	driver.handleSSEEvent(openCodeSessionCompactedEvent(t, "sess-1"))
	driver.handleSSEEvent(openCodeStepFinishEvent(t, "sess-1", "part-1", 100, 0, 0, 20, 0))
	driver.handleSSEEvent(openCodeStepFinishEvent(t, "sess-1", "part-2", 80, 0, 0, 10, 0))

	first := driver.ParseLine(<-driver.translatedLines)
	second := driver.ParseLine(<-driver.translatedLines)
	require.True(t, first[0].ContextCompacted)
	require.False(t, second[0].ContextCompacted)
}

func openCodeMessageUpdatedEvent(t *testing.T, sessionID, messageID, modelID string, input, cachedRead, output float64) string {
	t.Helper()

	data, err := json.Marshal(map[string]any{
		"type": "message.updated",
		"properties": map[string]any{
			"sessionID": sessionID,
			"info": map[string]any{
				"id":        messageID,
				"sessionID": sessionID,
				"role":      "assistant",
				"modelID":   modelID,
				"tokens": map[string]any{
					"input":  input,
					"output": output,
					"cache": map[string]any{
						"read": cachedRead,
					},
				},
			},
		},
	})
	require.NoError(t, err)
	return string(data)
}

func openCodeStepFinishEvent(t *testing.T, sessionID, partID string, input, cachedRead, cacheWrite, output, reasoning float64) string {
	t.Helper()

	data, err := json.Marshal(map[string]any{
		"type": "message.part.updated",
		"properties": map[string]any{
			"sessionID": sessionID,
			"part": map[string]any{
				"id":        partID,
				"messageID": "msg-assistant-current",
				"type":      "step-finish",
				"tokens": map[string]any{
					"input":     input,
					"output":    output,
					"reasoning": reasoning,
					"cache": map[string]any{
						"read":  cachedRead,
						"write": cacheWrite,
					},
				},
			},
		},
	})
	require.NoError(t, err)
	return string(data)
}

func openCodeUserMessageUpdatedEvent(t *testing.T, sessionID, messageID string) string {
	t.Helper()

	data, err := json.Marshal(map[string]any{
		"type": "message.updated",
		"properties": map[string]any{
			"sessionID": sessionID,
			"info": map[string]any{
				"id":        messageID,
				"sessionID": sessionID,
				"role":      "user",
			},
		},
	})
	require.NoError(t, err)
	return string(data)
}

func openCodeAssistantMessageUpdatedEvent(t *testing.T, sessionID, messageID, parentID string) string {
	t.Helper()

	data, err := json.Marshal(map[string]any{
		"type": "message.updated",
		"properties": map[string]any{
			"sessionID": sessionID,
			"info": map[string]any{
				"id":        messageID,
				"parentID":  parentID,
				"sessionID": sessionID,
				"role":      "assistant",
			},
		},
	})
	require.NoError(t, err)
	return string(data)
}

func openCodeSessionStatusEvent(t *testing.T, sessionID, status string) string {
	t.Helper()

	data, err := json.Marshal(map[string]any{
		"type": "session.status",
		"properties": map[string]any{
			"sessionID": sessionID,
			"status": map[string]any{
				"type": status,
			},
		},
	})
	require.NoError(t, err)
	return string(data)
}

func openCodeCompactionPartEvent(t *testing.T, sessionID, messageID string, auto bool) string {
	t.Helper()

	data, err := json.Marshal(map[string]any{
		"type": "message.part.updated",
		"properties": map[string]any{
			"sessionID": sessionID,
			"part": map[string]any{
				"id":        "part-compaction-" + messageID,
				"messageID": messageID,
				"sessionID": sessionID,
				"type":      "compaction",
				"auto":      auto,
			},
		},
	})
	require.NoError(t, err)
	return string(data)
}

func openCodeSummaryAssistantEvent(t *testing.T, sessionID, messageID, parentID string) string {
	t.Helper()

	data, err := json.Marshal(map[string]any{
		"type": "message.updated",
		"properties": map[string]any{
			"sessionID": sessionID,
			"info": map[string]any{
				"id":        messageID,
				"parentID":  parentID,
				"sessionID": sessionID,
				"role":      "assistant",
				"summary":   true,
			},
		},
	})
	require.NoError(t, err)
	return string(data)
}

func openCodeSessionCompactedEvent(t *testing.T, sessionID string) string {
	t.Helper()

	data, err := json.Marshal(map[string]any{
		"type":       "session.compacted",
		"properties": map[string]any{"sessionID": sessionID},
	})
	require.NoError(t, err)
	return string(data)
}

func openCodeSessionRetryStatusEvent(t *testing.T, sessionID string, attempt int, message string, next int64) string {
	t.Helper()

	data, err := json.Marshal(map[string]any{
		"type": "session.status",
		"properties": map[string]any{
			"sessionID": sessionID,
			"status": map[string]any{
				"type":    "retry",
				"attempt": attempt,
				"message": message,
				"next":    next,
			},
		},
	})
	require.NoError(t, err)
	return string(data)
}

func TestBuildOpenCodeServeEnv_DisablesClaudePromptLoading(t *testing.T) {
	t.Parallel()

	workDir := t.TempDir()
	env, err := buildOpenCodeServeEnv(drivercontract.LaunchConfig{WorkDir: workDir}, "pw-123")
	require.NoError(t, err)
	joined := strings.Join(env, "\n")

	require.Contains(t, joined, "OPENCODE_CONFIG_DIR="+filepath.Join(workDir, ".opencode"))
	require.Contains(t, joined, "OPENCODE_DISABLE_AUTOUPDATE=1")
	require.Contains(t, joined, "OPENCODE_DISABLE_CLAUDE_CODE_PROMPT=1")
	require.Contains(t, joined, "OPENCODE_DISABLE_LSP_DOWNLOAD=1")
	require.Contains(t, joined, "OPENCODE_DISABLE_MODELS_FETCH=1")
	require.Contains(t, joined, "OPENCODE_SERVER_USERNAME="+openCodeServerUsername)
	require.Contains(t, joined, "OPENCODE_SERVER_PASSWORD=pw-123")
	require.Contains(t, joined, "OPENCODE_CONFIG_CONTENT=")
	envMap := openCodeTestEnvMap(env)
	require.Contains(t, envMap["OPENCODE_CONFIG_CONTENT"], `"snapshot":false`)
}

func TestBuildOpenCodeServeEnv_UsesBundledModelsForBuiltinOpenCode(t *testing.T) {
	t.Setenv("oPeNcOdE_dB", "/ambient/opencode.db")

	workDir := t.TempDir()
	runtimeStateDir := filepath.Join(t.TempDir(), "agents", "agt_builtin", ".runtime")
	binary := filepath.Join(t.TempDir(), "opencode")
	env, err := buildOpenCodeServeEnv(drivercontract.LaunchConfig{
		AgentID:         "agt_builtin",
		RuntimeType:     runtimeconfig.TypeBuiltin,
		WorkDir:         workDir,
		RuntimeStateDir: runtimeStateDir,
		Binary:          binary,
		Env: map[string]string{
			"xdg_data_home":               "/user/override/data",
			"Xdg_State_Home":              "/user/override/state",
			"XDG_cache_HOME":              "/user/override/cache",
			"OpEnCoDe_Db":                 "/configured/opencode.db",
			"OpEnCoDe_Test_Home":          "/configured/test-home",
			"opencode_models_path":        "/configured/models.json",
			"opencode_disable_autoupdate": "0",
			"opencode_config_dir":         "/configured/config",
			"opencode_server_password":    "configured-password",
		},
	}, "pw-123")
	require.NoError(t, err)
	envMap := openCodeTestEnvMap(env)

	require.Equal(t, runtimeconfig.ManagedOpenCodeConfigDir(workDir), envMap["OPENCODE_CONFIG_DIR"])
	require.Equal(t, filepath.Join(filepath.Dir(binary), "models.json"), envMap["OPENCODE_MODELS_PATH"])
	require.Equal(t, "1", envMap["OPENCODE_DISABLE_AUTOUPDATE"])
	require.Equal(t, "1", envMap["OPENCODE_DISABLE_CLAUDE_CODE_PROMPT"])
	require.Equal(t, "1", envMap["OPENCODE_DISABLE_LSP_DOWNLOAD"])
	require.Equal(t, "1", envMap["OPENCODE_DISABLE_MODELS_FETCH"])
	require.Empty(t, envMap["OPENCODE_DISABLE_PROJECT_CONFIG"])
	require.Empty(t, envMap["OPENCODE_DISABLE_DEFAULT_PLUGINS"])
	require.Empty(t, envMap["OPENCODE_DISABLE_EXTERNAL_SKILLS"])
	require.Empty(t, envMap["OPENCODE_DISABLE_CLAUDE_CODE_SKILLS"])
	require.Empty(t, envMap["OPENCODE_TEST_HOME"])
	require.Empty(t, envMap["XDG_CONFIG_HOME"])
	storageRoot := filepath.Join(runtimeStateDir, "opencode")
	require.Equal(t, []string{filepath.Join(storageRoot, "data")}, openCodeTestEnvValuesEqualFold(env, "XDG_DATA_HOME"))
	require.Equal(t, []string{filepath.Join(storageRoot, "state")}, openCodeTestEnvValuesEqualFold(env, "XDG_STATE_HOME"))
	require.Equal(t, []string{filepath.Join(storageRoot, "cache")}, openCodeTestEnvValuesEqualFold(env, "XDG_CACHE_HOME"))
	require.Equal(t, []string{"1"}, openCodeTestEnvValuesEqualFold(env, "OPENCODE_DISABLE_AUTOUPDATE"))
	require.Equal(t, []string{filepath.Join(filepath.Dir(binary), "models.json")}, openCodeTestEnvValuesEqualFold(env, "OPENCODE_MODELS_PATH"))
	require.Equal(t, []string{runtimeconfig.ManagedOpenCodeConfigDir(workDir)}, openCodeTestEnvValuesEqualFold(env, "OPENCODE_CONFIG_DIR"))
	require.Equal(t, []string{"pw-123"}, openCodeTestEnvValuesEqualFold(env, "OPENCODE_SERVER_PASSWORD"))
	require.Empty(t, openCodeTestEnvValuesEqualFold(env, openCodeDatabaseEnv))
	require.Empty(t, openCodeTestEnvValuesEqualFold(env, openCodeTestHomeEnv))
	for _, key := range []string{"XDG_DATA_HOME", "XDG_STATE_HOME", "XDG_CACHE_HOME"} {
		require.DirExists(t, envMap[key])
	}
}

func TestBuildOpenCodeServeEnv_BuiltinStorageFollowsAgentAcrossLaunchWorkDirs(t *testing.T) {
	agentRoot := filepath.Join(t.TempDir(), "agents", "agt_stable")
	runtimeStateDir := filepath.Join(agentRoot, ".runtime")
	workDirA := filepath.Join(agentRoot, "sessions", "ephemeral_one")
	workDirB := filepath.Join(agentRoot, "sessions", "ephemeral_two")
	binary := filepath.Join(t.TempDir(), "opencode")

	build := func(agentID, workDir, stateDir string) map[string]string {
		t.Helper()
		env, err := buildOpenCodeServeEnv(drivercontract.LaunchConfig{
			AgentID:         agentID,
			RuntimeType:     runtimeconfig.TypeBuiltin,
			WorkDir:         workDir,
			RuntimeStateDir: stateDir,
			Binary:          binary,
		}, "pw-123")
		require.NoError(t, err)
		return openCodeTestEnvMap(env)
	}

	first := build("agt_stable", workDirA, runtimeStateDir)
	require.NoError(t, os.RemoveAll(workDirA), "session cleanup may remove only the launch workdir")
	require.DirExists(t, first["XDG_DATA_HOME"])
	second := build("agt_stable", workDirB, runtimeStateDir)
	require.Equal(t, first["XDG_DATA_HOME"], second["XDG_DATA_HOME"])
	require.Equal(t, first["XDG_STATE_HOME"], second["XDG_STATE_HOME"])
	require.Equal(t, first["XDG_CACHE_HOME"], second["XDG_CACHE_HOME"])

	otherAgentRoot := filepath.Join(t.TempDir(), "agents", "agt_other")
	other := build("agt_other", filepath.Join(otherAgentRoot, "sessions", "ephemeral"), filepath.Join(otherAgentRoot, ".runtime"))
	require.NotEqual(t, first["XDG_DATA_HOME"], other["XDG_DATA_HOME"])

	require.NoError(t, os.RemoveAll(agentRoot), "agent-root cleanup owns runtime storage lifecycle")
	require.NoDirExists(t, first["XDG_DATA_HOME"])
}

func TestBuildOpenCodeServeEnv_BuiltinRequiresExplicitRuntimeStateDir(t *testing.T) {
	workDir := t.TempDir()
	_, err := buildOpenCodeServeEnv(drivercontract.LaunchConfig{
		AgentID:     "agt_missing_state",
		RuntimeType: runtimeconfig.TypeBuiltin,
		WorkDir:     workDir,
		Binary:      filepath.Join(t.TempDir(), "opencode"),
	}, "pw-123")
	require.ErrorContains(t, err, "runtime state directory")
	require.NoDirExists(t, filepath.Join(workDir, ".runtime", "opencode"))
}

func TestBuildOpenCodeServeEnv_DoesNotSetBundledModelsForExternalOpenCode(t *testing.T) {
	t.Parallel()

	workDir := t.TempDir()
	binary := filepath.Join(t.TempDir(), "opencode")
	env, err := buildOpenCodeServeEnv(drivercontract.LaunchConfig{
		RuntimeType: runtimeconfig.TypeOpenCode,
		WorkDir:     workDir,
		Binary:      binary,
		Env: map[string]string{
			"xdg_data_home":      "/external/data",
			"Xdg_State_Home":     "/external/state",
			"XDG_cache_HOME":     "/external/cache",
			"OpEnCoDe_Db":        "/external/opencode.db",
			"OpEnCoDe_Test_Home": "/external/test-home",
		},
	}, "pw-123")
	require.NoError(t, err)
	envMap := openCodeTestEnvMap(env)

	require.Equal(t, filepath.Join(workDir, ".opencode"), envMap["OPENCODE_CONFIG_DIR"])
	require.Empty(t, envMap["OPENCODE_MODELS_PATH"])
	require.Empty(t, envMap["OPENCODE_DISABLE_PROJECT_CONFIG"])
	require.Empty(t, envMap["OPENCODE_DISABLE_DEFAULT_PLUGINS"])
	require.Empty(t, envMap["OPENCODE_DISABLE_EXTERNAL_SKILLS"])
	require.Empty(t, envMap["OPENCODE_DISABLE_CLAUDE_CODE_SKILLS"])
	require.Empty(t, envMap["OPENCODE_TEST_HOME"])
	require.Empty(t, envMap["XDG_CONFIG_HOME"])
	require.Contains(t, openCodeTestEnvValuesEqualFold(env, "XDG_DATA_HOME"), "/external/data")
	require.Contains(t, openCodeTestEnvValuesEqualFold(env, "XDG_STATE_HOME"), "/external/state")
	require.Contains(t, openCodeTestEnvValuesEqualFold(env, "XDG_CACHE_HOME"), "/external/cache")
	require.Contains(t, openCodeTestEnvValuesEqualFold(env, openCodeDatabaseEnv), "/external/opencode.db")
	require.Contains(t, openCodeTestEnvValuesEqualFold(env, openCodeTestHomeEnv), "/external/test-home")
}

func TestOpenCodeLaunchDiagnosticsSanitizesSecretsAndURLs(t *testing.T) {
	t.Parallel()

	modelsPath := filepath.Join(t.TempDir(), "models.json")
	require.NoError(t, os.WriteFile(modelsPath, []byte("{}"), 0o644))
	diagnostics := openCodeLaunchDiagnostics(drivercontract.LaunchConfig{
		RuntimeType: runtimeconfig.TypeBuiltin,
		Binary:      filepath.Join(t.TempDir(), "opencode"),
		WorkDir:     t.TempDir(),
		Model:       "provider/model",
		Provider: &drivercontract.ProviderConfig{
			ID:      "provider",
			Name:    "Provider",
			BaseURL: "http://user:pass@10.1.2.3:8000/v1?token=secret#frag",
			APIKey:  "secret-key",
		},
	}, []string{
		"OPENCODE_CONFIG_DIR=" + filepath.Join(t.TempDir(), ".opencode"),
		"OPENCODE_MODELS_PATH=" + modelsPath,
		"OPENCODE_DISABLE_MODELS_FETCH=1",
		"OPENCODE_SERVER_PASSWORD=secret-password",
		`OPENCODE_CONFIG_CONTENT={"apiKey":"secret-key"}`,
	})

	attrs := diagnostics.logAttrs()
	joined := strings.Join(openCodeTestAttrStrings(attrs), "\n")
	require.Contains(t, joined, "provider_base_url=http://10.1.2.3:8000/v1")
	require.Contains(t, joined, "serve_log_path="+filepath.Join(diagnostics.workDir, "diagnostics", "opencode-serve.log"))
	require.Equal(t, "<invalid-url>", sanitizeOpenCodeLogURL("http://%zz?token=secret"))
	require.NotContains(t, joined, "secret-key")
	require.NotContains(t, joined, "secret-password")
	require.NotContains(t, joined, "token=secret")
	require.True(t, diagnostics.modelsExists)
	require.Equal(t, int64(2), diagnostics.modelsSize)
}

func TestOpenCodeHealthCheckErrorScrubsCapturedOutput(t *testing.T) {
	t.Parallel()

	err := openCodeHealthCheckError("timed out", "Get http://127.0.0.1", func() string {
		return "OPENCODE_SERVER_PASSWORD=secret Authorization=Bearer abc apiKey=secret X-API-Key=secret token=secret AGENTTEAM_EXEC_TOKEN_FILE=/private/exec-token"
	}, func() string {
		return "OPENCODE_CONFIG_CONTENT={secret}"
	})

	message := err.Error()
	require.Contains(t, message, "OPENCODE_SERVER_PASSWORD=<redacted>")
	require.Contains(t, message, "Authorization=<redacted>")
	require.Contains(t, message, "apiKey=<redacted>")
	require.Contains(t, message, "X-API-Key=<redacted>")
	require.Contains(t, message, "token=<redacted>")
	require.Contains(t, message, "AGENTTEAM_EXEC_TOKEN_FILE=<redacted>")
	require.NotContains(t, message, "/private/exec-token")
	require.Contains(t, message, "OPENCODE_CONFIG_CONTENT=<redacted>")
	require.NotContains(t, message, "Bearer abc")
	require.NotContains(t, message, "={secret}")
}

func TestWaitForHealthyIncludesUnhealthyResponseBody(t *testing.T) {
	t.Parallel()

	requestSeen := make(chan struct{})
	closeRequestSeen := sync.OnceFunc(func() { close(requestSeen) })
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/global/health" {
			http.NotFound(w, r)
			return
		}
		http.Error(w, "warming up provider", http.StatusServiceUnavailable)
		closeRequestSeen()
	}))
	defer server.Close()

	d := &OpenCodeDriver{serverURL: server.URL, password: "pw", httpClient: &http.Client{Timeout: 120 * time.Second}}
	process := &openCodeServeProcess{exited: make(chan struct{})}
	go func() {
		<-requestSeen
		close(process.exited)
	}()

	err := d.waitForHealthy(context.Background(), "agt_test", process, nil, nil, openCodeLaunchDiagnostic{})
	require.Error(t, err)
	require.Contains(t, err.Error(), "status 503: warming up provider")
}

func TestWaitForHealthyDoesNotBlockOnHungHealthHandler(t *testing.T) {
	t.Parallel()

	requestCanceled := make(chan struct{})
	closeRequestCanceled := sync.OnceFunc(func() { close(requestCanceled) })
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path != "/global/health" {
			http.NotFound(w, r)
			return
		}
		<-r.Context().Done()
		closeRequestCanceled()
	}))
	defer server.Close()

	d := &OpenCodeDriver{serverURL: server.URL, password: "pw", httpClient: &http.Client{Timeout: 120 * time.Second}}
	process := &openCodeServeProcess{exited: make(chan struct{})}
	go func() {
		<-requestCanceled
		close(process.exited)
	}()

	started := time.Now()
	err := d.waitForHealthy(context.Background(), "agt_test", process, nil, nil, openCodeLaunchDiagnostic{})
	require.Error(t, err)
	require.Contains(t, err.Error(), "serve process exited before health check passed")
	// The production health timeout is 60s. The point of this test is that a
	// hung health handler does not block until that timeout when the serve
	// process exits. Use a generous ceiling well under 60s so CI scheduling
	// noise (t.Parallel) never trips the assertion.
	require.Less(t, time.Since(started), 30*time.Second)
}

func openCodeTestAttrStrings(attrs []any) []string {
	result := make([]string, 0, len(attrs)/2)
	for i := 0; i+1 < len(attrs); i += 2 {
		result = append(result, fmt.Sprintf("%v=%v", attrs[i], attrs[i+1]))
	}
	return result
}

func openCodeTestEnvMap(env []string) map[string]string {
	result := make(map[string]string, len(env))
	for _, entry := range env {
		key, value, ok := strings.Cut(entry, "=")
		if ok {
			result[key] = value
		}
	}
	return result
}

func openCodeTestEnvValuesEqualFold(env []string, wantKey string) []string {
	var values []string
	for _, entry := range env {
		key, value, ok := strings.Cut(entry, "=")
		if ok && strings.EqualFold(key, wantKey) {
			values = append(values, value)
		}
	}
	return values
}

func TestEnsureOpenCodeConfigDir_CreatesAgentPrivateConfigDir(t *testing.T) {
	t.Parallel()

	workDir := t.TempDir()
	configDir, err := ensureOpenCodeConfigDir(runtimeconfig.TypeOpenCode, workDir)
	require.NoError(t, err)
	require.Equal(t, filepath.Join(workDir, ".opencode"), configDir)
	info, statErr := os.Stat(configDir)
	require.NoError(t, statErr)
	require.True(t, info.IsDir())
}

func TestEnsureOpenCodeConfigDir_CreatesBuiltinManagedConfigDir(t *testing.T) {
	t.Parallel()

	workDir := t.TempDir()
	configDir, err := ensureOpenCodeConfigDir(runtimeconfig.TypeBuiltin, workDir)
	require.NoError(t, err)
	require.Equal(t, runtimeconfig.ManagedOpenCodeConfigDir(workDir), configDir)
	info, statErr := os.Stat(configDir)
	require.NoError(t, statErr)
	require.True(t, info.IsDir())
}

func TestBuildOpenCodeServeConfigContent_MergesNativeConfigAndAppliesModelOverride(t *testing.T) {
	homeDir := t.TempDir()
	t.Setenv("HOME", homeDir)
	configDir := filepath.Join(homeDir, ".config", "opencode")
	require.NoError(t, os.MkdirAll(configDir, 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(configDir, "opencode.json"), []byte(`{
  "model": "agentteam/gpt-5.4",
  "provider": {
    "agentteam": {
      "name": "AgentTeam Native",
      "options": {
        "baseURL": "https://proxy.example.com/v1",
        "apiKey": "sk-native"
      },
      "models": {
        "gpt-5.4": {}
      }
    }
  }
}`), 0o644))

	content, err := buildOpenCodeServeConfigContent(drivercontract.LaunchConfig{
		Model: "gpt-5.5",
	})
	require.NoError(t, err)

	var config openCodeServeConfig
	require.NoError(t, json.Unmarshal([]byte(content), &config))
	require.Equal(t, "agentteam/gpt-5.5", config.Model)
	require.Contains(t, config.Provider, "agentteam")
	require.Equal(t, "https://proxy.example.com/v1", config.Provider["agentteam"].Options.BaseURL)
	require.Equal(t, "sk-native", config.Provider["agentteam"].Options.APIKey)
	require.Contains(t, config.Provider["agentteam"].Models, "gpt-5.5")
}

func TestBuildOpenCodeServeConfigContent_IsolatesBuiltinProviderFromNativeConfig(t *testing.T) {
	homeDir := t.TempDir()
	t.Setenv("HOME", homeDir)
	configDir := filepath.Join(homeDir, ".config", "opencode")
	require.NoError(t, os.MkdirAll(configDir, 0o755))
	require.NoError(t, os.WriteFile(filepath.Join(configDir, "opencode.json"), []byte(`{
  "model": "deepseek/deepseek-v4-pro",
  "small_model": "deepseek/deepseek-v4-pro",
  "provider": {
    "deepseek": {
      "name": "DeepSeek Native",
      "options": {
        "baseURL": "https://api.deepseek.com",
        "apiKey": "sk-native"
      },
      "models": {
        "deepseek-v4-pro": {}
      }
    }
  }
}`), 0o644))

	content, err := buildOpenCodeServeConfigContent(drivercontract.LaunchConfig{
		RuntimeType: runtimeconfig.TypeBuiltin,
		Model:       "qwen2.5:0.5b",
		Provider: &drivercontract.ProviderConfig{
			ID:       "agentteam",
			Name:     "AgentTeam Managed",
			BaseURL:  "http://127.0.0.1:11434/v1",
			APIKey:   "ollama",
			WireAPIs: []string{"responses"},
		},
	})
	require.NoError(t, err)

	var config openCodeServeConfig
	require.NoError(t, json.Unmarshal([]byte(content), &config))
	require.Equal(t, "agentteam/qwen2.5:0.5b", config.Model)
	require.Empty(t, config.SmallModel)
	require.NotContains(t, config.Provider, "deepseek")
	require.Contains(t, config.Provider, "agentteam")
	require.Equal(t, "http://127.0.0.1:11434/v1", config.Provider["agentteam"].Options.BaseURL)
	require.Equal(t, "ollama", config.Provider["agentteam"].Options.APIKey)
	require.Contains(t, config.Provider["agentteam"].Models, "qwen2.5:0.5b")
}

func TestBuildOpenCodeServeConfigContentPermissionMode(t *testing.T) {
	fullAccessContent, err := buildOpenCodeServeConfigContent(drivercontract.LaunchConfig{
		PermissionMode: types.AgentPermissionModeFullAccess,
	})
	require.NoError(t, err)
	var fullAccessConfig openCodeServeConfig
	require.NoError(t, json.Unmarshal([]byte(fullAccessContent), &fullAccessConfig))
	require.NotNil(t, fullAccessConfig.Permission)
	require.Equal(t, "allow", fullAccessConfig.Permission.All)
}

func TestBuildOpenCodeServeConfigContentRejectsRemovedKnowledgeQAMode(t *testing.T) {
	content, err := buildOpenCodeServeConfigContent(drivercontract.LaunchConfig{
		PermissionMode: "knowledge_qa_scratch",
	})
	require.Empty(t, content)
	require.ErrorContains(t, err, `unsupported agent permission mode "knowledge_qa_scratch"`)
}

func TestBuildOpenCodeServeConfigContentDisablesNativeTools(t *testing.T) {
	content, err := buildOpenCodeServeConfigContent(drivercontract.LaunchConfig{
		RuntimeType:        runtimeconfig.TypeOpenCode,
		DisableNativeTools: true,
		MCPs: []types.NamedMCPConfig{{
			Name:      "knowledge",
			Transport: types.MCPTransportHTTP,
			URL:       "http://127.0.0.1/knowledge",
		}},
	})
	require.NoError(t, err)

	var config openCodeServeConfig
	require.NoError(t, json.Unmarshal([]byte(content), &config))
	require.NotNil(t, config.Permission)
	require.Equal(t, "deny", config.Permission.All)
	require.Equal(t, "allow", config.Permission.Knowledge)
	require.Len(t, config.MCP, 1)
	require.Contains(t, config.MCP, "knowledge")
}

func TestBuildOpenCodeServeConfigContentSupportsHTTPAndStdioMCPServers(t *testing.T) {
	content, err := buildOpenCodeServeConfigContent(drivercontract.LaunchConfig{MCPs: []types.NamedMCPConfig{
		{Name: "zap_http", Transport: types.MCPTransportHTTP, URL: "https://mcp.example.com", Headers: map[string]string{"Authorization": "Bearer token"}},
		{Name: "zap_stdio", Transport: types.MCPTransportStdio, Command: "/bin/zap", Args: []string{"--flag"}, Env: map[string]string{"API_KEY": "secret"}, CWD: "/tmp"},
	}})
	require.NoError(t, err)

	var config openCodeServeConfig
	require.NoError(t, json.Unmarshal([]byte(content), &config))
	require.Equal(t, "remote", config.MCP["zap_http"].Type)
	require.Equal(t, "https://mcp.example.com", config.MCP["zap_http"].URL)
	require.Equal(t, map[string]string{"Authorization": "Bearer token"}, config.MCP["zap_http"].Headers)
	require.Equal(t, "local", config.MCP["zap_stdio"].Type)
	require.Equal(t, []string{"/bin/zap", "--flag"}, config.MCP["zap_stdio"].Command)
	require.Equal(t, map[string]string{"API_KEY": "secret"}, config.MCP["zap_stdio"].Environment)
	require.Equal(t, "/tmp", config.MCP["zap_stdio"].CWD)
}

// OpenCode connects directly to the configured MCP server; AgentTeam does not parse
// or transform the server's tools/list schemas on this path.
func TestBuildOpenCodeServeConfigContentWiresGenericApprovalServer(t *testing.T) {
	content, err := buildOpenCodeServeConfigContent(drivercontract.LaunchConfig{MCPs: []types.NamedMCPConfig{{
		Name: "agentteam", Transport: types.MCPTransportHTTP, URL: "https://agentteam.example.com/mcp",
	}}})
	require.NoError(t, err)
	var config openCodeServeConfig
	require.NoError(t, json.Unmarshal([]byte(content), &config))
	require.Equal(t, "remote", config.MCP["agentteam"].Type)
	require.Equal(t, "https://agentteam.example.com/mcp", config.MCP["agentteam"].URL)
}
