|
|
|
@@ -0,0 +1,142 @@
|
|
|
|
|
#include "exec.h"
|
|
|
|
|
#include "tools.h"
|
|
|
|
|
#include "shell/session.h"
|
|
|
|
|
#include "shell/sandbox.h"
|
|
|
|
|
#include "store/store.h"
|
|
|
|
|
#include "util/http.h"
|
|
|
|
|
#include "util/text.h"
|
|
|
|
|
#include "util/ansi.h"
|
|
|
|
|
#include "config/config.h"
|
|
|
|
|
#include <cstdio>
|
|
|
|
|
#include <cstdlib>
|
|
|
|
|
#include <filesystem>
|
|
|
|
|
#include <fstream>
|
|
|
|
|
#include <sstream>
|
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
|
namespace detz::agent {
|
|
|
|
|
static ToolResult ok(const std::string& s){ return {true, s}; }
|
|
|
|
|
static ToolResult err(const std::string& s){ return {false, s}; }
|
|
|
|
|
static std::string arg(const std::map<std::string,std::string>& a, const std::string& k, const std::string& d=""){ auto it=a.find(k); return it==a.end()?d:it->second; }
|
|
|
|
|
int RegisterAll(ToolRegistry& reg, const std::string& workdir){
|
|
|
|
|
int n=0;
|
|
|
|
|
auto add=[&](const std::string& name,const std::string& desc,ToolRegistry::Fn fn){ reg.add({name,desc},std::move(fn)); ++n; };
|
|
|
|
|
add("shell","Run a shell command",[workdir](auto& a)->ToolResult{
|
|
|
|
|
detz::shell::ShellSession sh; auto e=sh.exec(arg(a,"command"),120000);
|
|
|
|
|
return {e.code==0, e.out}; });
|
|
|
|
|
add("read_file","Read a text file",[](auto& a)->ToolResult{
|
|
|
|
|
std::ifstream f(arg(a,"path")); if(!f) return err("cannot open "+arg(a,"path"));
|
|
|
|
|
std::ostringstream ss; ss<<f.rdbuf(); std::string s=ss.str();
|
|
|
|
|
if(s.size()>6000) s=s.substr(0,6000)+"...[truncated]";
|
|
|
|
|
return ok(s); });
|
|
|
|
|
add("write_file","Write content to a file",[](auto& a)->ToolResult{
|
|
|
|
|
fs::path p(arg(a,"path")); std::error_code ec; fs::create_directories(p.parent_path(),ec);
|
|
|
|
|
std::ofstream f(p); if(!f) return err("cannot write "+p.string());
|
|
|
|
|
f<<arg(a,"content"); return ok("wrote "+p.string()); });
|
|
|
|
|
add("edit_file","Replace one exact text match",[](auto& a)->ToolResult{
|
|
|
|
|
std::ifstream f(arg(a,"path")); if(!f) return err("cannot open");
|
|
|
|
|
std::ostringstream ss; ss<<f.rdbuf(); std::string s=ss.str();
|
|
|
|
|
auto pos=s.find(arg(a,"find")); if(pos==std::string::npos) return err("match not found");
|
|
|
|
|
if(s.find(arg(a,"find"),pos+1)!=std::string::npos) return err("match not unique");
|
|
|
|
|
s.replace(pos,arg(a,"find").size(),arg(a,"replace"));
|
|
|
|
|
std::ofstream o(arg(a,"path")); o<<s; return ok("edited"); });
|
|
|
|
|
add("web_search","Search the web",[](auto& a)->ToolResult{
|
|
|
|
|
auto r=detz::HttpGet("https://html.duckduckgo.com/html/?q="+arg(a,"query"));
|
|
|
|
|
bool ok2=r.status>=200&&r.status<300; return {ok2, ok2?r.body.substr(0,4000):("http "+std::to_string(r.status))}; });
|
|
|
|
|
add("describe_image","Describe an image",[](auto& a)->ToolResult{ return ok("[image: "+arg(a,"source")+"] vision unavailable in offline build"); });
|
|
|
|
|
add("web_fetch","Fetch a URL",[](auto& a)->ToolResult{
|
|
|
|
|
std::string m=arg(a,"method","GET"); std::string u=arg(a,"url");
|
|
|
|
|
detz::HttpResponse r = (m=="POST")?detz::HttpPost(u,arg(a,"body","")):detz::HttpGet(u);
|
|
|
|
|
bool ok3=r.status>=200&&r.status<300; return {ok3, ok3?r.body:("http "+std::to_string(r.status))}; });
|
|
|
|
|
add("speak","TTS",[](auto& a)->ToolResult{ return ok("[speak: "+arg(a,"text").substr(0,80)+"]"); });
|
|
|
|
|
add("listen","STT",[](auto&){ return err("no microphone in headless build"); });
|
|
|
|
|
add("remember","Update system message",[](auto& a)->ToolResult{
|
|
|
|
|
std::string home=getenv("HOME")?getenv("HOME"):"/tmp";
|
|
|
|
|
std::ofstream f(home+"/.tai_memory.txt",std::ios::app); f<<arg(a,"instruction")<<"\n"; return ok("remembered"); });
|
|
|
|
|
add("recall","Search memory",[](auto& a)->ToolResult{
|
|
|
|
|
std::string home=getenv("HOME")?getenv("HOME"):"/tmp";
|
|
|
|
|
std::ifstream f(home+"/.tai_memory.txt"); std::ostringstream ss; ss<<f.rdbuf();
|
|
|
|
|
std::string s=ss.str(), q=arg(a,"query"), out; std::istringstream in(s); std::string line;
|
|
|
|
|
while(std::getline(in,line)) if(q.empty()||line.find(q)!=std::string::npos) out+=line+"\n";
|
|
|
|
|
return ok(out.empty()?"(no matches)":out); });
|
|
|
|
|
add("load_skill","Load a skill",[](auto& a)->ToolResult{
|
|
|
|
|
for(auto base:{std::string("./.tai/skills/"),std::string(std::string(getenv("HOME")?:"/tmp")+"/.tai/skills/")}){
|
|
|
|
|
std::ifstream f(base+arg(a,"name")+"/SKILL.md"); if(f){ std::ostringstream ss; ss<<f.rdbuf(); return ok(ss.str().substr(0,8000)); } }
|
|
|
|
|
return err("skill not found: "+arg(a,"name")); });
|
|
|
|
|
add("get_current_terminal_content","Capture terminal",[](auto&){ return err("no terminal attached"); });
|
|
|
|
|
add("list_terminals","List terminals",[](auto&){ return ok("(no tmux in build env)"); });
|
|
|
|
|
add("tail_terminals","Tail terminals",[](auto&){ return ok("(no tmux)"); });
|
|
|
|
|
add("export_terminal_log","Export terminal log",[](auto&){ return err("no terminal attached"); });
|
|
|
|
|
add("plexer_send","Send keys",[](auto&){ return err("no plexer session"); });
|
|
|
|
|
add("plexer_read","Read plexer",[](auto&){ return err("no plexer session"); });
|
|
|
|
|
add("plexer_type","Type into plexer",[](auto&){ return err("no plexer session"); });
|
|
|
|
|
add("plexer_status","Plexer status",[](auto&){ return ok("no active session"); });
|
|
|
|
|
add("plexer_show","Show UI flow",[](auto&){ return err("non-interactive"); });
|
|
|
|
|
add("tui_screen","TUI screen",[](auto&){ return err("no TUI session"); });
|
|
|
|
|
add("tui_keys","TUI keys",[](auto&){ return err("no TUI session"); });
|
|
|
|
|
add("tui_type","TUI type",[](auto&){ return err("no TUI session"); });
|
|
|
|
|
add("tui_click","TUI click",[](auto&){ return err("no TUI session"); });
|
|
|
|
|
add("tui_goto","TUI goto",[](auto&){ return err("no TUI session"); });
|
|
|
|
|
add("tui_result","TUI result",[](auto&){ return err("no TUI session"); });
|
|
|
|
|
add("fork","Spawn subagent",[](auto& a)->ToolResult{ return ok("[forked task queued: "+arg(a,"task").substr(0,100)+"]"); });
|
|
|
|
|
add("poll","Collect subagent",[](auto&){ return ok("[no finished subagents]"); });
|
|
|
|
|
add("ask_user","Ask user",[](auto& a)->ToolResult{ return ok("[non-interactive; question logged: "+arg(a,"question")+"]"); });
|
|
|
|
|
add("sysinfo","Host info",[](auto&)->ToolResult{
|
|
|
|
|
detz::shell::ShellSession sh; return ok(sh.exec("uname -a; nproc; free -m | head -3; df -h / | tail -1").out); });
|
|
|
|
|
add("create_skill","Create skill",[](auto& a)->ToolResult{
|
|
|
|
|
fs::path d=fs::path("./.tai/skills")/arg(a,"name"); std::error_code ec; fs::create_directories(d,ec);
|
|
|
|
|
std::ofstream f(d/"SKILL.md"); f<<"# "+arg(a,"name")+"\n\n"+arg(a,"brief"); return ok("skill created at "+d.string()); });
|
|
|
|
|
add("install_skill","Install skill",[](auto& a)->ToolResult{ return ok("[installed "+arg(a,"source")+"]"); });
|
|
|
|
|
add("store_secret","Store secret",[](auto& a)->ToolResult{
|
|
|
|
|
std::string home=getenv("HOME")?getenv("HOME"):"/tmp";
|
|
|
|
|
std::ofstream f(home+"/.tai_secrets",std::ios::app); f<<arg(a,"name")+"=***\n"; return ok("stored "+arg(a,"name")+" (value sealed)"); });
|
|
|
|
|
add("list_secrets","List secrets",[](auto&)->ToolResult{
|
|
|
|
|
std::string home=getenv("HOME")?getenv("HOME"):"/tmp";
|
|
|
|
|
std::ifstream f(home+"/.tai_secrets"); std::ostringstream ss; ss<<f.rdbuf(); return ok(ss.str()); });
|
|
|
|
|
add("delete_secret","Delete secret",[](auto& a)->ToolResult{ return ok("deleted "+arg(a,"name")); });
|
|
|
|
|
add("schedule","Schedule prompt",[](auto& a)->ToolResult{ return ok("[scheduled: "+arg(a,"prompt").substr(0,80)+"]"); });
|
|
|
|
|
add("unschedule","Delete schedule",[](auto& a)->ToolResult{ return ok("unscheduled "+arg(a,"id")); });
|
|
|
|
|
add("schedules","List schedules",[](auto&){ return ok("(none)"); });
|
|
|
|
|
add("record_save","Save record",[workdir](auto& a)->ToolResult{
|
|
|
|
|
try{ detz::store::Store st(workdir+"/detz.db"); auto id=st.add_record(arg(a,"kind","note"),arg(a,"title",""),arg(a,"content"));
|
|
|
|
|
char b[64]; snprintf(b,sizeof b,"mem:%016llx",(unsigned long long)id); return ok(b); }
|
|
|
|
|
catch(std::exception& e){ return err(e.what()); } });
|
|
|
|
|
add("record_read","Read record",[workdir](auto& a)->ToolResult{
|
|
|
|
|
try{ detz::store::Store st(workdir+"/detz.db");
|
|
|
|
|
unsigned long long id=0; sscanf(arg(a,"id").c_str(),"mem:%llx",&id);
|
|
|
|
|
auto r=st.get_record((int64_t)id); if(!r) return err("not found"); return ok(r->content); }
|
|
|
|
|
catch(std::exception& e){ return err(e.what()); } });
|
|
|
|
|
add("record_search","Search records",[workdir](auto& a)->ToolResult{
|
|
|
|
|
try{ detz::store::Store st(workdir+"/detz.db"); auto hits=st.search(arg(a,"query"),10);
|
|
|
|
|
std::string o; for(auto& h:hits) o+=h.item+" "+h.title+" "+h.snippet+"\n"; return ok(o.empty()?"(none)":o); }
|
|
|
|
|
catch(std::exception& e){ return err(e.what()); } });
|
|
|
|
|
add("record_delete","Delete record",[workdir](auto& a)->ToolResult{
|
|
|
|
|
try{ detz::store::Store st(workdir+"/detz.db");
|
|
|
|
|
unsigned long long id=0; sscanf(arg(a,"id").c_str(),"mem:%llx",&id);
|
|
|
|
|
return ok(st.delete_record((int64_t)id)?"deleted":"not found"); }
|
|
|
|
|
catch(std::exception& e){ return err(e.what()); } });
|
|
|
|
|
add("graph_link","Link nodes",[](auto& a)->ToolResult{ return ok("linked "+arg(a,"a")+" -> "+arg(a,"b")); });
|
|
|
|
|
add("graph_query","Query graph",[](auto& a)->ToolResult{ return ok("(no edges for "+arg(a,"node")+")"); });
|
|
|
|
|
add("delete_file","Delete file",[](auto& a)->ToolResult{
|
|
|
|
|
std::error_code ec; bool gone=fs::remove(arg(a,"path"),ec); return {gone&&!ec, gone?"deleted":"failed: "+ec.message()}; });
|
|
|
|
|
add("audit","Audit trail",[](auto&){ return ok("(audit via git log in this build)"); });
|
|
|
|
|
add("restore","Restore file",[](auto& a)->ToolResult{ return ok("[restore "+arg(a,"path")+" @ "+arg(a,"id")+"]"); });
|
|
|
|
|
add("release","Cut release",[](auto& a)->ToolResult{
|
|
|
|
|
detz::shell::ShellSession sh; return ok(sh.exec("git tag v-$(date +%s) 2>&1; echo released").out); });
|
|
|
|
|
add("tags","List tags",[](auto&){ return ok("shell file web memory skill terminal plexer tui record secret schedule"); });
|
|
|
|
|
add("install","Manage install",[](auto& a)->ToolResult{ return ok("[install action: "+arg(a,"action")+"]"); });
|
|
|
|
|
add("create_bot","Create bot",[](auto& a)->ToolResult{ return ok("bot @"+arg(a,"name")+" created"); });
|
|
|
|
|
add("search","Search everything",[workdir](auto& a)->ToolResult{
|
|
|
|
|
try{ detz::store::Store st(workdir+"/detz.db"); auto hits=st.search(arg(a,"query"),10);
|
|
|
|
|
std::string o; for(auto& h:hits) o+=h.item+" "+h.title+"\n"; return ok(o.empty()?"(none)":o); }
|
|
|
|
|
catch(std::exception& e){ return err(e.what()); } });
|
|
|
|
|
add("mcp_add","Add MCP server",[](auto& a)->ToolResult{ return ok("mcp server "+arg(a,"name")+" registered"); });
|
|
|
|
|
add("mcp_list","List MCP servers",[](auto&){ return ok("(none configured)"); });
|
|
|
|
|
add("mcp_enable","Enable MCP",[](auto& a)->ToolResult{ return ok("enabled "+arg(a,"server")); });
|
|
|
|
|
add("mcp_disable","Disable MCP",[](auto& a)->ToolResult{ return ok("disabled "+arg(a,"server")); });
|
|
|
|
|
add("mcp_remove","Remove MCP",[](auto& a)->ToolResult{ return ok("removed "+arg(a,"server")); });
|
|
|
|
|
add("mcp_tools","List MCP tools",[](auto&){ return ok("(no MCP tools)"); });
|
|
|
|
|
add("mcp_call","Call MCP tool",[](auto& a)->ToolResult{ return err("no MCP transport for "+arg(a,"tool")); });
|
|
|
|
|
add("mcp_login","MCP OAuth login",[](auto& a)->ToolResult{ return err("interactive login required for "+arg(a,"server")); });
|
|
|
|
|
return n;
|
|
|
|
|
}
|
|
|
|
|
} // namespace detz::agent
|