package main

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/http/httptest"
	"os"
	"os/exec"
	"path/filepath"
	"strings"
	"testing"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/stretchr/testify/require"

	"github.com/tidbcloud/AgentTeam/desktop/internal/agentview"
	agentteamexec "github.com/tidbcloud/AgentTeam/desktop/internal/api/exec"
	"github.com/tidbcloud/AgentTeam/desktop/internal/token"
	"github.com/tidbcloud/AgentTeam/desktop/internal/wire/apiclient"
)

type arbitraryExitCodeError struct{}

func (arbitraryExitCodeError) Error() string { return "arbitrary exit code" }
func (arbitraryExitCodeError) ExitCode() int { return 42 }

func TestAgentTeamProcessUsesExecErrorExitCodes(t *testing.T) {
	binary := filepath.Join(t.TempDir(), "agentteam")
	build := exec.Command("go", "build", "-o", binary, ".")
	if output, err := build.CombinedOutput(); err != nil {
		t.Fatalf("build agentteam: %v\n%s", err, output)
	}

	catalog := execRuntimeCatalogJSON(t)
	helpServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodGet || !strings.HasSuffix(r.URL.Path, "/catalog") {
			http.NotFound(w, r)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write(catalog)
	}))
	defer helpServer.Close()
	helpTokenFile := writeExecTokenFile(t, "help-token")
	help := exec.Command(binary, "exec", "--help")
	help.Env = agentteamExecProcessEnv(
		agentteamexec.EnvBaseURL+"="+helpServer.URL+"/api/internal/exec/servers/server-1/agents/agent-1",
		agentteamexec.EnvTokenFile+"="+helpTokenFile,
	)
	helpOutput, err := help.CombinedOutput()
	if err != nil {
		t.Fatalf("agentteam exec help with a runtime catalog: %v\n%s", err, helpOutput)
	}
	if !strings.Contains(string(helpOutput), "Usage: agentteam exec <domain> <action>") {
		t.Fatalf("agentteam exec help missing command usage\n%s", helpOutput)
	}

	tests := []struct {
		name       string
		args       []string
		statusCode int
		errorCode  string
		want       int
	}{
		{
			name: "validation", args: []string{"exec", "workspace", "list", `{"unknown":true}`},
			want: 64,
		},
		{
			name: "not found", args: []string{"exec", "workspace", "missing", `{}`},
			want: 2,
		},
		{
			name: "permission denied", args: []string{"exec", "workspace", "list", `{}`},
			statusCode: http.StatusForbidden, errorCode: "permission_denied", want: 77,
		},
		{
			name: "conflict", args: []string{"exec", "workspace", "list", `{}`},
			statusCode: http.StatusConflict, errorCode: "conflict", want: 1,
		},
		{
			name: "internal", args: []string{"exec", "workspace", "list", `{}`},
			statusCode: http.StatusInternalServerError, errorCode: "internal", want: 70,
		},
		{
			name: "ordinary cobra error", args: []string{"unknown-command"},
			want: 1,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
				w.Header().Set("Content-Type", "application/json")
				if r.Method == http.MethodGet && strings.HasSuffix(r.URL.Path, "/catalog") {
					_, _ = w.Write(catalog)
					return
				}
				status := test.statusCode
				if status == 0 {
					status = http.StatusOK
				}
				w.WriteHeader(status)
				if test.errorCode != "" {
					_ = json.NewEncoder(w).Encode(map[string]any{
						"error": map[string]string{"code": test.errorCode, "problem": "test failure"},
					})
					return
				}
				_, _ = w.Write([]byte(`{"data":{},"text":"ok"}`))
			}))
			defer server.Close()

			tokenFile := writeExecTokenFile(t, "test-token")
			command := exec.Command(binary, test.args...)
			command.Env = agentteamExecProcessEnv(
				agentteamexec.EnvBaseURL+"="+server.URL+"/api/internal/exec/servers/server-1/agents/agent-1",
				agentteamexec.EnvTokenFile+"="+tokenFile,
			)
			output, err := command.CombinedOutput()
			if err == nil {
				t.Fatalf("agentteam succeeded, want exit %d\n%s", test.want, output)
			}
			exitErr, ok := err.(*exec.ExitError)
			if !ok {
				t.Fatalf("agentteam error = %T %v, want *exec.ExitError", err, err)
			}
			if got := exitErr.ExitCode(); got != test.want {
				t.Fatalf("exit code = %d, want %d\n%s", got, test.want, output)
			}
		})
	}
}

