From d2244b00f066cc77a456ef0fdeb6117ba9e690fd Mon Sep 17 00:00:00 2001 From: retoor Date: Sat, 24 Oct 2026 06:45:02 +0200 Subject: [PATCH] detz: M2 util+config --- taicpp/src/agent/agent.cpp | 27 ++++++++++++++------ taicpp/src/agent/agent.h | 49 +++++++++++++++++++++++++----------- taicpp/src/cap/cap.cpp | 7 +++--- taicpp/src/cap/cap.h | 17 +++++++------ taicpp/src/comms/comms.cpp | 8 +++--- taicpp/src/comms/comms.h | 16 ++++++------ taicpp/src/config/config.cpp | 21 +++++++++++----- taicpp/src/mcp/mcp.cpp | 7 +++--- taicpp/src/mcp/mcp.h | 11 +++----- taicpp/src/plexer/plexer.cpp | 9 ++++--- taicpp/src/plexer/plexer.h | 31 +++++++++++++++++------ taicpp/src/tui/tui.cpp | 6 ++--- taicpp/src/tui/tui.h | 15 +++++------ taicpp/tests/test_main.cpp | 37 +++++++++++++++++++++++++++ 14 files changed, 175 insertions(+), 86 deletions(-) diff --git a/taicpp/src/agent/agent.cpp b/taicpp/src/agent/agent.cpp index 0c0f966..1512b4e 100644 --- a/taicpp/src/agent/agent.cpp +++ b/taicpp/src/agent/agent.cpp @@ -1,10 +1,21 @@ #include "agent.h" namespace detz::agent { -void ToolRegistry::add(ToolDef d){tools_[d.name]=d;} bool ToolRegistry::has(const std::string& n) const {return tools_.count(n)>0;} -std::vector ToolRegistry::names() const {std::vector o; for(auto&kv:tools_)o.push_back(kv.first); return o;} -Approval::Decision Approval::check(const std::string&) const {return Decision::Allow;} -GoalVerdict GoalVerifier::verify(const std::string& g,const std::string& o) const {return {!g.empty()&&!o.empty(),o.empty()?"empty outcome":""};} -std::vector RetrievalEval::score(const std::string&,const std::vector& docs) const {std::vector o; for(auto&d:docs)o.push_back({0.0,d}); return o;} -void RagStats::record(bool h){d_.queries++; if(h)d_.hits++;} RagStatsData RagStats::data() const {return d_;} -std::string Agent::run(const std::string& g){return "agent:"+g;} ToolRegistry& Agent::registry(){return reg_;} -} // namespace detz::agent +void ToolRegistry::add(const ToolSpec& s, Fn fn) { tools_[s.name] = {s, std::move(fn)}; } +std::optional ToolRegistry::call(const std::string& n, const std::map& a) { + auto it = tools_.find(n); if (it == tools_.end()) { return std::nullopt; } + return it->second.second(a); +} +std::vector ToolRegistry::list() const { + std::vector v; for (auto& [k, p] : tools_) v.push_back(p.first); return v; +} +Approval ApprovalGate::review(const std::string& t, const std::map&) { + if (auto_) { + return {true, "auto"}; + } + return {t == "read", t == "read" ? "safe" : "needs-user"}; +} +GoalVerdict verify_goal(const std::string& g, const std::string& e) { return {!g.empty() && !e.empty(), e}; } +RagStats rag_stats(size_t h, size_t c, double m) { return {h, c, m}; } +Agent::Agent(ToolRegistry r) : reg_(std::move(r)) {} +std::string Agent::run(const std::string& g) { return "done: " + g; } +} // namespace detz::agent diff --git a/taicpp/src/agent/agent.h b/taicpp/src/agent/agent.h index d180136..2a5b0e7 100644 --- a/taicpp/src/agent/agent.h +++ b/taicpp/src/agent/agent.h @@ -1,19 +1,38 @@ #pragma once +#include +#include +#include #include #include -#include -#include -#include -#include namespace detz::agent { -struct ToolDef { std::string name, desc; }; -class ToolRegistry { public: void add(ToolDef d); bool has(const std::string& n) const; std::vector names() const; private: std::map tools_; }; -class Approval { public: enum class Decision { Allow, Deny, Ask }; Decision check(const std::string& tool) const; }; -struct GoalVerdict { bool ok=false; std::string reason; }; -class GoalVerifier { public: GoalVerdict verify(const std::string& goal, const std::string& outcome) const; }; -struct RetrievalScore { double score=0; std::string doc; }; -class RetrievalEval { public: std::vector score(const std::string& q, const std::vector& docs) const; }; -struct RagStatsData { std::size_t queries=0, hits=0; }; -class RagStats { public: void record(bool hit); RagStatsData data() const; private: RagStatsData d_; }; -class Agent { public: explicit Agent(ToolRegistry r): reg_(r) {} std::string run(const std::string& goal); ToolRegistry& registry(); private: ToolRegistry reg_; }; -} // namespace detz::agent +struct ToolResult { bool ok{true}; std::string out; }; +struct ToolSpec { std::string name, desc; }; +class ToolRegistry { + public: + using Fn = std::function&)>; + void add(const ToolSpec& s, Fn fn); + std::optional call(const std::string& name, const std::map& args); + std::vector list() const; + private: + std::map> tools_; +}; +struct Approval { bool approved{false}; std::string reason; }; +class ApprovalGate { + public: + Approval review(const std::string& tool, const std::map& args); + void set_auto(bool v) { auto_ = v; } + private: + bool auto_{false}; +}; +struct GoalVerdict { bool met{false}; std::string note; }; +GoalVerdict verify_goal(const std::string& goal, const std::string& evidence); +struct RagStats { size_t hits{0}, chunks{0}; double ms{0}; }; +RagStats rag_stats(size_t hits, size_t chunks, double ms); +class Agent { + public: + explicit Agent(ToolRegistry reg); + std::string run(const std::string& goal); + private: + ToolRegistry reg_; +}; +} // namespace detz::agent diff --git a/taicpp/src/cap/cap.cpp b/taicpp/src/cap/cap.cpp index 5f66536..e727834 100644 --- a/taicpp/src/cap/cap.cpp +++ b/taicpp/src/cap/cap.cpp @@ -1,5 +1,6 @@ #include "cap.h" namespace detz::cap { -void CapOrchestrator::add(Cap c){caps_[c.name]=c;} bool CapOrchestrator::has(const std::string& n) const {return caps_.count(n)>0;} -std::string CapOrchestrator::run(const std::string& n,const std::string& a) const {return n+":"+a;} -} // namespace detz::cap +void Orchestrator::grant(const std::string& n) { for (auto& c : caps_) if (c.name == n) { c.granted = true; return; } caps_.push_back({n, true}); } +bool Orchestrator::check(const std::string& n) const { for (auto& c : caps_) if (c.name == n) return c.granted; return false; } +std::vector Orchestrator::list() const { return caps_; } +} // namespace detz::cap diff --git a/taicpp/src/cap/cap.h b/taicpp/src/cap/cap.h index 44efc62..a99db26 100644 --- a/taicpp/src/cap/cap.h +++ b/taicpp/src/cap/cap.h @@ -1,11 +1,14 @@ #pragma once #include #include -#include -#include -#include -#include namespace detz::cap { -struct Cap { std::string name; std::string desc; }; -class CapOrchestrator { public: void add(Cap c); bool has(const std::string& n) const; std::string run(const std::string& n, const std::string& arg) const; private: std::map caps_; }; -} // namespace detz::cap +struct Cap { std::string name; bool granted{false}; }; +class Orchestrator { + public: + void grant(const std::string& n); + bool check(const std::string& n) const; + std::vector list() const; + private: + std::vector caps_; +}; +} // namespace detz::cap diff --git a/taicpp/src/comms/comms.cpp b/taicpp/src/comms/comms.cpp index d1c0cee..7386a37 100644 --- a/taicpp/src/comms/comms.cpp +++ b/taicpp/src/comms/comms.cpp @@ -1,6 +1,6 @@ #include "comms.h" namespace detz::comms { -bool Telegram::send(const std::string&,const std::string&) const {return !tok_.empty();} -bool Installer::install(const std::string&) const {return true;} -SysInfoData SysInfo::collect() const {return {"linux","x86_64"};} -} // namespace detz::comms +SysInfo sys_info() { return {"linux", "x86_64", 4}; } +bool telegram_send(const TelegramCfg& c, const std::string&) { return !c.token.empty(); } +InstallPlan install_plan() { return {{"bin", "hook"}}; } +} // namespace detz::comms diff --git a/taicpp/src/comms/comms.h b/taicpp/src/comms/comms.h index 4e31f83..51594a0 100644 --- a/taicpp/src/comms/comms.h +++ b/taicpp/src/comms/comms.h @@ -1,13 +1,11 @@ #pragma once #include #include -#include -#include -#include -#include namespace detz::comms { -class Telegram { public: explicit Telegram(std::string token=""):tok_(std::move(token)){} bool send(const std::string& chat, const std::string& msg) const; private: std::string tok_; }; -class Installer { public: bool install(const std::string& target) const; }; -struct SysInfoData { std::string os, arch; }; -class SysInfo { public: SysInfoData collect() const; }; -} // namespace detz::comms +struct SysInfo { std::string os, arch; int cpus{0}; }; +SysInfo sys_info(); +struct TelegramCfg { std::string token, chat; }; +bool telegram_send(const TelegramCfg& c, const std::string& text); +struct InstallPlan { std::vector steps; }; +InstallPlan install_plan(); +} // namespace detz::comms diff --git a/taicpp/src/config/config.cpp b/taicpp/src/config/config.cpp index 93287d5..d70b2df 100644 --- a/taicpp/src/config/config.cpp +++ b/taicpp/src/config/config.cpp @@ -40,18 +40,27 @@ Config Config::FromEnv(bool noOpencode) { return c; } std::string Config::Get(const std::string& key) const { - if(key=="model")return model; if(key=="devplace_base")return devplaceBase; if(key=="voice")return voice; - if(key=="openrouter_key")return openrouterKey; if(key=="devplace_key")return devplaceKey; + if (key == "model") return model; + if (key == "devplace_base") return devplaceBase; + if (key == "voice") return voice; + if (key == "openrouter_key") return openrouterKey; + if (key == "devplace_key") return devplaceKey; return ""; } std::vector KnownModelIds(const Config& cfg, const std::vector& extra) { std::vector ids; auto add=[&](const std::string& s){ if(!s.empty()&&std::find(ids.begin(),ids.end(),s)==ids.end()) ids.push_back(s); }; - for(auto&s:ZenFreeModels())add(s); for(auto&s:PollinationsModels())add(s); for(auto&s:DevplaceFreeRoutes())add(s); + for (auto& s : ZenFreeModels()) add(s); + for (auto& s : PollinationsModels()) add(s); + for (auto& s : DevplaceFreeRoutes()) add(s); add(kOpenRouterModel); - for(auto&s:cfg.openrouterModels)add(s); for(auto&s:cfg.devplaceModels)add(s); - for(auto&s:cfg.pollinationsModels)add(s); for(auto&s:cfg.zenModels)add(s); for(auto&s:cfg.opencodeModels)add(s); - add(cfg.explicitModel); for(auto&s:extra)add(s); + for (auto& s : cfg.openrouterModels) add(s); + for (auto& s : cfg.devplaceModels) add(s); + for (auto& s : cfg.pollinationsModels) add(s); + for (auto& s : cfg.zenModels) add(s); + for (auto& s : cfg.opencodeModels) add(s); + add(cfg.explicitModel); + for (auto& s : extra) add(s); return ids; } std::pair ResolveTag(const std::string& tag, const std::map& bots, const std::vector& models, const std::string& directory) { diff --git a/taicpp/src/mcp/mcp.cpp b/taicpp/src/mcp/mcp.cpp index 55c5e3c..acd864c 100644 --- a/taicpp/src/mcp/mcp.cpp +++ b/taicpp/src/mcp/mcp.cpp @@ -1,6 +1,5 @@ #include "mcp.h" namespace detz::mcp { -void McpServer::add(McpTool t){tools_.push_back(t);} -std::vector McpServer::list() const {std::vector o; for(auto&t:tools_)o.push_back(t.name); return o;} -std::string McpServer::serve(const std::string& r) const {return "{\"echo\":\""+r+"\"}";} -} // namespace detz::mcp +std::vector serve_list() { return {{"search", "full-text search"}, {"shell", "run shell"}}; } +std::string serve_call(const std::string& n, const std::string&) { return "{\"tool\":\"" + n + "\",\"ok\":true}"; } +} // namespace detz::mcp diff --git a/taicpp/src/mcp/mcp.h b/taicpp/src/mcp/mcp.h index fc645da..a2ffaa6 100644 --- a/taicpp/src/mcp/mcp.h +++ b/taicpp/src/mcp/mcp.h @@ -1,11 +1,8 @@ #pragma once #include #include -#include -#include -#include -#include namespace detz::mcp { -struct McpTool { std::string name, desc; }; -class McpServer { public: void add(McpTool t); std::vector list() const; std::string serve(const std::string& req) const; private: std::vector tools_; }; -} // namespace detz::mcp +struct Tool { std::string name, desc; }; +std::vector serve_list(); +std::string serve_call(const std::string& name, const std::string& args_json); +} // namespace detz::mcp diff --git a/taicpp/src/plexer/plexer.cpp b/taicpp/src/plexer/plexer.cpp index b731e3e..5f2d720 100644 --- a/taicpp/src/plexer/plexer.cpp +++ b/taicpp/src/plexer/plexer.cpp @@ -1,5 +1,8 @@ #include "plexer.h" +#include namespace detz::plexer { -Backend PlexerController::backend() const {return b_;} bool PlexerController::send(const std::string& s){buf_+=s; return true;} std::string PlexerController::read(std::size_t n){return buf_.substr(0,n);} -PlexerController PlexerControllerFactory::make(const std::string& n){return PlexerController(n=="tmux"?Backend::Tmux:Backend::Native);} -} // namespace detz::plexer +static std::string sh(const std::string& c) { std::string o; char b[256]; FILE* p=::popen(c.c_str(),"r"); if(!p) return o; while(std::fgets(b,sizeof b,p)) o+=b; ::pclose(p); return o; } +std::vector Tmux::list() { std::vector v; for (auto& l : std::vector{sh("tmux list-panes -F '#{pane_id} #{pane_current_path} #{pane_current_command}' 2>/dev/null")}) {(void)l;} return v; } +std::string Tmux::capture(const std::string& id) { return sh("tmux capture-pane -p -t " + id + " 2>/dev/null"); } +void Tmux::send(const std::string& id, const std::string& k) { (void)sh("tmux send-keys -t " + id + " " + k + " 2>/dev/null"); } +} // namespace detz::plexer diff --git a/taicpp/src/plexer/plexer.h b/taicpp/src/plexer/plexer.h index ef1145d..b83a4ee 100644 --- a/taicpp/src/plexer/plexer.h +++ b/taicpp/src/plexer/plexer.h @@ -1,12 +1,27 @@ #pragma once #include #include -#include -#include -#include -#include namespace detz::plexer { -enum class Backend { Native, Tmux }; -class PlexerController { public: explicit PlexerController(Backend b=Backend::Native):b_(b){} Backend backend() const; bool send(const std::string& s); std::string read(std::size_t n=1024); private: Backend b_; std::string buf_; }; -class PlexerControllerFactory { public: static PlexerController make(const std::string& name); }; -} // namespace detz::plexer +struct Pane { std::string id; std::string cwd; std::string cmd; }; +class Controller { + public: + virtual ~Controller() = default; + virtual std::vector list() = 0; + virtual std::string capture(const std::string& id) = 0; + virtual void send(const std::string& id, const std::string& keys) = 0; +}; +class Native : public Controller { + public: + std::vector list() override { return panes_; } + std::string capture(const std::string&) override { return ""; } + void send(const std::string&, const std::string&) override {} + private: + std::vector panes_; +}; +class Tmux : public Controller { + public: + std::vector list() override; + std::string capture(const std::string& id) override; + void send(const std::string& id, const std::string& keys) override; +}; +} // namespace detz::plexer diff --git a/taicpp/src/tui/tui.cpp b/taicpp/src/tui/tui.cpp index 91c07b8..47b3d75 100644 --- a/taicpp/src/tui/tui.cpp +++ b/taicpp/src/tui/tui.cpp @@ -1,5 +1,5 @@ #include "tui.h" namespace detz::tui { -std::string Renderer::render(const std::vector& ss,const Theme&) const {std::string o; for(auto&s:ss)o+=s.text; return o;} -std::string Renderer::renderText(const std::string& s) const {return s;} -} // namespace detz::tui +std::string render(const std::string& b, const Theme&) { return b; } +std::vector poll_events() { return {}; } +} // namespace detz::tui diff --git a/taicpp/src/tui/tui.h b/taicpp/src/tui/tui.h index 75ab5d4..73365a9 100644 --- a/taicpp/src/tui/tui.h +++ b/taicpp/src/tui/tui.h @@ -1,13 +1,10 @@ #pragma once #include #include -#include -#include -#include -#include namespace detz::tui { -struct Theme { std::string name{"default"}; int fg=7, bg=0; }; -struct Style { bool bold=false, dim=false; }; -struct Span { std::string text; Style style; }; -class Renderer { public: std::string render(const std::vector& spans, const Theme& t) const; std::string renderText(const std::string& s) const; }; -} // namespace detz::tui +struct Theme { std::string name{"dark"}; std::string accent{"cyan"}; }; +struct Style { bool bold{false}; std::string fg; }; +std::string render(const std::string& body, const Theme& th = {}); +struct Event { std::string kind; std::string data; }; +std::vector poll_events(); +} // namespace detz::tui diff --git a/taicpp/tests/test_main.cpp b/taicpp/tests/test_main.cpp index 0be7cdc..0b42d2a 100644 --- a/taicpp/tests/test_main.cpp +++ b/taicpp/tests/test_main.cpp @@ -6,7 +6,12 @@ #include "../src/shell/livestream.h" #include "../src/shell/lanes.h" #include "../src/llm/llm.h" +#include "../src/util/ansi.h" +#include "../src/util/text.h" +#include "../src/config/config.h" +#include "../src/config/consts.h" #include +#include #include using detz::llm::ChatClient; using detz::llm::ChatRequest; @@ -64,6 +69,38 @@ int main() { bool be = false; try { detz::llm::Opencode no; no.complete("m", {}); } catch (const detz::llm::BackendError&) { be = true; } assert(be); + // M2 util+config + assert(detz::Consts::kTotalStepCap == 500 && detz::Consts::kAuditMaxChars == 20000); + assert(std::string(detz::kDefaultModel) == "muse"); + assert(!detz::ZenFreeModels().empty() && !detz::PollinationsModels().empty()); + assert(detz::Paint("x", detz::Ansi::kRed).find("x") != std::string::npos); + assert(detz::Truncate("abc", 1500, 500) == "abc"); + std::string big(3000, 'a'); + assert(detz::Truncate(big, 100, 100).find("chars truncated") != std::string::npos); + assert(detz::TruncMarker(5).find("5") != std::string::npos); + assert(detz::CappedText("hi") == "hi"); + assert(detz::ChunkLines("").empty()); + assert(detz::ChunkLines("a\nb").size() == 1); + assert(detz::CollapseRepeatedLines("x\nx\nx").find("repeated 2") != std::string::npos); + assert(detz::CollapseRepeatedLines("a\nb").find("repeated") == std::string::npos); + { auto [tags, rest] = detz::SplitLeadingTags("@a @b hello"); assert(tags.size() == 2 && rest == "hello"); } + { auto [tags, rest] = detz::SplitLeadingTags("hi"); assert(tags.empty() && rest == "hi"); } + { auto dir = std::filesystem::temp_directory_path().string(); + std::string f = dir + "/detz_m2_test.txt"; FILE* fh = std::fopen(f.c_str(), "w"); std::fputs("x", fh); std::fclose(fh); + assert(detz::ExpandFileMentions("@detz_m2_test.txt", dir).find("detz_m2_test") != std::string::npos); + assert(detz::ExpandFileMentions("see @nope-missing-xyz", dir).find("@nope") != std::string::npos); + auto cf = detz::CompleteFiles("detz_m2_test", dir); assert(!cf.empty()); + assert(detz::CompleteFiles("zzz-no-match-xyz", dir).empty()); + std::filesystem::remove(f); } + { detz::Config cfg = detz::Config::FromEnv(); assert(!cfg.model.empty()); + assert(!detz::SettingsCatalog().empty()); + assert(!detz::SplitModels("a, b;c").empty()); + auto ids = detz::KnownModelIds(cfg); assert(!ids.empty()); + auto rt = detz::ResolveTag("auto", {}, ids, "/tmp"); assert(rt.first == "auto"); + auto ru = detz::ResolveTag("zzz-unknown-xyz", {}, ids, "/tmp"); assert(ru.first == "unknown"); + std::map bots = {{"bob", "BotBob"}}; + auto rb = detz::ResolveTag("bob", bots, ids, "/tmp"); assert(rb.first == "bot"); } + std::puts("M2 OK"); std::puts("M4+M5 OK"); return 0; }