agentix-cli 0.18.0 → 0.19.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/README.md +75 -27
- package/dist/{agent-RDVGL2TW.js → agent-57MG4LBM.js} +2 -2
- package/dist/{audit-Z47AQFFD.js → audit-JU3HWISB.js} +2 -2
- package/dist/{board-dashboard-KX2CNOSY.js → board-dashboard-HXDOYGIG.js} +2237 -69
- package/dist/board-dashboard-HXDOYGIG.js.map +1 -0
- package/dist/chunk-23AUU4S2.js +102 -0
- package/dist/chunk-23AUU4S2.js.map +1 -0
- package/dist/chunk-4MREHZP5.js +111 -0
- package/dist/chunk-4MREHZP5.js.map +1 -0
- package/dist/chunk-4YCH6IZV.js +2 -0
- package/dist/chunk-6B74TO2J.js +743 -0
- package/dist/chunk-6B74TO2J.js.map +1 -0
- package/dist/chunk-7P5QRX5W.js +108 -0
- package/dist/chunk-7P5QRX5W.js.map +1 -0
- package/dist/chunk-A6BKSZQA.js +3 -0
- package/dist/chunk-A6BKSZQA.js.map +1 -0
- package/dist/{chunk-6PPIFDOQ.js → chunk-CNNLREHD.js} +67 -22
- package/dist/{chunk-6PPIFDOQ.js.map → chunk-CNNLREHD.js.map} +1 -1
- package/dist/{chunk-J4WKMKTP.js → chunk-DLVOHCTA.js} +2 -2
- package/dist/chunk-E4ZZILAK.js +15 -0
- package/dist/chunk-E4ZZILAK.js.map +1 -0
- package/dist/{chunk-TRQSP67X.js → chunk-IKBHHTIT.js} +2 -2
- package/dist/chunk-JTJIPNXY.js +2 -0
- package/dist/chunk-JTJIPNXY.js.map +1 -0
- package/dist/chunk-MBOLXKA2.js +6 -0
- package/dist/chunk-MBOLXKA2.js.map +1 -0
- package/dist/{inbox-O5UB3YQN.js → chunk-MTRWXRFF.js} +69 -4
- package/dist/chunk-MTRWXRFF.js.map +1 -0
- package/dist/{processes-7W27G3J3.js → chunk-NOF4HSGK.js} +3 -3
- package/dist/{processes-7W27G3J3.js.map → chunk-NOF4HSGK.js.map} +1 -1
- package/dist/chunk-QW2OYR3N.js +7 -0
- package/dist/chunk-QW2OYR3N.js.map +1 -0
- package/dist/chunk-SV3I4CI7.js +440 -0
- package/dist/chunk-SV3I4CI7.js.map +1 -0
- package/dist/{chunk-JXEPJ3YD.js → chunk-TC3BSUP7.js} +5 -5
- package/dist/chunk-VWTBHLMX.js +1 -0
- package/dist/chunk-WFHIIVLL.js +719 -0
- package/dist/chunk-WFHIIVLL.js.map +1 -0
- package/dist/chunk-WPPUOTY6.js +6 -0
- package/dist/chunk-WPPUOTY6.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/{compaction-S5H6DTOR.js → compaction-BTUJJ3LJ.js} +4 -4
- package/dist/config-QXWKEQON.js +2 -0
- package/dist/config-mutator-53XGR2NT.js +2 -0
- package/dist/conflict-detector-D7VIYPDI.js +2 -0
- package/dist/conflict-detector-D7VIYPDI.js.map +1 -0
- package/dist/{context-planner-2RGHJHSO.js → context-planner-I7ID7XF3.js} +3 -3
- package/dist/debug-N3B5NVJU.js +2 -0
- package/dist/{discover-OATU7SHI.js → discover-XJXQXXIO.js} +2 -2
- package/dist/doctor-SEOUFHSL.js +2 -0
- package/dist/{editor-chat-FUBQLMAQ.js → editor-chat-RVLCKKB7.js} +2 -2
- package/dist/{heal-YP7QI7D3.js → heal-YEQYWX2L.js} +2 -2
- package/dist/inbox-BIN2S5XN.js +2 -0
- package/dist/index.d.ts +830 -100
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/{ingest-whatsapp-C3O5UPBX.js → ingest-whatsapp-LZE62INA.js} +2 -2
- package/dist/loader-5CA3O5GM.js +2 -0
- package/dist/loader-6IHUCPVO.js +2 -0
- package/dist/memory-extract-KRCHD3OP.js +2 -0
- package/dist/migrate-v2-QO7SLKIM.js +4 -0
- package/dist/migrate-v2-QO7SLKIM.js.map +1 -0
- package/dist/processes-RJ6RDP32.js +2 -0
- package/dist/program-SASDC2GV.js +2 -0
- package/dist/{providers-WCQSKTNP.js → providers-WVCMIW67.js} +2 -2
- package/dist/query-HZYYRCLU.js +2 -0
- package/dist/recipes-EPLE2SNT.js +2 -0
- package/dist/registry-HM4ELSXS.js +2 -0
- package/dist/rotation-memo-GMTZPMNE.js +13 -0
- package/dist/rotation-memo-GMTZPMNE.js.map +1 -0
- package/dist/scheduler-XVFIK4XH.js +2 -0
- package/dist/{slack-D27BE37Q.js → slack-6P7ZJ32Y.js} +2 -2
- package/dist/store-BAPDZHXE.js +2 -0
- package/dist/{subagent-EPOT3QDM.js → subagent-WV7QKQ3L.js} +2 -2
- package/dist/{task-store-3NUWLYRY.js → task-store-RHISEU6V.js} +2 -2
- package/dist/{telegram-UWV67KWG.js → telegram-PIYDRM46.js} +2 -2
- package/dist/timers-5LPZN55J.js +2 -0
- package/dist/types-VECTMYTO.js +2 -0
- package/dist/types-VECTMYTO.js.map +1 -0
- package/dist/types-ZRLHHCGB.js +2 -0
- package/dist/types-ZRLHHCGB.js.map +1 -0
- package/dist/web/activity-graph.global.js +654 -0
- package/dist/web/activity-graph.global.js.map +1 -0
- package/dist/web/workflow-editor.global.js +1 -1
- package/dist/web/workflow-editor.global.js.map +1 -1
- package/dist/{whatsapp-KJ2NRKKQ.js → whatsapp-PFRDMUMT.js} +2 -2
- package/dist/{whatsapp-state-3ZVQ6MQD.js → whatsapp-state-XMTCSRXU.js} +2 -2
- package/dist/wiki-53W5NLZV.js +2 -0
- package/dist/wiki-53W5NLZV.js.map +1 -0
- package/dist/workspace-setup-FPGN5VNY.js +2 -0
- package/dist/workspace-setup-FPGN5VNY.js.map +1 -0
- package/package.json +3 -2
- package/dist/board-dashboard-KX2CNOSY.js.map +0 -1
- package/dist/chunk-34NG7UDX.js +0 -11
- package/dist/chunk-34NG7UDX.js.map +0 -1
- package/dist/chunk-ASSJ4Z6Z.js +0 -164
- package/dist/chunk-ASSJ4Z6Z.js.map +0 -1
- package/dist/chunk-BLUXL6HR.js +0 -854
- package/dist/chunk-BLUXL6HR.js.map +0 -1
- package/dist/chunk-BTAEYL3P.js +0 -72
- package/dist/chunk-BTAEYL3P.js.map +0 -1
- package/dist/chunk-DSCMCVTV.js +0 -7
- package/dist/chunk-DSCMCVTV.js.map +0 -1
- package/dist/chunk-HPDHNIA3.js +0 -2
- package/dist/chunk-IM376SD6.js +0 -13
- package/dist/chunk-IM376SD6.js.map +0 -1
- package/dist/chunk-LEIYC3BF.js +0 -104
- package/dist/chunk-LEIYC3BF.js.map +0 -1
- package/dist/chunk-LY7JGDV7.js +0 -6
- package/dist/chunk-LY7JGDV7.js.map +0 -1
- package/dist/chunk-U364GNQX.js +0 -719
- package/dist/chunk-U364GNQX.js.map +0 -1
- package/dist/chunk-WZEYII2S.js +0 -6
- package/dist/chunk-WZEYII2S.js.map +0 -1
- package/dist/config-UOHEG4BW.js +0 -2
- package/dist/config-mutator-KSEBWKJH.js +0 -2
- package/dist/debug-TGAP7E7W.js +0 -2
- package/dist/inbox-O5UB3YQN.js.map +0 -1
- package/dist/loader-BGWA7IRY.js +0 -2
- package/dist/loader-LBHKSXBK.js +0 -2
- package/dist/memory-extract-KKPKAOPH.js +0 -2
- package/dist/program-2MDELQLX.js +0 -2
- package/dist/query-FZAL7AQD.js +0 -2
- package/dist/recipes-LZ56TA3X.js +0 -2
- package/dist/registry-ONJSILH4.js +0 -2
- package/dist/scheduler-XRM2RJIV.js +0 -2
- package/dist/store-4UCO56K2.js +0 -2
- package/dist/timers-QB4KYNAE.js +0 -2
- package/dist/types-ECMEC5BZ.js +0 -2
- package/dist/types-TTB6DJUE.js +0 -2
- package/dist/usage-dashboard-FQGPPABD.js +0 -803
- package/dist/usage-dashboard-FQGPPABD.js.map +0 -1
- package/dist/wiki-OUYKM7MB.js +0 -2
- package/dist/workspace-setup-6GDBEORT.js +0 -2
- /package/dist/{agent-RDVGL2TW.js.map → agent-57MG4LBM.js.map} +0 -0
- /package/dist/{audit-Z47AQFFD.js.map → audit-JU3HWISB.js.map} +0 -0
- /package/dist/{chunk-HPDHNIA3.js.map → chunk-4YCH6IZV.js.map} +0 -0
- /package/dist/{chunk-J4WKMKTP.js.map → chunk-DLVOHCTA.js.map} +0 -0
- /package/dist/{chunk-TRQSP67X.js.map → chunk-IKBHHTIT.js.map} +0 -0
- /package/dist/{chunk-JXEPJ3YD.js.map → chunk-TC3BSUP7.js.map} +0 -0
- /package/dist/{config-UOHEG4BW.js.map → chunk-VWTBHLMX.js.map} +0 -0
- /package/dist/{compaction-S5H6DTOR.js.map → compaction-BTUJJ3LJ.js.map} +0 -0
- /package/dist/{config-mutator-KSEBWKJH.js.map → config-QXWKEQON.js.map} +0 -0
- /package/dist/{debug-TGAP7E7W.js.map → config-mutator-53XGR2NT.js.map} +0 -0
- /package/dist/{context-planner-2RGHJHSO.js.map → context-planner-I7ID7XF3.js.map} +0 -0
- /package/dist/{heal-YP7QI7D3.js.map → debug-N3B5NVJU.js.map} +0 -0
- /package/dist/{discover-OATU7SHI.js.map → discover-XJXQXXIO.js.map} +0 -0
- /package/dist/{loader-BGWA7IRY.js.map → doctor-SEOUFHSL.js.map} +0 -0
- /package/dist/{editor-chat-FUBQLMAQ.js.map → editor-chat-RVLCKKB7.js.map} +0 -0
- /package/dist/{loader-LBHKSXBK.js.map → heal-YEQYWX2L.js.map} +0 -0
- /package/dist/{memory-extract-KKPKAOPH.js.map → inbox-BIN2S5XN.js.map} +0 -0
- /package/dist/{ingest-whatsapp-C3O5UPBX.js.map → ingest-whatsapp-LZE62INA.js.map} +0 -0
- /package/dist/{program-2MDELQLX.js.map → loader-5CA3O5GM.js.map} +0 -0
- /package/dist/{providers-WCQSKTNP.js.map → loader-6IHUCPVO.js.map} +0 -0
- /package/dist/{query-FZAL7AQD.js.map → memory-extract-KRCHD3OP.js.map} +0 -0
- /package/dist/{recipes-LZ56TA3X.js.map → processes-RJ6RDP32.js.map} +0 -0
- /package/dist/{registry-ONJSILH4.js.map → program-SASDC2GV.js.map} +0 -0
- /package/dist/{scheduler-XRM2RJIV.js.map → providers-WVCMIW67.js.map} +0 -0
- /package/dist/{store-4UCO56K2.js.map → query-HZYYRCLU.js.map} +0 -0
- /package/dist/{task-store-3NUWLYRY.js.map → recipes-EPLE2SNT.js.map} +0 -0
- /package/dist/{timers-QB4KYNAE.js.map → registry-HM4ELSXS.js.map} +0 -0
- /package/dist/{types-ECMEC5BZ.js.map → scheduler-XVFIK4XH.js.map} +0 -0
- /package/dist/{slack-D27BE37Q.js.map → slack-6P7ZJ32Y.js.map} +0 -0
- /package/dist/{types-TTB6DJUE.js.map → store-BAPDZHXE.js.map} +0 -0
- /package/dist/{subagent-EPOT3QDM.js.map → subagent-WV7QKQ3L.js.map} +0 -0
- /package/dist/{wiki-OUYKM7MB.js.map → task-store-RHISEU6V.js.map} +0 -0
- /package/dist/{telegram-UWV67KWG.js.map → telegram-PIYDRM46.js.map} +0 -0
- /package/dist/{workspace-setup-6GDBEORT.js.map → timers-5LPZN55J.js.map} +0 -0
- /package/dist/{whatsapp-KJ2NRKKQ.js.map → whatsapp-PFRDMUMT.js.map} +0 -0
- /package/dist/{whatsapp-state-3ZVQ6MQD.js.map → whatsapp-state-XMTCSRXU.js.map} +0 -0
package/dist/chunk-WZEYII2S.js
DELETED
|
@@ -1,6 +0,0 @@
|
|
|
1
|
-
var O={version:1,levels:[{id:"scope",description:"The outermost context \u2014 everything else is nested inside.",axes:[{name:"kind",type:"enum",values:["personal","business","other"],description:"Which realm this message belongs to."}]},{id:"location",description:"Physical or logical location. Optional \u2014 skip for remote-first orgs.",axes:[{name:"country",type:"free"},{name:"city",type:"free"}]},{id:"org",description:"Any collective this message is about \u2014 company, family, club, team, or just yourself.",axes:[{name:"name",type:"free"},{name:"orgKind",type:"enum",values:["company","team","client","family","household","club","community","group","self"],description:"What kind of collective. `self` = just you, no org."}]},{id:"unit",description:"A person or sub-group within the org (department, squad, branch of a family, etc.).",axes:[{name:"unitKind",type:"enum",values:["person","department","group","household-member","teammate"]},{name:"name",type:"free"},{name:"role",type:"free",optional:!0,description:"Role/title/relationship (DevOps Lead, mother, coach, captain\u2026). Optional for casual contacts."},{name:"lead",type:"ref",refLevel:"unit",optional:!0,description:"If this is a sub-group (department/team), the person who leads/represents it. Doesn't apply to unitKind=person."}]},{id:"activity",description:"The actual intent. Three primaries: who / what / where.",axes:[{name:"who",type:"ref",refLevel:"unit"},{name:"what",type:"free",description:"The verb \u2014 review, deploy, draft, plan dinner, book training\u2026"},{name:"where",type:"ref",refLevel:"org"}]}],leafInput:{name:"input",type:"enum",values:["telegram","whatsapp","slack","discord","gitlab-mr","gitlab-issue","cron","webhook","http"],description:"Where the message entered the system."},leafOutput:{name:"output",type:"free",description:"Expected result. Free text \u2014 the LLM proposes, you refine."}};import{readFileSync as $,writeFileSync as q,appendFileSync as X,existsSync as b,mkdirSync as F,renameSync as Y}from"fs";import{resolve as C,dirname as T}from"path";import{createHash as Q}from"crypto";import{z as i}from"zod";var H=i.enum(["enum","free","ref"]),E=i.object({name:i.string().regex(/^[a-z][a-z0-9_]*$/i,"axis name must be a simple identifier"),type:H,values:i.array(i.string()).optional(),refLevel:i.string().optional(),optional:i.boolean().optional(),description:i.string().optional()}),M=i.object({id:i.string().regex(/^[a-z][a-z0-9_-]*$/,"level id must be lowercase slug"),axes:i.array(E).min(1),description:i.string().optional()}),P=i.object({version:i.number().int().default(1),levels:i.array(M).min(1).max(5),leafInput:E.optional(),leafOutput:E.optional()}),j=i.string().regex(/^[a-z0-9][a-z0-9_-]*$/,"node id must be lowercase slug"),B=i.object({id:j,level:i.string(),parentId:j.nullable(),axes:i.record(i.string(),i.string()),createdAt:i.string(),createdBy:i.string().optional()}),U=i.enum(["llm","user","cache"]),K=i.enum(["pending","approved","rejected"]),A=i.object({ts:i.string(),msgHash:i.string(),agentId:i.string().optional(),channel:i.string().optional(),sender:i.string().optional(),path:i.array(j),proposedAxes:i.record(i.string(),i.record(i.string(),i.string())).default({}),leaf:i.object({input:i.string().optional(),output:i.string().optional()}).partial().default({}),source:U,status:K,confidence:i.number().min(0).max(1).optional(),preview:i.string().optional()}),W=i.object({fingerprint:i.string(),path:i.array(j),leaf:A.shape.leaf,updatedAt:i.string()}),k=i.object({version:i.number().int().default(1),nodes:i.array(B).default([])}),R=i.object({version:i.number().int().default(1),entries:i.record(i.string(),W).default({})});var D=class{baseDir;log;constructor(e={}){this.baseDir=e.baseDir??C(process.cwd(),".agentx/graph"),this.log=e.log??console.error.bind(console,"[graph]"),F(this.baseDir,{recursive:!0})}schemaPath(){return C(this.baseDir,"schema.json")}nodesPath(){return C(this.baseDir,"nodes.json")}indexPath(){return C(this.baseDir,"index.json")}classificationsPath(){return C(this.baseDir,"classifications.jsonl")}loadSchema(){let e=this.schemaPath();if(!b(e))return this.saveSchema(O),this.log("seeded starter schema at",e),O;let t=$(e,"utf-8"),s=P.safeParse(JSON.parse(t));if(!s.success)throw new Error(`Invalid ${e}: ${s.error.issues.map(n=>n.message).join("; ")}`);return s.data}saveSchema(e){let t=P.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid schema: ${t.error.issues.map(s=>s.message).join("; ")}`);G(this.schemaPath(),t.data)}loadNodes(){let e=this.nodesPath();if(!b(e))return{version:1,nodes:[]};let t=$(e,"utf-8"),s=k.safeParse(JSON.parse(t));if(!s.success)throw new Error(`Invalid ${e}: ${s.error.issues.map(n=>n.message).join("; ")}`);return s.data}saveNodes(e){let t=k.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid nodes file: ${t.error.issues.map(s=>s.message).join("; ")}`);G(this.nodesPath(),t.data)}validateNode(e,t,s){let n=t.levels.find(r=>r.id===e.level);if(!n)throw new Error(`Unknown level: "${e.level}"`);let o=n.axes.filter(r=>!r.optional).filter(r=>!(r.name in e.axes));if(o.length)throw new Error(`Node ${e.id} (${e.level}) missing required axes: ${o.map(r=>r.name).join(", ")}`);for(let r of n.axes){let p=e.axes[r.name];if(p!=null){if(r.type==="enum"&&r.values&&!r.values.includes(p))throw new Error(`Node ${e.id}: axis ${r.name}="${p}" not in enum ${JSON.stringify(r.values)}`);if(r.type==="ref"){if(!s.some(h=>h.id===p))throw new Error(`Node ${e.id}: axis ${r.name}="${p}" refers to unknown node`);if(r.refLevel){let h=s.find(f=>f.id===p);if(h.level!==r.refLevel)throw new Error(`Node ${e.id}: axis ${r.name} expected level "${r.refLevel}" but got "${h.level}"`)}}}}let a=t.levels.findIndex(r=>r.id===e.level);if(a===0){if(e.parentId!==null)throw new Error(`Root-level node ${e.id} must have parentId: null`)}else{if(!e.parentId)throw new Error(`Non-root node ${e.id} must have a parentId`);let r=s.find(h=>h.id===e.parentId);if(!r)throw new Error(`Node ${e.id}: parent "${e.parentId}" not found`);let p=t.levels.findIndex(h=>h.id===r.level);if(p<0||p>=a)throw new Error(`Node ${e.id} at level "${e.level}" has parent at level "${r.level}" \u2014 parent must be at a higher level`)}}commitNodesAlongPath(e,t,s,n){let o=[],a=[],r=new Date().toISOString();for(let p=0;p<e.length;p++){let h=e[p],f=this.loadNodes().nodes;if(f.find(c=>c.id===h)){a.push(h);continue}let v=0;for(let c=p-1;c>=0;c--){let u=e[c],y=f.find(g=>g.id===u)||o.find(g=>g.id===u);if(y){v=s.levels.findIndex(g=>g.id===y.level)+1;break}}let x=t[h]??{},m=null;for(let c=v;c<s.levels.length;c++){let u=s.levels[c];if(u.axes.filter(g=>!g.optional).filter(g=>!(g.name in x)).length===0){m=u.id,v=c+1;break}}if(!m&&v<s.levels.length&&(m=s.levels[v].id,v++),!m)throw new Error(`Can't place node ${h} \u2014 no schema level left (cursor=${v}, levels=${s.levels.length})`);let S=null,N=s.levels.findIndex(c=>c.id===m);for(let c=p-1;c>=0;c--){let u=e[c],y=f.find(w=>w.id===u)||o.find(w=>w.id===u);if(!y)continue;let g=s.levels.findIndex(w=>w.id===y.level);if(g>=0&&g<N){S=u;break}}let l={id:h,level:m,parentId:S,axes:x,createdAt:r,createdBy:n};this.addNode(l),o.push(l)}return{added:o,skipped:a}}addNode(e){let t=this.loadSchema(),s=this.loadNodes();if(s.nodes.some(n=>n.id===e.id))throw new Error(`Node id already exists: ${e.id}`);return this.validateNode(e,t,[...s.nodes,e]),s.nodes.push(e),this.saveNodes(s),e}appendClassification(e){let t=A.safeParse(e);if(!t.success){let o=t.error.issues.map(a=>`${a.path.join(".")}: ${a.message}`).join(" \xB7 ");return this.log(`skipped invalid classification \u2014 ${o} \xB7`,`path=${JSON.stringify(e.path)}`,`axes-keys=${JSON.stringify(Object.keys(e.proposedAxes||{}))}`),!1}let s=JSON.stringify(t.data)+`
|
|
2
|
-
`,n=this.classificationsPath();return b(n)||F(T(n),{recursive:!0}),X(n,s),!0}listPendingClassifications(e=200){return this.listByStatus("pending",e)}listByStatus(e,t=200){return this.readAllCollapsed().filter(n=>n.status===e).slice(0,t)}readAllCollapsed(){let e=this.classificationsPath();if(!b(e))return[];let s=$(e,"utf-8").split(`
|
|
3
|
-
`).filter(Boolean),n=new Map;for(let o of s)try{let a=A.safeParse(JSON.parse(o));if(!a.success)continue;n.set(a.data.msgHash,a.data)}catch{}return Array.from(n.values()).sort((o,a)=>o.ts<a.ts?1:-1)}updateClassificationStatus(e,t){let n=this.readAllCollapsed().find(a=>a.msgHash===e);if(!n)return null;let o={...n,status:t.status,ts:new Date().toISOString()};if(t.reviewer||t.reviewReason){let a=` \xB7 ${t.reviewer?`reviewer=${t.reviewer}`:""}${t.reviewReason?` \xB7 ${t.reviewReason}`:""}`;o.preview=((n.preview||"")+a).slice(0,500)}return this.appendClassification(o),o}readRecentClassifications(e=50){let t=this.classificationsPath();if(!b(t))return[];let n=$(t,"utf-8").split(`
|
|
4
|
-
`).filter(Boolean),o=[];for(let a=n.length-1;a>=0&&o.length<e;a--)try{let r=A.safeParse(JSON.parse(n[a]));r.success&&o.push(r.data)}catch{}return o}fingerprint(e){let s=[e.text.toLowerCase().replace(/\s+/g," ").trim(),e.channel??"",e.sender??""].join("");return Q("sha256").update(s).digest("hex").slice(0,32)}loadIndex(){let e=this.indexPath();if(!b(e))return{version:1,entries:{}};let t=$(e,"utf-8"),s=R.safeParse(JSON.parse(t));if(!s.success)throw new Error(`Invalid ${e}: ${s.error.issues.map(n=>n.message).join("; ")}`);return s.data}saveIndex(e){let t=R.safeParse(e);if(!t.success)throw new Error(`Refusing to save invalid index: ${t.error.issues.map(s=>s.message).join("; ")}`);G(this.indexPath(),t.data)}getFingerprint(e){return this.loadIndex().entries[e]}setFingerprint(e,t){let s=this.loadIndex();s.entries[e]={...t,fingerprint:e,updatedAt:new Date().toISOString()},this.saveIndex(s)}updateNodeAxes(e,t){let s=this.loadSchema(),n=this.loadNodes(),o=n.nodes.find(r=>r.id===e);if(!o)throw new Error(`Node not found: ${e}`);let a={...o,axes:t};return this.validateNode(a,s,n.nodes.map(r=>r.id===e?a:r)),n.nodes=n.nodes.map(r=>r.id===e?a:r),this.saveNodes(n),a}deleteNode(e){let t=this.loadNodes(),s=t.nodes.find(o=>o.parentId===e);if(s)throw new Error(`Cannot delete "${e}" \u2014 child node "${s.id}" still points to it`);let n=t.nodes.find(o=>Object.values(o.axes).some(a=>a===e));if(n)throw new Error(`Cannot delete "${e}" \u2014 node "${n.id}" references it via an axis`);t.nodes=t.nodes.filter(o=>o.id!==e),this.saveNodes(t)}approveClassification(e){if(this.getFingerprint(e.msgHash))return;let t=this.loadSchema(),s=new Set(this.loadNodes().nodes.map(n=>n.id));for(let n=0;n<e.path.length;n++){let o=e.path[n];if(s.has(o))continue;let a=t.levels[n]?.id;if(!a)break;let r=e.proposedAxes?.[o]??{},p=n===0?null:e.path[n-1];this.addNode({id:o,level:a,parentId:p,axes:r,createdAt:new Date().toISOString(),createdBy:e.agentId}),s.add(o)}this.setFingerprint(e.msgHash,{path:e.path,leaf:e.leaf}),this.appendClassification({...e,status:"approved",ts:new Date().toISOString()})}rejectClassification(e){this.appendClassification({...e,status:"rejected",ts:new Date().toISOString()})}};function G(d,e){F(T(d),{recursive:!0});let t=`${d}.tmp-${process.pid}`;q(t,JSON.stringify(e,null,2)),Y(t,d)}import{createHash as V}from"crypto";var L=class{store;daemonUrl;token;draftAgent;autoApprove;autoApproveStructure;log;constructor(e){this.store=e.store,this.daemonUrl=e.daemonUrl.replace(/\/+$/,""),this.token=e.token,this.draftAgent=e.draftAgent,this.autoApprove=e.autoApproveConfidence??1,this.autoApproveStructure=e.autoApproveStructure??"extend-leaves",this.log=e.log??console.error.bind(console,"[classifier]")}async classify(e){let t=this.store.fingerprint({text:e.text,channel:e.channel,sender:e.sender}),s=this.store.getFingerprint(t);if(s)return{path:s.path,pathId:z(s.path),pathLabel:J(s.path,this.store.loadNodes().nodes),axes:{},leaf:s.leaf,source:"cache",status:"approved"};if(!this.draftAgent||e.agentId&&e.agentId===this.draftAgent)return null;let n=this.store.loadSchema(),o=this.store.loadNodes().nodes,a=await this.proposePath(e,n,o).catch(l=>(this.log("LLM proposal failed:",l?.message||l),null));if(!a)return null;let{path:r,proposedAxes:p,confidence:h,leaf:f}=a;if(r.length===0)return null;let I={ts:new Date().toISOString(),msgHash:t,agentId:e.agentId,channel:e.channel,sender:e.sender,path:r,proposedAxes:p,leaf:f,source:"llm",status:"pending",confidence:h,preview:e.text.slice(0,200)},v=new Set(o.map(l=>l.id)),x=r.map((l,c)=>v.has(l)?-1:c).filter(l=>l>=0),m=!1;if(this.autoApproveStructure==="any")m=!0;else if(this.autoApproveStructure==="extend-leaves"){let l=x.length===0,c=x.length===1&&x[0]===r.length-1;m=l||c}let S=h!==void 0&&h>=this.autoApprove,N="pending";if(m||S)try{this.commitNewNodes(r,p,n,o,e.sender),this.store.setFingerprint(t,{path:r,leaf:f}),N="approved"}catch(l){this.log("auto-approve failed \u2014 staying pending \xB7",`path=${JSON.stringify(r)}`,`reason=${(l?.message||l).toString().slice(0,200)}`)}return I.status=N,this.store.appendClassification(I),{path:r,pathId:z(r),pathLabel:J(r,this.store.loadNodes().nodes),axes:p,leaf:f,source:"llm",status:N,confidence:h}}async proposePath(e,t,s){let n=s.map(l=>({id:l.id,level:l.level,parentId:l.parentId,axes:l.axes})),o=["You are the intent classifier for AgentX's knowledge graph.","","SCHEMA (fixed axes per level, root \u2192 leaf):","```json",JSON.stringify(t,null,2),"```","","EXISTING NODES (choose ids from here when the message matches; propose NEW nodes only when nothing fits):","```json",JSON.stringify(n,null,2),"```","","MESSAGE:","```",e.text.slice(0,2e3),"```",`channel: ${e.channel??"?"}`,`sender: ${e.sender??"?"}`,"","TASK: classify this message into one path through the schema \u2014 one node per level from root toward leaf. Shorter paths are OK if you are not confident past a certain depth. For any node id you invent, include its axes in proposedAxes.","","NODE ID RULES (strict \u2014 invalid ids get dropped):","- lowercase-kebab only: [a-z0-9][a-z0-9_-]*","- no spaces, no uppercase, no punctuation, no Arabic/other non-Latin",'- if the human-readable label is "Sales Manager", the id is "sales-manager"',"- if the label contains non-Latin script, use a short Latin-slug + put the original in axes.name",'- good: "business", "noqta", "sales-manager", "activity-review-mr"','- bad: "Business", "sales manager", "Scope: Business", "\u062A\u0644\u064A\u062C\u0631\u0627\u0645"',"","If a node needs a display name, put the human-readable form in axes.name \u2014 never in the node id.","","Return ONE JSON object on one line, no prose, no fences:",' { "path": string[], "proposedAxes": { [nodeId]: { [axisName]: string } }, "leaf": { "input"?: string, "output"?: string }, "confidence": number }',"confidence in [0,1]. Prefer low confidence over guessing."].join(`
|
|
5
|
-
`),a=new AbortController,r=setTimeout(()=>a.abort(),6e4),p;try{p=await fetch(`${this.daemonUrl}/task`,{method:"POST",headers:{"Content-Type":"application/json",...this.token?{Authorization:`Bearer ${this.token}`}:{}},body:JSON.stringify({agent:this.draftAgent,message:o,context:{channel:"a2a",sender:"graph-classifier",systemPrompt:"You are a taxonomy classifier. Respond with a single JSON object. No prose, no code fences."}}),signal:a.signal})}finally{clearTimeout(r)}if(!p.ok)throw new Error(`daemon /task HTTP ${p.status}`);let h=await p.json();if(h?.error)throw new Error(String(h.error));let f=ee((h?.content||"").toString());if(!f||!Array.isArray(f.path))return null;let I=/^[a-z0-9][a-z0-9_-]*$/,v=new Map,x=f.path.filter(l=>typeof l=="string"&&l.length>0).map(l=>{let c=l.trim(),u=_(c);return c!==u&&v.set(c,u),u}).filter(l=>I.test(l));if(x.length===0)return null;let m={};if(f.proposedAxes&&typeof f.proposedAxes=="object")for(let[l,c]of Object.entries(f.proposedAxes)){if(!c||typeof c!="object")continue;let u=v.get(l)??_(l);if(!u)continue;let y={};for(let[g,w]of Object.entries(c))typeof w=="string"&&(y[g]=w);!y.name&&l!==u&&(y.name=l.trim()),m[u]=y}let S={};f.leaf&&typeof f.leaf=="object"&&(typeof f.leaf.input=="string"&&(S.input=f.leaf.input),typeof f.leaf.output=="string"&&(S.output=f.leaf.output));let N=typeof f.confidence=="number"?Math.max(0,Math.min(1,f.confidence)):void 0;return{path:x,proposedAxes:m,confidence:N,leaf:S}}commitNewNodes(e,t,s,n,o){this.store.commitNodesAlongPath(e,t,s,o)}};function _(d){let e=d.trim().toLowerCase().replace(/[^a-z0-9_-]+/g,"-").replace(/^[-_]+|[-_]+$/g,"").replace(/-{2,}/g,"-");return e?/^[a-z0-9]/.test(e)?e:`n-${e}`.replace(/[-_]+$/,""):""}function z(d){return V("sha1").update(d.join("")).digest("hex").slice(0,16)}function J(d,e){return d.map(t=>{let s=e.find(o=>o.id===t);return s&&(s.axes?.name||s.axes?.what)||t}).join(" \u203A ")}function Z(d,e){if(d.length===0||e.length===0)return 0;let t=0;for(let n=0;n<Math.min(d.length,e.length)&&d[n]===e[n];n++)t++;if(t===0)return 0;let s=Math.max(d.length,e.length);return t/s}function ee(d){if(!d)return null;try{return JSON.parse(d)}catch{}let e=d.match(/```(?:json)?\s*([\s\S]*?)```/);if(e)try{return JSON.parse(e[1])}catch{}let t=d.indexOf("{");if(t<0)return null;let s=0;for(let n=t;n<d.length;n++)if(d[n]==="{")s++;else if(d[n]==="}"&&(s--,s===0))try{return JSON.parse(d.slice(t,n+1))}catch{return null}return null}export{O as a,D as b,L as c,J as d,Z as e};
|
|
6
|
-
//# sourceMappingURL=chunk-WZEYII2S.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/graph/starter-schema.ts","../src/graph/store.ts","../src/graph/types.ts","../src/graph/classifier.ts"],"sourcesContent":["import type { GraphSchema } from \"./types\"\n\n/**\n * Ships with the install. When .agentx/graph/schema.json is missing, the\n * store seeds this so a fresh daemon has something to classify against.\n *\n * Five levels, three primaries at the leaf (who / what / where) — matches\n * the \"primary colors per level\" design from the plan.\n */\nexport const STARTER_SCHEMA: GraphSchema = {\n version: 1,\n levels: [\n {\n id: \"scope\",\n description: \"The outermost context — everything else is nested inside.\",\n axes: [\n {\n name: \"kind\",\n type: \"enum\",\n values: [\"personal\", \"business\", \"other\"],\n description: \"Which realm this message belongs to.\",\n },\n ],\n },\n {\n id: \"location\",\n description: \"Physical or logical location. Optional — skip for remote-first orgs.\",\n axes: [\n { name: \"country\", type: \"free\" },\n { name: \"city\", type: \"free\" },\n ],\n },\n {\n id: \"org\",\n description: \"Any collective this message is about — company, family, club, team, or just yourself.\",\n axes: [\n { name: \"name\", type: \"free\" },\n {\n name: \"orgKind\",\n type: \"enum\",\n values: [\n \"company\",\n \"team\",\n \"client\",\n \"family\",\n \"household\",\n \"club\",\n \"community\",\n \"group\",\n \"self\",\n ],\n description: \"What kind of collective. `self` = just you, no org.\",\n },\n ],\n },\n {\n id: \"unit\",\n description: \"A person or sub-group within the org (department, squad, branch of a family, etc.).\",\n axes: [\n {\n name: \"unitKind\",\n type: \"enum\",\n values: [\"person\", \"department\", \"group\", \"household-member\", \"teammate\"],\n },\n { name: \"name\", type: \"free\" },\n { name: \"role\", type: \"free\", optional: true, description: \"Role/title/relationship (DevOps Lead, mother, coach, captain…). Optional for casual contacts.\" },\n {\n name: \"lead\",\n type: \"ref\",\n refLevel: \"unit\",\n optional: true,\n description: \"If this is a sub-group (department/team), the person who leads/represents it. Doesn't apply to unitKind=person.\",\n },\n ],\n },\n {\n id: \"activity\",\n description: \"The actual intent. Three primaries: who / what / where.\",\n axes: [\n { name: \"who\", type: \"ref\", refLevel: \"unit\" },\n { name: \"what\", type: \"free\", description: \"The verb — review, deploy, draft, plan dinner, book training…\" },\n { name: \"where\", type: \"ref\", refLevel: \"org\" },\n ],\n },\n ],\n leafInput: {\n name: \"input\",\n type: \"enum\",\n values: [\n \"telegram\",\n \"whatsapp\",\n \"slack\",\n \"discord\",\n \"gitlab-mr\",\n \"gitlab-issue\",\n \"cron\",\n \"webhook\",\n \"http\",\n ],\n description: \"Where the message entered the system.\",\n },\n leafOutput: {\n name: \"output\",\n type: \"free\",\n description: \"Expected result. Free text — the LLM proposes, you refine.\",\n },\n}\n","import {\n readFileSync,\n writeFileSync,\n appendFileSync,\n existsSync,\n mkdirSync,\n renameSync,\n} from \"fs\"\nimport { resolve, dirname } from \"path\"\nimport { createHash } from \"crypto\"\nimport {\n graphSchemaSchema,\n nodesFileSchema,\n indexFileSchema,\n classificationSchema,\n type GraphSchema,\n type GraphNode,\n type NodesFile,\n type IndexFile,\n type Classification,\n type FingerprintEntry,\n} from \"./types\"\nimport { STARTER_SCHEMA } from \"./starter-schema\"\n\n// --- Intent Knowledge Graph filesystem store ---\n//\n// Mirrors the .agentx/wiki pattern: plain JSON on disk, one directory, no DB.\n// Not thread-safe across processes — writes go through safeWrite (tmp + rename).\n\nexport interface GraphStoreOptions {\n baseDir?: string\n log?: (...args: unknown[]) => void\n}\n\nexport class GraphStore {\n readonly baseDir: string\n private log: (...args: unknown[]) => void\n\n constructor(opts: GraphStoreOptions = {}) {\n this.baseDir = opts.baseDir ?? resolve(process.cwd(), \".agentx/graph\")\n this.log = opts.log ?? console.error.bind(console, \"[graph]\")\n mkdirSync(this.baseDir, { recursive: true })\n }\n\n // --- Paths ---\n\n schemaPath(): string { return resolve(this.baseDir, \"schema.json\") }\n nodesPath(): string { return resolve(this.baseDir, \"nodes.json\") }\n indexPath(): string { return resolve(this.baseDir, \"index.json\") }\n classificationsPath(): string { return resolve(this.baseDir, \"classifications.jsonl\") }\n\n // --- Schema ---\n\n /** Load the graph schema, seeding STARTER_SCHEMA if the file is missing. */\n loadSchema(): GraphSchema {\n const p = this.schemaPath()\n if (!existsSync(p)) {\n this.saveSchema(STARTER_SCHEMA)\n this.log(\"seeded starter schema at\", p)\n return STARTER_SCHEMA\n }\n const raw = readFileSync(p, \"utf-8\")\n const parsed = graphSchemaSchema.safeParse(JSON.parse(raw))\n if (!parsed.success) {\n throw new Error(`Invalid ${p}: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n return parsed.data\n }\n\n saveSchema(schema: GraphSchema): void {\n const parsed = graphSchemaSchema.safeParse(schema)\n if (!parsed.success) {\n throw new Error(`Refusing to save invalid schema: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n safeWriteJson(this.schemaPath(), parsed.data)\n }\n\n // --- Nodes ---\n\n loadNodes(): NodesFile {\n const p = this.nodesPath()\n if (!existsSync(p)) return { version: 1, nodes: [] }\n const raw = readFileSync(p, \"utf-8\")\n const parsed = nodesFileSchema.safeParse(JSON.parse(raw))\n if (!parsed.success) {\n throw new Error(`Invalid ${p}: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n return parsed.data\n }\n\n saveNodes(file: NodesFile): void {\n const parsed = nodesFileSchema.safeParse(file)\n if (!parsed.success) {\n throw new Error(`Refusing to save invalid nodes file: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n safeWriteJson(this.nodesPath(), parsed.data)\n }\n\n /** Validate a node against the schema AND uniqueness within the nodes file. */\n validateNode(node: GraphNode, schema: GraphSchema, all: GraphNode[]): void {\n const level = schema.levels.find((l) => l.id === node.level)\n if (!level) throw new Error(`Unknown level: \"${node.level}\"`)\n\n const missing = level.axes\n .filter((a) => !a.optional)\n .filter((a) => !(a.name in node.axes))\n if (missing.length) {\n throw new Error(\n `Node ${node.id} (${node.level}) missing required axes: ${missing.map((a) => a.name).join(\", \")}`,\n )\n }\n\n for (const a of level.axes) {\n const v = node.axes[a.name]\n if (v == null) continue\n if (a.type === \"enum\" && a.values && !a.values.includes(v)) {\n throw new Error(`Node ${node.id}: axis ${a.name}=\"${v}\" not in enum ${JSON.stringify(a.values)}`)\n }\n if (a.type === \"ref\") {\n if (!all.some((n) => n.id === v)) {\n throw new Error(`Node ${node.id}: axis ${a.name}=\"${v}\" refers to unknown node`)\n }\n if (a.refLevel) {\n const target = all.find((n) => n.id === v)!\n if (target.level !== a.refLevel) {\n throw new Error(\n `Node ${node.id}: axis ${a.name} expected level \"${a.refLevel}\" but got \"${target.level}\"`,\n )\n }\n }\n }\n }\n\n const levelIdx = schema.levels.findIndex((l) => l.id === node.level)\n if (levelIdx === 0) {\n if (node.parentId !== null) {\n throw new Error(`Root-level node ${node.id} must have parentId: null`)\n }\n } else {\n if (!node.parentId) {\n throw new Error(`Non-root node ${node.id} must have a parentId`)\n }\n const parent = all.find((n) => n.id === node.parentId)\n if (!parent) throw new Error(`Node ${node.id}: parent \"${node.parentId}\" not found`)\n const parentLevelIdx = schema.levels.findIndex((l) => l.id === parent.level)\n // Parent may be at any higher level — intermediate levels can be skipped\n // for orgs that don't use them (e.g. remote-first: no location node).\n if (parentLevelIdx < 0 || parentLevelIdx >= levelIdx) {\n throw new Error(\n `Node ${node.id} at level \"${node.level}\" has parent at level \"${parent.level}\" — parent must be at a higher level`,\n )\n }\n }\n }\n\n /**\n * Commit a full classification path: for each path element that doesn't\n * yet exist, infer its schema level by matching the proposed axes against\n * the level's required axes (first match ≥ cursor wins — this handles\n * paths that skip levels, like business → noqta where location is\n * absent). Refreshes the node list between adds so callers in a loop\n * don't hit \"Node id already exists\" on the second iteration.\n */\n commitNodesAlongPath(\n path: string[],\n proposedAxes: Record<string, Record<string, string>>,\n schema: GraphSchema,\n createdBy?: string,\n ): { added: GraphNode[]; skipped: string[] } {\n const added: GraphNode[] = []\n const skipped: string[] = []\n const now = new Date().toISOString()\n\n for (let i = 0; i < path.length; i++) {\n const id = path[i]\n // Load fresh each iteration — cheap O(N) file read, avoids stale snapshots.\n const current = this.loadNodes().nodes\n const existing = current.find((n) => n.id === id)\n if (existing) {\n skipped.push(id)\n continue\n }\n\n // Cursor: how deep the schema-levels we've already filled up to.\n let cursor = 0\n // Scan backwards through path[0..i-1] to find the deepest already-committed\n // or just-added node and resume after its level.\n for (let j = i - 1; j >= 0; j--) {\n const priorId = path[j]\n const priorNode = current.find((n) => n.id === priorId) || added.find((n) => n.id === priorId)\n if (priorNode) {\n cursor = schema.levels.findIndex((l) => l.id === priorNode.level) + 1\n break\n }\n }\n\n const axes = proposedAxes[id] ?? {}\n // Find the first level at or after the cursor whose required axes are\n // satisfied by proposedAxes[id]. \"Required\" here = listed in level.axes;\n // we accept a partial match (axes present in proposal, even if some\n // schema axes are missing — validateNode will catch truly broken ones).\n let chosenLevel: string | null = null\n for (let j = cursor; j < schema.levels.length; j++) {\n const level = schema.levels[j]\n const missing = level.axes\n .filter((a) => !a.optional)\n .filter((a) => !(a.name in axes))\n if (missing.length === 0) {\n chosenLevel = level.id\n cursor = j + 1\n break\n }\n }\n // Fallback: if no level's axes fully match, try the NEXT level after\n // the cursor anyway. Better to commit at the likely level and let\n // validateNode reject on write than to silently drop the whole path.\n if (!chosenLevel && cursor < schema.levels.length) {\n chosenLevel = schema.levels[cursor].id\n cursor++\n }\n if (!chosenLevel) {\n throw new Error(`Can't place node ${id} — no schema level left (cursor=${cursor}, levels=${schema.levels.length})`)\n }\n\n // Parent is the DEEPEST already-placed node at a level < chosenLevel.\n let parentId: string | null = null\n const chosenIdx = schema.levels.findIndex((l) => l.id === chosenLevel)\n for (let j = i - 1; j >= 0; j--) {\n const priorId = path[j]\n const prior = current.find((n) => n.id === priorId) || added.find((n) => n.id === priorId)\n if (!prior) continue\n const priorIdx = schema.levels.findIndex((l) => l.id === prior.level)\n if (priorIdx >= 0 && priorIdx < chosenIdx) { parentId = priorId; break }\n }\n\n const node: GraphNode = {\n id, level: chosenLevel, parentId, axes,\n createdAt: now,\n createdBy,\n }\n this.addNode(node) // throws if schema validation fails\n added.push(node)\n }\n\n return { added, skipped }\n }\n\n /** Append a node. Caller is responsible for id uniqueness; we check and throw. */\n addNode(node: GraphNode): GraphNode {\n const schema = this.loadSchema()\n const file = this.loadNodes()\n if (file.nodes.some((n) => n.id === node.id)) {\n throw new Error(`Node id already exists: ${node.id}`)\n }\n this.validateNode(node, schema, [...file.nodes, node])\n file.nodes.push(node)\n this.saveNodes(file)\n return node\n }\n\n // --- Classifications (append-only log) ---\n\n appendClassification(c: Classification): boolean {\n const parsed = classificationSchema.safeParse(c)\n if (!parsed.success) {\n // Before the fix this threw and took down the whole task. Now we drop\n // the bad classification and log enough detail (the offending path +\n // axes keys) for the operator to diagnose the prompt. Returning false\n // lets callers decide whether to retry with a cleaner proposal.\n const issues = parsed.error.issues.map((i) => `${i.path.join(\".\")}: ${i.message}`).join(\" · \")\n this.log(\n `skipped invalid classification — ${issues} ·`,\n `path=${JSON.stringify(c.path)}`,\n `axes-keys=${JSON.stringify(Object.keys(c.proposedAxes || {}))}`,\n )\n return false\n }\n const line = JSON.stringify(parsed.data) + \"\\n\"\n const p = this.classificationsPath()\n if (!existsSync(p)) {\n mkdirSync(dirname(p), { recursive: true })\n }\n appendFileSync(p, line)\n return true\n }\n\n /**\n * All pending classifications, newest first. Collapses the append-only\n * ledger by msgHash — the most recent entry per message wins (so if an\n * entry was updated via updateClassificationStatus, we see the current\n * state, not the original).\n */\n listPendingClassifications(limit = 200): Classification[] {\n return this.listByStatus(\"pending\", limit)\n }\n\n /** Same contract as listPendingClassifications but filtered to any status. */\n listByStatus(status: Classification[\"status\"], limit = 200): Classification[] {\n const all = this.readAllCollapsed()\n return all.filter((c) => c.status === status).slice(0, limit)\n }\n\n /**\n * Read every classification, collapse by msgHash keeping the newest entry\n * per message. Used by listByStatus + the review loop to see current state.\n * Returns newest-first order.\n */\n private readAllCollapsed(): Classification[] {\n const p = this.classificationsPath()\n if (!existsSync(p)) return []\n const raw = readFileSync(p, \"utf-8\")\n const lines = raw.split(\"\\n\").filter(Boolean)\n const byHash = new Map<string, Classification>()\n for (const line of lines) {\n try {\n const parsed = classificationSchema.safeParse(JSON.parse(line))\n if (!parsed.success) continue\n // Later entries overwrite earlier ones for the same msgHash.\n byHash.set(parsed.data.msgHash, parsed.data)\n } catch {\n // skip malformed\n }\n }\n // Newest-first — sort by ts desc.\n return Array.from(byHash.values()).sort((a, b) => (a.ts < b.ts ? 1 : -1))\n }\n\n /**\n * Append a status-update entry for an existing classification. The ledger\n * stays append-only; readers collapse by msgHash. Returns the updated\n * classification, or null if no prior entry for msgHash exists.\n */\n updateClassificationStatus(\n msgHash: string,\n update: {\n status: Classification[\"status\"]\n reviewer?: string\n reviewReason?: string\n },\n ): Classification | null {\n const all = this.readAllCollapsed()\n const prior = all.find((c) => c.msgHash === msgHash)\n if (!prior) return null\n const next: Classification = {\n ...prior,\n status: update.status,\n ts: new Date().toISOString(),\n }\n // Store reviewer + reason in the preview field so readers can see who\n // flipped what without needing a separate review-log file. Non-breaking:\n // preview is already optional text meant for UI display.\n if (update.reviewer || update.reviewReason) {\n const suffix = ` · ${update.reviewer ? `reviewer=${update.reviewer}` : \"\"}${update.reviewReason ? ` · ${update.reviewReason}` : \"\"}`\n next.preview = ((prior.preview || \"\") + suffix).slice(0, 500)\n }\n this.appendClassification(next)\n return next\n }\n\n /** Read the last N classifications, newest first. Cheap enough for the admin UI. */\n readRecentClassifications(limit = 50): Classification[] {\n const p = this.classificationsPath()\n if (!existsSync(p)) return []\n const raw = readFileSync(p, \"utf-8\")\n const lines = raw.split(\"\\n\").filter(Boolean)\n const out: Classification[] = []\n for (let i = lines.length - 1; i >= 0 && out.length < limit; i--) {\n try {\n const parsed = classificationSchema.safeParse(JSON.parse(lines[i]))\n if (parsed.success) out.push(parsed.data)\n } catch {\n // skip malformed line — append-only log; don't fail reads on one bad line\n }\n }\n return out\n }\n\n // --- Fingerprint index ---\n\n /**\n * Hash that represents \"this kind of message\" for snap-to-path caching.\n * Normalizes casing + whitespace; includes channel + sender so two people\n * writing the same words can still land on different paths if they should.\n */\n fingerprint(msg: {\n text: string\n channel?: string\n sender?: string\n }): string {\n const norm = msg.text.toLowerCase().replace(/\\s+/g, \" \").trim()\n const payload = [norm, msg.channel ?? \"\", msg.sender ?? \"\"].join(\"\\u0001\")\n return createHash(\"sha256\").update(payload).digest(\"hex\").slice(0, 32)\n }\n\n loadIndex(): IndexFile {\n const p = this.indexPath()\n if (!existsSync(p)) return { version: 1, entries: {} }\n const raw = readFileSync(p, \"utf-8\")\n const parsed = indexFileSchema.safeParse(JSON.parse(raw))\n if (!parsed.success) {\n throw new Error(`Invalid ${p}: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n return parsed.data\n }\n\n saveIndex(file: IndexFile): void {\n const parsed = indexFileSchema.safeParse(file)\n if (!parsed.success) {\n throw new Error(`Refusing to save invalid index: ${parsed.error.issues.map((i) => i.message).join(\"; \")}`)\n }\n safeWriteJson(this.indexPath(), parsed.data)\n }\n\n getFingerprint(fp: string): FingerprintEntry | undefined {\n return this.loadIndex().entries[fp]\n }\n\n setFingerprint(fp: string, entry: Omit<FingerprintEntry, \"fingerprint\" | \"updatedAt\">): void {\n const file = this.loadIndex()\n file.entries[fp] = {\n ...entry,\n fingerprint: fp,\n updatedAt: new Date().toISOString(),\n }\n this.saveIndex(file)\n }\n\n /** Update the axes on an existing node. Schema-validates the result. */\n updateNodeAxes(id: string, axes: Record<string, string>): GraphNode {\n const schema = this.loadSchema()\n const file = this.loadNodes()\n const node = file.nodes.find((n) => n.id === id)\n if (!node) throw new Error(`Node not found: ${id}`)\n const updated: GraphNode = { ...node, axes }\n this.validateNode(updated, schema, file.nodes.map((n) => (n.id === id ? updated : n)))\n file.nodes = file.nodes.map((n) => (n.id === id ? updated : n))\n this.saveNodes(file)\n return updated\n }\n\n /** Remove a node. Refuses to delete if any other node still references it\n * (child via parentId, or axis ref). */\n deleteNode(id: string): void {\n const file = this.loadNodes()\n const child = file.nodes.find((n) => n.parentId === id)\n if (child) {\n throw new Error(`Cannot delete \"${id}\" — child node \"${child.id}\" still points to it`)\n }\n const refHolder = file.nodes.find((n) =>\n Object.values(n.axes).some((v) => v === id),\n )\n if (refHolder) {\n throw new Error(`Cannot delete \"${id}\" — node \"${refHolder.id}\" references it via an axis`)\n }\n file.nodes = file.nodes.filter((n) => n.id !== id)\n this.saveNodes(file)\n }\n\n /** Approve a pending classification. Commits any new nodes along its path,\n * stamps the fingerprint index so future similar messages hit the cache,\n * and appends an audit entry. Idempotent — no-op if already cached. */\n approveClassification(c: Classification): void {\n if (this.getFingerprint(c.msgHash)) return // already approved\n // Commit any path nodes the LLM proposed but hadn't been confirmed yet.\n const schema = this.loadSchema()\n const known = new Set(this.loadNodes().nodes.map((n) => n.id))\n for (let i = 0; i < c.path.length; i++) {\n const id = c.path[i]\n if (known.has(id)) continue\n const level = schema.levels[i]?.id\n if (!level) break\n const axes = c.proposedAxes?.[id] ?? {}\n const parentId = i === 0 ? null : c.path[i - 1]\n this.addNode({\n id,\n level,\n parentId,\n axes,\n createdAt: new Date().toISOString(),\n createdBy: c.agentId,\n })\n known.add(id)\n }\n this.setFingerprint(c.msgHash, { path: c.path, leaf: c.leaf })\n this.appendClassification({ ...c, status: \"approved\", ts: new Date().toISOString() })\n }\n\n /** Record rejection — no nodes committed, no fingerprint set. */\n rejectClassification(c: Classification): void {\n this.appendClassification({ ...c, status: \"rejected\", ts: new Date().toISOString() })\n }\n}\n\n/** Atomic write: tmp file + rename. Avoids half-written JSON on crashes. */\nfunction safeWriteJson(path: string, data: unknown): void {\n mkdirSync(dirname(path), { recursive: true })\n const tmp = `${path}.tmp-${process.pid}`\n writeFileSync(tmp, JSON.stringify(data, null, 2))\n renameSync(tmp, path)\n}\n","import { z } from \"zod\"\n\n// --- Intent Knowledge Graph types + Zod schemas ---\n//\n// A hierarchical, enumerable intent taxonomy with fixed axes per level.\n// See plan: /Users/macbookpro/.claude/plans/delightful-floating-diffie.md\n//\n// Shape on disk (.agentx/graph/):\n// schema.json — level definitions + required axes per level\n// nodes.json — tree of nodes: { id, level, parentId, axes }\n// classifications.jsonl — append-only log of message → path decisions\n// index.json — fingerprint → pathId snap-to-path cache\n\nexport const axisTypeSchema = z.enum([\"enum\", \"free\", \"ref\"])\nexport type AxisType = z.infer<typeof axisTypeSchema>\n\nexport const axisDefSchema = z.object({\n name: z.string().regex(/^[a-z][a-z0-9_]*$/i, \"axis name must be a simple identifier\"),\n type: axisTypeSchema,\n /** For type=\"enum\": closed set of allowed values. */\n values: z.array(z.string()).optional(),\n /** For type=\"ref\": which level's nodes may be referenced. */\n refLevel: z.string().optional(),\n /** If true, this axis may be absent from a node without failing validation.\n * Use for axes that apply conditionally (e.g. a department's \"lead\" ref\n * doesn't apply to a person-kind unit). Absent/false = required. */\n optional: z.boolean().optional(),\n /** Optional human-readable help, shown as tooltip in the admin UI. */\n description: z.string().optional(),\n})\nexport type AxisDef = z.infer<typeof axisDefSchema>\n\nexport const levelDefSchema = z.object({\n id: z.string().regex(/^[a-z][a-z0-9_-]*$/, \"level id must be lowercase slug\"),\n /** Axes every node at this level MUST carry. Order matters for UI rendering. */\n axes: z.array(axisDefSchema).min(1),\n description: z.string().optional(),\n})\nexport type LevelDef = z.infer<typeof levelDefSchema>\n\nexport const graphSchemaSchema = z.object({\n version: z.number().int().default(1),\n /** Ordered from root to leaf. Max 5 to keep the taxonomy scannable. */\n levels: z.array(levelDefSchema).min(1).max(5),\n /** Axis describing where a leaf classification came from (channel, cron, etc.). */\n leafInput: axisDefSchema.optional(),\n /** Free-form description of the expected result of a leaf classification. */\n leafOutput: axisDefSchema.optional(),\n})\nexport type GraphSchema = z.infer<typeof graphSchemaSchema>\n\nexport const nodeIdSchema = z.string().regex(/^[a-z0-9][a-z0-9_-]*$/, \"node id must be lowercase slug\")\n\nexport const graphNodeSchema = z.object({\n id: nodeIdSchema,\n /** Matches a `levels[*].id` from the schema. */\n level: z.string(),\n /** null for root-level nodes (level[0]); otherwise the id of a node at level-1. */\n parentId: nodeIdSchema.nullable(),\n /** Values for every axis declared at this level's schema entry. `ref` axes\n * hold the id of another node; `enum` axes hold one of the declared values;\n * `free` axes hold any string. */\n axes: z.record(z.string(), z.string()),\n createdAt: z.string(),\n createdBy: z.string().optional(),\n})\nexport type GraphNode = z.infer<typeof graphNodeSchema>\n\nexport const classificationSourceSchema = z.enum([\"llm\", \"user\", \"cache\"])\nexport type ClassificationSource = z.infer<typeof classificationSourceSchema>\n\nexport const classificationStatusSchema = z.enum([\"pending\", \"approved\", \"rejected\"])\nexport type ClassificationStatus = z.infer<typeof classificationStatusSchema>\n\nexport const classificationSchema = z.object({\n ts: z.string(),\n msgHash: z.string(),\n agentId: z.string().optional(),\n channel: z.string().optional(),\n sender: z.string().optional(),\n /** Node ids from root to leaf, one per level. Shorter-than-full paths are\n * allowed if the classifier was only confident down to a certain depth. */\n path: z.array(nodeIdSchema),\n /** Axis values asserted by the classifier for new nodes it proposed along\n * the path. Keyed by node id. */\n proposedAxes: z.record(z.string(), z.record(z.string(), z.string())).default({}),\n /** Leaf-level extras. */\n leaf: z.object({\n input: z.string().optional(),\n output: z.string().optional(),\n }).partial().default({}),\n source: classificationSourceSchema,\n status: classificationStatusSchema,\n confidence: z.number().min(0).max(1).optional(),\n /** Redacted first ~200 chars of the message, for the approval UI. */\n preview: z.string().optional(),\n})\nexport type Classification = z.infer<typeof classificationSchema>\n\n/** Row in the fingerprint → path snap cache. */\nexport const fingerprintEntrySchema = z.object({\n fingerprint: z.string(),\n path: z.array(nodeIdSchema),\n leaf: classificationSchema.shape.leaf,\n updatedAt: z.string(),\n})\nexport type FingerprintEntry = z.infer<typeof fingerprintEntrySchema>\n\n/** Nodes file is a plain list, not a nested tree — lookups are by id. */\nexport const nodesFileSchema = z.object({\n version: z.number().int().default(1),\n nodes: z.array(graphNodeSchema).default([]),\n})\nexport type NodesFile = z.infer<typeof nodesFileSchema>\n\n/** Index file — record of fingerprints. Hot path; kept as a map on disk. */\nexport const indexFileSchema = z.object({\n version: z.number().int().default(1),\n entries: z.record(z.string(), fingerprintEntrySchema).default({}),\n})\nexport type IndexFile = z.infer<typeof indexFileSchema>\n","import { createHash } from \"crypto\"\nimport { GraphStore } from \"./store\"\nimport {\n type GraphSchema,\n type GraphNode,\n type Classification,\n} from \"./types\"\n\n// --- Intent Knowledge Graph classifier ---\n//\n// For every incoming message, returns a path through the taxonomy:\n// 1. fingerprint → cache hit → reuse path (0 LLM calls, source=cache)\n// 2. miss → LLM draftAgent proposes { path, axes } → validated → pending\n// 3. autoApproveConfidence met → committed + cached\n//\n// Pending paths are still usable immediately (caller tags artifacts with\n// them); approval flips the flag and populates the fingerprint index so\n// the next similar message hits the cache.\n\nexport interface ClassifyInput {\n text: string\n channel?: string\n sender?: string\n /** Agent that will RECEIVE the message after classification. When this\n * equals `draftAgent`, classification is skipped to prevent a deadlock —\n * the classifier's sub-task would otherwise queue behind the in-progress\n * main task on the same agent. */\n agentId?: string\n}\n\nexport interface ClassifyResult {\n /** Node ids from root to leaf. Shorter-than-full paths are allowed. */\n path: string[]\n /** Stable hash of the path — used as the `graph:<pathId>` wiki tag. */\n pathId: string\n /** Human-readable \"A › B › C\" path for logs + UI. */\n pathLabel: string\n /** Axis values asserted by the classifier. Keyed by node id. */\n axes: Record<string, Record<string, string>>\n leaf: { input?: string; output?: string }\n source: \"cache\" | \"llm\"\n status: \"pending\" | \"approved\"\n confidence?: number\n}\n\nexport type ApprovalStructurePolicy = \"strict\" | \"extend-leaves\" | \"any\"\n\nexport interface ClassifierOptions {\n store: GraphStore\n /** Base URL of the daemon — used to POST /task for LLM proposals. */\n daemonUrl: string\n /** Optional bearer token for the daemon. */\n token?: string\n /** Which agent makes the proposal. Required if caller ever expects a\n * non-cached classification (cache hits work without an agent). */\n draftAgent?: string\n /** Structural approval policy — see config.ts for semantics. Default\n * \"extend-leaves\" (auto-approve pure reuse + single-leaf additions). */\n autoApproveStructure?: ApprovalStructurePolicy\n /** Minimum confidence (0..1) to commit without human approval. 1.0 = never.\n * OR'd with the structural policy. */\n autoApproveConfidence?: number\n log?: (...args: unknown[]) => void\n}\n\nexport class Classifier {\n private store: GraphStore\n private daemonUrl: string\n private token?: string\n private draftAgent?: string\n private autoApprove: number\n private autoApproveStructure: ApprovalStructurePolicy\n private log: (...args: unknown[]) => void\n\n constructor(opts: ClassifierOptions) {\n this.store = opts.store\n this.daemonUrl = opts.daemonUrl.replace(/\\/+$/, \"\")\n this.token = opts.token\n this.draftAgent = opts.draftAgent\n this.autoApprove = opts.autoApproveConfidence ?? 1.0\n this.autoApproveStructure = opts.autoApproveStructure ?? \"extend-leaves\"\n this.log = opts.log ?? console.error.bind(console, \"[classifier]\")\n }\n\n async classify(input: ClassifyInput): Promise<ClassifyResult | null> {\n const fp = this.store.fingerprint({\n text: input.text,\n channel: input.channel,\n sender: input.sender,\n })\n\n // 1. Cache hit — instant return, zero LLM.\n const cached = this.store.getFingerprint(fp)\n if (cached) {\n return {\n path: cached.path,\n pathId: hashPath(cached.path),\n pathLabel: pathLabel(cached.path, this.store.loadNodes().nodes),\n axes: {},\n leaf: cached.leaf,\n source: \"cache\",\n status: \"approved\",\n }\n }\n\n // 2. Cache miss — need an LLM proposal. Callers without a draftAgent\n // get null, which the context engine treats as \"no classification\n // this turn, fall through to the old regex tags.\"\n if (!this.draftAgent) return null\n // Skip classification when the target agent IS the draftAgent. The\n // classifier's sub-call goes through the same registry; with\n // maxConcurrent=1 it would queue behind the task we're trying to\n // classify for, deadlocking both.\n if (input.agentId && input.agentId === this.draftAgent) return null\n\n const schema = this.store.loadSchema()\n const nodes = this.store.loadNodes().nodes\n const proposal = await this.proposePath(input, schema, nodes).catch((e) => {\n this.log(\"LLM proposal failed:\", e?.message || e)\n return null\n })\n if (!proposal) return null\n\n // 3. Validate + persist as pending. Any NEW node the LLM proposed is\n // only committed if the whole classification auto-approves below.\n const { path, proposedAxes, confidence, leaf } = proposal\n if (path.length === 0) return null\n\n const classification: Classification = {\n ts: new Date().toISOString(),\n msgHash: fp,\n agentId: input.agentId,\n channel: input.channel,\n sender: input.sender,\n path,\n proposedAxes,\n leaf,\n source: \"llm\",\n status: \"pending\",\n confidence,\n preview: input.text.slice(0, 200),\n }\n\n // --- Auto-approval policy (see config.ts for full semantics) ---\n //\n // Two conditions, OR'd. Either approves the classification:\n // 1. Structure policy — what KIND of change the proposed path makes:\n // strict: nothing auto — always pending\n // extend-leaves: auto if the path either (a) reuses only existing\n // nodes, or (b) adds exactly one new node at the\n // DEEPEST level (a new leaf). Structural changes\n // (new mid-path or root node) still queue.\n // any: auto regardless of structure.\n // 2. Confidence — LLM-self-reported confidence >= autoApproveConfidence.\n //\n // Pending classifications still persist to the log, but don't commit nodes\n // and don't populate the fingerprint cache (so similar messages re-query).\n const existingIds = new Set(nodes.map((n) => n.id))\n const newNodeIndices = path\n .map((id, idx) => (existingIds.has(id) ? -1 : idx))\n .filter((idx) => idx >= 0)\n\n let structureOk = false\n if (this.autoApproveStructure === \"any\") {\n structureOk = true\n } else if (this.autoApproveStructure === \"extend-leaves\") {\n const isReuseOnly = newNodeIndices.length === 0\n const isSingleLeafAddition =\n newNodeIndices.length === 1 && newNodeIndices[0] === path.length - 1\n structureOk = isReuseOnly || isSingleLeafAddition\n }\n const confidenceOk = confidence !== undefined && confidence >= this.autoApprove\n\n let status: \"pending\" | \"approved\" = \"pending\"\n if (structureOk || confidenceOk) {\n // Commit any new nodes; if ANY fail schema validation (missing axes,\n // bad ref, etc.) fall back to pending for the whole classification\n // rather than half-committing the graph and bubbling the error up to\n // the task handler.\n try {\n this.commitNewNodes(path, proposedAxes, schema, nodes, input.sender)\n this.store.setFingerprint(fp, { path, leaf })\n status = \"approved\"\n } catch (e: any) {\n this.log(\n `auto-approve failed — staying pending ·`,\n `path=${JSON.stringify(path)}`,\n `reason=${(e?.message || e).toString().slice(0, 200)}`,\n )\n }\n }\n classification.status = status\n this.store.appendClassification(classification)\n\n return {\n path,\n pathId: hashPath(path),\n pathLabel: pathLabel(path, this.store.loadNodes().nodes),\n axes: proposedAxes,\n leaf,\n source: \"llm\",\n status,\n confidence,\n }\n }\n\n /** Ask the draftAgent to propose a path. Pure I/O; no side effects. */\n private async proposePath(\n input: ClassifyInput,\n schema: GraphSchema,\n nodes: GraphNode[],\n ): Promise<{\n path: string[]\n proposedAxes: Record<string, Record<string, string>>\n confidence?: number\n leaf: { input?: string; output?: string }\n } | null> {\n const nodesForPrompt = nodes.map((n) => ({\n id: n.id,\n level: n.level,\n parentId: n.parentId,\n axes: n.axes,\n }))\n const userPrompt = [\n `You are the intent classifier for AgentX's knowledge graph.`,\n ``,\n `SCHEMA (fixed axes per level, root → leaf):`,\n \"```json\",\n JSON.stringify(schema, null, 2),\n \"```\",\n ``,\n `EXISTING NODES (choose ids from here when the message matches; propose NEW nodes only when nothing fits):`,\n \"```json\",\n JSON.stringify(nodesForPrompt, null, 2),\n \"```\",\n ``,\n `MESSAGE:`,\n \"```\",\n input.text.slice(0, 2000),\n \"```\",\n `channel: ${input.channel ?? \"?\"}`,\n `sender: ${input.sender ?? \"?\"}`,\n ``,\n `TASK: classify this message into one path through the schema — one node per level from root toward leaf. Shorter paths are OK if you are not confident past a certain depth. For any node id you invent, include its axes in proposedAxes.`,\n ``,\n `NODE ID RULES (strict — invalid ids get dropped):`,\n `- lowercase-kebab only: [a-z0-9][a-z0-9_-]*`,\n `- no spaces, no uppercase, no punctuation, no Arabic/other non-Latin`,\n `- if the human-readable label is \"Sales Manager\", the id is \"sales-manager\"`,\n `- if the label contains non-Latin script, use a short Latin-slug + put the original in axes.name`,\n `- good: \"business\", \"noqta\", \"sales-manager\", \"activity-review-mr\"`,\n `- bad: \"Business\", \"sales manager\", \"Scope: Business\", \"تليجرام\"`,\n ``,\n `If a node needs a display name, put the human-readable form in axes.name — never in the node id.`,\n ``,\n `Return ONE JSON object on one line, no prose, no fences:`,\n ` { \"path\": string[], \"proposedAxes\": { [nodeId]: { [axisName]: string } }, \"leaf\": { \"input\"?: string, \"output\"?: string }, \"confidence\": number }`,\n `confidence in [0,1]. Prefer low confidence over guessing.`,\n ].join(\"\\n\")\n\n // 60s ceiling. Originally 20s on the assumption the classifier makes a\n // tiny JSON-only call, but in practice the daemon routes it through the\n // draftAgent's Claude Code subprocess which has real cold-start + queue\n // wait costs. At 20s almost every proposal was timing out on a fresh\n // daemon. 60s is the right balance: long enough for a sonnet turn,\n // short enough that a genuinely hung sub-task doesn't stall the caller.\n const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), 60_000)\n let r: Response\n try {\n r = await fetch(`${this.daemonUrl}/task`, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n ...(this.token ? { Authorization: `Bearer ${this.token}` } : {}),\n },\n body: JSON.stringify({\n agent: this.draftAgent,\n message: userPrompt,\n context: {\n channel: \"a2a\",\n sender: \"graph-classifier\",\n systemPrompt:\n \"You are a taxonomy classifier. Respond with a single JSON object. No prose, no code fences.\",\n },\n }),\n signal: ac.signal,\n })\n } finally {\n clearTimeout(timer)\n }\n if (!r.ok) throw new Error(`daemon /task HTTP ${r.status}`)\n const data: any = await r.json()\n if (data?.error) throw new Error(String(data.error))\n\n const parsed = extractJson((data?.content || \"\").toString())\n if (!parsed || !Array.isArray(parsed.path)) return null\n\n // LLM often returns human-readable labels (\"Business\", \"Sales Manager\").\n // The node-id schema requires a lowercase slug, so normalize here and\n // remember the original → slug mapping so proposedAxes stays attached.\n // We also drop any element that can't be slugified into a valid id (e.g.\n // a path entry of pure Arabic script) — better to classify with a shorter\n // path than to have the store reject the whole classification.\n const NODE_ID_RE = /^[a-z0-9][a-z0-9_-]*$/\n const slugMap = new Map<string, string>()\n const path: string[] = parsed.path\n .filter((s: unknown): s is string => typeof s === \"string\" && s.length > 0)\n .map((s: string) => {\n const trimmed = s.trim()\n const slugged = slugifyNodeId(trimmed)\n if (trimmed !== slugged) slugMap.set(trimmed, slugged)\n return slugged\n })\n .filter((s: string) => NODE_ID_RE.test(s))\n if (path.length === 0) return null\n\n const proposedAxes: Record<string, Record<string, string>> = {}\n if (parsed.proposedAxes && typeof parsed.proposedAxes === \"object\") {\n for (const [rawNodeId, axes] of Object.entries(parsed.proposedAxes as Record<string, any>)) {\n if (!axes || typeof axes !== \"object\") continue\n const nodeId = slugMap.get(rawNodeId) ?? slugifyNodeId(rawNodeId)\n if (!nodeId) continue\n const clean: Record<string, string> = {}\n for (const [k, v] of Object.entries(axes)) {\n if (typeof v === \"string\") clean[k] = v\n }\n // If the LLM didn't volunteer a human-readable name but we slugified\n // one away, preserve the original as a `name` axis so the UI can\n // render \"Sales Manager\" not \"sales-manager\".\n if (!clean.name && rawNodeId !== nodeId) clean.name = rawNodeId.trim()\n proposedAxes[nodeId] = clean\n }\n }\n\n const leaf: { input?: string; output?: string } = {}\n if (parsed.leaf && typeof parsed.leaf === \"object\") {\n if (typeof parsed.leaf.input === \"string\") leaf.input = parsed.leaf.input\n if (typeof parsed.leaf.output === \"string\") leaf.output = parsed.leaf.output\n }\n\n const confidence = typeof parsed.confidence === \"number\"\n ? Math.max(0, Math.min(1, parsed.confidence))\n : undefined\n\n return { path, proposedAxes, confidence, leaf }\n }\n\n /** Create any nodes along `path` that don't exist yet. Delegates to the\n * store so level inference (for paths that skip levels) + fresh reloads\n * between adds are shared with the review path. */\n private commitNewNodes(\n path: string[],\n proposedAxes: Record<string, Record<string, string>>,\n schema: GraphSchema,\n _existing: GraphNode[],\n createdBy?: string,\n ): void {\n this.store.commitNodesAlongPath(path, proposedAxes, schema, createdBy)\n }\n}\n\n/** Normalize an LLM-proposed node label to a valid node id. Matches the\n * schema's `^[a-z0-9][a-z0-9_-]*$` — lowercase, alnum/_/-, leading alnum. */\nfunction slugifyNodeId(raw: string): string {\n const s = raw\n .trim()\n .toLowerCase()\n .replace(/[^a-z0-9_-]+/g, \"-\")\n .replace(/^[-_]+|[-_]+$/g, \"\")\n .replace(/-{2,}/g, \"-\")\n if (!s) return \"\"\n // Ensure leading char is alnum (schema rejects leading _ or -).\n return /^[a-z0-9]/.test(s) ? s : `n-${s}`.replace(/[-_]+$/, \"\")\n}\n\n// --- path helpers used by both classifier + wiki retrieval ---\n\n/** Deterministic hash of a path, used as the `graph:<pathId>` wiki tag. */\nexport function hashPath(path: string[]): string {\n return createHash(\"sha1\")\n .update(path.join(\"\\u0001\"))\n .digest(\"hex\")\n .slice(0, 16)\n}\n\n/** \"Business › Noqta › DevOps › Review MR\" — for the UI + context render. */\nexport function pathLabel(path: string[], nodes: GraphNode[]): string {\n return path\n .map((id) => {\n const n = nodes.find((x) => x.id === id)\n if (!n) return id\n // Prefer a `name` axis when the schema has one; fall back to id.\n const name = n.axes?.name || n.axes?.what || id\n return name\n })\n .join(\" › \")\n}\n\n/**\n * Depth of the deepest common ancestor between two paths, normalized to [0,1].\n * Used to score wiki articles: exact match = 1, shared grandparent = 0.5, none = 0.\n */\nexport function ancestryScore(a: string[], b: string[]): number {\n if (a.length === 0 || b.length === 0) return 0\n let shared = 0\n for (let i = 0; i < Math.min(a.length, b.length); i++) {\n if (a[i] === b[i]) shared++\n else break\n }\n if (shared === 0) return 0\n const depth = Math.max(a.length, b.length)\n return shared / depth\n}\n\n// --- tiny JSON extractor (agents wrap output in fences sometimes) ---\n\nfunction extractJson(text: string): any {\n if (!text) return null\n try {\n return JSON.parse(text)\n } catch {\n /* fall through */\n }\n const fenced = text.match(/```(?:json)?\\s*([\\s\\S]*?)```/)\n if (fenced) {\n try {\n return JSON.parse(fenced[1])\n } catch {\n /* fall through */\n }\n }\n // Last-ditch: grab the first balanced {...}.\n const start = text.indexOf(\"{\")\n if (start < 0) return null\n let depth = 0\n for (let i = start; i < text.length; i++) {\n if (text[i] === \"{\") depth++\n else if (text[i] === \"}\") {\n depth--\n if (depth === 0) {\n try {\n return JSON.parse(text.slice(start, i + 1))\n } catch {\n return null\n }\n }\n }\n }\n return null\n}\n"],"mappings":"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","names":["STARTER_SCHEMA","readFileSync","writeFileSync","appendFileSync","existsSync","mkdirSync","renameSync","resolve","dirname","createHash","z","axisTypeSchema","axisDefSchema","levelDefSchema","graphSchemaSchema","nodeIdSchema","graphNodeSchema","classificationSourceSchema","classificationStatusSchema","classificationSchema","fingerprintEntrySchema","nodesFileSchema","indexFileSchema","GraphStore","opts","resolve","mkdirSync","p","existsSync","STARTER_SCHEMA","raw","readFileSync","parsed","graphSchemaSchema","i","schema","safeWriteJson","nodesFileSchema","file","node","all","level","l","missing","a","v","n","target","levelIdx","parent","parentLevelIdx","path","proposedAxes","createdBy","added","skipped","now","id","current","cursor","j","priorId","priorNode","axes","chosenLevel","parentId","chosenIdx","prior","priorIdx","c","classificationSchema","issues","line","dirname","appendFileSync","limit","status","lines","byHash","b","msgHash","update","next","suffix","out","msg","payload","createHash","indexFileSchema","fp","entry","updated","child","refHolder","known","data","tmp","writeFileSync","renameSync","createHash","Classifier","opts","input","fp","cached","hashPath","pathLabel","schema","nodes","proposal","e","path","proposedAxes","confidence","leaf","classification","existingIds","n","newNodeIndices","id","idx","structureOk","isReuseOnly","isSingleLeafAddition","confidenceOk","status","nodesForPrompt","userPrompt","ac","timer","r","data","parsed","extractJson","NODE_ID_RE","slugMap","s","trimmed","slugged","slugifyNodeId","rawNodeId","axes","nodeId","clean","k","v","_existing","createdBy","raw","x","ancestryScore","a","b","shared","i","depth","text","fenced","start"]}
|
package/dist/config-UOHEG4BW.js
DELETED
package/dist/debug-TGAP7E7W.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/daemon/ui/pages/inbox.ts"],"sourcesContent":["import { renderShell, esc, type TopbarPeer } from \"..\"\n\n// --- Inbox page ---\n//\n// Per-actor (or global) list of open user-tasks with an inline form\n// renderer. Reads /api/workflows/tasks; submits via\n// POST /api/workflows/tasks/:id/submit.\n//\n// Scope is intentionally tight for Phase 1:\n// - text / long-text / number / boolean / select / multi-select / date\n// - primary + optional secondary action buttons\n// - required-field validation is server-side; the UI just relays errors\n//\n// No channel-adapter coupling here — the form UI is the same whether the\n// renderer ran on Telegram or not.\n\nexport interface InboxPageOpts {\n peers?: TopbarPeer[]\n currentPeerId?: string\n /** Optional actor id to pre-filter. When unset, the page shows a prompt\n * asking the user to pick their actor id. */\n actor?: string\n}\n\nexport function renderInboxPage(opts: InboxPageOpts = {}): string {\n const body = `<div class=\"ax-inbox__root\" data-actor=\"${esc(opts.actor ?? \"\")}\">\n <aside class=\"ax-inbox__list\">\n <header>\n <h2>Inbox</h2>\n <button id=\"inbox-refresh\" class=\"ax-inbox__icon\" title=\"Refresh (r)\">↻</button>\n </header>\n <div class=\"ax-inbox__actor\">\n <label>Actor <input id=\"inbox-actor\" type=\"text\" placeholder=\"actor:<id>\" value=\"${esc(opts.actor ?? \"\")}\" autocomplete=\"off\" /></label>\n </div>\n <ul id=\"inbox-tasks\" class=\"ax-inbox__cards\" aria-live=\"polite\"></ul>\n <div id=\"inbox-empty\" class=\"ax-inbox__empty\" hidden>\n <p>No open tasks.</p>\n <p class=\"hint\">Set the actor filter to see tasks assigned to you, or leave blank for all tasks in the system.</p>\n </div>\n </aside>\n\n <section class=\"ax-inbox__detail\">\n <header id=\"inbox-detail-head\">\n <span class=\"hint\">Select a task to open its form.</span>\n </header>\n <div id=\"inbox-detail-body\" class=\"ax-inbox__detail-body\"></div>\n </section>\n</div>\n\n<div id=\"inbox-toast\" class=\"ax-inbox__toast\" hidden></div>`\n\n return renderShell({\n title: \"AgentX · Inbox\",\n activeTab: \"workflows\",\n subtitle: \"Inbox\",\n peers: opts.peers,\n currentPeerId: opts.currentPeerId,\n body,\n css: INBOX_PAGE_CSS,\n scripts: `<script>${INBOX_PAGE_SCRIPT}</script>`,\n })\n}\n\nconst INBOX_PAGE_CSS = `\n.ax-inbox__root {\n display: grid;\n grid-template-columns: 340px 1fr;\n height: calc(100vh - var(--ax-topbar-h, 48px));\n background: var(--ax-bg);\n}\n.ax-inbox__list {\n border-right: 1px solid var(--ax-border);\n display: flex;\n flex-direction: column;\n min-height: 0;\n}\n.ax-inbox__list > header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n padding: 12px 14px;\n border-bottom: 1px solid var(--ax-border);\n}\n.ax-inbox__list > header h2 { margin: 0; font-size: 14px; font-weight: 600; }\n.ax-inbox__actor {\n padding: 10px 14px;\n border-bottom: 1px solid var(--ax-border);\n}\n.ax-inbox__actor label { display: flex; flex-direction: column; gap: 4px; font-size: 12px; color: var(--ax-muted); }\n.ax-inbox__actor input {\n font: inherit;\n padding: 6px 8px;\n border: 1px solid var(--ax-border);\n border-radius: 6px;\n background: var(--ax-bg);\n color: var(--ax-fg);\n}\n.ax-inbox__cards { list-style: none; margin: 0; padding: 6px; overflow-y: auto; flex: 1; }\n.ax-inbox__card {\n padding: 10px 12px;\n border-radius: 6px;\n margin-bottom: 4px;\n cursor: pointer;\n border: 1px solid transparent;\n}\n.ax-inbox__card:hover { background: var(--ax-surface-2, rgba(127,127,127,0.08)); }\n.ax-inbox__card.is-active { background: var(--ax-surface-2); border-color: var(--ax-border); }\n.ax-inbox__card-title { font-weight: 600; font-size: 13px; margin-bottom: 2px; }\n.ax-inbox__card-meta { font-size: 11px; color: var(--ax-muted); }\n.ax-inbox__card-due { font-size: 11px; color: #d48a00; margin-top: 4px; }\n.ax-inbox__empty { padding: 20px; color: var(--ax-muted); text-align: center; font-size: 13px; }\n.ax-inbox__detail { display: flex; flex-direction: column; min-height: 0; }\n.ax-inbox__detail > header { padding: 14px 18px; border-bottom: 1px solid var(--ax-border); }\n.ax-inbox__detail-body { overflow-y: auto; padding: 18px; flex: 1; }\n.ax-inbox__form { max-width: 560px; display: grid; gap: 14px; }\n.ax-inbox__field { display: flex; flex-direction: column; gap: 4px; }\n.ax-inbox__field label { font-size: 12px; color: var(--ax-muted); font-weight: 600; }\n.ax-inbox__field input, .ax-inbox__field textarea, .ax-inbox__field select {\n font: inherit;\n padding: 8px 10px;\n border: 1px solid var(--ax-border);\n border-radius: 6px;\n background: var(--ax-bg);\n color: var(--ax-fg);\n}\n.ax-inbox__field textarea { min-height: 100px; resize: vertical; }\n.ax-inbox__field-hint { font-size: 11px; color: var(--ax-muted); }\n.ax-inbox__field-error { font-size: 11px; color: #c0392b; }\n.ax-inbox__actions { display: flex; gap: 8px; margin-top: 8px; }\n.ax-inbox__actions button {\n font: inherit;\n padding: 8px 14px;\n border: 1px solid var(--ax-border);\n border-radius: 6px;\n cursor: pointer;\n background: var(--ax-surface-2);\n color: var(--ax-fg);\n}\n.ax-inbox__actions button.is-primary {\n background: #2ecc71; color: white; border-color: #27ae60;\n}\n.ax-inbox__actions button:disabled { opacity: 0.5; cursor: not-allowed; }\n.ax-inbox__icon {\n background: none; border: 1px solid transparent; color: var(--ax-fg); cursor: pointer;\n font-size: 14px; padding: 4px 8px; border-radius: 4px;\n}\n.ax-inbox__icon:hover { background: var(--ax-surface-2); }\n.ax-inbox__toast {\n position: fixed; bottom: 16px; right: 16px; padding: 10px 14px;\n background: var(--ax-fg); color: var(--ax-bg); border-radius: 6px;\n font-size: 13px; z-index: 100;\n}\n`\n\n// Deliberately vanilla JS — no framework dep. Fetches task list, renders\n// per-field inputs, POSTs submission.\nconst INBOX_PAGE_SCRIPT = `\n(function(){\n const $ = (sel) => document.querySelector(sel);\n const state = { tasks: [], selected: null, actor: document.querySelector('.ax-inbox__root').dataset.actor || '' };\n const toast = (msg) => {\n const el = $('#inbox-toast');\n el.textContent = msg;\n el.hidden = false;\n setTimeout(() => { el.hidden = true; }, 2500);\n };\n\n async function fetchTasks() {\n const qs = state.actor ? ('?actor=' + encodeURIComponent(state.actor)) : '';\n try {\n const r = await fetch('/api/workflows/tasks' + qs, { credentials: 'same-origin' });\n if (!r.ok) { toast('fetch failed: ' + r.status); return; }\n const data = await r.json();\n state.tasks = Array.isArray(data.tasks) ? data.tasks : [];\n renderList();\n if (state.selected && !state.tasks.find(t => t.id === state.selected)) {\n state.selected = null;\n renderDetail();\n }\n } catch (e) { toast('fetch error: ' + e.message); }\n }\n\n function renderList() {\n const ul = $('#inbox-tasks');\n const empty = $('#inbox-empty');\n ul.innerHTML = '';\n if (!state.tasks.length) { empty.hidden = false; return; }\n empty.hidden = true;\n for (const t of state.tasks) {\n const li = document.createElement('li');\n li.className = 'ax-inbox__card' + (t.id === state.selected ? ' is-active' : '');\n li.innerHTML =\n '<div class=\"ax-inbox__card-title\">' + escHtml(t.title) + '</div>' +\n '<div class=\"ax-inbox__card-meta\">' + escHtml(t.assignee) + ' · ' + escHtml(t.workflowId) + '</div>' +\n (t.dueAt ? ('<div class=\"ax-inbox__card-due\">due ' + new Date(t.dueAt).toLocaleString() + '</div>') : '');\n li.addEventListener('click', () => { state.selected = t.id; renderList(); renderDetail(); });\n ul.appendChild(li);\n }\n }\n\n function renderDetail() {\n const head = $('#inbox-detail-head');\n const body = $('#inbox-detail-body');\n const t = state.tasks.find(x => x.id === state.selected);\n if (!t) {\n head.innerHTML = '<span class=\"hint\">Select a task to open its form.</span>';\n body.innerHTML = '';\n return;\n }\n head.innerHTML = '<h2 style=\"margin:0;font-size:15px\">' + escHtml(t.title) + '</h2>' +\n (t.description ? '<p class=\"hint\" style=\"margin:6px 0 0\">' + escHtml(t.description) + '</p>' : '');\n const form = document.createElement('form');\n form.className = 'ax-inbox__form';\n form.addEventListener('submit', (e) => { e.preventDefault(); submit(t, 'primary'); });\n for (const field of (t.form.fields || [])) {\n form.appendChild(renderField(field));\n }\n const actions = document.createElement('div');\n actions.className = 'ax-inbox__actions';\n const primary = document.createElement('button');\n primary.type = 'submit';\n primary.className = 'is-primary';\n primary.textContent = t.form.submitLabel || 'Submit';\n actions.appendChild(primary);\n if (t.form.secondaryAction) {\n const secondary = document.createElement('button');\n secondary.type = 'button';\n secondary.textContent = t.form.secondaryAction.label;\n secondary.addEventListener('click', () => submit(t, 'secondary'));\n actions.appendChild(secondary);\n }\n form.appendChild(actions);\n body.innerHTML = '';\n body.appendChild(form);\n }\n\n function renderField(field) {\n const wrap = document.createElement('div');\n wrap.className = 'ax-inbox__field';\n const label = document.createElement('label');\n label.textContent = field.label + (field.required ? ' *' : '');\n wrap.appendChild(label);\n let input;\n switch (field.type) {\n case 'long-text':\n input = document.createElement('textarea');\n break;\n case 'number':\n input = document.createElement('input'); input.type = 'number';\n break;\n case 'boolean':\n input = document.createElement('select');\n for (const v of ['', 'yes', 'no']) {\n const o = document.createElement('option'); o.value = v; o.textContent = v || '—';\n input.appendChild(o);\n }\n break;\n case 'date':\n input = document.createElement('input'); input.type = 'date';\n break;\n case 'select':\n input = document.createElement('select');\n if (!field.required) { const o = document.createElement('option'); o.value = ''; o.textContent = '—'; input.appendChild(o); }\n for (const opt of (field.options || [])) {\n const o = document.createElement('option'); o.value = opt; o.textContent = opt;\n input.appendChild(o);\n }\n break;\n case 'multi-select':\n input = document.createElement('select'); input.multiple = true; input.size = Math.min(6, (field.options||[]).length || 3);\n for (const opt of (field.options || [])) {\n const o = document.createElement('option'); o.value = opt; o.textContent = opt;\n input.appendChild(o);\n }\n break;\n case 'file':\n input = document.createElement('input'); input.type = 'text'; input.placeholder = 'URL or path';\n break;\n default:\n input = document.createElement('input'); input.type = 'text';\n }\n input.name = field.key;\n if (field.defaultValue !== undefined && field.type !== 'multi-select') input.value = String(field.defaultValue);\n wrap.appendChild(input);\n if (field.hint) {\n const hint = document.createElement('div');\n hint.className = 'ax-inbox__field-hint';\n hint.textContent = field.hint;\n wrap.appendChild(hint);\n }\n const err = document.createElement('div');\n err.className = 'ax-inbox__field-error';\n err.dataset.for = field.key;\n wrap.appendChild(err);\n return wrap;\n }\n\n async function submit(task, action) {\n const form = $('.ax-inbox__form');\n if (!form) return;\n const values = {};\n for (const field of (task.form.fields || [])) {\n const el = form.querySelector('[name=\"' + field.key + '\"]');\n if (!el) continue;\n if (field.type === 'multi-select') {\n values[field.key] = Array.from(el.selectedOptions).map(o => o.value);\n } else if (field.type === 'boolean') {\n values[field.key] = el.value;\n } else {\n values[field.key] = el.value;\n }\n }\n form.querySelectorAll('.ax-inbox__field-error').forEach(e => { e.textContent = ''; });\n try {\n const r = await fetch('/api/workflows/tasks/' + encodeURIComponent(task.id) + '/submit', {\n method: 'POST',\n credentials: 'same-origin',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ submittedBy: state.actor || 'anonymous', submission: { action, values } }),\n });\n const data = await r.json().catch(() => ({}));\n if (!r.ok) {\n if (data.fieldErrors && Array.isArray(data.fieldErrors)) {\n for (const fe of data.fieldErrors) {\n const node = form.querySelector('[data-for=\"' + fe.field + '\"]');\n if (node) node.textContent = fe.message;\n }\n toast('form has errors');\n } else {\n toast(data.error || 'submit failed');\n }\n return;\n }\n toast('submitted');\n state.selected = null;\n fetchTasks();\n } catch (e) { toast('submit error: ' + e.message); }\n }\n\n function escHtml(s) {\n return String(s).replace(/[&<>\"']/g, c => ({'&':'&','<':'<','>':'>','\"':'"',\"'\":'''})[c]);\n }\n\n $('#inbox-actor').addEventListener('change', (e) => {\n state.actor = e.target.value.trim();\n state.selected = null;\n const url = new URL(window.location.href);\n if (state.actor) url.searchParams.set('actor', state.actor); else url.searchParams.delete('actor');\n history.replaceState({}, '', url.toString());\n fetchTasks();\n });\n $('#inbox-refresh').addEventListener('click', fetchTasks);\n\n fetchTasks();\n setInterval(fetchTasks, 10000);\n})();\n`\n"],"mappings":"sEAwBO,SAASA,EAAgBC,EAAsB,CAAC,EAAW,CAChE,IAAMC,EAAO,2CAA2CC,EAAIF,EAAK,OAAS,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,yFAOWE,EAAIF,EAAK,OAAS,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6DAmB3G,OAAOG,EAAY,CACjB,MAAO,oBACP,UAAW,YACX,SAAU,QACV,MAAOH,EAAK,MACZ,cAAeA,EAAK,cACpB,KAAAC,EACA,IAAKG,EACL,QAAS,WAAWC,YACtB,CAAC,CACH,CAEA,IAAMD,EAAiB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA6FjBC,EAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;","names":["renderInboxPage","opts","body","esc","renderShell","INBOX_PAGE_CSS","INBOX_PAGE_SCRIPT"]}
|
package/dist/loader-BGWA7IRY.js
DELETED
package/dist/loader-LBHKSXBK.js
DELETED
package/dist/program-2MDELQLX.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{a}from"./chunk-BLUXL6HR.js";import"./chunk-ASSJ4Z6Z.js";import"./chunk-6PPIFDOQ.js";import"./chunk-U364GNQX.js";import"./chunk-IM376SD6.js";import"./chunk-ZE2YFX4K.js";import"./chunk-JXEPJ3YD.js";import"./chunk-DSCMCVTV.js";import"./chunk-J4WKMKTP.js";import"./chunk-LY7JGDV7.js";import"./chunk-N67JZAXU.js";import"./chunk-CDNWCYF7.js";import"./chunk-AFRHKA7B.js";import"./chunk-EW27SCCC.js";import"./chunk-BTAEYL3P.js";import"./chunk-34NG7UDX.js";import"./chunk-5F3J55LG.js";import"./chunk-WJXU5F4Z.js";import"./chunk-LEIYC3BF.js";import"./chunk-WZEYII2S.js";import"./chunk-64P4QHSZ.js";import"./chunk-BFZUG66X.js";import"./chunk-SR3X25TN.js";import"./chunk-53NRX2NA.js";import"./chunk-SFQUP3BP.js";import"./chunk-JGXE2YNV.js";import"./chunk-VJUTQAPJ.js";import"./chunk-DJEZ2HVU.js";import"./chunk-DSYKYZMT.js";import"./chunk-TRQSP67X.js";import"./chunk-QOYAX2FT.js";import"./chunk-HPDHNIA3.js";export{a as buildProgram};
|
|
2
|
-
//# sourceMappingURL=program-2MDELQLX.js.map
|
package/dist/query-FZAL7AQD.js
DELETED
package/dist/recipes-LZ56TA3X.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{f as a,g as b,h as c}from"./chunk-BTAEYL3P.js";import"./chunk-34NG7UDX.js";import"./chunk-5F3J55LG.js";import"./chunk-WJXU5F4Z.js";import"./chunk-LEIYC3BF.js";import"./chunk-WZEYII2S.js";import"./chunk-64P4QHSZ.js";import"./chunk-BFZUG66X.js";import"./chunk-SR3X25TN.js";import"./chunk-53NRX2NA.js";import"./chunk-VJUTQAPJ.js";import"./chunk-TRQSP67X.js";import"./chunk-QOYAX2FT.js";import"./chunk-HPDHNIA3.js";export{c as AgentRegistry,b as getGlobalRegistry,a as setGlobalRegistry};
|
|
2
|
-
//# sourceMappingURL=registry-ONJSILH4.js.map
|
package/dist/store-4UCO56K2.js
DELETED
package/dist/timers-QB4KYNAE.js
DELETED
package/dist/types-ECMEC5BZ.js
DELETED
package/dist/types-TTB6DJUE.js
DELETED