func agentteamExecProcessEnv(overrides ...string) []string {
	env := os.Environ()
	filtered := env[:0]
	for _, entry := range env {
		if strings.HasPrefix(entry, agentteamexec.EnvBaseURL+"=") ||
			strings.HasPrefix(entry, "AGENTTEAM_EXEC_TOKEN=") ||
			strings.HasPrefix(entry, agentteamexec.EnvTokenFile+"=") {
			continue
		}
		filtered = append(filtered, entry)
	}
	return append(filtered, overrides...)
}

func execRuntimeCatalogJSON(t *testing.T) []byte {
	t.Helper()
	gin.SetMode(gin.TestMode)
	manager, err := token.NewKeyManager(filepath.Join(t.TempDir(), "config.yaml"))
	if err != nil {
		t.Fatalf("create Exec token manager: %v", err)
	}
	authorizations := agentteamexec.NewRuntimeAuthorizationStore(manager)
	credential, err := authorizations.Create(agentteamexec.RuntimeAuthorizationConfig{
		ServerID:  "server-1",
		AgentID:   "agent-1",
		SessionID: "session-1",
		RunID:     "run-1",
		WorkDir:   t.TempDir(),
		Server:    &apiclient.Client{},
	})
	if err != nil {
		t.Fatalf("create Exec runtime authorization: %v", err)
	}
	router := gin.New()
	agentteamexec.RegisterRoutes(
		router.Group("/api/internal/exec"),
		agentteamexec.RouteDependencies{Authorizations: authorizations},
	)
	request := httptest.NewRequest(
		http.MethodGet,
		"/api/internal/exec/servers/server-1/agents/agent-1/catalog",
		nil,
	)
	request.Header.Set("Authorization", "Bearer "+credential.Token)
	response := httptest.NewRecorder()
	router.ServeHTTP(response, request)
	if response.Code != http.StatusOK {
		t.Fatalf("fetch Exec Runtime Catalog: status %d: %s", response.Code, response.Body.String())
	}
	return append([]byte(nil), response.Body.Bytes()...)
}

func writeExecTokenFile(t *testing.T, value string) string {
	t.Helper()
	path := filepath.Join(t.TempDir(), "exec-token")
	if err := os.WriteFile(path, []byte(value+"\n"), 0o600); err != nil {
		t.Fatalf("write Exec token file: %v", err)
	}
	return path
}

func TestProcessExitCodeDoesNotTrustArbitraryExitCodeErrors(t *testing.T) {
	subprocessErr := exec.Command("sh", "-c", "exit 23").Run()
	if subprocessErr == nil {
		t.Fatal("subprocess succeeded")
	}
	if got := processExitCode(subprocessErr); got != 1 {
		t.Fatalf("processExitCode(subprocess error) = %d, want 1", got)
	}
	if got := processExitCode(arbitraryExitCodeError{}); got != 1 {
		t.Fatalf("processExitCode(custom exit error) = %d, want 1", got)
	}
	if got := processExitCode(fmt.Errorf("wrapped: %w", arbitraryExitCodeError{})); got != 1 {
		t.Fatalf("processExitCode(wrapped custom exit error) = %d, want 1", got)
	}
	if got := processExitCode(errors.New("ordinary error")); got != 1 {
		t.Fatalf("processExitCode(ordinary error) = %d, want 1", got)
	}
}

type fakeAgentReader struct {
	snapshots []agentview.Snapshot
}

func (fake fakeAgentReader) ListAgents(context.Context) ([]agentview.Snapshot, error) {
	return append([]agentview.Snapshot(nil), fake.snapshots...), nil
}

func (fake fakeAgentReader) GetAgent(_ context.Context, id string) (agentview.Snapshot, bool, error) {
	for _, snapshot := range fake.snapshots {
		if snapshot.ID == id {
			return snapshot, true, nil
		}
	}
	return agentview.Snapshot{}, false, nil
}

