ohwow 0.8.1 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/index.js +2801 -1675
  2. package/dist/mcp-server/index.js +12 -12
  3. package/dist/migrations/107-code-skills.sql +20 -0
  4. package/dist/migrations/108-archive-procedure-skills.sql +29 -0
  5. package/dist/migrations/109-workspace-default-fs-paths.sql +19 -0
  6. package/dist/migrations/110-task-state-ttl.sql +28 -0
  7. package/dist/migrations/111-conversation-status.sql +25 -0
  8. package/dist/migrations/112-deliverables-created-at-iso.sql +40 -0
  9. package/dist/migrations/113-permission-requests.sql +36 -0
  10. package/dist/migrations/114-llm-calls-tool-telemetry.sql +32 -0
  11. package/dist/migrations/115-trigger-watchdog.sql +46 -0
  12. package/dist/migrations/116-self-findings.sql +46 -0
  13. package/dist/migrations/117-experiment-validations.sql +64 -0
  14. package/dist/migrations/118-validation-rollback.sql +33 -0
  15. package/dist/migrations/119-runtime-config-overrides.sql +44 -0
  16. package/dist/migrations/120-business-vitals.sql +44 -0
  17. package/dist/migrations/121-x-contact-events.sql +42 -0
  18. package/dist/migrations/122-video-jobs.sql +52 -0
  19. package/dist/migrations/123-insight-distiller.sql +68 -0
  20. package/dist/migrations/124-x-dm-messages.sql +55 -0
  21. package/dist/migrations/125-x-dm-messages-bodies.sql +40 -0
  22. package/dist/migrations/126-x-dm-signals.sql +52 -0
  23. package/dist/migrations/127-x-dm-contact-linking.sql +36 -0
  24. package/dist/migrations/128-attribution-view.sql +59 -0
  25. package/dist/migrations/129-x-posted-log.sql +36 -0
  26. package/dist/migrations/130-patches-attempted-log.sql +44 -0
  27. package/dist/web/assets/index-Bp9CoQ8c.css +1 -0
  28. package/dist/web/assets/index-C5xtuLcg.js +102 -0
  29. package/dist/web/index.html +2 -2
  30. package/package.json +5 -1
  31. package/dist/web/assets/index-Bgm-uSeA.js +0 -100
  32. package/dist/web/assets/index-DZAi92e-.css +0 -1
@@ -1,11 +1,11 @@
1
1
  import { createRequire } from 'module'; const require = createRequire(import.meta.url);
