Run executes an agent in non-TUI mode, handling user input and runtime events. userMessages contains the user messages to send. If a single message is "-", input is read from stdin. If empty, an interactive prompt loop is started.
(ctx context.Context, out *Printer, cfg Config, rt runtime.Runtime, sess *session.Session, userMessages []string)
| 80 | // userMessages contains the user messages to send. If a single message is "-", |
| 81 | // input is read from stdin. If empty, an interactive prompt loop is started. |
| 82 | func Run(ctx context.Context, out *Printer, cfg Config, rt runtime.Runtime, sess *session.Session, userMessages []string) error { |
| 83 | // Create a cancellable context for this agentic loop and wire Ctrl+C to cancel it |
| 84 | ctx, cancel := context.WithCancel(ctx) |
| 85 | defer cancel() |
| 86 | |
| 87 | // Ensure telemetry is initialized and add to context so runtime can access it |
| 88 | telemetry.EnsureGlobalTelemetryInitialized(ctx) |
| 89 | if telemetryClient := telemetry.GetGlobalTelemetryClient(ctx); telemetryClient != nil { |
| 90 | ctx = telemetry.WithClient(ctx, telemetryClient) |
| 91 | } |
| 92 | |
| 93 | sess.Title = "Running agent" |
| 94 | // If the last received event was an error, return it. That way the exit code |
| 95 | // will be non-zero if the agent failed. |
| 96 | var lastErr error |
| 97 | |
| 98 | oneLoop := func(text string, rd io.Reader) error { |
| 99 | autoExtensions := 0 |
| 100 | |
| 101 | userInput := strings.TrimSpace(text) |
| 102 | if userInput == "" { |
| 103 | return nil |
| 104 | } |
| 105 | |
| 106 | userMsg, attachedPath, err := PrepareUserMessage(ctx, rt, userInput, cfg.AttachmentPath) |
| 107 | if err != nil { |
| 108 | return fmt.Errorf("failed to prepare message: %w", err) |
| 109 | } |
| 110 | if userMsg == nil { |
| 111 | // Agent-only command with no content - agent switched but no message to send |
| 112 | return nil |
| 113 | } |
| 114 | sess.AddMessage(userMsg) |
| 115 | sess.AddAttachedFile(attachedPath) |
| 116 | |
| 117 | if cfg.OutputJSON { |
| 118 | for event := range rt.RunStream(ctx, sess) { |
| 119 | switch e := event.(type) { |
| 120 | case *runtime.ToolCallConfirmationEvent: |
| 121 | // JSON mode has no user at stdin — reject unconditionally. |
| 122 | // A confirmation event under AutoApprove means a |
| 123 | // preempt-yolo hook overrode --yolo; the safe answer is |
| 124 | // still Reject (the hook said Ask, not Approve). |
| 125 | rt.Resume(ctx, runtime.ResumeReject("")) |
| 126 | case *runtime.ElicitationRequestEvent: |
| 127 | _ = rt.ResumeElicitation(ctx, "decline", nil) |
| 128 | case *runtime.MaxIterationsReachedEvent: |
| 129 | switch handleMaxIterationsAutoApprove(cfg.AutoApprove, &autoExtensions, e.MaxIterations) { |
| 130 | case maxIterContinue: |
| 131 | rt.Resume(ctx, runtime.ResumeApprove()) |
| 132 | default: // maxIterStop or maxIterPrompt (no interactive prompt in JSON mode) |
| 133 | rt.Resume(ctx, runtime.ResumeReject("")) |
| 134 | return nil |
| 135 | } |
| 136 | case *runtime.ErrorEvent: |
| 137 | return fmt.Errorf("%s", e.Error) |
| 138 | } |
| 139 |