// TestAgentTeamExecDiscoveryAndInvocationSurviveIsolatedBadExtension proves the real
// HTTP/CLI chain: GET /catalog returns 200 with the bad action absent and
// built-ins present, the real agentteam binary discovers then POSTs `agent list` and
// `workflow run show` successfully, and the isolated bad action is not callable.
func TestAgentTeamExecDiscoveryAndInvocationSurviveIsolatedBadExtension(t *testing.T) {
	binary := filepath.Join(t.TempDir(), "agentteam")
	build := exec.Command("go", "build", "-o", binary, ".")
	if output, err := build.CombinedOutput(); err != nil {
		t.Fatalf("build agentteam: %v\n%s", err, output)
	}

	gin.SetMode(gin.TestMode)
	// Mock upstream serving the agent-runtime workflow run endpoint used by the
	// builtin `workflow run show` invoker.
	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		if r.Method != http.MethodPost || !strings.Contains(r.URL.Path, "/workflow/run") {
			http.NotFound(w, r)
			return
		}
		w.Header().Set("Content-Type", "application/json")
		_, _ = w.Write([]byte(`{"run":{"id":"wri_1","planId":"pln_1","planVersionId":"plv_1","status":"waiting","queuedAt":"2026-08-17T00:00:00Z"}}`))
	}))
	defer upstream.Close()

	// Build a RuntimeCatalog with one isolated bad action and one good
	// extension action.
	builtin, err := agentteamexec.DefaultCatalog()
	require.NoError(t, err)
	goodAction := agentteamexec.ExtensionAction{
		ExtensionName: "app", DisplayName: "App",
		Name: "good", Summary: "Good", Safety: agentteamexec.ExtensionSafety{Class: "read"},
		InputSchema:  map[string]any{"type": "object"},
		OutputSchema: map[string]any{"type": "object"},
	}
	badAction := agentteamexec.ExtensionAction{
		ExtensionName: "app", DisplayName: "App",
		Name: "bad", Summary: "Bad", Safety: agentteamexec.ExtensionSafety{Class: "read"},
		InputSchema:  map[string]any{"type": "object"},
		OutputSchema: map[string]any{"type": "object", "properties": map[string]any{"x": map[string]any{"$ref": "#/$defs/missingDef"}}},
	}
	domains := []agentteamexec.ExtensionDomain{{
		ExtensionName: "app", DisplayName: "App",
		Actions: []agentteamexec.ExtensionAction{goodAction, badAction},
	}}
	snapshot := agentteamexec.NewExtensionSnapshot(`"ext-v1-test"`, domains)
	runtimeCatalog, err := agentteamexec.NewRuntimeCatalog(builtin, snapshot)
	require.NoError(t, err)
	require.NotEmpty(t, runtimeCatalog.Diagnostics())

	manager, err := token.NewKeyManager(filepath.Join(t.TempDir(), "config.yaml"))
	require.NoError(t, err)
	authorizations := agentteamexec.NewRuntimeAuthorizationStore(manager)
	credential, err := authorizations.Create(agentteamexec.RuntimeAuthorizationConfig{
		ServerID: "server-1", AgentID: "agent-1", SessionID: "session-1", RunID: "run-1",
		WorkDir:           t.TempDir(),
		ProcessGeneration: "gen-1",
		RuntimeCatalog:    runtimeCatalog,
		Server: &apiclient.Client{
			ServerURL:        upstream.URL,
			AgentID:          "agent-1",
			LocalAgentID:     "agent-1",
			AuthToken:        "agent-token",
			RouteWorkspaceID: "workspace-1",
			HTTPClient:       upstream.Client(),
		},
		TokenTTL: time.Hour,
	})
	require.NoError(t, err)
	router := gin.New()
	agentteamexec.RegisterRoutes(
		router.Group("/api/internal/exec"),
		agentteamexec.RouteDependencies{
			Authorizations: authorizations,
			Agents: fakeAgentReader{snapshots: []agentview.Snapshot{{
				ID: "agt_1", Name: "Agent 1", Runtime: "claude", Status: "active",
			}}},
		},
	)
	server := httptest.NewServer(router)
	defer server.Close()

	// 1) GET /catalog directly: 200, bad action absent, built-in present.
	catalogReq := httptest.NewRequest(
		http.MethodGet,
		"/api/internal/exec/servers/server-1/agents/agent-1/catalog",
		nil,
	)
	catalogReq.Header.Set("Authorization", "Bearer "+credential.Token)
	catalogRec := httptest.NewRecorder()
	router.ServeHTTP(catalogRec, catalogReq)
	require.Equal(t, http.StatusOK, catalogRec.Code)
	catalogBody := catalogRec.Body.String()
	require.Contains(t, catalogBody, `"domain":"agent"`)
	require.Contains(t, catalogBody, `"name":"list"`)
	require.Contains(t, catalogBody, `"domain":"app"`)
	require.Contains(t, catalogBody, `"name":"good"`)
	require.NotContains(t, catalogBody, `"name":"bad"`)

	// 2) Real CLI discovery + invocation.
	baseURL := server.URL + "/api/internal/exec/servers/server-1/agents/agent-1"
	tokenFile := writeExecTokenFile(t, credential.Token)
	baseEnv := agentteamExecProcessEnv(
		agentteamexec.EnvBaseURL+"="+baseURL,
		agentteamexec.EnvTokenFile+"="+tokenFile,
	)

	agentList := exec.Command(binary, "exec", "agent", "list")
	agentList.Env = baseEnv
	agentListOut, err := agentList.CombinedOutput()
	require.NoError(t, err, "agentteam exec agent list failed: %s", agentListOut)
	require.Contains(t, string(agentListOut), "agt_1")

	runShow := exec.Command(binary, "exec", "workflow", "run", `{"action":"show","runId":"wri_1"}`)
	runShow.Env = baseEnv
	runShowOut, err := runShow.CombinedOutput()
	require.NoError(t, err, "agentteam exec workflow run show failed: %s", runShowOut)
	require.Contains(t, string(runShowOut), "wri_1")

	// 3) The isolated bad action is not callable: discovery rejects it before
	// any POST, so the CLI fails with a nonzero exit.
	badActionRun := exec.Command(binary, "exec", "app", "bad", `{}`)
	badActionRun.Env = baseEnv
	badActionErr := badActionRun.Run()
	require.Error(t, badActionErr, "isolated bad action must not be callable")
}

