// shell_parity_driver: runs one shell/util function of the C++ port per invocation and prints a // canonical text form. tests/shell_parity.py runs the same function of ../tai.py, prints the same // form, and diffs the two byte for byte. #include "ansi.h" #include "audio.h" #include "diffrender.h" #include "edge.h" #include "glow.h" #include "jsonvalue.h" #include "lanes.h" #include "livestream.h" #include "markdown.h" #include "pyhighlight.h" #include "runlive.h" #include "sandbox.h" #include "session.h" #include "spinner.h" #include "targets.h" #include "text.h" #include "toolargs.h" #include "utf8.h" #include #include #include #include #include using namespace detz; using namespace detz::shell; namespace { const char kRs = '\x1e', kUs = '\x1f'; std::string J(const std::string& s) { return JsonValue::String(s).Dump(); } std::string Join(const std::vector& v, const std::string& sep) { return Utf8::Join(v, sep); } void Out(const std::string& s) { std::fwrite(s.data(), 1, s.size(), stdout); } long L(const std::string& s) { return std::strtol(s.c_str(), nullptr, 10); } double D(const std::string& s) { return std::strtod(s.c_str(), nullptr); } std::optional Opt(const std::string& s) { if (s == "\x01NONE") return std::nullopt; return s; } std::string FromHex(const std::string& h) { std::string o; for (size_t i = 0; i + 1 < h.size(); i += 2) o.push_back((char)std::strtol(h.substr(i, 2).c_str(), nullptr, 16)); return o; } std::string ToHex(const std::string& raw) { static const char* d = "0123456789abcdef"; std::string o; for (unsigned char c : raw) { o.push_back(d[c >> 4]); o.push_back(d[c & 15]); } return o; } struct MemorySocket : EdgeSocket { std::string inbox, sent; void SendAll(const std::string& data) override { sent += data; } std::string Recv(size_t max) override { std::string r = inbox.substr(0, max); inbox.erase(0, r.size()); return r; } void Close() override {} }; long gUuidCounter = 0; void FixEnv(double now) { gUuidCounter = 0; EdgeSpeech::env().now = [now] { return now; }; EdgeSpeech::env().randomBytes = [](size_t n) { std::string o; for (size_t i = 0; i < n; ++i) o.push_back((char)((i * 7 + 1) % 256)); return o; }; EdgeSpeech::env().uuidHex = [] { char b[40]; std::snprintf(b, sizeof b, "%032lx", ++gUuidCounter); return std::string(b); }; } struct RecordingStream : StreamSink { void Feed(const std::string& line) override { Out("FEED " + J(line) + "\n"); } void Pause() override { Out("PAUSE\n"); } void Close(const std::string&) override {} }; AgentRow RowFrom(const JsonValue& v) { AgentRow r; auto num = [&](const char* k) { const JsonValue* x = v.Get(k); return x ? x->Str() : std::string(); }; r.id = L(num("id")); r.status = v.Get("status") ? v.Get("status")->Str() : ""; r.task = v.Get("task") && !v.Get("task")->is_null() ? v.Get("task")->Str() : ""; r.elapsed = D(num("elapsed")); if (v.Get("tools") && !v.Get("tools")->is_null()) r.tools = L(num("tools")); r.collected = v.Get("collected") && v.Get("collected")->Truthy(); return r; } } // namespace int main(int argc, char** argv) { std::vector a(argv + 1, argv + argc); if (a.empty()) return 2; for (auto& item : a) { // arguments beyond the kernel's per-argument limit arrive as "@@FILE:" if (item.rfind("@@FILE:", 0) == 0) { FILE* fh = std::fopen(item.c_str() + 7, "rb"); if (!fh) return 2; item.clear(); char buf[65536]; size_t n; while ((n = std::fread(buf, 1, sizeof buf, fh)) > 0) item.append(buf, n); std::fclose(fh); } } const std::string f = a[0]; auto arg = [&](size_t i) -> std::string { return i < a.size() ? a[i] : std::string(); }; if (f == "truncate") Out(Truncate(arg(1), L(arg(2)), L(arg(3)))); else if (f == "capped") { Capped c = CappedText(arg(2) == "none" ? std::nullopt : std::optional(arg(1))); Out(std::string(c.present ? "1" : "0") + std::string(1, kRs) + std::to_string(c.size) + std::string(1, kRs) + c.text); } else if (f == "chunk") Out(Join(ChunkLines(arg(1), (int)L(arg(2)), (int)L(arg(3)), (int)L(arg(4))), std::string(1, kRs))); else if (f == "collapse") Out(CollapseRepeatedLines(arg(1), (int)L(arg(2)))); else if (f == "tags") { auto r = SplitLeadingTags(arg(1)); Out(Join(r.first, std::string(1, kUs)) + std::string(1, kRs) + r.second); } else if (f == "mentions") Out(ExpandFileMentions(arg(1), arg(2))); else if (f == "complete") Out(Join(CompleteFiles(arg(1), arg(2), (size_t)L(arg(3))), "\n")); else if (f == "human_size") Out(HumanSize(L(arg(1)))); else if (f == "stream_status") Out(StreamStatus((int)L(arg(1)), arg(2), D(arg(3)), arg(4) == "1")); else if (f == "duration") { long v = ParseDuration(arg(1)); Out(v < 0 ? "None" : std::to_string(v)); } else if (f == "delay") Out(FormatDelay(L(arg(1)))); else if (f == "tokens") Out(std::to_string(EstimateTokens(arg(1)))); else if (f == "as_int") Out(std::to_string(AsInt(arg(1), L(arg(2))))); else if (f == "toolname") Out(ToolNameClean(arg(1))); else if (f == "target_paths") Out(Join(ShellTargets::Paths(arg(1), arg(2)), "\n")); else if (f == "segment") Out(Join(ShellTargets::Segment(std::vector(a.begin() + 1, a.end())), "\n")); else if (f == "curl_hint") Out(CurlHint(arg(1))); else if (f == "strip_ansi") Out(AnsiText::Strip(arg(1))); else if (f == "wrap") Out(Join(AnsiText::Wrap(arg(1), L(arg(2)), arg(3), arg(4)), "\n")); else if (f == "trunc_ansi") Out(AnsiText::Truncate(arg(1), L(arg(2)))); else if (f == "pad_tinted") Out(AnsiText::PadTinted(arg(1), L(arg(2)))); else if (f == "style_span") Out(AnsiText::StyleSpan(arg(1), {arg(2), arg(3)})); else if (f == "md_inline") Out(Markdown::Inline(arg(1))); else if (f == "markdown") Out(Markdown::Render(arg(1), L(arg(2)), 1)); else if (f == "diff") Out(DiffRender::Render(arg(1), Opt(arg(2)), Opt(arg(3)), L(arg(4)), arg(5) == "1")); else if (f == "highlight") Out(PyHighlight::Line(arg(1))); else if (f == "g_event") Out(Glow::EventRow(arg(1), arg(2), arg(3), arg(4), L(arg(5)), arg(6))); else if (f == "g_done") Out(Glow::DoneRow(arg(1), arg(2), L(arg(3)), D(arg(4)), arg(5) == "1", L(arg(6)))); else if (f == "g_ellipsis") Out(Glow::Ellipsis(arg(1), L(arg(2)), arg(3) == "1")); else if (f == "g_shimmer") Out(Glow::Shimmer(arg(1), L(arg(2)), L(arg(3)))); else if (f == "g_describe") { CallDescription c = Glow::DescribeCall(arg(1), arg(2)); Out(J(c.verb) + "\n" + J(c.target) + "\n" + J(c.extra) + "\n" + J(c.preview)); } else if (f == "g_status") Out(Glow::Statusbar(arg(1), arg(2), arg(3), arg(4) == "1", L(arg(5)), L(arg(6)))); else if (f == "g_accent") Out(Glow::AccentFor(arg(1)) + "|" + Glow::Bar(arg(1)) + "|" + Glow::IconFor(arg(1))); else if (f == "g_hair") Out(Glow::Hairline(L(arg(1)))); else if (f == "g_prompt") Out(Glow::PromptText()); else if (f == "g_preview") Out(Glow::PreviewRow(arg(1), L(arg(2)))); else if (f == "g_agent_row") { bool ok; JsonValue v = JsonValue::Parse(arg(1), &ok); Out(Glow::AgentRowText(RowFrom(v), L(arg(2)), arg(3), arg(4))); } else if (f == "g_lanes") { bool ok; JsonValue v = JsonValue::Parse(arg(1), &ok); std::vector rows; for (auto& item : v.items()) rows.push_back(RowFrom(item)); Out(Join(Glow::Lanes(rows, L(arg(2)), L(arg(3)), arg(4)), "\n")); } else if (f == "json") { bool ok; JsonValue v = JsonValue::Parse(arg(1), &ok); Out(ok ? v.Dump(false) + "\n" + v.Dump(true) + "\n" + v.Str() + "\n" + v.Repr() : "ERR"); } else if (f == "tool_args") Out(ToolArgs::Text(arg(1)) + "\n" + ToolArgs::Valid(arg(1)) + "\n" + ToolArgs::Signature("name", arg(1))); else if (f == "md5") Out(ShellSession::Md5Hex(arg(1))); else if (f == "sess_stat") { auto st = ShellSession::ParseStat(arg(1)); Out(st.ok ? J(st.comm) + " " + std::to_string(st.ppid) + " " + std::to_string(st.ticks) : "None"); } else if (f == "sess_name") Out(ShellSession::ShellName(arg(1), arg(2))); else if (f == "sess_boot") Out(ShellSession::BootTag(arg(1))); else if (f == "sess_current") { EnvMap env; if (!arg(1).empty()) env.Set("TAI_SESSION", arg(1)); ShellSession s = ShellSession::Current(&env, L(arg(2)), arg(3), D(arg(4)), L(arg(5))); std::string chain; for (auto& e : s.chain) chain += std::to_string(e.pid) + "," + std::to_string(e.ppid) + "," + e.comm + "," + e.argv0 + "," + e.name + "," + e.startIso + "," + e.sid + ";"; Out(J(s.sid) + "\n" + J(s.shell) + "\n" + std::to_string(s.pid) + "\n" + J(s.started) + "\n" + (s.parent ? J(*s.parent) : "null") + "\n" + chain); } else if (f == "live") { // live ... : LiveStream into a StringSink StringSink sink; LiveStream ls((int)L(arg(1)), (int)L(arg(2)), &sink, true); for (size_t i = 4; i < a.size(); ++i) { if (a[i] == "\x01PAUSE") ls.Pause(); else ls.Feed(a[i]); } ls.Close(arg(3)); Out(sink.str()); } else if (f == "run") { // run -- command... (answers: text, or "\001DECLINE") double timeout = D(arg(1)); bool useShell = arg(2) == "shell"; bool ask = arg(3) == "1"; size_t dash = 4; std::vector answers; while (dash < a.size() && a[dash] != "--") answers.push_back(a[dash++]); std::vector cmd(a.begin() + (long)dash + 1, a.end()); RecordingStream stream; RunLiveOptions opt; opt.timeout = timeout; opt.stream = &stream; size_t next = 0; if (ask) opt.ask = [&](const std::string& text, bool secret) -> std::optional { Out("ASK " + J(text) + " " + (secret ? "secret" : "plain") + "\n"); if (next >= answers.size()) return std::nullopt; std::string ans = answers[next++]; if (ans.rfind("\001SLEEP", 0) == 0) { // "\001SLEEP:": the user takes a while to answer size_t colon = ans.find(':'); usleep((useconds_t)L(ans.substr(6, colon - 6)) * 1000); ans = ans.substr(colon + 1); } if (ans == "\001DECLINE") return std::nullopt; return ans; }; try { RunResult r = RunLive::Run(useShell ? Command::Shell(cmd[0]) : Command::Argv(cmd), opt); Out("RESULT " + std::to_string(r.code) + " " + (r.timedOut ? "timeout" : "ok") + " " + J(r.output) + "\n"); } catch (const std::exception& e) { Out(std::string("ERROR ") + J(e.what()) + "\n"); } } else if (f == "box") { ::dup2(1, 2); // the Python side captures stdout and stderr into one stream const std::string sc = arg(1), engine = arg(2); auto text = [](const std::string& bytes) { return Utf8::Encode(Utf8::Decode(bytes)); }; try { if (sc == "engine") Out(Sandbox::ContainerEngine()); else if (sc == "state") Out(Sandbox::BoxState(engine)); else if (sc == "ensure") Out("OK " + Sandbox::EnsureBox()); else if (sc == "caps") Out(Sandbox::UpdateBoxCaps(engine) ? "True" : "False"); else if (sc == "python") Out(Sandbox::BoxPython(engine)); else if (sc == "stt") Out("OK " + Sandbox::BoxTranscribe(engine, arg(3))); else if (sc == "tts") Out("OK " + ToHex(Sandbox::BoxSpeak(engine, arg(3), arg(4)))); else if (sc == "ocbin") Out(Sandbox::BoxOpencodeBinary(engine) ? "True" : "False"); else if (sc == "ocrun") { CompletedProcess d = Sandbox::BoxOpencodeExec(engine, arg(3), arg(4), D(arg(5)), "build"); Out("PARSE|" + std::to_string(d.returncode) + "|" + text(d.out) + "|" + text(d.err)); } else if (sc == "exec") { CompletedProcess d = Sandbox::BoxExec(engine, std::vector(a.begin() + 3, a.end())); Out("RC " + std::to_string(d.returncode) + " " + J(text(d.out)) + " " + J(text(d.err))); } } catch (const BackendError& e) { Out(std::string("ERR ") + e.what()); } catch (const TimeoutExpired& e) { Out(std::string("TIMEOUT ") + e.what()); } catch (const OsError& e) { Out(std::string("OSERR ") + e.what()); } } else if (f == "edge_token") { FixEnv(D(arg(2))); Out(EdgeSpeech::Token(L(arg(1)))); } else if (f == "edge_ts") { FixEnv(D(arg(1))); Out(EdgeSpeech::Timestamp()); } else if (f == "edge_ssml") Out(EdgeSpeech::Ssml(arg(1), arg(2))); else if (f == "edge_chunks") Out(Join(EdgeSpeech::Chunks(arg(1), (size_t)L(arg(2))), std::string(1, kRs))); else if (f == "ws_send") { FixEnv(0); MemorySocket m; EdgeSpeech::WsSend(m, arg(1)); Out(ToHex(m.sent)); } else if (f == "ws_recv") { FixEnv(0); MemorySocket m; m.inbox = FromHex(arg(1)); try { auto r = EdgeSpeech::WsRecv(m); Out("OK " + std::to_string(r.first) + " " + ToHex(r.second)); } catch (const BackendError& e) { Out(std::string("ERR ") + e.what()); } Out("|sent=" + ToHex(m.sent)); } else if (f == "edge_skew") { FixEnv(D(arg(2))); Out(std::to_string(EdgeSpeech::Skew(arg(1)))); } else if (f == "edge_session") { FixEnv(D(arg(4))); MemorySocket m; m.inbox = FromHex(arg(1)); try { std::string audio = EdgeSpeech::SynthesizeOn(m, arg(2), arg(3)); Out("OK " + ToHex(audio)); } catch (const BackendError& e) { Out(std::string("ERR ") + e.what()); } Out("|sent=" + ToHex(m.sent)); } else if (f == "edge_live") { try { std::string audio = EdgeSpeech::Synthesize(arg(1), arg(2)); Out("OK " + std::to_string(audio.size()) + " " + ToHex(audio.substr(0, 3))); } catch (const std::exception& e) { Out(std::string("ERR ") + e.what()); } } else if (f == "listen") Out(Audio::Listen(L(arg(1)), arg(2))); else if (f == "play") Out(Audio::Play(arg(1)) ? "True" : "False"); else if (f == "spinner") { // spinner