From 0290962eb71f3d2477d13a850e1d6118613f7868 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 20 Mar 2026 18:20:44 +0100 Subject: [PATCH 1/2] Fix panic Signed-off-by: David Gageot --- cmd/root/run.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/cmd/root/run.go b/cmd/root/run.go index 55a59cdc0..6070b1c3f 100644 --- a/cmd/root/run.go +++ b/cmd/root/run.go @@ -416,7 +416,10 @@ func (f *runExecFlags) createLocalRuntimeAndSession(ctx context.Context, loadRes func (f *runExecFlags) handleExecMode(ctx context.Context, out *cli.Printer, rt runtime.Runtime, sess *session.Session, args []string) error { // args[0] is the agent file; args[1:] are user messages for multi-turn conversation - userMessages := args[1:] + var userMessages []string + if len(args) > 1 { + userMessages = args[1:] + } err := cli.Run(ctx, out, cli.Config{ AppName: AppName, From 6182de9f24f3602518ba9a51fd26bf6d3a7e19c5 Mon Sep 17 00:00:00 2001 From: David Gageot Date: Fri, 20 Mar 2026 18:30:04 +0100 Subject: [PATCH 2/2] Hide agent name header when stdout is not a TTY Skip printing the '--- Agent: root ---' header when output is piped, so that 'echo hello | docker agent | cat' produces clean output. Assisted-By: docker-agent --- e2e/exec_test.go | 22 +++++++--------------- pkg/cli/printer.go | 13 +++++++++++-- pkg/cli/runner.go | 12 ++++++++++++ 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/e2e/exec_test.go b/e2e/exec_test.go index 3358ec93d..15d226f7e 100644 --- a/e2e/exec_test.go +++ b/e2e/exec_test.go @@ -9,7 +9,7 @@ import ( func TestExec_OpenAI(t *testing.T) { out := runCLI(t, "run", "--exec", "testdata/basic.yaml", "What's 2+2?") - require.Equal(t, "\n--- Agent: root ---\n2 + 2 equals 4.", out) + require.Equal(t, "2 + 2 equals 4.", out) } // TestExec_OpenAI_V3Config tests that v3 configs work correctly with thinking disabled by default. @@ -18,7 +18,7 @@ func TestExec_OpenAI_V3Config(t *testing.T) { out := runCLI(t, "run", "--exec", "testdata/basic_v3.yaml", "What's 2+2?") // v3 config with gpt-5 should work correctly (thinking disabled by default for old configs) - require.Equal(t, "\n--- Agent: root ---\n4", out) + require.Equal(t, "4", out) } // TestExec_OpenAI_WithThinkingBudget tests that when thinking_budget is explicitly configured @@ -28,41 +28,38 @@ func TestExec_OpenAI_WithThinkingBudget(t *testing.T) { // With thinking_budget explicitly configured, response should include reasoning // The output format includes the reasoning summary when thinking is enabled - require.Contains(t, out, "--- Agent: root ---") require.Contains(t, out, "4") } func TestExec_OpenAI_ToolCall(t *testing.T) { out := runCLI(t, "run", "--exec", "testdata/fs_tools.yaml", "How many files in testdata/working_dir? Only output the number.") - require.Equal(t, "\n--- Agent: root ---\n\nCalling list_directory(path: \"testdata/working_dir\")\n\nlist_directory response → \"FILE README.me\\n\"\n1", out) + require.Equal(t, "\nCalling list_directory(path: \"testdata/working_dir\")\n\nlist_directory response → \"FILE README.me\\n\"\n1", out) } func TestExec_OpenAI_HideToolCalls(t *testing.T) { out := runCLI(t, "run", "--exec", "testdata/fs_tools.yaml", "--hide-tool-calls", "How many files in testdata/working_dir? Only output the number.") - require.Equal(t, "\n--- Agent: root ---\n1", out) + require.Equal(t, "1", out) } func TestExec_OpenAI_gpt5(t *testing.T) { out := runCLI(t, "run", "--exec", "testdata/basic.yaml", "--model=openai/gpt-5", "What's 2+2?") // With thinking enabled by default, response may include reasoning summary - require.Contains(t, out, "--- Agent: root ---") require.Contains(t, out, "4") } func TestExec_OpenAI_gpt5_1(t *testing.T) { out := runCLI(t, "run", "--exec", "testdata/basic.yaml", "--model=openai/gpt-5.1", "What's 2+2?") - require.Equal(t, "\n--- Agent: root ---\n2 + 2 = 4.", out) + require.Equal(t, "2 + 2 = 4.", out) } func TestExec_OpenAI_gpt5_codex(t *testing.T) { out := runCLI(t, "run", "--exec", "testdata/basic.yaml", "--model=openai/gpt-5-codex", "What's 2+2?") // Model reasoning summary varies, just check for the core response - require.Contains(t, out, "--- Agent: root ---") require.Contains(t, out, "4") } @@ -70,7 +67,6 @@ func TestExec_Anthropic(t *testing.T) { out := runCLI(t, "run", "--exec", "testdata/basic.yaml", "--model=anthropic/claude-sonnet-4-0", "What's 2+2?") // With interleaved thinking enabled by default, Anthropic responses include thinking content - require.Contains(t, out, "--- Agent: root ---") require.Contains(t, out, "2 + 2 = 4") } @@ -78,7 +74,6 @@ func TestExec_Anthropic_ToolCall(t *testing.T) { out := runCLI(t, "run", "--exec", "testdata/fs_tools.yaml", "--model=anthropic/claude-sonnet-4-0", "How many files in testdata/working_dir? Only output the number.") // With interleaved thinking enabled by default, Anthropic responses include thinking content - require.Contains(t, out, "--- Agent: root ---") require.Contains(t, out, `Calling list_directory(path: "testdata/working_dir")`) require.Contains(t, out, `list_directory response → "FILE README.me\n"`) // The response should end with "1" (the count) @@ -89,7 +84,6 @@ func TestExec_Anthropic_AgentsMd(t *testing.T) { out := runCLI(t, "run", "--exec", "testdata/agents-md.yaml", "--model=anthropic/claude-sonnet-4-0", "What's 2+2?") // With interleaved thinking enabled by default, Anthropic responses include thinking content - require.Contains(t, out, "--- Agent: root ---") require.Contains(t, out, "2 + 2 = 4") } @@ -97,7 +91,6 @@ func TestExec_Gemini(t *testing.T) { out := runCLI(t, "run", "--exec", "testdata/basic.yaml", "--model=google/gemini-2.5-flash", "What's 2+2?") // With thinking enabled by default (dynamic thinking for Gemini 2.5), responses may include thinking content - require.Contains(t, out, "--- Agent: root ---") // The response should contain the answer "4" somewhere require.Contains(t, out, "4") } @@ -106,7 +99,6 @@ func TestExec_Gemini_ToolCall(t *testing.T) { out := runCLI(t, "run", "--exec", "testdata/fs_tools.yaml", "--model=google/gemini-2.5-flash", "How many files in testdata/working_dir? Only output the number.") // With thinking enabled by default (dynamic thinking for Gemini 2.5), responses include thinking content - require.Contains(t, out, "--- Agent: root ---") require.Contains(t, out, `Calling list_directory(path: "testdata/working_dir")`) require.Contains(t, out, `list_directory response → "FILE README.me\n"`) // The response should end with "1" (the count) @@ -116,13 +108,13 @@ func TestExec_Gemini_ToolCall(t *testing.T) { func TestExec_Mistral(t *testing.T) { out := runCLI(t, "run", "--exec", "testdata/basic.yaml", "--model=mistral/mistral-small", "What's 2+2?") - require.Equal(t, "\n--- Agent: root ---\nThe sum of 2 + 2 is 4.", out) + require.Equal(t, "The sum of 2 + 2 is 4.", out) } func TestExec_Mistral_ToolCall(t *testing.T) { out := runCLI(t, "run", "--exec", "testdata/fs_tools.yaml", "--model=mistral/mistral-small", "How many files in testdata/working_dir? Only output the number.") - require.Equal(t, "\n--- Agent: root ---\n\nCalling list_directory(path: \"testdata/working_dir\")\n\nlist_directory response → \"FILE README.me\\n\"\n1", out) + require.Equal(t, "\nCalling list_directory(path: \"testdata/working_dir\")\n\nlist_directory response → \"FILE README.me\\n\"\n1", out) } func TestExec_ToolCallsNeedAcceptance(t *testing.T) { diff --git a/pkg/cli/printer.go b/pkg/cli/printer.go index cb7cbaa64..129d8693e 100644 --- a/pkg/cli/printer.go +++ b/pkg/cli/printer.go @@ -30,12 +30,18 @@ const ( var bold = color.New(color.Bold).SprintfFunc() type Printer struct { - out io.Writer + out io.Writer + isTTYOut bool } func NewPrinter(out io.Writer) *Printer { + isTTY := false + if f, ok := out.(*os.File); ok { + isTTY = isatty.IsTerminal(f.Fd()) + } return &Printer{ - out: out, + out: out, + isTTYOut: isTTY, } } @@ -63,6 +69,9 @@ func (p *Printer) PrintError(err error) { // PrintAgentName prints the agent name header func (p *Printer) PrintAgentName(agentName string) { + if !p.isTTYOut { + return + } p.Printf("\n--- Agent: %s ---\n", bold(agentName)) } diff --git a/pkg/cli/runner.go b/pkg/cli/runner.go index f874e5358..ea9fb8ec9 100644 --- a/pkg/cli/runner.go +++ b/pkg/cli/runner.go @@ -12,6 +12,8 @@ import ( "path/filepath" "strings" + "github.com/mattn/go-isatty" + "github.com/docker/docker-agent/pkg/chat" "github.com/docker/docker-agent/pkg/input" "github.com/docker/docker-agent/pkg/runtime" @@ -267,6 +269,16 @@ func Run(ctx context.Context, out *Printer, cfg Config, rt runtime.Runtime, sess return err } } + case !isatty.IsTerminal(os.Stdin.Fd()): + // Stdin is not a terminal: read all input from stdin + buf, err := io.ReadAll(os.Stdin) + if err != nil { + return fmt.Errorf("failed to read from stdin: %w", err) + } + + if err := oneLoop(string(buf), os.Stdin); err != nil { + return err + } default: // No messages: interactive prompt loop out.PrintWelcomeMessage(cfg.AppName)