#pragma once // RunLive: run_live() / run_live_pty() of tai.py. Every command an agent runs for the user // streams live into a StreamSink and is returned to the agent. With an ask function the // command runs on a pty; when it stops on a prompt (sudo, ssh, y/n) ask(text, secret) gets // the question and its answer is typed into the command. nullopt declines: the command gets // end-of-file. Time spent waiting for the user does not count against the timeout. #include "proc.h" #include "sink.h" #include #include #include #include namespace detz::shell { constexpr int kShellTimeout = 120; // SHELL_TIMEOUT constexpr long kRunOutputCap = 4000000; // RUN_OUTPUT_CAP constexpr double kRunPromptIdle = 1.0; // RUN_PROMPT_IDLE constexpr int kRunMaxAsks = 20; // RUN_MAX_ASKS constexpr size_t kRunAnswerCap = 1000; // RUN_ANSWER_CAP // ask(text, secret): the user's answer, or nullopt to decline. using AskFn = std::function(const std::string& text, bool secret)>; // What to run: an argv list, or a shell command line (shell=True). struct Command { std::vector argv; std::optional shell; static Command Argv(std::vector a) { Command c; c.argv = std::move(a); return c; } static Command Shell(std::string line) { Command c; c.shell = std::move(line); return c; } }; // (returncode, output, elapsed seconds, timed_out) struct RunResult { int code{0}; std::string output; double elapsed{0}; bool timedOut{false}; }; // Ctrl-C landed while the command ran: the child is already killed and reaped. struct RunInterrupted : public std::runtime_error { RunInterrupted() : std::runtime_error("interrupted") {} }; struct RunLiveOptions { double timeout{kShellTimeout}; std::optional cwd; const EnvMap* env{nullptr}; // null: inherit StreamSink* stream{nullptr}; AskFn ask; // empty: no pty, no questions bool allow_interrupt{true}; // false: Ctrl-C is not ours (background workers) }; class RunLive { public: // run_live(): pty path when ask is set, pipe path otherwise. Throws OsError when the // command cannot start (missing program, bad cwd). static RunResult Run(const Command& command, const RunLiveOptions& options = RunLiveOptions()); static RunResult RunPty(const Command& command, const RunLiveOptions& options); // RUN_CTRL_RE.sub("", text) static std::string StripControl(const std::string& text); // RUN_PROMPT_RE.search(text) is not None static bool LooksLikePrompt(const std::string& text); }; } // namespace detz::shell