agentix-cli 0.114.0 → 0.114.1
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.
- package/dist/{agent-5F4EKGQN.js → agent-NGCPRTBH.js} +2 -2
- package/dist/{board-dashboard-GGW5ZZLT.js → board-dashboard-7B2B564A.js} +244 -244
- package/dist/{board-dashboard-GGW5ZZLT.js.map → board-dashboard-7B2B564A.js.map} +1 -1
- package/dist/{chunk-O2RVUM2B.js → chunk-CSLVHAYV.js} +2 -2
- package/dist/{chunk-QQIWLN3H.js → chunk-DOQ6Y7GZ.js} +5 -5
- package/dist/chunk-DOQ6Y7GZ.js.map +1 -0
- package/dist/{chunk-4COYYJJY.js → chunk-EVB23EHN.js} +2 -2
- package/dist/chunk-JVSTKXMI.js +6 -0
- package/dist/chunk-JVSTKXMI.js.map +1 -0
- package/dist/{chunk-XTO4GRGN.js → chunk-QBWJGDKE.js} +3 -3
- package/dist/{chunk-XX7NXI3Y.js → chunk-TZIPWZY6.js} +2 -2
- package/dist/{chunk-HPY54DU6.js → chunk-UMFFIUU6.js} +2 -2
- package/dist/{chunk-YU5NDKPA.js → chunk-UZB5UO35.js} +2 -2
- package/dist/{chunk-VQM77L54.js → chunk-Z6OPM7EH.js} +2 -2
- package/dist/{chunk-6WNQY6K2.js → chunk-ZXQYOSHP.js} +2 -2
- package/dist/cli.js +1 -1
- package/dist/index.js +1 -1
- package/dist/{program-IEMICVO3.js → program-4IMJEMDM.js} +7 -7
- package/dist/{promote-S7SKOKX2.js → promote-4VTXBVUC.js} +2 -2
- package/dist/{query-GOBQ3GL5.js → query-B32UDQJS.js} +2 -2
- package/dist/{registry-HUJEORRW.js → registry-GY4X2GZX.js} +2 -2
- package/dist/{wiki-FZHSX4K3.js → wiki-LVYHDK2R.js} +2 -2
- package/package.json +1 -1
- package/dist/chunk-ILGLEA3I.js +0 -6
- package/dist/chunk-ILGLEA3I.js.map +0 -1
- package/dist/chunk-QQIWLN3H.js.map +0 -1
- /package/dist/{agent-5F4EKGQN.js.map → agent-NGCPRTBH.js.map} +0 -0
- /package/dist/{chunk-O2RVUM2B.js.map → chunk-CSLVHAYV.js.map} +0 -0
- /package/dist/{chunk-4COYYJJY.js.map → chunk-EVB23EHN.js.map} +0 -0
- /package/dist/{chunk-XTO4GRGN.js.map → chunk-QBWJGDKE.js.map} +0 -0
- /package/dist/{chunk-XX7NXI3Y.js.map → chunk-TZIPWZY6.js.map} +0 -0
- /package/dist/{chunk-HPY54DU6.js.map → chunk-UMFFIUU6.js.map} +0 -0
- /package/dist/{chunk-YU5NDKPA.js.map → chunk-UZB5UO35.js.map} +0 -0
- /package/dist/{chunk-VQM77L54.js.map → chunk-Z6OPM7EH.js.map} +0 -0
- /package/dist/{chunk-6WNQY6K2.js.map → chunk-ZXQYOSHP.js.map} +0 -0
- /package/dist/{program-IEMICVO3.js.map → program-4IMJEMDM.js.map} +0 -0
- /package/dist/{promote-S7SKOKX2.js.map → promote-4VTXBVUC.js.map} +0 -0
- /package/dist/{query-GOBQ3GL5.js.map → query-B32UDQJS.js.map} +0 -0
- /package/dist/{registry-HUJEORRW.js.map → registry-GY4X2GZX.js.map} +0 -0
- /package/dist/{wiki-FZHSX4K3.js.map → wiki-LVYHDK2R.js.map} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{q as _,r as W}from"./chunk-2AXFYDWF.js";import{b as Y,c as I,d as O}from"./chunk-AQASR6KR.js";import{b as F}from"./chunk-6W74CHNW.js";import{a as E}from"./chunk-
|
|
1
|
+
import{q as _,r as W}from"./chunk-2AXFYDWF.js";import{b as Y,c as I,d as O}from"./chunk-AQASR6KR.js";import{b as F}from"./chunk-6W74CHNW.js";import{a as E}from"./chunk-TZIPWZY6.js";import{b as L}from"./chunk-CECHWYJ2.js";import{createHash as ee}from"crypto";import{existsSync as H,readdirSync as te,readFileSync as re,writeFileSync as ne}from"fs";import{resolve as P}from"path";function be(n,r,s,t,o,i,c=""){return Z(r,s,t,o,i,c)}function Z(n,r,s,t,o,i=""){let c=r?`
|
|
2
2
|
## Worldview
|
|
3
3
|
|
|
4
4
|
${r}
|
|
@@ -240,4 +240,4 @@ ${g}`}var x="memory-promoter",oe=["project","reference","feedback"],K="_memory-p
|
|
|
240
240
|
Re-emit the FULL corrected JSON object \u2014 same structure, fixed issues, valid JSON only.`:n,y=await t(`${a}/task`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({agent:r.viaAgent,message:h,freshSession:s===null,context:{channel:"api",chatId:r.chatId??"memory-promote"}}),signal:i.signal});if(!y.ok){let f=await y.text().catch(()=>"");throw new Error(`agent /task ${y.status}: ${f.slice(0,500)}`)}let w=await y.json();if(w.error)throw new Error(`agent reported error: ${w.error}`);let b=String(w.content??"").trim();if(!b)throw new Error("agent returned empty content");return b}if(!r.model)throw new Error("callPromotionLlm: viaAgent or model is required");let p=process.env.ANTHROPIC_API_KEY;if(!p)throw new Error("ANTHROPIC_API_KEY not set; use --via <agentId> instead");let u=[{role:"user",content:n}];s&&(u.push({role:"assistant",content:"(previous attempt; will retry with corrections)"}),u.push({role:"user",content:`Your previous response failed validation: ${s}
|
|
241
241
|
Re-emit the FULL corrected JSON object.`}));let g=await t(me,{method:"POST",headers:{"Content-Type":"application/json","x-api-key":p,"anthropic-version":ge},body:JSON.stringify({model:r.model,max_tokens:8e3,messages:u}),signal:i.signal});if(!g.ok){let a=await g.text().catch(()=>"");throw new Error(`anthropic API ${g.status}: ${a.slice(0,500)}`)}let l=await g.json(),e=(Array.isArray(l.content)?l.content:[]).filter(a=>a?.type==="text").map(a=>a.text).join(`
|
|
242
242
|
`).trim();if(!e)throw new Error("anthropic API returned no text content");return e}finally{clearTimeout(c)}}async function Se(n={}){let r=n.wikiDir??P(process.cwd(),".agentx","wiki"),s=n.memoryRoot??P(process.cwd(),".agentx"),t=n.log??(()=>{}),o=n.now??Date.now(),i={candidates:[],clusters:[],proposed:[],skipped:[],gaps:[],warnings:[],errors:[],dryRun:!n.commit},c=new E(r,(...m)=>t(m.map(String).join(" "))),p=c.rebuildIndex(),u=z(r),g=[...ce(s),...n.extraCandidates??[]];if(i.candidates=de(g,p,u,{types:n.types,sinceMs:n.sinceMs,agentFilter:n.agentFilter,now:o,max:n.max}),i.candidates.length===0)return t("no unpromoted memories in window"),i;if(i.clusters=le(i.candidates),i.dryRun)return i;let l=p.articles.map(m=>({title:m.title,path:m.path,type:m.type})),d=c.getWorldview()??"",e=n.budgetTokens??ue,a=i.clusters,h=D(x,a,l,d);for(;R(h)>e&&a.length>1;)a=a.slice(0,-1),h=D(x,a,l,d);if(R(h)>e)return i.errors.push(`judge prompt (~${R(h)} tokens) exceeds the ${e}-token budget even with one cluster`),i;a.length<i.clusters.length&&i.warnings.push(`token budget: judged ${a.length} of ${i.clusters.length} clusters; the rest wait for the next run`);let y=a.flatMap(m=>m.candidates),w=new Set(y.map(m=>m.stamp)),b=n.chatId??`memory-promote-${new Date(o).toISOString().slice(0,10)}`,f=B(await G(h,{...n,chatId:b}),w);if("error"in f&&(t(`parse failed (${f.error}) \u2014 retrying with feedback`),f=B(await G(h,{...n,chatId:b},f.error),w)),"error"in f)return i.errors.push(`LLM response unusable after retry: ${f.error}`),i;i.warnings.push(...f.warnings),i.gaps=f.gaps;let v=new Date(o).toISOString(),k=[],T=new Map(y.map(m=>[m.stamp,m]));for(let m of f.articles){let A=c.readArticle(m.path);if(A&&!c.canWrite(A.meta,x)){i.errors.push(`write denied for "${m.path}" (owned by ${A.meta.owner??"?"})`);continue}let M=fe(m.promotedFrom,T),S={id:Y(o,m.path),createdAt:v,status:"pending",article:m,evidence:M,...A?{replaces:{lastUpdated:A.meta.lastUpdated,contentHash:V(A.content)}}:{}};I(r,S),i.proposed.push({id:S.id,path:m.path,title:m.title,type:m.type,stamps:m.promotedFrom,agents:M.agents,occurrences:M.occurrences});for(let X of m.promotedFrom)k.push({stamp:X,decision:"proposed",article:m.path,proposal:S.id,at:v})}for(let m of f.skipped)k.push({stamp:m.stamp,decision:"skipped",reason:m.reason,at:v}),i.skipped.push(m);return k.length&&C(r,k),i}var ue=6e4;function R(n){return Math.ceil(n.length/4)}function V(n){return ee("sha256").update(n).digest("hex").slice(0,16)}var he=600;function ye(n){return n.startsWith("review:")?"review":n.startsWith(j)?"failure":"memory"}function fe(n,r){let s=[];for(let t of n){let o=r.get(t);o&&s.push({stamp:t,kind:ye(t),agentId:o.agentId,type:o.memory.type,name:o.memory.name,description:o.memory.description,...o.occurrences?{occurrences:o.occurrences}:{},...o.sessions?.length?{sessions:o.sessions}:{},...o.tasks?.length?{tasks:o.tasks}:{},...o.failure?{failure:o.failure}:{},...o.memory.author?{author:o.memory.author}:{},...o.memory.taskId?{taskId:o.memory.taskId}:{},updatedAt:o.memory.updatedAt,excerpt:o.memory.body.slice(0,he)})}return{agents:[...new Set(s.map(t=>t.agentId))].sort(),occurrences:Math.max(1,...s.map(t=>t.occurrences??1)),sources:s}}function Ee(n,r,s={}){let t=O(n,r);if(!t)return{ok:!1,error:`no proposal "${r}"`};if(t.status!=="pending")return{ok:!1,error:`proposal "${r}" is already ${t.status}`};let o=s.log??(()=>{}),i=new E(n,(...a)=>o(a.map(String).join(" "))),{article:c}=t,p=i.readArticle(c.path);if(!s.force){if(p&&!t.replaces)return{ok:!1,error:`"${c.path}" was created after this proposal; review it, then approve with --force`};if(p&&t.replaces&&V(p.content)!==t.replaces.contentHash)return{ok:!1,error:`"${c.path}" changed since this proposal; review it, then approve with --force`}}let u=s.now??Date.now(),g=new Date(u).toISOString().slice(0,10);if(!i.writeArticle(c.path,{title:c.title,type:L(c.type)?c.type:void 0,related:c.related,tags:c.tags,owner:x,access:"public",created:p?.meta.created??g,lastUpdated:g,sources:ae(p?.meta.sources,c.promotedFrom)},c.content,x))return{ok:!1,error:`write denied for "${c.path}" (owned by ${p?.meta.owner??"?"})`};let d=new Date(u).toISOString();C(n,c.promotedFrom.map(a=>({stamp:a,decision:"promoted",article:c.path,proposal:r,at:d}))),i.rebuildIndex();let e={...t,status:"approved",decidedBy:s.by??"operator",decidedAt:d};return I(n,e),{ok:!0,proposal:e}}function Oe(n,r,s={}){let t=O(n,r);if(!t)return{ok:!1,error:`no proposal "${r}"`};if(t.status!=="pending")return{ok:!1,error:`proposal "${r}" is already ${t.status}`};let o=new Date(s.now??Date.now()).toISOString();C(n,t.article.promotedFrom.map(c=>({stamp:c,decision:"rejected",article:t.article.path,proposal:r,reason:s.reason,at:o})));let i={...t,status:"rejected",decidedBy:s.by??"operator",decidedAt:o,...s.reason?{reason:s.reason}:{}};return I(n,i),{ok:!0,proposal:i}}export{be as a,x as b,oe as c,K as d,q as e,ie as f,$ as g,ae as h,ce as i,z as j,C as k,le as l,B as m,de as n,G as o,Se as p,R as q,ye as r,fe as s,Ee as t,Oe as u};
|
|
243
|
-
//# sourceMappingURL=chunk-
|
|
243
|
+
//# sourceMappingURL=chunk-EVB23EHN.js.map
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import{a as T,b as E,c as Y,d as j,e as G}from"./chunk-WPNRDEHW.js";import{v as R,y as O,z as U}from"./chunk-A35RTVMM.js";import{a as V,b as C,f as B}from"./chunk-72SZI2UW.js";import{a as W,b as se}from"./chunk-FX3D7YON.js";import{o as z,p as re}from"./chunk-2PJKGZWP.js";B();var xe="wiki-rerank",Se=30,ne=900;function Ne(a){let e=a.excerptChars??ne;return{query:a.query,candidates:a.candidates.map(t=>({id:t.id,title:t.title,tags:t.tags?.length?t.tags.join(", "):null,excerpt:ie(t.excerpt,e)}))}}function ke(a){let e={};for(let t of a)e[t.id]=t.title;return{best:C(e,"Which candidate answers the query?"),answerable:V("Do any of the candidates answer the query?")}}function Ie(a,e){let t=a.best,r={};for(let i of e)r[i.id]=t.probabilities[i.id]??0;let n=new Map(e.map((i,s)=>[i.id,s]));return{ranked:[...e.map(i=>i.id)].sort((i,s)=>{let c=r[s]-r[i];return c!==0?c:(n.get(i)??0)-(n.get(s)??0)}),scores:r,answerable:a.answerable.noul,confidence:t.confidence}}function ie(a,e){let t=String(a??"").replace(/\s+/g," ").trim();return t.length>e?t.slice(0,e):t}var x={version:2,levels:[{id:"category",description:"Top-level kind of work. Closed set \u2014 pick the closest fit.",axes:[{name:"kind",type:"enum",values:["code","ops","support","admin","knowledge","social","system"],description:"Which category this work belongs in."}]},{id:"verb",description:"Specific intent \u2014 verb-level only, NOT client/project-bound.",axes:[{name:"name",type:"free",description:"Lower-kebab dot-namespaced verb. Examples: review.merge-request, fix.bug, investigate.error, deploy.staging, deploy.production, rollback.release, spec.feature, triage.issue, document.feature, chat.greeting, chat.support-request, schedule.add, config.change, ingest.contact, transcribe.audio, brief.daily, summarize.thread, plan.work."}]}],leafInput:{name:"input",type:"enum",values:["telegram","whatsapp","slack","discord","gitlab-mr","gitlab-issue","github-issue","github-pr","cron","webhook","mesh","http"],description:"Where the message entered the system."},leafOutput:{name:"output",type:"free",description:"One short sentence describing what the agent is expected to produce. Generic \u2014 refers to the verb's outcome, not the specific client/project (those are on the event metadata already)."}},$={code:"Work on source code: review a PR or MR, fix a bug, build a feature, refactor, write a spec, or answer a question about code. Includes changes to AgentX's own code.",ops:"Running systems: deploy, roll back, monitor, scale, restart a service, or handle a production incident.",support:"A person talking to an agent: a greeting, casual chat, thanks, a question to answer, a request for help, a follow-up or complaint about an earlier request, or routing a request to someone else. The default for conversation that is not itself one of the other kinds of work.",admin:"Changing how the agents are set up: configuration, schedules, tokens, roles, adding an agent or a channel. Not a question about the setup or a complaint that something did not run.",knowledge:"Writing or gathering knowledge to keep: documentation, wiki articles, research, summarising a thread.",social:"Content made for an audience other than the agent: posts, announcements, daily briefs, weekly reports. Not a conversation with the agent.",system:"Internal sub-calls made by the platform itself, such as the intent classifier. A message from a person is almost never this."},M=[{category:"code",verb:"review.merge-request",description:"Review a GitLab MR or GitHub PR"},{category:"code",verb:"review.code-change",description:"General code review (snippet, diff)"},{category:"code",verb:"fix.bug",description:"Fix a reported bug"},{category:"code",verb:"implement.feature",description:"Build a new feature"},{category:"code",verb:"investigate.error",description:"Investigate a failing test, exception, pipeline"},{category:"code",verb:"refactor.code",description:"Restructure existing code without behaviour change"},{category:"code",verb:"spec.feature",description:"Write or update a feature spec / RFC"},{category:"code",verb:"triage.issue",description:"Classify, label, or assign an issue"},{category:"code",verb:"answer.code-question",description:"Answer a code-related question"},{category:"ops",verb:"deploy.staging",description:"Deploy to a staging/preview environment"},{category:"ops",verb:"deploy.production",description:"Deploy to production"},{category:"ops",verb:"rollback.release",description:"Roll back a release"},{category:"ops",verb:"investigate.incident",description:"Investigate a production incident"},{category:"ops",verb:"monitor.system",description:"Check system status / metrics"},{category:"ops",verb:"rotate.credential",description:"Rotate a token, key, certificate"},{category:"ops",verb:"audit.security",description:"Review for security issues"},{category:"support",verb:"chat.greeting",description:"A casual hello, no actionable request"},{category:"support",verb:"chat.support-request",description:"End-user / customer asking for help"},{category:"support",verb:"chat.casual",description:"Informal back-and-forth chat"},{category:"support",verb:"answer.question",description:"Answer an open-ended question"},{category:"support",verb:"route.request",description:"Forward a request to the right agent"},{category:"admin",verb:"config.change",description:"Change agentx.json / environment / config"},{category:"admin",verb:"schedule.add",description:"Add a new cron / scheduled task"},{category:"admin",verb:"schedule.remove",description:"Remove or disable a schedule"},{category:"admin",verb:"token.create",description:"Mint a scoped API token"},{category:"admin",verb:"agent.add",description:"Configure a new agent"},{category:"admin",verb:"channel.add",description:"Wire up a new channel"},{category:"knowledge",verb:"document.feature",description:"Write or update documentation"},{category:"knowledge",verb:"summarize.thread",description:"Summarize a long conversation/thread"},{category:"knowledge",verb:"research.topic",description:"Investigate / gather info on a topic"},{category:"knowledge",verb:"wiki.absorb",description:"Roll raw entries into typed wiki articles"},{category:"social",verb:"brief.daily",description:"Compose a daily brief / digest"},{category:"social",verb:"draft.post",description:"Draft a marketing post / announcement"},{category:"social",verb:"report.weekly",description:"Compose a weekly status report"},{category:"system",verb:"classify.intent",description:"Internal: graph-classifier sub-call"}];import{readFileSync as I,writeFileSync as _,appendFileSync as oe,existsSync as S,mkdirSync as D,renameSync as ae,statSync as Q}from"fs";import{resolve as A,dirname as Z}from"path";import{createHash as ce}from"crypto";var F=class{baseDir;log;parsed=new Map;constructor(e={}){this.baseDir=e.baseDir??A(process.cwd(),".agentx/graph"),this.log=e.log??console.error.bind(console,"[graph]"),D(this.baseDir,{recursive:!0})}schemaPath(){return A(this.baseDir,"schema.json")}nodesPath(){return A(this.baseDir,"nodes.json")}indexPath(){return A(this.baseDir,"index.json")}classificationsPath(){return A(this.baseDir,"classifications.jsonl")}loadSchema(){let e=this.schemaPath();if(!S(e))return this.saveSchema(x),this.log("seeded starter schema at",e),x;let t=I(e,"utf-8"),r=T.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);if((r.data.version??1)<(x.version??2)){let n=e.replace(/\.json$/,`.v${r.data.version??1}.bak.json`);try{_(n,t),this.log(`migrated graph schema v${r.data.version??1} -> v${x.version}; old saved at ${n}`)}catch(i){this.log(`could not back up old schema: ${i?.message??i}`)}this.saveSchema(x);let o=this.nodesPath();if(S(o)){let i=o.replace(/\.json$/,".v1.bak.json");try{_(i,I(o,"utf-8")),this.log(`archived v1 nodes.json -> ${i}`)}catch(s){this.log(`could not back up old nodes: ${s?.message??s}`)}try{let s=K();this.saveNodes(s)}catch(s){this.log(`could not seed v2 nodes: ${s?.message??s}`)}}return x}return r.data}saveSchema(e){let t=T.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid schema: ${t.error.issues.map(r=>r.message).join("; ")}`);X(this.schemaPath(),t.data)}loadNodes(){let e=this.nodesPath();if(!S(e)){let t=K();return this.saveNodes(t),t}return structuredClone(this.readParsed(e,t=>{let r=j.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);return r.data}))}saveNodes(e){let t=j.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid nodes file: ${t.error.issues.map(r=>r.message).join("; ")}`);this.writeParsed(this.nodesPath(),t.data)}readParsed(e,t){let{mtimeMs:r,size:n}=Q(e),o=this.parsed.get(e);if(o&&o.mtimeMs===r&&o.size===n)return o.data;let i=t(I(e,"utf-8"));return this.parsed.set(e,{mtimeMs:r,size:n,data:i}),i}writeParsed(e,t){this.parsed.delete(e),X(e,t);let{mtimeMs:r,size:n}=Q(e);this.parsed.set(e,{mtimeMs:r,size:n,data:t})}validateNode(e,t,r){let n=t.levels.find(s=>s.id===e.level);if(!n)throw new Error(`Unknown level: "${e.level}"`);let o=n.axes.filter(s=>!s.optional).filter(s=>!(s.name in e.axes));if(o.length)throw new Error(`Node ${e.id} (${e.level}) missing required axes: ${o.map(s=>s.name).join(", ")}`);for(let s of n.axes){let c=e.axes[s.name];if(c!=null){if(s.type==="enum"&&s.values&&!s.values.includes(c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" not in enum ${JSON.stringify(s.values)}`);if(s.type==="ref"){if(!r.some(l=>l.id===c))throw new Error(`Node ${e.id}: axis ${s.name}="${c}" refers to unknown node`);if(s.refLevel){let l=r.find(h=>h.id===c);if(l.level!==s.refLevel)throw new Error(`Node ${e.id}: axis ${s.name} expected level "${s.refLevel}" but got "${l.level}"`)}}}}let i=t.levels.findIndex(s=>s.id===e.level);if(i===0){if(e.parentId!==null)throw new Error(`Root-level node ${e.id} must have parentId: null`)}else{if(!e.parentId)throw new Error(`Non-root node ${e.id} must have a parentId`);let s=r.find(l=>l.id===e.parentId);if(!s)throw new Error(`Node ${e.id}: parent "${e.parentId}" not found`);let c=t.levels.findIndex(l=>l.id===s.level);if(c<0||c>=i)throw new Error(`Node ${e.id} at level "${e.level}" has parent at level "${s.level}" \u2014 parent must be at a higher level`)}}commitNodesAlongPath(e,t,r,n){let o=[],i=[],s=new Date().toISOString();for(let c=0;c<e.length;c++){let l=e[c],h=this.loadNodes().nodes;if(h.find(p=>p.id===l)){i.push(l);continue}let u=0;for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(w=>w.id===y)||o.find(w=>w.id===y);if(N){u=r.levels.findIndex(w=>w.id===N.level)+1;break}}let m=t[l]??{},d=null;for(let p=u;p<r.levels.length;p++){let y=r.levels[p];if(y.axes.filter(w=>!w.optional).filter(w=>!(w.name in m)).length===0){d=y.id,u=p+1;break}}if(!d&&u<r.levels.length&&(d=r.levels[u].id,u++),!d)throw new Error(`Can't place node ${l} \u2014 no schema level left (cursor=${u}, levels=${r.levels.length})`);let f=null,v=r.levels.findIndex(p=>p.id===d);for(let p=c-1;p>=0;p--){let y=e[p],N=h.find(k=>k.id===y)||o.find(k=>k.id===y);if(!N)continue;let w=r.levels.findIndex(k=>k.id===N.level);if(w>=0&&w<v){f=y;break}}let g={id:l,level:d,parentId:f,axes:m,createdAt:s,createdBy:n};this.addNode(g),o.push(g)}return{added:o,skipped:i}}addNode(e){let t=this.loadSchema(),r=this.loadNodes();if(r.nodes.some(n=>n.id===e.id))throw new Error(`Node id already exists: ${e.id}`);return this.validateNode(e,t,[...r.nodes,e]),r.nodes.push(e),this.saveNodes(r),e}appendClassification(e){let t=E.safeParse(e);if(!t.success){let o=t.error.issues.map(i=>`${i.path.join(".")}: ${i.message}`).join(" \xB7 ");return this.log(`skipped invalid classification \u2014 ${o} \xB7`,`path=${JSON.stringify(e.path)}`,`axes-keys=${JSON.stringify(Object.keys(e.proposedAxes||{}))}`),!1}let r=JSON.stringify(t.data)+`
|
|
2
|
+
`,n=this.classificationsPath();return S(n)||D(Z(n),{recursive:!0}),oe(n,r),!0}listPendingClassifications(e=200){return this.listByStatus("pending",e)}listAllClassifications(){return this.readAllCollapsed()}listByStatus(e,t=200){return this.readAllCollapsed().filter(n=>n.status===e).slice(0,t)}readAllCollapsed(){let e=this.classificationsPath();if(!S(e))return[];let r=I(e,"utf-8").split(`
|
|
3
|
+
`).filter(Boolean),n=new Map;for(let o of r)try{let i=E.safeParse(JSON.parse(o));if(!i.success)continue;n.set(i.data.msgHash,i.data)}catch{}return Array.from(n.values()).sort((o,i)=>o.ts<i.ts?1:-1)}updateClassificationStatus(e,t){let n=this.readAllCollapsed().find(i=>i.msgHash===e);if(!n)return null;let o={...n,status:t.status,ts:new Date().toISOString()};if(t.reviewer||t.reviewReason){let i=` \xB7 ${t.reviewer?`reviewer=${t.reviewer}`:""}${t.reviewReason?` \xB7 ${t.reviewReason}`:""}`;o.preview=((n.preview||"")+i).slice(0,500)}return this.appendClassification(o),o}readRecentClassifications(e=50){let t=this.classificationsPath();if(!S(t))return[];let n=I(t,"utf-8").split(`
|
|
4
|
+
`).filter(Boolean),o=[];for(let i=n.length-1;i>=0&&o.length<e;i--)try{let s=E.safeParse(JSON.parse(n[i]));s.success&&o.push(s.data)}catch{}return o}fingerprint(e){let r=[de(e.text),e.channel??""].join("");return ce("sha256").update(r).digest("hex").slice(0,32)}loadIndex(){return structuredClone(this.index())}saveIndex(e){let t=G.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid index: ${t.error.issues.map(r=>r.message).join("; ")}`);this.writeParsed(this.indexPath(),t.data)}index(){let e=this.indexPath();return S(e)?this.readParsed(e,t=>{let r=G.safeParse(JSON.parse(t));if(!r.success)throw new Error(`Invalid ${e}: ${r.error.issues.map(n=>n.message).join("; ")}`);return r.data}):{version:1,entries:{}}}getFingerprint(e){let t=this.index().entries[e];return t&&structuredClone(t)}setFingerprint(e,t){let r=Y.safeParse({...t,fingerprint:e,updatedAt:new Date().toISOString()});if(!r.success)throw new Error(`Refusing to save invalid index entry: ${r.error.issues.map(o=>o.message).join("; ")}`);let n=this.index();this.writeParsed(this.indexPath(),{...n,entries:{...n.entries,[e]:r.data}})}updateNodeAxes(e,t){let r=this.loadSchema(),n=this.loadNodes(),o=n.nodes.find(s=>s.id===e);if(!o)throw new Error(`Node not found: ${e}`);let i={...o,axes:t};return this.validateNode(i,r,n.nodes.map(s=>s.id===e?i:s)),n.nodes=n.nodes.map(s=>s.id===e?i:s),this.saveNodes(n),i}deleteNode(e){let t=this.loadNodes(),r=t.nodes.find(o=>o.parentId===e);if(r)throw new Error(`Cannot delete "${e}" \u2014 child node "${r.id}" still points to it`);let n=t.nodes.find(o=>Object.values(o.axes).some(i=>i===e));if(n)throw new Error(`Cannot delete "${e}" \u2014 node "${n.id}" references it via an axis`);t.nodes=t.nodes.filter(o=>o.id!==e),this.saveNodes(t)}approveClassification(e){if(this.getFingerprint(e.msgHash))return;let t=this.loadSchema(),r=new Set(this.loadNodes().nodes.map(n=>n.id));for(let n=0;n<e.path.length;n++){let o=e.path[n];if(r.has(o))continue;let i=t.levels[n]?.id;if(!i)break;let s=e.proposedAxes?.[o]??{},c=n===0?null:e.path[n-1];this.addNode({id:o,level:i,parentId:c,axes:s,createdAt:new Date().toISOString(),createdBy:e.agentId}),r.add(o)}this.setFingerprint(e.msgHash,{path:e.path,leaf:e.leaf}),this.appendClassification({...e,status:"approved",ts:new Date().toISOString()})}rejectClassification(e){this.appendClassification({...e,status:"rejected",ts:new Date().toISOString()})}};function X(a,e){D(Z(a),{recursive:!0});let t=`${a}.tmp-${process.pid}`;_(t,JSON.stringify(e,null,2)),ae(t,a)}function K(){let a=new Date().toISOString(),e=[],t=new Set;for(let r of M)t.has(r.category)||(e.push({id:r.category,level:"category",parentId:null,axes:{kind:r.category,description:$[r.category]},createdAt:a,createdBy:"starter"}),t.add(r.category));for(let r of M)e.push({id:r.verb,level:"verb",parentId:r.category,axes:{name:r.verb,...r.description?{description:r.description}:{}},createdAt:a,createdBy:"starter"});return{version:1,nodes:e}}function de(a){return a.toLowerCase().replace(/https?:\/\/\S+/g," url ").replace(/[@#][\w.\/-]+/g," ref ").replace(/\b[0-9a-f]{7,}\b/g," id ").replace(/\d+/g," num ").replace(/[^\p{L}\p{N}\s]+/gu," ").replace(/\s+/g," ").trim().slice(0,400)}se();re();U();import{createHash as ye}from"crypto";B();U();var P="intent-path",le=.5,pe=.3,ue=.45;function ge(a){return{message:ve(a.message,2e3),channel:a.channel??null,sender:a.sender??null,agent:a.agent??null}}function ee(a,e){let t={};for(let r of a)t[r.id]=me(r);return C(t,e)}function he(a){let e=a.map(t=>{let r=$[t.id];return t.axes?.description?.trim()||!r?t:{...t,axes:{...t.axes,description:r}}});return{category:ee(e,"Which category of work does this message ask for or report on? Pick the one a reviewer would file it under.")}}function fe(a){return{verb:ee(a,"Within that category, which specific action does the message ask for or report on? Prefer the most specific verb that fits; a casual or ambiguous message belongs to the most general one.")}}async function q(a,e,t={}){if(R(P)==="off")return null;let r=e.filter(m=>m.parentId===null);if(r.length<2)return null;let n=ge(a),o={agent:a.agent??"unknown",channel:a.channel??"unknown"},i=a.agent?[{kind:"agent",id:a.agent}]:void 0,s=await O(P,n,he(r),{incumbent:t.incumbent?.[0]?{category:t.incumbent[0]}:void 0,features:{...o,stage:"category"},links:i});if(!s)return null;let c=s.answers.category,l=c.pMax,h=c.pMax>=le,b=[c.choice],u=e.filter(m=>m.parentId===c.choice);if(u.length>=2){let m=t.incumbent?.[0]===c.choice,d=await O(P,{...n,category:c.choice},fe(u),{incumbent:m&&t.incumbent?.[1]?{verb:t.incumbent[1]}:void 0,features:{...o,stage:"verb",category:c.choice},links:i});if(!d)return null;let f=d.answers.verb;h=h&&f.pMax>=pe,(f.pMax>=ue||!h)&&(l=Math.min(l,f.pMax),b.push(f.choice))}else u.length===1&&b.push(u[0].id);return{path:b,confidence:l,confident:h,mode:s.mode}}function me(a){let e=a.axes?.description?.trim()||void 0,t=a.axes?.name?.trim(),r=t&&t!==a.id?t:void 0;return e&&r?`${r}: ${e}`:e??r??null}function ve(a,e){return a.length>e?`${a.slice(0,e)}\u2026`:a}var L=class{store;daemonUrl;token;draftAgent;autoApprove;autoApproveStructure;classifierModel;classifierProviderName;providerInstance;log;constructor(e){this.store=e.store,this.daemonUrl=e.daemonUrl.replace(/\/+$/,""),this.token=e.token,this.draftAgent=e.draftAgent,this.autoApprove=e.autoApproveConfidence??1,this.autoApproveStructure=e.autoApproveStructure??"extend-leaves",this.classifierModel=e.classifierModel??"claude-haiku-4-5-20251001",this.classifierProviderName=e.classifierProvider??"claude-code",this.log=e.log??console.error.bind(console,"[classifier]")}getProvider(){return this.providerInstance||(this.providerInstance=z(this.classifierProviderName)),this.providerInstance}async classify(e){let t=this.store.fingerprint({text:e.text,channel:e.channel,sender:e.sender}),r=this.store.getFingerprint(t);if(r)return this.store.appendClassification({ts:new Date().toISOString(),msgHash:t,agentId:e.agentId,channel:e.channel,sender:e.sender,chatId:e.chatId,path:r.path,proposedAxes:{},leaf:r.leaf,source:"cache",status:"approved",preview:e.text.slice(0,200)}),{path:r.path,pathId:H(r.path),pathLabel:J(r.path,this.store.loadNodes().nodes),axes:{},leaf:r.leaf,source:"cache",status:"approved"};let n=this.store.loadSchema(),o=this.store.loadNodes().nodes,i=await this.propose(e,n,o);if(!i)return null;let{path:s,proposedAxes:c,confidence:l,leaf:h}=i;if(s.length===0)return null;let b={ts:new Date().toISOString(),msgHash:t,agentId:e.agentId,channel:e.channel,sender:e.sender,chatId:e.chatId,path:s,proposedAxes:c,leaf:h,source:i.source,status:"pending",confidence:l,preview:e.text.slice(0,200)},u=new Set(o.map(g=>g.id)),m=s.map((g,p)=>u.has(g)?-1:p).filter(g=>g>=0),d=!1;if(this.autoApproveStructure==="any")d=!0;else if(this.autoApproveStructure==="extend-leaves"){let g=m.length===0,p=m.length===1&&m[0]===s.length-1;d=g||p}let f=l!==void 0&&l>=this.autoApprove,v="pending";if(d||f)try{this.commitNewNodes(s,c,n,o,e.sender),this.store.setFingerprint(t,{path:s,leaf:h}),v="approved"}catch(g){this.log("auto-approve failed \u2014 staying pending \xB7",`path=${JSON.stringify(s)}`,`reason=${(g?.message||g).toString().slice(0,200)}`)}return b.status=v,this.store.appendClassification(b),{path:s,pathId:H(s),pathLabel:J(s,this.store.loadNodes().nodes),axes:c,leaf:h,source:i.source,status:v,confidence:l}}async propose(e,t,r){let n=R(P),o={message:e.text,channel:e.channel,sender:e.sender,agent:e.agentId};if(n==="active"){let s=await q(o,r).catch(c=>(this.log("intent-path seat failed:",c?.message||c),null));if(s?.confident)return{path:s.path,proposedAxes:{},leaf:{},confidence:s.confidence,source:"seat"}}let i=await this.proposePath(e,t,r).catch(s=>(this.log("LLM proposal failed:",s?.message||s),null));return n==="shadow"&&i&&q(o,r,{incumbent:i.path}).catch(()=>{}),i?{...i,source:"llm"}:null}async proposePath(e,t,r){let n=r.map(d=>({id:d.id,level:d.level,parentId:d.parentId,axes:d.axes})),o=["You are the intent classifier for AgentX. You answer ONE question:",'"what kind of work does this message describe?" \u2014 verb-level only.',"","IMPORTANT \u2014 what NOT to encode in the path:","- Client / company / project / team names. Those are already on the event"," metadata (project, channel, agentId). Embedding them in the path causes"," duplicate nodes (one per client) for the same verb.","- The specific subject (issue number, file name, person name). Same reason.","- The agent's name or role. Pick the verb the *requester* intended.","","SCHEMA (fixed levels \u2014 pick one node per level):","```json",JSON.stringify(t,null,2),"```","","EXISTING NODES (prefer reusing these; only invent a new node when no","existing one fits):","```json",JSON.stringify(n,null,2),"```","","MESSAGE:","```",e.text.slice(0,2e3),"```",`channel: ${e.channel??"?"}`,`sender: ${e.sender??"?"}`,"","TASK:","1. Classify into 'category' (closed enum): code, ops, support, admin,"," knowledge, social, system. Pick the closest fit.","2. Pick or propose a 'verb' node. Verb ids are dot-namespaced lower-kebab,",' e.g. "review.merge-request", "deploy.staging", "investigate.error",',' "chat.greeting", "fix.bug". Reuse an existing verb when the message is'," the same kind of work as something already classified, even when the"," client / project / subject differs.","","Examples (right vs wrong):",'- "Please review MR #957 on globex/system" \u2192 ["code", "review.merge-request"]',' NOT ["business", "acme", "globex-v2", "review-mr-957-system"]','- "Deploy initech-v2 to staging please" \u2192 ["ops", "deploy.staging"]',' NOT ["business", "acme", "initech-v2", "deploy-initech-v2-to-staging"]','- "Hello Atlas" \u2192 ["support", "chat.greeting"]',"","NODE ID RULES (strict \u2014 invalid ids get dropped):","- lowercase-kebab + dots only: [a-z0-9][a-z0-9._-]*","- no spaces, no uppercase, no Arabic/other non-Latin",'- verb ids should use a "category.specific" or "verb.modifier" shape',"","Return ONE JSON object on one line, no prose, no fences:",' { "path": string[], "proposedAxes": { [nodeId]: { [axisName]: string } }, "leaf": { "input"?: string, "output"?: string }, "confidence": number }',"confidence in [0,1]. Prefer low confidence over guessing."].join(`
|
|
5
|
+
`),i=await this.callProvider(o),s=W(i);if(!s||!Array.isArray(s.path))return null;let c=/^[a-z0-9][a-z0-9._-]*$/,l=new Map,h=s.path.filter(d=>typeof d=="string"&&d.length>0).map(d=>{let f=d.trim(),v=te(f);return f!==v&&l.set(f,v),v}).filter(d=>c.test(d));if(h.length===0)return null;let b={};if(s.proposedAxes&&typeof s.proposedAxes=="object")for(let[d,f]of Object.entries(s.proposedAxes)){if(!f||typeof f!="object")continue;let v=l.get(d)??te(d);if(!v)continue;let g={};for(let[p,y]of Object.entries(f))typeof y=="string"&&(g[p]=y);!g.name&&d!==v&&(g.name=d.trim()),b[v]=g}let u={};s.leaf&&typeof s.leaf=="object"&&(typeof s.leaf.input=="string"&&(u.input=s.leaf.input),typeof s.leaf.output=="string"&&(u.output=s.leaf.output));let m=typeof s.confidence=="number"?Math.max(0,Math.min(1,s.confidence)):void 0;return{path:h,proposedAxes:b,confidence:m,leaf:u}}commitNewNodes(e,t,r,n,o){this.store.commitNodesAlongPath(e,t,r,o)}async callProvider(e){let t=this.getProvider(),r=new AbortController,n=setTimeout(()=>r.abort(),3e4);try{return(await t.generate([{role:"system",content:"You are a taxonomy classifier. Respond with a single JSON object on one line. No prose, no code fences."},{role:"user",content:e}],{model:this.classifierModel,maxTokens:800,abortSignal:r.signal,bare:!0})).content||""}finally{clearTimeout(n)}}};function te(a){let e=a.trim().toLowerCase().replace(/[^a-z0-9._-]+/g,"-").replace(/^[-_]+|[-_]+$/g,"").replace(/-{2,}/g,"-");return e?/^[a-z0-9]/.test(e)?e:`n-${e}`.replace(/[-_]+$/,""):""}function H(a){return ye("sha1").update(a.join("")).digest("hex").slice(0,16)}function J(a,e){return a.map(t=>{let r=e.find(o=>o.id===t);return r&&(r.axes?.name||r.axes?.what)||t}).join(" \u203A ")}function we(a,e){if(a.length===0||e.length===0)return 0;let t=0;for(let n=0;n<Math.min(a.length,e.length)&&a[n]===e[n];n++)t++;if(t===0)return 0;let r=Math.max(a.length,e.length);return t/r}export{xe as a,Se as b,Ne as c,ke as d,Ie as e,x as f,F as g,L as h,J as i,we as j};
|
|
6
|
+
//# sourceMappingURL=chunk-JVSTKXMI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/decisions/seats/wiki-rerank.ts","../src/graph/starter-schema.ts","../src/graph/store.ts","../src/graph/classifier.ts","../src/decisions/seats/intent-path.ts"],"sourcesContent":["import { choice, noul } from \"../questions\"\nimport type { AnswersFor, ChoiceAnswer, NoulAnswer, StateValue } from \"../types\"\n\n// Re-ranking a BM25 shortlist.\n//\n// Measured on the operator's own wiki (11,656 articles, 12 queries):\n// BM25 alone gets R@1 33.3%, R@3 41.7%. Two thirds of queries do not put\n// the right article first. BM25 is a bag-of-words match, so it does well\n// on \"gitlab token expired\" and badly on \"the bot keeps repeating itself\n// what is wrong\" — the synonym cases score 33% / 33%.\n//\n// Shape follows TypeSafe's semantic_find cookbook rather than their\n// rerank one. Rerank scores each query-candidate pair separately, which\n// is N calls; semantic_find scores 218 line ids against one query with a\n// single Choice, and the per-option probabilities ARE the ranking. One\n// call for the whole shortlist, and the candidates are compared against\n// each other rather than each judged alone — which is what ranking means.\n//\n// The companion Noul is the part that keeps this honest. A Choice must\n// return something: asked which of 30 articles answers a question the\n// wiki has no answer to, it will still name one, with a confident-looking\n// probability. `answerable` is the question that can say no, and code\n// reads it first.\n\nexport const WIKI_RERANK_SEAT = \"wiki-rerank\"\n\n// Measured on the 12-query eval, sweeping candidate count against\n// excerpt length under a fixed state budget:\n//\n// k=120 x 150 chars R@1 25.0% (worse than BM25's 33.3%)\n// k=60 x 300 chars R@1 41.7%\n// k=30 x 600 chars R@1 41.7%\n// k=30 x 900 chars R@1 50.0% <- default\n// k=20 x 1100 chars R@1 50.0%\n//\n// Candidates and excerpt trade against one another, and excerpt wins.\n// More candidates raises the ceiling — what the shortlist could contain —\n// while starving the re-ranker of the text it needs to rank with. At 150\n// characters a candidate is barely more than its title, and the result is\n// worse than no re-ranking at all. Raise k only alongside the state\n// budget, never instead of the excerpt.\nexport const DEFAULT_SHORTLIST = 30\nexport const DEFAULT_EXCERPT_CHARS = 900\n\nexport interface RerankCandidate {\n id: string\n title: string\n tags?: string[]\n excerpt: string\n}\n\nexport interface RerankInput {\n query: string\n candidates: RerankCandidate[]\n /** Per-candidate excerpt budget. See DEFAULT_EXCERPT_CHARS — this is\n * the lever that actually moves accuracy. */\n excerptChars?: number\n}\n\nexport function rerankState(input: RerankInput): StateValue {\n const budget = input.excerptChars ?? DEFAULT_EXCERPT_CHARS\n return {\n query: input.query,\n candidates: input.candidates.map((c) => ({\n id: c.id,\n title: c.title,\n tags: c.tags?.length ? c.tags.join(\", \") : null,\n excerpt: clip(c.excerpt, budget),\n })),\n }\n}\n\n/** Built per call, because the option set IS the candidate set. */\nexport function rerankQuestions(candidates: RerankCandidate[]) {\n const criteria: Record<string, string> = {}\n for (const c of candidates) criteria[c.id] = c.title\n return {\n best: choice(criteria, \"Which candidate answers the query?\"),\n answerable: noul(\"Do any of the candidates answer the query?\"),\n }\n}\n\nexport type RerankAnswers = AnswersFor<ReturnType<typeof rerankQuestions>>\n\nexport interface RerankResult {\n /** Candidate ids, best first, by the Choice's per-option probability. */\n ranked: string[]\n scores: Record<string, number>\n /** P(the shortlist contains an answer at all). */\n answerable: number\n /** The Choice's own confidence — flat means it could not separate them. */\n confidence: number\n}\n\n/**\n * Turn one answer into a ranking.\n *\n * Ties keep the incoming order, so a Choice that genuinely cannot\n * separate the candidates degrades to BM25's ranking rather than to an\n * arbitrary one.\n */\nexport function toRanking(\n answers: RerankAnswers,\n candidates: RerankCandidate[],\n): RerankResult {\n const best = answers.best as ChoiceAnswer\n const scores: Record<string, number> = {}\n for (const c of candidates) scores[c.id] = best.probabilities[c.id] ?? 0\n\n const order = new Map(candidates.map((c, i) => [c.id, i]))\n const ranked = [...candidates.map((c) => c.id)].sort((a, b) => {\n const diff = scores[b] - scores[a]\n return diff !== 0 ? diff : (order.get(a) ?? 0) - (order.get(b) ?? 0)\n })\n\n return {\n ranked,\n scores,\n answerable: (answers.answerable as NoulAnswer).noul,\n confidence: best.confidence,\n }\n}\n\nfunction clip(text: string, max: number): string {\n const flat = String(text ?? \"\").replace(/\\s+/g, \" \").trim()\n return flat.length > max ? flat.slice(0, max) : flat\n}\n","import type { GraphSchema } from \"./types\"\n\n/**\n * Ships with the install. When .agentx/graph/schema.json is missing,\n * the store seeds this so a fresh daemon has something to classify\n * against.\n *\n * v2 — Phase 1 of the classifier-retire plan: verb-level intent\n * taxonomy. The classifier now answers ONLY \"what kind of work is\n * this\" — it does not embed client/project/agent in the leaf because\n * those already live on the event metadata (project, subject,\n * agentId). One copy of \"review.merge-request\" instead of one per\n * client.\n *\n * Two levels:\n * category — broad work area (code/ops/support/admin/knowledge/social/system)\n * verb — specific intent (review.merge-request, deploy.staging, etc.)\n *\n * The category is closed-set; the verb is open-set so the operator\n * can add new ones via `agentx graph review` over time. Most paths\n * will reuse the seeded verbs below.\n *\n * Why so few levels: every level is information the classifier\n * actually has to decide. The previous v1 schema (scope > location >\n * org > unit > activity) re-encoded data already on the event —\n * forcing a 5-LLM-question prompt to recover what we already knew.\n */\nexport const STARTER_SCHEMA: GraphSchema = {\n version: 2,\n levels: [\n {\n id: \"category\",\n description: \"Top-level kind of work. Closed set — pick the closest fit.\",\n axes: [\n {\n name: \"kind\",\n type: \"enum\",\n values: [\n \"code\", // anything code-related: review, fix, implement, refactor\n \"ops\", // infra: deploy, monitor, rollback, scale, incident\n \"support\", // human-facing: answer question, triage, route\n \"admin\", // governance: config change, scheduling, token, role\n \"knowledge\", // wiki, docs, research, summarize\n \"social\", // marketing, comms, brief, content\n \"system\", // internal infrastructure (rare; mostly classifier itself)\n ],\n description: \"Which category this work belongs in.\",\n },\n ],\n },\n {\n id: \"verb\",\n description: \"Specific intent — verb-level only, NOT client/project-bound.\",\n axes: [\n {\n name: \"name\",\n type: \"free\",\n description:\n \"Lower-kebab dot-namespaced verb. Examples: review.merge-request, fix.bug, \" +\n \"investigate.error, deploy.staging, deploy.production, rollback.release, \" +\n \"spec.feature, triage.issue, document.feature, chat.greeting, \" +\n \"chat.support-request, schedule.add, config.change, ingest.contact, \" +\n \"transcribe.audio, brief.daily, summarize.thread, plan.work.\",\n },\n ],\n },\n ],\n leafInput: {\n name: \"input\",\n type: \"enum\",\n values: [\n \"telegram\",\n \"whatsapp\",\n \"slack\",\n \"discord\",\n \"gitlab-mr\",\n \"gitlab-issue\",\n \"github-issue\",\n \"github-pr\",\n \"cron\",\n \"webhook\",\n \"mesh\",\n \"http\",\n ],\n description: \"Where the message entered the system.\",\n },\n leafOutput: {\n name: \"output\",\n type: \"free\",\n description:\n \"One short sentence describing what the agent is expected to produce. \" +\n \"Generic — refers to the verb's outcome, not the specific client/project \" +\n \"(those are on the event metadata already).\",\n },\n}\n\n/** What each category covers, in the words a reviewer would use to file a\n * message. Category nodes were seeded with only their `kind`, so a model\n * picking between them saw bare ids; the boundaries below are the ones a\n * labelled sample of seat answers got wrong most (support read as social\n * or admin, code read as system). */\nexport const CATEGORY_DESCRIPTIONS: Record<StarterCategory, string> = {\n code: \"Work on source code: review a PR or MR, fix a bug, build a feature, refactor, write a spec, or answer a question about code. Includes changes to AgentX's own code.\",\n ops: \"Running systems: deploy, roll back, monitor, scale, restart a service, or handle a production incident.\",\n support: \"A person talking to an agent: a greeting, casual chat, thanks, a question to answer, a request for help, a follow-up or complaint about an earlier request, or routing a request to someone else. The default for conversation that is not itself one of the other kinds of work.\",\n admin: \"Changing how the agents are set up: configuration, schedules, tokens, roles, adding an agent or a channel. Not a question about the setup or a complaint that something did not run.\",\n knowledge: \"Writing or gathering knowledge to keep: documentation, wiki articles, research, summarising a thread.\",\n social: \"Content made for an audience other than the agent: posts, announcements, daily briefs, weekly reports. Not a conversation with the agent.\",\n system: \"Internal sub-calls made by the platform itself, such as the intent classifier. A message from a person is almost never this.\",\n}\n\n/** Pre-seeded verbs shipped with v2. The classifier picks from these\n * first; new verbs go through the review queue before they're\n * committed (so the taxonomy doesn't drift). Adding to this list is\n * cheap — the seed runs once when nodes.json is empty. */\nexport type StarterCategory = \"code\" | \"ops\" | \"support\" | \"admin\" | \"knowledge\" | \"social\" | \"system\"\n\nexport const STARTER_VERB_NODES: Array<{\n category: StarterCategory\n verb: string\n description?: string\n}> = [\n // code\n { category: \"code\", verb: \"review.merge-request\", description: \"Review a GitLab MR or GitHub PR\" },\n { category: \"code\", verb: \"review.code-change\", description: \"General code review (snippet, diff)\" },\n { category: \"code\", verb: \"fix.bug\", description: \"Fix a reported bug\" },\n { category: \"code\", verb: \"implement.feature\", description: \"Build a new feature\" },\n { category: \"code\", verb: \"investigate.error\", description: \"Investigate a failing test, exception, pipeline\" },\n { category: \"code\", verb: \"refactor.code\", description: \"Restructure existing code without behaviour change\" },\n { category: \"code\", verb: \"spec.feature\", description: \"Write or update a feature spec / RFC\" },\n { category: \"code\", verb: \"triage.issue\", description: \"Classify, label, or assign an issue\" },\n { category: \"code\", verb: \"answer.code-question\", description: \"Answer a code-related question\" },\n\n // ops\n { category: \"ops\", verb: \"deploy.staging\", description: \"Deploy to a staging/preview environment\" },\n { category: \"ops\", verb: \"deploy.production\", description: \"Deploy to production\" },\n { category: \"ops\", verb: \"rollback.release\", description: \"Roll back a release\" },\n { category: \"ops\", verb: \"investigate.incident\", description: \"Investigate a production incident\" },\n { category: \"ops\", verb: \"monitor.system\", description: \"Check system status / metrics\" },\n { category: \"ops\", verb: \"rotate.credential\", description: \"Rotate a token, key, certificate\" },\n { category: \"ops\", verb: \"audit.security\", description: \"Review for security issues\" },\n\n // support\n { category: \"support\", verb: \"chat.greeting\", description: \"A casual hello, no actionable request\" },\n { category: \"support\", verb: \"chat.support-request\", description: \"End-user / customer asking for help\" },\n { category: \"support\", verb: \"chat.casual\", description: \"Informal back-and-forth chat\" },\n { category: \"support\", verb: \"answer.question\", description: \"Answer an open-ended question\" },\n { category: \"support\", verb: \"route.request\", description: \"Forward a request to the right agent\" },\n\n // admin\n { category: \"admin\", verb: \"config.change\", description: \"Change agentx.json / environment / config\" },\n { category: \"admin\", verb: \"schedule.add\", description: \"Add a new cron / scheduled task\" },\n { category: \"admin\", verb: \"schedule.remove\", description: \"Remove or disable a schedule\" },\n { category: \"admin\", verb: \"token.create\", description: \"Mint a scoped API token\" },\n { category: \"admin\", verb: \"agent.add\", description: \"Configure a new agent\" },\n { category: \"admin\", verb: \"channel.add\", description: \"Wire up a new channel\" },\n\n // knowledge\n { category: \"knowledge\", verb: \"document.feature\", description: \"Write or update documentation\" },\n { category: \"knowledge\", verb: \"summarize.thread\", description: \"Summarize a long conversation/thread\" },\n { category: \"knowledge\", verb: \"research.topic\", description: \"Investigate / gather info on a topic\" },\n { category: \"knowledge\", verb: \"wiki.absorb\", description: \"Roll raw entries into typed wiki articles\" },\n\n // social\n { category: \"social\", verb: \"brief.daily\", description: \"Compose a daily brief / digest\" },\n { category: \"social\", verb: \"draft.post\", description: \"Draft a marketing post / announcement\" },\n { category: \"social\", verb: \"report.weekly\", description: \"Compose a weekly status report\" },\n\n // system (the classifier itself; rare to expose)\n { category: \"system\", verb: \"classify.intent\", description: \"Internal: graph-classifier sub-call\" },\n]\n\n","import {\n readFileSync,\n writeFileSync,\n appendFileSync,\n existsSync,\n mkdirSync,\n renameSync,\n statSync,\n} from \"fs\"\nimport { resolve, dirname } from \"path\"\nimport { createHash } from \"crypto\"\nimport {\n graphSchemaSchema,\n nodesFileSchema,\n indexFileSchema,\n fingerprintEntrySchema,\n classificationSchema,\n type GraphSchema,\n type GraphNode,\n type NodesFile,\n type IndexFile,\n type Classification,\n type FingerprintEntry,\n} from \"./types\"\nimport { CATEGORY_DESCRIPTIONS, STARTER_SCHEMA, STARTER_VERB_NODES } from \"./starter-schema\"\n\n// --- Intent Knowledge Graph filesystem store ---\n//\n// Mirrors the .agentx/wiki pattern: plain JSON on disk, one directory, no DB.\n// Not thread-safe across processes — writes go through safeWrite (tmp + rename).\n\nexport interface GraphStoreOptions {\n baseDir?: string\n log?: (...args: unknown[]) => void\n}\n\nexport class GraphStore {\n readonly baseDir: string\n private log: (...args: unknown[]) => void\n /** Parsed nodes/index files, keyed by path. The classifier reads both\n * several times per message and they grow without bound, so re-reading\n * and re-validating them each time blocked the daemon for seconds. */\n private parsed = new Map<string, { mtimeMs: number; size: number; data: unknown }>()\n\n constructor(opts: GraphStoreOptions = {}) {\n this.baseDir = opts.baseDir ?? resolve(process.cwd(), \".agentx/graph\")\n this.log = opts.log ?? console.error.bind(console, \"[graph]\")\n mkdirSync(this.baseDir, { recursive: true })\n }\n\n // --- Paths ---\n\n schemaPath(): string { return resolve(this.baseDir, \"schema.json\") }\n nodesPath(): string { return resolve(this.baseDir, \"nodes.json\") }\n indexPath(): string { return resolve(this.baseDir, \"index.json\") }\n classificationsPath(): string { return resolve(this.baseDir, \"classifications.jsonl\") }\n\n // --- Schema ---\n\n /** Load the graph schema, seeding STARTER_SCHEMA if the file is missing.\n * Phase 1 of the classifier-retire plan ships a v2 verb-level schema;\n * installs with the v1 hierarchical schema (scope > org > unit >\n * activity) get auto-migrated on next load: the v1 file is renamed\n * to schema.v1.bak.json, the v2 starter is written, and the\n * per-event nodes.json + classifications.jsonl are left alone for\n * historical reading. New events classify under v2 from then on. */\n loadSchema(): GraphSchema {\n const p = this.schemaPath()\n if (!existsSync(p)) {\n this.saveSchema(STARTER_SCHEMA)\n this.log(\"seeded starter schema at\", p)\n return STARTER_SCHEMA\n }\n const raw = readFileSync(p, \"utf-8\")\n const parsed = graphSchemaSchema.safeParse(JSON.parse(raw))\n if (!parsed.success) {\n throw new Error(`Invalid ${p}: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n if ((parsed.data.version ?? 1) < (STARTER_SCHEMA.version ?? 2)) {\n const backup = p.replace(/\\.json$/, `.v${parsed.data.version ?? 1}.bak.json`)\n try {\n writeFileSync(backup, raw)\n this.log(`migrated graph schema v${parsed.data.version ?? 1} -> v${STARTER_SCHEMA.version}; old saved at ${backup}`)\n } catch (e: any) {\n this.log(`could not back up old schema: ${e?.message ?? e}`)\n }\n this.saveSchema(STARTER_SCHEMA)\n // Same treatment for nodes.json — the v1 nodes were parented under\n // levels that don't exist in v2 and would fail validation. Archive\n // them and re-seed.\n const np = this.nodesPath()\n if (existsSync(np)) {\n const nbackup = np.replace(/\\.json$/, `.v1.bak.json`)\n try {\n writeFileSync(nbackup, readFileSync(np, \"utf-8\"))\n this.log(`archived v1 nodes.json -> ${nbackup}`)\n } catch (e: any) {\n this.log(`could not back up old nodes: ${e?.message ?? e}`)\n }\n try {\n const seeded = seedNodesFile()\n this.saveNodes(seeded)\n } catch (e: any) {\n this.log(`could not seed v2 nodes: ${e?.message ?? e}`)\n }\n }\n return STARTER_SCHEMA\n }\n return parsed.data\n }\n\n saveSchema(schema: GraphSchema): void {\n const parsed = graphSchemaSchema.safeParse(schema)\n if (!parsed.success) {\n throw new Error(`Refusing to save invalid schema: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n safeWriteJson(this.schemaPath(), parsed.data)\n }\n\n // --- Nodes ---\n\n loadNodes(): NodesFile {\n const p = this.nodesPath()\n if (!existsSync(p)) {\n // Seed the verb-level taxonomy on first load. Lets the classifier\n // pick from a stable set of common verbs out of the box, instead of\n // inventing slightly-different variants for every event.\n const seeded = seedNodesFile()\n this.saveNodes(seeded)\n return seeded\n }\n // A copy, so callers can edit it before saveNodes without touching the cache.\n return structuredClone(this.readParsed(p, (raw) => {\n const parsed = nodesFileSchema.safeParse(JSON.parse(raw))\n if (!parsed.success) {\n throw new Error(`Invalid ${p}: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n return parsed.data\n }))\n }\n\n saveNodes(file: NodesFile): void {\n const parsed = nodesFileSchema.safeParse(file)\n if (!parsed.success) {\n throw new Error(`Refusing to save invalid nodes file: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n this.writeParsed(this.nodesPath(), parsed.data)\n }\n\n /** The parsed file, re-read only when its mtime or size changes, so a\n * write from another process (CLI, dashboard) is still picked up. */\n private readParsed<T>(p: string, parse: (raw: string) => T): T {\n const { mtimeMs, size } = statSync(p)\n const hit = this.parsed.get(p)\n if (hit && hit.mtimeMs === mtimeMs && hit.size === size) return hit.data as T\n const data = parse(readFileSync(p, \"utf-8\"))\n this.parsed.set(p, { mtimeMs, size, data })\n return data\n }\n\n /** Write the file and keep the parsed copy, so the writer never re-reads\n * what it just wrote. `data` must not be mutated afterwards. */\n private writeParsed(p: string, data: unknown): void {\n this.parsed.delete(p)\n safeWriteJson(p, data)\n const { mtimeMs, size } = statSync(p)\n this.parsed.set(p, { mtimeMs, size, data })\n }\n\n /** Validate a node against the schema AND uniqueness within the nodes file. */\n validateNode(node: GraphNode, schema: GraphSchema, all: GraphNode[]): void {\n const level = schema.levels.find((l) => l.id === node.level)\n if (!level) throw new Error(`Unknown level: \"${node.level}\"`)\n\n const missing = level.axes\n .filter((a) => !a.optional)\n .filter((a) => !(a.name in node.axes))\n if (missing.length) {\n throw new Error(\n `Node ${node.id} (${node.level}) missing required axes: ${missing.map((a) => a.name).join(\", \")}`,\n )\n }\n\n for (const a of level.axes) {\n const v = node.axes[a.name]\n if (v == null) continue\n if (a.type === \"enum\" && a.values && !a.values.includes(v)) {\n throw new Error(`Node ${node.id}: axis ${a.name}=\"${v}\" not in enum ${JSON.stringify(a.values)}`)\n }\n if (a.type === \"ref\") {\n if (!all.some((n) => n.id === v)) {\n throw new Error(`Node ${node.id}: axis ${a.name}=\"${v}\" refers to unknown node`)\n }\n if (a.refLevel) {\n const target = all.find((n) => n.id === v)!\n if (target.level !== a.refLevel) {\n throw new Error(\n `Node ${node.id}: axis ${a.name} expected level \"${a.refLevel}\" but got \"${target.level}\"`,\n )\n }\n }\n }\n }\n\n const levelIdx = schema.levels.findIndex((l) => l.id === node.level)\n if (levelIdx === 0) {\n if (node.parentId !== null) {\n throw new Error(`Root-level node ${node.id} must have parentId: null`)\n }\n } else {\n if (!node.parentId) {\n throw new Error(`Non-root node ${node.id} must have a parentId`)\n }\n const parent = all.find((n) => n.id === node.parentId)\n if (!parent) throw new Error(`Node ${node.id}: parent \"${node.parentId}\" not found`)\n const parentLevelIdx = schema.levels.findIndex((l) => l.id === parent.level)\n // Parent may be at any higher level — intermediate levels can be skipped\n // for orgs that don't use them (e.g. remote-first: no location node).\n if (parentLevelIdx < 0 || parentLevelIdx >= levelIdx) {\n throw new Error(\n `Node ${node.id} at level \"${node.level}\" has parent at level \"${parent.level}\" — parent must be at a higher level`,\n )\n }\n }\n }\n\n /**\n * Commit a full classification path: for each path element that doesn't\n * yet exist, infer its schema level by matching the proposed axes against\n * the level's required axes (first match ≥ cursor wins — this handles\n * paths that skip levels, like business → acme where location is\n * absent). Refreshes the node list between adds so callers in a loop\n * don't hit \"Node id already exists\" on the second iteration.\n */\n commitNodesAlongPath(\n path: string[],\n proposedAxes: Record<string, Record<string, string>>,\n schema: GraphSchema,\n createdBy?: string,\n ): { added: GraphNode[]; skipped: string[] } {\n const added: GraphNode[] = []\n const skipped: string[] = []\n const now = new Date().toISOString()\n\n for (let i = 0; i < path.length; i++) {\n const id = path[i]\n // Load fresh each iteration — cheap O(N) file read, avoids stale snapshots.\n const current = this.loadNodes().nodes\n const existing = current.find((n) => n.id === id)\n if (existing) {\n skipped.push(id)\n continue\n }\n\n // Cursor: how deep the schema-levels we've already filled up to.\n let cursor = 0\n // Scan backwards through path[0..i-1] to find the deepest already-committed\n // or just-added node and resume after its level.\n for (let j = i - 1; j >= 0; j--) {\n const priorId = path[j]\n const priorNode = current.find((n) => n.id === priorId) || added.find((n) => n.id === priorId)\n if (priorNode) {\n cursor = schema.levels.findIndex((l) => l.id === priorNode.level) + 1\n break\n }\n }\n\n const axes = proposedAxes[id] ?? {}\n // Find the first level at or after the cursor whose required axes are\n // satisfied by proposedAxes[id]. \"Required\" here = listed in level.axes;\n // we accept a partial match (axes present in proposal, even if some\n // schema axes are missing — validateNode will catch truly broken ones).\n let chosenLevel: string | null = null\n for (let j = cursor; j < schema.levels.length; j++) {\n const level = schema.levels[j]\n const missing = level.axes\n .filter((a) => !a.optional)\n .filter((a) => !(a.name in axes))\n if (missing.length === 0) {\n chosenLevel = level.id\n cursor = j + 1\n break\n }\n }\n // Fallback: if no level's axes fully match, try the NEXT level after\n // the cursor anyway. Better to commit at the likely level and let\n // validateNode reject on write than to silently drop the whole path.\n if (!chosenLevel && cursor < schema.levels.length) {\n chosenLevel = schema.levels[cursor].id\n cursor++\n }\n if (!chosenLevel) {\n throw new Error(`Can't place node ${id} — no schema level left (cursor=${cursor}, levels=${schema.levels.length})`)\n }\n\n // Parent is the DEEPEST already-placed node at a level < chosenLevel.\n let parentId: string | null = null\n const chosenIdx = schema.levels.findIndex((l) => l.id === chosenLevel)\n for (let j = i - 1; j >= 0; j--) {\n const priorId = path[j]\n const prior = current.find((n) => n.id === priorId) || added.find((n) => n.id === priorId)\n if (!prior) continue\n const priorIdx = schema.levels.findIndex((l) => l.id === prior.level)\n if (priorIdx >= 0 && priorIdx < chosenIdx) { parentId = priorId; break }\n }\n\n const node: GraphNode = {\n id, level: chosenLevel, parentId, axes,\n createdAt: now,\n createdBy,\n }\n this.addNode(node) // throws if schema validation fails\n added.push(node)\n }\n\n return { added, skipped }\n }\n\n /** Append a node. Caller is responsible for id uniqueness; we check and throw. */\n addNode(node: GraphNode): GraphNode {\n const schema = this.loadSchema()\n const file = this.loadNodes()\n if (file.nodes.some((n) => n.id === node.id)) {\n throw new Error(`Node id already exists: ${node.id}`)\n }\n this.validateNode(node, schema, [...file.nodes, node])\n file.nodes.push(node)\n this.saveNodes(file)\n return node\n }\n\n // --- Classifications (append-only log) ---\n\n appendClassification(c: Classification): boolean {\n const parsed = classificationSchema.safeParse(c)\n if (!parsed.success) {\n // Before the fix this threw and took down the whole task. Now we drop\n // the bad classification and log enough detail (the offending path +\n // axes keys) for the operator to diagnose the prompt. Returning false\n // lets callers decide whether to retry with a cleaner proposal.\n const issues = parsed.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`).join(\" · \")\n this.log(\n `skipped invalid classification — ${issues} ·`,\n `path=${JSON.stringify(c.path)}`,\n `axes-keys=${JSON.stringify(Object.keys(c.proposedAxes || {}))}`,\n )\n return false\n }\n const line = JSON.stringify(parsed.data) + \"\\n\"\n const p = this.classificationsPath()\n if (!existsSync(p)) {\n mkdirSync(dirname(p), { recursive: true })\n }\n appendFileSync(p, line)\n return true\n }\n\n /**\n * All pending classifications, newest first. Collapses the append-only\n * ledger by msgHash — the most recent entry per message wins (so if an\n * entry was updated via updateClassificationStatus, we see the current\n * state, not the original).\n */\n listPendingClassifications(limit = 200): Classification[] {\n return this.listByStatus(\"pending\", limit)\n }\n\n /** Every classification, newest-first, collapsed one row per message.\n * The human-label queue needs the whole population rather than one\n * status, because the rows worth checking are the auto-approved ones. */\n listAllClassifications(): Classification[] {\n return this.readAllCollapsed()\n }\n\n /** Same contract as listPendingClassifications but filtered to any status. */\n listByStatus(status: Classification[\"status\"], limit = 200): Classification[] {\n const all = this.readAllCollapsed()\n return all.filter((c) => c.status === status).slice(0, limit)\n }\n\n /**\n * Read every classification, collapse by msgHash keeping the newest entry\n * per message. Used by listByStatus + the review loop to see current state.\n * Returns newest-first order.\n */\n private readAllCollapsed(): Classification[] {\n const p = this.classificationsPath()\n if (!existsSync(p)) return []\n const raw = readFileSync(p, \"utf-8\")\n const lines = raw.split(\"\\n\").filter(Boolean)\n const byHash = new Map<string, Classification>()\n for (const line of lines) {\n try {\n const parsed = classificationSchema.safeParse(JSON.parse(line))\n if (!parsed.success) continue\n // Later entries overwrite earlier ones for the same msgHash.\n byHash.set(parsed.data.msgHash, parsed.data)\n } catch {\n // skip malformed\n }\n }\n // Newest-first — sort by ts desc.\n return Array.from(byHash.values()).sort((a, b) => (a.ts < b.ts ? 1 : -1))\n }\n\n /**\n * Append a status-update entry for an existing classification. The ledger\n * stays append-only; readers collapse by msgHash. Returns the updated\n * classification, or null if no prior entry for msgHash exists.\n */\n updateClassificationStatus(\n msgHash: string,\n update: {\n status: Classification[\"status\"]\n reviewer?: string\n reviewReason?: string\n },\n ): Classification | null {\n const all = this.readAllCollapsed()\n const prior = all.find((c) => c.msgHash === msgHash)\n if (!prior) return null\n const next: Classification = {\n ...prior,\n status: update.status,\n ts: new Date().toISOString(),\n }\n // Store reviewer + reason in the preview field so readers can see who\n // flipped what without needing a separate review-log file. Non-breaking:\n // preview is already optional text meant for UI display.\n if (update.reviewer || update.reviewReason) {\n const suffix = ` · ${update.reviewer ? `reviewer=${update.reviewer}` : \"\"}${update.reviewReason ? ` · ${update.reviewReason}` : \"\"}`\n next.preview = ((prior.preview || \"\") + suffix).slice(0, 500)\n }\n this.appendClassification(next)\n return next\n }\n\n /** Read the last N classifications, newest first. Cheap enough for the admin UI. */\n readRecentClassifications(limit = 50): Classification[] {\n const p = this.classificationsPath()\n if (!existsSync(p)) return []\n const raw = readFileSync(p, \"utf-8\")\n const lines = raw.split(\"\\n\").filter(Boolean)\n const out: Classification[] = []\n for (let i = lines.length - 1; i >= 0 && out.length < limit; i--) {\n try {\n const parsed = classificationSchema.safeParse(JSON.parse(lines[i]))\n if (parsed.success) out.push(parsed.data)\n } catch {\n // skip malformed line — append-only log; don't fail reads on one bad line\n }\n }\n return out\n }\n\n // --- Fingerprint index ---\n\n /**\n * Hash that represents \"this kind of message\" for snap-to-path caching.\n *\n * Keyed on the normalized wording plus the channel. The sender used to be\n * part of the key too, and the exact text was: measured over the last\n * 2,000 classifications, 89% missed, so nearly every task paid an LLM\n * call for a taxonomy label. Identifiers, numbers, links and mentions\n * are what differ between two messages that mean the same thing, so\n * they are folded away (see `normalizeForFingerprint`).\n */\n fingerprint(msg: {\n text: string\n channel?: string\n sender?: string\n }): string {\n const norm = normalizeForFingerprint(msg.text)\n const payload = [norm, msg.channel ?? \"\"].join(\"\\u0001\")\n return createHash(\"sha256\").update(payload).digest(\"hex\").slice(0, 32)\n }\n\n loadIndex(): IndexFile {\n return structuredClone(this.index())\n }\n\n saveIndex(file: IndexFile): void {\n const parsed = indexFileSchema.safeParse(file)\n if (!parsed.success) {\n throw new Error(`Refusing to save invalid index: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n this.writeParsed(this.indexPath(), parsed.data)\n }\n\n /** The cached index. Read-only: copy before handing it to a caller. */\n private index(): IndexFile {\n const p = this.indexPath()\n if (!existsSync(p)) return { version: 1, entries: {} }\n return this.readParsed(p, (raw) => {\n const parsed = indexFileSchema.safeParse(JSON.parse(raw))\n if (!parsed.success) {\n throw new Error(`Invalid ${p}: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n return parsed.data\n })\n }\n\n getFingerprint(fp: string): FingerprintEntry | undefined {\n const entry = this.index().entries[fp]\n return entry && structuredClone(entry)\n }\n\n setFingerprint(fp: string, entry: Omit<FingerprintEntry, \"fingerprint\" | \"updatedAt\">): void {\n // Validate only the new entry: the rest of the index was validated when\n // it was read, and re-checking thousands of entries per message is the\n // cost this cache exists to avoid.\n const parsed = fingerprintEntrySchema.safeParse({\n ...entry,\n fingerprint: fp,\n updatedAt: new Date().toISOString(),\n })\n if (!parsed.success) {\n throw new Error(`Refusing to save invalid index entry: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n const current = this.index()\n this.writeParsed(this.indexPath(), { ...current, entries: { ...current.entries, [fp]: parsed.data } })\n }\n\n /** Update the axes on an existing node. Schema-validates the result. */\n updateNodeAxes(id: string, axes: Record<string, string>): GraphNode {\n const schema = this.loadSchema()\n const file = this.loadNodes()\n const node = file.nodes.find((n) => n.id === id)\n if (!node) throw new Error(`Node not found: ${id}`)\n const updated: GraphNode = { ...node, axes }\n this.validateNode(updated, schema, file.nodes.map((n) => (n.id === id ? updated : n)))\n file.nodes = file.nodes.map((n) => (n.id === id ? updated : n))\n this.saveNodes(file)\n return updated\n }\n\n /** Remove a node. Refuses to delete if any other node still references it\n * (child via parentId, or axis ref). */\n deleteNode(id: string): void {\n const file = this.loadNodes()\n const child = file.nodes.find((n) => n.parentId === id)\n if (child) {\n throw new Error(`Cannot delete \"${id}\" — child node \"${child.id}\" still points to it`)\n }\n const refHolder = file.nodes.find((n) =>\n Object.values(n.axes).some((v) => v === id),\n )\n if (refHolder) {\n throw new Error(`Cannot delete \"${id}\" — node \"${refHolder.id}\" references it via an axis`)\n }\n file.nodes = file.nodes.filter((n) => n.id !== id)\n this.saveNodes(file)\n }\n\n /** Approve a pending classification. Commits any new nodes along its path,\n * stamps the fingerprint index so future similar messages hit the cache,\n * and appends an audit entry. Idempotent — no-op if already cached. */\n approveClassification(c: Classification): void {\n if (this.getFingerprint(c.msgHash)) return // already approved\n // Commit any path nodes the LLM proposed but hadn't been confirmed yet.\n const schema = this.loadSchema()\n const known = new Set(this.loadNodes().nodes.map((n) => n.id))\n for (let i = 0; i < c.path.length; i++) {\n const id = c.path[i]\n if (known.has(id)) continue\n const level = schema.levels[i]?.id\n if (!level) break\n const axes = c.proposedAxes?.[id] ?? {}\n const parentId = i === 0 ? null : c.path[i - 1]\n this.addNode({\n id,\n level,\n parentId,\n axes,\n createdAt: new Date().toISOString(),\n createdBy: c.agentId,\n })\n known.add(id)\n }\n this.setFingerprint(c.msgHash, { path: c.path, leaf: c.leaf })\n this.appendClassification({ ...c, status: \"approved\", ts: new Date().toISOString() })\n }\n\n /** Record rejection — no nodes committed, no fingerprint set. */\n rejectClassification(c: Classification): void {\n this.appendClassification({ ...c, status: \"rejected\", ts: new Date().toISOString() })\n }\n}\n\n/** Atomic write: tmp file + rename. Avoids half-written JSON on crashes. */\nfunction safeWriteJson(path: string, data: unknown): void {\n mkdirSync(dirname(path), { recursive: true })\n const tmp = `${path}.tmp-${process.pid}`\n writeFileSync(tmp, JSON.stringify(data, null, 2))\n renameSync(tmp, path)\n}\n\n/** Build the seed nodes.json — one category node per enum value, plus\n * one verb node per STARTER_VERB_NODES entry parented under its\n * category. Idempotent shape: same input always produces the same\n * file, so re-seeding is safe. */\nfunction seedNodesFile(): NodesFile {\n const now = new Date().toISOString()\n const nodes: NodesFile[\"nodes\"] = []\n const seenCats = new Set<string>()\n for (const v of STARTER_VERB_NODES) {\n if (!seenCats.has(v.category)) {\n nodes.push({\n id: v.category,\n level: \"category\",\n parentId: null,\n axes: { kind: v.category, description: CATEGORY_DESCRIPTIONS[v.category] },\n createdAt: now,\n createdBy: \"starter\",\n })\n seenCats.add(v.category)\n }\n }\n for (const v of STARTER_VERB_NODES) {\n nodes.push({\n id: v.verb,\n level: \"verb\",\n parentId: v.category,\n axes: { name: v.verb, ...(v.description ? { description: v.description } : {}) },\n createdAt: now,\n createdBy: \"starter\",\n })\n }\n return { version: 1, nodes }\n}\n\n/**\n * Fold a message down to the words that carry its intent. Links, mentions,\n * issue or MR references, hashes and numbers vary between two requests of\n * the same kind, so they collapse to placeholders; punctuation and case go.\n * Long webhook bodies are cut so a note on the same thread hashes alike.\n */\nexport function normalizeForFingerprint(text: string): string {\n return text\n .toLowerCase()\n .replace(/https?:\\/\\/\\S+/g, \" url \")\n .replace(/[@#][\\w.\\/-]+/g, \" ref \")\n .replace(/\\b[0-9a-f]{7,}\\b/g, \" id \")\n .replace(/\\d+/g, \" num \")\n .replace(/[^\\p{L}\\p{N}\\s]+/gu, \" \")\n .replace(/\\s+/g, \" \")\n .trim()\n .slice(0, 400)\n}\n","import { createHash } from \"crypto\"\nimport { extractJson } from \"@/utils/extract-json\"\nimport { GraphStore } from \"./store\"\nimport {\n type GraphSchema,\n type GraphNode,\n type Classification,\n} from \"./types\"\nimport { createProvider, type ProviderName, type AgentProvider } from \"@/agent/providers\"\nimport { getSeatMode } from \"@/decisions/seat\"\nimport { INTENT_PATH_SEAT, proposePathViaSeat } from \"@/decisions/seats/intent-path\"\n\n/** A path for a cache miss, with where it came from. */\ninterface Proposal {\n path: string[]\n proposedAxes: Record<string, Record<string, string>>\n confidence?: number\n leaf: { input?: string; output?: string }\n source: \"llm\" | \"seat\"\n}\n\n// --- Intent Knowledge Graph classifier ---\n//\n// For every incoming message, returns a path through the taxonomy:\n// 1. fingerprint → cache hit → reuse path (0 LLM calls, source=cache)\n// 2. miss → LLM draftAgent proposes { path, axes } → validated → pending\n// 3. autoApproveConfidence met → committed + cached\n//\n// Pending paths are still usable immediately (caller tags artifacts with\n// them); approval flips the flag and populates the fingerprint index so\n// the next similar message hits the cache.\n\nexport interface ClassifyInput {\n text: string\n channel?: string\n sender?: string\n /** Conversation id; stored on the classification so `agentx_wiki_query`\n * can look up the path of the request it is serving. */\n chatId?: string\n /** Agent that will RECEIVE the message after classification. When this\n * equals `draftAgent`, classification is skipped to prevent a deadlock —\n * the classifier's sub-task would otherwise queue behind the in-progress\n * main task on the same agent. */\n agentId?: string\n}\n\nexport interface ClassifyResult {\n /** Node ids from root to leaf. Shorter-than-full paths are allowed. */\n path: string[]\n /** Stable hash of the path — used as the `graph:<pathId>` wiki tag. */\n pathId: string\n /** Human-readable \"A › B › C\" path for logs + UI. */\n pathLabel: string\n /** Axis values asserted by the classifier. Keyed by node id. */\n axes: Record<string, Record<string, string>>\n leaf: { input?: string; output?: string }\n source: \"cache\" | \"llm\" | \"seat\"\n status: \"pending\" | \"approved\"\n confidence?: number\n}\n\nexport type ApprovalStructurePolicy = \"strict\" | \"extend-leaves\" | \"any\"\n\nexport interface ClassifierOptions {\n store: GraphStore\n /** Base URL of the daemon — used to POST /task for LLM proposals. */\n daemonUrl: string\n /** Optional bearer token for the daemon. */\n token?: string\n /** Which agent makes the proposal. Required if caller ever expects a\n * non-cached classification (cache hits work without an agent). */\n draftAgent?: string\n /** Structural approval policy — see config.ts for semantics. Default\n * \"extend-leaves\" (auto-approve pure reuse + single-leaf additions). */\n autoApproveStructure?: ApprovalStructurePolicy\n /** Minimum confidence (0..1) to commit without human approval. 1.0 = never.\n * OR'd with the structural policy. */\n autoApproveConfidence?: number\n /** Provider used for the in-process classifier call. Defaults to\n * \"claude-code\", which auto-detects auth via `loadAuthConfig()` —\n * Claude Max OAuth (CLI subprocess), API key, etc. NEVER opens a\n * paid path beyond what the operator has already configured. */\n classifierProvider?: ProviderName\n /** Model id passed to the provider. For claude-code OAuth this is\n * mapped through `CLI_MODEL_ALIASES` (sonnet/opus/haiku). Cheap\n * default — classification is metadata, not work. */\n classifierModel?: string\n log?: (...args: unknown[]) => void\n}\n\nexport class Classifier {\n private store: GraphStore\n private daemonUrl: string\n private token?: string\n private draftAgent?: string\n private autoApprove: number\n private autoApproveStructure: ApprovalStructurePolicy\n private classifierModel: string\n private classifierProviderName: ProviderName\n private providerInstance?: AgentProvider\n private log: (...args: unknown[]) => void\n\n constructor(opts: ClassifierOptions) {\n this.store = opts.store\n this.daemonUrl = opts.daemonUrl.replace(/\\/+$/, \"\")\n this.token = opts.token\n this.draftAgent = opts.draftAgent\n this.autoApprove = opts.autoApproveConfidence ?? 1.0\n this.autoApproveStructure = opts.autoApproveStructure ?? \"extend-leaves\"\n this.classifierModel = opts.classifierModel ?? \"claude-haiku-4-5-20251001\"\n this.classifierProviderName = opts.classifierProvider ?? \"claude-code\"\n this.log = opts.log ?? console.error.bind(console, \"[classifier]\")\n }\n\n /** Lazily build the provider instance. Lazy because constructing\n * ClaudeCodeProvider hits auth-store; deferring it keeps the\n * classifier importable in tests / dry-run paths that never call\n * classify(). */\n private getProvider(): AgentProvider {\n if (!this.providerInstance) {\n this.providerInstance = createProvider(this.classifierProviderName)\n }\n return this.providerInstance\n }\n\n async classify(input: ClassifyInput): Promise<ClassifyResult | null> {\n const fp = this.store.fingerprint({\n text: input.text,\n channel: input.channel,\n sender: input.sender,\n })\n\n // 1. Cache hit — instant return, zero LLM. Logged to classifications.jsonl\n // so the cache-hit rate is measurable from the persisted record alone\n // (otherwise hits leave no trace and ROI of the classifier is invisible).\n const cached = this.store.getFingerprint(fp)\n if (cached) {\n this.store.appendClassification({\n ts: new Date().toISOString(),\n msgHash: fp,\n agentId: input.agentId,\n channel: input.channel,\n sender: input.sender,\n chatId: input.chatId,\n path: cached.path,\n proposedAxes: {},\n leaf: cached.leaf,\n source: \"cache\",\n status: \"approved\",\n preview: input.text.slice(0, 200),\n })\n return {\n path: cached.path,\n pathId: hashPath(cached.path),\n pathLabel: pathLabel(cached.path, this.store.loadNodes().nodes),\n axes: {},\n leaf: cached.leaf,\n source: \"cache\",\n status: \"approved\",\n }\n }\n\n // 2. Cache miss — need an LLM proposal. Phase 2 of classifier-retire\n // replaced the /task → graph-agent dispatch with a direct Anthropic\n // Messages call, so we no longer need a draftAgent and the\n // self-dispatch deadlock guard (`agentId === draftAgent`) is moot.\n // `graph.enabled` is the only gate now.\n const schema = this.store.loadSchema()\n const nodes = this.store.loadNodes().nodes\n const proposal = await this.propose(input, schema, nodes)\n if (!proposal) return null\n\n // 3. Validate + persist as pending. Any NEW node the LLM proposed is\n // only committed if the whole classification auto-approves below.\n const { path, proposedAxes, confidence, leaf } = proposal\n if (path.length === 0) return null\n\n const classification: Classification = {\n ts: new Date().toISOString(),\n msgHash: fp,\n agentId: input.agentId,\n channel: input.channel,\n sender: input.sender,\n chatId: input.chatId,\n path,\n proposedAxes,\n leaf,\n source: proposal.source,\n status: \"pending\",\n confidence,\n preview: input.text.slice(0, 200),\n }\n\n // --- Auto-approval policy (see config.ts for full semantics) ---\n //\n // Two conditions, OR'd. Either approves the classification:\n // 1. Structure policy — what KIND of change the proposed path makes:\n // strict: nothing auto — always pending\n // extend-leaves: auto if the path either (a) reuses only existing\n // nodes, or (b) adds exactly one new node at the\n // DEEPEST level (a new leaf). Structural changes\n // (new mid-path or root node) still queue.\n // any: auto regardless of structure.\n // 2. Confidence — LLM-self-reported confidence >= autoApproveConfidence.\n //\n // Pending classifications still persist to the log, but don't commit nodes\n // and don't populate the fingerprint cache (so similar messages re-query).\n const existingIds = new Set(nodes.map((n) => n.id))\n const newNodeIndices = path\n .map((id, idx) => (existingIds.has(id) ? -1 : idx))\n .filter((idx) => idx >= 0)\n\n let structureOk = false\n if (this.autoApproveStructure === \"any\") {\n structureOk = true\n } else if (this.autoApproveStructure === \"extend-leaves\") {\n const isReuseOnly = newNodeIndices.length === 0\n const isSingleLeafAddition =\n newNodeIndices.length === 1 && newNodeIndices[0] === path.length - 1\n structureOk = isReuseOnly || isSingleLeafAddition\n }\n const confidenceOk = confidence !== undefined && confidence >= this.autoApprove\n\n let status: \"pending\" | \"approved\" = \"pending\"\n if (structureOk || confidenceOk) {\n // Commit any new nodes; if ANY fail schema validation (missing axes,\n // bad ref, etc.) fall back to pending for the whole classification\n // rather than half-committing the graph and bubbling the error up to\n // the task handler.\n try {\n this.commitNewNodes(path, proposedAxes, schema, nodes, input.sender)\n this.store.setFingerprint(fp, { path, leaf })\n status = \"approved\"\n } catch (e: any) {\n this.log(\n `auto-approve failed — staying pending ·`,\n `path=${JSON.stringify(path)}`,\n `reason=${(e?.message || e).toString().slice(0, 200)}`,\n )\n }\n }\n classification.status = status\n this.store.appendClassification(classification)\n\n return {\n path,\n pathId: hashPath(path),\n pathLabel: pathLabel(path, this.store.loadNodes().nodes),\n axes: proposedAxes,\n leaf,\n source: proposal.source,\n status,\n confidence,\n }\n }\n\n /**\n * Where a cache miss gets its path from, by the intent-path seat's mode:\n *\n * off the LLM, as before\n * shadow the LLM decides; the seat runs afterwards with the LLM's path\n * as incumbent, so agreement is measured before it can act\n * active the seat decides when both stages clear their thresholds;\n * the LLM only answers what the seat could not\n *\n * The seat reuses existing nodes only, so a seat proposal never adds to\n * the taxonomy; the LLM keeps that role in every mode.\n */\n private async propose(\n input: ClassifyInput,\n schema: GraphSchema,\n nodes: GraphNode[],\n ): Promise<Proposal | null> {\n const mode = getSeatMode(INTENT_PATH_SEAT)\n const seatInput = { message: input.text, channel: input.channel, sender: input.sender, agent: input.agentId }\n\n if (mode === \"active\") {\n const viaSeat = await proposePathViaSeat(seatInput, nodes).catch((e) => {\n this.log(\"intent-path seat failed:\", e?.message || e)\n return null\n })\n if (viaSeat?.confident) {\n return { path: viaSeat.path, proposedAxes: {}, leaf: {}, confidence: viaSeat.confidence, source: \"seat\" }\n }\n }\n\n const viaLlm = await this.proposePath(input, schema, nodes).catch((e) => {\n this.log(\"LLM proposal failed:\", e?.message || e)\n return null\n })\n if (mode === \"shadow\" && viaLlm) {\n void proposePathViaSeat(seatInput, nodes, { incumbent: viaLlm.path }).catch(() => {})\n }\n return viaLlm ? { ...viaLlm, source: \"llm\" } : null\n }\n\n /** Direct call to Anthropic to propose a path. Pure I/O; no side effects.\n * Bypasses the mesh / dispatch / ledger pipeline so a classification\n * doesn't surface as a sibling task in the activity graph. */\n private async proposePath(\n input: ClassifyInput,\n schema: GraphSchema,\n nodes: GraphNode[],\n ): Promise<{\n path: string[]\n proposedAxes: Record<string, Record<string, string>>\n confidence?: number\n leaf: { input?: string; output?: string }\n } | null> {\n const nodesForPrompt = nodes.map((n) => ({\n id: n.id,\n level: n.level,\n parentId: n.parentId,\n axes: n.axes,\n }))\n const userPrompt = [\n `You are the intent classifier for AgentX. You answer ONE question:`,\n `\"what kind of work does this message describe?\" — verb-level only.`,\n ``,\n `IMPORTANT — what NOT to encode in the path:`,\n `- Client / company / project / team names. Those are already on the event`,\n ` metadata (project, channel, agentId). Embedding them in the path causes`,\n ` duplicate nodes (one per client) for the same verb.`,\n `- The specific subject (issue number, file name, person name). Same reason.`,\n `- The agent's name or role. Pick the verb the *requester* intended.`,\n ``,\n `SCHEMA (fixed levels — pick one node per level):`,\n \"```json\",\n JSON.stringify(schema, null, 2),\n \"```\",\n ``,\n `EXISTING NODES (prefer reusing these; only invent a new node when no`,\n `existing one fits):`,\n \"```json\",\n JSON.stringify(nodesForPrompt, null, 2),\n \"```\",\n ``,\n `MESSAGE:`,\n \"```\",\n input.text.slice(0, 2000),\n \"```\",\n `channel: ${input.channel ?? \"?\"}`,\n `sender: ${input.sender ?? \"?\"}`,\n ``,\n `TASK:`,\n `1. Classify into 'category' (closed enum): code, ops, support, admin,`,\n ` knowledge, social, system. Pick the closest fit.`,\n `2. Pick or propose a 'verb' node. Verb ids are dot-namespaced lower-kebab,`,\n ` e.g. \"review.merge-request\", \"deploy.staging\", \"investigate.error\",`,\n ` \"chat.greeting\", \"fix.bug\". Reuse an existing verb when the message is`,\n ` the same kind of work as something already classified, even when the`,\n ` client / project / subject differs.`,\n ``,\n `Examples (right vs wrong):`,\n `- \"Please review MR #957 on globex/system\" → [\"code\", \"review.merge-request\"]`,\n ` NOT [\"business\", \"acme\", \"globex-v2\", \"review-mr-957-system\"]`,\n `- \"Deploy initech-v2 to staging please\" → [\"ops\", \"deploy.staging\"]`,\n ` NOT [\"business\", \"acme\", \"initech-v2\", \"deploy-initech-v2-to-staging\"]`,\n `- \"Hello Atlas\" → [\"support\", \"chat.greeting\"]`,\n ``,\n `NODE ID RULES (strict — invalid ids get dropped):`,\n `- lowercase-kebab + dots only: [a-z0-9][a-z0-9._-]*`,\n `- no spaces, no uppercase, no Arabic/other non-Latin`,\n `- verb ids should use a \"category.specific\" or \"verb.modifier\" shape`,\n ``,\n `Return ONE JSON object on one line, no prose, no fences:`,\n ` { \"path\": string[], \"proposedAxes\": { [nodeId]: { [axisName]: string } }, \"leaf\": { \"input\"?: string, \"output\"?: string }, \"confidence\": number }`,\n `confidence in [0,1]. Prefer low confidence over guessing.`,\n ].join(\"\\n\")\n\n // Phase 2 of classifier-retire: in-process provider call.\n // Replaces the `/task → graph-agent → mesh` dispatch path that\n // generated a sibling ledger event per classification (60–70% of\n // dispatches in the activity graph). Reuses whatever auth the\n // operator already configured (Claude Max OAuth via CLI subprocess,\n // Anthropic API key, etc.) — never opens a paid path the operator\n // didn't already opt into. The provider returns a plain JSON-text\n // GenerationResult, so we parse the same way as before.\n const text = await this.callProvider(userPrompt)\n const parsed = extractJson(text)\n if (!parsed || !Array.isArray(parsed.path)) return null\n\n // LLM often returns human-readable labels (\"Business\", \"Sales Manager\").\n // The node-id schema requires a lowercase slug, so normalize here and\n // remember the original → slug mapping so proposedAxes stays attached.\n // We also drop any element that can't be slugified into a valid id (e.g.\n // a path entry of pure Arabic script) — better to classify with a shorter\n // path than to have the store reject the whole classification.\n const NODE_ID_RE = /^[a-z0-9][a-z0-9._-]*$/\n const slugMap = new Map<string, string>()\n const path: string[] = parsed.path\n .filter((s: unknown): s is string => typeof s === \"string\" && s.length > 0)\n .map((s: string) => {\n const trimmed = s.trim()\n const slugged = slugifyNodeId(trimmed)\n if (trimmed !== slugged) slugMap.set(trimmed, slugged)\n return slugged\n })\n .filter((s: string) => NODE_ID_RE.test(s))\n if (path.length === 0) return null\n\n const proposedAxes: Record<string, Record<string, string>> = {}\n if (parsed.proposedAxes && typeof parsed.proposedAxes === \"object\") {\n for (const [rawNodeId, axes] of Object.entries(parsed.proposedAxes as Record<string, any>)) {\n if (!axes || typeof axes !== \"object\") continue\n const nodeId = slugMap.get(rawNodeId) ?? slugifyNodeId(rawNodeId)\n if (!nodeId) continue\n const clean: Record<string, string> = {}\n for (const [k, v] of Object.entries(axes)) {\n if (typeof v === \"string\") clean[k] = v\n }\n // If the LLM didn't volunteer a human-readable name but we slugified\n // one away, preserve the original as a `name` axis so the UI can\n // render \"Sales Manager\" not \"sales-manager\".\n if (!clean.name && rawNodeId !== nodeId) clean.name = rawNodeId.trim()\n proposedAxes[nodeId] = clean\n }\n }\n\n const leaf: { input?: string; output?: string } = {}\n if (parsed.leaf && typeof parsed.leaf === \"object\") {\n if (typeof parsed.leaf.input === \"string\") leaf.input = parsed.leaf.input\n if (typeof parsed.leaf.output === \"string\") leaf.output = parsed.leaf.output\n }\n\n const confidence = typeof parsed.confidence === \"number\"\n ? Math.max(0, Math.min(1, parsed.confidence))\n : undefined\n\n return { path, proposedAxes, confidence, leaf }\n }\n\n /** Create any nodes along `path` that don't exist yet. Delegates to the\n * store so level inference (for paths that skip levels) + fresh reloads\n * between adds are shared with the review path. */\n private commitNewNodes(\n path: string[],\n proposedAxes: Record<string, Record<string, string>>,\n schema: GraphSchema,\n _existing: GraphNode[],\n createdBy?: string,\n ): void {\n this.store.commitNodesAlongPath(path, proposedAxes, schema, createdBy)\n }\n\n /** Run the prompt through whichever AgentProvider the operator already\n * configured (Claude Max subscription via CLI, Anthropic API key, …).\n * Hard rule: NEVER open a paid path the operator didn't opt into —\n * this is just a thin wrapper that reuses existing auth. 30s timeout\n * is enough for Haiku/CLI cold-start; any longer and the classifier\n * is stalling the caller. */\n private async callProvider(userPrompt: string): Promise<string> {\n const provider = this.getProvider()\n const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), 30_000)\n try {\n const res = await provider.generate(\n [\n {\n role: \"system\",\n content:\n \"You are a taxonomy classifier. Respond with a single JSON object on one line. No prose, no code fences.\",\n },\n { role: \"user\", content: userPrompt },\n ],\n { model: this.classifierModel, maxTokens: 800, abortSignal: ac.signal, bare: true },\n )\n return res.content || \"\"\n } finally {\n clearTimeout(timer)\n }\n }\n}\n\n/** Normalize an LLM-proposed node label to a valid node id. Matches the\n * schema's `^[a-z0-9][a-z0-9._-]*$` — lowercase, alnum/./-/_, leading alnum.\n * Dots are preserved for verb ids like \"review.merge-request\". */\nfunction slugifyNodeId(raw: string): string {\n const s = raw\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9._-]+/g, \"-\")\n .replace(/^[-_]+|[-_]+$/g, \"\")\n .replace(/-{2,}/g, \"-\")\n if (!s) return \"\"\n // Ensure leading char is alnum (schema rejects leading _ or -).\n return /^[a-z0-9]/.test(s) ? s : `n-${s}`.replace(/[-_]+$/, \"\")\n}\n\n// --- path helpers used by both classifier + wiki retrieval ---\n\n/** Deterministic hash of a path, used as the `graph:<pathId>` wiki tag. */\nexport function hashPath(path: string[]): string {\n return createHash(\"sha1\")\n .update(path.join(\"\\u0001\"))\n .digest(\"hex\")\n .slice(0, 16)\n}\n\n/** \"Business › Acme › DevOps › Review MR\" — for the UI + context render. */\nexport function pathLabel(path: string[], nodes: GraphNode[]): string {\n return path\n .map((id) => {\n const n = nodes.find((x) => x.id === id)\n if (!n) return id\n // Prefer a `name` axis when the schema has one; fall back to id.\n const name = n.axes?.name || n.axes?.what || id\n return name\n })\n .join(\" › \")\n}\n\n/**\n * Depth of the deepest common ancestor between two paths, normalized to [0,1].\n * Used to score wiki articles: exact match = 1, shared grandparent = 0.5, none = 0.\n */\nexport function ancestryScore(a: string[], b: string[]): number {\n if (a.length === 0 || b.length === 0) return 0\n let shared = 0\n for (let i = 0; i < Math.min(a.length, b.length); i++) {\n if (a[i] === b[i]) shared++\n else break\n }\n if (shared === 0) return 0\n const depth = Math.max(a.length, b.length)\n return shared / depth\n}\n\n","import { choice } from \"../questions\"\nimport { askSeat, getSeatMode } from \"../seat\"\nimport type { SeatMode } from \"../store\"\nimport type { ChoiceAnswer, StateValue } from \"../types\"\nimport type { GraphNode } from \"@/graph/types\"\nimport { CATEGORY_DESCRIPTIONS } from \"@/graph/starter-schema\"\n\n// Which intent-graph path a message belongs to.\n//\n// The intent graph is a closed set: seven categories, a few hundred verbs,\n// and in the last few thousand classifications the LLM classifier never\n// proposed a node that did not already exist. Picking one label out of a\n// known list is what a decision seat does in under a second for a fraction\n// of a cent; asking a chat model to write the label out cost a fresh CLI\n// process per task, twenty seconds of wall time on average.\n//\n// Two stages because Jev caps a Choice at 255 options and the largest\n// category holds about a hundred verbs: first the category, then the verb\n// within it. Each stage carries the LLM's answer as the incumbent while the\n// seat runs in shadow, so `agentx decisions stats` reports agreement before\n// the seat is allowed to decide.\n//\n// Fail-open, as every seat: a missing or unconfident answer means \"no\n// proposal\", and the classifier falls back to the LLM it uses today. The\n// seat can only ever reuse existing nodes; growing the taxonomy stays the\n// LLM's job.\n\nexport const INTENT_PATH_SEAT = \"intent-path\"\n\n/** Below these the seat's answer is not acted on. A Choice always returns\n * an argmax, so the probability mass behind it is the only signal that\n * the pick is a match rather than the least bad option. */\nexport const INTENT_PATH_MIN_CATEGORY_P = 0.5\nexport const INTENT_PATH_MIN_VERB_P = 0.3\n/** A verb between the floor above and this keeps only its category. In the\n * first days active, 30% of seat labels sat at 0.30-0.50 and most of the\n * nonsensical verbs in a spot check were under 0.45, while the category\n * stayed right. Dropping the verb keeps the sub-second answer instead of\n * handing the message to an LLM fallback that failed about half the time. */\nexport const INTENT_PATH_FULL_VERB_P = 0.45\n\nexport interface IntentPathInput {\n message: string\n channel?: string\n sender?: string\n agent?: string\n}\n\nexport interface IntentPathProposal {\n /** `[category]` or `[category, verb]`, existing node ids only. A verb\n * under `INTENT_PATH_FULL_VERB_P` is dropped from `path`. */\n path: string[]\n /** min over the stages' argmax probability, over the stages kept in\n * `path`. */\n confidence: number\n /** Both stages cleared their thresholds; the caller may act on `path`. */\n confident: boolean\n mode: SeatMode\n}\n\nexport function intentPathState(input: IntentPathInput): StateValue {\n return {\n message: clip(input.message, 2000),\n channel: input.channel ?? null,\n sender: input.sender ?? null,\n agent: input.agent ?? null,\n }\n}\n\n/** One Choice over the given nodes. The option text is the node's own\n * description when it has one, so the seat reads what a reviewer reads. */\nexport function nodeChoice(nodes: GraphNode[], instructions: string) {\n const criteria: Record<string, string | null> = {}\n for (const n of nodes) criteria[n.id] = describe(n)\n return choice(criteria, instructions)\n}\n\nexport function categoryQuestions(categories: GraphNode[]) {\n // Stores seeded before categories carried a description still hold bare\n // `{ kind }` nodes; the shipped text fills in so the options are not ids.\n const described = categories.map((n) => {\n const fallback = (CATEGORY_DESCRIPTIONS as Record<string, string | undefined>)[n.id]\n return n.axes?.description?.trim() || !fallback ? n : { ...n, axes: { ...n.axes, description: fallback } }\n })\n return {\n category: nodeChoice(\n described,\n \"Which category of work does this message ask for or report on? Pick the one a reviewer would file it under.\",\n ),\n }\n}\n\nexport function verbQuestions(verbs: GraphNode[]) {\n return {\n verb: nodeChoice(\n verbs,\n \"Within that category, which specific action does the message ask for or report on? Prefer the most specific verb that fits; a casual or ambiguous message belongs to the most general one.\",\n ),\n }\n}\n\nexport interface ProposeViaSeatOptions {\n /** The LLM's path for the same message, recorded as the incumbent so the\n * shadow report can measure agreement. */\n incumbent?: string[]\n}\n\n/**\n * Two-stage classification through the seat. Returns null when the seat is\n * off, unreachable or the taxonomy is too small to ask; otherwise a proposal\n * whose `confident` flag says whether the thresholds were met.\n */\nexport async function proposePathViaSeat(\n input: IntentPathInput,\n nodes: GraphNode[],\n opts: ProposeViaSeatOptions = {},\n): Promise<IntentPathProposal | null> {\n if (getSeatMode(INTENT_PATH_SEAT) === \"off\") return null\n const categories = nodes.filter((n) => n.parentId === null)\n if (categories.length < 2) return null\n\n const state = intentPathState(input)\n const features = { agent: input.agent ?? \"unknown\", channel: input.channel ?? \"unknown\" }\n const links = input.agent ? [{ kind: \"agent\", id: input.agent }] : undefined\n\n const stage1 = await askSeat(INTENT_PATH_SEAT, state, categoryQuestions(categories), {\n incumbent: opts.incumbent?.[0] ? { category: opts.incumbent[0] } : undefined,\n features: { ...features, stage: \"category\" },\n links,\n })\n if (!stage1) return null\n const cat = stage1.answers.category as ChoiceAnswer\n let confidence = cat.pMax\n let confident = cat.pMax >= INTENT_PATH_MIN_CATEGORY_P\n const path = [cat.choice]\n\n const verbs = nodes.filter((n) => n.parentId === cat.choice)\n if (verbs.length >= 2) {\n const sameCategory = opts.incumbent?.[0] === cat.choice\n const stage2 = await askSeat(INTENT_PATH_SEAT, { ...(state as Record<string, StateValue>), category: cat.choice }, verbQuestions(verbs), {\n // The LLM's verb is only a fair incumbent when it lives in the same\n // category; otherwise it is not among the options at all.\n incumbent: sameCategory && opts.incumbent?.[1] ? { verb: opts.incumbent[1] } : undefined,\n features: { ...features, stage: \"verb\", category: cat.choice },\n links,\n })\n if (!stage2) return null\n const verb = stage2.answers.verb as ChoiceAnswer\n confident = confident && verb.pMax >= INTENT_PATH_MIN_VERB_P\n if (verb.pMax >= INTENT_PATH_FULL_VERB_P || !confident) {\n confidence = Math.min(confidence, verb.pMax)\n path.push(verb.choice)\n }\n } else if (verbs.length === 1) {\n path.push(verbs[0].id)\n }\n\n return { path, confidence, confident, mode: stage1.mode }\n}\n\nfunction describe(n: GraphNode): string | null {\n const d = n.axes?.description?.trim() || undefined\n const rawName = n.axes?.name?.trim()\n // A name that merely repeats the id adds nothing the label does not say.\n const name = rawName && rawName !== n.id ? rawName : undefined\n if (d && name) return `${name}: ${d}`\n return d ?? name ?? null\n}\n\nfunction clip(s: string, max: number): string {\n return s.length > max ? `${s.slice(0, max)}…` : s\n}\n"],"mappings":"gRAAAA,IAwBO,IAAMC,GAAmB,cAiBnBC,GAAoB,GACpBC,GAAwB,IAiB9B,SAASC,GAAYC,EAAgC,CAC1D,IAAMC,EAASD,EAAM,cAAgBF,GACrC,MAAO,CACL,MAAOE,EAAM,MACb,WAAYA,EAAM,WAAW,IAAKE,IAAO,CACvC,GAAIA,EAAE,GACN,MAAOA,EAAE,MACT,KAAMA,EAAE,MAAM,OAASA,EAAE,KAAK,KAAK,IAAI,EAAI,KAC3C,QAASC,GAAKD,EAAE,QAASD,CAAM,CACjC,EAAE,CACJ,CACF,CAGO,SAASG,GAAgBC,EAA+B,CAC7D,IAAMC,EAAmC,CAAC,EAC1C,QAAWJ,KAAKG,EAAYC,EAASJ,EAAE,EAAE,EAAIA,EAAE,MAC/C,MAAO,CACL,KAAMK,EAAOD,EAAU,oCAAoC,EAC3D,WAAYE,EAAK,4CAA4C,CAC/D,CACF,CAqBO,SAASC,GACdC,EACAL,EACc,CACd,IAAMM,EAAOD,EAAQ,KACfE,EAAiC,CAAC,EACxC,QAAWV,KAAKG,EAAYO,EAAOV,EAAE,EAAE,EAAIS,EAAK,cAAcT,EAAE,EAAE,GAAK,EAEvE,IAAMW,EAAQ,IAAI,IAAIR,EAAW,IAAI,CAACH,EAAGY,IAAM,CAACZ,EAAE,GAAIY,CAAC,CAAC,CAAC,EAMzD,MAAO,CACL,OANa,CAAC,GAAGT,EAAW,IAAKH,GAAMA,EAAE,EAAE,CAAC,EAAE,KAAK,CAACa,EAAGC,IAAM,CAC7D,IAAMC,EAAOL,EAAOI,CAAC,EAAIJ,EAAOG,CAAC,EACjC,OAAOE,IAAS,EAAIA,GAAQJ,EAAM,IAAIE,CAAC,GAAK,IAAMF,EAAM,IAAIG,CAAC,GAAK,EACpE,CAAC,EAIC,OAAAJ,EACA,WAAaF,EAAQ,WAA0B,KAC/C,WAAYC,EAAK,UACnB,CACF,CAEA,SAASR,GAAKe,EAAcC,EAAqB,CAC/C,IAAMC,EAAO,OAAOF,GAAQ,EAAE,EAAE,QAAQ,OAAQ,GAAG,EAAE,KAAK,EAC1D,OAAOE,EAAK,OAASD,EAAMC,EAAK,MAAM,EAAGD,CAAG,EAAIC,CAClD,CCnGO,IAAMC,EAA8B,CACzC,QAAS,EACT,OAAQ,CACN,CACE,GAAI,WACJ,YAAa,kEACb,KAAM,CACJ,CACE,KAAM,OACN,KAAM,OACN,OAAQ,CACN,OACA,MACA,UACA,QACA,YACA,SACA,QACF,EACA,YAAa,sCACf,CACF,CACF,EACA,CACE,GAAI,OACJ,YAAa,oEACb,KAAM,CACJ,CACE,KAAM,OACN,KAAM,OACN,YACE,+UAKJ,CACF,CACF,CACF,EACA,UAAW,CACT,KAAM,QACN,KAAM,OACN,OAAQ,CACN,WACA,WACA,QACA,UACA,YACA,eACA,eACA,YACA,OACA,UACA,OACA,MACF,EACA,YAAa,uCACf,EACA,WAAY,CACV,KAAM,SACN,KAAM,OACN,YACE,8LAGJ,CACF,EAOaC,EAAyD,CACpE,KAAM,sKACN,IAAK,0GACL,QAAS,oRACT,MAAO,uLACP,UAAW,wGACX,OAAQ,4IACR,OAAQ,8HACV,EAQaC,EAIR,CAEH,CAAE,SAAU,OAAQ,KAAM,uBAAyB,YAAa,iCAAkC,EAClG,CAAE,SAAU,OAAQ,KAAM,qBAAyB,YAAa,qCAAsC,EACtG,CAAE,SAAU,OAAQ,KAAM,UAAyB,YAAa,oBAAqB,EACrF,CAAE,SAAU,OAAQ,KAAM,oBAAyB,YAAa,qBAAsB,EACtF,CAAE,SAAU,OAAQ,KAAM,oBAAyB,YAAa,iDAAkD,EAClH,CAAE,SAAU,OAAQ,KAAM,gBAAyB,YAAa,oDAAqD,EACrH,CAAE,SAAU,OAAQ,KAAM,eAAyB,YAAa,sCAAuC,EACvG,CAAE,SAAU,OAAQ,KAAM,eAAyB,YAAa,qCAAsC,EACtG,CAAE,SAAU,OAAQ,KAAM,uBAAyB,YAAa,gCAAiC,EAGjG,CAAE,SAAU,MAAQ,KAAM,iBAAyB,YAAa,yCAA0C,EAC1G,CAAE,SAAU,MAAQ,KAAM,oBAAyB,YAAa,sBAAuB,EACvF,CAAE,SAAU,MAAQ,KAAM,mBAAyB,YAAa,qBAAsB,EACtF,CAAE,SAAU,MAAQ,KAAM,uBAAyB,YAAa,mCAAoC,EACpG,CAAE,SAAU,MAAQ,KAAM,iBAAyB,YAAa,+BAAgC,EAChG,CAAE,SAAU,MAAQ,KAAM,oBAAyB,YAAa,kCAAmC,EACnG,CAAE,SAAU,MAAQ,KAAM,iBAAyB,YAAa,4BAA6B,EAG7F,CAAE,SAAU,UAAW,KAAM,gBAA4B,YAAa,uCAAwC,EAC9G,CAAE,SAAU,UAAW,KAAM,uBAA4B,YAAa,qCAAsC,EAC5G,CAAE,SAAU,UAAW,KAAM,cAA4B,YAAa,8BAA+B,EACrG,CAAE,SAAU,UAAW,KAAM,kBAA4B,YAAa,+BAAgC,EACtG,CAAE,SAAU,UAAW,KAAM,gBAA4B,YAAa,sCAAuC,EAG7G,CAAE,SAAU,QAAS,KAAM,gBAAwB,YAAa,2CAA4C,EAC5G,CAAE,SAAU,QAAS,KAAM,eAAwB,YAAa,iCAAkC,EAClG,CAAE,SAAU,QAAS,KAAM,kBAAwB,YAAa,8BAA+B,EAC/F,CAAE,SAAU,QAAS,KAAM,eAAwB,YAAa,yBAA0B,EAC1F,CAAE,SAAU,QAAS,KAAM,YAAwB,YAAa,uBAAwB,EACxF,CAAE,SAAU,QAAS,KAAM,cAAwB,YAAa,uBAAwB,EAGxF,CAAE,SAAU,YAAa,KAAM,mBAAwB,YAAa,+BAAgC,EACpG,CAAE,SAAU,YAAa,KAAM,mBAAwB,YAAa,sCAAuC,EAC3G,CAAE,SAAU,YAAa,KAAM,iBAAwB,YAAa,sCAAuC,EAC3G,CAAE,SAAU,YAAa,KAAM,cAAwB,YAAa,2CAA4C,EAGhH,CAAE,SAAU,SAAU,KAAM,cAAuB,YAAa,gCAAiC,EACjG,CAAE,SAAU,SAAU,KAAM,aAAuB,YAAa,uCAAwC,EACxG,CAAE,SAAU,SAAU,KAAM,gBAAuB,YAAa,gCAAiC,EAGjG,CAAE,SAAU,SAAU,KAAM,kBAAuB,YAAa,qCAAsC,CACxG,EC1KA,OACE,gBAAAC,EACA,iBAAAC,EACA,kBAAAC,GACA,cAAAC,EACA,aAAAC,EACA,cAAAC,GACA,YAAAC,MACK,KACP,OAAS,WAAAC,EAAS,WAAAC,MAAe,OACjC,OAAS,cAAAC,OAAkB,SA0BpB,IAAMC,EAAN,KAAiB,CACb,QACD,IAIA,OAAS,IAAI,IAErB,YAAYC,EAA0B,CAAC,EAAG,CACxC,KAAK,QAAUA,EAAK,SAAWC,EAAQ,QAAQ,IAAI,EAAG,eAAe,EACrE,KAAK,IAAMD,EAAK,KAAO,QAAQ,MAAM,KAAK,QAAS,SAAS,EAC5DE,EAAU,KAAK,QAAS,CAAE,UAAW,EAAK,CAAC,CAC7C,CAIA,YAAqB,CAAE,OAAOD,EAAQ,KAAK,QAAS,aAAa,CAAE,CACnE,WAAoB,CAAE,OAAOA,EAAQ,KAAK,QAAS,YAAY,CAAE,CACjE,WAAoB,CAAE,OAAOA,EAAQ,KAAK,QAAS,YAAY,CAAE,CACjE,qBAA8B,CAAE,OAAOA,EAAQ,KAAK,QAAS,uBAAuB,CAAE,CAWtF,YAA0B,CACxB,IAAME,EAAI,KAAK,WAAW,EAC1B,GAAI,CAACC,EAAWD,CAAC,EACf,YAAK,WAAWE,CAAc,EAC9B,KAAK,IAAI,2BAA4BF,CAAC,EAC/BE,EAET,IAAMC,EAAMC,EAAaJ,EAAG,OAAO,EAC7BK,EAASC,EAAkB,UAAU,KAAK,MAAMH,CAAG,CAAC,EAC1D,GAAI,CAACE,EAAO,QACV,MAAM,IAAI,MAAM,WAAWL,MAAMK,EAAO,MAAM,OAAO,IAAKE,GAAMA,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,EAEzF,IAAKF,EAAO,KAAK,SAAW,IAAMH,EAAe,SAAW,GAAI,CAC9D,IAAMM,EAASR,EAAE,QAAQ,UAAW,KAAKK,EAAO,KAAK,SAAW,YAAY,EAC5E,GAAI,CACFI,EAAcD,EAAQL,CAAG,EACzB,KAAK,IAAI,0BAA0BE,EAAO,KAAK,SAAW,SAASH,EAAe,yBAAyBM,GAAQ,CACrH,OAASE,EAAP,CACA,KAAK,IAAI,iCAAiCA,GAAG,SAAWA,GAAG,CAC7D,CACA,KAAK,WAAWR,CAAc,EAI9B,IAAMS,EAAK,KAAK,UAAU,EAC1B,GAAIV,EAAWU,CAAE,EAAG,CAClB,IAAMC,EAAUD,EAAG,QAAQ,UAAW,cAAc,EACpD,GAAI,CACFF,EAAcG,EAASR,EAAaO,EAAI,OAAO,CAAC,EAChD,KAAK,IAAI,6BAA6BC,GAAS,CACjD,OAASF,EAAP,CACA,KAAK,IAAI,gCAAgCA,GAAG,SAAWA,GAAG,CAC5D,CACA,GAAI,CACF,IAAMG,EAASC,EAAc,EAC7B,KAAK,UAAUD,CAAM,CACvB,OAASH,EAAP,CACA,KAAK,IAAI,4BAA4BA,GAAG,SAAWA,GAAG,CACxD,EAEF,OAAOR,EAET,OAAOG,EAAO,IAChB,CAEA,WAAWU,EAA2B,CACpC,IAAMV,EAASC,EAAkB,UAAUS,CAAM,EACjD,GAAI,CAACV,EAAO,QACV,MAAM,IAAI,MAAM,oCAAoCA,EAAO,MAAM,OAAO,IAAKE,GAAMA,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,EAE5GS,EAAc,KAAK,WAAW,EAAGX,EAAO,IAAI,CAC9C,CAIA,WAAuB,CACrB,IAAML,EAAI,KAAK,UAAU,EACzB,GAAI,CAACC,EAAWD,CAAC,EAAG,CAIlB,IAAMa,EAASC,EAAc,EAC7B,YAAK,UAAUD,CAAM,EACdA,EAGT,OAAO,gBAAgB,KAAK,WAAWb,EAAIG,GAAQ,CACjD,IAAME,EAASY,EAAgB,UAAU,KAAK,MAAMd,CAAG,CAAC,EACxD,GAAI,CAACE,EAAO,QACV,MAAM,IAAI,MAAM,WAAWL,MAAMK,EAAO,MAAM,OAAO,IAAKE,GAAMA,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,EAEzF,OAAOF,EAAO,IAChB,CAAC,CAAC,CACJ,CAEA,UAAUa,EAAuB,CAC/B,IAAMb,EAASY,EAAgB,UAAUC,CAAI,EAC7C,GAAI,CAACb,EAAO,QACV,MAAM,IAAI,MAAM,wCAAwCA,EAAO,MAAM,OAAO,IAAKE,GAAMA,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,EAEhH,KAAK,YAAY,KAAK,UAAU,EAAGF,EAAO,IAAI,CAChD,CAIQ,WAAcL,EAAWmB,EAA8B,CAC7D,GAAM,CAAE,QAAAC,EAAS,KAAAC,CAAK,EAAIC,EAAStB,CAAC,EAC9BuB,EAAM,KAAK,OAAO,IAAIvB,CAAC,EAC7B,GAAIuB,GAAOA,EAAI,UAAYH,GAAWG,EAAI,OAASF,EAAM,OAAOE,EAAI,KACpE,IAAMC,EAAOL,EAAMf,EAAaJ,EAAG,OAAO,CAAC,EAC3C,YAAK,OAAO,IAAIA,EAAG,CAAE,QAAAoB,EAAS,KAAAC,EAAM,KAAAG,CAAK,CAAC,EACnCA,CACT,CAIQ,YAAYxB,EAAWwB,EAAqB,CAClD,KAAK,OAAO,OAAOxB,CAAC,EACpBgB,EAAchB,EAAGwB,CAAI,EACrB,GAAM,CAAE,QAAAJ,EAAS,KAAAC,CAAK,EAAIC,EAAStB,CAAC,EACpC,KAAK,OAAO,IAAIA,EAAG,CAAE,QAAAoB,EAAS,KAAAC,EAAM,KAAAG,CAAK,CAAC,CAC5C,CAGA,aAAaC,EAAiBV,EAAqBW,EAAwB,CACzE,IAAMC,EAAQZ,EAAO,OAAO,KAAMa,GAAMA,EAAE,KAAOH,EAAK,KAAK,EAC3D,GAAI,CAACE,EAAO,MAAM,IAAI,MAAM,mBAAmBF,EAAK,QAAQ,EAE5D,IAAMI,EAAUF,EAAM,KACnB,OAAQG,GAAM,CAACA,EAAE,QAAQ,EACzB,OAAQA,GAAM,EAAEA,EAAE,QAAQL,EAAK,KAAK,EACvC,GAAII,EAAQ,OACV,MAAM,IAAI,MACR,QAAQJ,EAAK,OAAOA,EAAK,iCAAiCI,EAAQ,IAAKC,GAAMA,EAAE,IAAI,EAAE,KAAK,IAAI,GAChG,EAGF,QAAWA,KAAKH,EAAM,KAAM,CAC1B,IAAMI,EAAIN,EAAK,KAAKK,EAAE,IAAI,EAC1B,GAAIC,GAAK,KACT,IAAID,EAAE,OAAS,QAAUA,EAAE,QAAU,CAACA,EAAE,OAAO,SAASC,CAAC,EACvD,MAAM,IAAI,MAAM,QAAQN,EAAK,YAAYK,EAAE,SAASC,kBAAkB,KAAK,UAAUD,EAAE,MAAM,GAAG,EAElG,GAAIA,EAAE,OAAS,MAAO,CACpB,GAAI,CAACJ,EAAI,KAAMM,GAAMA,EAAE,KAAOD,CAAC,EAC7B,MAAM,IAAI,MAAM,QAAQN,EAAK,YAAYK,EAAE,SAASC,2BAA2B,EAEjF,GAAID,EAAE,SAAU,CACd,IAAMG,EAASP,EAAI,KAAMM,GAAMA,EAAE,KAAOD,CAAC,EACzC,GAAIE,EAAO,QAAUH,EAAE,SACrB,MAAM,IAAI,MACR,QAAQL,EAAK,YAAYK,EAAE,wBAAwBA,EAAE,sBAAsBG,EAAO,QACpF,KAMR,IAAMC,EAAWnB,EAAO,OAAO,UAAWa,GAAMA,EAAE,KAAOH,EAAK,KAAK,EACnE,GAAIS,IAAa,GACf,GAAIT,EAAK,WAAa,KACpB,MAAM,IAAI,MAAM,mBAAmBA,EAAK,6BAA6B,MAElE,CACL,GAAI,CAACA,EAAK,SACR,MAAM,IAAI,MAAM,iBAAiBA,EAAK,yBAAyB,EAEjE,IAAMU,EAAST,EAAI,KAAMM,GAAMA,EAAE,KAAOP,EAAK,QAAQ,EACrD,GAAI,CAACU,EAAQ,MAAM,IAAI,MAAM,QAAQV,EAAK,eAAeA,EAAK,qBAAqB,EACnF,IAAMW,EAAiBrB,EAAO,OAAO,UAAW,GAAM,EAAE,KAAOoB,EAAO,KAAK,EAG3E,GAAIC,EAAiB,GAAKA,GAAkBF,EAC1C,MAAM,IAAI,MACR,QAAQT,EAAK,gBAAgBA,EAAK,+BAA+BU,EAAO,gDAC1E,EAGN,CAUA,qBACEE,EACAC,EACAvB,EACAwB,EAC2C,CAC3C,IAAMC,EAAqB,CAAC,EACtBC,EAAoB,CAAC,EACrBC,EAAM,IAAI,KAAK,EAAE,YAAY,EAEnC,QAASnC,EAAI,EAAGA,EAAI8B,EAAK,OAAQ9B,IAAK,CACpC,IAAMoC,EAAKN,EAAK9B,CAAC,EAEXqC,EAAU,KAAK,UAAU,EAAE,MAEjC,GADiBA,EAAQ,KAAMZ,GAAMA,EAAE,KAAOW,CAAE,EAClC,CACZF,EAAQ,KAAKE,CAAE,EACf,SAIF,IAAIE,EAAS,EAGb,QAASC,EAAIvC,EAAI,EAAGuC,GAAK,EAAGA,IAAK,CAC/B,IAAMC,EAAUV,EAAKS,CAAC,EAChBE,EAAYJ,EAAQ,KAAMZ,GAAMA,EAAE,KAAOe,CAAO,GAAKP,EAAM,KAAMR,GAAMA,EAAE,KAAOe,CAAO,EAC7F,GAAIC,EAAW,CACbH,EAAS9B,EAAO,OAAO,UAAWa,GAAMA,EAAE,KAAOoB,EAAU,KAAK,EAAI,EACpE,OAIJ,IAAMC,EAAOX,EAAaK,CAAE,GAAK,CAAC,EAK9BO,EAA6B,KACjC,QAASJ,EAAID,EAAQC,EAAI/B,EAAO,OAAO,OAAQ+B,IAAK,CAClD,IAAMnB,EAAQZ,EAAO,OAAO+B,CAAC,EAI7B,GAHgBnB,EAAM,KACnB,OAAQG,GAAM,CAACA,EAAE,QAAQ,EACzB,OAAQA,GAAM,EAAEA,EAAE,QAAQmB,EAAK,EACtB,SAAW,EAAG,CACxBC,EAAcvB,EAAM,GACpBkB,EAASC,EAAI,EACb,OAUJ,GAJI,CAACI,GAAeL,EAAS9B,EAAO,OAAO,SACzCmC,EAAcnC,EAAO,OAAO8B,CAAM,EAAE,GACpCA,KAEE,CAACK,EACH,MAAM,IAAI,MAAM,oBAAoBP,yCAAqCE,aAAkB9B,EAAO,OAAO,SAAS,EAIpH,IAAIoC,EAA0B,KACxBC,EAAYrC,EAAO,OAAO,UAAWa,GAAMA,EAAE,KAAOsB,CAAW,EACrE,QAASJ,EAAIvC,EAAI,EAAGuC,GAAK,EAAGA,IAAK,CAC/B,IAAMC,EAAUV,EAAKS,CAAC,EAChBO,EAAQT,EAAQ,KAAMZ,GAAMA,EAAE,KAAOe,CAAO,GAAKP,EAAM,KAAMR,GAAMA,EAAE,KAAOe,CAAO,EACzF,GAAI,CAACM,EAAO,SACZ,IAAMC,EAAWvC,EAAO,OAAO,UAAWa,GAAMA,EAAE,KAAOyB,EAAM,KAAK,EACpE,GAAIC,GAAY,GAAKA,EAAWF,EAAW,CAAED,EAAWJ,EAAS,OAGnE,IAAMtB,EAAkB,CACtB,GAAAkB,EAAI,MAAOO,EAAa,SAAAC,EAAU,KAAAF,EAClC,UAAWP,EACX,UAAAH,CACF,EACA,KAAK,QAAQd,CAAI,EACjBe,EAAM,KAAKf,CAAI,EAGjB,MAAO,CAAE,MAAAe,EAAO,QAAAC,CAAQ,CAC1B,CAGA,QAAQhB,EAA4B,CAClC,IAAMV,EAAS,KAAK,WAAW,EACzBG,EAAO,KAAK,UAAU,EAC5B,GAAIA,EAAK,MAAM,KAAM,GAAM,EAAE,KAAOO,EAAK,EAAE,EACzC,MAAM,IAAI,MAAM,2BAA2BA,EAAK,IAAI,EAEtD,YAAK,aAAaA,EAAMV,EAAQ,CAAC,GAAGG,EAAK,MAAOO,CAAI,CAAC,EACrDP,EAAK,MAAM,KAAKO,CAAI,EACpB,KAAK,UAAUP,CAAI,EACZO,CACT,CAIA,qBAAqB8B,EAA4B,CAC/C,IAAMlD,EAASmD,EAAqB,UAAUD,CAAC,EAC/C,GAAI,CAAClD,EAAO,QAAS,CAKnB,IAAMoD,EAASpD,EAAO,MAAM,OAAO,IAAK,GAAM,GAAG,EAAE,KAAK,KAAK,GAAG,MAAM,EAAE,SAAS,EAAE,KAAK,QAAK,EAC7F,YAAK,IACH,yCAAoCoD,SACpC,QAAQ,KAAK,UAAUF,EAAE,IAAI,IAC7B,aAAa,KAAK,UAAU,OAAO,KAAKA,EAAE,cAAgB,CAAC,CAAC,CAAC,GAC/D,EACO,GAET,IAAMG,EAAO,KAAK,UAAUrD,EAAO,IAAI,EAAI;AAAA,EACrCL,EAAI,KAAK,oBAAoB,EACnC,OAAKC,EAAWD,CAAC,GACfD,EAAU4D,EAAQ3D,CAAC,EAAG,CAAE,UAAW,EAAK,CAAC,EAE3C4D,GAAe5D,EAAG0D,CAAI,EACf,EACT,CAQA,2BAA2BG,EAAQ,IAAuB,CACxD,OAAO,KAAK,aAAa,UAAWA,CAAK,CAC3C,CAKA,wBAA2C,CACzC,OAAO,KAAK,iBAAiB,CAC/B,CAGA,aAAaC,EAAkCD,EAAQ,IAAuB,CAE5E,OADY,KAAK,iBAAiB,EACvB,OAAQN,GAAMA,EAAE,SAAWO,CAAM,EAAE,MAAM,EAAGD,CAAK,CAC9D,CAOQ,kBAAqC,CAC3C,IAAM7D,EAAI,KAAK,oBAAoB,EACnC,GAAI,CAACC,EAAWD,CAAC,EAAG,MAAO,CAAC,EAE5B,IAAM+D,EADM3D,EAAaJ,EAAG,OAAO,EACjB,MAAM;AAAA,CAAI,EAAE,OAAO,OAAO,EACtCgE,EAAS,IAAI,IACnB,QAAWN,KAAQK,EACjB,GAAI,CACF,IAAM1D,EAASmD,EAAqB,UAAU,KAAK,MAAME,CAAI,CAAC,EAC9D,GAAI,CAACrD,EAAO,QAAS,SAErB2D,EAAO,IAAI3D,EAAO,KAAK,QAASA,EAAO,IAAI,CAC7C,MAAE,CAEF,CAGF,OAAO,MAAM,KAAK2D,EAAO,OAAO,CAAC,EAAE,KAAK,CAAClC,EAAGmC,IAAOnC,EAAE,GAAKmC,EAAE,GAAK,EAAI,EAAG,CAC1E,CAOA,2BACEC,EACAC,EAKuB,CAEvB,IAAMd,EADM,KAAK,iBAAiB,EAChB,KAAME,GAAMA,EAAE,UAAYW,CAAO,EACnD,GAAI,CAACb,EAAO,OAAO,KACnB,IAAMe,EAAuB,CAC3B,GAAGf,EACH,OAAQc,EAAO,OACf,GAAI,IAAI,KAAK,EAAE,YAAY,CAC7B,EAIA,GAAIA,EAAO,UAAYA,EAAO,aAAc,CAC1C,IAAME,EAAS,SAAMF,EAAO,SAAW,YAAYA,EAAO,WAAa,KAAKA,EAAO,aAAe,SAAMA,EAAO,eAAiB,KAChIC,EAAK,UAAYf,EAAM,SAAW,IAAMgB,GAAQ,MAAM,EAAG,GAAG,EAE9D,YAAK,qBAAqBD,CAAI,EACvBA,CACT,CAGA,0BAA0BP,EAAQ,GAAsB,CACtD,IAAM7D,EAAI,KAAK,oBAAoB,EACnC,GAAI,CAACC,EAAWD,CAAC,EAAG,MAAO,CAAC,EAE5B,IAAM+D,EADM3D,EAAaJ,EAAG,OAAO,EACjB,MAAM;AAAA,CAAI,EAAE,OAAO,OAAO,EACtCsE,EAAwB,CAAC,EAC/B,QAAS,EAAIP,EAAM,OAAS,EAAG,GAAK,GAAKO,EAAI,OAAST,EAAO,IAC3D,GAAI,CACF,IAAMxD,EAASmD,EAAqB,UAAU,KAAK,MAAMO,EAAM,CAAC,CAAC,CAAC,EAC9D1D,EAAO,SAASiE,EAAI,KAAKjE,EAAO,IAAI,CAC1C,MAAE,CAEF,CAEF,OAAOiE,CACT,CAcA,YAAYC,EAID,CAET,IAAMC,EAAU,CADHC,GAAwBF,EAAI,IAAI,EACtBA,EAAI,SAAW,EAAE,EAAE,KAAK,GAAQ,EACvD,OAAOG,GAAW,QAAQ,EAAE,OAAOF,CAAO,EAAE,OAAO,KAAK,EAAE,MAAM,EAAG,EAAE,CACvE,CAEA,WAAuB,CACrB,OAAO,gBAAgB,KAAK,MAAM,CAAC,CACrC,CAEA,UAAUtD,EAAuB,CAC/B,IAAMb,EAASsE,EAAgB,UAAUzD,CAAI,EAC7C,GAAI,CAACb,EAAO,QACV,MAAM,IAAI,MAAM,mCAAmCA,EAAO,MAAM,OAAO,IAAKE,GAAMA,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,EAE3G,KAAK,YAAY,KAAK,UAAU,EAAGF,EAAO,IAAI,CAChD,CAGQ,OAAmB,CACzB,IAAML,EAAI,KAAK,UAAU,EACzB,OAAKC,EAAWD,CAAC,EACV,KAAK,WAAWA,EAAIG,GAAQ,CACjC,IAAME,EAASsE,EAAgB,UAAU,KAAK,MAAMxE,CAAG,CAAC,EACxD,GAAI,CAACE,EAAO,QACV,MAAM,IAAI,MAAM,WAAWL,MAAMK,EAAO,MAAM,OAAO,IAAKE,GAAMA,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,EAEzF,OAAOF,EAAO,IAChB,CAAC,EAP0B,CAAE,QAAS,EAAG,QAAS,CAAC,CAAE,CAQvD,CAEA,eAAeuE,EAA0C,CACvD,IAAMC,EAAQ,KAAK,MAAM,EAAE,QAAQD,CAAE,EACrC,OAAOC,GAAS,gBAAgBA,CAAK,CACvC,CAEA,eAAeD,EAAYC,EAAkE,CAI3F,IAAMxE,EAASyE,EAAuB,UAAU,CAC9C,GAAGD,EACH,YAAaD,EACb,UAAW,IAAI,KAAK,EAAE,YAAY,CACpC,CAAC,EACD,GAAI,CAACvE,EAAO,QACV,MAAM,IAAI,MAAM,yCAAyCA,EAAO,MAAM,OAAO,IAAKE,GAAMA,EAAE,OAAO,EAAE,KAAK,IAAI,GAAG,EAEjH,IAAMqC,EAAU,KAAK,MAAM,EAC3B,KAAK,YAAY,KAAK,UAAU,EAAG,CAAE,GAAGA,EAAS,QAAS,CAAE,GAAGA,EAAQ,QAAS,CAACgC,CAAE,EAAGvE,EAAO,IAAK,CAAE,CAAC,CACvG,CAGA,eAAesC,EAAYM,EAAyC,CAClE,IAAMlC,EAAS,KAAK,WAAW,EACzBG,EAAO,KAAK,UAAU,EACtBO,EAAOP,EAAK,MAAM,KAAMc,GAAMA,EAAE,KAAOW,CAAE,EAC/C,GAAI,CAAClB,EAAM,MAAM,IAAI,MAAM,mBAAmBkB,GAAI,EAClD,IAAMoC,EAAqB,CAAE,GAAGtD,EAAM,KAAAwB,CAAK,EAC3C,YAAK,aAAa8B,EAAShE,EAAQG,EAAK,MAAM,IAAKc,GAAOA,EAAE,KAAOW,EAAKoC,EAAU/C,CAAE,CAAC,EACrFd,EAAK,MAAQA,EAAK,MAAM,IAAKc,GAAOA,EAAE,KAAOW,EAAKoC,EAAU/C,CAAE,EAC9D,KAAK,UAAUd,CAAI,EACZ6D,CACT,CAIA,WAAWpC,EAAkB,CAC3B,IAAMzB,EAAO,KAAK,UAAU,EACtB8D,EAAQ9D,EAAK,MAAM,KAAMc,GAAMA,EAAE,WAAaW,CAAE,EACtD,GAAIqC,EACF,MAAM,IAAI,MAAM,kBAAkBrC,yBAAqBqC,EAAM,wBAAwB,EAEvF,IAAMC,EAAY/D,EAAK,MAAM,KAAMc,GACjC,OAAO,OAAOA,EAAE,IAAI,EAAE,KAAMD,GAAMA,IAAMY,CAAE,CAC5C,EACA,GAAIsC,EACF,MAAM,IAAI,MAAM,kBAAkBtC,mBAAesC,EAAU,+BAA+B,EAE5F/D,EAAK,MAAQA,EAAK,MAAM,OAAQc,GAAMA,EAAE,KAAOW,CAAE,EACjD,KAAK,UAAUzB,CAAI,CACrB,CAKA,sBAAsBqC,EAAyB,CAC7C,GAAI,KAAK,eAAeA,EAAE,OAAO,EAAG,OAEpC,IAAMxC,EAAS,KAAK,WAAW,EACzBmE,EAAQ,IAAI,IAAI,KAAK,UAAU,EAAE,MAAM,IAAK,GAAM,EAAE,EAAE,CAAC,EAC7D,QAAS3E,EAAI,EAAGA,EAAIgD,EAAE,KAAK,OAAQhD,IAAK,CACtC,IAAMoC,EAAKY,EAAE,KAAKhD,CAAC,EACnB,GAAI2E,EAAM,IAAIvC,CAAE,EAAG,SACnB,IAAMhB,EAAQZ,EAAO,OAAOR,CAAC,GAAG,GAChC,GAAI,CAACoB,EAAO,MACZ,IAAMsB,EAAOM,EAAE,eAAeZ,CAAE,GAAK,CAAC,EAChCQ,EAAW5C,IAAM,EAAI,KAAOgD,EAAE,KAAKhD,EAAI,CAAC,EAC9C,KAAK,QAAQ,CACX,GAAAoC,EACA,MAAAhB,EACA,SAAAwB,EACA,KAAAF,EACA,UAAW,IAAI,KAAK,EAAE,YAAY,EAClC,UAAWM,EAAE,OACf,CAAC,EACD2B,EAAM,IAAIvC,CAAE,EAEd,KAAK,eAAeY,EAAE,QAAS,CAAE,KAAMA,EAAE,KAAM,KAAMA,EAAE,IAAK,CAAC,EAC7D,KAAK,qBAAqB,CAAE,GAAGA,EAAG,OAAQ,WAAY,GAAI,IAAI,KAAK,EAAE,YAAY,CAAE,CAAC,CACtF,CAGA,qBAAqBA,EAAyB,CAC5C,KAAK,qBAAqB,CAAE,GAAGA,EAAG,OAAQ,WAAY,GAAI,IAAI,KAAK,EAAE,YAAY,CAAE,CAAC,CACtF,CACF,EAGA,SAASvC,EAAcqB,EAAcb,EAAqB,CACxDzB,EAAU4D,EAAQtB,CAAI,EAAG,CAAE,UAAW,EAAK,CAAC,EAC5C,IAAM8C,EAAM,GAAG9C,SAAY,QAAQ,MACnC5B,EAAc0E,EAAK,KAAK,UAAU3D,EAAM,KAAM,CAAC,CAAC,EAChD4D,GAAWD,EAAK9C,CAAI,CACtB,CAMA,SAASvB,GAA2B,CAClC,IAAM4B,EAAM,IAAI,KAAK,EAAE,YAAY,EAC7B2C,EAA4B,CAAC,EAC7BC,EAAW,IAAI,IACrB,QAAWvD,KAAKwD,EACTD,EAAS,IAAIvD,EAAE,QAAQ,IAC1BsD,EAAM,KAAK,CACT,GAAItD,EAAE,SACN,MAAO,WACP,SAAU,KACV,KAAM,CAAE,KAAMA,EAAE,SAAU,YAAayD,EAAsBzD,EAAE,QAAQ,CAAE,EACzE,UAAWW,EACX,UAAW,SACb,CAAC,EACD4C,EAAS,IAAIvD,EAAE,QAAQ,GAG3B,QAAWA,KAAKwD,EACdF,EAAM,KAAK,CACT,GAAItD,EAAE,KACN,MAAO,OACP,SAAUA,EAAE,SACZ,KAAM,CAAE,KAAMA,EAAE,KAAM,GAAIA,EAAE,YAAc,CAAE,YAAaA,EAAE,WAAY,EAAI,CAAC,CAAG,EAC/E,UAAWW,EACX,UAAW,SACb,CAAC,EAEH,MAAO,CAAE,QAAS,EAAG,MAAA2C,CAAM,CAC7B,CAQO,SAASZ,GAAwBgB,EAAsB,CAC5D,OAAOA,EACJ,YAAY,EACZ,QAAQ,kBAAmB,OAAO,EAClC,QAAQ,iBAAkB,OAAO,EACjC,QAAQ,oBAAqB,MAAM,EACnC,QAAQ,OAAQ,OAAO,EACvB,QAAQ,qBAAsB,GAAG,EACjC,QAAQ,OAAQ,GAAG,EACnB,KAAK,EACL,MAAM,EAAG,GAAG,CACjB,CCvoBAC,KAOAC,KACAC,IATA,OAAS,cAAAC,OAAkB,SCA3BC,IACAC,IA0BO,IAAMC,EAAmB,cAKnBC,GAA6B,GAC7BC,GAAyB,GAMzBC,GAA0B,IAqBhC,SAASC,GAAgBC,EAAoC,CAClE,MAAO,CACL,QAASC,GAAKD,EAAM,QAAS,GAAI,EACjC,QAASA,EAAM,SAAW,KAC1B,OAAQA,EAAM,QAAU,KACxB,MAAOA,EAAM,OAAS,IACxB,CACF,CAIO,SAASE,GAAWC,EAAoBC,EAAsB,CACnE,IAAMC,EAA0C,CAAC,EACjD,QAAWC,KAAKH,EAAOE,EAASC,EAAE,EAAE,EAAIC,GAASD,CAAC,EAClD,OAAOE,EAAOH,EAAUD,CAAY,CACtC,CAEO,SAASK,GAAkBC,EAAyB,CAGzD,IAAMC,EAAYD,EAAW,IAAKJ,GAAM,CACtC,IAAMM,EAAYC,EAA6DP,EAAE,EAAE,EACnF,OAAOA,EAAE,MAAM,aAAa,KAAK,GAAK,CAACM,EAAWN,EAAI,CAAE,GAAGA,EAAG,KAAM,CAAE,GAAGA,EAAE,KAAM,YAAaM,CAAS,CAAE,CAC3G,CAAC,EACD,MAAO,CACL,SAAUV,GACRS,EACA,6GACF,CACF,CACF,CAEO,SAASG,GAAcC,EAAoB,CAChD,MAAO,CACL,KAAMb,GACJa,EACA,4LACF,CACF,CACF,CAaA,eAAsBC,EACpBhB,EACAG,EACAc,EAA8B,CAAC,EACK,CACpC,GAAIC,EAAYvB,CAAgB,IAAM,MAAO,OAAO,KACpD,IAAMe,EAAaP,EAAM,OAAQG,GAAMA,EAAE,WAAa,IAAI,EAC1D,GAAII,EAAW,OAAS,EAAG,OAAO,KAElC,IAAMS,EAAQpB,GAAgBC,CAAK,EAC7BoB,EAAW,CAAE,MAAOpB,EAAM,OAAS,UAAW,QAASA,EAAM,SAAW,SAAU,EAClFqB,EAAQrB,EAAM,MAAQ,CAAC,CAAE,KAAM,QAAS,GAAIA,EAAM,KAAM,CAAC,EAAI,OAE7DsB,EAAS,MAAMC,EAAQ5B,EAAkBwB,EAAOV,GAAkBC,CAAU,EAAG,CACnF,UAAWO,EAAK,YAAY,CAAC,EAAI,CAAE,SAAUA,EAAK,UAAU,CAAC,CAAE,EAAI,OACnE,SAAU,CAAE,GAAGG,EAAU,MAAO,UAAW,EAC3C,MAAAC,CACF,CAAC,EACD,GAAI,CAACC,EAAQ,OAAO,KACpB,IAAME,EAAMF,EAAO,QAAQ,SACvBG,EAAaD,EAAI,KACjBE,EAAYF,EAAI,MAAQ5B,GACtB+B,EAAO,CAACH,EAAI,MAAM,EAElBT,EAAQZ,EAAM,OAAQG,GAAMA,EAAE,WAAakB,EAAI,MAAM,EAC3D,GAAIT,EAAM,QAAU,EAAG,CACrB,IAAMa,EAAeX,EAAK,YAAY,CAAC,IAAMO,EAAI,OAC3CK,EAAS,MAAMN,EAAQ5B,EAAkB,CAAE,GAAIwB,EAAsC,SAAUK,EAAI,MAAO,EAAGV,GAAcC,CAAK,EAAG,CAGvI,UAAWa,GAAgBX,EAAK,YAAY,CAAC,EAAI,CAAE,KAAMA,EAAK,UAAU,CAAC,CAAE,EAAI,OAC/E,SAAU,CAAE,GAAGG,EAAU,MAAO,OAAQ,SAAUI,EAAI,MAAO,EAC7D,MAAAH,CACF,CAAC,EACD,GAAI,CAACQ,EAAQ,OAAO,KACpB,IAAMC,EAAOD,EAAO,QAAQ,KAC5BH,EAAYA,GAAaI,EAAK,MAAQjC,IAClCiC,EAAK,MAAQhC,IAA2B,CAAC4B,KAC3CD,EAAa,KAAK,IAAIA,EAAYK,EAAK,IAAI,EAC3CH,EAAK,KAAKG,EAAK,MAAM,QAEdf,EAAM,SAAW,GAC1BY,EAAK,KAAKZ,EAAM,CAAC,EAAE,EAAE,EAGvB,MAAO,CAAE,KAAAY,EAAM,WAAAF,EAAY,UAAAC,EAAW,KAAMJ,EAAO,IAAK,CAC1D,CAEA,SAASf,GAASD,EAA6B,CAC7C,IAAMyB,EAAIzB,EAAE,MAAM,aAAa,KAAK,GAAK,OACnC0B,EAAU1B,EAAE,MAAM,MAAM,KAAK,EAE7B2B,EAAOD,GAAWA,IAAY1B,EAAE,GAAK0B,EAAU,OACrD,OAAID,GAAKE,EAAa,GAAGA,MAASF,IAC3BA,GAAKE,GAAQ,IACtB,CAEA,SAAShC,GAAKiC,EAAWC,EAAqB,CAC5C,OAAOD,EAAE,OAASC,EAAM,GAAGD,EAAE,MAAM,EAAGC,CAAG,UAAOD,CAClD,CDjFO,IAAME,EAAN,KAAiB,CACd,MACA,UACA,MACA,WACA,YACA,qBACA,gBACA,uBACA,iBACA,IAER,YAAYC,EAAyB,CACnC,KAAK,MAAQA,EAAK,MAClB,KAAK,UAAYA,EAAK,UAAU,QAAQ,OAAQ,EAAE,EAClD,KAAK,MAAQA,EAAK,MAClB,KAAK,WAAaA,EAAK,WACvB,KAAK,YAAcA,EAAK,uBAAyB,EACjD,KAAK,qBAAuBA,EAAK,sBAAwB,gBACzD,KAAK,gBAAkBA,EAAK,iBAAmB,4BAC/C,KAAK,uBAAyBA,EAAK,oBAAsB,cACzD,KAAK,IAAMA,EAAK,KAAO,QAAQ,MAAM,KAAK,QAAS,cAAc,CACnE,CAMQ,aAA6B,CACnC,OAAK,KAAK,mBACR,KAAK,iBAAmBC,EAAe,KAAK,sBAAsB,GAE7D,KAAK,gBACd,CAEA,MAAM,SAASC,EAAsD,CACnE,IAAMC,EAAK,KAAK,MAAM,YAAY,CAChC,KAAMD,EAAM,KACZ,QAASA,EAAM,QACf,OAAQA,EAAM,MAChB,CAAC,EAKKE,EAAS,KAAK,MAAM,eAAeD,CAAE,EAC3C,GAAIC,EACF,YAAK,MAAM,qBAAqB,CAC9B,GAAI,IAAI,KAAK,EAAE,YAAY,EAC3B,QAASD,EACT,QAASD,EAAM,QACf,QAASA,EAAM,QACf,OAAQA,EAAM,OACd,OAAQA,EAAM,OACd,KAAME,EAAO,KACb,aAAc,CAAC,EACf,KAAMA,EAAO,KACb,OAAQ,QACR,OAAQ,WACR,QAASF,EAAM,KAAK,MAAM,EAAG,GAAG,CAClC,CAAC,EACM,CACL,KAAME,EAAO,KACb,OAAQC,EAASD,EAAO,IAAI,EAC5B,UAAWE,EAAUF,EAAO,KAAM,KAAK,MAAM,UAAU,EAAE,KAAK,EAC9D,KAAM,CAAC,EACP,KAAMA,EAAO,KACb,OAAQ,QACR,OAAQ,UACV,EAQF,IAAMG,EAAS,KAAK,MAAM,WAAW,EAC/BC,EAAQ,KAAK,MAAM,UAAU,EAAE,MAC/BC,EAAW,MAAM,KAAK,QAAQP,EAAOK,EAAQC,CAAK,EACxD,GAAI,CAACC,EAAU,OAAO,KAItB,GAAM,CAAE,KAAAC,EAAM,aAAAC,EAAc,WAAAC,EAAY,KAAAC,CAAK,EAAIJ,EACjD,GAAIC,EAAK,SAAW,EAAG,OAAO,KAE9B,IAAMI,EAAiC,CACrC,GAAI,IAAI,KAAK,EAAE,YAAY,EAC3B,QAASX,EACT,QAASD,EAAM,QACf,QAASA,EAAM,QACf,OAAQA,EAAM,OACd,OAAQA,EAAM,OACd,KAAAQ,EACA,aAAAC,EACA,KAAAE,EACA,OAAQJ,EAAS,OACjB,OAAQ,UACR,WAAAG,EACA,QAASV,EAAM,KAAK,MAAM,EAAG,GAAG,CAClC,EAgBMa,EAAc,IAAI,IAAIP,EAAM,IAAKQ,GAAMA,EAAE,EAAE,CAAC,EAC5CC,EAAiBP,EACpB,IAAI,CAACQ,EAAIC,IAASJ,EAAY,IAAIG,CAAE,EAAI,GAAKC,CAAI,EACjD,OAAQA,GAAQA,GAAO,CAAC,EAEvBC,EAAc,GAClB,GAAI,KAAK,uBAAyB,MAChCA,EAAc,WACL,KAAK,uBAAyB,gBAAiB,CACxD,IAAMC,EAAcJ,EAAe,SAAW,EACxCK,EACJL,EAAe,SAAW,GAAKA,EAAe,CAAC,IAAMP,EAAK,OAAS,EACrEU,EAAcC,GAAeC,EAE/B,IAAMC,EAAeX,IAAe,QAAaA,GAAc,KAAK,YAEhEY,EAAiC,UACrC,GAAIJ,GAAeG,EAKjB,GAAI,CACF,KAAK,eAAeb,EAAMC,EAAcJ,EAAQC,EAAON,EAAM,MAAM,EACnE,KAAK,MAAM,eAAeC,EAAI,CAAE,KAAAO,EAAM,KAAAG,CAAK,CAAC,EAC5CW,EAAS,UACX,OAASC,EAAP,CACA,KAAK,IACH,kDACA,QAAQ,KAAK,UAAUf,CAAI,IAC3B,WAAWe,GAAG,SAAWA,GAAG,SAAS,EAAE,MAAM,EAAG,GAAG,GACrD,CACF,CAEF,OAAAX,EAAe,OAASU,EACxB,KAAK,MAAM,qBAAqBV,CAAc,EAEvC,CACL,KAAAJ,EACA,OAAQL,EAASK,CAAI,EACrB,UAAWJ,EAAUI,EAAM,KAAK,MAAM,UAAU,EAAE,KAAK,EACvD,KAAMC,EACN,KAAAE,EACA,OAAQJ,EAAS,OACjB,OAAAe,EACA,WAAAZ,CACF,CACF,CAcA,MAAc,QACZV,EACAK,EACAC,EAC0B,CAC1B,IAAMkB,EAAOC,EAAYC,CAAgB,EACnCC,EAAY,CAAE,QAAS3B,EAAM,KAAM,QAASA,EAAM,QAAS,OAAQA,EAAM,OAAQ,MAAOA,EAAM,OAAQ,EAE5G,GAAIwB,IAAS,SAAU,CACrB,IAAMI,EAAU,MAAMC,EAAmBF,EAAWrB,CAAK,EAAE,MAAOiB,IAChE,KAAK,IAAI,2BAA4BA,GAAG,SAAWA,CAAC,EAC7C,KACR,EACD,GAAIK,GAAS,UACX,MAAO,CAAE,KAAMA,EAAQ,KAAM,aAAc,CAAC,EAAG,KAAM,CAAC,EAAG,WAAYA,EAAQ,WAAY,OAAQ,MAAO,EAI5G,IAAME,EAAS,MAAM,KAAK,YAAY9B,EAAOK,EAAQC,CAAK,EAAE,MAAOiB,IACjE,KAAK,IAAI,uBAAwBA,GAAG,SAAWA,CAAC,EACzC,KACR,EACD,OAAIC,IAAS,UAAYM,GAClBD,EAAmBF,EAAWrB,EAAO,CAAE,UAAWwB,EAAO,IAAK,CAAC,EAAE,MAAM,IAAM,CAAC,CAAC,EAE/EA,EAAS,CAAE,GAAGA,EAAQ,OAAQ,KAAM,EAAI,IACjD,CAKA,MAAc,YACZ9B,EACAK,EACAC,EAMQ,CACR,IAAMyB,EAAiBzB,EAAM,IAAKQ,IAAO,CACvC,GAAIA,EAAE,GACN,MAAOA,EAAE,MACT,SAAUA,EAAE,SACZ,KAAMA,EAAE,IACV,EAAE,EACIkB,EAAa,CACjB,qEACA,0EACA,GACA,mDACA,4EACA,4EACA,wDACA,8EACA,sEACA,GACA,wDACA,UACA,KAAK,UAAU3B,EAAQ,KAAM,CAAC,EAC9B,MACA,GACA,uEACA,sBACA,UACA,KAAK,UAAU0B,EAAgB,KAAM,CAAC,EACtC,MACA,GACA,WACA,MACA/B,EAAM,KAAK,MAAM,EAAG,GAAI,EACxB,MACA,YAAYA,EAAM,SAAW,MAC7B,WAAWA,EAAM,QAAU,MAC3B,GACA,QACA,wEACA,sDACA,6EACA,yEACA,4EACA,0EACA,yCACA,GACA,6BACA,qFACA,mEACA,2EACA,4EACA,sDACA,GACA,yDACA,sDACA,uDACA,uEACA,GACA,2DACA,sJACA,2DACF,EAAE,KAAK;AAAA,CAAI,EAULiC,EAAO,MAAM,KAAK,aAAaD,CAAU,EACzCE,EAASC,EAAYF,CAAI,EAC/B,GAAI,CAACC,GAAU,CAAC,MAAM,QAAQA,EAAO,IAAI,EAAG,OAAO,KAQnD,IAAME,EAAa,yBACbC,EAAU,IAAI,IACd7B,EAAiB0B,EAAO,KAC3B,OAAQI,GAA4B,OAAOA,GAAM,UAAYA,EAAE,OAAS,CAAC,EACzE,IAAKA,GAAc,CAClB,IAAMC,EAAUD,EAAE,KAAK,EACjBE,EAAUC,GAAcF,CAAO,EACrC,OAAIA,IAAYC,GAASH,EAAQ,IAAIE,EAASC,CAAO,EAC9CA,CACT,CAAC,EACA,OAAQF,GAAcF,EAAW,KAAKE,CAAC,CAAC,EAC3C,GAAI9B,EAAK,SAAW,EAAG,OAAO,KAE9B,IAAMC,EAAuD,CAAC,EAC9D,GAAIyB,EAAO,cAAgB,OAAOA,EAAO,cAAiB,SACxD,OAAW,CAACQ,EAAWC,CAAI,IAAK,OAAO,QAAQT,EAAO,YAAmC,EAAG,CAC1F,GAAI,CAACS,GAAQ,OAAOA,GAAS,SAAU,SACvC,IAAMC,EAASP,EAAQ,IAAIK,CAAS,GAAKD,GAAcC,CAAS,EAChE,GAAI,CAACE,EAAQ,SACb,IAAMC,EAAgC,CAAC,EACvC,OAAW,CAACC,EAAGC,CAAC,IAAK,OAAO,QAAQJ,CAAI,EAClC,OAAOI,GAAM,WAAUF,EAAMC,CAAC,EAAIC,GAKpC,CAACF,EAAM,MAAQH,IAAcE,IAAQC,EAAM,KAAOH,EAAU,KAAK,GACrEjC,EAAamC,CAAM,EAAIC,EAI3B,IAAMlC,EAA4C,CAAC,EAC/CuB,EAAO,MAAQ,OAAOA,EAAO,MAAS,WACpC,OAAOA,EAAO,KAAK,OAAU,WAAUvB,EAAK,MAAQuB,EAAO,KAAK,OAChE,OAAOA,EAAO,KAAK,QAAW,WAAUvB,EAAK,OAASuB,EAAO,KAAK,SAGxE,IAAMxB,EAAa,OAAOwB,EAAO,YAAe,SAC5C,KAAK,IAAI,EAAG,KAAK,IAAI,EAAGA,EAAO,UAAU,CAAC,EAC1C,OAEJ,MAAO,CAAE,KAAA1B,EAAM,aAAAC,EAAc,WAAAC,EAAY,KAAAC,CAAK,CAChD,CAKQ,eACNH,EACAC,EACAJ,EACA2C,EACAC,EACM,CACN,KAAK,MAAM,qBAAqBzC,EAAMC,EAAcJ,EAAQ4C,CAAS,CACvE,CAQA,MAAc,aAAajB,EAAqC,CAC9D,IAAMkB,EAAW,KAAK,YAAY,EAC5BC,EAAK,IAAI,gBACTC,EAAQ,WAAW,IAAMD,EAAG,MAAM,EAAG,GAAM,EACjD,GAAI,CAYF,OAXY,MAAMD,EAAS,SACzB,CACE,CACE,KAAM,SACN,QACE,yGACJ,EACA,CAAE,KAAM,OAAQ,QAASlB,CAAW,CACtC,EACA,CAAE,MAAO,KAAK,gBAAiB,UAAW,IAAK,YAAamB,EAAG,OAAQ,KAAM,EAAK,CACpF,GACW,SAAW,EACxB,QAAE,CACA,aAAaC,CAAK,CACpB,CACF,CACF,EAKA,SAASX,GAAcY,EAAqB,CAC1C,IAAMf,EAAIe,EACP,KAAK,EACL,YAAY,EACZ,QAAQ,iBAAkB,GAAG,EAC7B,QAAQ,iBAAkB,EAAE,EAC5B,QAAQ,SAAU,GAAG,EACxB,OAAKf,EAEE,YAAY,KAAKA,CAAC,EAAIA,EAAI,KAAKA,IAAI,QAAQ,SAAU,EAAE,EAF/C,EAGjB,CAKO,SAASnC,EAASK,EAAwB,CAC/C,OAAO8C,GAAW,MAAM,EACrB,OAAO9C,EAAK,KAAK,GAAQ,CAAC,EAC1B,OAAO,KAAK,EACZ,MAAM,EAAG,EAAE,CAChB,CAGO,SAASJ,EAAUI,EAAgBF,EAA4B,CACpE,OAAOE,EACJ,IAAKQ,GAAO,CACX,IAAMF,EAAIR,EAAM,KAAMiD,GAAMA,EAAE,KAAOvC,CAAE,EACvC,OAAKF,IAEQA,EAAE,MAAM,MAAQA,EAAE,MAAM,OAAQE,CAE/C,CAAC,EACA,KAAK,UAAK,CACf,CAMO,SAASwC,GAAc,EAAaC,EAAqB,CAC9D,GAAI,EAAE,SAAW,GAAKA,EAAE,SAAW,EAAG,MAAO,GAC7C,IAAIC,EAAS,EACb,QAASC,EAAI,EAAGA,EAAI,KAAK,IAAI,EAAE,OAAQF,EAAE,MAAM,GACzC,EAAEE,CAAC,IAAMF,EAAEE,CAAC,EADgCA,IAC7BD,IAGrB,GAAIA,IAAW,EAAG,MAAO,GACzB,IAAME,EAAQ,KAAK,IAAI,EAAE,OAAQH,EAAE,MAAM,EACzC,OAAOC,EAASE,CAClB","names":["init_questions","WIKI_RERANK_SEAT","DEFAULT_SHORTLIST","DEFAULT_EXCERPT_CHARS","rerankState","input","budget","c","clip","rerankQuestions","candidates","criteria","choice","noul","toRanking","answers","best","scores","order","i","a","b","diff","text","max","flat","STARTER_SCHEMA","CATEGORY_DESCRIPTIONS","STARTER_VERB_NODES","readFileSync","writeFileSync","appendFileSync","existsSync","mkdirSync","renameSync","statSync","resolve","dirname","createHash","GraphStore","opts","resolve","mkdirSync","p","existsSync","STARTER_SCHEMA","raw","readFileSync","parsed","graphSchemaSchema","i","backup","writeFileSync","e","np","nbackup","seeded","seedNodesFile","schema","safeWriteJson","nodesFileSchema","file","parse","mtimeMs","size","statSync","hit","data","node","all","level","l","missing","a","v","n","target","levelIdx","parent","parentLevelIdx","path","proposedAxes","createdBy","added","skipped","now","id","current","cursor","j","priorId","priorNode","axes","chosenLevel","parentId","chosenIdx","prior","priorIdx","c","classificationSchema","issues","line","dirname","appendFileSync","limit","status","lines","byHash","b","msgHash","update","next","suffix","out","msg","payload","normalizeForFingerprint","createHash","indexFileSchema","fp","entry","fingerprintEntrySchema","updated","child","refHolder","known","tmp","renameSync","nodes","seenCats","STARTER_VERB_NODES","CATEGORY_DESCRIPTIONS","text","init_extract_json","init_providers","init_seat","createHash","init_questions","init_seat","INTENT_PATH_SEAT","INTENT_PATH_MIN_CATEGORY_P","INTENT_PATH_MIN_VERB_P","INTENT_PATH_FULL_VERB_P","intentPathState","input","clip","nodeChoice","nodes","instructions","criteria","n","describe","choice","categoryQuestions","categories","described","fallback","CATEGORY_DESCRIPTIONS","verbQuestions","verbs","proposePathViaSeat","opts","getSeatMode","state","features","links","stage1","askSeat","cat","confidence","confident","path","sameCategory","stage2","verb","d","rawName","name","s","max","Classifier","opts","createProvider","input","fp","cached","hashPath","pathLabel","schema","nodes","proposal","path","proposedAxes","confidence","leaf","classification","existingIds","n","newNodeIndices","id","idx","structureOk","isReuseOnly","isSingleLeafAddition","confidenceOk","status","e","mode","getSeatMode","INTENT_PATH_SEAT","seatInput","viaSeat","proposePathViaSeat","viaLlm","nodesForPrompt","userPrompt","text","parsed","extractJson","NODE_ID_RE","slugMap","s","trimmed","slugged","slugifyNodeId","rawNodeId","axes","nodeId","clean","k","v","_existing","createdBy","provider","ac","timer","raw","createHash","x","ancestryScore","b","shared","i","depth"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{B as Zs,a as Ds,b as Os,d as Ls,f as He,l as zs,s as Xs,u as Ys,z as Vs}from"./chunk-J3GXZQXY.js";import{a as Es}from"./chunk-GLQLCBJS.js";import{b as Ns}from"./chunk-6W74CHNW.js";import{a as Qs}from"./chunk-W4YR2RO2.js";import{b as Hs,c as Yt,d as Us,e as Bs,h as Ws}from"./chunk-VGI4EZTP.js";import{a as Ps}from"./chunk-
|
|
1
|
+
import{B as Zs,a as Ds,b as Os,d as Ls,f as He,l as zs,s as Xs,u as Ys,z as Vs}from"./chunk-J3GXZQXY.js";import{a as Es}from"./chunk-GLQLCBJS.js";import{b as Ns}from"./chunk-6W74CHNW.js";import{a as Qs}from"./chunk-W4YR2RO2.js";import{b as Hs,c as Yt,d as Us,e as Bs,h as Ws}from"./chunk-VGI4EZTP.js";import{a as Ps}from"./chunk-Z6OPM7EH.js";import{e as _s,f as Rs}from"./chunk-36AXN6ZB.js";import{a as zt,b as Fs}from"./chunk-J3SOEFRE.js";import{a as Xt,b as js}from"./chunk-SIU6STB2.js";import{e as qs,i as Gs,j as Ks,k as Js}from"./chunk-X5C5W3E4.js";import{c as ws}from"./chunk-HG6QA6EU.js";import{a as bs,c as xs}from"./chunk-MYVHVJWZ.js";import{a as Ss,b as Is,c as Ts,d as vs,l as ks}from"./chunk-AV7T3SYT.js";import{a as As,b as $s}from"./chunk-Y5MFVVTM.js";import{g as Cs,h as Ms}from"./chunk-JVSTKXMI.js";import{a as ve,c as ye}from"./chunk-2GIDVZ4H.js";import{s as hs,v as ut,w as ys,y as ue,z as Qt}from"./chunk-A35RTVMM.js";import{a as Fe,b as Te,f as ct}from"./chunk-72SZI2UW.js";import{a as Ie,b as Bt}from"./chunk-FCPTN6NF.js";import{c as rs,d as os}from"./chunk-QOYAX2FT.js";import{e as he}from"./chunk-DSYKYZMT.js";import{C as cs,E as Kt,G as Ki,J as us,N as Ji,T as ls,U as Jt,V as gs,W as Le,X as ps,Y as fs,_ as ms,ma as Qi}from"./chunk-Y5HBYSJW.js";import{f as Gt,n as Gi}from"./chunk-UIBUZ2N3.js";import{g as qt,h as qi}from"./chunk-BRK6M6LA.js";import{a as is,b as ie,c as as,d as ds,e as Wt}from"./chunk-2QNT5JDS.js";Qt();ct();var lt="tool-set",er=["answer","code","full"],tr={answer:["Read","Grep","Glob"],code:["Read","Grep","Glob","Edit","MultiEdit","Write","Bash"]},zi=.1;function sr(n){return{task:nr(n.message,2e3),agent:n.agent,channel:n.channel??null,...n.agentPrompt?{agentInstructions:nr(n.agentPrompt,600)}:{}}}var rr={bundle:Te({answer:"Read-only: the task can be done by reading, searching and listing files and then replying. It changes no file and runs no command (no tests, no git, no scripts).",code:"Change files or run commands in the workspace: edit or write files, run tests, builds, git or shell commands. Still all inside the workspace, with no web access, no sub-agents and no notebooks.",full:"Needs more than that, or it is not clear: fetching or searching the web, starting sub-agents, notebooks, or anything the other two do not clearly cover."},"Which set of built-in tools does an agent need to do this task completely? Pick the smallest set that is clearly enough.")};function or(n,e=zi){let s=n?.bundle?.probabilities;if(!s)return"full";let r=Vt(s.answer),o=Vt(s.code),i=Vt(s.full);return r===null||o===null||i===null?"full":o+i<e?"answer":i<e?"code":"full"}function ir(n){let e=[...n].filter(t=>!t.startsWith("mcp__"));return e.every(t=>tr.answer.includes(t))?"answer":e.every(t=>tr.code.includes(t))?"code":"full"}function ar(n,e){return er.indexOf(n)<er.indexOf(e)}function Vt(n){return typeof n=="number"&&Number.isFinite(n)?n:null}function nr(n,e){return n.length>e?`${n.slice(0,e)}\u2026`:n}var dr=!1;function cr(n,e){let t=ut(lt);if(t!=="off")return t==="active"&&!dr&&(dr=!0,e(`[decisions] seat "${lt}" is set to active, but only shadow is built: it records and changes nothing`)),ue(lt,sr(n),rr,{incumbent:{bundle:"full"},links:[{kind:"task",id:n.taskId}],features:{agent:n.agent,channel:n.channel??"unknown"}}).then(s=>{if(!s)return null;let r=s.answers,o=or(r),i=r.bundle?.probabilities??{};return{callId:s.callId,bundle:o,probabilities:i}}).catch(()=>null)}function ur(n,e,t){return n.then(s=>{if(!s||e.error||!e.eventsSeen)return;let r=[...e.toolUses.keys()],o=ir(r);ys(s.callId,"bundle",o,{kind:"outcome",labeledBy:"tool-use"});let i=a=>typeof s.probabilities[a]=="number"?s.probabilities[a].toFixed(2):"-";t(`[${e.agentId}] tool-set shadow: would start with ${s.bundle} (answer ${i("answer")}, code ${i("code")}, full ${i("full")}); run used ${r.filter(a=>!a.startsWith("mcp__")).join(", ")||"no built-in tool"} \u2192 needed ${o}`+(ar(s.bundle,o)?" \u2014 MISS":""))}).catch(()=>{})}Qt();ct();var lr="session-continuity",gr={continues:Fe("The new request continues the task the user was already working on in the previous request, rather than starting a separate one.",{true:`The new request advances, narrows, corrects or follows up on the previous request's task \u2014 including terse replies such as "and the cost?" or "why?" that mean nothing on their own.`,false:"The new request opens a subject that could be answered without having seen the previous request, even if it arrived seconds later or belongs to the same broad field."}),needsHistory:Fe("Does the new request refer to something stated earlier in the conversation?")};function pr(n){let e=(n.recentRequests??[]).filter(Boolean);return{newRequest:Zt(n.message,1200),previousRequest:n.previousMessage?Zt(n.previousMessage,800):null,...e.length?{earlierRequests:e.map(t=>Zt(t,400))}:{},minutesSinceLastTurn:n.minutesSinceLastTurn,turnsSoFar:n.turnCount,currentContextTokens:n.lastTurnContextTokens,agent:n.agentId,channel:n.channel}}function fr(n,e){if(e.mechanicalRotation)return!1;let t=e.maxContinuity??.2,s=n.continues.noul,r=n.needsHistory.noul;return s<=t&&r<=t}function Zt(n,e){let t=String(n??"");return t.length>e?t.slice(0,e):t}import{execa as xt}from"execa";import{spawn as Xi}from"node:child_process";import{createHash as Yi}from"node:crypto";import{createInterface as Vi}from"node:readline";var le=class extends Error{},Zi=n=>Xi("codex",["app-server",...n.args],{cwd:n.cwd,env:n.env,stdio:["pipe","pipe","pipe"]}),en=class{child;pending=new Map;nextId=0;dead=!1;busy=!1;threadId;idle;notify;failTurn;constructor(e,t){this.child=t(e),this.child.stderr.resume(),Vi({input:this.child.stdout}).on("line",r=>{let o;try{o=JSON.parse(r)}catch{return}if(o.method&&o.id!==void 0)this.write({id:o.id,error:{code:-32601,message:"Interactive requests are not supported by AgentX"}});else if(o.id!==void 0){let i=this.pending.get(o.id);if(!i)return;this.pending.delete(o.id),o.error?i.reject(new Error(o.error.message||"Codex RPC failed")):i.resolve(o.result)}else o.method&&this.notify?.(o.method,o.params)}),this.child.on("error",r=>this.close(r)),this.child.stdin.on("error",r=>this.close(r)),this.child.on("exit",()=>this.close(new Error("Codex app-server exited")))}write(e){this.dead||this.child.stdin.write(JSON.stringify(e)+`
|
|
2
2
|
`)}rpc(e,t){if(this.dead)return Promise.reject(new Error("Codex connection closed"));let s=++this.nextId;return new Promise((r,o)=>{this.pending.set(s,{resolve:r,reject:o}),this.write({id:s,method:e,params:t})})}close(e=new Error("Codex connection closed")){if(this.dead)return;this.dead=!0,clearTimeout(this.idle);for(let s of this.pending.values())s.reject(e);this.pending.clear(),this.failTurn?.(e),this.child.kill("SIGTERM"),setTimeout(()=>{this.child.exitCode===null&&this.child.kill("SIGKILL")},5e3).unref()}},tn=class{constructor(e=Zi,t=8,s=3e5){this.launchProcess=e;this.capacity=t;this.idleMs=s}entries=new Map;stop(){for(let{connection:e}of this.entries.values())e.close();this.entries.clear()}async run(e){let t=Date.now();if(e.signal?.aborted)return{content:"",error:"task cancelled by operator",duration:0};let s=Yi("sha256").update(JSON.stringify([e.cwd,e.model,e.bypass,e.args,Object.entries(e.env).sort(([b],[R])=>b.localeCompare(R))])).digest("hex"),r=this.entries.get(e.key);if(r?.connection.busy)throw new le("Conversation already running");r&&(r.fingerprint!==s||e.fresh||r.connection.dead)&&(r.connection.close(),this.entries.delete(e.key),r=void 0);let o=!!r;if(!r){if(this.entries.size>=this.capacity){let b=[...this.entries].find(([,R])=>!R.connection.busy);if(!b)throw new le("Codex warm process capacity reached");b[1].connection.close(),this.entries.delete(b[0])}try{r={fingerprint:s,connection:new en(e,this.launchProcess)}}catch{throw new le("Could not start Codex app-server")}this.entries.set(e.key,r)}let i=r.connection;i.busy=!0,clearTimeout(i.idle);let a=!1,c="",d,u,l,f,h=!1,p=()=>i.close(new Error("task cancelled by operator"));e.signal?.addEventListener("abort",p,{once:!0});let I=setTimeout(()=>i.close(new Error("Codex turn timed out")),e.timeoutMs),k=setTimeout(()=>i.close(new Error("Codex initialization timed out")),15e3);try{o||(await i.rpc("initialize",{clientInfo:{name:"agentx",version:"1.0.0"},capabilities:{}}),i.write({method:"initialized",params:{}})),i.threadId&&i.threadId!==e.resume&&(await i.rpc("thread/unsubscribe",{threadId:i.threadId}),i.threadId=void 0);let b={cwd:e.cwd,model:e.model,approvalPolicy:"never",sandbox:e.bypass?"danger-full-access":"workspace-write"};u=(await i.rpc(e.resume&&!e.fresh?"thread/resume":"thread/start",{...b,...e.resume&&!e.fresh?{threadId:e.resume}:{}})).thread.id,i.threadId=u,clearTimeout(k),e.onEvent?.({type:"codex.ready",reused:o,startupMs:Date.now()-t,model:e.model});let E=new Promise((O,P)=>{i.failTurn=P,i.notify=(D,N)=>{if(N?.threadId!==u)return;let L=N.item,B={agentMessage:"agent_message",commandExecution:"command_execution",fileChange:"file_change",mcpToolCall:"mcp_tool_call"};if(e.onEvent?.({...N,type:D.replace("/","."),...L?{item:{...L,type:B[L.type]||L.type}}:{}}),D==="item/completed"&&L?.type==="agentMessage"&&L.phase==="final_answer"&&(d=L.text),D==="item/agentMessage/delta"&&(h||(h=!0,e.onEvent?.({type:"codex.first_output",elapsedMs:Date.now()-t,reused:o})),c+=N.delta,e.onDelta?.(N.delta,c)),D==="thread/tokenUsage/updated"){let H=N.tokenUsage.last;f=H.inputTokens;let q=H.cachedInputTokens||0;l={inputTokens:(l?.inputTokens||0)+Math.max(0,(H.inputTokens||0)-q),outputTokens:(l?.outputTokens||0)+(H.outputTokens||0),cacheReadTokens:(l?.cacheReadTokens||0)+q,cacheCreateTokens:(l?.cacheCreateTokens||0)+(H.cacheWriteInputTokens||0)}}D==="turn/completed"&&(N.turn.status==="completed"?O():P(new Error(N.turn.error?.message||`Codex turn ${N.turn.status}`)))}});return E.catch(()=>{}),a=!0,await i.rpc("turn/start",{threadId:u,input:[{type:"text",text:e.prompt,text_elements:[]}]}),await E,{content:d??c,codexSessionId:u,billedModel:e.model,usage:l,contextTokens:f,duration:Date.now()-t}}catch(b){if(i.close(),!a&&!e.signal?.aborted)throw new le(b.message);return{content:c,codexSessionId:u,usage:l,contextTokens:f,error:b.message,duration:Date.now()-t}}finally{clearTimeout(k),clearTimeout(I),e.signal?.removeEventListener("abort",p),i.notify=void 0,i.failTurn=void 0,i.busy=!1,i.dead?this.entries.get(e.key)?.connection===i&&this.entries.delete(e.key):(i.idle=setTimeout(()=>{i.close(),this.entries.delete(e.key)},this.idleMs),i.idle.unref())}}},mr=new tn;import{execFile as xa}from"child_process";import{StringDecoder as qe}from"string_decoder";import{mkdtempSync as Er,readFileSync as Dr,rmSync as Or}from"fs";import{join as wt}from"path";import{tmpdir as Nr}from"os";function gt(n){let e=(n||"").trim();if(!e)return{message:"Unknown error from the model.",kind:"unknown",retryable:!1};if(/timed out after/i.test(e))return{message:e.slice(0,300),kind:"timeout",retryable:!0,raw:e};if(/not logged in|please run \/login/i.test(e))return{kind:"auth",retryable:!0,message:"The agent's Claude Code session is logged out.",fix:"Usually a transient OAuth refresh race under concurrent load \u2014 retry in a moment. If it persists, run `/login` in the agent's workspace.",raw:e};if(/specified API usage limits/i.test(e)){let d=e.match(/regain access on ([^.\n]+?)(?:\.|$)/i);return{kind:"usage_cap",retryable:!1,message:`Claude Code subscription /usage cap exhausted.${d?` Resets ${d[1].trim()}.`:""}`,fix:"Raise the cap at https://claude.ai/settings/usage (this is the user-set monthly limit on the OAuth account, not Anthropic API billing). If the daemon is using a different account than expected, check CLAUDE_CODE_OAUTH_TOKEN / ANTHROPIC_API_KEY in the spawn env \u2014 capped keys leak the same error.",raw:e}}if(/out of extra usage/i.test(e))return{kind:"overage_disabled",retryable:!0,message:"Claude Max-plan overage is unavailable (disabled or depleted at the org level).",fix:"Enable overage / extra usage at https://claude.ai/settings/usage (this is the subscription account's toggle \u2014 not Anthropic API billing). Warm sessions can still succeed via prompt cache; cold dispatches will keep failing until overage is re-enabled.",raw:e};if(/credit balance (is )?too low|out of credit|credit(s)? exhausted|insufficient credit/i.test(e))return{kind:"out_of_credits",retryable:!1,message:"The agent is out of Anthropic credits.",fix:"Top up at https://console.anthropic.com/settings/billing (or claude.ai/settings/usage for Max subscribers), then retry.",raw:e};let t=e.match(/\{.*\}\s*$/s)||e.match(/\{[\s\S]*\}/),s;if(t)try{s=JSON.parse(t[0])}catch{}let r=s?.error||s,o=typeof r?.type=="string"?r.type:void 0,i=typeof r?.message=="string"?r.message:void 0,a=e.match(/API Error:\s*(\d{3})/i),c=a?parseInt(a[1],10):void 0;return i&&/out of extra usage/i.test(i)?{kind:"overage_disabled",retryable:!0,message:"Claude Max-plan overage is unavailable (disabled or depleted at the org level).",fix:"Enable overage / extra usage at https://claude.ai/settings/usage (this is the subscription account's toggle \u2014 not Anthropic API billing). Warm sessions can still succeed via prompt cache; cold dispatches will keep failing until overage is re-enabled.",raw:e}:i&&/out of (extra )?usage|out of credit|credit(s)? exhausted|insufficient credit|credit balance (is )?too low|low credit balance/i.test(i)?{kind:"out_of_credits",retryable:!1,message:"The agent is out of Anthropic credits.",fix:"Top up at https://console.anthropic.com/settings/billing (or claude.ai/settings/usage for Max subscribers), then retry.",raw:e}:i&&/context|prompt is too long|too many tokens|maximum context length/i.test(i)?{kind:"context_too_large",retryable:!1,message:"The conversation has grown past what the model can hold.",fix:"Clear session history or switch the agent to a bigger context model.",raw:e}:o==="authentication_error"||i&&/authentication|invalid.*api.*key|no api key|not authenticated/i.test(i)?{kind:"auth",retryable:!1,message:"Authentication failed talking to Anthropic.",fix:"Check ANTHROPIC_API_KEY in .env (or the Claude Code login for claude-code tier agents).",raw:e}:o==="permission_error"||c===403?{kind:"permission",retryable:!1,message:i||"The API key doesn't have permission for this request.",raw:e}:o==="rate_limit_error"||c===429?{kind:"rate_limit",retryable:!0,message:"Anthropic's API rate-limited this request.",fix:"AgentX will retry automatically. If it recurs, lower the agent's concurrency or spread scheduled jobs.",raw:e}:o==="overloaded_error"||c===529||c===503?{kind:"overloaded",retryable:!0,message:"Anthropic's API is temporarily overloaded.",fix:"AgentX will retry in a moment.",raw:e}:o==="invalid_request_error"||c===400?{kind:"invalid_request",retryable:!1,message:i||"The model rejected the request as invalid.",raw:e}:c&&c>=500?{kind:"upstream_api",retryable:!0,message:i||`Anthropic API error (HTTP ${c}).`,fix:"Transient \u2014 retry. Check https://status.anthropic.com if it persists.",raw:e}:{kind:"unknown",retryable:!1,message:e.replace(/\s+/g," ").slice(0,220),raw:e}}function pt(n){return n.fix?`${n.message} \u2014 ${n.fix}`:n.message}function ft(n){if(!n||typeof n!="object")return;let e,t=-1,s=-1;for(let[r,o]of Object.entries(n)){let i=o&&typeof o=="object"?o:{},a=typeof i.costUSD=="number"?i.costUSD:0,c=(Number(i.inputTokens)||0)+(Number(i.outputTokens)||0)+(Number(i.cacheReadInputTokens)||0)+(Number(i.cacheCreationInputTokens)||0);(a>t||a===t&&c>s)&&(e=r,t=a,s=c)}return e}var hr=null;function mt(){return hr}function Cu(n){hr=n}function je(n){return`${n.agentId}:${n.channel}:${n.chatId}`}function ea(n,e){if(n.state()==="busy")return null;let s=n.snapshot(),r=n.opts?.model;return r&&e.model&&r!==e.model?`model changed (${r} -> ${e.model})`:e.resumeSessionId?s.claudeSessionId&&s.claudeSessionId!==e.resumeSessionId?`session changed (${s.claudeSessionId.slice(0,8)} -> ${e.resumeSessionId.slice(0,8)})`:null:s.claudeSessionId?"session rotated":null}var ke=class extends Error{constructor(t,s,r){super(t);this.scope=s;this.cap=r;this.name="RegistryCapExceeded"}},yr=class{handles=new Map;sweeper;handleWorkspace=new Map;cfg;constructor(e){this.cfg={maxProcessesGlobal:e.maxProcessesGlobal??64,maxProcessesPerAgent:e.maxProcessesPerAgent??16,idleTimeoutMs:e.idleTimeoutMs??3e4,staleTimeoutMs:e.staleTimeoutMs??45*60*1e3,sweepIntervalMs:e.sweepIntervalMs??5e3,factory:e.factory,log:e.log??(()=>{}),currentWorkspaceHash:e.currentWorkspaceHash}}start(){this.sweeper||(this.sweeper=setInterval(()=>this.sweepIdle(),this.cfg.sweepIntervalMs),typeof this.sweeper.unref=="function"&&this.sweeper.unref())}async stop(){this.sweeper&&(clearInterval(this.sweeper),this.sweeper=void 0);let e=Array.from(this.handles.values());this.handles.clear(),await Promise.allSettled(e.map(t=>t.kill("registry-stop")))}acquire(e,t){let s=je(e),r=this.handles.get(s);if(r&&r.state()!=="dead"){let a=ea(r,t);if(!a)return r.claim?.(),r;this.handles.delete(s),this.handleWorkspace.delete(s),this.cfg.log(`[process-registry] respawning ${s}: ${a}`),r.kill(a).catch(()=>{})}if(this.handles.size>=this.cfg.maxProcessesGlobal&&this.evictOldestIdle({scope:"global"}),this.countForAgent(e.agentId)>=this.cfg.maxProcessesPerAgent&&this.evictOldestIdle({scope:"agent",agentId:e.agentId}),this.handles.size>=this.cfg.maxProcessesGlobal)throw new ke(`process registry at global cap (${this.cfg.maxProcessesGlobal}) and no idle handle to evict`,"global",this.cfg.maxProcessesGlobal);if(this.countForAgent(e.agentId)>=this.cfg.maxProcessesPerAgent)throw new ke(`process registry at per-agent cap for ${e.agentId} (${this.cfg.maxProcessesPerAgent}) and no idle handle to evict`,"agent",this.cfg.maxProcessesPerAgent);let i=this.cfg.factory.spawn(e,t);return this.handles.set(s,i),this.handleWorkspace.set(s,t.workspace),this.cfg.log(`[process-registry] spawned ${s}`),i}release(e,t={}){let s=je(e),r=this.handles.get(s);r&&(this.handles.delete(s),this.handleWorkspace.delete(s),t.kill&&r.kill(t.reason??"release"))}list(){return Array.from(this.handles.values()).map(e=>e.snapshot())}hasLive(e){let t=je(e),s=this.handles.get(t);return s!=null&&s.state()!=="dead"}async kill(e,t="operator"){let s=je(e),r=this.handles.get(s);r&&(this.handles.delete(s),this.handleWorkspace.delete(s),await r.kill(t))}countForAgent(e){let t=0;for(let s of this.handles.values())s.key.agentId===e&&s.state()!=="dead"&&(t+=1);return t}evictOldestIdle(e){let t,s=Number.POSITIVE_INFINITY;for(let o of this.handles.values()){if(o.state()!=="idle"||e.scope==="agent"&&o.key.agentId!==e.agentId)continue;let i=o.snapshot().lastTurnAt;i<s&&(s=i,t=o)}if(!t)return!1;let r=je(t.key);return this.handles.delete(r),t.kill("evicted (cap reached)"),this.cfg.log(`[process-registry] evicted ${r} (cap)`),!0}sweepIdle(){let e=Date.now();for(let[t,s]of this.handles.entries()){let r=s.snapshot();if(s.state()==="dead"){this.handles.delete(t),this.handleWorkspace.delete(t),this.cfg.log(`[process-registry] reaped dead ${t}`);continue}if(s.state()!=="idle")continue;let o=e-r.lastTurnAt;if(o>=this.cfg.staleTimeoutMs){this.handles.delete(t),this.handleWorkspace.delete(t),s.kill(`stale (idle ${Math.round(o/1e3)}s)`),this.cfg.log(`[process-registry] killed stale ${t}`);continue}if(o>=this.cfg.idleTimeoutMs){this.handles.delete(t),this.handleWorkspace.delete(t),s.kill(`idle (${Math.round(o/1e3)}s)`),this.cfg.log(`[process-registry] killed idle ${t}`);continue}if(this.cfg.currentWorkspaceHash){let i=this.handleWorkspace.get(t);if(i){let a=null;try{a=this.cfg.currentWorkspaceHash(s.key,i)}catch(c){this.cfg.log(`[process-registry] drift-check failed for ${t}: ${c?.message||c}`);continue}if(a!==r.claudeMdHash){this.handles.delete(t),this.handleWorkspace.delete(t);let c=r.claudeMdHash??"unmarked",d=a??"unmarked";s.kill(`claude-md drifted (${c} \u2192 ${d})`),this.cfg.log(`[process-registry] killed drifted ${t} (${c} \u2192 ${d})`)}}}}}};import{spawn as ta}from"child_process";import{randomUUID as na}from"crypto";import{existsSync as sa,readFileSync as ra}from"fs";import{resolve as oa}from"path";function ia(n){let e=oa(n,"CLAUDE.md");if(!sa(e))return null;try{let t=ra(e,"utf8");return ks(t)}catch{return null}}var aa=20*60*1e3,da=5e3,Ue=class extends Error{constructor(t){super(`turn exceeded its ${Math.round(t/6e4)}m budget`);this.budgetMs=t}},Be=class extends Error{constructor(t){super(`claude process stopped mid-turn (${t})`);this.reason=t}};function ca(n,e){return n.AGENTX_AGENT_ID=e.agentId,n.AGENTX_CHANNEL=e.channel,n.AGENTX_CHAT_ID=e.chatId,delete n.AGENTX_TASK_ID,n}var wr=class{constructor(e={}){this.opts=e}spawn(e,t){return new nn(e,t,this.opts)}},nn=class{constructor(e,t,s){this.key=e;this.opts=t;this.factoryOpts=s;let r=s.binary??"claude",o=this.buildArgs(t),i=this.log=s.log??(()=>{});this.child=ta(r,o,{cwd:t.workspace,env:ye(ca({...process.env},e),t.billing),stdio:["pipe","pipe","pipe"]}),this.snap={key:e,pid:this.child.pid??null,claudeSessionId:t.resumeSessionId??null,state:"warm-cold",spawnedAt:Date.now(),lastTurnAt:Date.now(),turnCount:0,lastInputTokens:0,pendingTaskId:null,claudeMdHash:ia(t.workspace)},this.child.stdout.setEncoding("utf8"),this.child.stdout.on("data",a=>this.onStdout(a)),this.child.stderr.setEncoding("utf8"),this.child.stderr.on("data",a=>i(`[claude pid=${this.snap.pid}] stderr: ${a.trim()}`)),this.child.on("exit",a=>{this.exited=!0,this.exitCode=a,this.snap={...this.snap,state:"dead",deadReason:this.killReason??`exit=${a}`};let c=this.waiters.splice(0);for(let d of c)d(null)}),this.child.on("error",a=>{i(`[claude pid=${this.snap.pid}] spawn error: ${a.message}`),this.killReason=`spawn-error: ${a.message}`})}child;buf="";pendingEvents=[];waiters=[];exited=!1;exitCode=null;killReason;turnQueue=Promise.resolve();snap;log;state(){return this.snap.state}snapshot(){return{...this.snap}}async*runTurn(e){let t,s=new Promise(h=>{t=h}),r=this.turnQueue;if(this.turnQueue=r.then(()=>s),await r,this.exited||this.snap.state==="dead")throw t(),new Error(`claude process for ${this.key.agentId}:${this.key.chatId} is dead (${this.snap.deadReason??"?"})`);let o=Date.now();this.snap={...this.snap,state:"busy",pendingTaskId:e.taskId,lastTurnAt:o};let i=na(),a=JSON.stringify({type:"user",uuid:i,message:{role:"user",content:e.message}});this.child.stdin.write(a+`
|
|
3
3
|
`);let c=e.deadlineMs??aa,d=o+c,u=()=>(this.kill(`turn deadline (${Math.round(c/6e4)}m)`),new Ue(c)),l=!1,f=null;try{for(;;){let h=d-Date.now();if(h<=0)throw u();let p=await this.nextEvent(h);if(p===null&&this.killReason)throw new Be(this.killReason);if(p===null&&!this.exited)throw u();if(p===null)throw new Error(`claude process exited mid-turn (code=${this.exitCode}, reason=${this.snap.deadReason??"?"})`);if(!l){if(p.type==="user"&&p.uuid===i)l=!0,f&&(yield{type:f.type,raw:f});else if(p.type==="system"&&p.subtype==="init")f=p;else if(p.type==="result"){f=null;let I=typeof p.result=="string"?p.result:"";this.log(`[claude pid=${this.snap.pid}] dropped a reply no question asked for (${I.length} chars): ${I.slice(0,120)}`)}continue}if(yield{type:p.type,raw:p},p.type==="result"){this.onResultEvent(p);return}}}finally{let h=this.snap.state==="busy";this.snap={...this.snap,...h?{state:"idle",lastTurnAt:Date.now()}:{},pendingTaskId:null},t()}}claim(){this.snap.state!=="dead"&&(this.snap={...this.snap,lastTurnAt:Date.now()})}async kill(e){if(this.exited)return;this.killReason=e,this.snap={...this.snap,state:"dead",deadReason:e};try{this.child.kill("SIGTERM")}catch{}let t=this.waiters.splice(0);for(let r of t)r(null);let s=await new Promise(r=>{let o=setTimeout(()=>{try{this.child.kill("SIGKILL")}catch{}r(!0)},da);this.child.once("exit",()=>{clearTimeout(o),r(!1)})})}buildArgs(e){let t=["-p","--input-format","stream-json","--output-format","stream-json","--verbose","--replay-user-messages"];return e.model&&t.push("--model",e.model),e.permissionMode==="bypassPermissions"&&t.push("--dangerously-skip-permissions"),e.resumeSessionId&&t.push("--resume",e.resumeSessionId),e.systemPromptAppend&&e.systemPromptAppend.trim().length>0&&t.push("--append-system-prompt",e.systemPromptAppend),e.extraArgs&&t.push(...e.extraArgs),this.factoryOpts.extraArgs&&t.push(...this.factoryOpts.extraArgs),t}onStdout(e){this.buf+=e;let t;for(;(t=this.buf.indexOf(`
|
|
4
4
|
`))!==-1;){let s=this.buf.slice(0,t);if(this.buf=this.buf.slice(t+1),!s.trim())continue;let r;try{r=JSON.parse(s)}catch{continue}this.waiters.length>0?this.waiters.shift()(r):this.pendingEvents.push(r)}}nextEvent(e){return this.pendingEvents.length>0?Promise.resolve(this.pendingEvents.shift()):this.exited?Promise.resolve(null):new Promise(t=>{let s=!1,r=setTimeout(()=>{if(s)return;s=!0;let i=this.waiters.indexOf(o);i>=0&&this.waiters.splice(i,1),t(null)},e),o=i=>{s||(s=!0,clearTimeout(r),t(i))};this.waiters.push(o)})}onResultEvent(e){let t=e.usage??{},s=typeof e.session_id=="string"?e.session_id:this.snap.claudeSessionId;this.snap={...this.snap,lastTurnAt:Date.now(),turnCount:this.snap.turnCount+1,lastInputTokens:typeof t.input_tokens=="number"?t.input_tokens:this.snap.lastInputTokens,claudeSessionId:s,state:"idle"}}};Wt();Gi();Bt();import{resolve as br,join as xr}from"path";var ua=4e3;function ht(n){try{let e=xr(br(n.root),".agentx","db.sqlite"),t=Gt({path:e});if(!t)return null;try{t.pragma("busy_timeout = 3000")}catch{}let s=Ie(),r=n.input.command?n.input.command.slice(0,ua):n.input.filePath??null;return t.prepare(`INSERT INTO guardrail_decisions
|
|
@@ -27,7 +27,7 @@ ${n}`}function Ia(n,e,t,s,r){let o=["run","--format","json","--thinking"],i=t||n
|
|
|
27
27
|
`,l)}};O.stdout?.on("data",T=>{L+=H.write(T);let w=L.split(`
|
|
28
28
|
`);L=w.pop()||"";for(let _ of w)G(_)}),O.stderr?.on("data",T=>{B+=q.write(T)});let $=await O;if(i&&i.removeEventListener("abort",N),L+=H.end(),L.trim()&&G(L),B+=q.end(),P)return{content:"",error:D||"task cancelled by operator",errorKind:"cancelled",duration:Date.now()-c,opencodeSessionId:h};if($.exitCode!==0||p){let T=$,w;return T.timedOut?w=`OpenCode timed out after ${Math.round(R/6e4)}m. Bump agent.maxExecutionMinutes for "${n.name||"this agent"}" if tasks need longer.`:p?w=p:T.code==="ENOENT"||/ENOENT|spawn opencode/i.test((T.shortMessage||"")+" "+B)?w=Pr():w=B.trim()||`OpenCode exited with code ${T.exitCode??"unknown"}`,{content:"",...X(w),duration:Date.now()-c,usage:f,opencodeSessionId:h}}return k=!1,{content:l.trim(),duration:Date.now()-c,usage:f,billedModel:e.model||n.model,opencodeSessionId:h}}catch(R){if(i?.aborted)return{content:"",error:"task cancelled by operator",errorKind:"cancelled",duration:Date.now()-c};let E=/ENOENT|spawn opencode/i.test(R?.message||"")?Pr():R?.message;return{content:l,...X(E||"OpenCode failed"),duration:Date.now()-c,usage:f,opencodeSessionId:h}}finally{b?.(),I?.release(k)}}async function Ea(n,e,t,s){let r=Date.now();try{let o=await import("@anthropic-ai/claude-agent-sdk"),{query:i}=o,a=pe(n,e,s),c="",d=i({prompt:a,options:{model:e.model||n.model,cwd:n.workspace,permissionMode:"bypassPermissions"}});for await(let u of d)u.type==="result"&&u.subtype==="success"&&(c=u.result||"");return{content:c,duration:Date.now()-r}}catch(o){return{content:"",...X(`SDK error: ${o.message}`),duration:Date.now()-r}}}async function Da(n,e,t,s,r,o,i,a,c){let d=Date.now(),u=Math.max(6e4,(n.maxExecutionMinutes??20)*6e4),l=new AbortController,f=!1,h=()=>{let k=a.reason?.message||(typeof a.reason=="string"?a.reason:"task cancelled by operator");try{l.abort(new Error(k))}catch{}};a&&(a.aborted?h():a.addEventListener("abort",h,{once:!0}));let p=setTimeout(()=>{f=!0;try{l.abort(new Error(`orchestrator timed out after ${Math.round(u/6e4)}m`))}catch{}},u);p.unref?.();let I=()=>({content:"",...X(`Orchestrator timed out after ${Math.round(u/6e4)}m. Bump agent.maxExecutionMinutes for "${n.name||"this agent"}" if tasks need longer.`),duration:Date.now()-d});try{let k=n.provider||"claude-code",b=s?`${s}
|
|
29
29
|
|
|
30
|
-
${e.message}`:e.message,R=e.context?.sender,E=typeof R=="string"&&/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(R),O=e.context?.channel==="web-chat"&&E?{userId:R,agentId:e.agentId||"acme-public"}:void 0,P=vs(n),D=Object.keys(P).length>0,N={task:b,cwd:n.workspace,provider:k,model:e.model||n.model,apiKey:t,overwrite:!0,interactive:!1,context7:!1,acmeContext:O,providerOpts:o,abortSignal:l.signal,mcpServers:D?P:void 0};if(r){let{generateStream:G}=await import("./agent-
|
|
30
|
+
${e.message}`:e.message,R=e.context?.sender,E=typeof R=="string"&&/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(R),O=e.context?.channel==="web-chat"&&E?{userId:R,agentId:e.agentId||"acme-public"}:void 0,P=vs(n),D=Object.keys(P).length>0,N={task:b,cwd:n.workspace,provider:k,model:e.model||n.model,apiKey:t,overwrite:!0,interactive:!1,context7:!1,acmeContext:O,providerOpts:o,abortSignal:l.signal,mcpServers:D?P:void 0};if(r){let{generateStream:G}=await import("./agent-NGCPRTBH.js"),$="",T,w,_,x;for await(let v of G(N)){if(c)try{c(v)}catch{}if(v.type==="text_delta")$+=v.text,r(v.text,$);else if(v.type==="thinking_delta")v.text&&i&&i(v.text);else if(v.type==="generate_result"){if(v.result.content&&v.result.content!==$){let K=v.result.content.slice($.length);K?($=v.result.content,r(K,$)):$=v.result.content}T=v.result.tokensUsed,v.result.inputTokens!=null&&(w=v.result.inputTokens),v.result.outputTokens!=null&&(_=v.result.outputTokens)}else if(v.type==="done"){if(v.result.content&&v.result.content!==$){let K=v.result.content.slice($.length);K?($=v.result.content,r(K,$)):$=v.result.content}v.result.tokensUsed!=null&&(T=v.result.tokensUsed)}else v.type==="error"&&(x=v.error)}return x&&!$?f?I():x==="cancelled"||a?.aborted||l.signal.aborted?{content:"",error:"task cancelled by operator",errorKind:"cancelled",duration:Date.now()-d}:{content:"",...X(`Orchestrator error: ${x}`),duration:Date.now()-d}:((w===void 0||_===void 0)&&T&&(w=Math.round(T*.7),_=T-w),{content:$||"Done.",tokensUsed:T,usage:w!==void 0||_!==void 0?{inputTokens:w||0,outputTokens:_||0,cacheReadTokens:0,cacheCreateTokens:0}:void 0,duration:Date.now()-d})}let{generate:L}=await import("./agent-NGCPRTBH.js"),B=await L(N),H=B.inputTokens,q=B.outputTokens;if((H===void 0||q===void 0)&&B.tokensUsed){let G=B.tokensUsed;H=Math.round(G*.7),q=G-H}return{content:B.content||"Done.",tokensUsed:B.tokensUsed,usage:H!==void 0||q!==void 0?{inputTokens:H||0,outputTokens:q||0,cacheReadTokens:0,cacheCreateTokens:0}:void 0,duration:Date.now()-d}}catch(k){return f?I():k?.message==="cancelled"||a?.aborted||l.signal.aborted?{content:"",error:"task cancelled by operator",errorKind:"cancelled",duration:Date.now()-d}:{content:"",...X(`Orchestrator error: ${k.message}`),duration:Date.now()-d}}finally{clearTimeout(p),a&&a.removeEventListener("abort",h)}}var Oa=1e3;async function Na(n,e){let t=Promise.resolve(n).then(i=>({kind:"exit",r:i})),s,r=e.then(()=>new Promise(i=>{s=setTimeout(()=>i({kind:"grace"}),Oa),s.unref?.()})),o=await Promise.race([t,r]);return s&&clearTimeout(s),o.kind==="exit"?o.r:(t.catch(()=>{}),{exitCode:0,stdout:"",stderr:"",detachedAfterResult:!0})}async function La(n,e,t,s,r,o,i){let a=mt();if(!a)return null;let c=Date.now(),{channel:d,chatId:u}=It(e.context),l={agentId:e.agentId,channel:d,chatId:u};if(e.freshSession)try{await a.kill(l,"freshSession=true")}catch{}let f,h=!1;try{let $=a.list().length;f=a.acquire(l,{agentId:e.agentId,channel:d,chatId:u,workspace:n.workspace,model:e.model||n.model,permissionMode:n.permissionMode,billing:n.billing,systemPromptAppend:e.systemPromptAppend,resumeSessionId:s,extraArgs:e.claudeArgs}),h=a.list().length>$}catch($){return $ instanceof ke,null}h&&dn(e.agentId,n,e.model,s,"persistent");let p=pe(n,e,t),I="",k,b,R,E,O,P,D,N="",L="",B=$=>{if(!i||!$)return;let T;if(L&&$.startsWith(L)?T=$.slice(L.length):T=N&&!N.endsWith(`
|
|
31
31
|
`)?`
|
|
32
32
|
|
|
33
33
|
${$}`:$,L=$,!!T){N+=T;try{i(T,N)}catch{}}},H=!1,q=()=>{H=!0,a.kill(l,"operator-cancel").catch(()=>{})};o&&(o.aborted?q():o.addEventListener("abort",q,{once:!0}));let G=!1;try{let $=Math.max(6e4,(n.maxExecutionMinutes??20)*6e4);for await(let T of f.runTurn({message:p,taskId:e.taskId??"unknown",deadlineMs:$})){if(G=!0,r)try{r(T.raw)}catch{}if(T.type==="assistant"){let w=T.raw.message?.content??[];for(let x of w)x.type==="text"&&typeof x.text=="string"?(I=x.text,B(x.text)):x.type==="tool_use"&&(L="");let _=T.raw.message?.usage;if(_){let x=(_.input_tokens||0)+(_.cache_read_input_tokens||0)+(_.cache_creation_input_tokens||0);x>0&&(E=x)}}if(T.type==="result"){let w=T.raw;if(w.is_error){let _=X(typeof w.result=="string"?w.result:"claude error");k=_.error,b=_.errorKind}else typeof w.result=="string"&&w.result.length>0&&(I=w.result,N||B(w.result));typeof w.session_id=="string"&&(D=w.session_id),w.usage&&(R={inputTokens:w.usage.input_tokens||0,outputTokens:w.usage.output_tokens||0,cacheReadTokens:w.usage.cache_read_input_tokens||0,cacheCreateTokens:w.usage.cache_creation_input_tokens||0}),typeof w.num_turns=="number"&&(O=w.num_turns),typeof w.message?.model=="string"?P=w.message.model:typeof w.model=="string"?P=w.model:P=ft(w.modelUsage)||P}}}catch($){if(H)k="task cancelled by operator",b="cancelled";else if(G)if($ instanceof Be)k=`Claude Code was stopped mid-turn (${$.reason}), before its time limit`,b="interrupted";else if($ instanceof Ue){let T=X(`Claude Code timed out after ${Math.round($.budgetMs/6e4)}m. Bump agent.maxExecutionMinutes for "${n.name||e.agentId}" if tasks need longer.`);k=T.error,b=T.errorKind}else{let T=X(`persistent claude process error: ${$?.message||String($)}`);k=T.error,b=T.errorKind}else return console.error(`[runtime] persistent handle unusable before first event (${$?.message||$}); falling back to spawn-per-task`),a.release(l,{kill:!0,reason:"pre-turn failure"}),o&&o.removeEventListener("abort",q),null}finally{o&&o.removeEventListener("abort",q)}return H&&!k&&(k="task cancelled by operator",b="cancelled"),{content:H?"":I,error:k,errorKind:b,duration:Date.now()-c,usage:R,contextTokens:E,numTurns:O,billedModel:P,claudeSessionId:D}}function Gr(n){let e=ie(n.autonomy);if(!e&&!n.personLimits)return{args:[]};if(!n.taskId)return{error:"a guarded task requires a task id to enforce"};let t=e?Tr(n.autonomy,n.agentId,n.taskId):{args:[]};return"error"in t||!n.personLimits?t:Cr(t.args,n.taskId,n.agentId)}function Fa(n){return ie(n.autonomy)||!!n.personLimits}async function Ha(n,e,t,s,r,o,i){let a=ie(e.autonomy)?e.autonomy:void 0,c=a&&vr(a,n.tier)||e.personLimits&&Mr(n.tier);if(c)return{content:"",error:c,...a?{autonomy:a}:{}};let d=e.taskId||`guarded-${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`,u={...e,taskId:d,message:a?`${ds(a)}
|
|
@@ -151,4 +151,4 @@ ${te}`),m+=te.length}catch{}}}let Fi=Lt.length>0?Lt.join(`
|
|
|
151
151
|
|
|
152
152
|
Agent: ${C.content}`,meta:du(V,e.context)})}catch{}$s(e.agentId,e.message,C.content,{channel:y,chatId:A,sender:Dt},this.memoryStore).catch(()=>{}),io(e.agentId,e.message,C.content,{channel:y,date:new Date().toISOString().slice(0,10)},this.patternStore).catch(()=>{})}let Q=C.billedModel||e.model||d.def.model,ce=e.context?.channel,at=ce&&(e.context?.chatId||e.context?.group||e.context?.sender)?`${ce}:${e.context?.chatId||e.context?.group||e.context?.sender}`:void 0;this.tokenTracker.record(e.agentId,C.duration||0,C.usage,e.message.length,C.content.length,!1,Q,ce,at),this.log(`[${e.agentId}] completed in ${C.duration}ms`+(C.tokensUsed?` (${C.tokensUsed} tokens)`:"")+` ${w()}`)}return C}catch(g){d.errors++,this.log(`[${e.agentId}] unexpected error: ${g.message}`);let m=gt(g.message);return re={content:"",error:pt(m),errorKind:m.kind},re}finally{if(ss.stop(),!ns)try{Y().emit("task:completed",{taskId:E,agentId:e.agentId,channel:u,chatId:l,durationMs:Date.now()-p.startedAt.getTime(),error:re?.error||(k.signal.aborted?Xe(k.signal).message:"run ended before completion"),errorKind:re?.errorKind,interrupted:this.interruptedRuns.has(p.id)?!0:void 0,at:new Date().toISOString()})}catch{}Pn(re)}}getActiveTaskCount(){let e=0;for(let t of this.agents.values())e+=t.activeTasks;return e}runningAgentDrainSeconds(){let e=[];for(let t of this.agents.values())t.activeTasks>0&&e.push(t.def.drainTimeoutSeconds);return e}list(){return Array.from(this.agents.values()).map(e=>{let t=this.lastSummaries.get(e.id);return{id:e.id,name:e.def.name,tier:e.def.tier,model:e.def.model,workspace:e.def.workspace,active:e.activeTasks,total:e.totalTasks,errors:e.errors,lastActive:e.lastActive,runningTasks:e.runningTasks,lastSummary:t?{text:t.text,at:t.at.toISOString(),ok:t.ok}:void 0,hourlyTasks:this.getHourlySparkline(e.id,24)}})}persistTaskRecord(e){let t=e.endedAt.slice(0,10),s=J(process.cwd(),Re,this.safe(e.agentId),t);ne(s)||Vc(s,{recursive:!0});let r=J(s,`${this.safe(e.id)}.json`);eu(r,JSON.stringify(e,null,2),"utf-8")}getHourlySparkline(e,t=24){let s=Date.now(),r=this.sparklineCache.get(e);if(r&&s-r.at<6e4&&r.hourly.length===t)return r.hourly;let o=new Array(t).fill(0),i=s-t*36e5,a=J(process.cwd(),Re,this.safe(e));if(!ne(a))return this.sparklineCache.set(e,{hourly:o,at:s}),o;let c=new Date(s).toISOString().slice(0,10),d=new Date(s-864e5).toISOString().slice(0,10);new Date(s).setUTCHours(0,0,0,0);for(let l of[d,c]){let f=J(a,l);if(ne(f))try{for(let h of ae(f)){if(!h.endsWith(".json"))continue;let p=h.split("-")[0],I=parseInt(p,10);if(!Number.isFinite(I)||I<i||I>s)continue;let k=Math.floor((s-I)/36e5),b=t-1-k;b>=0&&b<t&&o[b]++}}catch{}}return this.sparklineCache.set(e,{hourly:o,at:s}),o}hydrateLastSummariesFromDisk(){let e=J(process.cwd(),Re);if(!ne(e))return 0;let t=0;for(let s of ae(e)){let r=J(e,s),o;try{o=ae(r).filter(i=>/^\d{4}-\d{2}-\d{2}$/.test(i)).sort().reverse()}catch{continue}e:for(let i of o){let a=J(r,i),c;try{c=ae(a).filter(d=>d.endsWith(".json")).sort().reverse()}catch{continue}for(let d of c)try{let u=JSON.parse(Ye(J(a,d),"utf-8")),l=(u.responseText||u.error||"").trim();if(!l)continue;let f=l.split(/\r?\n/)[0].slice(0,140);this.lastSummaries.set(u.agentId,{text:f,at:new Date(u.endedAt),ok:u.ok}),t++;break e}catch{}}}return t}pruneTaskHistory(e=au){let t=J(process.cwd(),Re);if(!ne(t))return 0;let s=Date.now()-e*24*60*60*1e3,r=0;for(let o of ae(t)){let i=J(t,o),a;try{a=ae(i)}catch{continue}for(let c of a){let d=Date.parse(c+"T00:00:00Z");if(!(Number.isNaN(d)||d>=s))try{Zc(J(i,c),{recursive:!0,force:!0}),r++}catch{}}}return r}listTaskHistory(e,t=50){let s=J(process.cwd(),Re,this.safe(e));if(!ne(s))return[];let r=ae(s).filter(i=>/^\d{4}-\d{2}-\d{2}$/.test(i)).sort().reverse(),o=[];for(let i of r){if(o.length>=t)break;let a=J(s,i),c;try{c=ae(a).filter(d=>d.endsWith(".json"))}catch{continue}c.sort().reverse();for(let d of c){if(o.length>=t)break;try{let u=JSON.parse(Ye(J(a,d),"utf-8")),{transcript:l,responseText:f,...h}=u;o.push(h)}catch{}}}return o}getTaskRecord(e,t){let s=J(process.cwd(),Re,this.safe(e));if(!ne(s))return null;let r=this.safe(t),o=ae(s).filter(i=>/^\d{4}-\d{2}-\d{2}$/.test(i)).sort().reverse();for(let i of o){let a=J(s,i,`${r}.json`);if(ne(a))try{return JSON.parse(Ye(a,"utf-8"))}catch{return null}}return null}safe(e){return e.replace(/[^a-zA-Z0-9_.:-]/g,"_")}postQueuedResponseToChannel(e,t,s){let r=Qs();if(!r)return;let o=t?.channel,i=t?.chatId;if(!o||!i||s.errorKind==="cancelled"||s.errorKind==="interrupted"||He(s.error))return;let a=s.error?`Error: ${s.error}`:(s.content||"").trim();a&&r.sendOutbound({channel:o,chatId:i,text:a,agentId:e}).catch(c=>this.log(`[${e}] queued response post failed: ${c?.message??c}`))}runningTaskOwner(e){for(let t of this.agents.values()){let s=t.runningTasks.find(r=>r.id===e);if(s)return{agentId:t.id,channel:s.channel,chatId:s.chatId}}return null}findRunningTurn(e,t={}){let s=this.agents.get(e);if(!s)return null;let r;if(t.taskId)r=s.runningTasks.find(i=>i.id===t.taskId);else if(t.channel&&t.chatId){let i=s.runningTasks.filter(a=>{let c=It(a);return c.channel===t.channel&&c.chatId===t.chatId});r=i.length===1?i[0]:void 0}if(!r)return null;let o=this.runningContexts.get(r.id);return o?{taskId:r.id,context:o}:null}slotHolders(e){let t=this.agents.get(e);return t?{full:t.activeTasks>=t.def.maxConcurrent,runIds:t.runningTasks.map(s=>s.id)}:null}isChatBusy(e,t,s){return this.messageQueue.isBusy(e,t,s)}runningIntentPath(e,t,s){let r=this.agents.get(e);if(!r)return;let o;for(let i of r.runningTasks)i.channel!==t||i.chatId!==s||(!o||i.startedAt>o.startedAt)&&(o=i);return o?.intentPath?.slice()}cancelRunningTask(e,t="operator"){let s=this.taskAborts.get(e);if(!s)return null;try{s.controller.abort(new Error(t))}catch{}return this.log(`[${s.agentId}] task ${e} cancelled \u2014 ${t}`),{agentId:s.agentId,channel:s.channel,chatId:s.chatId}}interruptRunning(e){let t=0;for(let[s,r]of this.taskAborts){this.interruptedRuns.set(s,e);try{r.controller.abort(new Error(e))}catch{}this.log(`[${r.agentId}] task ${s} interrupted \u2014 ${e}`),t++}return t}cancelChatTasks(e,t,s,r="operator"){let o=0;for(let[i,a]of this.taskAborts)a.agentId===e&&a.channel===t&&a.chatId===s&&this.cancelRunningTask(i,r)&&o++;return o}queueFollowUp(e,t,s,r={}){let o=this.taskAborts.get(e);if(!o)return null;let{agentId:i,channel:a,chatId:c,originalMessage:d}=o,u=o.controller.signal.aborted,l=!1;u&&d&&(l=this.sessions.removeLastUserMessageIfMatches(i,a,c,d));let f=Date.now();this.messageQueue.enqueue(i,a,c,{text:t,sender:s,timestamp:f,channel:a,chatId:c,originalContext:{channel:a,chatId:c,sender:s}});let h=this.messageQueue.pendingCount(i,a,c);Y().emit("task:queued",{agentId:i,channel:a,chatId:c,at:new Date(f).toISOString(),sender:{name:s},humanRoot:!1,person:ls(this.config.people)?.id??null,messagePreview:t.slice(0,200),queuedAt:f});let p=!1;if(r.replace){let I=r.reason||`replaced by follow-up from ${s}`;try{o.controller.abort(new Error(I))}catch{}this.log(`[${i}] task ${e} replaced by follow-up \u2014 ${I}`),p=!0}else this.log(`[${i}] follow-up queued for task ${e} (pending=${h}${l?", edited cancelled turn":""})`);return{agentId:i,channel:a,chatId:c,replaced:p,edited:l,pending:h}}async continueFinishedTask(e,t,s,r,o={}){let i=this.getTaskRecord(e,t);if(!i)return{ok:!1,status:404,error:`no task ${t} for agent ${e}`};if(i.channel!=="cron"||!i.chatId)return{ok:!1,status:409,error:`task ${t} is finished; only scheduled (cron) runs can be resumed from the dashboard`};let a=i.channel,c=i.chatId;return new Promise(d=>{let u=!1,l=f=>{u||(u=!0,d(f))};this.log(`[${e}] operator resumed ${a}:${c} from finished task ${t}`),this.execute({message:s,agentId:e,model:o.model?.(c),context:{channel:a,chatId:c,sender:r},onStart:f=>l({ok:!0,agentId:e,channel:a,chatId:c,taskId:f,queued:!1})}).then(f=>{He(f.error)?l({ok:!0,agentId:e,channel:a,chatId:c,queued:!0}):f.error?l({ok:!1,status:500,error:f.error||"run did not start"}):l({ok:!0,agentId:e,channel:a,chatId:c,queued:!1,answeredBy:f.viaAttachedSession?"attached":"other"})}).catch(f=>l({ok:!1,status:500,error:f?.message??String(f)}))})}subscribeToTaskOutput(e,t){let s=this.taskOutputs.get(e);return s?s.done?{initial:s.buffer,done:!0,unsubscribe:()=>{}}:(s.subscribers.add(t),{initial:s.buffer,done:!1,unsubscribe:()=>{s.subscribers.delete(t)}}):null}getUsage(e=7){return this.tokenTracker.summary(e)}getTodayUsage(){return this.tokenTracker.today()}getTokenTracker(){return this.tokenTracker}};function fu(n,e){return n===!1||e!=="telegram"&&e!=="whatsapp"&&e!=="app"?"":["[Rich replies]","On this chat channel you may add buttons, a poll, or media to a reply by appending ONE fenced block at the very end:","```agentx:ui",'{ "buttons": [{"label": "Open docs", "url": "https://..."}], "poll": {"question": "Ship it?", "options": ["Yes", "No"]}, "media": {"type": "image", "url": "https://..."} }',"```","All fields are optional; include only what helps. Buttons must be https URLs (tappable callback actions aren't supported yet). Use this sparingly \u2014 only when a link, choice, or image genuinely improves the reply. The block is stripped from the visible text.",...e==="app"?[`In this phone app you may also offer up to 4 short answers the user can tap instead of typing: "quickReplies": ["Yes", "Not now"] (at most 40 characters each), or a button with a reply instead of a url: {"label": "Run the tests", "reply": "Please run the tests"}. A tap sends that text as the user's next message, nothing more. Offer them only when the user is likely to answer with one of them.`]:[]].join(`
|
|
153
153
|
`)}export{mr as a,Cu as b,yr as c,ia as d,wr as e,ht as f,Uu as g,Gu as h,Ku as i,Yu as j,It as k,$a as l,Wr as m,Ra as n,Ea as o,Da as p,Kr as q,Tt as r,vt as s,td as t,kl as u,Al as v,fn as w,Y as x,ao as y,ld as z,md as A,lo as B,Wl as C,go as D,ho as E,Io as F,sg as G,To as H,$t as I,dg as J,cg as K,ze as L,hc as M,yc as N,wc as O,bc as P,xc as Q,Sn as R,nu as S,su as T,Kp as U,Jp as V,du as W,cu as X,uu as Y,Cn as Z,mi as _,hi as $,gu as aa,yi as ba,wi as ca,fu as da};
|
|
154
|
-
//# sourceMappingURL=chunk-
|
|
154
|
+
//# sourceMappingURL=chunk-QBWJGDKE.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{b as C}from"./chunk-CECHWYJ2.js";import{a as L,b as _,c as R,d as j,e as M,j as I}from"./chunk-
|
|
1
|
+
import{b as C}from"./chunk-CECHWYJ2.js";import{a as L,b as _,c as R,d as j,e as M,j as I}from"./chunk-JVSTKXMI.js";import{y as x,z as O}from"./chunk-A35RTVMM.js";import{c as A,d as D}from"./chunk-QOYAX2FT.js";import{readFileSync as y,writeFileSync as b,appendFileSync as P,existsSync as w,mkdirSync as $,readdirSync as v,statSync as B}from"fs";import{resolve as u,join as N,relative as k,dirname as U}from"path";O();var T=class{baseDir;rawDir;log;constructor(t=u(process.cwd(),".agentx/wiki"),e=console.error.bind(console,"[wiki]")){this.baseDir=t,this.rawDir=u(t,"raw/entries"),this.log=e,$(this.rawDir,{recursive:!0}),$(u(t,"raw"),{recursive:!0}),this.ensureSchema()}canRead(t,e){return t.access==="public"?!0:e?!!(t.owner===e||t.access==="shared"&&t.sharedWith?.includes(e)):!1}canWrite(t,e){return t.owner===e}addEntry(t){let e=`${t.date}_${t.id}.md`,r=u(this.rawDir,e),n=["---",`id: ${t.id}`,`date: ${t.date}`,`agent: ${t.agentId}`,`source: ${t.source}`];if(t.sourceContext&&n.push(`context: ${t.sourceContext}`),t.meta)for(let[a,s]of Object.entries(t.meta))n.push(`${a}: ${JSON.stringify(s)}`);return n.push("---","",t.content),b(r,n.join(`
|
|
2
2
|
`)),this.appendLog("ingest",`${t.id} from ${t.agentId} via ${t.source}`),e}listEntries(t){if(!w(this.rawDir))return[];let e=v(this.rawDir).filter(n=>n.endsWith(".md")).sort(),r=[];for(let n of e){let a=y(u(this.rawDir,n),"utf-8"),s=this.parseEntry(a,n);s&&(t?.agentId&&s.agentId!==t.agentId||t?.after&&s.date<t.after||t?.before&&s.date>t.before||r.push(s))}return r}parseEntry(t,e){let r=t.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(!r)return null;let n=r[1],a=r[2].trim(),s=c=>n.match(new RegExp(`^${c}:\\s*(.+)$`,"m"))?.[1]?.trim()||"",o=new Set(["id","date","agent","source","context"]),i={};for(let c of n.split(`
|
|
3
3
|
`)){let l=c.match(/^(\w+):\s*(.+)$/);if(!(!l||o.has(l[1])))try{i[l[1]]=JSON.parse(l[2])}catch{i[l[1]]=l[2].trim()}}return{id:s("id")||e.replace(".md",""),date:s("date"),agentId:s("agent"),source:s("source"),sourceContext:s("context")||void 0,content:a,meta:Object.keys(i).length>0?i:void 0}}writeArticle(t,e,r,n){let a=this.readArticle(t);if(a&&!this.canWrite(a.meta,n))return this.log(`Permission denied: "${n}" cannot write "${t}" (owner: ${a.meta.owner})`),!1;e.type&&!C(e.type)&&(this.log(`Dropping invalid article type "${e.type}" for "${t}" \u2014 enum is [person, project, place, concept, event, decision, pattern]`),e={...e,type:void 0});let s=u(this.baseDir,t);a&&this.saveVersion(t,s),$(U(s),{recursive:!0});let o=["---",`title: "${e.title}"`];e.type&&o.push(`type: ${e.type}`),e.related?.length&&o.push(`related: [${e.related.map(c=>`"${c}"`).join(", ")}]`),o.push(`tags: [${(e.tags||[]).map(c=>`"${c}"`).join(", ")}]`,`owner: ${e.owner}`,`access: ${e.access}`),e.sharedWith?.length&&o.push(`shared_with: [${e.sharedWith.map(c=>`"${c}"`).join(", ")}]`),o.push(`created: ${e.created}`,`last_updated: ${e.lastUpdated}`,`sources: [${e.sources.map(c=>`"${c}"`).join(", ")}]`),e.graphPath?.length&&o.push(`graph_path: [${e.graphPath.map(c=>`"${c}"`).join(", ")}]`),o.push("---","",r),b(s,o.join(`
|
|
4
4
|
`));let i=a?"update":"create";return this.appendLog(i,`${e.title} [${(e.tags||[]).slice(0,5).join(", ")}] by ${n} at ${t}`),!0}saveVersion(t,e){try{if(!w(e))return;let r=y(e,"utf-8"),n=new Date().toISOString().replace(/[:.]/g,"-"),a=u(this.baseDir,"_versions",t.replace(/\.md$/,""));$(a,{recursive:!0}),b(u(a,`${n}.md`),r)}catch{}}getVersions(t){let e=u(this.baseDir,"_versions",t.replace(/\.md$/,""));if(!w(e))return[];try{return v(e).filter(r=>r.endsWith(".md")).sort().reverse().map(r=>({timestamp:r.replace(/\.md$/,"").replace(/-/g,(n,a)=>a<19?a===10?"T":a===13||a===16?":":"-":n),path:u(e,r)}))}catch{return[]}}restoreVersion(t,e){let n=this.getVersions(t).find(a=>a.timestamp.startsWith(e));if(!n)return!1;try{let a=y(n.path,"utf-8"),s=u(this.baseDir,t);return this.saveVersion(t,s),b(s,a),this.appendLog("restore",`${t} restored to ${e}`),!0}catch{return!1}}readArticle(t){let e=u(this.baseDir,t);if(!w(e))return null;let r=y(e,"utf-8");return this.parseArticle(r,t)}readArticleAs(t,e){let r=this.readArticle(t);return!r||!this.canRead(r.meta,e)?null:r}parseArticle(t,e){let r=t.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(!r)return null;let n=r[1],a=r[2].trim(),s=m=>n.match(new RegExp(`^${m}:\\s*(.+)$`,"m"))?.[1]?.trim().replace(/^"(.*)"$/,"$1")||"",o=m=>{let h=n.match(new RegExp(`^${m}:\\s*\\[(.*)\\]$`,"m"));return h?h[1].split(",").map(f=>f.trim().replace(/^"(.*)"$/,"$1")).filter(Boolean):[]},i=s("type")||s("kind"),l=new Set(["person","project","place","concept","event","decision","pattern"]).has(i)?i:void 0,p=o("tags").filter((m,h,f)=>m&&f.indexOf(m)===h),d=o("related"),g=o("graph_path");return{meta:{title:s("title"),type:l,related:d.length?d:void 0,tags:p,owner:s("owner"),access:s("access")||"public",sharedWith:o("shared_with"),created:s("created"),lastUpdated:s("last_updated"),sources:o("sources"),graphPath:g.length?g:void 0},content:a,path:e}}listCache=new Map;listArticles(t){let e=[],r=[];this.walkDir(this.baseDir,(o,i)=>{if(!o.endsWith(".md"))return;let c=k(this.baseDir,o);c.startsWith("raw/")||c.startsWith("_")||(e.push(c),r.push(`${c}:${i?.mtimeMs??"?"}:${i?.size??"?"}`))});let n=r.join("|"),a=this.listCache.get(t);if(a&&a.signature===n)return a.articles.slice();let s=[];for(let o of e){let i=this.readArticle(o);i&&this.canRead(i.meta,t)&&s.push(i)}return this.listCache.set(t,{signature:n,articles:s}),s.slice()}search(t,e,r=10){let n=t.toLowerCase(),a=[];if(this.walkDir(this.baseDir,d=>{if(!d.endsWith(".md"))return;let g=k(this.baseDir,d);if(g.startsWith("raw/")||g.startsWith("_"))return;let m=this.readArticle(g);!m||!this.canRead(m.meta,e)||a.push(m)}),a.length===0)return[];let s=a.map(d=>d.content),o=u(this.baseDir,"_bm25_cache.json"),i=D(s,o),c=A(t,i),l=new Map(c.map(d=>[d.docIndex,d.score]));return a.map((d,g)=>{let m=l.get(g)??0;return d.meta.title.toLowerCase().includes(n)&&(m+=10),d.meta.tags?.some(h=>h.toLowerCase().includes(n))&&(m+=5),{article:d,score:m}}).filter(d=>d.score>0).sort((d,g)=>g.score-d.score).slice(0,r).map(d=>d.article)}async findRelevantReranked(t,e,r=10,n={}){let a=Math.max(r,n.shortlist??_),s=this.findRelevant(t,e,a);if(s.length<2)return s.slice(0,r);let o=s.map((l,p)=>({id:`c${p}`,title:l.meta.title||`(untitled ${p})`,tags:l.meta.tags,excerpt:l.content})),i=await x(L,R({query:t,candidates:o,excerptChars:n.excerptChars}),j(o),{incumbent:{best:o[0].id},features:{agent:e??"unknown"}});return!i||i.mode!=="active"?s.slice(0,r):M(i.answers,o).ranked.map(l=>s[Number(l.slice(1))]).filter(l=>!!l).slice(0,r)}findRelevant(t,e,r=3,n,a={graph:.6,bm25:.4}){let s=[];if(this.walkDir(this.baseDir,h=>{if(!h.endsWith(".md"))return;let f=k(this.baseDir,h);if(f.startsWith("raw/")||f.startsWith("_"))return;let W=this.readArticle(f);!W||!this.canRead(W.meta,e)||s.push(W)}),s.length===0)return[];let o=s.map(h=>`${h.meta.title} ${(h.meta.tags||[]).join(" ")} ${h.content}`),i=u(this.baseDir,"_bm25_relevance_cache.json"),c=D(o,i),l=A(t,c),p=l.reduce((h,f)=>Math.max(h,f.score),0)||1,d=new Map(l.map(h=>[h.docIndex,h.score/p])),g=!!(n&&n.length>0);return s.map((h,f)=>{let W=d.get(f)??0,S=0;g&&h.meta.graphPath?.length&&(S=I(n,h.meta.graphPath));let E=g?a.graph*S+a.bm25*W:W;return{article:h,score:E}}).filter(h=>h.score>0).sort((h,f)=>f.score-h.score).slice(0,r).map(h=>h.article)}buildContext(t,e=4e3){if(t.length===0)return"";let r=["[Wiki Knowledge]"],n=0;for(let a of t){let s=`
|
|
@@ -102,4 +102,4 @@ We use GitLab for version control.
|
|
|
102
102
|
There are ${e.length} unprocessed entries. Read each, understand what it means, and create or update wiki articles.
|
|
103
103
|
|
|
104
104
|
${r}`}walkDir(t,e){if(w(t))for(let r of v(t)){let n=N(t,r),a=B(n);a.isDirectory()?this.walkDir(n,e):e(n,a)}}};export{T as a};
|
|
105
|
-
//# sourceMappingURL=chunk-
|
|
105
|
+
//# sourceMappingURL=chunk-TZIPWZY6.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{c as ce,d as J,e as E,f as K,g as z}from"./chunk-SCJ5ZXEN.js";import{C as se}from"./chunk-
|
|
1
|
+
import{c as ce,d as J,e as E,f as K,g as z}from"./chunk-SCJ5ZXEN.js";import{C as se}from"./chunk-ZXQYOSHP.js";import{a as de,b as le}from"./chunk-BGG3ZSMW.js";import{c as ae,e as oe}from"./chunk-LQE73AZR.js";import{e as F,g as ie}from"./chunk-JIT5GPTX.js";import{a as ne,b as re}from"./chunk-CYKPDILL.js";import{a as te}from"./chunk-Z6OPM7EH.js";import{A as De,R as Z,Y as ze,ga as q,ia as ee,ja as U,ma as j,w as L,x as Q}from"./chunk-Y5HBYSJW.js";import{existsSync as M,readFileSync as ve,writeFileSync as be,appendFileSync as Oe,mkdirSync as Be}from"fs";import{dirname as He,resolve as P}from"path";import{fileURLToPath as Le}from"url";import{hostname as qe}from"os";import{spawn as je}from"child_process";j();import{existsSync as ge,readFileSync as pe,writeFileSync as Y,copyFileSync as Me}from"fs";import{resolve as me}from"path";function W(e,t={}){let n=t.configPath||me(process.cwd(),"agentx.json");if(!ge(n))throw new Error(`agentx.json not found at ${n}`);let r=JSON.parse(pe(n,"utf-8")),a=e(r),o=typeof a=="string"?{summary:a}:a,s=JSON.stringify(r,null,2)+`
|
|
2
2
|
`,l=n+".tmp";Y(l,s,"utf-8");try{JSON.parse(pe(l,"utf-8"))}catch(c){throw new Error(`config did not round-trip cleanly: ${c.message}`)}let i;return t.skipBackup||(i=`${n}.bak.${Date.now()}`,Me(n,i)),Y(n,s,"utf-8"),t.skipReload||Ne().catch(()=>{}),{summary:o.summary,payload:o.payload,backupPath:i}}function ue(e,t={}){let n=t.configPath||me(process.cwd(),"agentx.json");if(ge(n)&&!t.overwrite)throw new Error(`agentx.json already exists at ${n}. Pass overwrite=true to replace it.`);return Y(n,JSON.stringify(e,null,2)+`
|
|
3
3
|
`,"utf-8"),{file:n}}async function Ne(){try{let t=U().dashboard.daemonUrl?.replace(/\/+$/,"")||"http://127.0.0.1:18800";await fetch(`${t}/reload`,{method:"POST"}).catch(()=>null)}catch{}}function fe(e,t){let n=e.configExists?e.agentCount===0?"Add your first agent":"Add another agent":"Set up your team",r=`<header class="ax-topbar">
|
|
4
4
|
<div class="ax-topbar__left">
|
|
@@ -742,4 +742,4 @@ tr:hover td { background: var(--bg-hover); }
|
|
|
742
742
|
WHERE person = ? AND state IN (${n.map(()=>"?").join(",")})
|
|
743
743
|
${r}`).all(t,...n,...a).map(s=>({id:s.id,state:s.state,channel:s.channel,agentId:s.agent_id,text:s.text,createdAt:s.created_at})):[]}function pn(e,t){return Ae(e,t,L,"ORDER BY created_at")}function gn(e,t,n=50){return Ae(e,t,[...L,...Q],"ORDER BY created_at DESC, rowid DESC LIMIT ?",Math.max(1,Math.min(n,500)))}ze();var V="member:",gt=90,mt="members",ut="tailscale-user-login",ft=400,ht=60*60*1e3;function vt(e){let t=e.scopes.find(n=>n.startsWith(V));return t?t.slice(V.length):null}function Ie(e,t){if(!e)return null;let n=t.verify(e),r=n?vt(n):null;return n&&r?{rec:n,personId:r}:null}function xn(e,t){let n=e.people().find(l=>l.id===t);if(!n)return{ok:!1,error:`No person "${t}". Add them first: agentx people add ${t} --name "\u2026"`};let{token:r,record:a}=e.tokens.create({name:`${n.name} (machine not paired yet)`,scopes:[`${V}${n.id}`],expiresInDays:gt}),{code:o,expiresAt:s}=e.codes.create({token:r,tokenId:a.id,name:n.id});return e.members.log({person:n.id,device:a.id,event:"invited"}),{ok:!0,code:o,expiresAt:s,tokenId:a.id,person:n}}var bt=Math.round(ae/6e4);function xt(e){return e==="client"?"Your project":"My work"}function yn(e){let t=xt(e.person.role),n=e.person.role==="client"?`I use AgentX to keep track of the work I do for you. It has a page for you, "${t}": what you asked us for, and where each request stands.`:`I use AgentX to give our AI agents their jobs. It has a page for you, "${t}": what you asked the agents for, and where each request stands.`;return[`Hi ${e.person.name},`,"",n,"","To open it:","1. Accept the Tailscale share I sent you. Tailscale is a small program that connects your computer to mine, privately.",`2. Open ${e.origin.replace(/\/+$/,"")}/member in Edge or Chrome, give your computer a name and type this code: ${oe(e.code)}`,` The code works once, for ${bt} minutes. If it has stopped working, tell me and I will send you a new one.`,"","I then approve your machine, and the page opens by itself."].join(`
|
|
744
744
|
`)}var yt="The private network says someone else is connecting from this machine. Ask the person who invited you for a new code.",kt="Too many machines are waiting for the owner's answer. Try again later.",Se="That code didn't work. Check it, or ask the person who invited you for a new one.",Ee=(e,t)=>{let n=e.headers[t];return(Array.isArray(n)?n[0]:n||"").trim()};function Te(e){let t=e.socket?.remoteAddress||"";return t==="127.0.0.1"||t==="::1"||t==="::ffff:127.0.0.1"}function wt(e){if(!Te(e))return null;let t=Ee(e,ut).toLowerCase();return t?t.slice(0,80):null}function $t(e){let t=e.socket?.remoteAddress||"unknown";if(!Te(e))return t;let n=Ee(e,"x-forwarded-for").split(",").pop().trim();return/^[0-9a-fA-F:.]{2,45}$/.test(n)?n:t}function At(e){return typeof e=="string"?e.replace(/[^\p{L}\p{N} ._'()-]/gu," ").replace(/\s+/g," ").trim().slice(0,60):""}function St(e){return e.identities.map(Z).filter(t=>!!t&&t.channel==="tailscale").map(t=>t.value)}async function kn(e,t){let n=Date.now(),r=t.now??Date.now,a=t.log??(m=>console.log(m)),o=$t(e),s=t.limiter.retryAfter(o);if(s>0)return a(`[member] pair-code refused from ${o}: locked out for ${s}s more`),{status:429,body:{error:"too many attempts",retryAfter:s},retryAfter:s};let l=null,i="";try{let m=await se(e,2048);l=m.code,i=At(m.machine)}catch{}let c=t.codes.redeem(l),g=c?Ie(c.token,t.tokens):null;if(await Et(n,t.minMs??ft),!c||!g)return t.limiter.fail(o),a(`[member] pair-code failed from ${o}`),{status:401,body:{error:Se}};t.limiter.succeed(o);let{rec:f,personId:d}=g,h=t.people().find(m=>m.id===d);if(!h)return t.tokens.revoke(f.id),t.members.log({person:d,device:f.id,event:"refused",address:o,detail:"person no longer listed"}),a(`[member] pair-code for ${d} refused from ${o}: person no longer listed`),{status:403,body:{error:Se}};let u=wt(e),A=St(h);if(A.length&&(!u||!A.includes(u)))return t.tokens.revoke(f.id),t.members.log({person:d,device:f.id,event:"refused",address:o,detail:`network login ${u??"missing"}; expected ${A.join(" or ")}`}),a(`[member] ${d}: pairing from ${o} refused: the network says "${u??"nobody"}", expected ${A.join(" or ")}`),{status:403,body:{error:yt}};let x=i||"Unnamed machine",y=h.name.slice(0,40),v=ie(t.root,{title:`New machine for ${y}`,ask:`From ${o}, ${u?`network login ${u}`:"no network login reported"}. Machine name, as typed there: "${x}". Let it see ${y}'s own work?`,recommend:`Yes if ${y} told you they just paired this machine. No if you did not expect it.`,if_silent:"discard",raised_by:mt},{now:r()});return v.ok?(t.members.add({tokenId:f.id,personId:d,name:x,state:"pending",cardId:v.card.id,address:o,...u?{network:u}:{},createdAt:new Date(r()).toISOString()}),t.members.log({person:d,device:f.id,event:"paired",address:o,detail:x}),a(`[member] ${d} paired "${x}" from ${o}; waiting for the owner (card ${v.card.id})`),{status:200,body:{person:d,name:x,state:"pending"},token:c.token}):(t.tokens.revoke(f.id),t.members.log({person:d,device:f.id,event:"refused",address:o,detail:v.error}),a(`[member] ${d}: pairing from ${o} refused: ${v.error}`),{status:503,body:{error:kt}})}var H="this machine is not paired",It="waiting for the owner to approve this machine";function Ce(e){return e?e.status==="decided"?e.verdict??null:e.status==="expired"?"no":null:null}function wn(e,t){return t.state==="pending"&&t.cardId?Ce(F(e,t.cardId)):null}function $n(e,t,n){let r=t.now??Date.now,a=t.log??(g=>console.log(g)),o=Ie(e,t.tokens);if(!o)return{ok:!1,status:401,error:H};let{rec:s,personId:l}=o,i=t.members.byToken(s.id);if(!i||i.state==="removed"||i.personId!==l)return{ok:!1,status:401,error:H};if(t.people&&!t.people().some(g=>g.id===l))return Re(t,s.id,"person no longer listed"),a(`[member] ${l}: "${i.name}" ended: person no longer listed`),{ok:!1,status:401,error:H};if(i.state==="pending"){let g=i.cardId?F(t.root,i.cardId):null,f=Ce(g);if(f==="yes")i=t.members.update(s.id,{state:"active",approvedAt:new Date(r()).toISOString()})??i,t.members.log({person:l,device:s.id,event:"approved",detail:g?.decided_by}),a(`[member] ${l}: "${i.name}" approved`);else if(f==="no"){let d=g?.status==="expired"?"the owner did not answer in time":"the owner said no";return t.tokens.revoke(s.id),t.members.update(s.id,{state:"removed",removedAt:new Date(r()).toISOString(),removedReason:d}),t.members.log({person:l,device:s.id,event:"refused",detail:d}),a(`[member] ${l}: "${i.name}" refused: ${d}`),{ok:!1,status:401,error:H}}else return{ok:!1,status:403,error:It,waiting:!0}}let c=i.lastSeenAt?Date.parse(i.lastSeenAt):0;return n&&(r()-c>=ht||i.lastAddress!==n)&&(i=t.members.update(s.id,{lastSeenAt:new Date(r()).toISOString(),lastAddress:n})??i,t.members.log({person:l,device:s.id,event:"signed-in",address:n})),{ok:!0,device:i,personId:l,rec:s}}function Re(e,t,n="removed by the owner"){let r=e.members.byToken(t);if(!r)return null;e.tokens.revoke(t);let a=e.members.update(t,{state:"removed",removedAt:new Date((e.now??Date.now)()).toISOString(),removedReason:n});return e.members.log({person:r.personId,device:t,event:"removed",detail:n}),a}function An(e,t){return e.members.devices(t).filter(n=>n.state!=="removed").map(n=>Re(e,n.tokenId,"person removed by the owner")).filter(n=>!!n)}async function Et(e,t){let n=e+t-Date.now();n>0&&await new Promise(r=>setTimeout(r,n))}var Pe=["gitlab","github"];function Tt(e,t,n){let r=String(t||"").trim().toLowerCase();if(!Pe.includes(r))throw new Error(`Request status works on ${Pe.join(" and ")}, not on "${t}".`);e.requestStatus=e.requestStatus&&typeof e.requestStatus=="object"?e.requestStatus:{};let a=Array.isArray(e.requestStatus.channels)?e.requestStatus.channels.map(o=>String(o).toLowerCase()):[];return e.requestStatus.channels=n?[...new Set([...a,r])]:a.filter(o=>o!==r),`request status on ${r} is ${n?"on":"off"}`}function En(e){return Array.isArray(e?.requestStatus?.channels)?e.requestStatus.channels.map(t=>String(t).toLowerCase()):[]}function Tn(e){let{summary:t}=W(n=>Tt(n,e?.channel,!!e?.enabled));return{summary:t}}var Ct="https://ntfy.sh";function Rn(e){return{enabled:!!e?.enabled,server:e?.server||Ct,topicSet:!!e?.topic,tokenSet:!!e?.token}}function Pn(e,t){let n={...e??{}};if("enabled"in t&&(n.enabled=!!t.enabled),"server"in t){let r=String(t.server).trim().replace(/\/+$/,"");if(!/^https?:\/\/\S+$/.test(r))throw new Error("ntfy server must be an http(s) URL");n.server=r}if("topic"in t){let r=String(t.topic).trim();if(!r)throw new Error("ntfy topic cannot be empty");n.topic=r}if("token"in t){let r=String(t.token??"").trim();r?n.token=r:delete n.token}if(n.enabled&&!n.topic)throw new Error("set a topic before enabling ntfy");return n}export{W as a,xe as b,jt as c,Ft as d,Jt as e,Vt as f,Qt as g,Zt as h,cn as i,pn as j,gn as k,gt as l,xn as m,bt as n,xt as o,yn as p,$t as q,kn as r,wn as s,$n as t,Re as u,An as v,Pe as w,Tt as x,En as y,Tn as z,Rn as A,Pn as B,lt as C,sn as D};
|
|
745
|
-
//# sourceMappingURL=chunk-
|
|
745
|
+
//# sourceMappingURL=chunk-UMFFIUU6.js.map
|