func TestAgentTeamProcessWorkflowServerFailureEmitsOnlyStructuredStderrJSON(t *testing.T) {
	binary := filepath.Join(t.TempDir(), "agentteam")
	build := exec.Command("go", "build", "-o", binary, ".")
	if output, err := build.CombinedOutput(); err != nil {
		t.Fatalf("build agentteam: %v\n%s", err, output)
	}
	catalog := execRuntimeCatalogJSON(t)

	index := 2
	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		if r.Method == http.MethodGet && strings.HasSuffix(r.URL.Path, "/catalog") {
			_, _ = w.Write(catalog)
			return
		}
		w.WriteHeader(http.StatusBadRequest)
		_ = json.NewEncoder(w).Encode(map[string]any{
			"error": map[string]any{
				"code":       "validation",
				"problem":    "workflow step complete failed",
				"stage":      "run",
				"path":       "callback.workItems[2].key",
				"hint":       "Fix the WorkItem key.",
				"serverCode": "step_work_items_invalid",
				"diagnostics": []map[string]any{{
					"severity": "error",
					"code":     "callback.work_items.key_invalid",
					"path":     "callback.workItems[2].key",
					"field":    "key",
					"index":    index,
					"message":  "callback.workItems[2].key must match ^[a-z][a-z0-9_-]{0,255}$",
				}},
			},
		})
	}))
	defer server.Close()

	tokenFile := writeExecTokenFile(t, "test-token")
	command := exec.Command(
		binary,
		"exec", "workflow", "step",
		`{"action":"complete","runId":"wri_1","stepId":"wfx_1","idempotencyKey":"k-1","output":{}}`,
	)
	command.Env = agentteamExecProcessEnv(
		agentteamexec.EnvBaseURL+"="+server.URL+"/api/internal/exec/servers/server-1/agents/agent-1",
		agentteamexec.EnvTokenFile+"="+tokenFile,
	)
	output, err := command.CombinedOutput()
	if err == nil {
		t.Fatalf("agentteam succeeded, want workflow step failure\n%s", output)
	}
	exitErr, ok := err.(*exec.ExitError)
	if !ok {
		t.Fatalf("agentteam error = %T %v, want *exec.ExitError", err, err)
	}
	if got := exitErr.ExitCode(); got != 64 {
		t.Fatalf("exit code = %d, want 64 (validation)\n%s", got, output)
	}
	lines := strings.Split(strings.TrimSpace(string(output)), "\n")
	if len(lines) != 1 {
		t.Fatalf("stderr emitted %d lines, want exactly one structured JSON line\n%s", len(lines), output)
	}
	line := strings.TrimSpace(lines[0])
	if !strings.HasPrefix(line, "{") {
		t.Fatalf("stderr first line is not structured JSON: %q\n%s", line, output)
	}
	var envelope struct {
		Error struct {
			Code        string `json:"code"`
			ServerCode  string `json:"serverCode"`
			Stage       string `json:"stage"`
			Path        string `json:"path"`
			Hint        string `json:"hint"`
			Diagnostics []struct {
				Code  string `json:"code"`
				Field string `json:"field"`
				Index *int   `json:"index"`
			} `json:"diagnostics"`
		} `json:"error"`
	}
	if err := json.Unmarshal([]byte(line), &envelope); err != nil {
		t.Fatalf("stderr is not valid JSON: %v\n%s", err, output)
	}
	if envelope.Error.Code != "validation" || envelope.Error.ServerCode != "step_work_items_invalid" ||
		envelope.Error.Stage != "run" || envelope.Error.Path != "callback.workItems[2].key" {
		t.Fatalf("envelope lost allowlisted contract: %+v\n%s", envelope.Error, output)
	}
	if len(envelope.Error.Diagnostics) != 1 || envelope.Error.Diagnostics[0].Field != "key" {
		t.Fatalf("envelope lost diagnostics: %+v\n%s", envelope.Error.Diagnostics, output)
	}
	requireIndex := envelope.Error.Diagnostics[0].Index
	if requireIndex == nil || *requireIndex != 2 {
		t.Fatalf("diagnostic index lost: %v\n%s", requireIndex, output)
	}
	if !strings.Contains(line, `"code":"validation"`) {
		t.Fatalf("stderr missing stable error code\n%s", output)
	}
}

