Files
tai/taicpp/src/shell/runlive.h
T
retoor 4eff597be7 sessions: vault discovery tools, shared stdin, ctrl-key fixes
New sessions/session_export tools plus a sessions_block vault map in the system prompt (tai.py, ported to detz); recall/search carry a session footer instead of letting the model grep sealed bytes. write_file refuses new markdown at working/repo roots, and the blanket *.md gitignore is gone so docs stay visible. One LineInput owns fd 0 for every prompt (ask, approvals, shell_ask, secrets), ending phantom-empty answers; Ctrl-C re-prompts (double exits), Ctrl-D exits, Ctrl-Z suspends; mid-turn Ctrl-C announces itself and kills foreground shell children via RunInterrupted. Covered by SessionsTests, repl_keys.py pty checks, agent_diff sessions_block cases.
2026-10-28 13:11:45 +01:00

59 lines
2.6 KiB
C++

#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 <functional>
#include <optional>
#include <string>
#include <vector>
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<std::optional<std::string>(const std::string& text, bool secret)>;
// What to run: an argv list, or a shell command line (shell=True).
struct Command {
std::vector<std::string> argv;
std::optional<std::string> shell;
static Command Argv(std::vector<std::string> 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<std::string> 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