agentix-cli 0.127.0 → 0.129.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{App-FF66N2KH.js → App-G37JNRBW.js} +2 -2
- package/dist/{ChatApp-HMHQZVVD.js → ChatApp-NCKC7C3L.js} +2 -2
- package/dist/absorb-eval-ZCWHWKAD.js +2 -0
- package/dist/{agent-UAWDO3I2.js → agent-LJ4SUQCC.js} +2 -2
- package/dist/{board-dashboard-Y2OECKDG.js → board-dashboard-LZJT4YWV.js} +251 -235
- package/dist/board-dashboard-LZJT4YWV.js.map +1 -0
- package/dist/{chunk-NUSROFTJ.js → chunk-4FMZTM3W.js} +2 -2
- package/dist/chunk-4O4GZBKZ.js +4 -0
- package/dist/chunk-4O4GZBKZ.js.map +1 -0
- package/dist/{chunk-I4DGMFQG.js → chunk-4ZFLYY5P.js} +2 -2
- package/dist/chunk-A347YHEW.js +4 -0
- package/dist/chunk-A347YHEW.js.map +1 -0
- package/dist/chunk-AMW2R676.js +36 -0
- package/dist/chunk-AMW2R676.js.map +1 -0
- package/dist/{chunk-HNBW5Q47.js → chunk-AOFTTIZZ.js} +2 -2
- package/dist/{chunk-O6FP3LPC.js → chunk-ARSVKSC4.js} +2 -2
- package/dist/{chunk-EHLTZ6IB.js → chunk-BCUR2TJ7.js} +2 -2
- package/dist/{chunk-PH43FUU7.js → chunk-BWSB7NOX.js} +2 -2
- package/dist/{chunk-QWQOI4G4.js → chunk-CDLJOGVZ.js} +31 -31
- package/dist/chunk-CDLJOGVZ.js.map +1 -0
- package/dist/{chunk-COWAAE54.js → chunk-CPMQZ6Z7.js} +2 -2
- package/dist/{chunk-YV22EOGL.js → chunk-DVR6PFCJ.js} +2 -2
- package/dist/chunk-EOLEKSN6.js +54 -0
- package/dist/chunk-EOLEKSN6.js.map +1 -0
- package/dist/{chunk-VBJTGTF5.js → chunk-I3RAUJCD.js} +2 -2
- package/dist/chunk-M7IJVOJA.js +55 -0
- package/dist/chunk-M7IJVOJA.js.map +1 -0
- package/dist/{chunk-6W74CHNW.js → chunk-MEFPXHQJ.js} +13 -13
- package/dist/chunk-MEFPXHQJ.js.map +1 -0
- package/dist/chunk-PVUNDNT3.js +17 -0
- package/dist/chunk-PVUNDNT3.js.map +1 -0
- package/dist/{chunk-3KYZOC7N.js → chunk-R6UCPHUD.js} +2 -2
- package/dist/{chunk-CKYPQMUD.js → chunk-SCC7HBFZ.js} +2 -2
- package/dist/{chunk-56KNKUFF.js → chunk-VD7IO653.js} +3 -3
- package/dist/{chunk-56KNKUFF.js.map → chunk-VD7IO653.js.map} +1 -1
- package/dist/{chunk-XYT6XY7G.js → chunk-VJRITDSQ.js} +2 -2
- package/dist/chunk-X5JTG52Q.js +6 -0
- package/dist/chunk-X5JTG52Q.js.map +1 -0
- package/dist/{chunk-LTH3AYXC.js → chunk-XIA2ACKX.js} +2 -2
- package/dist/{chunk-73WQ42PO.js → chunk-XOQNDWVW.js} +2 -2
- package/dist/{chunk-XX5QKBX4.js → chunk-XZN7QBRH.js} +2 -2
- package/dist/{chunk-HYRBBH6T.js → chunk-YHD4PHFX.js} +240 -240
- package/dist/chunk-YHD4PHFX.js.map +1 -0
- package/dist/{chunk-4WZGZ2Z6.js → chunk-Z5CBUZIW.js} +2 -2
- package/dist/{chunk-EJT3F6QM.js → chunk-ZF5U5KPB.js} +13 -13
- package/dist/chunk-ZF5U5KPB.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/{client-2VVRJ3UF.js → client-A5OK3IFO.js} +2 -2
- package/dist/config-DQ5SILHX.js +2 -0
- package/dist/{config-mutate-47WZDZBO.js → config-mutate-62FCJ3S6.js} +2 -2
- package/dist/config-mutator-3ZL5YQSO.js +2 -0
- package/dist/contributions-L2YS4AH2.js +57 -0
- package/dist/contributions-L2YS4AH2.js.map +1 -0
- package/dist/{doctor-Q3QNZVLC.js → doctor-7TPSMMFA.js} +2 -2
- package/dist/{follow-up-tool-GKZVG4L2.js → follow-up-tool-FKWIRFPW.js} +2 -2
- package/dist/{hub-DNPINTIF.js → hub-EU5ECAJB.js} +2 -2
- package/dist/index.d.ts +301 -0
- package/dist/index.js +1 -1
- package/dist/note-reader-LPPIYYLT.js +8 -0
- package/dist/note-reader-LPPIYYLT.js.map +1 -0
- package/dist/{notes-YLTNRMB5.js → notes-2VC47RCK.js} +2 -2
- package/dist/program-UTSXNAHM.js +743 -0
- package/dist/program-UTSXNAHM.js.map +1 -0
- package/dist/{promote-PETKX7KR.js → promote-XBDWCHLU.js} +2 -2
- package/dist/query-UI6GQEL7.js +2 -0
- package/dist/{registry-2XDO53AJ.js → registry-JW6ETTBA.js} +2 -2
- package/dist/{request-planner-A3WJNPXO.js → request-planner-SEXYQN7D.js} +2 -2
- package/dist/{review-candidates-A6ID2RDX.js → review-candidates-PIJWPPHQ.js} +2 -2
- package/dist/{rotation-memo-6UW6DTBQ.js → rotation-memo-7VLREGS6.js} +2 -2
- package/dist/{routing-NPP3MEUW.js → routing-2V3Y6ZR5.js} +2 -2
- package/dist/schedule-tool-KFZ27JTH.js +2 -0
- package/dist/{scheduler-T6VLBM3U.js → scheduler-SVZKDKJV.js} +2 -2
- package/dist/score-KDNIUTNV.js +3 -0
- package/dist/score-KDNIUTNV.js.map +1 -0
- package/dist/{seat-D3DBYTSC.js → seat-C6TMCJWI.js} +2 -2
- package/dist/{select-RFY64RY7.js → select-DP64KWO4.js} +2 -2
- package/dist/{set-enabled-AU66DR5C.js → set-enabled-WCH6FU4N.js} +2 -2
- package/dist/{tool-PCHIVR27.js → tool-TWQRBV4Y.js} +2 -2
- package/dist/{tool-F3YA2ZZQ.js → tool-WUK3ZOWG.js} +2 -2
- package/dist/wiki-4LAJ36XR.js +2 -0
- package/package.json +1 -1
- package/dist/absorb-eval-ERKVVTGG.js +0 -2
- package/dist/board-dashboard-Y2OECKDG.js.map +0 -1
- package/dist/chunk-52SIHG45.js +0 -4
- package/dist/chunk-52SIHG45.js.map +0 -1
- package/dist/chunk-6W74CHNW.js.map +0 -1
- package/dist/chunk-EJT3F6QM.js.map +0 -1
- package/dist/chunk-HYRBBH6T.js.map +0 -1
- package/dist/chunk-PLIT25TH.js +0 -51
- package/dist/chunk-PLIT25TH.js.map +0 -1
- package/dist/chunk-QWQOI4G4.js.map +0 -1
- package/dist/chunk-TJ4P4WW2.js +0 -4
- package/dist/chunk-TJ4P4WW2.js.map +0 -1
- package/dist/chunk-UJXS4MS3.js +0 -49
- package/dist/chunk-UJXS4MS3.js.map +0 -1
- package/dist/chunk-Y27RUOD2.js +0 -54
- package/dist/chunk-Y27RUOD2.js.map +0 -1
- package/dist/config-YSKHOV44.js +0 -2
- package/dist/config-mutator-7DIEQUB5.js +0 -2
- package/dist/program-4QWUOJE3.js +0 -739
- package/dist/program-4QWUOJE3.js.map +0 -1
- package/dist/query-D44BUTH3.js +0 -2
- package/dist/schedule-tool-ZECUQMQK.js +0 -2
- package/dist/wiki-NI3ZPUZS.js +0 -2
- /package/dist/{App-FF66N2KH.js.map → App-G37JNRBW.js.map} +0 -0
- /package/dist/{ChatApp-HMHQZVVD.js.map → ChatApp-NCKC7C3L.js.map} +0 -0
- /package/dist/{absorb-eval-ERKVVTGG.js.map → absorb-eval-ZCWHWKAD.js.map} +0 -0
- /package/dist/{agent-UAWDO3I2.js.map → agent-LJ4SUQCC.js.map} +0 -0
- /package/dist/{chunk-NUSROFTJ.js.map → chunk-4FMZTM3W.js.map} +0 -0
- /package/dist/{chunk-I4DGMFQG.js.map → chunk-4ZFLYY5P.js.map} +0 -0
- /package/dist/{chunk-HNBW5Q47.js.map → chunk-AOFTTIZZ.js.map} +0 -0
- /package/dist/{chunk-O6FP3LPC.js.map → chunk-ARSVKSC4.js.map} +0 -0
- /package/dist/{chunk-EHLTZ6IB.js.map → chunk-BCUR2TJ7.js.map} +0 -0
- /package/dist/{chunk-PH43FUU7.js.map → chunk-BWSB7NOX.js.map} +0 -0
- /package/dist/{chunk-COWAAE54.js.map → chunk-CPMQZ6Z7.js.map} +0 -0
- /package/dist/{chunk-YV22EOGL.js.map → chunk-DVR6PFCJ.js.map} +0 -0
- /package/dist/{chunk-VBJTGTF5.js.map → chunk-I3RAUJCD.js.map} +0 -0
- /package/dist/{chunk-3KYZOC7N.js.map → chunk-R6UCPHUD.js.map} +0 -0
- /package/dist/{chunk-CKYPQMUD.js.map → chunk-SCC7HBFZ.js.map} +0 -0
- /package/dist/{chunk-XYT6XY7G.js.map → chunk-VJRITDSQ.js.map} +0 -0
- /package/dist/{chunk-LTH3AYXC.js.map → chunk-XIA2ACKX.js.map} +0 -0
- /package/dist/{chunk-73WQ42PO.js.map → chunk-XOQNDWVW.js.map} +0 -0
- /package/dist/{chunk-XX5QKBX4.js.map → chunk-XZN7QBRH.js.map} +0 -0
- /package/dist/{chunk-4WZGZ2Z6.js.map → chunk-Z5CBUZIW.js.map} +0 -0
- /package/dist/{client-2VVRJ3UF.js.map → client-A5OK3IFO.js.map} +0 -0
- /package/dist/{config-YSKHOV44.js.map → config-DQ5SILHX.js.map} +0 -0
- /package/dist/{config-mutate-47WZDZBO.js.map → config-mutate-62FCJ3S6.js.map} +0 -0
- /package/dist/{config-mutator-7DIEQUB5.js.map → config-mutator-3ZL5YQSO.js.map} +0 -0
- /package/dist/{doctor-Q3QNZVLC.js.map → doctor-7TPSMMFA.js.map} +0 -0
- /package/dist/{follow-up-tool-GKZVG4L2.js.map → follow-up-tool-FKWIRFPW.js.map} +0 -0
- /package/dist/{hub-DNPINTIF.js.map → hub-EU5ECAJB.js.map} +0 -0
- /package/dist/{notes-YLTNRMB5.js.map → notes-2VC47RCK.js.map} +0 -0
- /package/dist/{promote-PETKX7KR.js.map → promote-XBDWCHLU.js.map} +0 -0
- /package/dist/{query-D44BUTH3.js.map → query-UI6GQEL7.js.map} +0 -0
- /package/dist/{registry-2XDO53AJ.js.map → registry-JW6ETTBA.js.map} +0 -0
- /package/dist/{request-planner-A3WJNPXO.js.map → request-planner-SEXYQN7D.js.map} +0 -0
- /package/dist/{review-candidates-A6ID2RDX.js.map → review-candidates-PIJWPPHQ.js.map} +0 -0
- /package/dist/{rotation-memo-6UW6DTBQ.js.map → rotation-memo-7VLREGS6.js.map} +0 -0
- /package/dist/{routing-NPP3MEUW.js.map → routing-2V3Y6ZR5.js.map} +0 -0
- /package/dist/{schedule-tool-ZECUQMQK.js.map → schedule-tool-KFZ27JTH.js.map} +0 -0
- /package/dist/{scheduler-T6VLBM3U.js.map → scheduler-SVZKDKJV.js.map} +0 -0
- /package/dist/{seat-D3DBYTSC.js.map → seat-C6TMCJWI.js.map} +0 -0
- /package/dist/{select-RFY64RY7.js.map → select-DP64KWO4.js.map} +0 -0
- /package/dist/{set-enabled-AU66DR5C.js.map → set-enabled-WCH6FU4N.js.map} +0 -0
- /package/dist/{tool-PCHIVR27.js.map → tool-TWQRBV4Y.js.map} +0 -0
- /package/dist/{tool-F3YA2ZZQ.js.map → tool-WUK3ZOWG.js.map} +0 -0
- /package/dist/{wiki-NI3ZPUZS.js.map → wiki-4LAJ36XR.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-
|
|
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-MEFPXHQJ.js";import{a as E}from"./chunk-BCUR2TJ7.js";import{b as L}from"./chunk-4VVHGR7H.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="",p=""){return Z(r,s,t,o,i,c,p)}function Z(n,r,s,t,o,i="",c=""){let p=r?`
|
|
2
2
|
## Worldview
|
|
3
3
|
|
|
4
4
|
${r}
|
|
@@ -240,4 +240,4 @@ ${u}`}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 m=[{role:"user",content:n}];s&&(m.push({role:"assistant",content:"(previous attempt; will retry with corrections)"}),m.push({role:"user",content:`Your previous response failed validation: ${s}
|
|
241
241
|
Re-emit the FULL corrected JSON object.`}));let u=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:m}),signal:i.signal});if(!u.ok){let a=await u.text().catch(()=>"");throw new Error(`anthropic API ${u.status}: ${a.slice(0,500)}`)}let l=await u.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,(...d)=>t(d.map(String).join(" "))),p=c.rebuildIndex(),m=z(r),u=[...ce(s),...n.extraCandidates??[]];if(i.candidates=de(u,p,m,{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(d=>({title:d.title,path:d.path,type:d.type})),g=c.getWorldview()??"",e=n.budgetTokens??ue,a=i.clusters,h=D(x,a,l,g);for(;R(h)>e&&a.length>1;)a=a.slice(0,-1),h=D(x,a,l,g);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(d=>d.candidates),w=new Set(y.map(d=>d.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(d=>[d.stamp,d]));for(let d of f.articles){let A=c.readArticle(d.path);if(A&&!c.canWrite(A.meta,x)){i.errors.push(`write denied for "${d.path}" (owned by ${A.meta.owner??"?"})`);continue}let M=fe(d.promotedFrom,T),S={id:Y(o,d.path),createdAt:v,status:"pending",article:d,evidence:M,...A?{replaces:{lastUpdated:A.meta.lastUpdated,contentHash:V(A.content)}}:{}};I(r,S),i.proposed.push({id:S.id,path:d.path,title:d.title,type:d.type,stamps:d.promotedFrom,agents:M.agents,occurrences:M.occurrences});for(let X of d.promotedFrom)k.push({stamp:X,decision:"proposed",article:d.path,proposal:S.id,at:v})}for(let d of f.skipped)k.push({stamp:d.stamp,decision:"skipped",reason:d.reason,at:v}),i.skipped.push(d);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 m=s.now??Date.now(),u=new Date(m).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??u,lastUpdated:u,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 g=new Date(m).toISOString();C(n,c.promotedFrom.map(a=>({stamp:a,decision:"promoted",article:c.path,proposal:r,at:g}))),i.rebuildIndex();let e={...t,status:"approved",decidedBy:s.by??"operator",decidedAt:g};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-I3RAUJCD.js.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import{c as j}from"./chunk-VD7IO653.js";import{a as D,b as I,c as _,d as L,e as T,j as U}from"./chunk-XIA2ACKX.js";import{y as O,z as V}from"./chunk-R6UCPHUD.js";import{b as P,c as $}from"./chunk-QOYAX2FT.js";import{readFileSync as J,existsSync as N}from"fs";V();import{resolve as W}from"path";import{execSync as z}from"child_process";async function pt(e,r,o,t={}){let d=t.selectorModel??"haiku",l=t.synthModel??"sonnet",p=t.maxCandidates??3,g=t.maxHops??2,h=t.maxArticles??8,s=t.timeoutMs??6e4,a=t.log??console.error.bind(console,"[wiki-query]"),c=t.messagePath?.length?t.messagePath:void 0,n=t.graphWeight??C,i=W(r.baseDir,"_index.md"),u=H(r,o,t.shared);if(!N(i)&&u.sharedPool.length===0)return S("no-catalog",e,"No _index.md found \u2014 run `agentx wiki status` to rebuild.");let f=(N(i)?J(i,"utf-8"):"")+q(e,u.sharedPool,c,n),E=Date.now(),k=[],y="";try{let m=await G(e,u.pool,o,p,c,n);m?(k=m,y=`[wiki-rerank seat] ${m.map(b=>b.title).join(" | ")}`):(y=await Q(et(e,f,p,c),d,s),k=nt(y))}catch(m){return a("selector failed:",m?.message),{...S("error",e,`Selector: ${m?.message||"unknown"}`),trace:{selectorMs:Date.now()-E,synthesisMs:0,selectorOutput:y}}}let x=Date.now()-E;if(k.length===0)return{...S("no-candidates",e,"Selector returned no candidates."),trace:{selectorMs:x,synthesisMs:0,selectorOutput:y}};let A=Y(k,u,g,h);if(A.length===0)return{...S("no-candidates",e,"Candidates did not resolve to readable articles."),candidates:k,trace:{selectorMs:x,synthesisMs:0,selectorOutput:y}};let v=Date.now(),R="";try{R=await Q(rt(e,A),l,s*2)}catch(m){return a("synthesis failed:",m?.message),{...S("error",e,`Synthesis: ${m?.message||"unknown"}`),candidates:k,walked:A.map(b=>({title:b.meta.title,path:b.path,type:b.meta.type,hop:b.hop??0})),trace:{selectorMs:x,synthesisMs:Date.now()-v,selectorOutput:y}}}let K=Date.now()-v;return{answer:R.trim(),citations:A.map(m=>({title:m.meta.title,path:m.path,type:m.meta.type})),candidates:k,walked:A.map(m=>({title:m.meta.title,path:m.path,type:m.meta.type,hop:m.hop??0})),status:"ok",trace:{selectorMs:x,synthesisMs:K,selectorOutput:y}}}async function gt(e,r,o,t={}){let d=t.maxCandidates??3,l=t.graphWeight??C,p=t.messagePath?.length?t.messagePath:void 0,g=t.pool??B(r);if(g.length===0||!e.trim())return[];let h=await G(e,g,o,d,p,l,t.stage??"retrieve");if(!h){let s=new Set($(e,P(g.map(F))).map(a=>a.docIndex));h=M(e,g,p,l).filter(a=>s.has(a)).slice(0,d).map(a=>({title:g[a].title,path:g[a].path}))}return h.length===0?[]:Y(h,H(r,o),t.maxHops??1,t.maxArticles??6)}function B(e){try{return(e.rebuildIndex().articles??[]).filter(r=>r.path&&!r.path.includes("/_versions/"))}catch{return[]}}function S(e,r,o){return{answer:"",citations:[],candidates:[],walked:[],status:e,error:o}}function H(e,r,o=[]){let t=new Map,d=[];for(let s of e.listArticles(r||""))t.set(s.meta.title.toLowerCase(),s.path),d.push({path:s.path,related:s.meta.related??[],lastUpdated:s.meta.lastUpdated??""});let l=[],p=new Map;for(let s of o)if(s.store.baseDir!==e.baseDir){p.set(s.id,s);for(let a of s.store.listArticles(r||"")){if(a.path.includes("/_versions/")||s.skip?.(a.path))continue;let c=`@${s.id}/${a.path}`;l.push(X(a,c)),d.push({path:c,related:a.meta.related??[],lastUpdated:a.meta.lastUpdated??""});let n=a.meta.title.toLowerCase();t.has(n)||t.set(n,c)}}let g=new Map;if(p.size>0)for(let s of[...d].sort((a,c)=>c.lastUpdated.localeCompare(a.lastUpdated)))for(let a of s.related){let c=a.toLowerCase(),n=g.get(c)??[];n.includes(s.path)||n.push(s.path),g.set(c,n)}let h;return{backlinks:g,get pool(){return h??=[...B(e),...l]},sharedPool:l,titleIndex:t,read(s){let a=s.match(/^@([^/]+)\/(.+)$/),c=a?p.get(a[1]):void 0;if(a&&!c)return null;let n=c?.store??e,i=a?a[2]:s,u=r?n.readArticleAs(i,r):n.readArticle(i);return u?{...u,path:s}:null}}}function X(e,r){return{path:r,title:e.meta.title,type:e.meta.type,related:e.meta.related,tags:e.meta.tags||[],owner:e.meta.owner,access:e.meta.access,sharedWith:e.meta.sharedWith,aliases:[e.meta.title.toLowerCase(),...(e.meta.aliases??[]).map(o=>o.toLowerCase())],backlinks:0,sources:e.meta.sources,lastUpdated:e.meta.lastUpdated,graphPath:e.meta.graphPath}}var Z=150;function q(e,r,o,t){return r.length===0?"":`
|
|
2
|
+
|
|
3
|
+
## Shared wiki (other agents' articles)
|
|
4
|
+
|
|
5
|
+
${M(e,r,o,t).slice(0,Z).map(l=>`- ${r[l].title}${r[l].type?` [${r[l].type}]`:""} (${r[l].path})`).join(`
|
|
6
|
+
`)}
|
|
7
|
+
`}var tt=3;function Y(e,r,o,t){let d=r.titleIndex,l=n=>n.startsWith("@"),p=[...e.filter(n=>!l(n.path)),...e.filter(n=>l(n.path))],g=p.length>0&&!l(p[0].path)?Math.floor(t/2):t,h=new Map,s=0,a=p.map(n=>({path:n.path,hop:0})),c=[];for(;h.size<t;){if(!a.length){if(!c.length||g>=t)break;g=t,a=c.splice(0)}let n=a.shift();if(h.has(n.path)||n.hop>o)continue;if(l(n.path)&&s>=g){c.push(n);continue}let i=r.read(n.path);if(i&&(h.set(n.path,{...i,hop:n.hop}),l(n.path)&&s++,!(n.hop>=o))){if(n.hop===0){let u=[i.meta.title,...i.meta.aliases??[]].map(f=>f.toLowerCase()),w=[...new Set(u.flatMap(f=>r.backlinks.get(f)??[]))].filter(f=>f!==n.path&&!h.has(f)).slice(0,tt);for(let f of w)a.push({path:f,hop:1})}for(let u of i.meta.related||[]){let w=d.get(u.toLowerCase());w&&!h.has(w)&&a.push({path:w,hop:n.hop+1})}}}return Array.from(h.values())}async function G(e,r,o,t,d,l=C,p="catalog-selector"){try{if(r.length<2)return null;let h=M(e,r,d,l).slice(0,I).map(n=>({id:`k${n}`,title:r[n].title||r[n].path,tags:r[n].tags,excerpt:[r[n].type?`type: ${r[n].type}`:"",(r[n].related??[]).length?`related: ${(r[n].related??[]).join(", ")}`:"",(r[n].graphPath??[]).length?`graph: ${(r[n].graphPath??[]).join(" \u203A ")}`:""].filter(Boolean).join(" \xB7 ")})),s=await O(D,_({query:e,candidates:h,excerptChars:300}),L(h),{features:{agent:o??"unknown",stage:p}});if(!s||s.mode!=="active")return null;let c=T(s.answers,h).ranked.slice(0,t).map(n=>r[Number(n.slice(1))]).filter(Boolean).map(n=>({title:n.title,path:n.path}));return c.length>0?c:null}catch{return null}}var C=.6;function M(e,r,o,t=C){let d=$(e,P(r.map(F))),l=d.reduce((i,u)=>Math.max(i,u.score),0),p=new Map(d.map(i=>[i.docIndex,l>0?i.score/l:0])),g=!!o?.length,h=i=>g&&i.graphPath?.length?U(o,i.graphPath):0,s=(i,u)=>u.score-i.score||i.i-u.i,a=r.map((i,u)=>({i:u,score:(p.get(u)??0)*(1+t*h(i))})).filter(i=>p.has(i.i)).sort(s),c=r.map((i,u)=>({i:u,score:h(i)})).filter(i=>!p.has(i.i)&&i.score>0).sort(s),n=[...a,...c].map(i=>i.i);for(let i=0;i<r.length;i++)n.includes(i)||n.push(i);return n}function F(e){return[e.title,(e.tags??[]).join(" "),(e.related??[]).join(" ")].join(" ")}function et(e,r,o,t){let d=t?.length?`
|
|
8
|
+
## Intent
|
|
9
|
+
|
|
10
|
+
The request was classified under the intent-graph path "${t.join(" \u203A ")}". Prefer articles filed under the same branch when their titles are equally plausible.
|
|
11
|
+
`:"";return`You are picking candidate articles from a wiki catalog to answer a question. You do NOT answer the question. You pick which articles the answer is likely to come from.
|
|
12
|
+
|
|
13
|
+
## Question
|
|
14
|
+
|
|
15
|
+
${e}
|
|
16
|
+
${d}
|
|
17
|
+
## Catalog (_index.md \u2014 articles grouped by type, with wikilink previews)
|
|
18
|
+
|
|
19
|
+
${r}
|
|
20
|
+
|
|
21
|
+
## Your task
|
|
22
|
+
|
|
23
|
+
Pick up to ${o} articles from the catalog that are most likely to answer the question. Prefer:
|
|
24
|
+
- Articles whose title directly names a subject in the question.
|
|
25
|
+
- Articles of the right type (e.g. a "who owns X" question \u2192 person or project; a "what happened on DATE" \u2192 event).
|
|
26
|
+
- Articles whose wikilinks suggest they sit at the center of a relevant subgraph.
|
|
27
|
+
|
|
28
|
+
Return ONLY valid JSON, no markdown fencing, no prose:
|
|
29
|
+
|
|
30
|
+
[
|
|
31
|
+
{"title": "Exact Article Title", "path": "path/from/catalog.md"},
|
|
32
|
+
...
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
If none of the articles match, return [].`}function rt(e,r){let o=r.map(t=>{let d=[t.meta.owner?`owner: ${t.meta.owner}`:"",t.meta.lastUpdated?`updated: ${t.meta.lastUpdated}`:""].filter(Boolean).join(", "),l=`### ${t.meta.title} \u2014 ${t.meta.type||"untyped"} (${t.path}) [hop ${t.hop}]${d?` {${d}}`:""}`,p=t.meta.related?.length?`Related: ${t.meta.related.join(", ")}`:"";return[l,p,"",t.content].filter(Boolean).join(`
|
|
36
|
+
`)}).join(`
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
`);return`You are answering a question using a small subgraph of wiki articles. The articles were walked from the catalog; trust them over your prior knowledge.
|
|
41
|
+
|
|
42
|
+
## Question
|
|
43
|
+
|
|
44
|
+
${e}
|
|
45
|
+
|
|
46
|
+
## Articles (${r.length} walked from the catalog)
|
|
47
|
+
|
|
48
|
+
${o}
|
|
49
|
+
|
|
50
|
+
## Your task
|
|
51
|
+
|
|
52
|
+
Answer the question in 2\u20136 sentences. Cite articles by their title in square brackets like [Article Title]. When two articles disagree, prefer the one updated more recently, and say which agent's page and date the answer comes from. If the articles do not answer the question, say so plainly (do not invent). Prefer concrete facts over hedging.
|
|
53
|
+
|
|
54
|
+
Output ONLY the answer \u2014 no preamble, no "here is my answer:", no markdown fencing.`}function nt(e){let r=e.match(/\[[\s\S]*?\]/);if(!r)return[];try{let o=JSON.parse(r[0]);return Array.isArray(o)?o.filter(t=>t&&typeof t.title=="string"&&typeof t.path=="string").map(t=>({title:t.title,path:t.path})):[]}catch{return[]}}async function Q(e,r,o){let{writeFileSync:t,mkdirSync:d,rmSync:l}=await import("fs"),{tmpdir:p}=await import("os"),g=W(p(),`agentx-wiki-query-${process.pid}`);d(g,{recursive:!0});let h=W(g,`prompt-${Date.now()}.txt`);t(h,e);try{let s=`cat '${h}' | claude -p - --output-format json --max-turns 1 --model ${r} --disallowedTools "Bash Read Write Edit Glob Grep Agent WebSearch WebFetch NotebookEdit"`,a=z(s,{env:j(),encoding:"utf-8",timeout:o,maxBuffer:4*1024*1024});try{let c=JSON.parse(a);return String(c.result||c.content||"")}catch{return a}}finally{try{l(h,{force:!0})}catch{}}}async function ut(e){try{let{loadDaemonConfig:r}=await import("./config-DQ5SILHX.js");return r(e).wiki.query.shared}catch{return!0}}export{pt as a,gt as b,B as c,Z as d,C as e,M as f,ut as g};
|
|
55
|
+
//# sourceMappingURL=chunk-M7IJVOJA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/wiki/query.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"fs\"\nimport { buildIndex, scoreAll } from \"@/memory/bm25\"\nimport { askSeat } from \"@/decisions/seat\"\nimport { ancestryScore } from \"@/graph\"\nimport {\n DEFAULT_SHORTLIST,\n WIKI_RERANK_SEAT,\n rerankQuestions,\n rerankState,\n toRanking,\n type RerankCandidate,\n} from \"@/decisions/seats/wiki-rerank\"\nimport { claudeCliEnv } from \"@/utils/workspace-env\"\nimport { resolve } from \"path\"\nimport { execSync } from \"child_process\"\nimport type { WikiArticle, WikiIndex } from \"./types\"\nimport type { WikiStore } from \"./store\"\n\n/**\n * Agentic wiki query — the \"Farzapedia-faithful\" retrieval path.\n *\n * Two LLM calls + a deterministic graph walk:\n * 1. Selector (cheap): read `_index.md` + question → pick up to `maxCandidates`\n * candidate articles by title + type.\n * 2. Walk (pure code): starting from candidates, follow `related` wikilinks\n * up to `maxHops`, capped at `maxArticles` total.\n * 3. Synthesis (main): answer the question from the walked subgraph.\n *\n * Returns an answer with citations and a trace of which articles were walked,\n * so callers can audit the retrieval path.\n */\n\nexport interface AgenticQueryOptions {\n /** Candidate-selection model (cheap). Defaults to \"haiku\". */\n selectorModel?: string\n /** Synthesis model (main). Defaults to \"sonnet\". */\n synthModel?: string\n /** Number of candidates the selector is asked to return. Default 3. */\n maxCandidates?: number\n /** Wikilink hops from candidates. Default 2. */\n maxHops?: number\n /** Hard cap on total articles walked. Default 8. */\n maxArticles?: number\n /** Timeout per LLM call, ms. Default 60_000. */\n timeoutMs?: number\n /** Logger — defaults to console.error. */\n log?: (...args: unknown[]) => void\n /** Intent-graph path the question's request was classified under.\n * Articles on the same branch rank higher in candidate selection. */\n messagePath?: string[]\n /** Weight of the branch match against the text match, 0–1. Default 0.6\n * (`graph.retrievalWeights.graph`). */\n graphWeight?: number\n /** Other wikis to search besides `store`: the shared wiki (#824). Only\n * articles the requester may read are offered. Their paths come back\n * as `@<id>/<path>`. */\n shared?: SharedWikiStore[]\n}\n\n/** A wiki searched alongside the requester's own. */\nexport interface SharedWikiStore {\n /** Shown in citations as `@<id>/<path>`: an agent id, or \"shared\". */\n id: string\n store: WikiStore\n /** Paths to leave out, e.g. the agents/ tree under the root store. */\n skip?: (path: string) => boolean\n}\n\nexport interface AgenticQueryResult {\n answer: string\n citations: Array<{ title: string; path: string; type?: string }>\n /** Titles/paths the selector picked. */\n candidates: Array<{ title: string; path: string }>\n /** Full article subgraph the walk produced. */\n walked: Array<{ title: string; path: string; type?: string; hop: number }>\n /** \"no-catalog\" | \"no-candidates\" | \"ok\" */\n status: \"ok\" | \"no-catalog\" | \"no-candidates\" | \"error\"\n /** For operator debugging only. */\n trace?: {\n selectorMs: number\n synthesisMs: number\n selectorOutput?: string\n }\n error?: string\n}\n\n/**\n * Run an agentic query against a specific agent's WikiStore.\n *\n * `requesterId` is used for permission checks — only articles the requester\n * can read will be included in the synthesis input.\n */\nexport async function agenticQuery(\n question: string,\n store: WikiStore,\n requesterId: string | undefined,\n opts: AgenticQueryOptions = {},\n): Promise<AgenticQueryResult> {\n const selectorModel = opts.selectorModel ?? \"haiku\"\n const synthModel = opts.synthModel ?? \"sonnet\"\n const maxCandidates = opts.maxCandidates ?? 3\n const maxHops = opts.maxHops ?? 2\n const maxArticles = opts.maxArticles ?? 8\n const timeoutMs = opts.timeoutMs ?? 60_000\n const log = opts.log ?? console.error.bind(console, \"[wiki-query]\")\n const messagePath = opts.messagePath?.length ? opts.messagePath : undefined\n const graphWeight = opts.graphWeight ?? DEFAULT_GRAPH_WEIGHT\n\n // --- Step 1: Load the catalog ---\n const catalogPath = resolve(store.baseDir, \"_index.md\")\n const view = scopeView(store, requesterId, opts.shared)\n if (!existsSync(catalogPath) && view.sharedPool.length === 0) {\n return emptyResult(\"no-catalog\", question, \"No _index.md found — run `agentx wiki status` to rebuild.\")\n }\n const ownCatalog = existsSync(catalogPath) ? readFileSync(catalogPath, \"utf-8\") : \"\"\n const catalog = ownCatalog + renderSharedCatalog(question, view.sharedPool, messagePath, graphWeight)\n\n // --- Step 2: Selector — pick candidates ---\n const selectorStart = Date.now()\n let candidates: Array<{ title: string; path: string }> = []\n let selectorOutput = \"\"\n try {\n const viaSeat = await selectCandidatesViaSeat(question, view.pool, requesterId, maxCandidates, messagePath, graphWeight)\n if (viaSeat) {\n candidates = viaSeat\n selectorOutput = `[wiki-rerank seat] ${viaSeat.map((c) => c.title).join(\" | \")}`\n } else {\n selectorOutput = await runClaude(\n buildSelectorPrompt(question, catalog, maxCandidates, messagePath),\n selectorModel,\n timeoutMs,\n )\n candidates = parseCandidates(selectorOutput)\n }\n } catch (e: any) {\n log(\"selector failed:\", e?.message)\n return { ...emptyResult(\"error\", question, `Selector: ${e?.message || \"unknown\"}`), trace: { selectorMs: Date.now() - selectorStart, synthesisMs: 0, selectorOutput } }\n }\n const selectorMs = Date.now() - selectorStart\n\n if (candidates.length === 0) {\n return { ...emptyResult(\"no-candidates\", question, \"Selector returned no candidates.\"), trace: { selectorMs, synthesisMs: 0, selectorOutput } }\n }\n\n // --- Step 3: Walk the subgraph via `related` wikilinks ---\n const walked = walkSubgraph(candidates, view, maxHops, maxArticles)\n\n if (walked.length === 0) {\n return { ...emptyResult(\"no-candidates\", question, \"Candidates did not resolve to readable articles.\"), candidates, trace: { selectorMs, synthesisMs: 0, selectorOutput } }\n }\n\n // --- Step 4: Synthesize the answer ---\n const synthStart = Date.now()\n let answer = \"\"\n try {\n answer = await runClaude(\n buildSynthesisPrompt(question, walked),\n synthModel,\n timeoutMs * 2,\n )\n } catch (e: any) {\n log(\"synthesis failed:\", e?.message)\n return { ...emptyResult(\"error\", question, `Synthesis: ${e?.message || \"unknown\"}`), candidates, walked: walked.map(w => ({ title: w.meta.title, path: w.path, type: w.meta.type, hop: (w as any).hop ?? 0 })), trace: { selectorMs, synthesisMs: Date.now() - synthStart, selectorOutput } }\n }\n const synthesisMs = Date.now() - synthStart\n\n return {\n answer: answer.trim(),\n citations: walked.map(a => ({ title: a.meta.title, path: a.path, type: a.meta.type })),\n candidates,\n walked: walked.map(w => ({ title: w.meta.title, path: w.path, type: w.meta.type, hop: (w as any).hop ?? 0 })),\n status: \"ok\",\n trace: { selectorMs, synthesisMs, selectorOutput },\n }\n}\n\nexport interface RetrieveOptions {\n /** Articles picked before the walk. Default 3. */\n maxCandidates?: number\n /** Wikilink hops from the picked articles. Default 1. */\n maxHops?: number\n /** Hard cap on articles returned. Default 6. */\n maxArticles?: number\n messagePath?: string[]\n graphWeight?: number\n /** The catalog to pick from. Pass it when retrieving many times in a row,\n * so the index is not rebuilt for every call. */\n pool?: CatalogEntry[]\n /** Seat telemetry label. */\n stage?: string\n}\n\n/**\n * The retrieval half of agenticQuery, without the synthesis call: BM25\n * shortlist, rerank seat, then the `related` wikilink walk. Returns the\n * walked articles, hop 0 first.\n *\n * When the seat is off or fails, the BM25 order decides on its own, and\n * only articles the text matched are picked: an article chosen for\n * nothing but its catalog position is noise here, not a candidate. There\n * is no CLI fallback, so this never spends an LLM call.\n */\nexport async function retrieveArticles(\n question: string,\n store: WikiStore,\n requesterId: string | undefined,\n opts: RetrieveOptions = {},\n): Promise<Array<WikiArticle & { hop: number }>> {\n const maxCandidates = opts.maxCandidates ?? 3\n const graphWeight = opts.graphWeight ?? DEFAULT_GRAPH_WEIGHT\n const messagePath = opts.messagePath?.length ? opts.messagePath : undefined\n const pool = opts.pool ?? catalogPool(store)\n if (pool.length === 0 || !question.trim()) return []\n\n let candidates = await selectCandidatesViaSeat(\n question, pool, requesterId, maxCandidates, messagePath, graphWeight, opts.stage ?? \"retrieve\",\n )\n if (!candidates) {\n const matched = new Set(scoreAll(question, buildIndex(pool.map(catalogDoc))).map((r) => r.docIndex))\n candidates = rankCatalogPool(question, pool, messagePath, graphWeight)\n .filter((i) => matched.has(i))\n .slice(0, maxCandidates)\n .map((i) => ({ title: pool[i].title, path: pool[i].path }))\n }\n if (candidates.length === 0) return []\n return walkSubgraph(candidates, scopeView(store, requesterId), opts.maxHops ?? 1, opts.maxArticles ?? 6)\n}\n\nexport type CatalogEntry = WikiIndex[\"articles\"][number]\n\n/** The catalog retrieval picks from: every indexed article but old versions. */\nexport function catalogPool(store: WikiStore): CatalogEntry[] {\n try {\n return (store.rebuildIndex().articles ?? []).filter((a) => a.path && !a.path.includes(\"/_versions/\"))\n } catch {\n return []\n }\n}\n\n// --- Helpers ---\n\nfunction emptyResult(\n status: AgenticQueryResult[\"status\"],\n _question: string,\n error?: string,\n): AgenticQueryResult {\n return {\n answer: \"\",\n citations: [],\n candidates: [],\n walked: [],\n status,\n error,\n }\n}\n\n/**\n * The articles a query may open: the requester's own wiki first, then\n * each shared one. Shared paths carry an `@<id>/` prefix so the walk\n * knows which store to read; a title the own wiki has resolves there.\n */\ninterface ScopeView {\n /** Own catalog, then the shared articles. */\n pool: CatalogEntry[]\n sharedPool: CatalogEntry[]\n titleIndex: Map<string, string>\n /** Title (lowercased) → pages whose `related` names it, newest first.\n * Built only when shared wikis are searched. */\n backlinks: Map<string, string[]>\n read(path: string): WikiArticle | null\n}\n\nfunction scopeView(store: WikiStore, requesterId: string | undefined, shared: SharedWikiStore[] = []): ScopeView {\n const titleIndex = new Map<string, string>()\n const linking: Array<{ path: string; related: string[]; lastUpdated: string }> = []\n for (const article of store.listArticles(requesterId || \"\")) {\n titleIndex.set(article.meta.title.toLowerCase(), article.path)\n linking.push({ path: article.path, related: article.meta.related ?? [], lastUpdated: article.meta.lastUpdated ?? \"\" })\n }\n const sharedPool: CatalogEntry[] = []\n const byId = new Map<string, SharedWikiStore>()\n for (const s of shared) {\n if (s.store.baseDir === store.baseDir) continue\n byId.set(s.id, s)\n // listArticles is cached and already drops what the requester can't\n // read; rebuilding every agent's index per query would not be.\n for (const a of s.store.listArticles(requesterId || \"\")) {\n if (a.path.includes(\"/_versions/\") || s.skip?.(a.path)) continue\n const path = `@${s.id}/${a.path}`\n sharedPool.push(catalogEntry(a, path))\n linking.push({ path, related: a.meta.related ?? [], lastUpdated: a.meta.lastUpdated ?? \"\" })\n const key = a.meta.title.toLowerCase()\n if (!titleIndex.has(key)) titleIndex.set(key, path)\n }\n }\n // In a fleet's shared pool the page that answers a question often links\n // TO the page the selector picked (a decision about a person), and the\n // forward walk never reaches it (#824 review: ranked 103rd of ~5,700).\n const backlinks = new Map<string, string[]>()\n if (byId.size > 0) {\n for (const l of [...linking].sort((a, b) => b.lastUpdated.localeCompare(a.lastUpdated))) {\n for (const t of l.related) {\n const key = t.toLowerCase()\n const list = backlinks.get(key) ?? []\n if (!list.includes(l.path)) list.push(l.path)\n backlinks.set(key, list)\n }\n }\n }\n let pool: CatalogEntry[] | undefined\n return {\n backlinks,\n // Lazy: retrieveArticles brings its own pool, and building this one\n // rebuilds the index.\n get pool() { return (pool ??= [...catalogPool(store), ...sharedPool]) },\n sharedPool,\n titleIndex,\n read(path) {\n const m = path.match(/^@([^/]+)\\/(.+)$/)\n const target = m ? byId.get(m[1]) : undefined\n if (m && !target) return null\n const s = target?.store ?? store\n const rel = m ? m[2] : path\n const article = requesterId ? s.readArticleAs(rel, requesterId) : s.readArticle(rel)\n return article ? { ...article, path } : null\n },\n }\n}\n\nfunction catalogEntry(a: WikiArticle, path: string): CatalogEntry {\n return {\n path,\n title: a.meta.title,\n type: a.meta.type,\n related: a.meta.related,\n tags: a.meta.tags || [],\n owner: a.meta.owner,\n access: a.meta.access,\n sharedWith: a.meta.sharedWith,\n aliases: [a.meta.title.toLowerCase(), ...(a.meta.aliases ?? []).map((x) => x.toLowerCase())],\n backlinks: 0,\n sources: a.meta.sources,\n lastUpdated: a.meta.lastUpdated,\n graphPath: a.meta.graphPath,\n }\n}\n\n/** Shared articles most related to the question, for the CLI selector.\n * Capped: a fleet's shared catalog would not fit one prompt. */\nexport const SHARED_CATALOG_LINES = 150\n\nfunction renderSharedCatalog(question: string, pool: CatalogEntry[], messagePath: string[] | undefined, graphWeight: number): string {\n if (pool.length === 0) return \"\"\n const lines = rankCatalogPool(question, pool, messagePath, graphWeight)\n .slice(0, SHARED_CATALOG_LINES)\n .map((i) => `- ${pool[i].title}${pool[i].type ? ` [${pool[i].type}]` : \"\"} (${pool[i].path})`)\n return `\\n\\n## Shared wiki (other agents' articles)\\n\\n${lines.join(\"\\n\")}\\n`\n}\n\n/** Pages that link to a picked page, opened per picked page. */\nconst BACKLINKS_PER_PICK = 3\n\nfunction walkSubgraph(\n candidates: Array<{ title: string; path: string }>,\n view: ScopeView,\n maxHops: number,\n maxArticles: number,\n): Array<WikiArticle & { hop: number }> {\n const titleIndex = view.titleIndex\n const isShared = (path: string) => path.startsWith(\"@\")\n\n // The agent's own pages come first, and shared pages may take at most\n // half the walk when it has any: in the #824 trial shared pages took\n // the slots of the agent's own correct pages and its score dropped.\n const ordered = [...candidates.filter((c) => !isShared(c.path)), ...candidates.filter((c) => isShared(c.path))]\n let sharedCap = ordered.length > 0 && !isShared(ordered[0].path) ? Math.floor(maxArticles / 2) : maxArticles\n\n const opened = new Map<string, WikiArticle & { hop: number }>()\n let sharedOpened = 0\n let frontier: Array<{ path: string; hop: number }> = ordered.map((c) => ({ path: c.path, hop: 0 }))\n // Shared pages turned away by the cap, in walk order. When the agent's\n // own pages run out first they get the slots left: an own pick with no\n // own neighbours must not leave the walk short of shared pages that answer.\n const deferred: Array<{ path: string; hop: number }> = []\n\n while (opened.size < maxArticles) {\n if (!frontier.length) {\n if (!deferred.length || sharedCap >= maxArticles) break\n sharedCap = maxArticles\n frontier = deferred.splice(0)\n }\n const next = frontier.shift()!\n if (opened.has(next.path)) continue\n if (next.hop > maxHops) continue\n if (isShared(next.path) && sharedOpened >= sharedCap) { deferred.push(next); continue }\n\n const article = view.read(next.path)\n if (!article) continue\n\n opened.set(next.path, { ...article, hop: next.hop })\n if (isShared(next.path)) sharedOpened++\n if (next.hop >= maxHops) continue\n\n if (next.hop === 0) {\n // Pages link to a subject by any of its names; skip what is already\n // open before taking the newest few, or an opened page uses a slot.\n const names = [article.meta.title, ...(article.meta.aliases ?? [])].map((n) => n.toLowerCase())\n const linking = [...new Set(names.flatMap((n) => view.backlinks.get(n) ?? []))]\n .filter((path) => path !== next.path && !opened.has(path))\n .slice(0, BACKLINKS_PER_PICK)\n for (const path of linking) frontier.push({ path, hop: 1 })\n }\n for (const target of article.meta.related || []) {\n const path = titleIndex.get(target.toLowerCase())\n if (path && !opened.has(path)) {\n frontier.push({ path, hop: next.hop + 1 })\n }\n }\n }\n\n return Array.from(opened.values())\n}\n\n// --- Prompts ---\n\n/**\n * Pick candidates with the wiki-rerank seat instead of a Claude CLI call.\n *\n * The selector is 56% of a wiki query — 14.1s of a 25s total, measured —\n * and its whole job is \"pick 3 articles from this catalog\", which is a\n * Choice with per-option probabilities. Jev answers that in about a\n * second. Synthesis stays on Sonnet; it writes prose, which Jev cannot.\n *\n * BM25 shortlists first because the shared catalog carries 971 entries,\n * past the 255-option cap. Same two-stage shape as findRelevantReranked,\n * and it reuses that seat rather than introducing a second one.\n *\n * Returns null whenever the seat is off, errors, or has nothing to rank,\n * and the caller falls back to the CLI selector — so this is strictly an\n * accelerator, never a new way to fail.\n */\nasync function selectCandidatesViaSeat(\n question: string,\n pool: CatalogEntry[],\n requesterId: string | undefined,\n maxCandidates: number,\n messagePath?: string[],\n graphWeight: number = DEFAULT_GRAPH_WEIGHT,\n stage = \"catalog-selector\",\n): Promise<Array<{ title: string; path: string }> | null> {\n try {\n if (pool.length < 2) return null\n\n const shortlist = rankCatalogPool(question, pool, messagePath, graphWeight).slice(0, DEFAULT_SHORTLIST)\n\n const candidates: RerankCandidate[] = shortlist.map((i) => ({\n id: `k${i}`,\n title: pool[i].title || pool[i].path,\n tags: pool[i].tags,\n // The catalog has no body — a title, its type, its wikilinks and its\n // branch of the intent graph are what the selector chooses on.\n excerpt: [\n pool[i].type ? `type: ${pool[i].type}` : \"\",\n (pool[i].related ?? []).length ? `related: ${(pool[i].related ?? []).join(\", \")}` : \"\",\n (pool[i].graphPath ?? []).length ? `graph: ${(pool[i].graphPath ?? []).join(\" › \")}` : \"\",\n ].filter(Boolean).join(\" · \"),\n }))\n\n const result = await askSeat(\n WIKI_RERANK_SEAT,\n rerankState({ query: question, candidates, excerptChars: 300 }),\n rerankQuestions(candidates),\n { features: { agent: requesterId ?? \"unknown\", stage } },\n )\n if (!result || result.mode !== \"active\") return null\n\n const ranking = toRanking(result.answers as never, candidates)\n const picked = ranking.ranked\n .slice(0, maxCandidates)\n .map((id) => pool[Number(id.slice(1))])\n .filter(Boolean)\n .map((a) => ({ title: a.title, path: a.path }))\n return picked.length > 0 ? picked : null\n } catch {\n return null\n }\n}\n\n/** Weight of the intent-graph branch match against the text match when\n * shortlisting catalog candidates. Mirrors `graph.retrievalWeights.graph`. */\nexport const DEFAULT_GRAPH_WEIGHT = 0.6\n\n/**\n * Order the catalog pool for a question. Three bands, never mixed:\n *\n * 1. articles the text match scored, ordered by `text * (1 + w * branch)`\n * when the request was classified: the branch reorders text matches\n * but cannot lift an unrelated article above one;\n * 2. articles on the request's branch that the text never matched, by\n * how much of the path they share;\n * 3. the rest, in catalog order.\n *\n * Band 1 comes first in full because the shortlist the reranker sees is\n * finite (DEFAULT_SHORTLIST): a busy category holds more same-branch\n * articles than that, and a blended score let them push the article that\n * actually answers the question out of the list.\n *\n * Exported for tests; the seat and the CLI selector both consume the order.\n */\nexport function rankCatalogPool(\n question: string,\n pool: Array<{ title: string; tags?: string[]; related?: string[]; graphPath?: string[] }>,\n messagePath?: string[],\n graphWeight: number = DEFAULT_GRAPH_WEIGHT,\n): number[] {\n const bm25 = scoreAll(question, buildIndex(pool.map(catalogDoc)))\n const maxBm25 = bm25.reduce((m, r) => Math.max(m, r.score), 0)\n const text = new Map(bm25.map((r) => [r.docIndex, maxBm25 > 0 ? r.score / maxBm25 : 0]))\n const useGraph = !!messagePath?.length\n const branch = (a: { graphPath?: string[] }) =>\n useGraph && a.graphPath?.length ? ancestryScore(messagePath!, a.graphPath) : 0\n\n const byScore = (x: { i: number; score: number }, y: { i: number; score: number }) => y.score - x.score || x.i - y.i\n const matched = pool\n .map((a, i) => ({ i, score: (text.get(i) ?? 0) * (1 + graphWeight * branch(a)) }))\n .filter((c) => text.has(c.i))\n .sort(byScore)\n const branchOnly = pool\n .map((a, i) => ({ i, score: branch(a) }))\n .filter((c) => !text.has(c.i) && c.score > 0)\n .sort(byScore)\n const ranked = [...matched, ...branchOnly].map((c) => c.i)\n for (let i = 0; i < pool.length; i++) if (!ranked.includes(i)) ranked.push(i)\n return ranked\n}\n\n/** What BM25 sees of a catalog entry: the catalog carries no body. */\nfunction catalogDoc(a: { title: string; tags?: string[]; related?: string[] }): string {\n return [a.title, (a.tags ?? []).join(\" \"), (a.related ?? []).join(\" \")].join(\" \")\n}\n\nfunction buildSelectorPrompt(question: string, catalog: string, maxCandidates: number, messagePath?: string[]): string {\n const branch = messagePath?.length\n ? `\\n## Intent\\n\\nThe request was classified under the intent-graph path \"${messagePath.join(\" › \")}\". Prefer articles filed under the same branch when their titles are equally plausible.\\n`\n : \"\"\n return `You are picking candidate articles from a wiki catalog to answer a question. You do NOT answer the question. You pick which articles the answer is likely to come from.\n\n## Question\n\n${question}\n${branch}\n## Catalog (_index.md — articles grouped by type, with wikilink previews)\n\n${catalog}\n\n## Your task\n\nPick up to ${maxCandidates} articles from the catalog that are most likely to answer the question. Prefer:\n- Articles whose title directly names a subject in the question.\n- Articles of the right type (e.g. a \"who owns X\" question → person or project; a \"what happened on DATE\" → event).\n- Articles whose wikilinks suggest they sit at the center of a relevant subgraph.\n\nReturn ONLY valid JSON, no markdown fencing, no prose:\n\n[\n {\"title\": \"Exact Article Title\", \"path\": \"path/from/catalog.md\"},\n ...\n]\n\nIf none of the articles match, return [].`\n}\n\nfunction buildSynthesisPrompt(\n question: string,\n articles: Array<WikiArticle & { hop: number }>,\n): string {\n const articlesBlock = articles\n .map(a => {\n // Owner and date let the answer say where a fact came from and pick\n // the newer page when two disagree (#824).\n const from = [a.meta.owner ? `owner: ${a.meta.owner}` : \"\", a.meta.lastUpdated ? `updated: ${a.meta.lastUpdated}` : \"\"].filter(Boolean).join(\", \")\n const header = `### ${a.meta.title} — ${a.meta.type || \"untyped\"} (${a.path}) [hop ${a.hop}]${from ? ` {${from}}` : \"\"}`\n const related = a.meta.related?.length ? `Related: ${a.meta.related.join(\", \")}` : \"\"\n return [header, related, \"\", a.content].filter(Boolean).join(\"\\n\")\n })\n .join(\"\\n\\n---\\n\\n\")\n\n return `You are answering a question using a small subgraph of wiki articles. The articles were walked from the catalog; trust them over your prior knowledge.\n\n## Question\n\n${question}\n\n## Articles (${articles.length} walked from the catalog)\n\n${articlesBlock}\n\n## Your task\n\nAnswer the question in 2–6 sentences. Cite articles by their title in square brackets like [Article Title]. When two articles disagree, prefer the one updated more recently, and say which agent's page and date the answer comes from. If the articles do not answer the question, say so plainly (do not invent). Prefer concrete facts over hedging.\n\nOutput ONLY the answer — no preamble, no \"here is my answer:\", no markdown fencing.`\n}\n\nfunction parseCandidates(raw: string): Array<{ title: string; path: string }> {\n const match = raw.match(/\\[[\\s\\S]*?\\]/)\n if (!match) return []\n try {\n const parsed = JSON.parse(match[0])\n if (!Array.isArray(parsed)) return []\n return parsed\n .filter((x: any) => x && typeof x.title === \"string\" && typeof x.path === \"string\")\n .map((x: any) => ({ title: x.title, path: x.path }))\n } catch {\n return []\n }\n}\n\nasync function runClaude(prompt: string, model: string, timeoutMs: number): Promise<string> {\n // Use stdin via a temp-file pipe to avoid shell-escaping a huge prompt.\n // Prompts can exceed the `-E` arg buffer on macOS, so we always pipe.\n const { writeFileSync, mkdirSync, rmSync } = await import(\"fs\")\n const { tmpdir } = await import(\"os\")\n const dir = resolve(tmpdir(), `agentx-wiki-query-${process.pid}`)\n mkdirSync(dir, { recursive: true })\n const promptPath = resolve(dir, `prompt-${Date.now()}.txt`)\n writeFileSync(promptPath, prompt)\n try {\n const cmd = `cat '${promptPath}' | claude -p - --output-format json --max-turns 1 --model ${model} --disallowedTools \"Bash Read Write Edit Glob Grep Agent WebSearch WebFetch NotebookEdit\"`\n const raw = execSync(cmd, { env: claudeCliEnv(), encoding: \"utf-8\", timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024 })\n try {\n const envelope = JSON.parse(raw)\n return String(envelope.result || envelope.content || \"\")\n } catch {\n return raw\n }\n } finally {\n try { rmSync(promptPath, { force: true }) } catch {}\n }\n}\n\n/** `wiki.query.shared` from agentx.json: whether a query also searches the\n * shared wiki. On when there is no config to read. The CLI and the\n * `agentx_wiki_query` tool both ask here, so the switch reaches both. */\nexport async function sharedQueryEnabled(configPath?: string): Promise<boolean> {\n try {\n const { loadDaemonConfig } = await import(\"@/daemon/config\")\n return loadDaemonConfig(configPath).wiki.query.shared\n } catch {\n return true\n }\n}\n"],"mappings":"iNAAA,OAAS,gBAAAA,EAAc,cAAAC,MAAkB,KAEzCC,IAWA,OAAS,WAAAC,MAAe,OACxB,OAAS,YAAAC,MAAgB,gBA8EzB,eAAsBC,GACpBC,EACAC,EACAC,EACAC,EAA4B,CAAC,EACA,CAC7B,IAAMC,EAAgBD,EAAK,eAAiB,QACtCE,EAAaF,EAAK,YAAc,SAChCG,EAAgBH,EAAK,eAAiB,EACtCI,EAAUJ,EAAK,SAAW,EAC1BK,EAAcL,EAAK,aAAe,EAClCM,EAAYN,EAAK,WAAa,IAC9BO,EAAMP,EAAK,KAAO,QAAQ,MAAM,KAAK,QAAS,cAAc,EAC5DQ,EAAcR,EAAK,aAAa,OAASA,EAAK,YAAc,OAC5DS,EAAcT,EAAK,aAAeU,EAGlCC,EAAcjB,EAAQI,EAAM,QAAS,WAAW,EAChDc,EAAOC,EAAUf,EAAOC,EAAaC,EAAK,MAAM,EACtD,GAAI,CAACc,EAAWH,CAAW,GAAKC,EAAK,WAAW,SAAW,EACzD,OAAOG,EAAY,aAAclB,EAAU,gEAA2D,EAGxG,IAAMmB,GADaF,EAAWH,CAAW,EAAIM,EAAaN,EAAa,OAAO,EAAI,IACrDO,EAAoBrB,EAAUe,EAAK,WAAYJ,EAAaC,CAAW,EAG9FU,EAAgB,KAAK,IAAI,EAC3BC,EAAqD,CAAC,EACtDC,EAAiB,GACrB,GAAI,CACF,IAAMC,EAAU,MAAMC,EAAwB1B,EAAUe,EAAK,KAAMb,EAAaI,EAAeK,EAAaC,CAAW,EACnHa,GACFF,EAAaE,EACbD,EAAiB,sBAAsBC,EAAQ,IAAKE,GAAMA,EAAE,KAAK,EAAE,KAAK,KAAK,MAE7EH,EAAiB,MAAMI,EACrBC,GAAoB7B,EAAUmB,EAASb,EAAeK,CAAW,EACjEP,EACAK,CACF,EACAc,EAAaO,GAAgBN,CAAc,EAE/C,OAASO,EAAP,CACA,OAAArB,EAAI,mBAAoBqB,GAAG,OAAO,EAC3B,CAAE,GAAGb,EAAY,QAASlB,EAAU,aAAa+B,GAAG,SAAW,WAAW,EAAG,MAAO,CAAE,WAAY,KAAK,IAAI,EAAIT,EAAe,YAAa,EAAG,eAAAE,CAAe,CAAE,CACxK,CACA,IAAMQ,EAAa,KAAK,IAAI,EAAIV,EAEhC,GAAIC,EAAW,SAAW,EACxB,MAAO,CAAE,GAAGL,EAAY,gBAAiBlB,EAAU,kCAAkC,EAAG,MAAO,CAAE,WAAAgC,EAAY,YAAa,EAAG,eAAAR,CAAe,CAAE,EAIhJ,IAAMS,EAASC,EAAaX,EAAYR,EAAMR,EAASC,CAAW,EAElE,GAAIyB,EAAO,SAAW,EACpB,MAAO,CAAE,GAAGf,EAAY,gBAAiBlB,EAAU,kDAAkD,EAAG,WAAAuB,EAAY,MAAO,CAAE,WAAAS,EAAY,YAAa,EAAG,eAAAR,CAAe,CAAE,EAI5K,IAAMW,EAAa,KAAK,IAAI,EACxBC,EAAS,GACb,GAAI,CACFA,EAAS,MAAMR,EACbS,GAAqBrC,EAAUiC,CAAM,EACrC5B,EACAI,EAAY,CACd,CACF,OAASsB,EAAP,CACA,OAAArB,EAAI,oBAAqBqB,GAAG,OAAO,EAC5B,CAAE,GAAGb,EAAY,QAASlB,EAAU,cAAc+B,GAAG,SAAW,WAAW,EAAG,WAAAR,EAAY,OAAQU,EAAO,IAAIK,IAAM,CAAE,MAAOA,EAAE,KAAK,MAAO,KAAMA,EAAE,KAAM,KAAMA,EAAE,KAAK,KAAM,IAAMA,EAAU,KAAO,CAAE,EAAE,EAAG,MAAO,CAAE,WAAAN,EAAY,YAAa,KAAK,IAAI,EAAIG,EAAY,eAAAX,CAAe,CAAE,CAC9R,CACA,IAAMe,EAAc,KAAK,IAAI,EAAIJ,EAEjC,MAAO,CACL,OAAQC,EAAO,KAAK,EACpB,UAAWH,EAAO,IAAIO,IAAM,CAAE,MAAOA,EAAE,KAAK,MAAO,KAAMA,EAAE,KAAM,KAAMA,EAAE,KAAK,IAAK,EAAE,EACrF,WAAAjB,EACA,OAAQU,EAAO,IAAIK,IAAM,CAAE,MAAOA,EAAE,KAAK,MAAO,KAAMA,EAAE,KAAM,KAAMA,EAAE,KAAK,KAAM,IAAMA,EAAU,KAAO,CAAE,EAAE,EAC5G,OAAQ,KACR,MAAO,CAAE,WAAAN,EAAY,YAAAO,EAAa,eAAAf,CAAe,CACnD,CACF,CA4BA,eAAsBiB,GACpBzC,EACAC,EACAC,EACAC,EAAwB,CAAC,EACsB,CAC/C,IAAMG,EAAgBH,EAAK,eAAiB,EACtCS,EAAcT,EAAK,aAAeU,EAClCF,EAAcR,EAAK,aAAa,OAASA,EAAK,YAAc,OAC5DuC,EAAOvC,EAAK,MAAQwC,EAAY1C,CAAK,EAC3C,GAAIyC,EAAK,SAAW,GAAK,CAAC1C,EAAS,KAAK,EAAG,MAAO,CAAC,EAEnD,IAAIuB,EAAa,MAAMG,EACrB1B,EAAU0C,EAAMxC,EAAaI,EAAeK,EAAaC,EAAaT,EAAK,OAAS,UACtF,EACA,GAAI,CAACoB,EAAY,CACf,IAAMqB,EAAU,IAAI,IAAIC,EAAS7C,EAAU8C,EAAWJ,EAAK,IAAIK,CAAU,CAAC,CAAC,EAAE,IAAKC,GAAMA,EAAE,QAAQ,CAAC,EACnGzB,EAAa0B,EAAgBjD,EAAU0C,EAAM/B,EAAaC,CAAW,EAClE,OAAQsC,GAAMN,EAAQ,IAAIM,CAAC,CAAC,EAC5B,MAAM,EAAG5C,CAAa,EACtB,IAAK4C,IAAO,CAAE,MAAOR,EAAKQ,CAAC,EAAE,MAAO,KAAMR,EAAKQ,CAAC,EAAE,IAAK,EAAE,EAE9D,OAAI3B,EAAW,SAAW,EAAU,CAAC,EAC9BW,EAAaX,EAAYP,EAAUf,EAAOC,CAAW,EAAGC,EAAK,SAAW,EAAGA,EAAK,aAAe,CAAC,CACzG,CAKO,SAASwC,EAAY1C,EAAkC,CAC5D,GAAI,CACF,OAAQA,EAAM,aAAa,EAAE,UAAY,CAAC,GAAG,OAAQuC,GAAMA,EAAE,MAAQ,CAACA,EAAE,KAAK,SAAS,aAAa,CAAC,CACtG,MAAE,CACA,MAAO,CAAC,CACV,CACF,CAIA,SAAStB,EACPiC,EACAC,EACAC,EACoB,CACpB,MAAO,CACL,OAAQ,GACR,UAAW,CAAC,EACZ,WAAY,CAAC,EACb,OAAQ,CAAC,EACT,OAAAF,EACA,MAAAE,CACF,CACF,CAkBA,SAASrC,EAAUf,EAAkBC,EAAiCoD,EAA4B,CAAC,EAAc,CAC/G,IAAMC,EAAa,IAAI,IACjBC,EAA2E,CAAC,EAClF,QAAWC,KAAWxD,EAAM,aAAaC,GAAe,EAAE,EACxDqD,EAAW,IAAIE,EAAQ,KAAK,MAAM,YAAY,EAAGA,EAAQ,IAAI,EAC7DD,EAAQ,KAAK,CAAE,KAAMC,EAAQ,KAAM,QAASA,EAAQ,KAAK,SAAW,CAAC,EAAG,YAAaA,EAAQ,KAAK,aAAe,EAAG,CAAC,EAEvH,IAAMC,EAA6B,CAAC,EAC9BC,EAAO,IAAI,IACjB,QAAW,KAAKL,EACd,GAAI,EAAE,MAAM,UAAYrD,EAAM,QAC9B,CAAA0D,EAAK,IAAI,EAAE,GAAI,CAAC,EAGhB,QAAW,KAAK,EAAE,MAAM,aAAazD,GAAe,EAAE,EAAG,CACvD,GAAI,EAAE,KAAK,SAAS,aAAa,GAAK,EAAE,OAAO,EAAE,IAAI,EAAG,SACxD,IAAM0D,EAAO,IAAI,EAAE,MAAM,EAAE,OAC3BF,EAAW,KAAKG,EAAa,EAAGD,CAAI,CAAC,EACrCJ,EAAQ,KAAK,CAAE,KAAAI,EAAM,QAAS,EAAE,KAAK,SAAW,CAAC,EAAG,YAAa,EAAE,KAAK,aAAe,EAAG,CAAC,EAC3F,IAAME,EAAM,EAAE,KAAK,MAAM,YAAY,EAChCP,EAAW,IAAIO,CAAG,GAAGP,EAAW,IAAIO,EAAKF,CAAI,GAMtD,IAAMG,EAAY,IAAI,IACtB,GAAIJ,EAAK,KAAO,EACd,QAAWK,IAAK,CAAC,GAAGR,CAAO,EAAE,KAAK,CAAC,EAAGS,IAAMA,EAAE,YAAY,cAAc,EAAE,WAAW,CAAC,EACpF,QAAWC,KAAKF,EAAE,QAAS,CACzB,IAAMF,EAAMI,EAAE,YAAY,EACpBC,EAAOJ,EAAU,IAAID,CAAG,GAAK,CAAC,EAC/BK,EAAK,SAASH,EAAE,IAAI,GAAGG,EAAK,KAAKH,EAAE,IAAI,EAC5CD,EAAU,IAAID,EAAKK,CAAI,EAI7B,IAAIzB,EACJ,MAAO,CACL,UAAAqB,EAGA,IAAI,MAAO,CAAE,OAAQrB,IAAS,CAAC,GAAGC,EAAY1C,CAAK,EAAG,GAAGyD,CAAU,CAAG,EACtE,WAAAA,EACA,WAAAH,EACA,KAAKK,EAAM,CACT,IAAMQ,EAAIR,EAAK,MAAM,kBAAkB,EACjCS,EAASD,EAAIT,EAAK,IAAIS,EAAE,CAAC,CAAC,EAAI,OACpC,GAAIA,GAAK,CAACC,EAAQ,OAAO,KACzB,IAAMC,EAAID,GAAQ,OAASpE,EACrBsE,EAAMH,EAAIA,EAAE,CAAC,EAAIR,EACjBH,EAAUvD,EAAcoE,EAAE,cAAcC,EAAKrE,CAAW,EAAIoE,EAAE,YAAYC,CAAG,EACnF,OAAOd,EAAU,CAAE,GAAGA,EAAS,KAAAG,CAAK,EAAI,IAC1C,CACF,CACF,CAEA,SAASC,EAAarB,EAAgBoB,EAA4B,CAChE,MAAO,CACL,KAAAA,EACA,MAAOpB,EAAE,KAAK,MACd,KAAMA,EAAE,KAAK,KACb,QAASA,EAAE,KAAK,QAChB,KAAMA,EAAE,KAAK,MAAQ,CAAC,EACtB,MAAOA,EAAE,KAAK,MACd,OAAQA,EAAE,KAAK,OACf,WAAYA,EAAE,KAAK,WACnB,QAAS,CAACA,EAAE,KAAK,MAAM,YAAY,EAAG,IAAIA,EAAE,KAAK,SAAW,CAAC,GAAG,IAAKgC,GAAMA,EAAE,YAAY,CAAC,CAAC,EAC3F,UAAW,EACX,QAAShC,EAAE,KAAK,QAChB,YAAaA,EAAE,KAAK,YACpB,UAAWA,EAAE,KAAK,SACpB,CACF,CAIO,IAAMiC,EAAuB,IAEpC,SAASpD,EAAoBrB,EAAkB0C,EAAsB/B,EAAmCC,EAA6B,CACnI,OAAI8B,EAAK,SAAW,EAAU,GAIvB;AAAA;AAAA;AAAA;AAAA,EAHOO,EAAgBjD,EAAU0C,EAAM/B,EAAaC,CAAW,EACnE,MAAM,EAAG6D,CAAoB,EAC7B,IAAKvB,GAAM,KAAKR,EAAKQ,CAAC,EAAE,QAAQR,EAAKQ,CAAC,EAAE,KAAO,KAAKR,EAAKQ,CAAC,EAAE,QAAU,OAAOR,EAAKQ,CAAC,EAAE,OAAO,EAChC,KAAK;AAAA,CAAI;AAAA,CAC1E,CAGA,IAAMwB,GAAqB,EAE3B,SAASxC,EACPX,EACAR,EACAR,EACAC,EACsC,CACtC,IAAM+C,EAAaxC,EAAK,WAClB4D,EAAYf,GAAiBA,EAAK,WAAW,GAAG,EAKhDgB,EAAU,CAAC,GAAGrD,EAAW,OAAQI,GAAM,CAACgD,EAAShD,EAAE,IAAI,CAAC,EAAG,GAAGJ,EAAW,OAAQI,GAAMgD,EAAShD,EAAE,IAAI,CAAC,CAAC,EAC1GkD,EAAYD,EAAQ,OAAS,GAAK,CAACD,EAASC,EAAQ,CAAC,EAAE,IAAI,EAAI,KAAK,MAAMpE,EAAc,CAAC,EAAIA,EAE3FsE,EAAS,IAAI,IACfC,EAAe,EACfC,EAAiDJ,EAAQ,IAAKjD,IAAO,CAAE,KAAMA,EAAE,KAAM,IAAK,CAAE,EAAE,EAI5FsD,EAAiD,CAAC,EAExD,KAAOH,EAAO,KAAOtE,GAAa,CAChC,GAAI,CAACwE,EAAS,OAAQ,CACpB,GAAI,CAACC,EAAS,QAAUJ,GAAarE,EAAa,MAClDqE,EAAYrE,EACZwE,EAAWC,EAAS,OAAO,CAAC,EAE9B,IAAMC,EAAOF,EAAS,MAAM,EAE5B,GADIF,EAAO,IAAII,EAAK,IAAI,GACpBA,EAAK,IAAM3E,EAAS,SACxB,GAAIoE,EAASO,EAAK,IAAI,GAAKH,GAAgBF,EAAW,CAAEI,EAAS,KAAKC,CAAI,EAAG,SAE7E,IAAMzB,EAAU1C,EAAK,KAAKmE,EAAK,IAAI,EACnC,GAAKzB,IAELqB,EAAO,IAAII,EAAK,KAAM,CAAE,GAAGzB,EAAS,IAAKyB,EAAK,GAAI,CAAC,EAC/CP,EAASO,EAAK,IAAI,GAAGH,IACrB,EAAAG,EAAK,KAAO3E,IAEhB,IAAI2E,EAAK,MAAQ,EAAG,CAGlB,IAAMC,EAAQ,CAAC1B,EAAQ,KAAK,MAAO,GAAIA,EAAQ,KAAK,SAAW,CAAC,CAAE,EAAE,IAAK2B,GAAMA,EAAE,YAAY,CAAC,EACxF5B,EAAU,CAAC,GAAG,IAAI,IAAI2B,EAAM,QAASC,GAAMrE,EAAK,UAAU,IAAIqE,CAAC,GAAK,CAAC,CAAC,CAAC,CAAC,EAC3E,OAAQxB,GAASA,IAASsB,EAAK,MAAQ,CAACJ,EAAO,IAAIlB,CAAI,CAAC,EACxD,MAAM,EAAGc,EAAkB,EAC9B,QAAWd,KAAQJ,EAASwB,EAAS,KAAK,CAAE,KAAApB,EAAM,IAAK,CAAE,CAAC,EAE5D,QAAWS,KAAUZ,EAAQ,KAAK,SAAW,CAAC,EAAG,CAC/C,IAAMG,EAAOL,EAAW,IAAIc,EAAO,YAAY,CAAC,EAC5CT,GAAQ,CAACkB,EAAO,IAAIlB,CAAI,GAC1BoB,EAAS,KAAK,CAAE,KAAApB,EAAM,IAAKsB,EAAK,IAAM,CAAE,CAAC,IAK/C,OAAO,MAAM,KAAKJ,EAAO,OAAO,CAAC,CACnC,CAoBA,eAAepD,EACb1B,EACA0C,EACAxC,EACAI,EACAK,EACAC,EAAsBC,EACtBwE,EAAQ,mBACgD,CACxD,GAAI,CACF,GAAI3C,EAAK,OAAS,EAAG,OAAO,KAI5B,IAAMnB,EAFY0B,EAAgBjD,EAAU0C,EAAM/B,EAAaC,CAAW,EAAE,MAAM,EAAG0E,CAAiB,EAEtD,IAAKpC,IAAO,CAC1D,GAAI,IAAIA,IACR,MAAOR,EAAKQ,CAAC,EAAE,OAASR,EAAKQ,CAAC,EAAE,KAChC,KAAMR,EAAKQ,CAAC,EAAE,KAGd,QAAS,CACPR,EAAKQ,CAAC,EAAE,KAAO,SAASR,EAAKQ,CAAC,EAAE,OAAS,IACxCR,EAAKQ,CAAC,EAAE,SAAW,CAAC,GAAG,OAAS,aAAaR,EAAKQ,CAAC,EAAE,SAAW,CAAC,GAAG,KAAK,IAAI,IAAM,IACnFR,EAAKQ,CAAC,EAAE,WAAa,CAAC,GAAG,OAAS,WAAWR,EAAKQ,CAAC,EAAE,WAAa,CAAC,GAAG,KAAK,UAAK,IAAM,EACzF,EAAE,OAAO,OAAO,EAAE,KAAK,QAAK,CAC9B,EAAE,EAEIqC,EAAS,MAAMC,EACnBC,EACAC,EAAY,CAAE,MAAO1F,EAAU,WAAAuB,EAAY,aAAc,GAAI,CAAC,EAC9DoE,EAAgBpE,CAAU,EAC1B,CAAE,SAAU,CAAE,MAAOrB,GAAe,UAAW,MAAAmF,CAAM,CAAE,CACzD,EACA,GAAI,CAACE,GAAUA,EAAO,OAAS,SAAU,OAAO,KAGhD,IAAMK,EADUC,EAAUN,EAAO,QAAkBhE,CAAU,EACtC,OACpB,MAAM,EAAGjB,CAAa,EACtB,IAAKwF,GAAOpD,EAAK,OAAOoD,EAAG,MAAM,CAAC,CAAC,CAAC,CAAC,EACrC,OAAO,OAAO,EACd,IAAKtD,IAAO,CAAE,MAAOA,EAAE,MAAO,KAAMA,EAAE,IAAK,EAAE,EAChD,OAAOoD,EAAO,OAAS,EAAIA,EAAS,IACtC,MAAE,CACA,OAAO,IACT,CACF,CAIO,IAAM/E,EAAuB,GAmB7B,SAASoC,EACdjD,EACA0C,EACA/B,EACAC,EAAsBC,EACZ,CACV,IAAMkF,EAAOlD,EAAS7C,EAAU8C,EAAWJ,EAAK,IAAIK,CAAU,CAAC,CAAC,EAC1DiD,EAAUD,EAAK,OAAO,CAAC3B,EAAGpB,IAAM,KAAK,IAAIoB,EAAGpB,EAAE,KAAK,EAAG,CAAC,EACvDiD,EAAO,IAAI,IAAIF,EAAK,IAAK/C,GAAM,CAACA,EAAE,SAAUgD,EAAU,EAAIhD,EAAE,MAAQgD,EAAU,CAAC,CAAC,CAAC,EACjFE,EAAW,CAAC,CAACvF,GAAa,OAC1BwF,EAAU3D,GACd0D,GAAY1D,EAAE,WAAW,OAAS4D,EAAczF,EAAc6B,EAAE,SAAS,EAAI,EAEzE6D,EAAU,CAAC7B,EAAiC8B,IAAoCA,EAAE,MAAQ9B,EAAE,OAASA,EAAE,EAAI8B,EAAE,EAC7G1D,EAAUF,EACb,IAAI,CAACF,EAAGU,KAAO,CAAE,EAAAA,EAAG,OAAQ+C,EAAK,IAAI/C,CAAC,GAAK,IAAM,EAAItC,EAAcuF,EAAO3D,CAAC,EAAG,EAAE,EAChF,OAAQb,GAAMsE,EAAK,IAAItE,EAAE,CAAC,CAAC,EAC3B,KAAK0E,CAAO,EACTE,EAAa7D,EAChB,IAAI,CAACF,EAAGU,KAAO,CAAE,EAAAA,EAAG,MAAOiD,EAAO3D,CAAC,CAAE,EAAE,EACvC,OAAQb,GAAM,CAACsE,EAAK,IAAItE,EAAE,CAAC,GAAKA,EAAE,MAAQ,CAAC,EAC3C,KAAK0E,CAAO,EACTG,EAAS,CAAC,GAAG5D,EAAS,GAAG2D,CAAU,EAAE,IAAK5E,GAAMA,EAAE,CAAC,EACzD,QAAS,EAAI,EAAG,EAAIe,EAAK,OAAQ,IAAU8D,EAAO,SAAS,CAAC,GAAGA,EAAO,KAAK,CAAC,EAC5E,OAAOA,CACT,CAGA,SAASzD,EAAWP,EAAmE,CACrF,MAAO,CAACA,EAAE,OAAQA,EAAE,MAAQ,CAAC,GAAG,KAAK,GAAG,GAAIA,EAAE,SAAW,CAAC,GAAG,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,CAClF,CAEA,SAASX,GAAoB7B,EAAkBmB,EAAiBb,EAAuBK,EAAgC,CACrH,IAAMwF,EAASxF,GAAa,OACxB;AAAA;AAAA;AAAA,0DAA0EA,EAAY,KAAK,UAAK;AAAA,EAChG,GACJ,MAAO;AAAA;AAAA;AAAA;AAAA,EAIPX;AAAA,EACAmG;AAAA;AAAA;AAAA,EAGAhF;AAAA;AAAA;AAAA;AAAA,aAIWb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,0CAab,CAEA,SAAS+B,GACPrC,EACAyG,EACQ,CACR,IAAMC,EAAgBD,EACnB,IAAIjE,GAAK,CAGR,IAAMmE,EAAO,CAACnE,EAAE,KAAK,MAAQ,UAAUA,EAAE,KAAK,QAAU,GAAIA,EAAE,KAAK,YAAc,YAAYA,EAAE,KAAK,cAAgB,EAAE,EAAE,OAAO,OAAO,EAAE,KAAK,IAAI,EAC3IoE,EAAS,OAAOpE,EAAE,KAAK,gBAAWA,EAAE,KAAK,MAAQ,cAAcA,EAAE,cAAcA,EAAE,OAAOmE,EAAO,KAAKA,KAAU,KAC9GE,EAAUrE,EAAE,KAAK,SAAS,OAAS,YAAYA,EAAE,KAAK,QAAQ,KAAK,IAAI,IAAM,GACnF,MAAO,CAACoE,EAAQC,EAAS,GAAIrE,EAAE,OAAO,EAAE,OAAO,OAAO,EAAE,KAAK;AAAA,CAAI,CACnE,CAAC,EACA,KAAK;AAAA;AAAA;AAAA;AAAA,CAAa,EAErB,MAAO;AAAA;AAAA;AAAA;AAAA,EAIPxC;AAAA;AAAA,eAEayG,EAAS;AAAA;AAAA,EAEtBC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yFAOF,CAEA,SAAS5E,GAAgBgF,EAAqD,CAC5E,IAAMC,EAAQD,EAAI,MAAM,cAAc,EACtC,GAAI,CAACC,EAAO,MAAO,CAAC,EACpB,GAAI,CACF,IAAMC,EAAS,KAAK,MAAMD,EAAM,CAAC,CAAC,EAClC,OAAK,MAAM,QAAQC,CAAM,EAClBA,EACJ,OAAQxC,GAAWA,GAAK,OAAOA,EAAE,OAAU,UAAY,OAAOA,EAAE,MAAS,QAAQ,EACjF,IAAKA,IAAY,CAAE,MAAOA,EAAE,MAAO,KAAMA,EAAE,IAAK,EAAE,EAHlB,CAAC,CAItC,MAAE,CACA,MAAO,CAAC,CACV,CACF,CAEA,eAAe5C,EAAUqF,EAAgBC,EAAezG,EAAoC,CAG1F,GAAM,CAAE,cAAA0G,EAAe,UAAAC,EAAW,OAAAC,CAAO,EAAI,KAAM,QAAO,IAAI,EACxD,CAAE,OAAAC,CAAO,EAAI,KAAM,QAAO,IAAI,EAC9BC,EAAM1H,EAAQyH,EAAO,EAAG,qBAAqB,QAAQ,KAAK,EAChEF,EAAUG,EAAK,CAAE,UAAW,EAAK,CAAC,EAClC,IAAMC,EAAa3H,EAAQ0H,EAAK,UAAU,KAAK,IAAI,OAAO,EAC1DJ,EAAcK,EAAYP,CAAM,EAChC,GAAI,CACF,IAAMQ,EAAM,QAAQD,+DAAwEN,6FACtFJ,EAAMhH,EAAS2H,EAAK,CAAE,IAAKC,EAAa,EAAG,SAAU,QAAS,QAASjH,EAAW,UAAW,EAAI,KAAO,IAAK,CAAC,EACpH,GAAI,CACF,IAAMkH,EAAW,KAAK,MAAMb,CAAG,EAC/B,OAAO,OAAOa,EAAS,QAAUA,EAAS,SAAW,EAAE,CACzD,MAAE,CACA,OAAOb,CACT,CACF,QAAE,CACA,GAAI,CAAEO,EAAOG,EAAY,CAAE,MAAO,EAAK,CAAC,CAAE,MAAE,CAAO,CACrD,CACF,CAKA,eAAsBI,GAAmBC,EAAuC,CAC9E,GAAI,CACF,GAAM,CAAE,iBAAAC,CAAiB,EAAI,KAAM,QAAO,sBAAiB,EAC3D,OAAOA,EAAiBD,CAAU,EAAE,KAAK,MAAM,MACjD,MAAE,CACA,MAAO,EACT,CACF","names":["readFileSync","existsSync","init_seat","resolve","execSync","agenticQuery","question","store","requesterId","opts","selectorModel","synthModel","maxCandidates","maxHops","maxArticles","timeoutMs","log","messagePath","graphWeight","DEFAULT_GRAPH_WEIGHT","catalogPath","view","scopeView","existsSync","emptyResult","catalog","readFileSync","renderSharedCatalog","selectorStart","candidates","selectorOutput","viaSeat","selectCandidatesViaSeat","c","runClaude","buildSelectorPrompt","parseCandidates","e","selectorMs","walked","walkSubgraph","synthStart","answer","buildSynthesisPrompt","w","synthesisMs","a","retrieveArticles","pool","catalogPool","matched","scoreAll","buildIndex","catalogDoc","r","rankCatalogPool","i","status","_question","error","shared","titleIndex","linking","article","sharedPool","byId","path","catalogEntry","key","backlinks","l","b","t","list","m","target","s","rel","x","SHARED_CATALOG_LINES","BACKLINKS_PER_PICK","isShared","ordered","sharedCap","opened","sharedOpened","frontier","deferred","next","names","n","stage","DEFAULT_SHORTLIST","result","askSeat","WIKI_RERANK_SEAT","rerankState","rerankQuestions","picked","toRanking","id","bm25","maxBm25","text","useGraph","branch","ancestryScore","byScore","y","branchOnly","ranked","articles","articlesBlock","from","header","related","raw","match","parsed","prompt","model","writeFileSync","mkdirSync","rmSync","tmpdir","dir","promptPath","cmd","claudeCliEnv","envelope","sharedQueryEnabled","configPath","loadDaemonConfig"]}
|
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import{existsSync as M,mkdirSync as G,readdirSync as H,readFileSync as J,unlinkSync as
|
|
1
|
+
import{existsSync as M,mkdirSync as G,readdirSync as H,readFileSync as J,unlinkSync as O,writeFileSync as T}from"fs";import{resolve as u}from"path";import{createHash as V}from"crypto";import{existsSync as A,mkdirSync as U,readdirSync as F,readFileSync as X,renameSync as L,unlinkSync as q,writeFileSync as W}from"fs";import{basename as C,dirname as D,resolve as g}from"path";var z=20,$=class extends Error{constructor(n){super(n?"memory changed since you read it (etag mismatch)":"memory does not exist (etag mismatch)");this.currentEtag=n;this.name="MemoryConflictError"}};function h(r){return V("sha256").update(r).digest("hex").slice(0,16)}function R(r,e){if(!e)return;let n=r===null?null:h(r);if(e.ifNoneMatch==="*"&&n!==null)throw new $(n);if(e.ifMatch!==void 0&&e.ifMatch!==n)throw new $(n)}function _(r,e){let n=g(D(r),`.${C(r)}.${process.pid}.${Date.now()}.tmp`);W(n,e),L(n,r)}function j(r){return g(D(r),"_versions",C(r,".md"))}function E(r,e){if(e===null)return;let n=j(r);U(n,{recursive:!0});let i=new Date().toISOString().replace(/[:.]/g,"-"),t=g(n,`${i}.md`);for(let o=1;A(t);o++)t=g(n,`${i}-${o}.md`);W(t,e),B(n)}function B(r){let e=F(r).filter(n=>n.endsWith(".md")).sort();for(let n of e.slice(0,Math.max(0,e.length-z)))try{q(g(r,n))}catch{}}function P(r){let e=j(r);return A(e)?F(e).filter(n=>n.endsWith(".md")).sort().reverse().map(n=>({id:n.slice(0,-3),content:X(g(e,n),"utf-8")})):[]}var m=["user","feedback","project","reference"],N=class{baseDir;constructor(e={}){let n=e.baseDir??u(process.cwd(),".agentx");this.baseDir=u(n,"agent-memory")}dirOf(e){return u(this.baseDir,v(e))}dirFor(e){let n=v(e),i=u(this.baseDir,n);return G(i,{recursive:!0}),i}fileFor(e,n,i){return u(this.dirFor(e),`${n}_${v(i)}.md`)}indexPath(e){return u(this.dirFor(e),"MEMORY.md")}save(e,n){if(!m.includes(e.type))throw new Error(`invalid memory type: ${e.type}`);if(!e.name.trim())throw new Error("memory name is required");if(!e.description.trim())throw new Error("memory description is required");let i=this.fileFor(e.agentId,e.type,e.name),t=y(i);R(t,n);let o=new Date().toISOString(),s=o;if(t){let a=b(t);a&&(s=a.createdAt)}let c={name:v(e.name),type:e.type,description:e.description.trim(),body:e.body.trimEnd(),createdAt:s,updatedAt:o,...e.author?{author:e.author}:{},...e.taskId?{taskId:e.taskId}:{}},d=Q(c);return E(i,t),_(i,d),this.rewriteIndex(e.agentId),{...c,etag:h(d)}}append(e,n){let i=this.get(e.agentId,e.name),t=i?`${i.body.trimEnd()}
|
|
2
2
|
|
|
3
|
-
${e.body.trim()}`:e.body;return this.save({...e,type:i?.type??e.type,body:t},n)}get(e,n){for(let i of m){let t=this.fileFor(e,i,n);if(!M(t))continue;let o=y(t)??"",s=b(o);if(s)return{...s,etag:h(o)}}return null}list(e){let n=this.dirFor(e);if(!M(n))return[];let i=[];for(let t of H(n,{withFileTypes:!0})){if(!t.isFile()||!t.name.endsWith(".md")||t.name==="MEMORY.md")continue;let o=y(
|
|
4
|
-
`):M(o)&&
|
|
3
|
+
${e.body.trim()}`:e.body;return this.save({...e,type:i?.type??e.type,body:t},n)}get(e,n){for(let i of m){let t=this.fileFor(e,i,n);if(!M(t))continue;let o=y(t)??"",s=b(o);if(s)return{...s,etag:h(o)}}return null}list(e){let n=this.dirFor(e);if(!M(n))return[];let i=[];for(let t of H(n,{withFileTypes:!0})){if(!t.isFile()||!t.name.endsWith(".md")||t.name==="MEMORY.md")continue;let o=y(u(n,t.name))??"",s=b(o);s&&i.push({...s,etag:h(o)})}return i.sort((t,o)=>{let s=m.indexOf(t.type),c=m.indexOf(o.type);return s!==c?s-c:t.name.localeCompare(o.name)})}remove(e,n,i){let t=m.map(o=>this.fileFor(e,o,n)).filter(o=>M(o));if(t.length===0)return!1;i&&R(y(t[0]),i);for(let o of t)E(o,y(o)),O(o);return this.rewriteIndex(e),!0}versions(e,n){return m.flatMap(i=>P(this.fileFor(e,i,n))).sort((i,t)=>t.id.localeCompare(i.id)).map(i=>{let t=b(i.content);return{id:i.id,record:t?{...t,etag:h(i.content)}:null}})}restore(e,n,i,t={}){let o=this.versions(e,n).find(a=>a.id===i);if(!o?.record)return null;let{type:s,description:c,body:d}=o.record;return this.save({agentId:e,type:s,name:n,description:c,body:d,...t})}indexMarkdown(e,n=0){let i=this.indexPath(e);return Y(y(i)??"",n)}syncToWorkspace(e,n,i=0){if(!n)return;let t=this.indexMarkdown(e).trim(),o=u(n,".agentx-memory.md");t?T(o,t+`
|
|
4
|
+
`):M(o)&&O(o);let s=Y(t,i),c=u(n,"CLAUDE.md"),d="<!-- AGENTX-MEMORY-START \u2014 auto-managed by agentx; edit via `agentx memory` -->",a="<!-- AGENTX-MEMORY-END -->",l=s?`${d}
|
|
5
5
|
${s}
|
|
6
|
-
${a}`:"",f=y(c)??"",p=f.indexOf(d),x=f.indexOf(a),
|
|
6
|
+
${a}`:"",f=y(c)??"",p=f.indexOf(d),x=f.indexOf(a),k;if(p>=0&&x>p){let S=f.slice(0,p).replace(/\n+$/,""),w=f.slice(x+a.length).replace(/^\n+/,"");k=l?[S,l,w].filter(Boolean).join(`
|
|
7
7
|
|
|
8
8
|
`)+`
|
|
9
|
-
`:[
|
|
9
|
+
`:[S,w].filter(Boolean).join(`
|
|
10
10
|
|
|
11
|
-
`)+(
|
|
12
|
-
`:"")}else if(
|
|
11
|
+
`)+(S||w?`
|
|
12
|
+
`:"")}else if(l)k=f?f.replace(/\n+$/,"")+`
|
|
13
13
|
|
|
14
|
-
`+
|
|
15
|
-
`:
|
|
16
|
-
`;else return;T(c,
|
|
14
|
+
`+l+`
|
|
15
|
+
`:l+`
|
|
16
|
+
`;else return;T(c,k)}rewriteIndex(e){let n=this.list(e);if(n.length===0){let o=this.indexPath(e);M(o)&&O(o);return}let i={user:[],feedback:[],project:[],reference:[]};for(let o of n)i[o.type].push(o);let t=[];t.push(`# Memory \u2014 ${e}`,""),t.push("This is the agent's experiential memory: user preferences,"),t.push("feedback, project-specific context, and external references."),t.push("Updated via `agentx memory` CLI or by the agent itself.");for(let o of m){let s=i[o];if(s.length){t.push("",`## ${ee(o)}`,"");for(let c of s)t.push(`- **${c.name}** \u2014 ${c.description} _(updated ${c.updatedAt.slice(0,10)})_`)}}t.push(""),T(this.indexPath(e),t.join(`
|
|
17
17
|
`))}},K="memories not shown here. The full index is in .agentx-memory.md in this workspace, or run `agentx memory index`.";function Y(r,e){if(!e||e<=0||r.length<=e)return r;let n=r.replace(/\n+$/,"").split(`
|
|
18
|
-
`),i=p=>p.startsWith("- "),t=n.filter(i).length,o=p=>`_(${p} more ${K})_`,s=o(t).length+2,c=[],d=0,a=0;for(let p of n){let x=p.length+1;if(d+x>e-s)break;c.push(p),d+=x,i(p)&&a++}for(;c.length&&!i(c[c.length-1]);)c.pop();let
|
|
18
|
+
`),i=p=>p.startsWith("- "),t=n.filter(i).length,o=p=>`_(${p} more ${K})_`,s=o(t).length+2,c=[],d=0,a=0;for(let p of n){let x=p.length+1;if(d+x>e-s)break;c.push(p),d+=x,i(p)&&a++}for(;c.length&&!i(c[c.length-1]);)c.pop();let l=t-a;return(l>0?[...c,"",o(l)]:c).join(`
|
|
19
19
|
`).replace(/^\n+/,"")}function Q(r){return["---",`name: ${r.name}`,`description: ${I(r.description)}`,`type: ${r.type}`,`created: ${r.createdAt}`,`updated: ${r.updatedAt}`,...r.author?[`author: ${I(r.author)}`]:[],...r.taskId?[`task: ${I(r.taskId)}`]:[],"---",""].join(`
|
|
20
20
|
`)+r.body+`
|
|
21
21
|
`}function b(r){if(!r.startsWith(`---
|
|
22
22
|
`))return null;let e=r.indexOf(`
|
|
23
23
|
---
|
|
24
24
|
`,4);if(e<0)return null;let n=r.slice(4,e),i=r.slice(e+5).replace(/^\n+/,"").trimEnd(),t={};for(let o of n.split(`
|
|
25
|
-
`)){let s=o.indexOf(":");if(s<0)continue;let c=o.slice(0,s).trim(),d=Z(o.slice(s+1).trim());t[c]=d}return!t.name||!t.type||!t.description||!m.includes(t.type)?null:{name:t.name,type:t.type,description:t.description,body:i,createdAt:t.created??"",updatedAt:t.updated??t.created??"",...t.author?{author:t.author}:{},...t.task?{taskId:t.task}:{}}}function I(r){return/[:#\n]/.test(r)?`"${r.replace(/"/g,'\\"')}"`:r}function Z(r){return r.startsWith('"')&&r.endsWith('"')&&r.length>=2?r.slice(1,-1).replace(/\\"/g,'"'):r}function
|
|
26
|
-
//# sourceMappingURL=chunk-
|
|
25
|
+
`)){let s=o.indexOf(":");if(s<0)continue;let c=o.slice(0,s).trim(),d=Z(o.slice(s+1).trim());t[c]=d}return!t.name||!t.type||!t.description||!m.includes(t.type)?null:{name:t.name,type:t.type,description:t.description,body:i,createdAt:t.created??"",updatedAt:t.updated??t.created??"",...t.author?{author:t.author}:{},...t.task?{taskId:t.task}:{}}}function I(r){return/[:#\n]/.test(r)?`"${r.replace(/"/g,'\\"')}"`:r}function Z(r){return r.startsWith('"')&&r.endsWith('"')&&r.length>=2?r.slice(1,-1).replace(/\\"/g,'"'):r}function v(r){return r.toLowerCase().replace(/[^a-z0-9]+/g,"_").replace(/^_+|_+$/g,"")||"unnamed"}function ee(r){return r.charAt(0).toUpperCase()+r.slice(1)}function y(r){try{return J(r,"utf-8")}catch{return null}}export{$ as a,N as b};
|
|
26
|
+
//# sourceMappingURL=chunk-MEFPXHQJ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agents/agent-memory.ts","../src/agents/memory-versions.ts"],"sourcesContent":["import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { atomicWrite, checkCondition, etagOf, readVersions, snapshot, type WriteCondition } from \"./memory-versions\"\n\n// --- AgentMemory: Claude-Code-style structured memory per agent ---\n//\n// Four memory kinds, each a separate file with YAML frontmatter + markdown\n// body, gathered under a per-agent root index (MEMORY.md):\n//\n// user/ — \"who this human is, what they do, what they know\"\n// feedback/ — \"rules the user gave me; what to do / avoid\"\n// project/ — \"facts about current work the code can't reveal\"\n// reference/ — \"pointers to external systems (linear board, grafana, …)\"\n//\n// Layout on disk:\n// .agentx/agent-memory/<agentId>/\n// MEMORY.md — one-line hook per memory, auto-maintained\n// user_deep_backend_expertise.md\n// feedback_no_mock_database.md\n// ...\n// _versions/<type>_<name>/<timestamp>.md — prior versions (memory-versions.ts)\n//\n// Philosophy: the wiki is authoritative / cross-agent / documented.\n// Memory is experiential / per-agent / \"what I learned across runs.\"\n// Durable, cross-agent-relevant memories get promoted to shared wiki\n// articles by `agentx wiki promote` (src/wiki/promote.ts) — corroboration\n// across agents boosts confidence but isn't required. This module stays\n// read-only from the promotion pipeline's perspective; authors keep both\n// layers parallel.\n\nexport type MemoryType = \"user\" | \"feedback\" | \"project\" | \"reference\"\n\nexport interface MemoryRecord {\n /** Slug-safe name, unique within an agent. */\n name: string\n type: MemoryType\n /** One-line hook — used as the MEMORY.md index entry. */\n description: string\n /** The body, excluding frontmatter. */\n body: string\n createdAt: string\n updatedAt: string\n /** Who wrote this version: an agent id verified against its running\n * task, \"operator\" (CLI), or \"unverified\" (a caller we couldn't check). */\n author?: string\n /** The running task that wrote it; opens on the Task page. */\n taskId?: string\n /** Content hash of the file as stored. Pass back as `ifMatch` to change\n * it only if nobody else has since. Not stored in the file. */\n etag?: string\n}\n\n/** Who is making a change, recorded in the memory's frontmatter. */\nexport interface MemoryProvenance {\n author?: string\n taskId?: string\n}\n\n/** One entry in a memory's history. */\nexport interface MemoryVersion {\n id: string\n record: MemoryRecord | null\n}\n\nconst TYPES: MemoryType[] = [\"user\", \"feedback\", \"project\", \"reference\"]\n\nexport interface AgentMemoryOptions {\n baseDir?: string\n}\n\nexport class AgentMemory {\n readonly baseDir: string\n\n constructor(opts: AgentMemoryOptions = {}) {\n const root = opts.baseDir ?? resolve(process.cwd(), \".agentx\")\n this.baseDir = resolve(root, \"agent-memory\")\n }\n\n /** The agent's note folder, without creating it. */\n dirOf(agentId: string): string {\n return resolve(this.baseDir, slug(agentId))\n }\n\n private dirFor(agentId: string): string {\n const safe = slug(agentId)\n const dir = resolve(this.baseDir, safe)\n mkdirSync(dir, { recursive: true })\n return dir\n }\n\n private fileFor(agentId: string, type: MemoryType, name: string): string {\n return resolve(this.dirFor(agentId), `${type}_${slug(name)}.md`)\n }\n\n private indexPath(agentId: string): string {\n return resolve(this.dirFor(agentId), \"MEMORY.md\")\n }\n\n /** Save (create or update) a memory. Keeps createdAt stable across\n * updates; bumps updatedAt. The replaced version goes to history first.\n * With `cond`, throws MemoryConflictError instead of overwriting a\n * version the caller hasn't seen. Always rewrites MEMORY.md. */\n save(args: {\n agentId: string\n type: MemoryType\n name: string\n description: string\n body: string\n } & MemoryProvenance, cond?: WriteCondition): MemoryRecord {\n if (!TYPES.includes(args.type)) throw new Error(`invalid memory type: ${args.type}`)\n if (!args.name.trim()) throw new Error(\"memory name is required\")\n if (!args.description.trim()) throw new Error(\"memory description is required\")\n\n const path = this.fileFor(args.agentId, args.type, args.name)\n const existing = safeRead(path)\n checkCondition(existing, cond)\n const now = new Date().toISOString()\n let createdAt = now\n if (existing) {\n const parsed = parseMemoryFile(existing)\n if (parsed) createdAt = parsed.createdAt\n }\n const record: MemoryRecord = {\n name: slug(args.name),\n type: args.type,\n description: args.description.trim(),\n body: args.body.trimEnd(),\n createdAt,\n updatedAt: now,\n ...(args.author ? { author: args.author } : {}),\n ...(args.taskId ? { taskId: args.taskId } : {}),\n }\n const content = serialize(record)\n snapshot(path, existing)\n atomicWrite(path, content)\n this.rewriteIndex(args.agentId)\n return { ...record, etag: etagOf(content) }\n }\n\n /** Add to the end of an existing memory's body (or create it) in one\n * step, so two appends can't lose each other. */\n append(args: {\n agentId: string\n type: MemoryType\n name: string\n description: string\n body: string\n } & MemoryProvenance, cond?: WriteCondition): MemoryRecord {\n const existing = this.get(args.agentId, args.name)\n const body = existing ? `${existing.body.trimEnd()}\\n\\n${args.body.trim()}` : args.body\n return this.save({ ...args, type: existing?.type ?? args.type, body }, cond)\n }\n\n get(agentId: string, name: string): MemoryRecord | null {\n for (const type of TYPES) {\n const path = this.fileFor(agentId, type, name)\n if (!existsSync(path)) continue\n const raw = safeRead(path) ?? \"\"\n const parsed = parseMemoryFile(raw)\n if (parsed) return { ...parsed, etag: etagOf(raw) }\n }\n return null\n }\n\n /** Every memory for an agent. Sorted by type then name for stable\n * index rendering. */\n list(agentId: string): MemoryRecord[] {\n const dir = this.dirFor(agentId)\n if (!existsSync(dir)) return []\n const out: MemoryRecord[] = []\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".md\") || entry.name === \"MEMORY.md\") continue\n const raw = safeRead(resolve(dir, entry.name)) ?? \"\"\n const parsed = parseMemoryFile(raw)\n if (parsed) out.push({ ...parsed, etag: etagOf(raw) })\n }\n return out.sort((a, b) => {\n const ta = TYPES.indexOf(a.type)\n const tb = TYPES.indexOf(b.type)\n if (ta !== tb) return ta - tb\n return a.name.localeCompare(b.name)\n })\n }\n\n /** Remove a memory by name. The removed version stays in history, so\n * `restore` can bring it back. With `cond.ifMatch`, throws\n * MemoryConflictError if it changed since the caller read it. Returns\n * true on success, false if none found. */\n remove(agentId: string, name: string, cond?: WriteCondition): boolean {\n const paths = TYPES.map((t) => this.fileFor(agentId, t, name)).filter((p) => existsSync(p))\n if (paths.length === 0) return false\n if (cond) checkCondition(safeRead(paths[0]), cond)\n for (const path of paths) {\n snapshot(path, safeRead(path))\n unlinkSync(path)\n }\n this.rewriteIndex(agentId)\n return true\n }\n\n /** Prior versions of a memory, newest first, including ones kept after\n * it was deleted. */\n versions(agentId: string, name: string): MemoryVersion[] {\n return TYPES\n .flatMap((t) => readVersions(this.fileFor(agentId, t, name)))\n .sort((a, b) => b.id.localeCompare(a.id))\n .map((v) => {\n const parsed = parseMemoryFile(v.content)\n return { id: v.id, record: parsed ? { ...parsed, etag: etagOf(v.content) } : null }\n })\n }\n\n /** Put a prior version back as the current one. The version it replaces\n * goes to history, so a restore can itself be undone. */\n restore(agentId: string, name: string, versionId: string, by: MemoryProvenance = {}): MemoryRecord | null {\n const version = this.versions(agentId, name).find((v) => v.id === versionId)\n if (!version?.record) return null\n const { type, description, body } = version.record\n return this.save({ agentId, type, name, description, body, ...by })\n }\n\n /** The content of MEMORY.md — used at prompt-build time to inline into\n * the agent's system prompt. Returns \"\" when the agent has no\n * memories (so the prompt builder can no-op). With `maxChars` > 0 the\n * index is cut to that size (#615): whole lines only, and a closing line\n * says how many memories were left out and where the full index is. */\n indexMarkdown(agentId: string, maxChars = 0): string {\n const path = this.indexPath(agentId)\n return trimMemoryIndex(safeRead(path) ?? \"\", maxChars)\n }\n\n /** Push the agent's memory into its workspace so Claude Code sessions\n * pick it up automatically. Does two things:\n *\n * 1. Writes `<workspace>/.agentx-memory.md` — full MEMORY.md content,\n * explicit file the skill can `Read` at session start.\n * 2. Merges that same content into `<workspace>/CLAUDE.md` between\n * sentinel comments so Claude Code auto-loads it into the cached\n * system context on every session. Idempotent: subsequent syncs\n * replace the sentinel block.\n *\n * Never touches content outside the sentinel block — if the file\n * doesn't exist, creates it with just the sentinels. Safe to call\n * on any save / delete. */\n syncToWorkspace(agentId: string, workspacePath: string, maxChars = 0): void {\n if (!workspacePath) return\n const full = this.indexMarkdown(agentId).trim()\n\n // 1. Explicit file for the `remember` skill to Read if it wants to.\n // Always the whole index: it is what the capped block points at.\n const explicitPath = resolve(workspacePath, \".agentx-memory.md\")\n if (full) {\n writeFileSync(explicitPath, full + \"\\n\")\n } else if (existsSync(explicitPath)) {\n unlinkSync(explicitPath)\n }\n\n // 2. Sentinel-merged into CLAUDE.md for auto-inject, capped when the\n // operator set session.memoryIndexMaxChars (#615).\n const memory = trimMemoryIndex(full, maxChars)\n const claudePath = resolve(workspacePath, \"CLAUDE.md\")\n const start = \"<!-- AGENTX-MEMORY-START — auto-managed by agentx; edit via `agentx memory` -->\"\n const end = \"<!-- AGENTX-MEMORY-END -->\"\n const block = memory\n ? `${start}\\n${memory}\\n${end}`\n : \"\"\n\n const existing = safeRead(claudePath) ?? \"\"\n const startIdx = existing.indexOf(start)\n const endIdx = existing.indexOf(end)\n\n let next: string\n if (startIdx >= 0 && endIdx > startIdx) {\n // Replace existing sentinel block (or remove it if memory is empty).\n const before = existing.slice(0, startIdx).replace(/\\n+$/, \"\")\n const after = existing.slice(endIdx + end.length).replace(/^\\n+/, \"\")\n next = block\n ? [before, block, after].filter(Boolean).join(\"\\n\\n\") + \"\\n\"\n : [before, after].filter(Boolean).join(\"\\n\\n\") + (before || after ? \"\\n\" : \"\")\n } else if (block) {\n // No prior block — append to the end of CLAUDE.md (creates the file\n // if needed). Keeps any existing content untouched.\n next = existing\n ? existing.replace(/\\n+$/, \"\") + \"\\n\\n\" + block + \"\\n\"\n : block + \"\\n\"\n } else {\n // Empty memory + no prior block → nothing to do.\n return\n }\n writeFileSync(claudePath, next)\n }\n\n /** Regenerate MEMORY.md from the on-disk files. Called automatically\n * after every save / remove; safe to call manually. */\n rewriteIndex(agentId: string): void {\n const entries = this.list(agentId)\n if (entries.length === 0) {\n const p = this.indexPath(agentId)\n if (existsSync(p)) unlinkSync(p)\n return\n }\n const grouped: Record<MemoryType, MemoryRecord[]> = {\n user: [], feedback: [], project: [], reference: [],\n }\n for (const r of entries) grouped[r.type].push(r)\n\n const lines: string[] = []\n lines.push(`# Memory — ${agentId}`, \"\")\n lines.push(\"This is the agent's experiential memory: user preferences,\")\n lines.push(\"feedback, project-specific context, and external references.\")\n lines.push(\"Updated via `agentx memory` CLI or by the agent itself.\")\n for (const type of TYPES) {\n const group = grouped[type]\n if (!group.length) continue\n lines.push(\"\", `## ${titleCase(type)}`, \"\")\n for (const r of group) {\n lines.push(`- **${r.name}** — ${r.description} _(updated ${r.updatedAt.slice(0, 10)})_`)\n }\n }\n lines.push(\"\")\n writeFileSync(this.indexPath(agentId), lines.join(\"\\n\"))\n }\n}\n\n/** What a cut index ends with: how many entries are missing and where the\n * whole index is (the workspace file, or `agentx memory index`). */\nexport const MEMORY_INDEX_CUT_NOTE = \"memories not shown here. The full index is in .agentx-memory.md in this workspace, or run `agentx memory index`.\"\n\n/** Cut a MEMORY.md index down to `maxChars` (#615). 0 or a size the index\n * already fits in returns it unchanged. Lines are kept whole, in order,\n * until the budget is spent; a type heading left with no entry under it\n * is dropped; the closing note counts the entries left out. The budget\n * includes that note, so the result never exceeds `maxChars`. */\nexport function trimMemoryIndex(markdown: string, maxChars: number): string {\n if (!maxChars || maxChars <= 0 || markdown.length <= maxChars) return markdown\n const lines = markdown.replace(/\\n+$/, \"\").split(\"\\n\")\n const isEntry = (line: string) => line.startsWith(\"- \")\n const total = lines.filter(isEntry).length\n // The note is longest when every entry is left out; reserve for that.\n const noteFor = (left: number) => `_(${left} more ${MEMORY_INDEX_CUT_NOTE})_`\n const reserve = noteFor(total).length + 2\n const kept: string[] = []\n let used = 0\n let entries = 0\n for (const line of lines) {\n const cost = line.length + 1\n if (used + cost > maxChars - reserve) break\n kept.push(line)\n used += cost\n if (isEntry(line)) entries++\n }\n // A heading (or blank) at the tail with no entry under it says nothing.\n while (kept.length && !isEntry(kept[kept.length - 1])) kept.pop()\n const left = total - entries\n const out = left > 0 ? [...kept, \"\", noteFor(left)] : kept\n return out.join(\"\\n\").replace(/^\\n+/, \"\")\n}\n\n// --- Serialisation helpers ---------------------------------------------\n\nfunction serialize(r: MemoryRecord): string {\n const frontmatter = [\n \"---\",\n `name: ${r.name}`,\n `description: ${escapeYaml(r.description)}`,\n `type: ${r.type}`,\n `created: ${r.createdAt}`,\n `updated: ${r.updatedAt}`,\n ...(r.author ? [`author: ${escapeYaml(r.author)}`] : []),\n ...(r.taskId ? [`task: ${escapeYaml(r.taskId)}`] : []),\n \"---\",\n \"\",\n ].join(\"\\n\")\n return frontmatter + r.body + \"\\n\"\n}\n\n/** Minimal frontmatter parser. Accepts `key: value` lines between `---`\n * markers; body is everything after. No YAML flow syntax — if we need\n * it later, swap for a proper parser. */\nexport function parseMemoryFile(raw: string): MemoryRecord | null {\n if (!raw.startsWith(\"---\\n\")) return null\n const end = raw.indexOf(\"\\n---\\n\", 4)\n if (end < 0) return null\n const header = raw.slice(4, end)\n const body = raw.slice(end + 5).replace(/^\\n+/, \"\").trimEnd()\n const fm: Record<string, string> = {}\n for (const line of header.split(\"\\n\")) {\n const colon = line.indexOf(\":\")\n if (colon < 0) continue\n const key = line.slice(0, colon).trim()\n const val = unescapeYaml(line.slice(colon + 1).trim())\n fm[key] = val\n }\n if (!fm.name || !fm.type || !fm.description) return null\n if (!TYPES.includes(fm.type as MemoryType)) return null\n return {\n name: fm.name,\n type: fm.type as MemoryType,\n description: fm.description,\n body,\n createdAt: fm.created ?? \"\",\n updatedAt: fm.updated ?? fm.created ?? \"\",\n ...(fm.author ? { author: fm.author } : {}),\n ...(fm.task ? { taskId: fm.task } : {}),\n }\n}\n\nfunction escapeYaml(s: string): string {\n if (/[:#\\n]/.test(s)) return `\"${s.replace(/\"/g, \"\\\\\\\"\")}\"`\n return s\n}\n\nfunction unescapeYaml(s: string): string {\n if (s.startsWith(\"\\\"\") && s.endsWith(\"\\\"\") && s.length >= 2) {\n return s.slice(1, -1).replace(/\\\\\"/g, \"\\\"\")\n }\n return s\n}\n\nfunction slug(s: string): string {\n return s.toLowerCase().replace(/[^a-z0-9]+/g, \"_\").replace(/^_+|_+$/g, \"\") || \"unnamed\"\n}\n\nfunction titleCase(s: string): string {\n return s.charAt(0).toUpperCase() + s.slice(1)\n}\n\nfunction safeRead(path: string): string | null {\n try { return readFileSync(path, \"utf-8\") } catch { return null }\n}\n","import { createHash } from \"crypto\"\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from \"fs\"\nimport { basename, dirname, resolve } from \"path\"\n\n// --- Version history and write safety for AgentMemory files ---\n//\n// Every overwrite or delete of a memory file first copies the current file\n// into `_versions/<file stem>/<timestamp>.md` next to it, so any change can\n// be rolled back. Writes go through a temp file and a rename, so a reader\n// never sees half a file. An etag (content hash) lets callers make\n// conditional writes: two agents editing the same memory can no longer\n// silently overwrite each other.\n\n/** Old versions kept per memory. The oldest are pruned past this. */\nexport const MAX_VERSIONS = 20\n\nexport class MemoryConflictError extends Error {\n constructor(readonly currentEtag: string | null) {\n super(currentEtag\n ? \"memory changed since you read it (etag mismatch)\"\n : \"memory does not exist (etag mismatch)\")\n this.name = \"MemoryConflictError\"\n }\n}\n\n/** Conditions for a write, mirroring HTTP If-Match / If-None-Match. */\nexport interface WriteCondition {\n /** Write only when the current file has this etag. */\n ifMatch?: string\n /** \"*\": write only when no memory exists yet (create, never overwrite). */\n ifNoneMatch?: \"*\"\n}\n\nexport function etagOf(content: string): string {\n return createHash(\"sha256\").update(content).digest(\"hex\").slice(0, 16)\n}\n\n/** Throws MemoryConflictError when `current` (file content, or null when\n * absent) doesn't satisfy `cond`. No condition always passes. */\nexport function checkCondition(current: string | null, cond: WriteCondition | undefined): void {\n if (!cond) return\n const etag = current === null ? null : etagOf(current)\n if (cond.ifNoneMatch === \"*\" && etag !== null) throw new MemoryConflictError(etag)\n if (cond.ifMatch !== undefined && cond.ifMatch !== etag) throw new MemoryConflictError(etag)\n}\n\nexport function atomicWrite(path: string, content: string): void {\n const tmp = resolve(dirname(path), `.${basename(path)}.${process.pid}.${Date.now()}.tmp`)\n writeFileSync(tmp, content)\n renameSync(tmp, path)\n}\n\nfunction versionsDir(memoryFile: string): string {\n return resolve(dirname(memoryFile), \"_versions\", basename(memoryFile, \".md\"))\n}\n\n/** Copy the current file into its history before it is replaced or\n * removed. No-op when the file doesn't exist yet. */\nexport function snapshot(memoryFile: string, current: string | null): void {\n if (current === null) return\n const dir = versionsDir(memoryFile)\n mkdirSync(dir, { recursive: true })\n const stamp = new Date().toISOString().replace(/[:.]/g, \"-\")\n let path = resolve(dir, `${stamp}.md`)\n for (let n = 1; existsSync(path); n++) path = resolve(dir, `${stamp}-${n}.md`)\n writeFileSync(path, current)\n prune(dir)\n}\n\nfunction prune(dir: string): void {\n const files = readdirSync(dir).filter((f) => f.endsWith(\".md\")).sort()\n for (const f of files.slice(0, Math.max(0, files.length - MAX_VERSIONS))) {\n try { unlinkSync(resolve(dir, f)) } catch { /* already gone */ }\n }\n}\n\nexport interface StoredVersion {\n /** File stem, e.g. 2026-09-26T07-30-00-123Z. Pass to restore. */\n id: string\n content: string\n}\n\n/** Stored versions of a memory file, newest first. */\nexport function readVersions(memoryFile: string): StoredVersion[] {\n const dir = versionsDir(memoryFile)\n if (!existsSync(dir)) return []\n return readdirSync(dir)\n .filter((f) => f.endsWith(\".md\"))\n .sort()\n .reverse()\n .map((f) => ({ id: f.slice(0, -3), content: readFileSync(resolve(dir, f), \"utf-8\") }))\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","writeFileSync","resolve","createHash","existsSync","mkdirSync","readdirSync","readFileSync","renameSync","unlinkSync","writeFileSync","basename","dirname","resolve","MAX_VERSIONS","MemoryConflictError","currentEtag","etagOf","content","checkCondition","current","cond","etag","atomicWrite","path","tmp","versionsDir","memoryFile","snapshot","dir","stamp","n","prune","files","f","readVersions","TYPES","AgentMemory","opts","root","resolve","agentId","slug","safe","dir","mkdirSync","type","name","args","cond","path","existing","safeRead","checkCondition","now","createdAt","parsed","parseMemoryFile","record","content","serialize","snapshot","atomicWrite","etagOf","body","existsSync","raw","out","entry","readdirSync","a","b","ta","tb","paths","t","p","unlinkSync","readVersions","v","versionId","by","version","description","maxChars","trimMemoryIndex","workspacePath","full","explicitPath","writeFileSync","memory","claudePath","start","end","block","startIdx","endIdx","next","before","after","entries","grouped","r","lines","group","titleCase","MEMORY_INDEX_CUT_NOTE","markdown","isEntry","line","total","noteFor","left","reserve","kept","used","cost","escapeYaml","header","fm","colon","key","val","unescapeYaml","s","readFileSync"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import{b as g}from"./chunk-M7IJVOJA.js";async function $(e,i,r,n,o={}){let a=o.retrieve??g,c=o.perEntry??2,s=o.maxArticles??8,f=o.queryChars??1500,h=new Map;for(let t of e){let l=[];try{l=await a(t.content.slice(0,f),i,r,{pool:n,maxCandidates:c,maxHops:1,maxArticles:c*2,stage:"absorb-covering"})}catch{}for(let u of l){let p=h.get(u.path);p?(p.hop=Math.min(p.hop,u.hop),p.entries.includes(t.id)||p.entries.push(t.id)):h.set(u.path,{...u,entries:[t.id]})}}return[...h.values()].sort((t,l)=>t.hop-l.hop||l.entries.length-t.entries.length||t.path.localeCompare(l.path)).slice(0,s)}function x(e){if(e.length===0)return"";let i=e.map(r=>{let n=`### ${r.meta.title} \u2014 ${r.meta.type||"untyped"} (${r.path})`,o=`Found for entries: ${r.entries.join(", ")}${r.hop>0?" (linked from a match)":""}`,a=r.meta.related?.length?`Related: ${r.meta.related.join(", ")}`:"";return[n,o,a,"",r.content].filter(c=>c!=="").join(`
|
|
2
|
+
`)}).join(`
|
|
3
|
+
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`);return`
|
|
7
|
+
## Articles already covering these entries
|
|
8
|
+
|
|
9
|
+
These ${e.length} articles were found by searching the wiki for each entry below. Read them before you write.
|
|
10
|
+
|
|
11
|
+
- When an entry is about the subject of one of these articles, UPDATE that article: write it at the SAME path with the SAME title, carrying its full content merged with what the entry adds.
|
|
12
|
+
- Keep every fact the article already has: commit hashes, URLs, numbers, dates and [[wikilinks]]. Restructure freely, but do not drop one unless an entry explicitly says it is wrong. A save that drops one is refused and the entries stay queued.
|
|
13
|
+
- Do not create a second article (a new path or a near-identical title) for a subject one of these already covers.
|
|
14
|
+
|
|
15
|
+
${i}
|
|
16
|
+
`}var d=/\bhttps?:\/\/[^\s<>"'`)\]]+/gi,y=/\[\[([^\]|]+)(?:\|[^\]]*)?\]\]/g,w=/(?<![\w-])(?=[0-9a-f]*[0-9])(?=[0-9a-f]*[a-f])[0-9a-f]{7,40}(?![\w-])/gi,k=/(?<![\w.,:/-])\d+(?:[.,:\-/]\d+)*(?![\w])/g,C=/^\s*\d+[.)]\s/gm;function A(e){let i=[...e.matchAll(d)].map(s=>s[0].replace(/[.,;:!?'"]+$/,"")),r=[...e.matchAll(y)].map(s=>s[1].trim()),n=e.replace(d," ").replace(y," ").replace(C," "),o=[...n.matchAll(w)].map(s=>s[0].toLowerCase()),c=[...n.replace(w," ").matchAll(k)].map(s=>v(s[0]));return{commits:m(o),urls:m(i),wikilinks:m(r),numbers:m(c)}}function R(e,i){let r=A(e),n=A(i),o=i.toLowerCase(),a=[];for(let t of r.commits)!o.includes(t)&&!n.commits.some(l=>l.startsWith(t)||t.startsWith(l))&&a.push(`commit ${t}`);let c=t=>t.toLowerCase().replace(/\/+$/,""),s=new Set(n.urls.map(c));for(let t of r.urls)s.has(c(t))||a.push(`link ${t}`);let f=new Set(n.wikilinks.map(t=>t.toLowerCase()));for(let t of r.wikilinks)f.has(t.toLowerCase())||a.push(`link [[${t}]]`);let h=new Set(n.numbers);for(let t of r.numbers)h.has(t)||a.push(`number ${t}`);return a}function v(e){return/^\d{1,3}(,\d{3})+$/.test(e)?e.replace(/,/g,""):e}function m(e){return[...new Set(e)]}function L(e,i){if(i.some(n=>n.path===e.path))return e.path;let r=b(e.title);return r?i.find(n=>b(n.title)===r)?.path??e.path:e.path}function b(e){return(e||"").toLowerCase().replace(/[^\p{L}\p{N}]+/gu," ").trim()}export{$ as a,x as b,A as c,R as d,L as e};
|
|
17
|
+
//# sourceMappingURL=chunk-PVUNDNT3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/wiki/absorb-context.ts"],"sourcesContent":["// What `agentx wiki absorb` knows about the wiki before it writes (#801).\n//\n// Absorb used to show the model the catalog — titles and paths — and\n// nothing else. In the 2026-10-07 A/B one model rewrote an existing event\n// article it had never read and lost the commit, the test count and the\n// review URL; the other wrote a second event article beside the first.\n// Both are what a writer does when it cannot see the page it is editing.\n//\n// So before the compile call, each entry is run through the wiki query\n// retrieval (BM25 shortlist, rerank seat, wikilink walk) and the articles\n// it lands on go into the prompt in full. After the call, a save that\n// would drop a commit, link or number from the article it replaces is\n// refused, and a new article whose title an existing one already has is\n// written over that one instead of beside it.\n\nimport { retrieveArticles, type CatalogEntry } from \"./query\"\nimport type { WikiArticle } from \"./types\"\nimport type { WikiStore } from \"./store\"\n\nexport type CoveringArticle = WikiArticle & { hop: number; entries: string[] }\n\nexport interface FindCoveringOptions {\n /** Articles picked per entry before the walk. Default 2. */\n perEntry?: number\n /** Cap on articles across the batch. Default 8. */\n maxArticles?: number\n /** Query text taken from each entry. Default 1500 chars. */\n queryChars?: number\n /** Swappable for tests. */\n retrieve?: typeof retrieveArticles\n}\n\n/**\n * The existing articles a batch of entries is about. Each entry is its own\n * query, so one batch mixing three subjects finds all three. Articles\n * picked directly (hop 0) and by more entries come first.\n */\nexport async function findCoveringArticles(\n entries: Array<{ id: string; content: string }>,\n store: WikiStore,\n agentId: string,\n pool: CatalogEntry[],\n opts: FindCoveringOptions = {},\n): Promise<CoveringArticle[]> {\n const retrieve = opts.retrieve ?? retrieveArticles\n const perEntry = opts.perEntry ?? 2\n const maxArticles = opts.maxArticles ?? 8\n const queryChars = opts.queryChars ?? 1500\n const found = new Map<string, CoveringArticle>()\n\n for (const entry of entries) {\n let hits: Array<WikiArticle & { hop: number }> = []\n try {\n hits = await retrieve(entry.content.slice(0, queryChars), store, agentId, {\n pool,\n maxCandidates: perEntry,\n maxHops: 1,\n maxArticles: perEntry * 2,\n stage: \"absorb-covering\",\n })\n } catch {\n // Retrieval is an aid; an absorb without it is the old absorb.\n }\n for (const hit of hits) {\n const seen = found.get(hit.path)\n if (seen) {\n seen.hop = Math.min(seen.hop, hit.hop)\n if (!seen.entries.includes(entry.id)) seen.entries.push(entry.id)\n } else {\n found.set(hit.path, { ...hit, entries: [entry.id] })\n }\n }\n }\n\n return [...found.values()]\n .sort((a, b) => a.hop - b.hop || b.entries.length - a.entries.length || a.path.localeCompare(b.path))\n .slice(0, maxArticles)\n}\n\n/** The prompt section carrying the covering articles, in full. Articles are\n * never cut short: the drop-guard checks the whole old article, so the\n * model must have seen all of it. */\nexport function renderCoveringBlock(articles: CoveringArticle[]): string {\n if (articles.length === 0) return \"\"\n const body = articles\n .map((a) => {\n const header = `### ${a.meta.title} — ${a.meta.type || \"untyped\"} (${a.path})`\n const why = `Found for entries: ${a.entries.join(\", \")}${a.hop > 0 ? \" (linked from a match)\" : \"\"}`\n const related = a.meta.related?.length ? `Related: ${a.meta.related.join(\", \")}` : \"\"\n return [header, why, related, \"\", a.content].filter((l) => l !== \"\").join(\"\\n\")\n })\n .join(\"\\n\\n---\\n\\n\")\n return `\n## Articles already covering these entries\n\nThese ${articles.length} articles were found by searching the wiki for each entry below. Read them before you write.\n\n- When an entry is about the subject of one of these articles, UPDATE that article: write it at the SAME path with the SAME title, carrying its full content merged with what the entry adds.\n- Keep every fact the article already has: commit hashes, URLs, numbers, dates and [[wikilinks]]. Restructure freely, but do not drop one unless an entry explicitly says it is wrong. A save that drops one is refused and the entries stay queued.\n- Do not create a second article (a new path or a near-identical title) for a subject one of these already covers.\n\n${body}\n`\n}\n\n// --- Drop-guard ---\n\nexport interface ArticleFacts {\n commits: string[]\n urls: string[]\n wikilinks: string[]\n numbers: string[]\n}\n\nconst URL_RE = /\\bhttps?:\\/\\/[^\\s<>\"'`)\\]]+/gi\nconst WIKILINK_RE = /\\[\\[([^\\]|]+)(?:\\|[^\\]]*)?\\]\\]/g\n// A short or full git hash: 7–40 hex chars holding at least one digit and\n// one letter, so words like \"defaced\" and plain numbers are not hashes.\nconst COMMIT_RE = /(?<![\\w-])(?=[0-9a-f]*[0-9])(?=[0-9a-f]*[a-f])[0-9a-f]{7,40}(?![\\w-])/gi\n// A number standing on its own: \"12\", \"1,234\", \"3.5\", \"2026-10-07\", \"14:30\",\n// \"v2\"-style suffixes excluded by the look-behind.\nconst NUMBER_RE = /(?<![\\w.,:/-])\\d+(?:[.,:\\-/]\\d+)*(?![\\w])/g\nconst LIST_MARKER_RE = /^\\s*\\d+[.)]\\s/gm\n\n/** The facts a rewrite must not lose, extracted from an article body. */\nexport function extractFacts(text: string): ArticleFacts {\n const urls = [...text.matchAll(URL_RE)].map((m) => m[0].replace(/[.,;:!?'\"]+$/, \"\"))\n const wikilinks = [...text.matchAll(WIKILINK_RE)].map((m) => m[1].trim())\n // Hashes and numbers are read with URLs and wikilink targets removed, so\n // a path segment or \"Release 2\" in a link is not counted twice.\n const rest = text.replace(URL_RE, \" \").replace(WIKILINK_RE, \" \").replace(LIST_MARKER_RE, \" \")\n const commits = [...rest.matchAll(COMMIT_RE)].map((m) => m[0].toLowerCase())\n const withoutCommits = rest.replace(COMMIT_RE, \" \")\n const numbers = [...withoutCommits.matchAll(NUMBER_RE)]\n .map((m) => normaliseNumber(m[0]))\n return {\n commits: unique(commits),\n urls: unique(urls),\n wikilinks: unique(wikilinks),\n numbers: unique(numbers),\n }\n}\n\n/**\n * The facts in `oldContent` that `newContent` no longer carries. Empty\n * means the rewrite is safe to save.\n *\n * A short hash is kept when the new text has it at any length; a URL or\n * wikilink is compared without case or trailing slash; a number is\n * compared as a token (thousands separators ignored), so \"12\" is not\n * found inside \"2012\".\n */\nexport function droppedFacts(oldContent: string, newContent: string): string[] {\n const before = extractFacts(oldContent)\n const after = extractFacts(newContent)\n const lowerNew = newContent.toLowerCase()\n const dropped: string[] = []\n\n for (const c of before.commits) {\n if (!lowerNew.includes(c) && !after.commits.some((n) => n.startsWith(c) || c.startsWith(n))) {\n dropped.push(`commit ${c}`)\n }\n }\n const urlKey = (u: string) => u.toLowerCase().replace(/\\/+$/, \"\")\n const newUrls = new Set(after.urls.map(urlKey))\n for (const u of before.urls) {\n if (!newUrls.has(urlKey(u))) dropped.push(`link ${u}`)\n }\n const newLinks = new Set(after.wikilinks.map((l) => l.toLowerCase()))\n for (const l of before.wikilinks) {\n if (!newLinks.has(l.toLowerCase())) dropped.push(`link [[${l}]]`)\n }\n const newNumbers = new Set(after.numbers)\n for (const n of before.numbers) {\n if (!newNumbers.has(n)) dropped.push(`number ${n}`)\n }\n return dropped\n}\n\nfunction normaliseNumber(n: string): string {\n // 1,234 → 1234; leave 3,5 (a decimal comma) and dates alone.\n return /^\\d{1,3}(,\\d{3})+$/.test(n) ? n.replace(/,/g, \"\") : n\n}\n\nfunction unique(list: string[]): string[] {\n return [...new Set(list)]\n}\n\n// --- Duplicate redirect ---\n\n/**\n * Where an absorbed article is written. A new path whose title an existing\n * article already carries is the same subject filed twice, so it goes to\n * the existing article — where the drop-guard then applies.\n */\nexport function absorbTargetPath(\n article: { path: string; title: string },\n catalog: Array<{ path: string; title: string }>,\n): string {\n if (catalog.some((a) => a.path === article.path)) return article.path\n const key = titleKey(article.title)\n if (!key) return article.path\n return catalog.find((a) => titleKey(a.title) === key)?.path ?? article.path\n}\n\nfunction titleKey(title: string): string {\n return (title || \"\").toLowerCase().replace(/[^\\p{L}\\p{N}]+/gu, \" \").trim()\n}\n"],"mappings":"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","names":["findCoveringArticles","entries","store","agentId","pool","opts","retrieve","retrieveArticles","perEntry","maxArticles","queryChars","found","entry","hits","hit","seen","a","b","renderCoveringBlock","articles","body","header","why","related","l","URL_RE","WIKILINK_RE","COMMIT_RE","NUMBER_RE","LIST_MARKER_RE","extractFacts","text","urls","m","wikilinks","rest","commits","numbers","normaliseNumber","unique","droppedFacts","oldContent","newContent","before","after","lowerNew","dropped","c","n","urlKey","u","newUrls","newLinks","newNumbers","list","absorbTargetPath","article","catalog","key","titleKey","title"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as rt,b as ot,c as st,d as x,e as A,f as v}from"./chunk-72SZI2UW.js";import{a as ne,b as et}from"./chunk-FCPTN6NF.js";import{a as Te,b as ht}from"./chunk-MCI3DIWW.js";import{o as we,p as Ze}from"./chunk-2PJKGZWP.js";import{
|
|
1
|
+
import{a as rt,b as ot,c as st,d as x,e as A,f as v}from"./chunk-72SZI2UW.js";import{a as ne,b as et}from"./chunk-FCPTN6NF.js";import{a as Te,b as ht}from"./chunk-MCI3DIWW.js";import{o as we,p as Ze}from"./chunk-2PJKGZWP.js";import{ta as tt,ua as nt}from"./chunk-EOLEKSN6.js";import{a as it,b as at,c as ct,d as ut,e as lt,f as dt,g as pt,h as mt,i as bt,j as ft,k as gt,l as yt}from"./chunk-RGDKHYY2.js";import{a as E,b as We,c as he}from"./chunk-QIJLFPWE.js";function R(e,t){$.set(e,t),X.delete(e)}function D(e){let t=X.get(e);if(t)return t;let r=$.get(e);if(!r){let o=Ee();throw new Error(`unknown decision backend "${e}"`+(o.length>0?` \u2014 registered: ${o.join(", ")}`:" \u2014 none registered"))}let n=r();return X.set(e,n),n}function Ee(){return[...$.keys()].sort()}function wt(){$.clear(),X.clear()}var $,X,C=E(()=>{"use strict";$=new Map,X=new Map});import Tt from"better-sqlite3";import{createHash as Et}from"crypto";import{mkdirSync as kt}from"fs";import{dirname as St,resolve as xt}from"path";function Q(e){if(e.type==="noul"){let r=e.noul,n={yes:r,no:1-r},o=r>=.5?"yes":"no",s=Math.abs(r-.5)*2;return{predicted:o,confidence:s,probabilities:n,topLabel:o,topProb:Math.max(r,1-r),expectedScore:null,pMax:Math.max(r,1-r),negEntropy:s}}if(e.type==="choice")return{predicted:e.choice,confidence:e.confidence,probabilities:{...e.probabilities},topLabel:e.choice,topProb:e.probabilities[e.choice]??0,expectedScore:null,pMax:e.pMax,negEntropy:e.negEntropy};let t=String(Math.round(e.score));return{predicted:t,confidence:e.confidence,probabilities:{...e.probabilities},topLabel:t,topProb:e.probabilities[t]??0,expectedScore:e.score,pMax:e.pMax,negEntropy:e.negEntropy}}function At(e){if(typeof e!="string"||e.length===0)return{};try{let t=JSON.parse(e);return t&&typeof t=="object"&&!Array.isArray(t)?t:{}}catch{return{}}}function Rt(e){e.exec("CREATE TABLE IF NOT EXISTS schema_version (v INTEGER PRIMARY KEY);");let t=e.prepare("SELECT MAX(v) AS v FROM schema_version").get().v??0;t<1&&vt(e),t<2&&Ot(e),t<3&&_t(e)}function _t(e){e.exec(`
|
|
2
2
|
ALTER TABLE decision_calls ADD COLUMN features_json TEXT;
|
|
3
3
|
INSERT INTO schema_version (v) VALUES (3);
|
|
4
4
|
`)}function Ot(e){e.exec(`
|
|
@@ -111,4 +111,4 @@ import{a as rt,b as ot,c as st,d as x,e as A,f as v}from"./chunk-72SZI2UW.js";im
|
|
|
111
111
|
`)}if(!m)throw new Error(`local decision backend could not get a valid answer after ${f+1} attempt(s): ${w}`);let M={},L=!1;for(let[k,q]of Object.entries(s.questions)){let S=_(q,m[k],{normalize:e.normalizeProbabilities??!0});M[k]=S.answer,S.repaired&&(L=!0)}return{model:a,answers:M,usage:h,meta:{backend:"local",structureMode:c,answerMode:"probabilities",repaired:L,retries:f,stateTruncated:l.truncated,latencyMs:Date.now()-i}}}finally{b()}}}}async function Jt(e,t,r,n,o){if(!e.generateRaw)throw new Error(`provider "${e.name}" has no generateRaw(); use structureMode "text"`);let s=[{role:"user",content:r},...n?[{role:"user",content:n}]:[]],i=await e.generateRaw(s,J,[{name:F,description:"Report a probability distribution for every question.",input_schema:se(t)}],{...o,toolChoice:{type:"tool",name:F}}),a=i.content.find(c=>c.type==="tool_use"&&c.name===F),u=a&&a.type==="tool_use"?a.input:null;return{answers:Be(u),usage:{inputTokens:i.usage.input_tokens,outputTokens:i.usage.output_tokens}}}async function Gt(e,t,r,n,o){let s=[r,ae(t)].join(`
|
|
112
112
|
`),i=[{role:"system",content:J},{role:"user",content:s},...n?[{role:"user",content:n}]:[]],a=await e.generate(i,o);return{answers:Be(Te(a.content??"")),usage:{inputTokens:0,outputTokens:a.tokensUsed??0}}}function Be(e){if(e&&typeof e=="object"&&!Array.isArray(e)){let t=e;if(t.answers&&typeof t.answers=="object")return t.answers}return e}function Yt(e,t){return e!=="auto"?e:t.generateRaw&&me[t.name]?.structuredOutput?"tool":"text"}function Kt(e,t){let r=new AbortController,n=setTimeout(()=>r.abort(new Error(`decision timed out after ${t}ms`)),t),o=()=>r.abort(e?.reason);return e&&(e.aborted?o():e.addEventListener("abort",o,{once:!0})),{signal:r.signal,dispose:()=>{clearTimeout(n),e?.removeEventListener("abort",o)}}}var Xt,$t,Vt,Fe,zt,be=E(()=>{"use strict";Ze();Ue();ht();j();Y();v();z();Xt="claude-code",$t="claude-haiku-4-5-20251001",Vt=24e3,Fe=2e3,zt=3e4});import{readFileSync as Ht}from"fs";import{homedir as Wt}from"os";import{join as Zt}from"path";function N(e={}){let t=(e.baseUrl??en).replace(/\/+$/,""),r=e.path??"/classifier",n=e.name??"simple-jev",o=e.maxStateChars??nn,s=e.maxChoiceOptions??rn,i={probabilitySource:e.probabilitySource??"logits",calibratedProbabilities:!1,maxChoiceOptions:s,maxStateChars:o,parallelQuestions:!0,images:!1};return{name:n,capabilities:i,async decide(a){A(a.questions),cn(a.questions,s);let u=Date.now(),c=a.model??e.model;if(!c)throw new Error("simple-jev backend needs a model id");let l=G(a.state,o),p=e.fetchImpl??fetch,{signal:g,dispose:d}=un(a.abortSignal,a.timeoutMs??e.timeoutMs??tn);try{let b={"Content-Type":"application/json"},h=an(e.apiKeyEnv,e.apiKeyFile);h&&(b.Authorization=`Bearer ${h}`);let T=await qe(p(`${t}${r}`,{method:"POST",headers:b,body:JSON.stringify({model:c,state:l.text,questions:sn(a.questions)}),signal:g}),g);if(!T.ok){let k=await T.text().catch(()=>"");throw new Error(`${n} ${T.status}: ${k.slice(0,300)}`)}let f=await qe(T.json(),g),m=P(a.questions).safeParse(f.answers);if(!m.success)throw new Error(`${n} returned a response that does not match the requested questions (contract drift, not a model slip \u2014 retrying will not help):
|
|
113
113
|
${U(m.error)}`);let w=m.data,M={},L=!1;for(let[k,q]of Object.entries(a.questions)){let S=_(q,w[k]);M[k]=S.answer,S.repaired&&(L=!0)}return{model:f.model??c,answers:M,usage:{inputTokens:f.usage?.input_tokens??0,outputTokens:f.usage?.output_tokens??0},meta:{backend:n,structureMode:i.probabilitySource==="native"?"native":"logprobs",answerMode:"probabilities",repaired:L,retries:0,stateTruncated:l.truncated,latencyMs:Date.now()-u}}}finally{d()}}}}function sn(e){let t={};for(let[r,n]of Object.entries(e))t[r]=n.instructions?n:{...n,instructions:on[n.type]};return t}function an(e,t){let r=e?process.env[e]:void 0;if(r)return r;if(!t)return;let n=t.startsWith("~/")?Zt(Wt(),t.slice(2)):t;if(!Z.has(n))try{let o=Ht(n,"utf-8").trim();Z.set(n,o.length>0?o:null)}catch{Z.set(n,null)}return Z.get(n)??void 0}function cn(e,t){for(let[r,n]of Object.entries(e))if(n.type==="choice"){let o=Object.keys(n.criteria).length;if(o>t)throw new Error(`question "${r}": accepts at most ${t} choice options, got ${o}`)}else if(n.type==="score"&&n.criteria.length>t)throw new Error(`question "${r}": accepts at most ${t} score levels, got ${n.criteria.length}`)}function qe(e,t){return t.aborted?Promise.reject(t.reason??new Error("aborted")):new Promise((r,n)=>{let o=()=>n(t.reason??new Error("aborted"));t.addEventListener("abort",o,{once:!0}),e.then(s=>{t.removeEventListener("abort",o),r(s)},s=>{t.removeEventListener("abort",o),n(s)})})}function un(e,t){let r=new AbortController,n=setTimeout(()=>r.abort(new Error(`decision timed out after ${t}ms`)),t),o=()=>r.abort(e?.reason);return e&&(e.aborted?o():e.addEventListener("abort",o,{once:!0})),{signal:r.signal,dispose:()=>{clearTimeout(n),e?.removeEventListener("abort",o)}}}var en,tn,nn,rn,on,Z,fe=E(()=>{"use strict";j();v();z();Y();en="http://127.0.0.1:8000/v1",tn=3e4,nn=6e3,rn=50;on={choice:"Which option best fits?",score:"Which level best fits?",noul:"Is this true of the state?"};Z=new Map});var Xe={};We(Xe,{DEFAULT_SEAT_TIMEOUT_MS:()=>Ve,DecisionStore:()=>I,MAX_CHOICE_OPTIONS:()=>MAX_CHOICE_OPTIONS,SYSTEM_PROMPT:()=>J,TOOL_NAME:()=>F,UNCERTAINTY_HIGH:()=>Ce,UNCERTAINTY_LOW:()=>De,_resetDecisionBackendsForTesting:()=>wt,agreement:()=>it,answerView:()=>Q,applyRecalibrator:()=>ue,applyTemperature:()=>mt,askSeat:()=>yn,band:()=>Ie,bootstrapInterval:()=>ft,brier:()=>lt,buildDesign:()=>ve,calibrationReport:()=>gt,choice:()=>ot,compareCalibrators:()=>It,confidenceFor:()=>re,configureDecisions:()=>pn,configuredSeatTimeout:()=>dn,coverageCurve:()=>pt,createLocalDecisionBackend:()=>W,createMockDecisionBackend:()=>H,createSimpleJevBackend:()=>N,decisionsRuntime:()=>bn,describeIssues:()=>U,ece:()=>ct,expectedScore:()=>Ae,finalizeAnswer:()=>_,fitLogistic:()=>Ne,fitRecalibrator:()=>B,fitTemperature:()=>bt,getDecisionBackend:()=>D,getSeatMode:()=>Je,labelSeatCall:()=>fn,labelsOf:()=>x,listDecisionBackends:()=>Ee,logLoss:()=>dt,logit:()=>le,mce:()=>ut,measureConsistency:()=>Ut,normalizeDistribution:()=>Se,normalizedNegEntropy:()=>oe,noul:()=>rt,pMax:()=>xe,parseSeatMode:()=>ge,rawAnswerSchema:()=>Re,rawAnswersSchema:()=>P,recordSeatOutcome:()=>gn,registerBuiltinDecisionBackends:()=>ln,registerDecisionBackend:()=>R,reliabilityBins:()=>at,renderQuestions:()=>_e,renderShapeInstructions:()=>ae,renderState:()=>G,renderUserPrompt:()=>ie,resetDecisionsRuntime:()=>mn,score:()=>st,seatEnvVar:()=>ze,sigmoid:()=>de,toolInputSchema:()=>se,validateQuestions:()=>A,withUid:()=>Qe});function ln(e={},t={},r={},n={}){R("mock",()=>H()),R("local",()=>W(e)),R("simple-jev",()=>N(t)),R("jev",()=>N({name:"jev",baseUrl:"https://openrouter.ai/api/alpha",path:"/decisions",model:"jev-latest",apiKeyEnv:"OPENROUTER_API_KEY",apiKeyFile:"~/.agentx/openrouter-key.txt",probabilitySource:"native",maxChoiceOptions:255,maxStateChars:9e4,...r})),R("typesafe",()=>N({name:"typesafe",baseUrl:"https://api.typesafe.ai/v1",path:"/systemone",model:"jev-latest",apiKeyEnv:"TYPESAFE_API_KEY",apiKeyFile:"~/.agentx/typesafe-key.txt",probabilitySource:"native",maxChoiceOptions:255,maxStateChars:9e4,...n}))}var $e=E(()=>{"use strict";ke();v();j();z();C();Y();V();yt();Me();je();Ge();pe();be();fe();C();pe();be();fe()});function dn(e){return Ke(),y.seats[e]?.timeoutMs}function pn(e){y={...ye,...y,...e},ee=!0}function mn(){y={...ye,seats:{}},ee=!1}function Ke(){if(!ee){ee=!0;try{let{loadDaemonConfig:e}=(nt(),he(tt)),t=e()?.decisions;if(!t?.enabled)return;let{registerBuiltinDecisionBackends:r}=($e(),he(Xe)),n=t.backends??{};r(n.local,n.simpleJev,n.jev,n.typesafe);let o=null;try{o=new I({path:t.dbPath})}catch{}y={enabled:!0,defaultBackend:t.defaultBackend,store:o,seats:Object.fromEntries(Object.entries(t.seats??{}).map(([s,i])=>[s,{...i,redactState:t.redactState,keepStateRows:t.keepStateRows}]))}}catch{}}}function bn(){return y}function ge(e){if(typeof e!="string")return null;let t=e.trim().toLowerCase();return hn.includes(t)?t:null}function ze(e){return`AGENTX_DECISION_SEAT_${e.toUpperCase().replace(/[^A-Z0-9]+/g,"_")}`}function Je(e,t=process.env){Ke();let r=ge(t[ze(e)]);return r||(y.enabled?ge(y.seats[e]?.mode)??"off":"off")}function fn(e,t,r,n={}){if(!(!e||!y.store))try{y.store.label(e,t,r,n)}catch{}}function gn(e,t,r=!1){if(!(!e||!y.store))try{y.store.recordOutcome(e,t,r)}catch{}}async function yn(e,t,r,n={}){let o=Je(e);if(o==="off")return null;let s=y.seats[e]??{mode:o},i=n.backend??s.backend??y.defaultBackend,a=Date.now();try{let c=await D(i).decide({state:t,questions:r,model:n.model??s.model,abortSignal:n.signal,timeoutMs:n.timeoutMs??s.timeoutMs??(y.seats[e]?Ve:void 0)}),l=wn(e,o,t,r,c,n,s);return{answers:c.answers,callId:l,mode:o}}catch(u){return Tn(e,o,t,r,i,n,s,a,u),En(e,i,u),null}}function wn(e,t,r,n,o,s,i){if(!y.store)return null;try{return y.store.recordCall({seat:e,mode:t,backend:o.meta.backend,model:o.model,meta:o.meta,state:r,questions:n,answers:o.answers,usage:o.usage,incumbent:He(s.incumbent),links:s.links,features:s.features,keepState:!i.redactState})}catch{return null}}function Tn(e,t,r,n,o,s,i,a,u){if(y.store)try{y.store.recordCall({seat:e,mode:t,backend:o,model:s.model??i.model??"unknown",meta:{structureMode:"text",answerMode:"probabilities",retries:0,stateTruncated:!1,latencyMs:Date.now()-a},state:r,questions:n,answers:{},incumbent:He(s.incumbent),links:s.links,features:s.features,error:String(u?.message??u).slice(0,500),keepState:!i.redactState})}catch{}}function He(e){if(!e)return;let t={};for(let[r,n]of Object.entries(e))n!==void 0&&(t[r]={value:n});return t}function En(e,t,r){let n=Date.now();n-Ye<6e4||(Ye=n,console.warn(`[decisions] seat "${e}" on backend "${t}" failed: ${r?.message??r}`))}var Ve,ye,y,ee,hn,Ye,Ge=E(()=>{C();V();Ve=1e4;ye={enabled:!1,defaultBackend:"local",seats:{},store:null},y={...ye},ee=!1;hn=["off","shadow","active"];Ye=0});export{D as a,Ee as b,C as c,I as d,V as e,It as f,Me as g,Ut as h,je as i,me as j,Xn as k,Ue as l,ln as m,$e as n,Ve as o,dn as p,pn as q,mn as r,bn as s,ge as t,ze as u,Je as v,fn as w,gn as x,yn as y,Ge as z};
|
|
114
|
-
//# sourceMappingURL=chunk-
|
|
114
|
+
//# sourceMappingURL=chunk-R6UCPHUD.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{f as j,i as x,p as O,r as I}from"./chunk-S2GAEH2F.js";import{a as R}from"./chunk-JXF54PT7.js";import{oa as S,
|
|
1
|
+
import{f as j,i as x,p as O,r as I}from"./chunk-S2GAEH2F.js";import{a as R}from"./chunk-JXF54PT7.js";import{oa as S,ua as L}from"./chunk-EOLEKSN6.js";L();import{Command as z}from"commander";import f from"chalk";import{execFileSync as h}from"child_process";import{accessSync as T,constants as H,existsSync as y,readFileSync as b,readdirSync as $,statSync as X}from"fs";import{join as E,resolve as m}from"path";import{homedir as B}from"os";import{createRequire as F}from"module";import{execFile as M}from"child_process";var q=(t,n)=>new Promise(e=>{try{M(t,n,{timeout:5e3},(i,s)=>e(i?null:String(s)))}catch{e(null)}}),N="Open System Settings \u203A Notifications \u203A AgentX Helper, turn on Allow notifications and choose Banners.";async function P(t={}){if((t.platform??process.platform)!=="darwin")return{state:"unsupported",message:"Desktop banners are only shown on a Mac."};let n=t.helper===void 0?I():t.helper;if(!n)return{state:"missing",message:"AgentX Helper is not installed, so banners come from Script Editor and show its icon.",fix:'Run agentx desktop install, then run agentx notify "Hello" --title "Test" and click Allow.'};let e=await(t.run??q)(n,["notify-status"]),i=null;try{i=e?JSON.parse(e):null}catch{i=null}return i?i.authorization==="notDetermined"?{state:"not-asked",path:n,message:"macOS has not asked yet whether AgentX Helper may notify; banners show the Script Editor icon until it does.",fix:'Run agentx notify "Hello" --title "Test" and click Allow when macOS asks.'}:i.authorization==="denied"?{state:"not-allowed",path:n,message:"macOS does not allow AgentX Helper to notify, so banners show the Script Editor icon.",fix:N}:i.alertStyle==="none"?{state:"hidden",path:n,message:"AgentX Helper may notify, but its banner style is None, so nothing appears on screen.",fix:N}:{state:"ready",path:n,message:"AgentX Helper posts banners with the AgentX icon."}:{state:"outdated",path:n,message:"AgentX Helper is too old to report whether it may notify.",fix:"Run agentx desktop install to update it."}}async function J(t={}){let n=[];await Z(n),U(n);let e=K(n);return e&&Q(n,e),e&&Y(n,e),e&&ee(n,e),e&&te(n,e),e&&oe(n,e),e&&await G(n,e),e&&V(n,e),t.running!==!1&&e&&await ie(n,e),{checks:n,summary:W(n)}}var ke=new z().name("doctor").description("pre-flight health check: Node, config, credentials, daemon").option("--no-running","skip the live daemon probe").option("--json","emit machine-readable JSON (stable shape for CI)").action(async t=>{let{checks:n,summary:e}=await J({running:t.running!==!1});t.json?process.stdout.write(JSON.stringify({checks:n,summary:e},null,2)+`
|
|
2
2
|
`):se(n),process.exit(e.errors>0?1:0)});async function G(t,n,e=P){if(!O(n.notifications?.local).banner)return;let i=await e();i.state!=="unsupported"&&t.push({severity:i.state==="ready"?"ok":"warn",group:"Notifications",title:i.message,detail:i.path,fix:i.fix})}function V(t,n,e=process.platform,i=s=>{try{return T(s,H.X_OK),!0}catch{return!1}}){let s=n.reminders;if(!s?.enabled)return;if(e!=="darwin"){t.push({severity:"warn",group:"Reminders",title:"Reminders hand-back unavailable on this machine",detail:"reminders.enabled is set, but Apple Reminders exists only on macOS. The daemon starts without it.",fix:"Set reminders.enabled to false here, or enable it on the Mac node instead."});return}let r=s.command||"remindctl",a=r.includes("/")?i(r)?r:null:x(r,process.env.PATH||"",i);t.push(a?{severity:"ok",group:"Reminders",title:`Watching ${(s.lists||[]).join(", ")} for due reminders`,detail:a}:{severity:"fail",group:"Reminders",title:`${r} not found`,detail:"The daemon reads Apple Reminders through remindctl.",fix:"brew install steipete/tap/remindctl, or set reminders.command to its full path."})}async function Z(t){let n=R(process.versions.node);t.push({severity:n?"ok":"fail",group:"Environment",title:`Node.js ${process.versions.node}`,detail:n?void 0:"AgentX requires Node 22.19 or newer, up to Node 26.",fix:n?void 0:"Install Node 22 (nvm install 22 && nvm use 22)"});let e=l=>{try{return h(process.platform==="win32"?"where":"which",[l],{encoding:"utf-8"}).trim().split(`
|
|
3
3
|
`)[0]}catch{return null}},i=e("npm");t.push({severity:i?"ok":"fail",group:"Environment",title:i?"npm on PATH":"npm not found",detail:i||void 0});let s=e("claude");if(s){let l;try{l=h("claude",["--version"],{encoding:"utf-8",timeout:3e3}).trim()}catch{}t.push({severity:"ok",group:"Environment",title:`claude CLI ${l||"installed"}`,detail:s})}else t.push({severity:"warn",group:"Environment",title:"claude CLI not on PATH",detail:"Only required for agents on the claude-code tier.",fix:"See https://docs.anthropic.com/en/docs/claude-code"});let r=e("codex");if(r){let l;try{l=h("codex",["--version"],{encoding:"utf-8",timeout:3e3}).trim()}catch{}t.push({severity:"ok",group:"Environment",title:`codex CLI ${l||"installed"}`,detail:r})}else t.push({severity:"warn",group:"Environment",title:"codex CLI not on PATH",detail:"Only required for agents on the codex-cli tier.",fix:"Install with `npm i -g @openai/codex`."});let a=e("opencode");if(a){let l;try{l=h("opencode",["--version"],{encoding:"utf-8",timeout:3e3}).trim()}catch{}t.push({severity:"ok",group:"Environment",title:`opencode CLI ${l||"installed"}`,detail:a})}else t.push({severity:"warn",group:"Environment",title:"opencode CLI not on PATH",detail:"Only required for agents on the opencode tier.",fix:"Install from https://opencode.ai/docs/."});try{F(import.meta.url)("better-sqlite3"),t.push({severity:"ok",group:"Environment",title:`better-sqlite3 loads for Node ${process.version}`,detail:`NODE_MODULE_VERSION ${process.versions.modules}`})}catch(l){t.push({severity:"fail",group:"Environment",title:"better-sqlite3 native binding does not load",detail:l?.message??String(l),fix:`Run "pnpm rebuild better-sqlite3" with the same Node used by the daemon (${process.version}, modules ${process.versions.modules}).`})}}function U(t){if(process.platform!=="darwin")return;let n=E(B(),"Library/LaunchAgents"),e=[];try{e=$(n).filter(s=>/agentx\.voice.*\.plist$/.test(s))}catch{return}let i=s=>{try{return T(s,H.X_OK),!0}catch{return!1}};for(let s of e){let r=j(b(E(n,s),"utf-8")),a=x("ffmpeg",r,i);t.push(a?{severity:"ok",group:"Desktop",title:"ffmpeg reachable by the desktop app",detail:`${a} (${s})`}:{severity:"warn",group:"Desktop",title:"ffmpeg not reachable by the desktop app",detail:`Local Whisper transcription fails without it. ${s} sets PATH to ${r}.`,fix:"Install ffmpeg (brew install ffmpeg), then rerun agentx desktop install."})}}function K(t){let n=m(process.cwd(),"agentx.json");if(!y(n))return t.push({severity:"fail",group:"Config",title:"agentx.json not found",detail:`Looked in ${n}`,fix:"Run `agentx setup` to create one via the web wizard."}),null;let e;try{e=JSON.parse(b(n,"utf-8"))}catch(a){return t.push({severity:"fail",group:"Config",title:"agentx.json is not valid JSON",detail:a.message}),null}try{S(n),t.push({severity:"ok",group:"Config",title:`agentx.json valid (${Object.keys(e.agents||{}).length} agents, ${Object.keys(e.crons||{}).length} schedules)`})}catch(a){t.push({severity:"fail",group:"Config",title:"agentx.json failed schema validation",detail:a.message,fix:"Open /admin \u2192 Advanced tab to edit and re-save."})}let i=Object.entries(e.agents||{}).filter(([,a])=>a.tier==="claude-code");i.length>0&&((()=>{try{return h(process.platform==="win32"?"where":"which",["claude"],{stdio:"ignore"}),!0}catch{return!1}})()||t.push({severity:"fail",group:"Config",title:`${i.length} agent(s) use claude-code tier but claude CLI is missing`,detail:i.map(([l])=>l).join(", "),fix:"Install Claude Code (https://docs.anthropic.com/en/docs/claude-code) or switch these agents to tier=sdk."}));let s=Object.entries(e.agents||{}).filter(([,a])=>a.tier==="codex-cli");s.length>0&&((()=>{try{return h(process.platform==="win32"?"where":"which",["codex"],{stdio:"ignore"}),!0}catch{return!1}})()||t.push({severity:"fail",group:"Config",title:`${s.length} agent(s) use codex-cli tier but codex CLI is missing`,detail:s.map(([l])=>l).join(", "),fix:"Install Codex CLI with `npm i -g @openai/codex` or switch these agents to tier=claude-code."}));let r=Object.entries(e.agents||{}).filter(([,a])=>a.tier==="opencode");return r.length>0&&((()=>{try{return h(process.platform==="win32"?"where":"which",["opencode"],{stdio:"ignore"}),!0}catch{return!1}})()||t.push({severity:"fail",group:"Config",title:`${r.length} agent(s) use opencode tier but OpenCode CLI is missing`,detail:r.map(([l])=>l).join(", "),fix:"Install OpenCode from https://opencode.ai/docs/ or switch these agents to another tier."})),e}function Q(t,n){let e=new Set;if(C(n,e),e.size===0){t.push({severity:"ok",group:"Secrets",title:"No ${VAR} references in agentx.json"});return}let i=m(process.cwd(),".env"),s={};if(y(i))for(let l of b(i,"utf-8").split(/\r?\n/)){let u=l.match(/^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$/i);u&&(s[u[1]]=u[2])}let r=[],a=[];for(let l of e){let u=process.env[l]??s[l];u===void 0?r.push(l):(!u.trim()||/^["']?\s*["']?$/.test(u))&&a.push(l)}r.length===0&&a.length===0?t.push({severity:"ok",group:"Secrets",title:`${e.size} env reference(s) resolved`}):(r.length>0&&t.push({severity:"fail",group:"Secrets",title:`${r.length} env var(s) referenced in agentx.json but missing`,detail:r.join(", "),fix:`Add to .env \u2014 e.g. ${r[0]}=<value>`}),a.length>0&&t.push({severity:"warn",group:"Secrets",title:`${a.length} env var(s) set but empty`,detail:a.join(", ")}))}function C(t,n){if(typeof t=="string")for(let e of t.matchAll(/\$\{([A-Z_][A-Z0-9_]*)\}/g))n.add(e[1]);else if(Array.isArray(t))for(let e of t)C(e,n);else if(t&&typeof t=="object")for(let e of Object.values(t))C(e,n)}function Y(t,n){let e=[];for(let[i,s]of Object.entries(n.agents||{})){if(!s.workspace)continue;let r=s.workspace.startsWith("/")?s.workspace:m(process.cwd(),s.workspace);try{X(r).isDirectory()||e.push(`${i}: ${r} (exists but not a directory)`)}catch{e.push(`${i}: ${r}`)}}e.length===0?t.push({severity:"ok",group:"Workspaces",title:"All agent workspace folders exist"}):t.push({severity:"warn",group:"Workspaces",title:`${e.length} agent workspace(s) missing`,detail:e.join(`
|
|
4
4
|
`),fix:"The daemon will create missing folders on first task, but you won't see CLAUDE.md / skills until you populate them."})}function ee(t,n){let e=/\$\{[A-Za-z_][A-Za-z0-9_]*\}/g,i=[];for(let[s,r]of Object.entries(n.agents||{})){if(!r.workspace)continue;let a=r.workspace.startsWith("/")?r.workspace:m(process.cwd(),r.workspace);for(let l of["settings.json","settings.local.json"]){let u=m(a,".claude",l);if(!y(u))continue;let w;try{w=JSON.parse(b(u,"utf-8"))}catch(d){i.push({severity:"fail",agent:s,msg:`${l}: JSON parse error \u2014 ${d.message}`});continue}let k=w.env||{};for(let[d,p]of Object.entries(k)){if(typeof p!="string")continue;let g=p.match(e);g&&i.push({severity:"fail",agent:s,msg:`${l} env.${d} contains unexpanded ${[...new Set(g)].join(",")} \u2014 Claude Code does NOT expand \${VAR} in settings.json (use a fully-literal value)`})}let o=k.PATH;if(typeof o=="string"){let d=o.split(":").filter(Boolean);d.includes("/usr/bin")||i.push({severity:"fail",agent:s,msg:`${l} env.PATH missing /usr/bin \u2014 cat/head/sh/which will fail`}),d.includes("/bin")||i.push({severity:"fail",agent:s,msg:`${l} env.PATH missing /bin \u2014 ls/cp/mv will fail`})}let c=w.permissions;if(c&&typeof c=="object"&&"allow"in c){let d=c.allow,p=c.deny;Array.isArray(d)&&d.length===0&&Array.isArray(p)&&p.length>0&&i.push({severity:"warn",agent:s,msg:`${l} permissions.allow is explicitly [] with ${p.length} deny rules \u2014 agent may be locked out (omit the key to use defaults instead)`})}}}if(i.length===0){t.push({severity:"ok",group:"Workspace settings",title:`All ${Object.keys(n.agents||{}).length} agent .claude/settings.json files clean`});return}for(let s of i)t.push({severity:s.severity,group:"Workspace settings",title:`${s.agent}: ${s.msg}`,fix:s.severity==="fail"?"Edit .claude/settings.json \u2014 replace ${VAR} with the literal value or omit the key to inherit.":"Either remove the empty allow list or populate it."})}function te(t,n){let e=n.webhooks??[],i=[],s=m(process.cwd(),n.workflows?.dir||".agentx/workflows"),r=new Set;try{if(y(s))for(let o of $(s))o.endsWith(".json")&&!o.startsWith("_")&&!o.endsWith(".disabled.json")&&r.add(o.replace(/\.json$/,""))}catch{}let a=new Set(["github","gitlab"]);for(let o of e){if(!o.enabled||!a.has(o.source))continue;!(o.triggers&&Object.keys(o.triggers).length>0)&&!o.defaultWorkflow&&i.push({severity:"warn",title:`webhook "${o.id}" (${o.source} -> ${o.agentId}) has no triggers map; every event-type collapses to the bound agent`,fix:'Add a triggers map: { "issues.opened": "wf-id", "pull_request.synchronize": "wf-id" } \u2014 or set defaultWorkflow.'})}for(let o of e)if(!(!o.enabled||!o.triggers)){for(let[c,d]of Object.entries(o.triggers))r.size>0&&!r.has(d)&&i.push({severity:"fail",title:`webhook "${o.id}".triggers["${c}"] -> "${d}" \u2014 no such workflow under ${n.workflows?.dir||".agentx/workflows"}`,fix:"Create the workflow file or fix the id in agentx.json"});o.defaultWorkflow&&r.size>0&&!r.has(o.defaultWorkflow)&&i.push({severity:"fail",title:`webhook "${o.id}".defaultWorkflow -> "${o.defaultWorkflow}" \u2014 no such workflow`,fix:"Create the workflow file or fix the id in agentx.json"})}try{if(y(s)){for(let o of $(s))if(!(!o.endsWith(".json")||o.startsWith("_")||o.endsWith(".disabled.json")))try{let c=JSON.parse(b(m(s,o),"utf-8"));c?.status==="active"&&c?.state==="quarantined"&&i.push({severity:"warn",title:`workflow "${c.id??o}" is status=active but state=quarantined \u2014 it looks live but will never dispatch`,fix:'Resolve the conflict the detector flagged, then set state back to "active" (or set status to "review" while parked).'})}catch{}}}catch{}let l=new Set((n.mesh?.peers??[]).map(o=>o.name));for(let o of e)!o.enabled||!o.node||l.has(o.node)||i.push({severity:"fail",title:`webhook "${o.id}" routes to mesh peer "${o.node}" which is not in mesh.peers`,fix:"Add the peer to mesh.peers or remove the node field on the webhook."});let u=new Map;for(let o of e){if(!o.enabled)continue;let c=`${o.source}:${o.agentId}`,d=u.get(c);d?i.push({severity:"warn",title:`webhook entries "${d}" and "${o.id}" both enabled for ${o.source} -> ${o.agentId}; only the first is dispatched`,fix:"Disable one entry, or merge their triggers maps into a single entry."}):u.set(c,o.id)}let w=new Set(Object.keys(n.agents??{}));for(let o of n.channels?.gitlab?.agentMappings??[])if(o?.agentId){if(o.node){l.has(o.node)||i.push({severity:"fail",title:`gitlab.agentMappings entry agentId="${o.agentId}" routes to mesh peer "${o.node}" which is not in mesh.peers`,fix:"Add the peer to mesh.peers or drop the node field on the mapping."});continue}w.has(o.agentId)||i.push({severity:"warn",title:`gitlab.agentMappings entry agentId="${o.agentId}" \u2014 no such agent in agents.* (and no node field, so it's not a cross-mesh route)`,fix:'Add the agent to agents.*, set node:"<peer>" if the agent lives remotely, or remove the mapping.'})}for(let[o,c]of Object.entries(n.channels?.telegram?.accounts??{}))c.agentBinding?w.has(c.agentBinding)||i.push({severity:"fail",title:`telegram account "${o}".agentBinding -> "${c.agentBinding}" \u2014 no such agent in agents.*`,fix:"Fix the agentBinding or define the agent in agents.*."}):i.push({severity:"fail",title:`telegram account "${o}" has no agentBinding; every DM to this bot will drop`,fix:"Set agentBinding to a valid agent id."});let k=new Set(["telegram-bot","whatsapp","slack-bot","discord-bot","gitlab-user","github-user","gmail","hotmail","smtp","hubspot","salesforce","pipedrive","odoo","trello","linear","asana","jira","notion","stripe","sentry","vercel","twilio","custom"]);for(let[o,c]of Object.entries(n.agents??{})){let d=c.integrations??[];if(!d.length)continue;let p=new Set;for(let g of d){let A=`${g.kind}::${g.label}`;p.has(A)&&i.push({severity:"warn",title:`agents.${o}.integrations: duplicate (${g.kind}, ${g.label})`,fix:"Remove the duplicate or rename one. (kind, label) must be unique per agent."}),p.add(A),k.has(g.kind)||i.push({severity:"warn",title:`agents.${o}.integrations: unknown kind "${g.kind}" on integration "${g.label}"`,fix:'Use a canonical kind from INTEGRATION_KINDS, or "custom" for ad-hoc services.'});let D=g.credentials||{};for(let[_,v]of Object.entries(D))typeof v=="string"&&/^[A-Z][A-Z0-9_]*$/.test(v)&&!process.env[v]&&i.push({severity:"warn",title:`agents.${o}.integrations[${g.kind}/${g.label}].${_} -> $${v} but env var is unset (in this shell)`,fix:`Set ${v} on the daemon environment. The daemon's process.env is what matters; this warning fires from your local shell.`})}}if(i.length===0){t.push({severity:"ok",group:"Routing",title:`${e.length} webhook(s), ${Object.keys(n.channels?.telegram?.accounts??{}).length} telegram account(s) \u2014 all routing references resolve`});return}for(let o of i)t.push({severity:o.severity,group:"Routing",title:o.title,fix:o.fix})}function ne(t){return typeof t?.canMeasure!="boolean"?null:t.canMeasure?{severity:"ok",group:"Runtime",title:"Daemon finds ffmpeg to measure phone recordings"}:{severity:"warn",group:"Runtime",title:t.allowUnmeasured?"Daemon can't find ffmpeg: phone recordings go unmeasured (voice.allowUnmeasured)":"Daemon can't find ffmpeg: phone voice input is refused",fix:"Install ffmpeg (brew install ffmpeg / sudo apt install ffmpeg), or set AGENTX_FFMPEG in the service's environment. The daemon's PATH is shorter than your shell's."}}async function ie(t,n){let e=n.dashboard?.daemonUrl?.replace(/\/+$/,"")||"http://127.0.0.1:18800",i={};n.dashboard?.token&&(i.Authorization=`Bearer ${n.dashboard.token}`);try{let s=new AbortController,r=setTimeout(()=>s.abort(),2e3),a=await fetch(e+"/health",{headers:i,signal:s.signal});if(clearTimeout(r),!a.ok){t.push({severity:"warn",group:"Runtime",title:`Daemon responded ${a.status}`,detail:e});return}let l=await a.json();t.push({severity:"ok",group:"Runtime",title:`Daemon healthy \u2014 ${l.agents?.length??0} agents live, uptime ${Math.round((l.uptime??0)/60)}m`,detail:e});let u=ne(l.voice);u&&t.push(u)}catch{t.push({severity:"warn",group:"Runtime",title:"Daemon not reachable (not running?)",detail:e,fix:"Run `agentx daemon start`. Skip this check with `agentx doctor --no-running`."})}}function oe(t,n){let e=Object.entries(n.agents||{}).filter(([,s])=>s?.codegraph===!0&&(s.tier==="claude-code"||s.tier==="codex-cli"));if(e.length===0)return;let i=null;try{i=h(process.platform==="win32"?"where":"which",["codegraph"],{encoding:"utf-8"}).trim().split(`
|
|
5
5
|
`)[0]||null}catch{i=null}if(i){let s;try{s=h("codegraph",["--version"],{encoding:"utf-8",timeout:3e3}).trim()}catch{}t.push({severity:"ok",group:"CodeGraph",title:`codegraph CLI ${s||"installed"}`,detail:i})}else t.push({severity:"fail",group:"CodeGraph",title:"codegraph CLI not on PATH",detail:`${e.length} agent(s) have codegraph:true but the binary isn't available.`,fix:"Install with `npm i -g @colbymchenry/codegraph`."});for(let[s,r]of e){if(!r.workspace||!y(r.workspace)){t.push({severity:"warn",group:"CodeGraph",title:`${s}: workspace missing`,detail:`Workspace path ${r.workspace} does not exist \u2014 codegraph will skip this agent.`});continue}let a=m(r.workspace,".codegraph","codegraph.db");y(a)?t.push({severity:"ok",group:"CodeGraph",title:`${s}: index present`,detail:a}):t.push({severity:"warn",group:"CodeGraph",title:`${s}: index not built yet`,detail:".codegraph/ missing \u2014 daemon will background-index this workspace on next boot.",fix:`Run \`codegraph init --index ${r.workspace}\` to build it now.`})}}function W(t){return{errors:t.filter(n=>n.severity==="fail").length,warnings:t.filter(n=>n.severity==="warn").length,ok:t.filter(n=>n.severity==="ok").length}}function se(t){console.log(),console.log(f.bold(" agentx doctor")),console.log();let n=new Map;for(let i of t)n.has(i.group)||n.set(i.group,[]),n.get(i.group).push(i);for(let[i,s]of n){console.log(f.bold(" "+i));for(let r of s){let a=r.severity==="ok"?f.green("\u2713"):r.severity==="warn"?f.yellow("!"):f.red("\u2717");console.log(` ${a} ${r.title}`),r.detail&&console.log(f.dim(" "+r.detail.replace(/\n/g,`
|
|
6
6
|
`))),r.fix&&console.log(f.dim(" \u2192 "+r.fix))}console.log()}let e=W(t);e.errors>0?console.log(f.red.bold(` ${e.errors} error${e.errors===1?"":"s"}`)+(e.warnings?f.yellow(`, ${e.warnings} warning${e.warnings===1?"":"s"}`):"")+`. Fix the error${e.errors===1?"":"s"} and rerun.`):e.warnings>0?console.log(f.yellow.bold(` ${e.warnings} warning${e.warnings===1?"":"s"}.`)+f.dim(" Review but not blocking.")):console.log(f.green.bold(` All checks passed (${e.ok}).`)),console.log()}export{P as a,J as b,ke as c,G as d,V as e,ne as f};
|
|
7
|
-
//# sourceMappingURL=chunk-
|
|
7
|
+
//# sourceMappingURL=chunk-SCC7HBFZ.js.map
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import{readFileSync as l,existsSync as f}from"fs";import{resolve as
|
|
2
|
-
`)){let t=r.trim();if(!t||t.startsWith("#"))continue;let s=t.startsWith("export ")?t.slice(7).trim():t,i=s.indexOf("=");if(i===-1)continue;let c=s.slice(0,i).trim(),o=s.slice(i+1).trim();o.length>=2&&(o.startsWith('"')&&o.endsWith('"')||o.startsWith("'")&&o.endsWith("'"))&&(o=o.slice(1,-1)),c&&n.push([c,o])}return n}function E(e){let n={};for(let r of[".env",".env.gitlab"]){let t=
|
|
3
|
-
//# sourceMappingURL=chunk-
|
|
1
|
+
import{readFileSync as l,existsSync as f}from"fs";import{resolve as d}from"path";function a(e){let n=[];for(let r of e.split(`
|
|
2
|
+
`)){let t=r.trim();if(!t||t.startsWith("#"))continue;let s=t.startsWith("export ")?t.slice(7).trim():t,i=s.indexOf("=");if(i===-1)continue;let c=s.slice(0,i).trim(),o=s.slice(i+1).trim();o.length>=2&&(o.startsWith('"')&&o.endsWith('"')||o.startsWith("'")&&o.endsWith("'"))&&(o=o.slice(1,-1)),c&&n.push([c,o])}return n}function E(e){let n={};for(let r of[".env",".env.gitlab"]){let t=d(e,r);if(f(t))try{for(let[s,i]of a(l(t,"utf-8")))n[s]=i}catch{}}return n}var p=new Map;function g(e=process.cwd()){let n=p.get(e);if(n)return n;let r=d(e,".env"),t;if(!f(r))t=new Set;else try{t=new Set(a(l(r,"utf-8")).map(([s])=>s))}catch{t=new Set}return p.set(e,t),t}function A(e,n=process.env,r=process.cwd()){let t=E(e),s=g(r),i={...n};for(let c of s)c in t||delete i[c];for(let[c,o]of Object.entries(t))i[c]=o;return i}function u(e){return delete e.ANTHROPIC_API_KEY,delete e.ANTHROPIC_API_KEY_OLD,e}function v(e){let n=e.HOME||process.env.HOME,r=[e.PATH||"",n?`${n}/.local/bin`:"","/opt/homebrew/bin","/usr/local/bin"];return e.PATH=Array.from(new Set(r.flatMap(t=>t.split(":")).filter(Boolean))).join(":"),e}function m(){let e=v(u({...process.env}));return delete e.CLAUDECODE,e}function S(e,n="subscription"){if(n!=="api")return u(e);if(!e.ANTHROPIC_API_KEY)throw new Error(`billing "api" needs ANTHROPIC_API_KEY in the agent's environment`);return delete e.ANTHROPIC_API_KEY_OLD,delete e.CLAUDE_CODE_OAUTH_TOKEN,e}function y(e){if(f(e))for(let[n,r]of a(l(e,"utf-8")))process.env[n]===void 0&&(process.env[n]=r)}export{A as a,u as b,m as c,S as d,y as e};
|
|
3
|
+
//# sourceMappingURL=chunk-VD7IO653.js.map
|