// TestAgentTeamProcessLocalFailureEmitsOnlyStructuredStderrJSON verifies a
// client-side validation failure (no server call) also renders exactly one
// stable structured stderr JSON line with the correct nonzero exit category,
// never a plain-text error line.
func TestAgentTeamProcessLocalFailureEmitsOnlyStructuredStderrJSON(t *testing.T) {
	binary := filepath.Join(t.TempDir(), "agentteam")
	build := exec.Command("go", "build", "-o", binary, ".")
	if output, err := build.CombinedOutput(); err != nil {
		t.Fatalf("build agentteam: %v\n%s", err, output)
	}
	catalog := execRuntimeCatalogJSON(t)

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		if r.Method == http.MethodGet && strings.HasSuffix(r.URL.Path, "/catalog") {
			_, _ = w.Write(catalog)
			return
		}
		// The invalid input must be rejected locally, before any action call.
		t.Fatalf("unexpected action call for local validation failure: %s", r.URL.Path)
	}))
	defer server.Close()

	tokenFile := writeExecTokenFile(t, "test-token")
	command := exec.Command(binary, "exec", "workflow", "step", `{"action":"complete"}`)
	command.Env = agentteamExecProcessEnv(
		agentteamexec.EnvBaseURL+"="+server.URL+"/api/internal/exec/servers/server-1/agents/agent-1",
		agentteamexec.EnvTokenFile+"="+tokenFile,
	)
	output, err := command.CombinedOutput()
	if err == nil {
		t.Fatalf("agentteam succeeded, want local workflow validation failure\n%s", output)
	}
	exitErr, ok := err.(*exec.ExitError)
	if !ok {
		t.Fatalf("agentteam error = %T %v, want *exec.ExitError", err, err)
	}
	if got := exitErr.ExitCode(); got != 64 {
		t.Fatalf("exit code = %d, want 64 (validation)\n%s", got, output)
	}
	lines := strings.Split(strings.TrimSpace(string(output)), "\n")
	if len(lines) != 1 {
		t.Fatalf("stderr emitted %d lines, want exactly one structured JSON line\n%s", len(lines), output)
	}
	line := strings.TrimSpace(lines[0])
	if !strings.HasPrefix(line, "{") || !json.Valid([]byte(line)) {
		t.Fatalf("stderr is not one structured JSON line: %q\n%s", line, output)
	}
	if !strings.Contains(line, `"code":"validation"`) {
		t.Fatalf("stderr missing stable error code\n%s", output)
	}
}