2
- import{McpServer as Ct}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as Pt}from"@modelcontextprotocol/sdk/server/stdio.js";import{readFileSync as R,existsSync as J}from"fs";import{join as D}from"path";import{homedir as nt}from"os";var k=class r{constructor(o,t,e){this.baseUrl=`http://127.0.0.1:${o}`,this.token=t,this.tokenPath=e}static async create(){let o=D(nt(),".ohwow"),t=D(o,"config.json"),e=D(o,"data","daemon.token"),n=7700;if(J(t))try{let c=R(t,"utf-8"),a=JSON.parse(c);a.port&&(n=a.port)}catch{}if(!J(e))throw new Error("OHWOW daemon is not running. Start it with: ohwow");let s=R(e,"utf-8").trim();if(!s)throw new Error("Couldn't authenticate with OHWOW daemon. Try: ohwow restart");let i=new r(n,s,e);try{await i.get("/health")}catch{throw new Error("OHWOW daemon is not running. Start it with: ohwow")}return i}refreshToken(){try{let o=R(this.tokenPath,"utf-8").trim();if(o&&o!==this.token)return this.token=o,!0}catch{}return!1}authHeaders(){return{Authorization:`Bearer ${this.token}`}}async fetchWithRetry(o,t){let e=await fetch(o,t);if(e.status===401&&this.refreshToken()){let n={...t.headers,...this.authHeaders()};e=await fetch(o,{...t,headers:n})}return e}async get(o){let t=await this.fetchWithRetry(`${this.baseUrl}${o}`,{headers:this.authHeaders()});if(!t.ok)throw new Error(`ohwow daemon error on GET ${o}: ${t.status}. Check daemon with: ohwow logs`);return t.json()}async post(o,t){let e=await this.fetchWithRetry(`${this.baseUrl}${o}`,{method:"POST",headers:{...this.authHeaders(),"Content-Type":"application/json"},body:JSON.stringify(t)});if(!e.ok)throw new Error(`ohwow daemon error on POST ${o}: ${e.status}. Check daemon with: ohwow logs`);return e.json()}async postSSE(o,t,e=12e4){let n=new AbortController,s=setTimeout(()=>n.abort(),e),i=[],c=async()=>fetch(`${this.baseUrl}${o}`,{method:"POST",headers:{...this.authHeaders(),"Content-Type":"application/json",Accept:"text/event-stream"},body:JSON.stringify(t),signal:n.signal});try{let a=await c();if(a.status===401&&this.refreshToken()&&(a=await c()),!a.ok)throw new Error(`Daemon API error: ${a.status} ${a.statusText}`);if(!a.body)throw new Error("No response body from daemon");let p=a.body.getReader(),g=new TextDecoder,f="";for(;;){let{done:m,value:rt}=await p.read();if(m)break;f+=g.decode(rt,{stream:!0});let M=f.split(`
3
- `);f=M.pop()||"";for(let I of M){if(!I.startsWith("data: "))continue;let W=I.slice(6).trim();if(W!=="[DONE]")try{let w=JSON.parse(W);if(w.type==="text"&&w.content)i.push(w.content);else if(w.type==="tool_start")i.push(`
4
- [Using tool: ${w.name}]
5
- `);else if(w.type==="error")i.push(`
6
- [Error: ${w.error}]
7
- `);else if(w.type==="done")break}catch{}}}return i.join("")}catch(a){if(a instanceof Error&&a.name==="AbortError")return i.join("")+`
8
- [Timed out after ${e/1e3}s. The task may still be running. Check with ohwow_list_tasks.]`;throw a}finally{clearTimeout(s)}}};import{z as l}from"zod";function L(r,o){r.tool("ohwow_chat","[Orchestrator] Send a message to the OHWOW orchestrator (88+ internal tools). Use this for: desktop control, automation creation, agent scheduling, approval management, agent state persistence, A2A protocol, PDF forms, media generation, and any multi-step request not covered by the direct tools. Do NOT use for simple listing or CRUD operations that have dedicated tools.",{message:l.string().describe("The message or instruction to send to the orchestrator")},async({message:t})=>{try{return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:t})||"No response from orchestrator"}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_list_agents","[Agents] List all agents in the OHWOW workspace with their status, role, and capabilities.",{},async()=>{try{let t=await o.get("/api/agents"),e=t.data||t;return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_run_agent","[Agents] Execute a specific agent with a prompt. Returns a task ID immediately (execution is async). Use ohwow_get_task to poll for status and result. Use ohwow_list_agents to find agent IDs.",{agentId:l.string().describe("The ID of the agent to run"),prompt:l.string().describe("The task or instruction for the agent")},async({agentId:t,prompt:e})=>{try{let n=await o.post("/api/tasks",{agentId:t,title:e});return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(n){return{content:[{type:"text",text:`Error: ${n instanceof Error?n.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_get_task","[Tasks] Get the status and result of a task by its ID.",{taskId:l.string().describe("The task ID to look up")},async({taskId:t})=>{try{let e=await o.get(`/api/tasks/${t}`);return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_list_tasks","[Tasks] List recent tasks. Optionally filter by status or agent.",{status:l.string().optional().describe("Filter by status: pending, running, completed, failed"),agentId:l.string().optional().describe("Filter by agent ID"),limit:l.number().optional().describe("Max number of tasks to return (default: 20)")},async({status:t,agentId:e,limit:n})=>{try{let s=new URLSearchParams;t&&s.set("status",t),e&&s.set("agentId",e),n&&s.set("limit",String(n));let i=s.toString(),c=await o.get(`/api/tasks${i?`?${i}`:""}`);return{content:[{type:"text",text:JSON.stringify(c,null,2)}]}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_workspace_status","[Workspace] Get workspace status: agent count, uptime, tier, and system stats.",{},async()=>{try{let t=await o.get("/api/dashboard/init");return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_llm","[LLM Organ] Invoke ohwow's model router for a specific sub-task. Pass a `purpose` (reasoning, generation, summarization, extraction, critique, translation, planning, classification, etc.) and a `prompt` string. ohwow resolves the agent's model_policy (if agentId is given), workspace defaults, and constraints, then picks a provider+model and runs the call. Returns { text, model_used, provider, purpose, tokens, cost_cents, latency_ms }. Use this when you want ohwow to act as your model selector instead of pinning to a specific model yourself.",{purpose:l.enum(["orchestrator_chat","agent_task","planning","browser_automation","memory_extraction","ocr","workflow_step","simple_classification","desktop_control","reasoning","generation","summarization","extraction","critique","translation","embedding"]).optional().describe('Semantic purpose that drives routing. Defaults to "reasoning".'),prompt:l.string().describe("The user prompt to send to the selected model."),system:l.string().optional().describe("Optional system prompt."),agentId:l.string().optional().describe("Agent ID to load model_policy from. Omit to use workspace defaults only."),max_tokens:l.number().optional().describe("Maximum output tokens."),temperature:l.number().optional().describe("Sampling temperature."),local_only:l.boolean().optional().describe("Force local inference (clamps modelSource to local)."),prefer_model:l.string().optional().describe("Call-site model override; wins over agent policy."),max_cost_cents:l.number().optional().describe("Advisory cost ceiling in cents."),difficulty:l.enum(["simple","moderate","complex"]).optional().describe("Difficulty hint for escalation.")},async t=>{try{let e=await o.post("/api/llm",t);return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as d}from"zod";function H(r,o){r.tool("ohwow_list_contacts","[CRM] List contacts in the workspace. Returns name, email, company, pipeline stage, and tags.",{search:d.string().optional().describe("Filter by name, email, or company"),limit:d.number().optional().describe("Max results (default: 50)")},async({search:t,limit:e})=>{try{let n=new URLSearchParams;t&&n.set("search",t),e&&n.set("limit",String(e));let s=n.toString(),i=await o.get(`/api/contacts${s?`?${s}`:""}`),c=i.data||i;return{content:[{type:"text",text:JSON.stringify(c,null,2)}]}}catch(n){return{content:[{type:"text",text:`Error: ${n instanceof Error?n.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_create_contact","[CRM] Add a new contact to the CRM.",{name:d.string().describe("Contact full name"),email:d.string().optional().describe("Email address"),phone:d.string().optional().describe("Phone number"),company:d.string().optional().describe("Company or organization"),tags:d.array(d.string()).optional().describe("Tags for categorization"),notes:d.string().optional().describe("Additional notes about the contact")},async({name:t,email:e,phone:n,company:s,tags:i,notes:c})=>{try{let a={name:t};e&&(a.email=e),n&&(a.phone=n),s&&(a.company=s),i&&(a.tags=i),c&&(a.notes=c);let p=await o.post("/api/contacts",a);return{content:[{type:"text",text:JSON.stringify(p,null,2)}]}}catch(a){return{content:[{type:"text",text:`Error: ${a instanceof Error?a.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_search_contacts","[CRM] Full-text search across contacts by name, email, company, or notes.",{query:d.string().describe("Search query")},async({query:t})=>{try{return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the search_contacts tool with query: "${t}". Return the results as-is.`},15e3)||"No contacts found"}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as y}from"zod";function F(r,o){r.tool("ohwow_list_workflows","[Workflows] List all workflows in the workspace with their steps and status.",{},async()=>{try{let t=await o.get("/api/workflows"),e=t.data||t;return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_run_workflow","[Workflows] Execute a workflow by ID. Use ohwow_list_workflows to find IDs. May take up to 60 seconds depending on complexity.",{workflowId:y.string().describe("The workflow ID to execute"),variables:y.record(y.string(),y.unknown()).optional().describe("Input variables for the workflow")},async({workflowId:t,variables:e})=>{try{let n=e?` with variables: ${JSON.stringify(e)}`:"";return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the run_workflow tool with workflowId: "${t}"${n}.`},6e4)||"Workflow started"}]}}catch(n){return{content:[{type:"text",text:`Error: ${n instanceof Error?n.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_list_automations","[Automations] List all automations with their triggers and status.",{},async()=>{try{let t=await o.get("/api/automations"),e=t.data||t;return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_run_automation","[Automations] Manually trigger an automation by ID. Use ohwow_list_automations to find IDs.",{automationId:y.string().describe("The automation ID to trigger")},async({automationId:t})=>{try{let e=await o.post(`/api/automations/${t}/execute`,{});return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as q}from"zod";function G(r,o){r.tool("ohwow_list_projects","[Projects] List all projects with status and task counts.",{},async()=>{try{let t=await o.get("/api/projects"),e=t.data||t;return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_create_project","[Projects] Create a new project for organizing work.",{name:q.string().describe("Project name"),description:q.string().optional().describe("Project description")},async({name:t,description:e})=>{try{let n={name:t};e&&(n.description=e);let s=await o.post("/api/projects",n);return{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(n){return{content:[{type:"text",text:`Error: ${n instanceof Error?n.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_list_goals","[Goals] List workspace goals with progress tracking.",{},async()=>{try{return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:"Use the list_goals tool. Return the results as-is."},15e3)||"No goals found"}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}})}import{z as T}from"zod";function z(r,o){r.tool("ohwow_list_knowledge","[Knowledge] List all documents in the knowledge base.",{},async()=>{try{return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:"Use the list_knowledge tool. Return the results as-is."},15e3)||"No knowledge documents found"}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_search_knowledge","[Knowledge] Semantic search across the knowledge base. Returns relevant document chunks.",{query:T.string().describe("The search query")},async({query:t})=>{try{return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the search_knowledge tool with query: "${t}". Return the results as-is.`},15e3)||"No results found"}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_add_knowledge_url","[Knowledge] Add a web page to the knowledge base. Fetches, chunks, and embeds the content for later search.",{url:T.string().describe("The URL to ingest"),title:T.string().optional().describe("Optional title for the document")},async({url:t,title:e})=>{try{let n=e?` with title: "${e}"`:"";return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the add_knowledge_from_url tool with url: "${t}"${n}.`},3e4)||"Knowledge document added"}]}}catch(n){return{content:[{type:"text",text:`Error: ${n instanceof Error?n.message:"Unknown error"}`}],isError:!0}}})}import{z as v}from"zod";function K(r,o){r.tool("ohwow_deep_research","[Research] Multi-source web research with synthesis. Timing: quick ~30s, thorough ~60s, comprehensive ~120s.",{question:v.string().describe("The research question"),depth:v.enum(["quick","thorough","comprehensive"]).optional().describe("Research depth (default: thorough)")},async({question:t,depth:e})=>{try{let n=e?` with depth: "${e}"`:"";return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the deep_research tool with question: "${t}"${n}. Return the full research report.`},18e4)||"No research results"}]}}catch(n){return{content:[{type:"text",text:`Error: ${n instanceof Error?n.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_scrape_url","[Research] Scrape a web page and return its structured content. Automatically handles anti-bot protection.",{url:v.string().describe("The URL to scrape")},async({url:t})=>{try{return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the scrape_url tool with url: "${t}". Return the scraped content.`},3e4)||"No content scraped"}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as S}from"zod";function B(r,o){r.tool("ohwow_send_message","[Messaging] Send a message via WhatsApp or Telegram. Use ohwow_list_chats to find chat IDs. The channel must be connected in the ohwow dashboard first.",{channel:S.enum(["whatsapp","telegram"]).describe("Messaging channel"),chatId:S.string().describe("Chat or contact ID to send to"),message:S.string().describe("Message text to send")},async({channel:t,chatId:e,message:n})=>{try{let s=t==="whatsapp"?"send_whatsapp_message":"send_telegram_message";return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the ${s} tool to send this message to chat "${e}": ${n}`},15e3)||"Message sent"}]}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_list_chats","[Messaging] List connected chats for WhatsApp or Telegram.",{channel:S.enum(["whatsapp","telegram"]).describe("Messaging channel")},async({channel:t})=>{try{let e=t==="whatsapp"?"list_whatsapp_chats":"list_telegram_chats";return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the ${e} tool. Return the results as-is.`},15e3)||"No chats found"}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}function V(r,o){r.tool("ohwow_list_sites","[Cloud] List all sites on the ohwow.fun cloud dashboard with status and URLs. Requires cloud connection.",{},async()=>{try{let t=await o.get("/api/cloud/sites");if(t.cloudConnected===!1)return{content:[{type:"text",text:"Not connected to ohwow.fun. Run `ohwow connect` to link your cloud account."}]};let e=t.data||[];return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_list_integrations","[Cloud] List connected integrations (Gmail, GitHub, Stripe, etc.) and their status. Requires cloud connection.",{},async()=>{try{let t=await o.get("/api/cloud/integrations");if(t.cloudConnected===!1)return{content:[{type:"text",text:"Not connected to ohwow.fun. Run `ohwow connect` to link your cloud account."}]};let e=t.data||[];return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}})}import{z as b}from"zod";import{join as h,dirname as gt}from"path";import{homedir as ft}from"os";import{existsSync as Z,readFileSync as yt}from"fs";import{fileURLToPath as xt}from"url";import{join as X}from"path";import{spawn as ct}from"child_process";import{openSync as pt,existsSync as lt,statSync as ut,renameSync as dt,writeFileSync as ie,unlinkSync as ce,readFileSync as pe}from"fs";import{readFileSync as st,writeFileSync as Zt,unlinkSync as Yt,existsSync as at,mkdirSync as te}from"fs";function _(r){try{if(!at(r))return null;let o=st(r,"utf-8");return JSON.parse(o)}catch{return null}}function E(r){try{return process.kill(r,0),!0}catch{return!1}}import it from"pino";var Q=it({level:process.env.LOG_LEVEL||(process.env.NODE_ENV==="production"?"info":"debug"),...process.env.NODE_ENV!=="production"&&{transport:{target:"pino-pretty",options:{colorize:!0}}}});var mt=10*1024*1024;function wt(r){return X(r,"daemon.log")}function ht(r){try{if(!lt(r))return;ut(r).size>mt&&dt(r,`${r}.1`)}catch{}}function C(r){return X(r,"daemon.pid")}async function x(r,o){let t=_(C(r));if(!t)return{running:!1};if(!E(t.pid))return{running:!1};let e=t.port||o;try{let n=await fetch(`http://localhost:${e}/health`,{signal:AbortSignal.timeout(2e3)});if(n.ok){let s=await n.json();if(s.status==="healthy"||s.status==="degraded")return{running:!0,pid:t.pid}}}catch{return{running:!0,pid:t.pid,healthy:!1}}return{running:!1}}function P(r,o,t){let e=wt(t);ht(e);let n=pt(e,"a"),i=r.endsWith(".ts")?["--import","tsx",r,"--daemon"]:[r,"--daemon"],c={...process.env,OHWOW_PORT:String(o)};process.env.OHWOW_WORKSPACE&&(c.OHWOW_WORKSPACE=process.env.OHWOW_WORKSPACE);let a=ct(process.execPath,i,{detached:!0,windowsHide:!0,stdio:["ignore",n,n],env:c});return a.unref(),a.pid}async function N(r,o=15e3){let t=Date.now()+o,e=300;for(;Date.now()<t;){try{if((await fetch(`http://localhost:${r}/health`,{signal:AbortSignal.timeout(1e3)})).ok)return!0}catch{}await new Promise(n=>setTimeout(n,e))}return!1}async function j(r){let o=_(C(r));if(!o||!E(o.pid))return!1;if(process.platform==="win32"&&o.port)try{return await fetch(`http://localhost:${o.port}/shutdown`,{method:"POST",signal:AbortSignal.timeout(3e3)}),!0}catch{try{return process.kill(o.pid),!0}catch{return!1}}try{return process.kill(o.pid,"SIGTERM"),!0}catch(t){return t.code==="EPERM"&&Q.error("Cannot stop daemon (PID %d): permission denied. It may be running as a different user.",o.pid),!1}}async function $(r,o=5e3){let t=C(r),e=Date.now()+o,n=200;for(;Date.now()<e;){let s=_(t);if(!s||!E(s.pid))return!0;await new Promise(i=>setTimeout(i,n))}return!1}function O(){let r=h(ft(),".ohwow"),o=h(r,"data"),t=7700,e=h(r,"config.json");if(Z(e))try{let c=yt(e,"utf-8"),a=JSON.parse(c);typeof a.port=="number"&&(t=a.port)}catch{}let n=gt(xt(import.meta.url)),i=[h(n,"..","..","index.js"),h(n,"..","..","..","index.js"),h(n,"..","..","..","index.ts"),h(n,"..","..","index.ts")].find(c=>Z(c))??null;return{dataDir:o,port:t,entryPath:i}}async function u(r){let o=await x(r.dataDir,r.port);return{running:o.running,healthy:o.running&&o.healthy!==!1,pid:o.pid??null,port:r.port,dataDir:r.dataDir,entryPath:r.entryPath}}function Y(r){r.tool("ohwow_daemon_status","[Daemon] Check whether the ohwow local daemon is running. Reports pid, port, health, and the resolved entry path. Does not modify state.",{},async()=>{try{let o=O(),t=await u(o);return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}catch(o){return{content:[{type:"text",text:`Error: ${o instanceof Error?o.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_daemon_stop","[Daemon] Stop the running ohwow daemon gracefully (SIGTERM on Unix, /shutdown on Windows). Returns whether it actually stopped within the timeout.",{timeoutMs:b.number().optional().describe("How long to wait for the daemon to stop before reporting failure. Default 5000ms.")},async({timeoutMs:o})=>{try{let t=O();if(!(await x(t.dataDir,t.port)).running)return{content:[{type:"text",text:JSON.stringify({ok:!0,alreadyStopped:!0,...await u(t)},null,2)}]};if(!await j(t.dataDir))return{content:[{type:"text",text:JSON.stringify({ok:!1,error:"Could not send stop signal (permission denied or stale pid)",...await u(t)},null,2)}],isError:!0};let s=await $(t.dataDir,o??5e3),i=await u(t);return{content:[{type:"text",text:JSON.stringify({ok:s,...i},null,2)}],isError:!s}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_daemon_start","[Daemon] Start the ohwow daemon in the background if it is not already running. Waits for the health endpoint before returning. No-op if the daemon is already healthy.",{timeoutMs:b.number().optional().describe("How long to wait for the daemon to become healthy. Default 15000ms.")},async({timeoutMs:o})=>{try{let t=O(),e=await x(t.dataDir,t.port);if(e.running&&e.healthy!==!1)return{content:[{type:"text",text:JSON.stringify({ok:!0,alreadyRunning:!0,...await u(t)},null,2)}]};if(!t.entryPath)return{content:[{type:"text",text:JSON.stringify({ok:!1,error:"Could not locate daemon entry (dist/index.js or src/index.ts)",...await u(t)},null,2)}],isError:!0};let n=P(t.entryPath,t.port,t.dataDir),s=await N(t.port,o??15e3),i=await u(t);return{content:[{type:"text",text:JSON.stringify({ok:s,spawnedPid:n,entryPath:t.entryPath,...i},null,2)}],isError:!s}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),r.tool("ohwow_daemon_restart","[Daemon] Restart the ohwow daemon: stop if running, wait for the PID to clear, then spawn a fresh background instance and wait for health. Use this after rebuilding dist/index.js so the running daemon picks up the new code. Safe to call even when the daemon is already dead \u2014 it will just start a fresh one.",{stopTimeoutMs:b.number().optional().describe("Timeout for the stop phase. Default 5000ms."),startTimeoutMs:b.number().optional().describe("Timeout for the start/health phase. Default 15000ms.")},async({stopTimeoutMs:o,startTimeoutMs:t})=>{try{let e=O();if(!e.entryPath)return{content:[{type:"text",text:JSON.stringify({ok:!1,error:"Could not locate daemon entry (dist/index.js or src/index.ts)",...await u(e)},null,2)}],isError:!0};let n=await x(e.dataDir,e.port),s={wasRunning:n.running,previousPid:n.pid??null};if(n.running){let p=await j(e.dataDir);if(s.stopSignalSent=p,!p)return{content:[{type:"text",text:JSON.stringify({ok:!1,phase:"stop",error:"Could not send stop signal",...s,...await u(e)},null,2)}],isError:!0};let g=await $(e.dataDir,o??5e3);if(s.stopped=g,!g)return{content:[{type:"text",text:JSON.stringify({ok:!1,phase:"stop",error:"Previous daemon did not exit within timeout",...s,...await u(e)},null,2)}],isError:!0}}let i=P(e.entryPath,e.port,e.dataDir);s.spawnedPid=i;let c=await N(e.port,t??15e3);s.ready=c;let a=await u(e);return{content:[{type:"text",text:JSON.stringify({ok:c,phase:c?"ready":"start",...s,...a},null,2)}],isError:!c}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}function tt(r,o){L(r,o),H(r,o),F(r,o),G(r,o),z(r,o),K(r,o),B(r,o),V(r,o),Y(r)}function et(r,o){r.resource("agents","ohwow://agents",{description:"All OHWOW agents with their descriptions, roles, and available tools"},async()=>{try{let t=await o.get("/api/agents"),e=t.data||t;return{contents:[{uri:"ohwow://agents",mimeType:"application/json",text:JSON.stringify(e,null,2)}]}}catch{return{contents:[{uri:"ohwow://agents",mimeType:"text/plain",text:"Could not load agents. Is the OHWOW daemon running?"}]}}}),r.resource("workspace","ohwow://workspace",{description:"OHWOW workspace status: tier, uptime, agent count, system stats"},async()=>{try{let t=await o.get("/api/dashboard/init");return{contents:[{uri:"ohwow://workspace",mimeType:"application/json",text:JSON.stringify(t,null,2)}]}}catch{return{contents:[{uri:"ohwow://workspace",mimeType:"text/plain",text:"Could not load workspace status. Is the OHWOW daemon running?"}]}}}),r.resource("contacts","ohwow://contacts",{description:"Recent CRM contacts with pipeline stage breakdown"},async()=>{try{let t=await o.get("/api/contacts?limit=10"),e=t.data||t;return{contents:[{uri:"ohwow://contacts",mimeType:"application/json",text:JSON.stringify(e,null,2)}]}}catch{return{contents:[{uri:"ohwow://contacts",mimeType:"text/plain",text:"Could not load contacts. Is the OHWOW daemon running?"}]}}}),r.resource("projects","ohwow://projects",{description:"Active projects with task counts and status"},async()=>{try{let t=await o.get("/api/projects"),e=t.data||t;return{contents:[{uri:"ohwow://projects",mimeType:"application/json",text:JSON.stringify(e,null,2)}]}}catch{return{contents:[{uri:"ohwow://projects",mimeType:"text/plain",text:"Could not load projects. Is the OHWOW daemon running?"}]}}}),r.resource("workflows","ohwow://workflows",{description:"Workflow and automation catalog with descriptions and trigger types"},async()=>{try{let[t,e]=await Promise.all([o.get("/api/workflows"),o.get("/api/automations")]),n=t.data||t,s=e.data||e;return{contents:[{uri:"ohwow://workflows",mimeType:"application/json",text:JSON.stringify({workflows:n,automations:s},null,2)}]}}catch{return{contents:[{uri:"ohwow://workflows",mimeType:"text/plain",text:"Could not load workflows. Is the OHWOW daemon running?"}]}}}),r.resource("capabilities","ohwow://capabilities",{description:"Complete list of all OHWOW MCP tools grouped by domain"},async()=>({contents:[{uri:"ohwow://capabilities",mimeType:"text/plain",text:kt}]}))}var kt=`OHWOW MCP Plugin \u2014 Quick Reference
2
+ import{McpServer as Et}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as Ot}from"@modelcontextprotocol/sdk/server/stdio.js";import{readFileSync as ee,existsSync as te}from"fs";import{join as re}from"path";import{readFileSync as Q,writeFileSync as Le,existsSync as M,mkdirSync as $e,readdirSync as He}from"fs";import{dirname as Pt,join as w}from"path";import{homedir as je}from"os";import Ue from"pino";var L=Ue({level:process.env.LOG_LEVEL||(process.env.NODE_ENV==="production"?"info":"debug"),...process.env.NODE_ENV!=="production"&&{transport:{target:"pino-pretty",options:{colorize:!0}}}});var C=w(je(),".ohwow"),Fe=w(C,"config.json"),Mt=w(C,"data","runtime.db"),D=7700;var N="default",$=w(C,"workspaces"),Y=w(C,"current-workspace"),W=w(C,"data");function H(o){return/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(o)}function O(o){let r=w($,o),e=w(r,"skills");return{name:o,dataDir:r,dbPath:w(r,"runtime.db"),skillsDir:e,compiledSkillsDir:w(e,".compiled")}}function Je(){if(!M(Y))return null;try{return Q(Y,"utf-8").trim()||null}catch{return null}}function ue(o){M(C)||$e(C,{recursive:!0}),Le(Y,o)}function ge(){if(!M($))return[];try{return He($,{withFileTypes:!0}).filter(o=>o.isDirectory()).map(o=>o.name).sort()}catch{return[]}}function S(){let o=process.env.OHWOW_WORKSPACE?.trim();if(o&&H(o))return O(o);let r=Je();if(r&&H(r))return O(r);let e=O(N);if(!M(e.dataDir)&&M(w(W,"runtime.db"))){let t=w(W,"skills");return{name:N,dataDir:W,dbPath:w(W,"runtime.db"),skillsDir:t,compiledSkillsDir:w(t,".compiled")}}return e}function Be(o){return w($,o,"workspace.json")}function Z(o){let r=Be(o);if(!M(r))return null;try{let e=Q(r,"utf-8");return JSON.parse(e)}catch(e){return L.warn(`[Config] Failed to parse ${r}: ${e}`),null}}function R(o){if(o===N)try{let e=Q(Fe,"utf-8");return JSON.parse(e).port??D}catch{return D}return Z(o)?.port??null}var j=class o{constructor(r,e,t){this.baseUrl=`http://127.0.0.1:${r}`,this.token=e,this.tokenPath=t}static async create(){let r=S(),e=R(r.name)??D,t=re(r.dataDir,"daemon.token");if(!te(t)){let a=re(W,"daemon.token");if(te(a))t=a;else throw new Error(`OHWOW daemon is not running for workspace "${r.name}". Start it with: ohwow workspace start ${r.name}`)}let n=ee(t,"utf-8").trim();if(!n)throw new Error(`Couldn't read daemon token for workspace "${r.name}". Try: ohwow workspace restart ${r.name}`);let s=new o(e,n,t);try{await s.get("/health")}catch{throw new Error(`OHWOW daemon for workspace "${r.name}" is not reachable on port ${e}. Start it with: ohwow workspace start ${r.name}`)}return s}async switchTo(r){if(!H(r))throw new Error(`Invalid workspace name: ${r}`);let e=O(r),t=R(r);if(t===null)throw new Error(`Workspace "${r}" has no port assigned yet. Start it once with: ohwow workspace start ${r}`);let n=re(e.dataDir,"daemon.token");if(!te(n))throw new Error(`Daemon for workspace "${r}" is not running. Start it with: ohwow workspace start ${r}`);let s=ee(n,"utf-8").trim();if(!s)throw new Error(`Empty daemon token for workspace "${r}"`);let a=`http://127.0.0.1:${t}`;try{let i=await fetch(`${a}/health`,{headers:{Authorization:`Bearer ${s}`},signal:AbortSignal.timeout(5e3)});if(!i.ok)throw new Error(`HTTP ${i.status}`)}catch(i){throw new Error(`Daemon for "${r}" not responding on port ${t}: ${i instanceof Error?i.message:i}`)}return this.baseUrl=a,this.token=s,this.tokenPath=n,ue(r),process.env.OHWOW_WORKSPACE=r,{port:t,workspaceName:r}}refreshToken(){try{let r=ee(this.tokenPath,"utf-8").trim();if(r&&r!==this.token)return this.token=r,!0}catch{}return!1}authHeaders(){return{Authorization:`Bearer ${this.token}`}}async fetchWithRetry(r,e){let t=await fetch(r,e);if(t.status===401&&this.refreshToken()){let n={...e.headers,...this.authHeaders()};t=await fetch(r,{...e,headers:n})}return t}async get(r){let e=await this.fetchWithRetry(`${this.baseUrl}${r}`,{headers:this.authHeaders()});if(!e.ok)throw new Error(`ohwow daemon error on GET ${r}: ${e.status}. Check daemon with: ohwow logs`);return e.json()}async post(r,e){let t=await this.fetchWithRetry(`${this.baseUrl}${r}`,{method:"POST",headers:{...this.authHeaders(),"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok)throw new Error(`ohwow daemon error on POST ${r}: ${t.status}. Check daemon with: ohwow logs`);return t.json()}async del(r){let e=await this.fetchWithRetry(`${this.baseUrl}${r}`,{method:"DELETE",headers:this.authHeaders()});if(!e.ok)throw new Error(`ohwow daemon error on DELETE ${r}: ${e.status}. Check daemon with: ohwow logs`);return e.json()}async patch(r,e){let t=await this.fetchWithRetry(`${this.baseUrl}${r}`,{method:"PATCH",headers:{...this.authHeaders(),"Content-Type":"application/json"},body:JSON.stringify(e)});if(!t.ok)throw new Error(`ohwow daemon error on PATCH ${r}: ${t.status}. Check daemon with: ohwow logs`);return t.json()}async postSSE(r,e,t=12e4){let n=new AbortController,s=setTimeout(()=>n.abort(),t),a=[],i=async()=>fetch(`${this.baseUrl}${r}`,{method:"POST",headers:{...this.authHeaders(),"Content-Type":"application/json",Accept:"text/event-stream"},body:JSON.stringify(e),signal:n.signal});try{let c=await i();if(c.status===401&&this.refreshToken()&&(c=await i()),!c.ok)throw new Error(`Daemon API error: ${c.status} ${c.statusText}`);if(!c.body)throw new Error("No response body from daemon");let p=c.body.getReader(),h=new TextDecoder,u="";for(;;){let{done:m,value:_}=await p.read();if(m)break;u+=h.decode(_,{stream:!0});let y=u.split(`
3
+ `);u=y.pop()||"";for(let E of y){if(!E.startsWith("data: "))continue;let T=E.slice(6).trim();if(T!=="[DONE]")try{let b=JSON.parse(T);if(b.type==="text"&&b.content)a.push(b.content);else if(b.type==="tool_start")a.push(`
4
+ [Using tool: ${b.name}]
5
+ `);else if(b.type==="error")a.push(`
6
+ [Error: ${b.error}]
7
+ `);else if(b.type==="done")break}catch{}}}return a.join("")}catch(c){if(c instanceof Error&&c.name==="AbortError")return a.join("")+`
8
+ [Timed out after ${t/1e3}s. The task may still be running. Check with ohwow_list_tasks.]`;throw c}finally{clearTimeout(s)}}};import{z as l}from"zod";function he(o,r){o.tool("ohwow_chat",'[Orchestrator] Send a message to the OHWOW orchestrator (88+ internal tools). Returns conversationId immediately and dispatches the turn in the background. Poll ohwow_get_chat until status !== "running" to read the final assistant message. Use this for: desktop control, automation creation, agent scheduling, approval management, agent state persistence, A2A protocol, PDF forms, media generation, and any multi-step request not covered by the direct tools. Do NOT use for simple listing or CRUD operations that have dedicated tools.',{message:l.string().describe("The message or instruction to send to the orchestrator"),sessionId:l.string().optional().describe("Optional conversation id to continue an existing session. Omit for a new conversation.")},async({message:e,sessionId:t})=>{try{let n=await r.post("/api/chat?async=1",{message:e,sessionId:t});return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(n){return{content:[{type:"text",text:`Error: ${n instanceof Error?n.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_get_chat",'[Orchestrator] Poll an in-flight or completed orchestrator conversation by id. Returns { status, messages, last_error, ... }. Status: "running" = still in flight, keep polling. "done" = final assistant message is in messages[]. "error" = look at last_error. Use after ohwow_chat to wait for the turn to complete.',{conversationId:l.string().describe("The conversation id returned by ohwow_chat")},async({conversationId:e})=>{try{let t=await r.get(`/api/chat/${e}`);return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_list_agents","[Agents] List all agents in the OHWOW workspace with their status, role, and capabilities.",{},async()=>{try{let e=await r.get("/api/agents"),t=e.data||e;return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_run_agent","[Agents] Execute a specific agent with a prompt. Returns a task ID immediately (execution is async). Use ohwow_get_task to poll for status and result. Use ohwow_list_agents to find agent IDs.",{agentId:l.string().describe("The ID of the agent to run"),prompt:l.string().describe("The task or instruction for the agent")},async({agentId:e,prompt:t})=>{try{let n=await r.post("/api/tasks",{agentId:e,title:t});return{content:[{type:"text",text:JSON.stringify(n,null,2)}]}}catch(n){return{content:[{type:"text",text:`Error: ${n instanceof Error?n.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_get_task","[Tasks] Get the status and result of a task by its ID.",{taskId:l.string().describe("The task ID to look up")},async({taskId:e})=>{try{let t=await r.get(`/api/tasks/${e}`);return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_list_tasks","[Tasks] List recent tasks. Optionally filter by status or agent.",{status:l.string().optional().describe("Filter by status: pending, running, completed, failed"),agentId:l.string().optional().describe("Filter by agent ID"),limit:l.number().optional().describe("Max number of tasks to return (default: 20)")},async({status:e,agentId:t,limit:n})=>{try{let s=new URLSearchParams;e&&s.set("status",e),t&&s.set("agentId",t),n&&s.set("limit",String(n));let a=s.toString(),i=await r.get(`/api/tasks${a?`?${a}`:""}`);return{content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_workspace_status","[Workspace] Get workspace status: agent count, uptime, tier, and system stats.",{},async()=>{try{let e=await r.get("/api/dashboard/init");return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_llm","[LLM Organ] Invoke ohwow's model router for a specific sub-task. Pass a `purpose` (reasoning, generation, summarization, extraction, critique, translation, planning, classification, etc.) and either a `prompt` string OR a `messages` history array. Optionally pass `tools` to enable tool calls. ohwow resolves the agent's model_policy (if agentId is given), workspace defaults, and constraints, then picks a provider+model and runs the call. Returns { text, content: ContentBlock[], model_used, provider, purpose, tokens, cost_cents, latency_ms }. The caller is responsible for executing tool calls and looping by appending tool_result blocks to `messages` on the next call.",{purpose:l.enum(["orchestrator_chat","agent_task","planning","browser_automation","memory_extraction","ocr","workflow_step","simple_classification","desktop_control","reasoning","generation","summarization","extraction","critique","translation","embedding"]).optional().describe('Semantic purpose that drives routing. Defaults to "reasoning".'),prompt:l.string().optional().describe("The user prompt to send to the selected model. Omit when passing `messages` instead."),messages:l.array(l.object({role:l.enum(["user","assistant","system","tool"]),content:l.union([l.string(),l.array(l.record(l.string(),l.unknown()))]),tool_calls:l.array(l.object({id:l.string(),type:l.literal("function"),function:l.object({name:l.string(),arguments:l.string()})})).optional(),tool_call_id:l.string().optional()})).optional().describe("Multi-turn conversation history. Preferred over `prompt` for tool-use loops \u2014 append assistant tool_calls and user tool_result blocks each iteration."),tools:l.array(l.object({name:l.string(),description:l.string().optional(),input_schema:l.record(l.string(),l.unknown()).optional()})).optional().describe("Optional tool definitions. When non-empty, the response `content` array includes `tool_use` blocks the caller must execute and feed back via `messages`."),tool_choice:l.enum(["auto","required","none"]).optional().describe("Tool selection mode. Defaults to provider default."),system:l.string().optional().describe("Optional system prompt."),agentId:l.string().optional().describe("Agent ID to load model_policy from. Omit to use workspace defaults only."),max_tokens:l.number().optional().describe("Maximum output tokens."),temperature:l.number().optional().describe("Sampling temperature."),local_only:l.boolean().optional().describe("Force local inference (clamps modelSource to local)."),prefer_model:l.string().optional().describe("Call-site model override; wins over agent policy."),max_cost_cents:l.number().optional().describe("Advisory cost ceiling in cents."),difficulty:l.enum(["simple","moderate","complex"]).optional().describe("Difficulty hint for escalation.")},async e=>{try{let t=await r.post("/api/llm",e);return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}})}import{z as x}from"zod";function me(o,r){o.tool("ohwow_list_contacts","[CRM] List contacts in the workspace. Returns name, email, company, pipeline stage, and tags.",{search:x.string().optional().describe("Filter by name, email, or company"),limit:x.number().optional().describe("Max results (default: 50)")},async({search:e,limit:t})=>{try{let n=new URLSearchParams;e&&n.set("search",e),t&&n.set("limit",String(t));let s=n.toString(),a=await r.get(`/api/contacts${s?`?${s}`:""}`),i=a.data||a;return{content:[{type:"text",text:JSON.stringify(i,null,2)}]}}catch(n){return{content:[{type:"text",text:`Error: ${n instanceof Error?n.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_create_contact","[CRM] Add a new contact to the CRM.",{name:x.string().describe("Contact full name"),email:x.string().optional().describe("Email address"),phone:x.string().optional().describe("Phone number"),company:x.string().optional().describe("Company or organization"),tags:x.array(x.string()).optional().describe("Tags for categorization"),notes:x.string().optional().describe("Additional notes about the contact")},async({name:e,email:t,phone:n,company:s,tags:a,notes:i})=>{try{let c={name:e};t&&(c.email=t),n&&(c.phone=n),s&&(c.company=s),a&&(c.tags=a),i&&(c.notes=i);let p=await r.post("/api/contacts",c);return{content:[{type:"text",text:JSON.stringify(p,null,2)}]}}catch(c){return{content:[{type:"text",text:`Error: ${c instanceof Error?c.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_search_contacts","[CRM] Full-text search across contacts by name, email, company, or notes.",{query:x.string().describe("Search query")},async({query:e})=>{try{return{content:[{type:"text",text:await r.postSSE("/api/chat",{message:`Use the search_contacts tool with query: "${e}". Return the results as-is.`},15e3)||"No contacts found"}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}})}import{z as U}from"zod";function fe(o,r){o.tool("ohwow_list_workflows","[Workflows] List all workflows in the workspace with their steps and status.",{},async()=>{try{let e=await r.get("/api/workflows"),t=e.data||e;return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_run_workflow","[Workflows] Execute a workflow by ID. Use ohwow_list_workflows to find IDs. May take up to 60 seconds depending on complexity.",{workflowId:U.string().describe("The workflow ID to execute"),variables:U.record(U.string(),U.unknown()).optional().describe("Input variables for the workflow")},async({workflowId:e,variables:t})=>{try{let n=t?` with variables: ${JSON.stringify(t)}`:"";return{content:[{type:"text",text:await r.postSSE("/api/chat",{message:`Use the run_workflow tool with workflowId: "${e}"${n}.`},6e4)||"Workflow started"}]}}catch(n){return{content:[{type:"text",text:`Error: ${n instanceof Error?n.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_list_automations","[Automations] List all automations with their triggers and status.",{},async()=>{try{let e=await r.get("/api/automations"),t=e.data||e;return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_run_automation","[Automations] Manually trigger an automation by ID. Use ohwow_list_automations to find IDs.",{automationId:U.string().describe("The automation ID to trigger")},async({automationId:e})=>{try{let t=await r.post(`/api/automations/${e}/execute`,{});return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}})}import{z as we}from"zod";function ye(o,r){o.tool("ohwow_list_projects","[Projects] List all projects with status and task counts.",{},async()=>{try{let e=await r.get("/api/projects"),t=e.data||e;return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_create_project","[Projects] Create a new project for organizing work.",{name:we.string().describe("Project name"),description:we.string().optional().describe("Project description")},async({name:e,description:t})=>{try{let n={name:e};t&&(n.description=t);let s=await r.post("/api/projects",n);return{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(n){return{content:[{type:"text",text:`Error: ${n instanceof Error?n.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_list_goals","[Goals] List workspace goals with progress tracking.",{},async()=>{try{return{content:[{type:"text",text:await r.postSSE("/api/chat",{message:"Use the list_goals tool. Return the results as-is."},15e3)||"No goals found"}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as oe}from"zod";function _e(o,r){o.tool("ohwow_list_knowledge","[Knowledge] List all documents in the knowledge base.",{},async()=>{try{return{content:[{type:"text",text:await r.postSSE("/api/chat",{message:"Use the list_knowledge tool. Return the results as-is."},15e3)||"No knowledge documents found"}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_search_knowledge","[Knowledge] Semantic search across the knowledge base. Returns relevant document chunks.",{query:oe.string().describe("The search query")},async({query:e})=>{try{return{content:[{type:"text",text:await r.postSSE("/api/chat",{message:`Use the search_knowledge tool with query: "${e}". Return the results as-is.`},15e3)||"No results found"}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_add_knowledge_url","[Knowledge] Add a web page to the knowledge base. Fetches, chunks, and embeds the content for later search.",{url:oe.string().describe("The URL to ingest"),title:oe.string().optional().describe("Optional title for the document")},async({url:e,title:t})=>{try{let n=t?` with title: "${t}"`:"";return{content:[{type:"text",text:await r.postSSE("/api/chat",{message:`Use the add_knowledge_from_url tool with url: "${e}"${n}.`},3e4)||"Knowledge document added"}]}}catch(n){return{content:[{type:"text",text:`Error: ${n instanceof Error?n.message:"Unknown error"}`}],isError:!0}}})}import{z as ne}from"zod";function be(o,r){o.tool("ohwow_deep_research","[Research] Multi-source web research with synthesis. Timing: quick ~30s, thorough ~60s, comprehensive ~120s.",{question:ne.string().describe("The research question"),depth:ne.enum(["quick","thorough","comprehensive"]).optional().describe("Research depth (default: thorough)")},async({question:e,depth:t})=>{try{let n=t?` with depth: "${t}"`:"";return{content:[{type:"text",text:await r.postSSE("/api/chat",{message:`Use the deep_research tool with question: "${e}"${n}. Return the full research report.`},18e4)||"No research results"}]}}catch(n){return{content:[{type:"text",text:`Error: ${n instanceof Error?n.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_scrape_url","[Research] Scrape a web page and return its structured content. Automatically handles anti-bot protection.",{url:ne.string().describe("The URL to scrape")},async({url:e})=>{try{return{content:[{type:"text",text:await r.postSSE("/api/chat",{message:`Use the scrape_url tool with url: "${e}". Return the scraped content.`},3e4)||"No content scraped"}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}})}import{z as F}from"zod";function ke(o,r){o.tool("ohwow_send_message","[Messaging] Send a message via WhatsApp or Telegram. Use ohwow_list_chats to find chat IDs. The channel must be connected in the ohwow dashboard first.",{channel:F.enum(["whatsapp","telegram"]).describe("Messaging channel"),chatId:F.string().describe("Chat or contact ID to send to"),message:F.string().describe("Message text to send")},async({channel:e,chatId:t,message:n})=>{try{let s=e==="whatsapp"?"send_whatsapp_message":"send_telegram_message";return{content:[{type:"text",text:await r.postSSE("/api/chat",{message:`Use the ${s} tool to send this message to chat "${t}": ${n}`},15e3)||"Message sent"}]}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_list_chats","[Messaging] List connected chats for WhatsApp or Telegram.",{channel:F.enum(["whatsapp","telegram"]).describe("Messaging channel")},async({channel:e})=>{try{let t=e==="whatsapp"?"list_whatsapp_chats":"list_telegram_chats";return{content:[{type:"text",text:await r.postSSE("/api/chat",{message:`Use the ${t} tool. Return the results as-is.`},15e3)||"No chats found"}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}})}function xe(o,r){o.tool("ohwow_list_sites","[Cloud] List all sites on the ohwow.fun cloud dashboard with status and URLs. Requires cloud connection.",{},async()=>{try{let e=await r.get("/api/cloud/sites");if(e.cloudConnected===!1)return{content:[{type:"text",text:"Not connected to ohwow.fun. Run `ohwow connect` to link your cloud account."}]};let t=e.data||[];return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_list_integrations","[Cloud] List connected integrations (Gmail, GitHub, Stripe, etc.) and their status. Requires cloud connection.",{},async()=>{try{let e=await r.get("/api/cloud/integrations");if(e.cloudConnected===!1)return{content:[{type:"text",text:"Not connected to ohwow.fun. Run `ohwow connect` to link your cloud account."}]};let t=e.data||[];return{content:[{type:"text",text:JSON.stringify(t,null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}})}import{z as q}from"zod";import{join as K,dirname as tt}from"path";import{existsSync as rt}from"fs";import{fileURLToPath as ot}from"url";import{join as ve}from"path";import{spawn as Ge}from"child_process";import{openSync as ze,existsSync as Ve,statSync as Xe,renameSync as Ye,writeFileSync as cr,unlinkSync as lr,readFileSync as dr}from"fs";import{readFileSync as qe,writeFileSync as tr,unlinkSync as rr,existsSync as Ke,mkdirSync as or}from"fs";function J(o){try{if(!Ke(o))return null;let r=qe(o,"utf-8");return JSON.parse(r)}catch{return null}}function B(o){try{return process.kill(o,0),!0}catch{return!1}}var Qe=10*1024*1024;function Ze(o){return ve(o,"daemon.log")}function et(o){try{if(!Ve(o))return;Xe(o).size>Qe&&Ye(o,`${o}.1`)}catch{}}function se(o){return ve(o,"daemon.pid")}async function A(o,r){let e=J(se(o));if(!e)return{running:!1};if(!B(e.pid))return{running:!1};let t=e.port||r;try{let n=await fetch(`http://localhost:${t}/health`,{signal:AbortSignal.timeout(2e3)});if(n.ok){let s=await n.json();if(s.status==="healthy"||s.status==="degraded")return{running:!0,pid:e.pid}}}catch{return{running:!0,pid:e.pid,healthy:!1}}return{running:!1}}function ae(o,r,e){let t=Ze(e);et(t);let n=ze(t,"a"),a=o.endsWith(".ts")?["--import","tsx",o,"--daemon"]:[o,"--daemon"],i={...process.env,OHWOW_PORT:String(r)};process.env.OHWOW_WORKSPACE&&(i.OHWOW_WORKSPACE=process.env.OHWOW_WORKSPACE);let c=Ge(process.execPath,a,{detached:!0,windowsHide:!0,stdio:["ignore",n,n],env:i});return c.unref(),c.pid}async function ie(o,r=15e3){let e=Date.now()+r,t=300;for(;Date.now()<e;){try{if((await fetch(`http://localhost:${o}/health`,{signal:AbortSignal.timeout(1e3)})).ok)return!0}catch{}await new Promise(n=>setTimeout(n,t))}return!1}async function ce(o){let r=J(se(o));if(!r||!B(r.pid))return!1;if(process.platform==="win32"&&r.port)try{return await fetch(`http://localhost:${r.port}/shutdown`,{method:"POST",signal:AbortSignal.timeout(3e3)}),!0}catch{try{return process.kill(r.pid),!0}catch{return!1}}try{return process.kill(r.pid,"SIGTERM"),!0}catch(e){return e.code==="EPERM"&&L.error("Cannot stop daemon (PID %d): permission denied. It may be running as a different user.",r.pid),!1}}async function le(o,r=5e3){let e=se(o),t=Date.now()+r,n=200;for(;Date.now()<t;){let s=J(e);if(!s||!B(s.pid))return!0;await new Promise(a=>setTimeout(a,n))}return!1}function G(){let o=S(),r=R(o.name)??D,e=tt(ot(import.meta.url)),n=[K(e,"..","..","index.js"),K(e,"..","..","..","index.js"),K(e,"..","..","..","index.ts"),K(e,"..","..","index.ts")].find(s=>rt(s))??null;return{workspaceName:o.name,dataDir:o.dataDir,port:r,entryPath:n}}async function k(o){let r=await A(o.dataDir,o.port);return{running:r.running,healthy:r.running&&r.healthy!==!1,pid:r.pid??null,port:o.port,dataDir:o.dataDir,entryPath:o.entryPath}}function Ee(o){o.tool("ohwow_daemon_status","[Daemon] Check whether the ohwow local daemon is running. Reports pid, port, health, and the resolved entry path. Does not modify state.",{},async()=>{try{let r=G(),e=await k(r);return{content:[{type:"text",text:JSON.stringify(e,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_daemon_stop","[Daemon] Stop the running ohwow daemon gracefully (SIGTERM on Unix, /shutdown on Windows). Returns whether it actually stopped within the timeout.",{timeoutMs:q.number().optional().describe("How long to wait for the daemon to stop before reporting failure. Default 5000ms.")},async({timeoutMs:r})=>{try{let e=G();if(!(await A(e.dataDir,e.port)).running)return{content:[{type:"text",text:JSON.stringify({ok:!0,alreadyStopped:!0,...await k(e)},null,2)}]};if(!await ce(e.dataDir))return{content:[{type:"text",text:JSON.stringify({ok:!1,error:"Could not send stop signal (permission denied or stale pid)",...await k(e)},null,2)}],isError:!0};let s=await le(e.dataDir,r??5e3),a=await k(e);return{content:[{type:"text",text:JSON.stringify({ok:s,...a},null,2)}],isError:!s}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_daemon_start","[Daemon] Start the ohwow daemon in the background if it is not already running. Waits for the health endpoint before returning. No-op if the daemon is already healthy.",{timeoutMs:q.number().optional().describe("How long to wait for the daemon to become healthy. Default 15000ms.")},async({timeoutMs:r})=>{try{let e=G(),t=await A(e.dataDir,e.port);if(t.running&&t.healthy!==!1)return{content:[{type:"text",text:JSON.stringify({ok:!0,alreadyRunning:!0,...await k(e)},null,2)}]};if(!e.entryPath)return{content:[{type:"text",text:JSON.stringify({ok:!1,error:"Could not locate daemon entry (dist/index.js or src/index.ts)",...await k(e)},null,2)}],isError:!0};let n=ae(e.entryPath,e.port,e.dataDir),s=await ie(e.port,r??15e3),a=await k(e);return{content:[{type:"text",text:JSON.stringify({ok:s,spawnedPid:n,entryPath:e.entryPath,...a},null,2)}],isError:!s}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_daemon_restart","[Daemon] Restart the ohwow daemon: stop if running, wait for the PID to clear, then spawn a fresh background instance and wait for health. Use this after rebuilding dist/index.js so the running daemon picks up the new code. Safe to call even when the daemon is already dead \u2014 it will just start a fresh one.",{stopTimeoutMs:q.number().optional().describe("Timeout for the stop phase. Default 5000ms."),startTimeoutMs:q.number().optional().describe("Timeout for the start/health phase. Default 15000ms.")},async({stopTimeoutMs:r,startTimeoutMs:e})=>{try{let t=G();if(!t.entryPath)return{content:[{type:"text",text:JSON.stringify({ok:!1,error:"Could not locate daemon entry (dist/index.js or src/index.ts)",...await k(t)},null,2)}],isError:!0};let n=await A(t.dataDir,t.port),s={wasRunning:n.running,previousPid:n.pid??null};if(n.running){let p=await ce(t.dataDir);if(s.stopSignalSent=p,!p)return{content:[{type:"text",text:JSON.stringify({ok:!1,phase:"stop",error:"Could not send stop signal",...s,...await k(t)},null,2)}],isError:!0};let h=await le(t.dataDir,r??5e3);if(s.stopped=h,!h)return{content:[{type:"text",text:JSON.stringify({ok:!1,phase:"stop",error:"Previous daemon did not exit within timeout",...s,...await k(t)},null,2)}],isError:!0}}let a=ae(t.entryPath,t.port,t.dataDir);s.spawnedPid=a;let i=await ie(t.port,e??15e3);s.ready=i;let c=await k(t);return{content:[{type:"text",text:JSON.stringify({ok:i,phase:i?"ready":"start",...s,...c},null,2)}],isError:!i}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}})}import{z as nt}from"zod";async function st(){let o=S().name,r=Array.from(new Set([...ge(),N])).sort(),e=[];for(let t of r){let n=Z(t),s=R(t),a=O(t),i=!1,c;if(s!==null)try{let p=await A(a.dataDir,s);i=p.running,c=p.pid}catch{}e.push({name:t,focused:t===o,mode:n?n.mode:"default",...n?.displayName?{displayName:n.displayName}:{},port:s,running:i,...c!==void 0?{pid:c}:{},...n?.cloudWorkspaceId?{cloudWorkspaceId:n.cloudWorkspaceId}:{}})}return e}function Oe(o,r){o.tool("ohwow_workspace_list","[Workspace] List every local workspace under ~/.ohwow/workspaces with mode, port, and live running status. Use this to discover what workspaces exist before switching with ohwow_workspace_use.",{},async()=>{try{let e=await st();return{content:[{type:"text",text:JSON.stringify({workspaces:e},null,2)}]}}catch(e){return{content:[{type:"text",text:`Error listing workspaces: ${e instanceof Error?e.message:e}`}],isError:!0}}}),o.tool("ohwow_workspace_use","[Workspace] Switch which workspace this MCP session targets. Reconnects the api-client to the named workspace's daemon (must already be running \u2014 start it with `ohwow workspace start <name>` from a terminal first if needed). Also updates ~/.ohwow/current-workspace so future sessions default to it. All subsequent tool calls in this MCP session will hit the new workspace.",{name:nt.string().describe('Workspace name (must already exist under ~/.ohwow/workspaces or be the legacy "default")')},async({name:e})=>{try{let t=S().name,n=await r.switchTo(e);return{content:[{type:"text",text:JSON.stringify({ok:!0,switched:!0,previous:t,current:n.workspaceName,port:n.port,message:`MCP session now targets workspace "${n.workspaceName}" on port ${n.port}. Future MCP launches will also default to this workspace.`},null,2)}]}}catch(t){return{content:[{type:"text",text:JSON.stringify({ok:!1,error:t instanceof Error?t.message:String(t)},null,2)}],isError:!0}}})}import{z as f}from"zod";var at=`Workspace-unique identifier for this MCP server (alphanumeric, dashes, underscores). Used as the namespace prefix when the server's tools are exposed to agents (e.g. name="avenued" \u2192 tools become mcp__avenued__<tool>).`,it='SENSITIVE. HTTP headers sent with every request to the MCP server, e.g. { "Authorization": "Bearer sk-..." }. Stored encrypted-equivalent in the workspace DB (never returned by list, never logged in plaintext, never exposed to agent context \u2014 injected at the transport layer).',ct='SENSITIVE. Environment variables for the stdio subprocess, e.g. { "GITHUB_TOKEN": "ghp_..." }. Stored in the workspace DB and passed to the child process at spawn time. Never returned by list, never logged in plaintext, never exposed to agent context.';function Ce(o,r){o.tool("ohwow_add_mcp_server","[MCP] Register a third-party MCP server in the current workspace. Its tools become available to all agents (and the orchestrator) on the next turn. Credentials in `headers` or `env` are SENSITIVE \u2014 they stay on the local daemon, never transit through the LLM, and are never returned by list/inspect calls. Use for hooking external platforms (Stripe, GitHub, Notion, custom internal MCPs) into an ohwow workspace.",{name:f.string().describe(at),transport:f.enum(["http","stdio"]).describe('Transport type. "http" for Streamable HTTP servers (most hosted MCPs); "stdio" for local subprocess servers launched with command+args.'),url:f.string().optional().describe('Required for transport="http". Full URL of the MCP endpoint, e.g. https://example.com/api/mcp. Must be a publicly routable HTTPS URL \u2014 private/link-local/metadata IPs are rejected.'),command:f.string().optional().describe('Required for transport="stdio". Executable to run, e.g. "npx" or "node".'),args:f.array(f.string()).optional().describe('For transport="stdio". Command-line arguments to pass to the executable.'),headers:f.record(f.string(),f.string()).optional().describe(it),env:f.record(f.string(),f.string()).optional().describe(ct),description:f.string().optional().describe("Human-readable description of what this server does. Shown in ohwow_list_mcp_servers output."),enabled:f.boolean().optional().describe("Whether the server should be connected on daemon startup. Default: true.")},async({name:e,transport:t,url:n,command:s,args:a,headers:i,env:c,description:p,enabled:h})=>{try{let u={name:e,transport:t};n&&(u.url=n),s&&(u.command=s),a&&(u.args=a),i&&(u.headers=i),c&&(u.env=c),p&&(u.description=p),h===!1&&(u.enabled=!1);let m=await r.post("/api/mcp/servers",u);return m.error?{content:[{type:"text",text:`Couldn't register MCP server: ${m.error}`}],isError:!0}:{content:[{type:"text",text:JSON.stringify({ok:!0,server:m.server,note:"Server registered. Credentials stored on-daemon and redacted from this response. Call ohwow_test_mcp_server to verify connectivity and list exposed tools."},null,2)}]}}catch(u){return{content:[{type:"text",text:`Error: ${u instanceof Error?u.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_list_mcp_servers",'[MCP] List all third-party MCP servers registered in the current workspace. Response shows name, transport, URL (for HTTP) or command (for stdio), description, enabled flag, and \u2014 for servers with credentials \u2014 the header/env KEY names replaced with "<set>". Actual credential values are NEVER returned. Does NOT perform live connections; use ohwow_test_mcp_server for that.',{},async()=>{try{let t=(await r.get("/api/mcp/servers")).servers??[];return{content:[{type:"text",text:JSON.stringify({count:t.length,servers:t},null,2)}]}}catch(e){return{content:[{type:"text",text:`Error: ${e instanceof Error?e.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_remove_mcp_server","[MCP] Remove a registered MCP server from the current workspace by name. Disconnects the server immediately and drops its tools from the agent tool surface on the next turn. The stored credentials are deleted along with the config.",{name:f.string().describe("Name of the MCP server to remove (must match a name from ohwow_list_mcp_servers).")},async({name:e})=>{try{let t=await r.del(`/api/mcp/servers/${encodeURIComponent(e)}`);return t.error?{content:[{type:"text",text:`Couldn't remove MCP server: ${t.error}`}],isError:!0}:{content:[{type:"text",text:JSON.stringify({ok:!0,removed:t.removed??e},null,2)}]}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}}),o.tool("ohwow_test_mcp_server","[MCP] Verify a registered MCP server connects, and return the names of tools it exposes. Uses the stored config (including credentials) but returns only tool NAMES \u2014 no schemas, no credential echo, no connection details. Use this right after ohwow_add_mcp_server to confirm the server is reachable and authenticated, and to discover what tools will become available to agents.",{name:f.string().describe("Name of a registered MCP server (from ohwow_list_mcp_servers).")},async({name:e})=>{try{let t=await r.post(`/api/mcp/servers/${encodeURIComponent(e)}/test`,{});return t.success?{content:[{type:"text",text:JSON.stringify({ok:!0,toolCount:t.toolCount??0,toolNames:t.toolNames??[],latencyMs:t.latencyMs},null,2)}]}:{content:[{type:"text",text:JSON.stringify({ok:!1,error:t.error??"Connection failed",latencyMs:t.latencyMs},null,2)}],isError:!0}}catch(t){return{content:[{type:"text",text:`Error: ${t instanceof Error?t.message:"Unknown error"}`}],isError:!0}}})}import{z as d}from"zod";var lt="Workspace-unique identifier for this agent. Alphanumeric plus dashes and underscores only (no spaces). Used by ohwow_get_agent, ohwow_update_agent, ohwow_delete_agent, and ohwow_run_agent to look up the row.",dt="The agent's core instructions. Stored verbatim and injected as the system message on every task run. Business-sensitive context belongs here; it is scoped to the current workspace and is NOT returned by ohwow_list_agents (which returns only summary fields). Use ohwow_get_agent to read it back.",pt='Explicit list of tool names the agent is allowed to call. If omitted, the agent inherits the workspace default tool surface. If provided, the agent sees ONLY these tools. Names must be either internal tool identifiers (e.g. "list_tasks", "scrape_url") or external MCP-server tools in the "mcp__<server>__<tool>" shape. Unknown internal names are rejected at create time so misconfigured agents fail fast.';async function I(o,r){return((await o.get("/api/agents")).data??[]).find(n=>n.name===r)??null}function g(o){return{content:[{type:"text",text:o}],isError:!0}}function P(o){return{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}function Se(o,r){o.tool("ohwow_create_agent","[Agents] Create a new agent in the current workspace. Writes directly to the agents table via the local daemon. Use this instead of ohwow_chat for agent creation \u2014 the orchestrator has no typed create primitive and will loop on ambient-state inspection if asked to create an agent via chat.",{name:d.string().describe(lt),displayName:d.string().optional().describe("Human-readable label shown in UIs. Defaults to `name` when omitted."),description:d.string().optional().describe("Short summary of what the agent does. Shown in list views and agent pickers."),systemPrompt:d.string().describe(dt),toolAllowlist:d.array(d.string()).optional().describe(pt),webSearchEnabled:d.boolean().optional().describe('Whether the built-in web_search tool should be available to this agent. Defaults to false \u2014 conservative default so narrow allowlists do not accidentally grant web access. When `toolAllowlist` is set, this flag is overridden by the allowlist: the agent sees web_search only if "web_search" is explicitly in the list.'),role:d.string().optional().describe('Free-text role label for categorization (e.g. "analyst", "writer"). Defaults to "assistant".'),enabled:d.boolean().optional().describe("Whether the agent is enabled and eligible to run. Default: true."),scheduled:d.object({cron:d.string().describe("Cron expression (5- or 6-field)."),timezone:d.string().optional().describe('IANA timezone name (e.g. "America/New_York"). Defaults to the workspace tz.')}).optional().describe("Optional cron schedule. Stored with the agent but not yet wired to the scheduler \u2014 runs must be triggered manually via ohwow_run_agent for now.")},async({name:e,displayName:t,description:n,systemPrompt:s,toolAllowlist:a,webSearchEnabled:i,role:c,enabled:p,scheduled:h})=>{try{let u={name:e,system_prompt:s};t!==void 0&&(u.display_name=t),n!==void 0&&(u.description=n),c!==void 0&&(u.role=c),p!==void 0&&(u.enabled=p),h!==void 0&&(u.scheduled=h);let m={};a!==void 0&&(m.tools_enabled=a,m.tools_mode="allowlist"),i!==void 0&&(m.web_search_enabled=i),Object.keys(m).length>0&&(u.config=m);let _=await r.post("/api/agents",u);return _.error?g(`Couldn't create agent: ${_.error}`):P({ok:!0,agent:_.data,note:"Agent created. Use ohwow_run_agent with this agent's name or id to dispatch a task."})}catch(u){return g(`Error: ${u instanceof Error?u.message:"Unknown error"}`)}}),o.tool("ohwow_get_agent","[Agents] Get an agent's full configuration by name or id, including the system prompt, tool allowlist, role, schedule, enabled flag, and timestamps. Use this instead of ohwow_list_agents when you need to inspect or iterate on a specific agent's system prompt \u2014 list_agents returns summary rows and does not include the prompt.",{name:d.string().optional().describe("Workspace-unique agent name. Provide this OR `id`."),id:d.string().optional().describe("Agent UUID. Provide this OR `name`.")},async({name:e,id:t})=>{try{if(!e&&!t)return g("Provide either `name` or `id`.");let n=t;if(!n&&e){let a=await I(r,e);if(!a)return g(`No agent named "${e}" in this workspace.`);n=a.id}let s=await r.get(`/api/agents/${encodeURIComponent(n)}`);return s.error||!s.data?g(s.error??"Agent not found"):P({ok:!0,agent:s.data})}catch(n){return g(`Error: ${n instanceof Error?n.message:"Unknown error"}`)}}),o.tool("ohwow_update_agent","[Agents] Update fields on an existing agent. Use this to iterate on a system prompt, tighten a tool allowlist, rename, or toggle enabled. Identify the agent by `name` or `id`. Any field left undefined is untouched. Agents never pin a model \u2014 the router picks per task.",{name:d.string().optional().describe("Workspace-unique agent name (provide this OR `id`)."),id:d.string().optional().describe("Agent UUID (provide this OR `name`)."),newName:d.string().optional().describe("Rename the agent. Must remain workspace-unique and match the alphanumeric+dash+underscore constraint."),displayName:d.string().optional().describe("Updated human-readable label."),description:d.string().optional().describe("Updated short summary."),systemPrompt:d.string().optional().describe("Replace the system prompt entirely."),toolAllowlist:d.array(d.string()).optional().describe("Replace the tool allowlist. Same validation rules as ohwow_create_agent."),webSearchEnabled:d.boolean().optional().describe("Toggle the built-in web_search tool. Overridden by the allowlist when one is active \u2014 see ohwow_create_agent."),role:d.string().optional().describe("Updated role label."),enabled:d.boolean().optional().describe("Toggle the agent on/off."),scheduled:d.object({cron:d.string(),timezone:d.string().optional()}).optional().describe("Replace the cron schedule.")},async({name:e,id:t,newName:n,displayName:s,description:a,systemPrompt:i,toolAllowlist:c,webSearchEnabled:p,role:h,enabled:u,scheduled:m})=>{try{if(!e&&!t)return g("Provide either `name` or `id`.");let _=t;if(!_&&e){let b=await I(r,e);if(!b)return g(`No agent named "${e}" in this workspace.`);_=b.id}let y={};n!==void 0&&(y.name=n),a!==void 0&&(y.description=a),i!==void 0&&(y.system_prompt=i),h!==void 0&&(y.role=h),u!==void 0&&(y.enabled=u),s!==void 0&&(y.display_name=s),m!==void 0&&(y.scheduled=m);let E={};if(c!==void 0&&(E.tools_enabled=c,E.tools_mode="allowlist"),p!==void 0&&(E.web_search_enabled=p),Object.keys(E).length>0&&(y.config=E),Object.keys(y).length===0)return g("No fields provided to update.");let T=await r.patch(`/api/agents/${encodeURIComponent(_)}`,y);return T.error?g(`Couldn't update agent: ${T.error}`):P({ok:!0,agent:T.data})}catch(_){return g(`Error: ${_ instanceof Error?_.message:"Unknown error"}`)}}),o.tool("ohwow_grant_agent_path","[Agents] Grant an agent permission to read/write files under a local directory. Writes a row to agent_file_access_paths that the FileAccessGuard reads on every task run, so the agent's filesystem tools (local_read_file, local_write_file, run_bash, etc.) will accept paths inside the granted directory. Without this, a narrowly-scoped agent hits \"path outside allowed directories\" and the task either fails the hallucination gate or routes to needs_approval. The daemon validates that the path exists, is a directory, lives inside the user's home, and isn't a sensitive subdirectory like .ssh or .gnupg. Identify the agent by `name` or `id`.",{name:d.string().optional().describe("Workspace-unique agent name (provide this OR `id`)."),id:d.string().optional().describe('Agent UUID, or the literal string "__orchestrator__" to grant paths to the orchestrator itself (provide this OR `name`).'),path:d.string().describe("Absolute directory path to grant access to. Must exist, be a directory, live inside the user's home, and not be under .ssh, .gnupg, /etc, /var, /usr, etc. Tildes are not expanded \u2014 pass a fully-resolved path."),label:d.string().optional().describe('Optional human-readable label shown in UIs (e.g. "living docs (diary)", "workspace data"). Purely cosmetic.')},async({name:e,id:t,path:n,label:s})=>{try{if(!e&&!t)return g("Provide either `name` or `id`.");let a=t;if(!a&&e){let c=await I(r,e);if(!c)return g(`No agent named "${e}" in this workspace.`);a=c.id}let i=await r.post(`/api/agents/${encodeURIComponent(a)}/file-access`,{path:n,label:s});return i.error?g(`Couldn't grant path: ${i.error}`):P({ok:!0,agent:e??a,path:i.data?.path??n,label:i.data?.label??s??null,note:"Path granted. FileAccessGuard re-reads this table on every task run, so the next run will see the new access."})}catch(a){return g(`Error: ${a instanceof Error?a.message:"Unknown error"}`)}}),o.tool("ohwow_list_agent_paths","[Agents] List all filesystem paths granted to an agent. Returns rows from agent_file_access_paths with id, path, label, and created_at. Use the returned `id` with ohwow_revoke_agent_path to remove a specific row. Identify the agent by `name` or `id`.",{name:d.string().optional().describe("Workspace-unique agent name (provide this OR `id`)."),id:d.string().optional().describe('Agent UUID, or the literal string "__orchestrator__" for orchestrator-scoped paths (provide this OR `name`).')},async({name:e,id:t})=>{try{if(!e&&!t)return g("Provide either `name` or `id`.");let n=t;if(!n&&e){let a=await I(r,e);if(!a)return g(`No agent named "${e}" in this workspace.`);n=a.id}let s=await r.get(`/api/agents/${encodeURIComponent(n)}/file-access`);return s.error?g(`Couldn't list paths: ${s.error}`):P({ok:!0,agent:e??n,paths:s.data??[]})}catch(n){return g(`Error: ${n instanceof Error?n.message:"Unknown error"}`)}}),o.tool("ohwow_revoke_agent_path","[Agents] Revoke an agent's access to a filesystem path. Identify the agent by `name` or `id`, then identify the row by either `pathId` (from ohwow_list_agent_paths) or `path` (exact match \u2014 the tool lists and resolves locally). Idempotent on a missing row.",{name:d.string().optional().describe("Workspace-unique agent name (provide this OR `id`)."),id:d.string().optional().describe('Agent UUID, or the literal string "__orchestrator__" (provide this OR `name`).'),pathId:d.string().optional().describe("The row id from ohwow_list_agent_paths. Takes precedence over `path` when both are provided."),path:d.string().optional().describe("Absolute directory path. The tool lists existing grants and deletes the matching row. Used when the caller doesn't know the row id. Exact-match, fully-resolved path.")},async({name:e,id:t,pathId:n,path:s})=>{try{if(!e&&!t)return g("Provide either `name` or `id`.");if(!n&&!s)return g("Provide either `pathId` or `path`.");let a=t;if(!a&&e){let p=await I(r,e);if(!p)return g(`No agent named "${e}" in this workspace.`);a=p.id}let i=n;if(!i&&s){let p=await r.get(`/api/agents/${encodeURIComponent(a)}/file-access`);if(p.error)return g(`Couldn't list paths for match: ${p.error}`);let h=(p.data??[]).find(u=>u.path===s);if(!h)return g(`No granted path matches "${s}" for this agent.`);i=h.id}let c=await r.del(`/api/agents/${encodeURIComponent(a)}/file-access/${encodeURIComponent(i)}`);return c.error?g(`Couldn't revoke path: ${c.error}`):P({ok:!0,agent:e??a,revoked:i})}catch(a){return g(`Error: ${a instanceof Error?a.message:"Unknown error"}`)}}),o.tool("ohwow_delete_agent","[Agents] Delete an agent from the current workspace. Also drops the agent's memory rows. Identify by `name` or `id`. This is destructive \u2014 there is no undo.",{name:d.string().optional().describe("Workspace-unique agent name (provide this OR `id`)."),id:d.string().optional().describe("Agent UUID (provide this OR `name`).")},async({name:e,id:t})=>{try{if(!e&&!t)return g("Provide either `name` or `id`.");let n=t,s=e;if(!n&&e){let i=await I(r,e);if(!i)return g(`No agent named "${e}" in this workspace.`);n=i.id,s=i.name}let a=await r.del(`/api/agents/${encodeURIComponent(n)}`);return a.error?g(`Couldn't delete agent: ${a.error}`):P({ok:!0,deleted:s??n})}catch(n){return g(`Error: ${n instanceof Error?n.message:"Unknown error"}`)}})}import{z}from"zod";function V(o){return{content:[{type:"text",text:o}],isError:!0}}function Re(o){return{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}function Ae(o,r){o.tool("ohwow_list_permission_requests",'[Permissions] List every task currently paused on a filesystem or bash permission request. Returns the task id, agent name, attempted path, suggested exact + parent paths the operator can grant, the guard reason, and when the denial happened. These are the actionable items in the "agent wants access to X" queue. Pair with ohwow_approve_permission_request to resolve any row. Workspace-scoped to the focused daemon.',{},async()=>{try{let e=await r.get("/api/permission-requests");if(e.error)return V(`Couldn't list permission requests: ${e.error}`);let t=e.data??[];return Re({ok:!0,count:t.length,requests:t,note:t.length===0?"No agents are waiting on a permission decision right now.":"Use ohwow_approve_permission_request with the task_id to approve once, approve always, or deny."})}catch(e){return V(`Error: ${e instanceof Error?e.message:"Unknown error"}`)}}),o.tool("ohwow_approve_permission_request",'[Permissions] Resolve a paused permission request. mode="once" grants the path for this single resumed run only and does NOT persist a row in agent_file_access_paths \u2014 use it for one-off operator approvals. mode="always" persists the grant by writing through the same agent_file_access_paths path that ohwow_grant_agent_path writes to, so future runs of this agent stay unblocked. mode="deny" terminates the original task with failure_category=permission_denied and does NOT spawn a resume. On approve, the original task is marked status=approved and a NEW child task is spawned (parent_task_id pointing back) that re-runs the work from scratch with the expanded guard. The child task id is returned so callers can poll its status.',{task_id:z.string().describe("Task id from ohwow_list_permission_requests. Must currently be in status=needs_approval with approval_reason=permission_denied; the route 409s otherwise."),mode:z.enum(["once","always","deny"]).describe('"once" = resume with an ephemeral grant on the child task only. "always" = persist the grant via agent_file_access_paths so future runs work. "deny" = terminate the task without resuming.'),scope:z.enum(["exact","parent","edit"]).optional().describe('Which path to grant (ignored for mode=deny). "exact" = the exact file/dir the agent asked for (default). "parent" = the parent directory of the exact path, so sibling files also work. "edit" = use the explicit `path` field below; the operator overrides what was suggested.'),path:z.string().optional().describe(`Required only when scope="edit". Absolute directory path to grant. Must live inside the user's home directory and not under a blocked system path.`)},async({task_id:e,mode:t,scope:n,path:s})=>{try{let a={mode:t};n!==void 0&&(a.scope=n),s!==void 0&&(a.path=s);let i=await r.post(`/api/permission-requests/${encodeURIComponent(e)}/approve`,a);return i.error?V(`Couldn't approve permission request: ${i.error}`):Re({ok:!0,...i,note:t==="deny"?"Task marked failed with failure_category=permission_denied. No resume spawned.":`Resumed as task ${i.child_task_id?.slice(0,8)??"<unknown>"}. Use ohwow_get_task with the child id to see when it completes.`})}catch(a){return V(`Error: ${a instanceof Error?a.message:"Unknown error"}`)}})}import{z as ut}from"zod";function Pe(o){return{content:[{type:"text",text:o}],isError:!0}}function gt(o){return{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}function Te(o,r){o.tool("ohwow_list_failing_triggers",'[Triggers] List every scheduled/event trigger that has been silently miscarrying. A trigger shows up here when its last N consecutive fires all failed (or landed in needs_approval without resolution) \u2014 the watchdog catches the class of "the daily diary cron has been broken for 2 weeks and nobody noticed." Returns the trigger id, name, consecutive_failures count, last_succeeded_at (or null if it has never succeeded since the watchdog was added), last_fired_at, trigger_type, enabled flag, and last_error string. Sorted worst-first. The threshold defaults to 3 but can be overridden via `threshold` to see near-misses too (e.g. `threshold: 1` returns every trigger with any failure at all).',{threshold:ut.number().int().positive().optional().describe("Minimum consecutive_failures to include in the list. Default: 3 (matches the daemon's TRIGGER_STUCK_THRESHOLD). Set to 1 to see every trigger with any failure on its most recent run.")},async({threshold:e})=>{try{let t=e!==void 0?`?threshold=${e}`:"",n=await r.get(`/api/failing-triggers${t}`);if(n.error)return Pe(`Couldn't list failing triggers: ${n.error}`);let s=n.data??[];return gt({ok:!0,threshold:n.threshold,count:s.length,triggers:s,note:s.length===0?`No triggers are at or above ${n.threshold} consecutive failures. Scheduled automations are running cleanly right now.`:`${s.length} trigger(s) have been failing ${n.threshold}+ runs in a row. Investigate via ohwow_get_task on recent tasks for each, or disable the trigger via the UI if it's a known-broken integration.`})}catch(t){return Pe(`Error: ${t instanceof Error?t.message:"Unknown error"}`)}})}import{z as v}from"zod";function X(o){return{content:[{type:"text",text:o}],isError:!0}}function We(o){return{content:[{type:"text",text:JSON.stringify(o,null,2)}]}}function Me(o,r){o.tool("ohwow_list_findings",'[Self-bench] List rows from the self_findings ledger \u2014 the structured record of every self-experiment the daemon has run. Each row captures: experiment_id, category, subject, hypothesis, verdict (pass/warning/fail/error), summary, evidence JSON, and an optional intervention_applied blob describing what the experiment changed. Use this BEFORE investigating a surface from scratch \u2014 past findings likely already tell you what the system learned about a model, trigger, tool, or handler. Filters compose freely. Defaults to active status, newest-first, 50 rows. Examples: to see current model-picker health run with {category: "model_health"}; to see every stuck trigger find run with {category: "trigger_stability", verdict: "fail"}; to see everything a specific experiment has logged run with {experiment_id: "model-health"}.',{experiment_id:v.string().optional().describe('Filter to one experiment id (e.g. "model-health", "trigger-stability"). Returns only rows that experiment produced.'),category:v.enum(["model_health","trigger_stability","tool_reliability","handler_audit","prompt_calibration","canary","other"]).optional().describe("Filter to one typed category. Categories match src/self-bench/experiment-types.ts."),verdict:v.enum(["pass","warning","fail","error"]).optional().describe('Filter to one verdict. Use "fail" or "error" to focus on problems; "warning" to see drift before it becomes failure.'),subject:v.string().optional().describe('Filter to rows about a specific subject (e.g. "qwen/qwen3.5-9b", "trigger:d1a924de..."). Lets you trace the history of a single model or trigger over time.'),status:v.enum(["active","superseded","revoked"]).optional().describe(`Row lifecycle filter. Defaults to "active" \u2014 rows that haven't been superseded or revoked by a later finding. Use "superseded" to see historical findings that got replaced.`),limit:v.number().int().positive().max(500).optional().describe("Cap on rows returned. Default 50, hard max 500.")},async({experiment_id:e,category:t,verdict:n,subject:s,status:a,limit:i})=>{try{let c=new URLSearchParams;e&&c.set("experiment_id",e),t&&c.set("category",t),n&&c.set("verdict",n),s&&c.set("subject",s),a&&c.set("status",a),i!==void 0&&c.set("limit",String(i));let p=c.toString()?`?${c.toString()}`:"",h=await r.get(`/api/findings${p}`);if(h.error)return X(`Couldn't list findings: ${h.error}`);let u=h.data??[];return We({ok:!0,count:u.length,limit:h.limit,findings:u,note:u.length===0?"No findings match these filters. Widen the filters (drop verdict or category) or wait for the next experiment tick.":`${u.length} finding(s) returned. Each is a historical record of one experiment run \u2014 use the evidence field to see raw probe output and intervention_applied to see what changed (if anything).`})}catch(c){return X(`Error: ${c instanceof Error?c.message:"Unknown error"}`)}}),o.tool("ohwow_list_insights",`[Self-bench] List DISTILLED insights \u2014 the "what is surprising right now?" view on top of self_findings. Each row is one (experiment, subject) cluster, already deduped, scored by novelty, and ranked. novelty_score is 0..1 where 1.0 is first-seen and 0 is routine. novelty_reason is one of: first_seen / verdict_flipped / value_z / repeat_count / normal. consecutive_fails tiebreaks stuck problems above equally-novel ones. Use this instead of ohwow_list_findings when you want to know what stands out today vs what's noise. Examples: top 10 surprises \u2192 {limit: 10}; only unusual observations \u2192 {min_score: 0.5}; include superseded history \u2192 {status: "any"}.`,{limit:v.number().int().positive().max(200).optional().describe("Cap on insights returned. Default 25, hard max 200."),min_score:v.number().min(0).max(1).optional().describe("Minimum novelty_score to include. 0 returns everything ranked; 0.5 filters to unusual only; 0.9 filters to extreme only. Default 0."),status:v.enum(["active","superseded","revoked","any"]).optional().describe('Underlying finding lifecycle. Default "active".')},async({limit:e,min_score:t,status:n})=>{try{let s=new URLSearchParams;e!==void 0&&s.set("limit",String(e)),t!==void 0&&s.set("min_score",String(t)),n&&s.set("status",n);let a=s.toString()?`?${s.toString()}`:"",i=await r.get(`/api/insights/distilled${a}`);if(i.error)return X(`Couldn't list insights: ${i.error}`);let c=i.data??[];return We({ok:!0,count:c.length,limit:i.limit,min_score:i.min_score,insights:c,note:c.length===0?"Nothing ranked above the score floor. Lower min_score, or wait for the next experiment tick to populate novelty data.":`${c.length} insight(s), most surprising first. novelty_reason explains why each rose to the top; use ohwow_list_findings with the experiment_id + subject to dig into the full ledger trail.`})}catch(s){return X(`Error: ${s instanceof Error?s.message:"Unknown error"}`)}})}function De(o,r){he(o,r),me(o,r),fe(o,r),ye(o,r),_e(o,r),be(o,r),ke(o,r),xe(o,r),Ee(o),Oe(o,r),Ce(o,r),Se(o,r),Ae(o,r),Te(o,r),Me(o,r)}function Ie(o,r){o.resource("agents","ohwow://agents",{description:"All OHWOW agents with their descriptions, roles, and available tools"},async()=>{try{let e=await r.get("/api/agents"),t=e.data||e;return{contents:[{uri:"ohwow://agents",mimeType:"application/json",text:JSON.stringify(t,null,2)}]}}catch{return{contents:[{uri:"ohwow://agents",mimeType:"text/plain",text:"Could not load agents. Is the OHWOW daemon running?"}]}}}),o.resource("workspace","ohwow://workspace",{description:"OHWOW workspace status: tier, uptime, agent count, system stats"},async()=>{try{let e=await r.get("/api/dashboard/init");return{contents:[{uri:"ohwow://workspace",mimeType:"application/json",text:JSON.stringify(e,null,2)}]}}catch{return{contents:[{uri:"ohwow://workspace",mimeType:"text/plain",text:"Could not load workspace status. Is the OHWOW daemon running?"}]}}}),o.resource("contacts","ohwow://contacts",{description:"Recent CRM contacts with pipeline stage breakdown"},async()=>{try{let e=await r.get("/api/contacts?limit=10"),t=e.data||e;return{contents:[{uri:"ohwow://contacts",mimeType:"application/json",text:JSON.stringify(t,null,2)}]}}catch{return{contents:[{uri:"ohwow://contacts",mimeType:"text/plain",text:"Could not load contacts. Is the OHWOW daemon running?"}]}}}),o.resource("projects","ohwow://projects",{description:"Active projects with task counts and status"},async()=>{try{let e=await r.get("/api/projects"),t=e.data||e;return{contents:[{uri:"ohwow://projects",mimeType:"application/json",text:JSON.stringify(t,null,2)}]}}catch{return{contents:[{uri:"ohwow://projects",mimeType:"text/plain",text:"Could not load projects. Is the OHWOW daemon running?"}]}}}),o.resource("workflows","ohwow://workflows",{description:"Workflow and automation catalog with descriptions and trigger types"},async()=>{try{let[e,t]=await Promise.all([r.get("/api/workflows"),r.get("/api/automations")]),n=e.data||e,s=t.data||t;return{contents:[{uri:"ohwow://workflows",mimeType:"application/json",text:JSON.stringify({workflows:n,automations:s},null,2)}]}}catch{return{contents:[{uri:"ohwow://workflows",mimeType:"text/plain",text:"Could not load workflows. Is the OHWOW daemon running?"}]}}}),o.resource("capabilities","ohwow://capabilities",{description:"Complete list of all OHWOW MCP tools grouped by domain"},async()=>({contents:[{uri:"ohwow://capabilities",mimeType:"text/plain",text:ht}]}))}var ht=`OHWOW MCP Plugin \u2014 Quick Reference
9
9
 
10
10
  GETTING STARTED
11
11
  1. ohwow_workspace_status Check connection and workspace overview
@@ -57,9 +57,9 @@ CLOUD (instant, requires ohwow.fun)
57
57
  USE ohwow_chat FOR: desktop control, scheduling, agent state, approvals,
58
58
  A2A protocol, PDF forms, media generation, automation creation, and
59
59
  anything not listed above.
60
- `;import{createServer as St}from"http";import{writeFileSync as _t,unlinkSync as Et,existsSync as bt}from"fs";import{join as Ot}from"path";import{homedir as Rt}from"os";var A=Ot(Rt(),".ohwow","data","mcp-sampling.port");function ot(r){let o=null,t=St(async(n,s)=>{if(n.method!=="POST"||n.url!=="/sampling"){s.writeHead(404),s.end("Not found");return}let i="";for await(let a of n)i+=a;let c;try{c=JSON.parse(i)}catch{s.writeHead(400),s.end(JSON.stringify({error:"Invalid JSON"}));return}if(!c.messages?.length){s.writeHead(400),s.end(JSON.stringify({error:"messages array is required"}));return}try{let a=c.messages.map(m=>({role:m.role,content:{type:"text",text:m.content}})),p=await r.server.createMessage({messages:a,systemPrompt:c.systemPrompt,maxTokens:c.maxTokens||4096,temperature:c.temperature}),f={content:Array.isArray(p.content)?p.content.filter(m=>m.type==="text").map(m=>m.text).join(""):typeof p.content=="object"&&"text"in p.content?p.content.text:String(p.content),model:p.model,stopReason:p.stopReason??void 0};s.writeHead(200,{"Content-Type":"application/json"}),s.end(JSON.stringify(f))}catch(a){let p=a instanceof Error?a.message:"Sampling failed";s.writeHead(500),s.end(JSON.stringify({error:p}))}});return o=t,t.listen(0,"127.0.0.1",()=>{let n=t.address();if(n&&typeof n=="object"){let s=n.port;try{_t(A,String(s)),process.stderr.write(`[ohwow-mcp] Sampling bridge listening on port ${s}
60
+ `;import{createServer as mt}from"http";import{writeFileSync as ft,unlinkSync as wt,existsSync as yt}from"fs";import{join as _t}from"path";import{homedir as bt}from"os";var de=_t(bt(),".ohwow","data","mcp-sampling.port");function Ne(o){let r=null,e=mt(async(n,s)=>{if(n.method!=="POST"||n.url!=="/sampling"){s.writeHead(404),s.end("Not found");return}let a="";for await(let c of n)a+=c;let i;try{i=JSON.parse(a)}catch{s.writeHead(400),s.end(JSON.stringify({error:"Invalid JSON"}));return}if(!i.messages?.length){s.writeHead(400),s.end(JSON.stringify({error:"messages array is required"}));return}try{let c=i.messages.map(m=>({role:m.role,content:{type:"text",text:m.content}})),p=await o.server.createMessage({messages:c,systemPrompt:i.systemPrompt,maxTokens:i.maxTokens||4096,temperature:i.temperature}),u={content:Array.isArray(p.content)?p.content.filter(m=>m.type==="text").map(m=>m.text).join(""):typeof p.content=="object"&&"text"in p.content?p.content.text:String(p.content),model:p.model,stopReason:p.stopReason??void 0};s.writeHead(200,{"Content-Type":"application/json"}),s.end(JSON.stringify(u))}catch(c){let p=c instanceof Error?c.message:"Sampling failed";s.writeHead(500),s.end(JSON.stringify({error:p}))}});return r=e,e.listen(0,"127.0.0.1",()=>{let n=e.address();if(n&&typeof n=="object"){let s=n.port;try{ft(de,String(s)),process.stderr.write(`[ohwow-mcp] Sampling bridge listening on port ${s}
61
61
  `)}catch{process.stderr.write(`[ohwow-mcp] Could not write sampling port file
62
- `)}}}),{cleanup:()=>{o&&(o.close(),o=null);try{bt(A)&&Et(A)}catch{}}}}import{readFileSync as Dt}from"fs";import{join as Tt}from"path";function vt(){try{let r=Dt(Tt(process.cwd(),"package.json"),"utf-8");return JSON.parse(r).version}catch{return"0.0.0"}}var U=vt();async function ze(){let r;try{r=await k.create()}catch(s){process.stderr.write(`[ohwow-mcp] ${s instanceof Error?s.message:"Unknown error"}
63
- `),process.exit(1)}let o=new Ct({name:"ohwow",version:U});tt(o,r),et(o,r);let t=new Pt,e=null,n=async()=>{e?.();try{await o.close()}catch{}process.exit(0)};process.on("SIGTERM",n),process.on("SIGINT",n);try{await o.connect(t),process.stderr.write(`[ohwow-mcp] Connected (v${U})
64
- `),e=ot(o).cleanup}catch(s){process.stderr.write(`[ohwow-mcp] Failed to connect: ${s instanceof Error?s.message:"Unknown error"}
65
- `),process.exit(1)}}export{ze as startMcpServer};
62
+ `)}}}),{cleanup:()=>{r&&(r.close(),r=null);try{yt(de)&&wt(de)}catch{}}}}import{readFileSync as kt}from"fs";import{join as xt}from"path";function vt(){try{let o=kt(xt(process.cwd(),"package.json"),"utf-8");return JSON.parse(o).version}catch{return"0.0.0"}}var pe=vt();async function ho(){let o;try{o=await j.create()}catch(s){process.stderr.write(`[ohwow-mcp] ${s instanceof Error?s.message:"Unknown error"}
63
+ `),process.exit(1)}let r=new Et({name:"ohwow",version:pe});De(r,o),Ie(r,o);let e=new Ot,t=null,n=async()=>{t?.();try{await r.close()}catch{}process.exit(0)};process.on("SIGTERM",n),process.on("SIGINT",n);try{await r.connect(e),process.stderr.write(`[ohwow-mcp] Connected (v${pe})
64
+ `),t=Ne(r).cleanup}catch(s){process.stderr.write(`[ohwow-mcp] Failed to connect: ${s instanceof Error?s.message:"Unknown error"}
65
+ `),process.exit(1)}}export{ho as startMcpServer};
@@ -0,0 +1,20 @@
1
+ -- Code skills: add columns that let the synthesis pipeline store
2
+ -- deterministic TypeScript-backed skills alongside the existing
3
+ -- procedure/judgement/extraction/verification skill_types.
4
+ --
5
+ -- Local SQLite has no CHECK constraint on skill_type, so adding a new
6
+ -- value ('code') is a no-op at the schema level. The new columns are
7
+ -- all nullable or default-initialized so existing rows remain valid.
8
+ --
9
+ -- Mirror migration lives at ohwow.fun/sql/359-code-skills.sql.
10
+
11
+ ALTER TABLE agent_workforce_skills ADD COLUMN script_path TEXT;
12
+ ALTER TABLE agent_workforce_skills ADD COLUMN selectors TEXT DEFAULT '{}';
13
+ ALTER TABLE agent_workforce_skills ADD COLUMN origin_trace_id TEXT;
14
+ ALTER TABLE agent_workforce_skills ADD COLUMN success_count INTEGER NOT NULL DEFAULT 0;
15
+ ALTER TABLE agent_workforce_skills ADD COLUMN fail_count INTEGER NOT NULL DEFAULT 0;
16
+ ALTER TABLE agent_workforce_skills ADD COLUMN promoted_at TEXT;
17
+
18
+ CREATE INDEX IF NOT EXISTS idx_skills_script_path
19
+ ON agent_workforce_skills(script_path)
20
+ WHERE script_path IS NOT NULL;
@@ -0,0 +1,29 @@
1
+ -- Archive all active procedure skills.
2
+ --
3
+ -- Context: the runtime's three keyword matchers (runAgent SOP loop,
4
+ -- engine compileSkills, prompt-builder triggerMatched) were removed
5
+ -- in the unified-skill-synthesis refactor. Procedure rows no longer
6
+ -- have any discovery path at runtime. Setting is_active=0 archives
7
+ -- them without losing the audit trail. Rows remain queryable for
8
+ -- historical analysis but stop appearing in the skill loader's
9
+ -- boot scan and the now-deleted matchers.
10
+ --
11
+ -- The three active rows on launch eve were all degenerate single-
12
+ -- tool wrappers:
13
+ -- - "Post a Tweet" → tool_sequence ["x_compose_tweet"]
14
+ -- - "Write X Article" → tool_sequence ["x_compose_article"]
15
+ -- - "Check X Messages" → tool_sequence ["x_list_dms"]
16
+ -- The LLM already has x_compose_tweet, x_compose_article, and
17
+ -- x_list_dms directly in its static tool list. No capability is lost.
18
+ --
19
+ -- The single inactive desktop SOP ("Check X Notifications") is
20
+ -- already is_active=0 and is unaffected.
21
+ --
22
+ -- Idempotent: safe to re-run. The `updated_at` stamp moves on each
23
+ -- run but no functional state changes after the first application.
24
+
25
+ UPDATE agent_workforce_skills
26
+ SET is_active = 0,
27
+ updated_at = datetime('now')
28
+ WHERE skill_type = 'procedure'
29
+ AND is_active = 1;
@@ -0,0 +1,19 @@
1
+ -- Workspace-level default filesystem paths.
2
+ --
3
+ -- Without this, every agent that doesn't have an explicit row in
4
+ -- agent_file_access_paths fails every local_read_file/local_write_file call
5
+ -- with "No directories are configured for file access". The orchestrator
6
+ -- pseudo-agent has a hardcoded /tmp baseline (filesystem.ts) but real agents
7
+ -- inherit nothing, so SOP-delegated work that touches the disk silently fails.
8
+ --
9
+ -- engine.ts unions this column with per-agent paths when constructing the
10
+ -- FileAccessGuard, so workspace defaults flow to every task execution.
11
+ -- filesystem.ts reads the same column so orchestrator chat and agent task
12
+ -- execution share a single source of truth for "what /tmp-like paths are
13
+ -- always allowed."
14
+ --
15
+ -- Default value gives every existing workspace /tmp out of the box, matching
16
+ -- the prior orchestrator-only baseline.
17
+
18
+ ALTER TABLE agent_workforce_workspaces
19
+ ADD COLUMN default_filesystem_paths TEXT NOT NULL DEFAULT '["/tmp"]';
@@ -0,0 +1,28 @@
1
+ -- TTL + lazy cleanup for agent_workforce_task_state.
2
+ --
3
+ -- The state table had no expiration mechanism, so once an agent wrote a key
4
+ -- (incident flag, health check, scratch value, etc.) it lived forever. Stale
5
+ -- rows from old sessions polluted reasoning: agents would get_state on a key
6
+ -- that was set during a long-dead incident, see RED, and refuse the unrelated
7
+ -- task in front of them.
8
+ --
9
+ -- expires_at is NULL for persistent state (the default for keys that don't
10
+ -- match the heuristic prefixes in state.ts). For ephemeral keys (incident_*,
11
+ -- *_health_*, temp_*, scratch_*) state.ts now writes a 24h default expiry.
12
+ -- getAgentState filters expired rows on read and lazy-deletes them so the
13
+ -- next reader doesn't trip the same poisoning.
14
+ --
15
+ -- The DELETE at the bottom is a one-shot purge of the two known-poisoning
16
+ -- keys observed in production today. They are not written by any current
17
+ -- code path (verified) so deleting them is a clean fix; they cannot grow
18
+ -- back through normal use.
19
+
20
+ ALTER TABLE agent_workforce_task_state
21
+ ADD COLUMN expires_at TEXT;
22
+
23
+ CREATE INDEX IF NOT EXISTS idx_task_state_expires
24
+ ON agent_workforce_task_state(expires_at)
25
+ WHERE expires_at IS NOT NULL;
26
+
27
+ DELETE FROM agent_workforce_task_state
28
+ WHERE key IN ('last_health_check', 'incident_scrapling_down');
@@ -0,0 +1,25 @@
1
+ -- Async chat: status tracking on orchestrator_conversations.
2
+ --
3
+ -- The MCP ohwow_chat tool now creates a conversation row, returns its id
4
+ -- immediately, and dispatches the orchestrator turn in the background so
5
+ -- long turns survive client disconnects. Clients poll GET /api/chat/:id
6
+ -- until status flips out of 'running'.
7
+ --
8
+ -- Status values:
9
+ -- idle — conversation exists, no turn currently executing (manual create)
10
+ -- running — orchestrator turn dispatched, in flight
11
+ -- done — turn finished, latest assistant message is the final answer
12
+ -- error — turn failed, last_error has the message
13
+ --
14
+ -- The partial index on status='running' keeps "find in-flight turns" cheap
15
+ -- without bloating the index for the 99% steady-state case.
16
+
17
+ ALTER TABLE orchestrator_conversations
18
+ ADD COLUMN status TEXT NOT NULL DEFAULT 'idle';
19
+
20
+ ALTER TABLE orchestrator_conversations
21
+ ADD COLUMN last_error TEXT;
22
+
23
+ CREATE INDEX IF NOT EXISTS idx_conv_status_running
24
+ ON orchestrator_conversations(workspace_id, status)
25
+ WHERE status = 'running';
@@ -0,0 +1,40 @@
1
+ -- Normalize agent_workforce_deliverables.created_at to ISO-8601 with a
2
+ -- trailing Z, so lexicographic comparison against JS `.toISOString()`
3
+ -- filter values in list_deliverables works correctly.
4
+ --
5
+ -- Background: the schema default is `datetime('now')`, which outputs
6
+ -- `YYYY-MM-DD HH:MM:SS` (space separator, no milliseconds, no Z).
7
+ -- One insert path (deliverables-recorder.ts) explicitly writes
8
+ -- `new Date().toISOString()` so those rows land as
9
+ -- `YYYY-MM-DDTHH:MM:SS.mmmZ`. Two other insert paths (saveDeliverable,
10
+ -- task-completion.ts) omit created_at entirely and fall back to the
11
+ -- default. That produces a mixed table: 24 rows in ISO-with-Z, 50 rows
12
+ -- in SQL-default.
13
+ --
14
+ -- list_deliverables' `since` filter calls `.gte('created_at', iso)`
15
+ -- where `iso` is always ISO-with-Z. SQLite compares lexicographically.
16
+ -- Position 10 of the string decides: space (0x20) < 'T' (0x54), so
17
+ -- every SQL-default row silently sorts BEFORE any ISO-with-Z filter,
18
+ -- and gets excluded even when it's chronologically newer. Found during
19
+ -- the M0.21 self-bench moonshot — ohwow reported 25 deliverables in
20
+ -- the 24h window when the real answer was 38. 13 rows lost to format
21
+ -- drift.
22
+ --
23
+ -- Fix:
24
+ -- 1. Backfill every row whose created_at is missing a T or Z to ISO.
25
+ -- `strftime('%Y-%m-%dT%H:%M:%f', col) || 'Z'` produces
26
+ -- `2026-04-13T19:46:18.000Z` regardless of the original shape.
27
+ -- 2. Do the same for updated_at since the same inserts leave it with
28
+ -- the SQL default.
29
+ -- 3. Use LIKE filters so re-running the migration is a no-op.
30
+ --
31
+ -- The downstream insert sites (saveDeliverable, task-completion.ts)
32
+ -- are fixed in the same commit so new rows land in ISO from here on.
33
+
34
+ UPDATE agent_workforce_deliverables
35
+ SET created_at = strftime('%Y-%m-%dT%H:%M:%f', created_at) || 'Z'
36
+ WHERE created_at NOT LIKE '%T%Z';
37
+
38
+ UPDATE agent_workforce_deliverables
39
+ SET updated_at = strftime('%Y-%m-%dT%H:%M:%f', updated_at) || 'Z'
40
+ WHERE updated_at IS NOT NULL AND updated_at NOT LIKE '%T%Z';
@@ -0,0 +1,36 @@
1
+ -- =====================================================================
2
+ -- Migration 113: Filesystem permission requests
3
+ --
4
+ -- When an agent's filesystem/bash call hits FileAccessGuard the task now
5
+ -- throws PermissionDeniedError and lands in needs_approval instead of
6
+ -- returning an error string that the model hallucinates around. These
7
+ -- columns back that flow:
8
+ --
9
+ -- approval_reason — typed discriminator on needs_approval rows.
10
+ -- 'permission_denied' is the new value the
11
+ -- approval routing writes; existing deliverable
12
+ -- and verifier-escalation paths can migrate to
13
+ -- it incrementally.
14
+ -- permission_request — JSON describing the denied call (tool name,
15
+ -- attempted path, suggestedExact, suggestedParent,
16
+ -- guardReason, iteration, timestamp).
17
+ -- permission_grants — JSON array of ephemeral "approve once" paths
18
+ -- carried on a resumed child task. Read by
19
+ -- resolveTaskCapabilities and union'd into the
20
+ -- FileAccessGuard so the child can write without
21
+ -- persisting a row in agent_file_access_paths.
22
+ -- resumed_from_task_id — backref so the UI can thread resume chains
23
+ -- when the operator approves a denied task.
24
+ -- =====================================================================
25
+
26
+ -- @statement
27
+ ALTER TABLE agent_workforce_tasks ADD COLUMN approval_reason TEXT;
28
+ -- @statement
29
+ ALTER TABLE agent_workforce_tasks ADD COLUMN permission_request TEXT;
30
+ -- @statement
31
+ ALTER TABLE agent_workforce_tasks ADD COLUMN permission_grants TEXT;
32
+ -- @statement
33
+ ALTER TABLE agent_workforce_tasks ADD COLUMN resumed_from_task_id TEXT;
34
+ -- @statement
35
+ CREATE INDEX IF NOT EXISTS idx_tasks_approval_reason
36
+ ON agent_workforce_tasks(approval_reason);
@@ -0,0 +1,32 @@
1
+ -- =====================================================================
2
+ -- Migration 114: LLM call tool-use telemetry
3
+ --
4
+ -- Adds per-row "did this model actually call tools on this call" signal
5
+ -- to llm_calls. The agent ReAct loops (react-loop.ts, model-router-loop.ts)
6
+ -- will start writing rows here after commit B wires them up — today the
7
+ -- table only has runLlmCall and orchestrator chat, so agent iterations
8
+ -- are invisible to telemetry.
9
+ --
10
+ -- Columns:
11
+ -- tool_call_count — integer count of tool_calls in the model response
12
+ -- for this single LLM call. NULL when not meaningful
13
+ -- (e.g. a generation purpose call that wasn't offered
14
+ -- tools). 0 is a real value — means the model was
15
+ -- offered tools and chose not to call any.
16
+ -- task_shape — 'work' when looksLikeToolWork(taskInput) is true
17
+ -- at the call site, 'chat' when it's false, NULL when
18
+ -- the call site doesn't have a task input (ad-hoc
19
+ -- /api/llm, orchestrator chat without a task, etc.).
20
+ --
21
+ -- Enables E1's selector self-healing: query the rolling tool-call rate
22
+ -- per (model, task_shape='work') and auto-demote models with <40% rate
23
+ -- from the FAST agent tier.
24
+ -- =====================================================================
25
+
26
+ -- @statement
27
+ ALTER TABLE llm_calls ADD COLUMN tool_call_count INTEGER;
28
+ -- @statement
29
+ ALTER TABLE llm_calls ADD COLUMN task_shape TEXT;
30
+ -- @statement
31
+ CREATE INDEX IF NOT EXISTS llm_calls_model_shape_idx
32
+ ON llm_calls(model, task_shape, created_at DESC);
@@ -0,0 +1,46 @@
1
+ -- =====================================================================
2
+ -- Migration 115: Trigger watchdog columns
3
+ --
4
+ -- Experiment E2: give the daemon a way to notice that a scheduled
5
+ -- trigger has been silently miscarrying. Without this, the diary
6
+ -- trigger shipped silent failures for 2+ weeks before anyone checked
7
+ -- — the trigger fired, the task failed, and nothing aggregated the
8
+ -- outcome at the trigger level.
9
+ --
10
+ -- Columns:
11
+ -- local_triggers.last_succeeded_at — ISO timestamp, set on every
12
+ -- task completion that
13
+ -- traces back to this trigger.
14
+ -- local_triggers.consecutive_failures — int counter. Incremented on
15
+ -- every failed/needs_approval
16
+ -- outcome; reset to 0 on
17
+ -- success. Crossing the
18
+ -- threshold (default 3) emits
19
+ -- a trigger_stuck activity row
20
+ -- so the operator's existing
21
+ -- activity surface picks it up
22
+ -- without new UI.
23
+ -- agent_workforce_tasks.source_trigger_id — FK-ish back-link so the
24
+ -- task finalization hook can
25
+ -- find which trigger to
26
+ -- update without walking the
27
+ -- title prefix or parsing
28
+ -- action_result JSON.
29
+ --
30
+ -- The resumed child task spawned by ohwow_approve_permission_request
31
+ -- inherits source_trigger_id from its parent so a successful resume
32
+ -- also resets the trigger counter.
33
+ -- =====================================================================
34
+
35
+ -- @statement
36
+ ALTER TABLE local_triggers ADD COLUMN last_succeeded_at TEXT;
37
+ -- @statement
38
+ ALTER TABLE local_triggers ADD COLUMN consecutive_failures INTEGER DEFAULT 0;
39
+ -- @statement
40
+ ALTER TABLE agent_workforce_tasks ADD COLUMN source_trigger_id TEXT;
41
+ -- @statement
42
+ CREATE INDEX IF NOT EXISTS idx_tasks_source_trigger
43
+ ON agent_workforce_tasks(source_trigger_id);
44
+ -- @statement
45
+ CREATE INDEX IF NOT EXISTS idx_triggers_consecutive_failures
46
+ ON local_triggers(consecutive_failures DESC) WHERE consecutive_failures > 0;