ohwow 0.6.9 → 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 (50) hide show
  1. package/dist/index.js +3112 -1534
  2. package/dist/mcp-server/index.js +12 -12
  3. package/dist/migrations/091-skills-sop-columns.sql +18 -0
  4. package/dist/migrations/092-operational-pillars.sql +55 -0
  5. package/dist/migrations/093-seed-operational-pillars.sql +176 -0
  6. package/dist/migrations/094-person-models.sql +58 -0
  7. package/dist/migrations/095-transition-engine.sql +52 -0
  8. package/dist/migrations/096-work-router.sql +95 -0
  9. package/dist/migrations/097-human-growth.sql +58 -0
  10. package/dist/migrations/098-observation-layer.sql +5 -0
  11. package/dist/migrations/099-collective-intelligence.sql +5 -0
  12. package/dist/migrations/100-agent-model-policy.sql +32 -0
  13. package/dist/migrations/101-llm-calls.sql +36 -0
  14. package/dist/migrations/102-team-member-guide.sql +14 -0
  15. package/dist/migrations/103-fix-person-models-fk.sql +89 -0
  16. package/dist/migrations/104-fix-person-observations-fk.sql +48 -0
  17. package/dist/migrations/105-onboarding-plans.sql +56 -0
  18. package/dist/migrations/106-deliverable-actor-link.sql +29 -0
  19. package/dist/migrations/107-code-skills.sql +20 -0
  20. package/dist/migrations/108-archive-procedure-skills.sql +29 -0
  21. package/dist/migrations/109-workspace-default-fs-paths.sql +19 -0
  22. package/dist/migrations/110-task-state-ttl.sql +28 -0
  23. package/dist/migrations/111-conversation-status.sql +25 -0
  24. package/dist/migrations/112-deliverables-created-at-iso.sql +40 -0
  25. package/dist/migrations/113-permission-requests.sql +36 -0
  26. package/dist/migrations/114-llm-calls-tool-telemetry.sql +32 -0
  27. package/dist/migrations/115-trigger-watchdog.sql +46 -0
  28. package/dist/migrations/116-self-findings.sql +46 -0
  29. package/dist/migrations/117-experiment-validations.sql +64 -0
  30. package/dist/migrations/118-validation-rollback.sql +33 -0
  31. package/dist/migrations/119-runtime-config-overrides.sql +44 -0
  32. package/dist/migrations/120-business-vitals.sql +44 -0
  33. package/dist/migrations/121-x-contact-events.sql +42 -0
  34. package/dist/migrations/122-video-jobs.sql +52 -0
  35. package/dist/migrations/123-insight-distiller.sql +68 -0
  36. package/dist/migrations/124-x-dm-messages.sql +55 -0
  37. package/dist/migrations/125-x-dm-messages-bodies.sql +40 -0
  38. package/dist/migrations/126-x-dm-signals.sql +52 -0
  39. package/dist/migrations/127-x-dm-contact-linking.sql +36 -0
  40. package/dist/migrations/128-attribution-view.sql +59 -0
  41. package/dist/migrations/129-x-posted-log.sql +36 -0
  42. package/dist/migrations/130-patches-attempted-log.sql +44 -0
  43. package/dist/scrapling-server/requirements.txt +3 -0
  44. package/dist/scrapling-server/server.py +224 -0
  45. package/dist/web/assets/index-Bp9CoQ8c.css +1 -0
  46. package/dist/web/assets/index-C5xtuLcg.js +102 -0
  47. package/dist/web/index.html +2 -2
  48. package/package.json +9 -4
  49. package/dist/web/assets/index-B2PzvKIq.js +0 -100
  50. 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 Z}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as tt}from"@modelcontextprotocol/sdk/server/stdio.js";import{readFileSync as f,existsSync as C}from"fs";import{join as x}from"path";import{homedir as H}from"os";var g=class n{constructor(o,t,e){this.baseUrl=`http://127.0.0.1:${o}`,this.token=t,this.tokenPath=e}static async create(){let o=x(H(),".ohwow"),t=x(o,"config.json"),e=x(o,"data","daemon.token"),r=7700;if(C(t))try{let c=f(t,"utf-8"),a=JSON.parse(c);a.port&&(r=a.port)}catch{}if(!C(e))throw new Error("OHWOW daemon is not running. Start it with: ohwow");let s=f(e,"utf-8").trim();if(!s)throw new Error("Couldn't authenticate with OHWOW daemon. Try: ohwow restart");let i=new n(r,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=f(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 r={...t.headers,...this.authHeaders()};e=await fetch(o,{...t,headers:r})}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 r=new AbortController,s=setTimeout(()=>r.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:r.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(),b=new TextDecoder,m="";for(;;){let{done:w,value:L}=await p.read();if(w)break;m+=b.decode(L,{stream:!0});let R=m.split(`
3
- `);m=R.pop()||"";for(let T of R){if(!T.startsWith("data: "))continue;let O=T.slice(6).trim();if(O!=="[DONE]")try{let u=JSON.parse(O);if(u.type==="text"&&u.content)i.push(u.content);else if(u.type==="tool_start")i.push(`
4
- [Using tool: ${u.name}]
5
- `);else if(u.type==="error")i.push(`
6
- [Error: ${u.error}]
7
- `);else if(u.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 h}from"zod";function $(n,o){n.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:h.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}}}),n.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}}}),n.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:h.string().describe("The ID of the agent to run"),prompt:h.string().describe("The task or instruction for the agent")},async({agentId:t,prompt:e})=>{try{let r=await o.post("/api/tasks",{agentId:t,title:e});return{content:[{type:"text",text:JSON.stringify(r,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_get_task","[Tasks] Get the status and result of a task by its ID.",{taskId:h.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}}}),n.tool("ohwow_list_tasks","[Tasks] List recent tasks. Optionally filter by status or agent.",{status:h.string().optional().describe("Filter by status: pending, running, completed, failed"),agentId:h.string().optional().describe("Filter by agent ID"),limit:h.number().optional().describe("Max number of tasks to return (default: 20)")},async({status:t,agentId:e,limit:r})=>{try{let s=new URLSearchParams;t&&s.set("status",t),e&&s.set("agentId",e),r&&s.set("limit",String(r));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}}}),n.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}}})}import{z as l}from"zod";function j(n,o){n.tool("ohwow_list_contacts","[CRM] List contacts in the workspace. Returns name, email, company, pipeline stage, and tags.",{search:l.string().optional().describe("Filter by name, email, or company"),limit:l.number().optional().describe("Max results (default: 50)")},async({search:t,limit:e})=>{try{let r=new URLSearchParams;t&&r.set("search",t),e&&r.set("limit",String(e));let s=r.toString(),i=await o.get(`/api/contacts${s?`?${s}`:""}`),c=i.data||i;return{content:[{type:"text",text:JSON.stringify(c,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_create_contact","[CRM] Add a new contact to the CRM.",{name:l.string().describe("Contact full name"),email:l.string().optional().describe("Email address"),phone:l.string().optional().describe("Phone number"),company:l.string().optional().describe("Company or organization"),tags:l.array(l.string()).optional().describe("Tags for categorization"),notes:l.string().optional().describe("Additional notes about the contact")},async({name:t,email:e,phone:r,company:s,tags:i,notes:c})=>{try{let a={name:t};e&&(a.email=e),r&&(a.phone=r),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}}}),n.tool("ohwow_search_contacts","[CRM] Full-text search across contacts by name, email, company, or notes.",{query:l.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 d}from"zod";function v(n,o){n.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}}}),n.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:d.string().describe("The workflow ID to execute"),variables:d.record(d.string(),d.unknown()).optional().describe("Input variables for the workflow")},async({workflowId:t,variables:e})=>{try{let r=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}"${r}.`},6e4)||"Workflow started"}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),n.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}}}),n.tool("ohwow_run_automation","[Automations] Manually trigger an automation by ID. Use ohwow_list_automations to find IDs.",{automationId:d.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 A}from"zod";function U(n,o){n.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}}}),n.tool("ohwow_create_project","[Projects] Create a new project for organizing work.",{name:A.string().describe("Project name"),description:A.string().optional().describe("Project description")},async({name:t,description:e})=>{try{let r={name:t};e&&(r.description=e);let s=await o.post("/api/projects",r);return{content:[{type:"text",text:JSON.stringify(s,null,2)}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),n.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 k}from"zod";function N(n,o){n.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}}}),n.tool("ohwow_search_knowledge","[Knowledge] Semantic search across the knowledge base. Returns relevant document chunks.",{query:k.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}}}),n.tool("ohwow_add_knowledge_url","[Knowledge] Add a web page to the knowledge base. Fetches, chunks, and embeds the content for later search.",{url:k.string().describe("The URL to ingest"),title:k.string().optional().describe("Optional title for the document")},async({url:t,title:e})=>{try{let r=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}"${r}.`},3e4)||"Knowledge document added"}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}})}import{z as _}from"zod";function D(n,o){n.tool("ohwow_deep_research","[Research] Multi-source web research with synthesis. Timing: quick ~30s, thorough ~60s, comprehensive ~120s.",{question:_.string().describe("The research question"),depth:_.enum(["quick","thorough","comprehensive"]).optional().describe("Research depth (default: thorough)")},async({question:t,depth:e})=>{try{let r=e?` with depth: "${e}"`:"";return{content:[{type:"text",text:await o.postSSE("/api/chat",{message:`Use the deep_research tool with question: "${t}"${r}. Return the full research report.`},18e4)||"No research results"}]}}catch(r){return{content:[{type:"text",text:`Error: ${r instanceof Error?r.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_scrape_url","[Research] Scrape a web page and return its structured content. Automatically handles anti-bot protection.",{url:_.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 y}from"zod";function M(n,o){n.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:y.enum(["whatsapp","telegram"]).describe("Messaging channel"),chatId:y.string().describe("Chat or contact ID to send to"),message:y.string().describe("Message text to send")},async({channel:t,chatId:e,message:r})=>{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}": ${r}`},15e3)||"Message sent"}]}}catch(s){return{content:[{type:"text",text:`Error: ${s instanceof Error?s.message:"Unknown error"}`}],isError:!0}}}),n.tool("ohwow_list_chats","[Messaging] List connected chats for WhatsApp or Telegram.",{channel:y.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 W(n,o){n.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}}}),n.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}}})}function I(n,o){$(n,o),j(n,o),v(n,o),U(n,o),N(n,o),D(n,o),M(n,o),W(n,o)}function P(n,o){n.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?"}]}}}),n.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?"}]}}}),n.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?"}]}}}),n.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?"}]}}}),n.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")]),r=t.data||t,s=e.data||e;return{contents:[{uri:"ohwow://workflows",mimeType:"application/json",text:JSON.stringify({workflows:r,automations:s},null,2)}]}}catch{return{contents:[{uri:"ohwow://workflows",mimeType:"text/plain",text:"Could not load workflows. Is the OHWOW daemon running?"}]}}}),n.resource("capabilities","ohwow://capabilities",{description:"Complete list of all OHWOW MCP tools grouped by domain"},async()=>({contents:[{uri:"ohwow://capabilities",mimeType:"text/plain",text:q}]}))}var q=`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 F}from"http";import{writeFileSync as G,unlinkSync as z,existsSync as K}from"fs";import{join as B}from"path";import{homedir as V}from"os";var S=B(V(),".ohwow","data","mcp-sampling.port");function J(n){let o=null,t=F(async(r,s)=>{if(r.method!=="POST"||r.url!=="/sampling"){s.writeHead(404),s.end("Not found");return}let i="";for await(let a of r)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(w=>({role:w.role,content:{type:"text",text:w.content}})),p=await n.server.createMessage({messages:a,systemPrompt:c.systemPrompt,maxTokens:c.maxTokens||4096,temperature:c.temperature}),m={content:Array.isArray(p.content)?p.content.filter(w=>w.type==="text").map(w=>w.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(m))}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 r=t.address();if(r&&typeof r=="object"){let s=r.port;try{G(S,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{K(S)&&z(S)}catch{}}}}import{createRequire as Q}from"module";var X=Q(import.meta.url),Y=X("../package.json"),E=Y.version;async function Ft(){let n;try{n=await g.create()}catch(s){process.stderr.write(`[ohwow-mcp] ${s instanceof Error?s.message:"Unknown error"}
63
- `),process.exit(1)}let o=new Z({name:"ohwow",version:E});I(o,n),P(o,n);let t=new tt,e=null,r=async()=>{e?.();try{await o.close()}catch{}process.exit(0)};process.on("SIGTERM",r),process.on("SIGINT",r);try{await o.connect(t),process.stderr.write(`[ohwow-mcp] Connected (v${E})
64
- `),e=J(o).cleanup}catch(s){process.stderr.write(`[ohwow-mcp] Failed to connect: ${s instanceof Error?s.message:"Unknown error"}
65
- `),process.exit(1)}}export{Ft 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,18 @@
1
+ -- Add columns for SOP system: trigger matching, usage tracking, and cloud sync.
2
+ -- Uses IF NOT EXISTS pattern to handle partial application safely.
3
+
4
+ -- Skills: trigger matching + usage tracking + sync
5
+ ALTER TABLE agent_workforce_skills ADD COLUMN triggers TEXT DEFAULT '[]';
6
+ ALTER TABLE agent_workforce_skills ADD COLUMN times_used INTEGER DEFAULT 0;
7
+ ALTER TABLE agent_workforce_skills ADD COLUMN success_rate REAL;
8
+ ALTER TABLE agent_workforce_skills ADD COLUMN last_used_at TEXT;
9
+ ALTER TABLE agent_workforce_skills ADD COLUMN locality_policy TEXT DEFAULT 'sync';
10
+ ALTER TABLE agent_workforce_skills ADD COLUMN source_device_id TEXT;
11
+
12
+ -- Discovered processes: trigger matching + auto-execution + sync
13
+ ALTER TABLE agent_workforce_discovered_processes ADD COLUMN trigger_message TEXT;
14
+ ALTER TABLE agent_workforce_discovered_processes ADD COLUMN last_auto_executed_at TEXT;
15
+ ALTER TABLE agent_workforce_discovered_processes ADD COLUMN auto_execute_count INTEGER DEFAULT 0;
16
+ ALTER TABLE agent_workforce_discovered_processes ADD COLUMN updated_at TEXT;
17
+ ALTER TABLE agent_workforce_discovered_processes ADD COLUMN locality_policy TEXT DEFAULT 'sync';
18
+ ALTER TABLE agent_workforce_discovered_processes ADD COLUMN source_device_id TEXT;
@@ -0,0 +1,55 @@
1
+ -- Operational Pillars: what a business SHOULD be doing at each stage
2
+ -- Global reference table + per-workspace instance tracking
3
+
4
+ CREATE TABLE IF NOT EXISTS agent_workforce_operational_pillars (
5
+ id TEXT PRIMARY KEY,
6
+ slug TEXT UNIQUE NOT NULL,
7
+ name TEXT NOT NULL,
8
+ description TEXT NOT NULL,
9
+ category TEXT NOT NULL CHECK (category IN (
10
+ 'acquisition', 'retention', 'operations', 'finance', 'product', 'team', 'strategy'
11
+ )),
12
+ icon TEXT NOT NULL DEFAULT 'circle',
13
+ business_types TEXT NOT NULL DEFAULT '[]', -- JSON array of business types
14
+ min_stage INTEGER NOT NULL DEFAULT 0,
15
+ max_stage INTEGER NOT NULL DEFAULT 9,
16
+ priority_by_stage TEXT NOT NULL DEFAULT '{}', -- JSON: { "0": "critical", "1": "important" }
17
+ kpis TEXT NOT NULL DEFAULT '[]', -- JSON array
18
+ best_practices TEXT NOT NULL DEFAULT '[]', -- JSON array
19
+ setup_steps TEXT NOT NULL DEFAULT '[]', -- JSON array
20
+ estimated_setup_hours REAL DEFAULT 1,
21
+ prerequisite_pillar_ids TEXT DEFAULT '[]', -- JSON array of IDs
22
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
23
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
24
+ );
25
+
26
+ CREATE TABLE IF NOT EXISTS agent_workforce_pillar_instances (
27
+ id TEXT PRIMARY KEY,
28
+ workspace_id TEXT NOT NULL REFERENCES agent_workforce_workspaces(id) ON DELETE CASCADE,
29
+ pillar_id TEXT NOT NULL REFERENCES agent_workforce_operational_pillars(id) ON DELETE CASCADE,
30
+ status TEXT NOT NULL DEFAULT 'not_started' CHECK (status IN (
31
+ 'not_started', 'suggested', 'building', 'running', 'optimizing', 'paused', 'dismissed'
32
+ )),
33
+ mode TEXT NOT NULL DEFAULT 'builder' CHECK (mode IN ('builder', 'optimizer')),
34
+ health_score REAL DEFAULT 0 CHECK (health_score >= 0 AND health_score <= 1),
35
+ last_health_check TEXT,
36
+ health_details TEXT DEFAULT '{}',
37
+ agent_ids TEXT DEFAULT '[]',
38
+ automation_ids TEXT DEFAULT '[]',
39
+ schedule_cron TEXT,
40
+ blueprint TEXT DEFAULT NULL,
41
+ blueprint_approved_at TEXT,
42
+ total_tasks_completed INTEGER DEFAULT 0,
43
+ total_cost_cents INTEGER DEFAULT 0,
44
+ estimated_hours_saved REAL DEFAULT 0,
45
+ suggested_at TEXT,
46
+ building_started_at TEXT,
47
+ running_since TEXT,
48
+ last_activity_at TEXT,
49
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
50
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
51
+ UNIQUE(workspace_id, pillar_id)
52
+ );
53
+
54
+ CREATE INDEX IF NOT EXISTS idx_pillar_instances_workspace ON agent_workforce_pillar_instances(workspace_id);
55
+ CREATE INDEX IF NOT EXISTS idx_pillar_instances_status ON agent_workforce_pillar_instances(workspace_id, status);
@@ -0,0 +1,176 @@
1
+ -- Seed operational pillars (SQLite version)
2
+ -- Same data as cloud migration 342 but with SQLite-compatible syntax
3
+
4
+ INSERT OR REPLACE INTO agent_workforce_operational_pillars (id, slug, name, description, category, icon, business_types, min_stage, max_stage, priority_by_stage, kpis, setup_steps, estimated_setup_hours) VALUES
5
+
6
+ -- ACQUISITION
7
+ (lower(hex(randomblob(16))), 'content_pipeline', 'Content Pipeline',
8
+ 'Systematic content creation and distribution. Blog posts, social media, newsletters, videos. Builds organic discovery and establishes authority in your space.',
9
+ 'acquisition', 'pencil-line', '[]', 0, 9,
10
+ '{"0":"important","1":"critical","2":"critical","3":"important","4":"important","5":"recommended","6":"recommended","7":"recommended","8":"critical","9":"recommended"}',
11
+ '[{"name":"Posts published per week","target":2,"unit":"count"},{"name":"Organic traffic growth","target":10,"unit":"percent_monthly"},{"name":"Email subscribers","target":null,"unit":"count"}]',
12
+ '[{"order":1,"title":"Audit existing content and brand voice","description":"Review what content exists, identify gaps, establish tone and style.","agent_role":"Content Strategist"},{"order":2,"title":"Build content calendar","description":"Plan 4 weeks of content across channels, aligned with business goals.","agent_role":"Content Strategist"},{"order":3,"title":"Draft first batch of content","description":"Create 4-6 pieces of content ready for review.","agent_role":"Content Creator"},{"order":4,"title":"Set up distribution channels","description":"Configure social media scheduling, newsletter tool, and cross-posting.","agent_role":"Social Media Manager"},{"order":5,"title":"Launch and measure","description":"Publish first batch, set up analytics tracking, establish baseline metrics.","agent_role":"Content Strategist"}]',
13
+ 3),
14
+
15
+ (lower(hex(randomblob(16))), 'outbound_outreach', 'Outbound Outreach',
16
+ 'Proactive outreach to potential customers. Cold email, DMs, partnerships, community engagement. Direct path to first customers and rapid feedback.',
17
+ 'acquisition', 'send', '[]', 0, 6,
18
+ '{"0":"critical","1":"critical","2":"important","3":"recommended","4":"recommended","5":"recommended","6":"nice_to_have"}',
19
+ '[{"name":"Outreach messages sent per week","target":50,"unit":"count"},{"name":"Reply rate","target":15,"unit":"percent"},{"name":"Conversations started","target":10,"unit":"count_weekly"}]',
20
+ '[{"order":1,"title":"Define ideal customer profile","description":"Who exactly are you reaching out to? What pain do they have?","agent_role":"Sales Strategist"},{"order":2,"title":"Build prospect list","description":"Research and compile list of 200+ prospects from relevant communities, directories, and social platforms.","agent_role":"Lead Researcher"},{"order":3,"title":"Craft outreach sequences","description":"Write personalized message templates for 3 different angles. Test which resonates.","agent_role":"Outreach Specialist"},{"order":4,"title":"Launch first campaign","description":"Send first batch of 50 messages. Track replies, iterate on messaging.","agent_role":"Outreach Specialist"}]',
21
+ 2),
22
+
23
+ (lower(hex(randomblob(16))), 'community_building', 'Community Building',
24
+ 'Build and nurture a community around your product or niche. Discord, forum, social groups. Creates organic word-of-mouth and deep customer relationships.',
25
+ 'acquisition', 'users', '[]', 1, 9,
26
+ '{"1":"recommended","2":"important","3":"important","4":"important","5":"critical","6":"important","7":"important","8":"critical","9":"important"}',
27
+ '[{"name":"Active community members","target":null,"unit":"count"},{"name":"Weekly engagement rate","target":20,"unit":"percent"},{"name":"Community-sourced leads","target":null,"unit":"count_monthly"}]',
28
+ '[{"order":1,"title":"Choose platform and structure","description":"Discord, GitHub Discussions, or forum. Define channels/categories for your audience.","agent_role":"Community Manager"},{"order":2,"title":"Seed with initial content","description":"Create 10+ discussion starters, resources, and guides.","agent_role":"Community Manager"},{"order":3,"title":"Invite first members","description":"Personal invites to existing users, supporters, and relevant contacts.","agent_role":"Outreach Specialist"},{"order":4,"title":"Establish engagement rhythm","description":"Weekly discussions, AMAs, showcases. Build habits.","agent_role":"Community Manager"}]',
29
+ 4),
30
+
31
+ (lower(hex(randomblob(16))), 'paid_acquisition', 'Paid Acquisition',
32
+ 'Paid advertising and sponsorships. Google Ads, social ads, newsletter sponsorships, influencer partnerships. Scalable customer acquisition with measurable ROI.',
33
+ 'acquisition', 'currency-dollar', '["saas_startup","ecommerce","b2b_enterprise_saas","education_edtech"]', 2, 9,
34
+ '{"2":"recommended","3":"important","4":"critical","5":"critical","6":"important","7":"important","8":"important","9":"recommended"}',
35
+ '[{"name":"Customer acquisition cost","target":null,"unit":"dollars"},{"name":"Return on ad spend","target":3,"unit":"ratio"},{"name":"Paid leads per week","target":null,"unit":"count"}]',
36
+ '[{"order":1,"title":"Define unit economics","description":"Know your LTV, target CAC, and breakeven timeline before spending.","agent_role":"Financial Analyst"},{"order":2,"title":"Choose initial channel","description":"Pick ONE paid channel based on where your customers are. Start small.","agent_role":"Ad Strategist"},{"order":3,"title":"Create ad assets","description":"Write copy, design creatives, build landing pages for first campaign.","agent_role":"Ad Copywriter"},{"order":4,"title":"Launch and iterate","description":"Start with $20-50/day budget. Measure, optimize, scale what works.","agent_role":"Ad Strategist"}]',
37
+ 2),
38
+
39
+ (lower(hex(randomblob(16))), 'partnership_channel', 'Partnerships and Referrals',
40
+ 'Structured partnerships, affiliate programs, and referral systems. Leverage other people''s audiences and networks for scalable, trust-based growth.',
41
+ 'acquisition', 'handshake', '[]', 2, 9,
42
+ '{"2":"nice_to_have","3":"recommended","4":"important","5":"critical","6":"critical","7":"important","8":"important","9":"important"}',
43
+ '[{"name":"Active partners","target":null,"unit":"count"},{"name":"Partner-sourced revenue","target":null,"unit":"dollars_monthly"},{"name":"Referral conversion rate","target":20,"unit":"percent"}]',
44
+ '[]', 3),
45
+
46
+ (lower(hex(randomblob(16))), 'seo_optimization', 'SEO and Organic Search',
47
+ 'Search engine optimization for long-term organic discovery. Keyword research, on-page optimization, link building.',
48
+ 'acquisition', 'search', '["saas_startup","ecommerce","agency","content_creator","service_business","consulting","education_edtech","b2b_enterprise_saas"]', 1, 9,
49
+ '{"1":"recommended","2":"important","3":"important","4":"critical","5":"critical","6":"important","7":"recommended","8":"important","9":"recommended"}',
50
+ '[{"name":"Organic search traffic","target":null,"unit":"visits_monthly"},{"name":"Keywords ranking top 10","target":null,"unit":"count"}]',
51
+ '[]', 2),
52
+
53
+ -- RETENTION
54
+ (lower(hex(randomblob(16))), 'user_onboarding', 'User Onboarding',
55
+ 'Structured onboarding that gets new users to their first success moment. Emails, in-app guides, tutorials.',
56
+ 'retention', 'door-open', '["saas_startup","ecommerce","tech_company","education_edtech","b2b_enterprise_saas"]', 0, 9,
57
+ '{"0":"important","1":"critical","2":"critical","3":"important","4":"important","5":"important"}',
58
+ '[{"name":"Activation rate","target":40,"unit":"percent"},{"name":"Time to first value","target":null,"unit":"minutes"}]',
59
+ '[]', 3),
60
+
61
+ (lower(hex(randomblob(16))), 'customer_feedback', 'Customer Feedback Loops',
62
+ 'Systematic collection and analysis of customer feedback. NPS, surveys, interviews, support tickets.',
63
+ 'retention', 'message-circle', '[]', 0, 9,
64
+ '{"0":"critical","1":"critical","2":"important","3":"important","4":"important"}',
65
+ '[{"name":"NPS score","target":40,"unit":"score"},{"name":"Feedback responses per month","target":null,"unit":"count"}]',
66
+ '[]', 2),
67
+
68
+ (lower(hex(randomblob(16))), 'customer_support', 'Customer Support',
69
+ 'Responsive support system. Help docs, chatbot, ticket management, escalation paths.',
70
+ 'retention', 'life-buoy', '["saas_startup","ecommerce","tech_company","service_business","b2b_enterprise_saas","healthcare_wellness"]', 1, 9,
71
+ '{"1":"recommended","2":"important","3":"critical","4":"critical","5":"critical"}',
72
+ '[{"name":"First response time","target":4,"unit":"hours"},{"name":"Resolution rate","target":90,"unit":"percent"}]',
73
+ '[]', 2),
74
+
75
+ (lower(hex(randomblob(16))), 'customer_success', 'Customer Success',
76
+ 'Proactive relationship management with existing customers. Check-ins, usage monitoring, expansion opportunities.',
77
+ 'retention', 'heart-handshake', '["saas_startup","agency","consulting","service_business","b2b_enterprise_saas"]', 3, 9,
78
+ '{"3":"recommended","4":"important","5":"critical","6":"critical","7":"important"}',
79
+ '[{"name":"Net revenue retention","target":110,"unit":"percent"},{"name":"Churn rate","target":5,"unit":"percent_monthly"}]',
80
+ '[]', 3),
81
+
82
+ -- OPERATIONS
83
+ (lower(hex(randomblob(16))), 'process_documentation', 'Process Documentation (SOPs)',
84
+ 'Document how things work. Standard operating procedures for recurring tasks.',
85
+ 'operations', 'file-text', '[]', 3, 9,
86
+ '{"3":"critical","4":"important","5":"important","6":"critical","7":"critical"}',
87
+ '[{"name":"Documented processes","target":null,"unit":"count"}]',
88
+ '[]', 4),
89
+
90
+ (lower(hex(randomblob(16))), 'reporting_analytics', 'Reporting and Analytics',
91
+ 'Know your numbers. Dashboards, weekly reports, KPI tracking.',
92
+ 'operations', 'chart-bar', '[]', 0, 9,
93
+ '{"0":"recommended","1":"important","2":"critical","3":"critical","4":"critical"}',
94
+ '[{"name":"Metrics tracked","target":null,"unit":"count"},{"name":"Report delivery consistency","target":100,"unit":"percent_weekly"}]',
95
+ '[]', 2),
96
+
97
+ (lower(hex(randomblob(16))), 'automation_workflows', 'Workflow Automation',
98
+ 'Automate repetitive tasks. Scheduled jobs, triggered actions. Every manual step eliminated is time reclaimed forever.',
99
+ 'operations', 'workflow', '[]', 2, 9,
100
+ '{"2":"recommended","3":"critical","4":"critical","5":"important","6":"critical"}',
101
+ '[{"name":"Automated workflows running","target":null,"unit":"count"},{"name":"Hours saved per week","target":null,"unit":"hours"}]',
102
+ '[]', 2),
103
+
104
+ -- FINANCE
105
+ (lower(hex(randomblob(16))), 'pricing_strategy', 'Pricing Strategy',
106
+ 'Intentional pricing that reflects value and supports growth.',
107
+ 'finance', 'tag', '[]', 1, 9,
108
+ '{"1":"critical","2":"important","3":"important","4":"critical","5":"important","6":"critical"}',
109
+ '[{"name":"Revenue per customer","target":null,"unit":"dollars"}]',
110
+ '[]', 2),
111
+
112
+ (lower(hex(randomblob(16))), 'financial_tracking', 'Financial Tracking',
113
+ 'Know where your money goes and comes from. Revenue tracking, expense management, runway calculation.',
114
+ 'finance', 'wallet', '[]', 1, 9,
115
+ '{"1":"recommended","2":"important","3":"critical","4":"critical","5":"critical","6":"critical","7":"critical"}',
116
+ '[{"name":"Monthly burn rate known","target":null,"unit":"boolean"},{"name":"Runway months","target":null,"unit":"months"}]',
117
+ '[]', 2),
118
+
119
+ (lower(hex(randomblob(16))), 'fundraise_readiness', 'Fundraise Readiness',
120
+ 'Passive preparation for fundraising or exit. Data room, metrics history, narrative.',
121
+ 'finance', 'briefcase', '["saas_startup","tech_company","b2b_enterprise_saas","education_edtech"]', 2, 9,
122
+ '{"2":"nice_to_have","3":"nice_to_have","4":"recommended","5":"important","6":"important","7":"critical","8":"critical","9":"critical"}',
123
+ '[{"name":"Data room completeness","target":100,"unit":"percent"}]',
124
+ '[]', 2),
125
+
126
+ -- PRODUCT
127
+ (lower(hex(randomblob(16))), 'product_analytics', 'Product Analytics',
128
+ 'Understand how people actually use your product. Feature adoption, user flows, drop-off points.',
129
+ 'product', 'chart-line', '["saas_startup","ecommerce","tech_company","education_edtech","b2b_enterprise_saas"]', 0, 9,
130
+ '{"0":"important","1":"critical","2":"critical","3":"important","4":"critical"}',
131
+ '[{"name":"Weekly active users","target":null,"unit":"count"},{"name":"Feature adoption rate","target":null,"unit":"percent"}]',
132
+ '[]', 2),
133
+
134
+ (lower(hex(randomblob(16))), 'competitive_intel', 'Competitive Intelligence',
135
+ 'Know what your competitors are doing. Product changes, pricing moves, marketing campaigns.',
136
+ 'strategy', 'binoculars', '[]', 0, 9,
137
+ '{"0":"recommended","1":"important","2":"important","3":"recommended","4":"important","5":"important","8":"critical"}',
138
+ '[{"name":"Competitors tracked","target":null,"unit":"count"},{"name":"Intel reports delivered","target":1,"unit":"count_weekly"}]',
139
+ '[]', 2),
140
+
141
+ (lower(hex(randomblob(16))), 'uptime_monitoring', 'Uptime and Performance Monitoring',
142
+ 'Know when things break before your customers tell you. Uptime checks, performance monitoring, alerting.',
143
+ 'product', 'activity', '["saas_startup","ecommerce","tech_company","b2b_enterprise_saas"]', 0, 9,
144
+ '{"0":"important","1":"critical","2":"critical","3":"critical","4":"important"}',
145
+ '[{"name":"Uptime percentage","target":99.9,"unit":"percent"},{"name":"Mean time to detection","target":5,"unit":"minutes"}]',
146
+ '[]', 1),
147
+
148
+ -- TEAM
149
+ (lower(hex(randomblob(16))), 'hiring_pipeline', 'Hiring Pipeline',
150
+ 'Structured approach to finding and onboarding new team members.',
151
+ 'team', 'user-plus', '[]', 3, 9,
152
+ '{"3":"recommended","4":"recommended","5":"important","6":"important","7":"critical","8":"critical"}',
153
+ '[{"name":"Open positions filled","target":null,"unit":"count"},{"name":"Time to hire","target":30,"unit":"days"}]',
154
+ '[]', 4),
155
+
156
+ (lower(hex(randomblob(16))), 'delegation_system', 'Delegation System',
157
+ 'Structured delegation of founder/leader decisions to team members and agents.',
158
+ 'team', 'git-branch', '[]', 3, 9,
159
+ '{"3":"critical","4":"critical","5":"important","6":"important","7":"critical"}',
160
+ '[{"name":"Decisions requiring founder","target":null,"unit":"count_weekly"}]',
161
+ '[]', 3),
162
+
163
+ -- STRATEGY
164
+ (lower(hex(randomblob(16))), 'positioning', 'Market Positioning',
165
+ 'Clear articulation of who you are, who you serve, and why you''re different.',
166
+ 'strategy', 'target', '[]', 0, 9,
167
+ '{"0":"critical","1":"critical","2":"important","3":"recommended","4":"critical","5":"important","8":"critical"}',
168
+ '[{"name":"Positioning statement exists","target":null,"unit":"boolean"}]',
169
+ '[]', 2),
170
+
171
+ (lower(hex(randomblob(16))), 'customer_research', 'Customer Research',
172
+ 'Deep understanding of your customers beyond surface demographics. Jobs-to-be-done, pain points, buying triggers.',
173
+ 'strategy', 'microscope', '[]', 0, 9,
174
+ '{"0":"critical","1":"critical","2":"important","3":"recommended","4":"critical"}',
175
+ '[{"name":"Customer interviews this month","target":4,"unit":"count"},{"name":"Personas documented","target":null,"unit":"count"}]',
176
+ '[]', 2);
@@ -0,0 +1,58 @@
1
+ -- Person Models: deep understanding of each human in the workspace
2
+
3
+ CREATE TABLE IF NOT EXISTS agent_workforce_person_models (
4
+ id TEXT PRIMARY KEY,
5
+ workspace_id TEXT NOT NULL REFERENCES agent_workforce_workspaces(id) ON DELETE CASCADE,
6
+ team_member_id TEXT REFERENCES team_members(id) ON DELETE SET NULL,
7
+ name TEXT NOT NULL,
8
+ email TEXT,
9
+ avatar_url TEXT,
10
+ role_title TEXT,
11
+ variant TEXT NOT NULL DEFAULT 'team_member' CHECK (variant IN ('founder', 'team_member', 'new_hire')),
12
+ work_history TEXT DEFAULT '[]',
13
+ skills_map TEXT DEFAULT '{}',
14
+ domain_expertise TEXT DEFAULT '{}',
15
+ blind_spots TEXT DEFAULT '[]',
16
+ tool_proficiency TEXT DEFAULT '{}',
17
+ communication_style TEXT DEFAULT '{}',
18
+ energy_patterns TEXT DEFAULT '{}',
19
+ learning_style TEXT,
20
+ collaboration_preferences TEXT DEFAULT '{}',
21
+ ambitions TEXT DEFAULT '{}',
22
+ values_and_motivations TEXT DEFAULT '[]',
23
+ friction_points TEXT DEFAULT '[]',
24
+ flow_triggers TEXT DEFAULT '[]',
25
+ skill_gaps_to_close TEXT DEFAULT '[]',
26
+ external_context TEXT DEFAULT '{}',
27
+ growth_arc TEXT DEFAULT '{}',
28
+ growth_velocity REAL DEFAULT 0,
29
+ growth_direction TEXT DEFAULT 'ascending' CHECK (growth_direction IN ('ascending', 'plateau', 'declining', 'transforming')),
30
+ growth_snapshots TEXT DEFAULT '[]',
31
+ ingestion_status TEXT DEFAULT 'not_started' CHECK (ingestion_status IN ('not_started', 'in_progress', 'initial_complete', 'mature')),
32
+ ingestion_variant TEXT,
33
+ last_ingestion_at TEXT,
34
+ observation_count INTEGER DEFAULT 0,
35
+ refinement_count INTEGER DEFAULT 0,
36
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
37
+ updated_at TEXT NOT NULL DEFAULT (datetime('now'))
38
+ );
39
+
40
+ CREATE TABLE IF NOT EXISTS agent_workforce_person_observations (
41
+ id TEXT PRIMARY KEY,
42
+ person_model_id TEXT NOT NULL REFERENCES agent_workforce_person_models(id) ON DELETE CASCADE,
43
+ workspace_id TEXT NOT NULL REFERENCES agent_workforce_workspaces(id) ON DELETE CASCADE,
44
+ dimension TEXT NOT NULL,
45
+ observation_type TEXT NOT NULL CHECK (observation_type IN (
46
+ 'task_outcome', 'communication', 'feedback', 'self_report', 'behavioral', 'peer_observation', 'correction'
47
+ )),
48
+ content TEXT NOT NULL,
49
+ data TEXT DEFAULT '{}',
50
+ confidence REAL DEFAULT 0.5 CHECK (confidence >= 0 AND confidence <= 1),
51
+ processed INTEGER DEFAULT 0,
52
+ source_type TEXT,
53
+ source_id TEXT,
54
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
55
+ );
56
+
57
+ CREATE INDEX IF NOT EXISTS idx_person_models_workspace ON agent_workforce_person_models(workspace_id);
58
+ CREATE INDEX IF NOT EXISTS idx_person_observations_model ON agent_workforce_person_observations(person_model_id);