// TestAgentTeamProcessTruncatedErrorBodyFailsClosed verifies that an oversized server
// error body (detected by limit+1 reading in the daemon apiclient) renders
// exactly one structured stderr JSON line with the internal category and the
// truncated flag, never a partial JSON collapse or a budget-only envelope.
func TestAgentTeamProcessTruncatedErrorBodyFailsClosed(t *testing.T) {
	binary := filepath.Join(t.TempDir(), "agentteam")
	build := exec.Command("go", "build", "-o", binary, ".")
	if output, err := build.CombinedOutput(); err != nil {
		t.Fatalf("build agentteam: %v\n%s", err, output)
	}
	catalog := execRuntimeCatalogJSON(t)

	upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Header().Set("Content-Type", "application/json")
		if r.Method == http.MethodGet && strings.HasSuffix(r.URL.Path, "/catalog") {
			_, _ = w.Write(catalog)
			return
		}
		w.WriteHeader(http.StatusBadGateway)
		_, _ = w.Write([]byte(`{"code":"internal_error","diagnostics":[{"message":"` + strings.Repeat("m", 96*1024) + `"}]}`))
	}))
	defer upstream.Close()

	gin.SetMode(gin.TestMode)
	keys, err := token.NewKeyManager(t.TempDir() + "/config.yaml")
	if err != nil {
		t.Fatal(err)
	}
	authorizations := agentteamexec.NewRuntimeAuthorizationStore(keys)
	lease, err := authorizations.Create(agentteamexec.RuntimeAuthorizationConfig{
		ServerID:  "server-1",
		AgentID:   "agent-1",
		SessionID: "session-1",
		RunID:     "run-1",
		WorkDir:   t.TempDir(),
		Server: &apiclient.Client{
			ServerURL:        upstream.URL,
			AgentID:          "server-agent-1",
			LocalAgentID:     "server-agent-1",
			AuthToken:        "server-agent-token",
			RouteWorkspaceID: "workspace-1",
			HTTPClient:       upstream.Client(),
		},
		TokenTTL: time.Hour,
	})
	if err != nil {
		t.Fatal(err)
	}
	router := gin.New()
	agentteamexec.RegisterRoutes(router.Group("/api/internal/exec"), agentteamexec.RouteDependencies{Authorizations: authorizations})
	daemon := httptest.NewServer(router)
	defer daemon.Close()

	tokenFile := writeExecTokenFile(t, lease.Token)
	command := exec.Command(
		binary,
		"exec", "workflow", "step",
		`{"action":"complete","runId":"wri_1","stepId":"wfx_1","idempotencyKey":"k-1","output":{}}`,
	)
	command.Env = agentteamExecProcessEnv(
		agentteamexec.EnvBaseURL+"="+daemon.URL+"/api/internal/exec/servers/server-1/agents/agent-1",
		agentteamexec.EnvTokenFile+"="+tokenFile,
	)
	output, err := command.CombinedOutput()
	if err == nil {
		t.Fatalf("agentteam succeeded, want truncated error body failure\n%s", output)
	}
	exitErr, ok := err.(*exec.ExitError)
	if !ok {
		t.Fatalf("agentteam error = %T %v, want *exec.ExitError", err, err)
	}
	if got := exitErr.ExitCode(); got != 70 {
		t.Fatalf("exit code = %d, want 70 (internal)\n%s", got, output)
	}
	lines := strings.Split(strings.TrimSpace(string(output)), "\n")
	if len(lines) != 1 {
		t.Fatalf("stderr emitted %d lines, want exactly one structured JSON line\n%s", len(lines), output)
	}
	line := strings.TrimSpace(lines[0])
	var envelope struct {
		Error struct {
			Code       string `json:"code"`
			Truncated  bool   `json:"truncated"`
			Truncation any    `json:"truncation"`
		} `json:"error"`
	}
	if err := json.Unmarshal([]byte(line), &envelope); err != nil {
		t.Fatalf("stderr is not valid JSON: %v\n%s", err, output)
	}
	if envelope.Error.Code != "internal" {
		t.Fatalf("Code = %q, want internal\n%s", envelope.Error.Code, output)
	}
	if !envelope.Error.Truncated {
		t.Fatalf("oversized error body must be flagged truncated\n%s", output)
	}
	if envelope.Error.Truncation != nil {
		t.Fatalf("client-side read truncation must not carry server truncation metadata\n%s", output)
	}
}
