agentix-cli 0.16.0 → 0.18.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/agent-RDVGL2TW.js +2 -0
- package/dist/audit-Z47AQFFD.js +3 -0
- package/dist/audit-Z47AQFFD.js.map +1 -0
- package/dist/board-dashboard-KX2CNOSY.js +7282 -0
- package/dist/board-dashboard-KX2CNOSY.js.map +1 -0
- package/dist/chunk-34NG7UDX.js +11 -0
- package/dist/chunk-34NG7UDX.js.map +1 -0
- package/dist/{chunk-6MKAXEE5.js → chunk-4ITQP3XL.js} +2 -2
- package/dist/{chunk-6MKAXEE5.js.map → chunk-4ITQP3XL.js.map} +1 -1
- package/dist/chunk-53NRX2NA.js +2 -0
- package/dist/chunk-53NRX2NA.js.map +1 -0
- package/dist/chunk-6PPIFDOQ.js +1742 -0
- package/dist/chunk-6PPIFDOQ.js.map +1 -0
- package/dist/chunk-AFRHKA7B.js +2 -0
- package/dist/chunk-AFRHKA7B.js.map +1 -0
- package/dist/chunk-ASSJ4Z6Z.js +164 -0
- package/dist/chunk-ASSJ4Z6Z.js.map +1 -0
- package/dist/chunk-BLUXL6HR.js +854 -0
- package/dist/chunk-BLUXL6HR.js.map +1 -0
- package/dist/chunk-BTAEYL3P.js +72 -0
- package/dist/chunk-BTAEYL3P.js.map +1 -0
- package/dist/chunk-CDNWCYF7.js +2 -0
- package/dist/chunk-CDNWCYF7.js.map +1 -0
- package/dist/chunk-DJEZ2HVU.js +5 -0
- package/dist/chunk-DJEZ2HVU.js.map +1 -0
- package/dist/{chunk-FLOGK2U7.js → chunk-DSCMCVTV.js} +3 -3
- package/dist/chunk-DSCMCVTV.js.map +1 -0
- package/dist/chunk-EW27SCCC.js +2 -0
- package/dist/chunk-EW27SCCC.js.map +1 -0
- package/dist/chunk-HPDHNIA3.js +2 -0
- package/dist/chunk-IM376SD6.js +13 -0
- package/dist/chunk-IM376SD6.js.map +1 -0
- package/dist/chunk-J4WKMKTP.js +5 -0
- package/dist/{chunk-YI2T5QYB.js.map → chunk-J4WKMKTP.js.map} +1 -1
- package/dist/chunk-JGXE2YNV.js +46 -0
- package/dist/chunk-JGXE2YNV.js.map +1 -0
- package/dist/chunk-JXEPJ3YD.js +118 -0
- package/dist/chunk-JXEPJ3YD.js.map +1 -0
- package/dist/chunk-LY7JGDV7.js +6 -0
- package/dist/chunk-LY7JGDV7.js.map +1 -0
- package/dist/chunk-N67JZAXU.js +2 -0
- package/dist/chunk-N67JZAXU.js.map +1 -0
- package/dist/chunk-SR3X25TN.js +3 -0
- package/dist/chunk-SR3X25TN.js.map +1 -0
- package/dist/{chunk-7MBTMCKY.js → chunk-TRQSP67X.js} +2 -2
- package/dist/chunk-U364GNQX.js +719 -0
- package/dist/chunk-U364GNQX.js.map +1 -0
- package/dist/chunk-VJUTQAPJ.js +2 -0
- package/dist/chunk-VJUTQAPJ.js.map +1 -0
- package/dist/{chunk-HI24KAEY.js → chunk-WJXU5F4Z.js} +3 -4
- package/dist/{chunk-HI24KAEY.js.map → chunk-WJXU5F4Z.js.map} +1 -1
- package/dist/{chunk-QZP65I2H.js → chunk-ZE2YFX4K.js} +2 -2
- package/dist/cli.js +1 -320
- package/dist/cli.js.map +1 -1
- package/dist/{compaction-JBZZ7I7Y.js → compaction-S5H6DTOR.js} +6 -6
- package/dist/compaction-S5H6DTOR.js.map +1 -0
- package/dist/config-UOHEG4BW.js +2 -0
- package/dist/config-mutator-KSEBWKJH.js +2 -0
- package/dist/context-planner-2RGHJHSO.js +17 -0
- package/dist/context-planner-2RGHJHSO.js.map +1 -0
- package/dist/debug-TGAP7E7W.js +2 -0
- package/dist/discover-OATU7SHI.js +4 -0
- package/dist/discover-OATU7SHI.js.map +1 -0
- package/dist/editor-chat-FUBQLMAQ.js +137 -0
- package/dist/editor-chat-FUBQLMAQ.js.map +1 -0
- package/dist/heal-YP7QI7D3.js +2 -0
- package/dist/inbox-O5UB3YQN.js +315 -0
- package/dist/inbox-O5UB3YQN.js.map +1 -0
- package/dist/index.d.ts +3390 -1814
- package/dist/index.js +23 -53
- package/dist/index.js.map +1 -1
- package/dist/ingest-whatsapp-C3O5UPBX.js +6 -0
- package/dist/ingest-whatsapp-C3O5UPBX.js.map +1 -0
- package/dist/loader-BGWA7IRY.js +2 -0
- package/dist/loader-LBHKSXBK.js +2 -0
- package/dist/memory-extract-KKPKAOPH.js +2 -0
- package/dist/processes-7W27G3J3.js +256 -0
- package/dist/processes-7W27G3J3.js.map +1 -0
- package/dist/program-2MDELQLX.js +2 -0
- package/dist/providers-WCQSKTNP.js +2 -0
- package/dist/query-FZAL7AQD.js +2 -0
- package/dist/recipes-LZ56TA3X.js +2 -0
- package/dist/registry-ONJSILH4.js +2 -0
- package/dist/scheduler-XRM2RJIV.js +2 -0
- package/dist/slack-D27BE37Q.js +3 -0
- package/dist/slack-D27BE37Q.js.map +1 -0
- package/dist/store-4UCO56K2.js +2 -0
- package/dist/store-4UCO56K2.js.map +1 -0
- package/dist/{subagent-2SJHSSJS.js → subagent-EPOT3QDM.js} +2 -2
- package/dist/task-store-3NUWLYRY.js +2 -0
- package/dist/task-store-3NUWLYRY.js.map +1 -0
- package/dist/telegram-UWV67KWG.js +3 -0
- package/dist/telegram-UWV67KWG.js.map +1 -0
- package/dist/timers-QB4KYNAE.js +2 -0
- package/dist/timers-QB4KYNAE.js.map +1 -0
- package/dist/types-ECMEC5BZ.js +2 -0
- package/dist/types-ECMEC5BZ.js.map +1 -0
- package/dist/types-TTB6DJUE.js +2 -0
- package/dist/types-TTB6DJUE.js.map +1 -0
- package/dist/usage-dashboard-FQGPPABD.js +803 -0
- package/dist/usage-dashboard-FQGPPABD.js.map +1 -0
- package/dist/web/workflow-editor.global.js +1450 -0
- package/dist/web/workflow-editor.global.js.map +1 -0
- package/dist/whatsapp-KJ2NRKKQ.js +3 -0
- package/dist/whatsapp-KJ2NRKKQ.js.map +1 -0
- package/dist/{whatsapp-state-6D6FHHD2.js → whatsapp-state-3ZVQ6MQD.js} +2 -2
- package/dist/{wiki-UDPYFJBL.js → wiki-OUYKM7MB.js} +2 -2
- package/dist/wiki-OUYKM7MB.js.map +1 -0
- package/dist/workspace-setup-6GDBEORT.js +2 -0
- package/dist/workspace-setup-6GDBEORT.js.map +1 -0
- package/package.json +19 -2
- package/dist/agent-BCDPSPKU.js +0 -2
- package/dist/board-dashboard-LA7WW56M.js +0 -5668
- package/dist/board-dashboard-LA7WW56M.js.map +0 -1
- package/dist/chunk-6U2XOQHU.js +0 -7
- package/dist/chunk-6U2XOQHU.js.map +0 -1
- package/dist/chunk-7FJQGD6U.js +0 -6
- package/dist/chunk-7FJQGD6U.js.map +0 -1
- package/dist/chunk-ABDP7TDL.js +0 -71
- package/dist/chunk-ABDP7TDL.js.map +0 -1
- package/dist/chunk-CXY2RYNI.js +0 -11
- package/dist/chunk-CXY2RYNI.js.map +0 -1
- package/dist/chunk-FIOB3MEX.js +0 -2
- package/dist/chunk-FLOGK2U7.js.map +0 -1
- package/dist/chunk-HVC6FJNP.js +0 -108
- package/dist/chunk-HVC6FJNP.js.map +0 -1
- package/dist/chunk-K7BE4E35.js +0 -138
- package/dist/chunk-K7BE4E35.js.map +0 -1
- package/dist/chunk-KXJSLLSQ.js +0 -199
- package/dist/chunk-KXJSLLSQ.js.map +0 -1
- package/dist/chunk-KXZMYLHQ.js +0 -46
- package/dist/chunk-KXZMYLHQ.js.map +0 -1
- package/dist/chunk-M7HKBG3V.js +0 -2
- package/dist/chunk-M7HKBG3V.js.map +0 -1
- package/dist/chunk-MCJUAE6V.js +0 -6
- package/dist/chunk-MCJUAE6V.js.map +0 -1
- package/dist/chunk-RB7265LO.js +0 -12
- package/dist/chunk-RB7265LO.js.map +0 -1
- package/dist/chunk-YI2T5QYB.js +0 -5
- package/dist/compaction-JBZZ7I7Y.js.map +0 -1
- package/dist/config-LN6JEHMF.js +0 -2
- package/dist/config-mutator-24TH7PHM.js +0 -2
- package/dist/debug-3MEUO2P2.js +0 -2
- package/dist/heal-WKY5I4KB.js +0 -2
- package/dist/loader-PTCXLJEX.js +0 -2
- package/dist/memory-extract-AD7KMRPC.js +0 -2
- package/dist/providers-VUIWO3ZK.js +0 -2
- package/dist/query-PKGYE5HR.js +0 -2
- package/dist/registry-ASC2QS5K.js +0 -2
- package/dist/scheduler-A3TQOCKT.js +0 -2
- package/dist/types-OIFU3KSQ.js +0 -2
- package/dist/usage-dashboard-QX3CF6KK.js +0 -404
- package/dist/usage-dashboard-QX3CF6KK.js.map +0 -1
- package/dist/workspace-setup-ZWTT42XT.js +0 -2
- /package/dist/{agent-BCDPSPKU.js.map → agent-RDVGL2TW.js.map} +0 -0
- /package/dist/{chunk-FIOB3MEX.js.map → chunk-HPDHNIA3.js.map} +0 -0
- /package/dist/{chunk-7MBTMCKY.js.map → chunk-TRQSP67X.js.map} +0 -0
- /package/dist/{chunk-QZP65I2H.js.map → chunk-ZE2YFX4K.js.map} +0 -0
- /package/dist/{config-LN6JEHMF.js.map → config-UOHEG4BW.js.map} +0 -0
- /package/dist/{config-mutator-24TH7PHM.js.map → config-mutator-KSEBWKJH.js.map} +0 -0
- /package/dist/{debug-3MEUO2P2.js.map → debug-TGAP7E7W.js.map} +0 -0
- /package/dist/{heal-WKY5I4KB.js.map → heal-YP7QI7D3.js.map} +0 -0
- /package/dist/{loader-PTCXLJEX.js.map → loader-BGWA7IRY.js.map} +0 -0
- /package/dist/{memory-extract-AD7KMRPC.js.map → loader-LBHKSXBK.js.map} +0 -0
- /package/dist/{providers-VUIWO3ZK.js.map → memory-extract-KKPKAOPH.js.map} +0 -0
- /package/dist/{query-PKGYE5HR.js.map → program-2MDELQLX.js.map} +0 -0
- /package/dist/{registry-ASC2QS5K.js.map → providers-WCQSKTNP.js.map} +0 -0
- /package/dist/{scheduler-A3TQOCKT.js.map → query-FZAL7AQD.js.map} +0 -0
- /package/dist/{types-OIFU3KSQ.js.map → recipes-LZ56TA3X.js.map} +0 -0
- /package/dist/{wiki-UDPYFJBL.js.map → registry-ONJSILH4.js.map} +0 -0
- /package/dist/{workspace-setup-ZWTT42XT.js.map → scheduler-XRM2RJIV.js.map} +0 -0
- /package/dist/{subagent-2SJHSSJS.js.map → subagent-EPOT3QDM.js.map} +0 -0
- /package/dist/{whatsapp-state-6D6FHHD2.js.map → whatsapp-state-3ZVQ6MQD.js.map} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as h}from"./chunk-QOYAX2FT.js";import"./chunk-
|
|
1
|
+
import{a as h}from"./chunk-QOYAX2FT.js";import"./chunk-HPDHNIA3.js";import{readFileSync as O,writeFileSync as k,existsSync as S,mkdirSync as E}from"fs";import{resolve as g}from"path";function f(t,o){let n=new Map;for(let r of t){if(r.role!=="agent")continue;let s=h(r.content);for(let a of s)n.set(a,(n.get(a)||0)+1)}let e=[...n.entries()].sort((r,s)=>s[1]-r[1]).slice(0,50).map(([r])=>r);return{lexicon:n,toolCalls:o||[],topKeywords:e,capturedAt:new Date().toISOString(),messageCount:t.length}}function y(t,o){let n=D(t,o),{distance:e,newTools:r,missingTools:s}=T(t,o),a=b(t,o),c=Math.min(1,n*.4+e*.3+a*.3),m=t.topKeywords.filter(l=>!o.lexicon.has(l)||(o.lexicon.get(l)||0)===0).slice(0,10);return{overallScore:Math.round(c*100)/100,lexiconDecay:Math.round(n*100)/100,toolShift:Math.round(e*100)/100,semanticDrift:Math.round(a*100)/100,lostWords:m,newTools:r,missingTools:s}}function D(t,o){if(t.topKeywords.length===0)return 0;let n=0;for(let e of t.topKeywords)o.lexicon.has(e)||n++;return n/t.topKeywords.length}function T(t,o){let n=new Set(t.toolCalls),e=new Set(o.toolCalls);if(n.size===0&&e.size===0)return{distance:0,newTools:[],missingTools:[]};let r=[...n].filter(u=>e.has(u)),s=new Set([...n,...e]),c=1-r.length/s.size,m=[...e].filter(u=>!n.has(u)),l=[...n].filter(u=>!e.has(u));return{distance:c,newTools:m,missingTools:l}}function b(t,o){if(t.topKeywords.length===0)return 0;let n=new Set(o.topKeywords),e=0;for(let r of t.topKeywords)n.has(r)&&e++;return 1-e/t.topKeywords.length}var w=".agentx/drift";function C(t,o,n=process.cwd()){let e=g(n,w);S(e)||E(e,{recursive:!0});let r={...o,lexicon:Object.fromEntries(o.lexicon)};k(g(e,`${t}.json`),JSON.stringify(r,null,2))}function M(t,o=process.cwd()){let n=g(o,w,`${t}.json`);if(!S(n))return null;try{let e=JSON.parse(O(n,"utf-8"));return{...e,lexicon:new Map(Object.entries(e.lexicon))}}catch{return null}}var I="claude-haiku-4-20250514";var R=5e4,A=6,v=8,K=`You are a conversation summarizer. Given a conversation history between a user and an AI agent, produce a concise summary that preserves:
|
|
2
2
|
|
|
3
3
|
1. Key decisions made and their reasoning
|
|
4
4
|
2. Important facts learned (names, IDs, paths, URLs, credentials shared)
|
|
@@ -15,14 +15,14 @@ Format as a structured summary:
|
|
|
15
15
|
- Preferences: ...
|
|
16
16
|
|
|
17
17
|
Keep it under 500 words. Prioritize actionable information over conversational flow.
|
|
18
|
-
Do NOT include greetings, acknowledgments, or meta-commentary about the conversation.`;function U(t){return t.length<v?!1:t.reduce((n,e)=>n+e.content.length,0)>R}async function G(t,o,n){if(t.length<v)return{summary:"",compactedCount:0,keptCount:t.length,memoryFlushed:!1};let e=Math.max(0,t.length-A),r=t.slice(0,e),s=t.slice(e);if(r.length===0)return{summary:"",compactedCount:0,keptCount:s.length,memoryFlushed:!1};let a=f(r);C(o,a);let c=!1;if(n)try{await
|
|
18
|
+
Do NOT include greetings, acknowledgments, or meta-commentary about the conversation.`;function U(t){return t.length<v?!1:t.reduce((n,e)=>n+e.content.length,0)>R}async function G(t,o,n){if(t.length<v)return{summary:"",compactedCount:0,keptCount:t.length,memoryFlushed:!1};let e=Math.max(0,t.length-A),r=t.slice(0,e),s=t.slice(e);if(r.length===0)return{summary:"",compactedCount:0,keptCount:s.length,memoryFlushed:!1};let a=f(r);C(o,a);let c=!1;if(n)try{await _(o,r,n),c=!0}catch{}let m=$(r),l=await j(m),{qualityScore:u,lostEntities:i}=P(m,l),p,F=[{role:"agent",content:l,timestamp:new Date().toISOString()}],x=f(F),d=M(o);return d&&(p=y(d,x)),{summary:l,compactedCount:r.length,keptCount:s.length,memoryFlushed:c,qualityScore:u,lostEntities:i.length>0?i:void 0,drift:p&&p.overallScore>.1?p:void 0}}function P(t,o){let n=B(t);if(n.length===0)return{qualityScore:100,lostEntities:[]};let e=o.toLowerCase(),r=[],s=[];for(let c of n)e.includes(c.toLowerCase())?r.push(c):s.push(c);return{qualityScore:Math.round(r.length/n.length*100),lostEntities:s}}function B(t){let o=[],n=new Set,e=i=>{let p=i.toLowerCase().trim();p.length<3||n.has(p)||(n.add(p),o.push(i.trim()))},r=t.match(/https?:\/\/[^\s<>)"]+/g)||[];for(let i of r)e(i.slice(0,80));let s=t.match(/(?:\/[\w.-]+){2,}/g)||[];for(let i of s)e(i);let a=t.match(/[#!]\d{2,}/g)||[];for(let i of a)e(i);let c=t.match(/[\w.-]+@[\w.-]+\.\w+/g)||[];for(let i of c)e(i);let m=t.match(/"([^"]{3,40})"/g)||[];for(let i of m)e(i.slice(1,-1));let l=t.match(/[A-Z][a-z]+(?:\s+[A-Z][a-z]+)+/g)||[];for(let i of l)e(i);let u=t.match(/[a-f0-9]{8,}/gi)||[];for(let i of u.slice(0,5))e(i);return o.slice(0,30)}function J(t,o){if(o.compactedCount===0)return t;let n=t.slice(-o.keptCount);return[{role:"agent",name:"system",content:`[Compacted conversation summary \u2014 ${o.compactedCount} earlier messages]
|
|
19
19
|
|
|
20
|
-
${o.summary}`,timestamp:new Date().toISOString()},...n]}async function
|
|
20
|
+
${o.summary}`,timestamp:new Date().toISOString()},...n]}async function _(t,o,n){let e=[],r=[];for(let a of o)a.role==="user"?e.push(`${a.name||"User"}: ${a.content}`):r.push(a.content);if(e.length===0||r.length===0)return;let{extractMemories:s}=await import("./memory-extract-KKPKAOPH.js");await s(t,e.join(`
|
|
21
21
|
`),r.join(`
|
|
22
|
-
`),{channel:"compaction",chatId:"flush",sender:"compaction"},n)}function
|
|
22
|
+
`),{channel:"compaction",chatId:"flush",sender:"compaction"},n)}function $(t){return t.map(o=>{let n=o.timestamp.slice(11,16),e=o.role==="user"?o.name||"User":o.name||"Agent";return`[${n}] ${e}: ${o.content}`}).join(`
|
|
23
23
|
|
|
24
|
-
`)}async function
|
|
24
|
+
`)}async function j(t){let n=t.length>2e4?t.slice(-2e4):t;try{let{createProvider:e}=await import("./providers-WCQSKTNP.js");return(await e("claude").generate([{role:"system",content:K},{role:"user",content:n}],{model:I,maxTokens:1024})).content.trim()}catch{let s=t.split(`
|
|
25
25
|
`).slice(-20);return`[Summary unavailable \u2014 keeping last ${s.length} lines]
|
|
26
26
|
${s.join(`
|
|
27
27
|
`)}`}}export{J as applyCompaction,G as compactSession,U as needsCompaction};
|
|
28
|
-
//# sourceMappingURL=compaction-
|
|
28
|
+
//# sourceMappingURL=compaction-S5H6DTOR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agents/drift-detection.ts","../src/agents/compaction.ts"],"sourcesContent":["import { tokenize } from \"../memory/bm25\"\n\n// --- Behavioral Drift Detection ---\n//\n// Detects silent behavioral shifts after compaction or session boundaries.\n// Three signals, all zero-AI (pure signal processing):\n//\n// 1. Ghost lexicon decay — domain vocabulary disappearing after compaction\n// 2. Tool-call sequence shift — Jaccard distance on tool usage patterns\n// 3. Semantic drift — keyword overlap decline across session boundaries\n//\n// Inspired by CrewAI #5155 and agent-morrow/compression-monitor.\n\nexport interface BehavioralFingerprint {\n /** Domain vocabulary: word → frequency */\n lexicon: Map<string, number>\n /** Tool call sequence: tool names in order */\n toolCalls: string[]\n /** Top keywords by frequency (for quick comparison) */\n topKeywords: string[]\n /** Timestamp of fingerprint capture */\n capturedAt: string\n /** Number of messages in the sample */\n messageCount: number\n}\n\nexport interface DriftReport {\n /** 0-1 score: 0 = no drift, 1 = complete drift */\n overallScore: number\n /** Ghost lexicon decay: percentage of domain words that disappeared */\n lexiconDecay: number\n /** Tool-call Jaccard distance: 0 = identical patterns, 1 = completely different */\n toolShift: number\n /** Keyword overlap decline: percentage of shared keywords lost */\n semanticDrift: number\n /** Domain words that disappeared */\n lostWords: string[]\n /** New tool calls that weren't in the baseline */\n newTools: string[]\n /** Missing tool calls that were in the baseline */\n missingTools: string[]\n}\n\n/**\n * Build a behavioral fingerprint from agent messages.\n * Call this BEFORE compaction to establish a baseline.\n */\nexport function buildFingerprint(\n messages: Array<{ role: string; content: string; name?: string }>,\n toolCalls?: string[],\n): BehavioralFingerprint {\n const lexicon = new Map<string, number>()\n\n // Build vocabulary from agent responses\n for (const msg of messages) {\n if (msg.role !== \"agent\") continue\n const words = tokenize(msg.content)\n for (const word of words) {\n lexicon.set(word, (lexicon.get(word) || 0) + 1)\n }\n }\n\n // Top keywords by frequency (domain vocabulary)\n const topKeywords = [...lexicon.entries()]\n .sort((a, b) => b[1] - a[1])\n .slice(0, 50)\n .map(([word]) => word)\n\n return {\n lexicon,\n toolCalls: toolCalls || [],\n topKeywords,\n capturedAt: new Date().toISOString(),\n messageCount: messages.length,\n }\n}\n\n/**\n * Compare two fingerprints and produce a drift report.\n * Call this AFTER compaction with the new session's fingerprint.\n */\nexport function detectDrift(\n baseline: BehavioralFingerprint,\n current: BehavioralFingerprint,\n): DriftReport {\n // 1. Ghost lexicon decay: how many baseline domain words are missing?\n const lexiconDecay = computeLexiconDecay(baseline, current)\n\n // 2. Tool-call sequence shift: Jaccard distance on tool usage\n const { distance: toolShift, newTools, missingTools } = computeToolShift(baseline, current)\n\n // 3. Semantic drift: keyword overlap decline\n const semanticDrift = computeSemanticDrift(baseline, current)\n\n // Overall score: weighted average (lexicon decay matters most)\n const overallScore = Math.min(1, (lexiconDecay * 0.4) + (toolShift * 0.3) + (semanticDrift * 0.3))\n\n // Lost domain words\n const lostWords = baseline.topKeywords.filter(\n word => !current.lexicon.has(word) || (current.lexicon.get(word) || 0) === 0,\n ).slice(0, 10)\n\n return {\n overallScore: Math.round(overallScore * 100) / 100,\n lexiconDecay: Math.round(lexiconDecay * 100) / 100,\n toolShift: Math.round(toolShift * 100) / 100,\n semanticDrift: Math.round(semanticDrift * 100) / 100,\n lostWords,\n newTools,\n missingTools,\n }\n}\n\n/**\n * Ghost lexicon decay: percentage of baseline's top domain words\n * that disappeared from the current fingerprint.\n */\nfunction computeLexiconDecay(\n baseline: BehavioralFingerprint,\n current: BehavioralFingerprint,\n): number {\n if (baseline.topKeywords.length === 0) return 0\n\n let missing = 0\n for (const word of baseline.topKeywords) {\n if (!current.lexicon.has(word)) missing++\n }\n\n return missing / baseline.topKeywords.length\n}\n\n/**\n * Tool-call sequence shift: Jaccard distance between tool usage sets.\n * J(A,B) = |A ∩ B| / |A ∪ B|, distance = 1 - J\n */\nfunction computeToolShift(\n baseline: BehavioralFingerprint,\n current: BehavioralFingerprint,\n): { distance: number; newTools: string[]; missingTools: string[] } {\n const baseSet = new Set(baseline.toolCalls)\n const currSet = new Set(current.toolCalls)\n\n if (baseSet.size === 0 && currSet.size === 0) {\n return { distance: 0, newTools: [], missingTools: [] }\n }\n\n const intersection = [...baseSet].filter(t => currSet.has(t))\n const union = new Set([...baseSet, ...currSet])\n const jaccard = intersection.length / union.size\n const distance = 1 - jaccard\n\n const newTools = [...currSet].filter(t => !baseSet.has(t))\n const missingTools = [...baseSet].filter(t => !currSet.has(t))\n\n return { distance, newTools, missingTools }\n}\n\n/**\n * Semantic drift: keyword overlap decline.\n * Compares top keywords between baseline and current.\n */\nfunction computeSemanticDrift(\n baseline: BehavioralFingerprint,\n current: BehavioralFingerprint,\n): number {\n if (baseline.topKeywords.length === 0) return 0\n\n const currentSet = new Set(current.topKeywords)\n let overlap = 0\n\n for (const word of baseline.topKeywords) {\n if (currentSet.has(word)) overlap++\n }\n\n // Drift = 1 - overlap ratio\n return 1 - (overlap / baseline.topKeywords.length)\n}\n\n// --- Per-agent fingerprint persistence ---\n\nimport { readFileSync, writeFileSync, existsSync, mkdirSync } from \"fs\"\nimport { resolve } from \"path\"\n\nconst DRIFT_DIR = \".agentx/drift\"\n\n/**\n * Save a behavioral fingerprint for an agent (pre-compaction baseline).\n */\nexport function saveFingerprint(\n agentId: string,\n fingerprint: BehavioralFingerprint,\n baseDir: string = process.cwd(),\n): void {\n const dir = resolve(baseDir, DRIFT_DIR)\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true })\n\n const serialized = {\n ...fingerprint,\n lexicon: Object.fromEntries(fingerprint.lexicon),\n }\n\n writeFileSync(\n resolve(dir, `${agentId}.json`),\n JSON.stringify(serialized, null, 2),\n )\n}\n\n/**\n * Load the saved fingerprint for an agent.\n */\nexport function loadFingerprint(\n agentId: string,\n baseDir: string = process.cwd(),\n): BehavioralFingerprint | null {\n const file = resolve(baseDir, DRIFT_DIR, `${agentId}.json`)\n if (!existsSync(file)) return null\n\n try {\n const data = JSON.parse(readFileSync(file, \"utf-8\"))\n return {\n ...data,\n lexicon: new Map(Object.entries(data.lexicon)),\n }\n } catch {\n return null\n }\n}\n","import type { SessionMessage } from \"./sessions\"\nimport type { MemoryStore } from \"./memory-store\"\nimport { buildFingerprint, detectDrift, saveFingerprint, loadFingerprint, type DriftReport } from \"./drift-detection\"\n\n// --- Context Compaction ---\n//\n// Summarizes conversation history when it grows too large,\n// preventing context loss in long-running sessions.\n//\n// Strategy:\n// 1. Memory flush: extract memorable facts BEFORE compacting\n// 2. Split-aware: keep tool_use/tool_result pairs intact\n// 3. Uses cheaper model (Haiku) for summarization\n// 4. Preserves recent messages verbatim, compacts older ones\n\nconst COMPACTION_MODEL = \"claude-haiku-4-20250514\"\nconst MAX_HISTORY_CHARS = 60_000\n// Start compacting when stored history exceeds this. Bumped 10K → 50K\n// because the original threshold tripped on trivial multi-message bursts\n// (project-bot queues, daily-tick storms), spending Haiku tokens to reduce\n// stored history that Claude itself never reads — Claude --resume replays\n// from its own copy. The cost-relevant thresholds are tier-2 token rotation\n// (~200K tokens / ~800K chars of input) and max-turns; compaction is just\n// for our local storage hygiene + cross-session summary seeding, so a\n// generous threshold is fine.\nconst COMPACT_THRESHOLD = 50_000\nconst KEEP_RECENT = 6 // Always keep last N messages verbatim\nconst MIN_MESSAGES_TO_COMPACT = 8 // Don't compact if fewer than this\n\nconst COMPACTION_PROMPT = `You are a conversation summarizer. Given a conversation history between a user and an AI agent, produce a concise summary that preserves:\n\n1. Key decisions made and their reasoning\n2. Important facts learned (names, IDs, paths, URLs, credentials shared)\n3. Commitments and action items (what was promised, deadlines)\n4. Current task state (what's in progress, what's done, what's blocked)\n5. User preferences expressed during the conversation\n\nFormat as a structured summary:\n[Session Summary]\n- Decisions: ...\n- Facts: ...\n- In progress: ...\n- Done: ...\n- Preferences: ...\n\nKeep it under 500 words. Prioritize actionable information over conversational flow.\nDo NOT include greetings, acknowledgments, or meta-commentary about the conversation.`\n\nexport interface CompactionResult {\n /** Summarized text replacing older messages */\n summary: string\n /** Messages that were compacted (removed from history) */\n compactedCount: number\n /** Messages kept verbatim (recent) */\n keptCount: number\n /** Whether memory flush was performed before compaction */\n memoryFlushed: boolean\n /** Quality score: percentage of key entities preserved in summary (0-100) */\n qualityScore?: number\n /** Entities that were lost in compaction */\n lostEntities?: string[]\n /** Behavioral drift report (compared to pre-compaction baseline) */\n drift?: DriftReport\n}\n\n/**\n * Check whether a session's history needs compaction.\n */\nexport function needsCompaction(messages: SessionMessage[]): boolean {\n if (messages.length < MIN_MESSAGES_TO_COMPACT) return false\n const totalChars = messages.reduce((sum, m) => sum + m.content.length, 0)\n return totalChars > COMPACT_THRESHOLD\n}\n\n/**\n * Compact a session's conversation history.\n *\n * Flow:\n * 1. Flush memories from the about-to-be-compacted messages\n * 2. Summarize older messages using Haiku\n * 3. Return summary + preserved recent messages\n */\nexport async function compactSession(\n messages: SessionMessage[],\n agentId: string,\n memoryStore?: MemoryStore,\n): Promise<CompactionResult> {\n if (messages.length < MIN_MESSAGES_TO_COMPACT) {\n return {\n summary: \"\",\n compactedCount: 0,\n keptCount: messages.length,\n memoryFlushed: false,\n }\n }\n\n // Split: older messages to compact, recent to keep\n const splitIdx = Math.max(0, messages.length - KEEP_RECENT)\n const toCompact = messages.slice(0, splitIdx)\n const toKeep = messages.slice(splitIdx)\n\n if (toCompact.length === 0) {\n return {\n summary: \"\",\n compactedCount: 0,\n keptCount: toKeep.length,\n memoryFlushed: false,\n }\n }\n\n // Step 0: Capture behavioral fingerprint BEFORE compaction (drift baseline)\n const preFingerprint = buildFingerprint(toCompact)\n saveFingerprint(agentId, preFingerprint)\n\n // Step 1: Memory flush — extract facts from messages about to be compacted\n let memoryFlushed = false\n if (memoryStore) {\n try {\n await flushMemories(agentId, toCompact, memoryStore)\n memoryFlushed = true\n } catch {\n // Best-effort memory flush\n }\n }\n\n // Step 2: Summarize older messages\n const historyText = formatMessagesForSummary(toCompact)\n const summary = await summarize(historyText)\n\n // Step 3: Verify compaction quality — check entity preservation\n const { qualityScore, lostEntities } = verifyCompactionQuality(historyText, summary)\n\n // Step 4: Check for behavioral drift — compare summary against pre-compaction baseline\n let drift: DriftReport | undefined\n const summaryMessages: SessionMessage[] = [{\n role: \"agent\" as const,\n content: summary,\n timestamp: new Date().toISOString(),\n }]\n const postFingerprint = buildFingerprint(summaryMessages)\n const baseline = loadFingerprint(agentId)\n if (baseline) {\n drift = detectDrift(baseline, postFingerprint)\n }\n\n return {\n summary,\n compactedCount: toCompact.length,\n keptCount: toKeep.length,\n memoryFlushed,\n qualityScore,\n lostEntities: lostEntities.length > 0 ? lostEntities : undefined,\n drift: drift && drift.overallScore > 0.1 ? drift : undefined,\n }\n}\n\n/**\n * Verify compaction quality by checking entity preservation.\n * Extracts key entities (names, IDs, URLs, decisions) from the original\n * and checks how many survived in the summary.\n */\nfunction verifyCompactionQuality(\n original: string,\n summary: string,\n): { qualityScore: number; lostEntities: string[] } {\n const entities = extractEntities(original)\n if (entities.length === 0) return { qualityScore: 100, lostEntities: [] }\n\n const summaryLower = summary.toLowerCase()\n const preserved: string[] = []\n const lost: string[] = []\n\n for (const entity of entities) {\n if (summaryLower.includes(entity.toLowerCase())) {\n preserved.push(entity)\n } else {\n lost.push(entity)\n }\n }\n\n const score = Math.round((preserved.length / entities.length) * 100)\n return { qualityScore: score, lostEntities: lost }\n}\n\n/**\n * Extract key entities from text: names, IDs, URLs, file paths, decisions.\n */\nfunction extractEntities(text: string): string[] {\n const entities: string[] = []\n const seen = new Set<string>()\n\n const add = (entity: string) => {\n const key = entity.toLowerCase().trim()\n if (key.length < 3 || seen.has(key)) return\n seen.add(key)\n entities.push(entity.trim())\n }\n\n // URLs\n const urls = text.match(/https?:\\/\\/[^\\s<>)\"]+/g) || []\n for (const url of urls) add(url.slice(0, 80))\n\n // File paths\n const paths = text.match(/(?:\\/[\\w.-]+){2,}/g) || []\n for (const p of paths) add(p)\n\n // Ticket/issue references (#123, !456)\n const refs = text.match(/[#!]\\d{2,}/g) || []\n for (const r of refs) add(r)\n\n // Email addresses\n const emails = text.match(/[\\w.-]+@[\\w.-]+\\.\\w+/g) || []\n for (const e of emails) add(e)\n\n // Quoted strings (potential names, IDs, keys)\n const quoted = text.match(/\"([^\"]{3,40})\"/g) || []\n for (const q of quoted) add(q.slice(1, -1))\n\n // Capitalized proper nouns (2+ words starting with uppercase)\n const properNouns = text.match(/[A-Z][a-z]+(?:\\s+[A-Z][a-z]+)+/g) || []\n for (const n of properNouns) add(n)\n\n // UUIDs and hashes\n const ids = text.match(/[a-f0-9]{8,}/gi) || []\n for (const id of ids.slice(0, 5)) add(id) // cap to avoid noise\n\n return entities.slice(0, 30) // max 30 entities to check\n}\n\n/**\n * Apply compaction result to a session's messages.\n * Replaces older messages with a single summary message and keeps recent ones.\n */\nexport function applyCompaction(\n messages: SessionMessage[],\n result: CompactionResult,\n): SessionMessage[] {\n if (result.compactedCount === 0) return messages\n\n const kept = messages.slice(-result.keptCount)\n\n // Prepend summary as a synthetic \"system\" message\n const summaryMessage: SessionMessage = {\n role: \"agent\",\n name: \"system\",\n content: `[Compacted conversation summary — ${result.compactedCount} earlier messages]\\n\\n${result.summary}`,\n timestamp: new Date().toISOString(),\n }\n\n return [summaryMessage, ...kept]\n}\n\n/**\n * Flush extractable memories from messages before they get compacted.\n * This ensures important facts survive compaction.\n */\nasync function flushMemories(\n agentId: string,\n messages: SessionMessage[],\n store: MemoryStore,\n): Promise<void> {\n // Build a conversation block from the messages\n const userMessages: string[] = []\n const agentMessages: string[] = []\n\n for (const msg of messages) {\n if (msg.role === \"user\") {\n userMessages.push(`${msg.name || \"User\"}: ${msg.content}`)\n } else {\n agentMessages.push(msg.content)\n }\n }\n\n if (userMessages.length === 0 || agentMessages.length === 0) return\n\n // Use the existing memory extraction on a combined block\n const { extractMemories } = await import(\"./memory-extract\")\n await extractMemories(\n agentId,\n userMessages.join(\"\\n\"),\n agentMessages.join(\"\\n\"),\n { channel: \"compaction\", chatId: \"flush\", sender: \"compaction\" },\n store,\n )\n}\n\n/**\n * Format messages into a readable transcript for the summarizer.\n */\nfunction formatMessagesForSummary(messages: SessionMessage[]): string {\n return messages\n .map((m) => {\n const time = m.timestamp.slice(11, 16)\n const speaker = m.role === \"user\" ? (m.name || \"User\") : (m.name || \"Agent\")\n return `[${time}] ${speaker}: ${m.content}`\n })\n .join(\"\\n\\n\")\n}\n\n/**\n * Summarize a conversation transcript using a cheap/fast model.\n */\nasync function summarize(transcript: string): Promise<string> {\n // Truncate extremely long transcripts to avoid exceeding Haiku's context\n const maxInputChars = 20000\n const truncated = transcript.length > maxInputChars\n ? transcript.slice(-maxInputChars)\n : transcript\n\n try {\n const { createProvider } = await import(\"@/agent/providers\")\n const provider = createProvider(\"claude\")\n\n const result = await provider.generate(\n [\n { role: \"system\", content: COMPACTION_PROMPT },\n { role: \"user\", content: truncated },\n ],\n { model: COMPACTION_MODEL, maxTokens: 1024 },\n )\n\n return result.content.trim()\n } catch (error: any) {\n // Fallback: simple truncation if summarization fails\n const lines = transcript.split(\"\\n\")\n const kept = lines.slice(-20)\n return `[Summary unavailable — keeping last ${kept.length} lines]\\n${kept.join(\"\\n\")}`\n }\n}\n"],"mappings":"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","names":["readFileSync","writeFileSync","existsSync","mkdirSync","resolve","buildFingerprint","messages","toolCalls","lexicon","msg","words","tokenize","word","topKeywords","a","b","detectDrift","baseline","current","lexiconDecay","computeLexiconDecay","toolShift","newTools","missingTools","computeToolShift","semanticDrift","computeSemanticDrift","overallScore","lostWords","missing","baseSet","currSet","intersection","t","union","distance","currentSet","overlap","DRIFT_DIR","saveFingerprint","agentId","fingerprint","baseDir","dir","serialized","loadFingerprint","file","data","COMPACTION_MODEL","COMPACT_THRESHOLD","KEEP_RECENT","MIN_MESSAGES_TO_COMPACT","COMPACTION_PROMPT","needsCompaction","messages","sum","m","compactSession","agentId","memoryStore","splitIdx","toCompact","toKeep","preFingerprint","buildFingerprint","saveFingerprint","memoryFlushed","flushMemories","historyText","formatMessagesForSummary","summary","summarize","qualityScore","lostEntities","verifyCompactionQuality","drift","summaryMessages","postFingerprint","baseline","loadFingerprint","detectDrift","original","entities","extractEntities","summaryLower","preserved","lost","entity","text","seen","add","key","urls","url","paths","p","refs","r","emails","e","quoted","q","properNouns","n","ids","id","applyCompaction","result","kept","store","userMessages","agentMessages","msg","extractMemories","time","speaker","transcript","truncated","createProvider","COMPACTION_MODEL"]}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import"./chunk-HPDHNIA3.js";var S="claude-haiku-4-5-20251001",x=`You are a context planner for an AI agent. Given the user's current message and recent conversation tail, decide what historical context the agent needs.
|
|
2
|
+
|
|
3
|
+
Output ONE JSON object on one line, no prose, no fences:
|
|
4
|
+
{ "recentTurns": number, "memory": { "include": boolean, "query": string }, "crossChat": boolean, "reasoning": string }
|
|
5
|
+
|
|
6
|
+
Rules:
|
|
7
|
+
- "recentTurns" in [0,6]. Short acknowledgments ("ok", "yes", "do it", "continue") need 2-3 turns to know what they refer to. New topics need 0. Technical follow-ups need 3-5.
|
|
8
|
+
- "memory.include" = true only when the message likely references a person, past event, decision, or project the agent should recall. "memory.query" = 3-8 words distilling what to search for (leave "" when include=false).
|
|
9
|
+
- "crossChat" = true only when the message references another chat ("DM", "the group", "earlier conversation", a peer agent's name). Most messages = false.
|
|
10
|
+
- "reasoning" = one short phrase explaining the plan.
|
|
11
|
+
|
|
12
|
+
Be conservative \u2014 unneeded context costs tokens. When in doubt, exclude.`;async function N(e){let s=Date.now(),o=e.timeoutMs??8e3,r=I(e.sessions,e.agentId,e.channel,e.chatId,4),t=[`Agent: ${e.agentId}`,`Channel: ${e.channel}`,r?`Recent conversation:
|
|
13
|
+
${r}`:"Recent conversation: (none \u2014 first message)","",`Current message: ${e.message.slice(0,1500)}`].join(`
|
|
14
|
+
`),n;try{let{createProvider:l}=await import("./providers-WCQSKTNP.js"),y=l("claude"),f=new AbortController,p=setTimeout(()=>f.abort(),o);try{let b=await y.generate([{role:"system",content:x},{role:"user",content:t}],{model:S,maxTokens:256});n=w(b.content)}finally{clearTimeout(p)}}catch(l){return process.env.AGENTX_PLANNER_DEBUG&&console.error(`[planner] error: ${l?.message??l}`),null}if(!n||typeof n!="object")return null;let m=M(n.recentTurns,0,6,3),a=!!n.memory?.include,c=typeof n.memory?.query=="string"?n.memory.query:"",i=!!n.crossChat,u=typeof n.reasoning=="string"?n.reasoning:void 0,d=C(e.sessions,e.agentId,e.channel,e.chatId,m),g="";if(a&&c)try{let l=e.memoryStore.findRelevant(c,e.agentId,5);g=e.memoryStore.buildContext(l)}catch{}let h="";return i&&(h=e.sessions.getCrossSessionSummary(e.agentId,e.channel,e.chatId,"")),{sessionHistory:d,memoryContext:g,crossChatContext:h,debug:{recentTurns:m,memoryIncluded:a,memoryQuery:c||void 0,crossChatIncluded:i,reasoning:u,planLatencyMs:Date.now()-s}}}function C(e,s,o,r,t){if(t<=0)return"";let n=e.getSession(s,o,r);if(n.messages.length===0)return"";let m=Math.min(n.messages.length,t*2),a=n.messages.slice(-m),c=[`[Recent exchange \u2014 last ${a.length} messages]`];for(let i of a){let u=i.name||(i.role==="user"?"User":"Agent");c.push(`${u}: ${i.content}`)}return c.push("[End recent exchange \u2014 respond to the latest message above]"),c.push(""),c.join(`
|
|
15
|
+
`)}function I(e,s,o,r,t){let n=e.getSession(s,o,r);return n.messages.length===0?"":n.messages.slice(-t).map(a=>{let c=a.name||(a.role==="user"?"User":"Agent"),i=a.content.length>200?a.content.slice(0,200)+"\u2026":a.content;return` ${c}: ${i}`}).join(`
|
|
16
|
+
`)}function w(e){if(!e)return null;let s=e.trim();try{return JSON.parse(s)}catch{}let o=s.indexOf("{");if(o<0)return null;let r=0;for(let t=o;t<s.length;t++)if(s[t]==="{")r++;else if(s[t]==="}"&&(r--,r===0))try{return JSON.parse(s.slice(o,t+1))}catch{return null}return null}function M(e,s,o,r){let t=typeof e=="number"?Math.floor(e):Number.parseInt(String(e??""),10);return Number.isFinite(t)?Math.min(o,Math.max(s,t)):r}export{N as planContext};
|
|
17
|
+
//# sourceMappingURL=context-planner-2RGHJHSO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agents/context-planner.ts"],"sourcesContent":["// --- Context Planner ---\n//\n// A Haiku pre-call that decides what context to retrieve for the current\n// message, replacing the blind \"stack every layer\" approach of the layered\n// strategy. Drops --resume bloat by avoiding the full session history blob\n// on every turn; instead pulls only the specific facts / memory / cross-chat\n// the current message actually needs.\n//\n// Always-on core (handled by registry, not the planner): channel, scope,\n// landscape, identity, intent, and a small `recentTurns`-sized tail of the\n// session verbatim. The planner decides: how many tail turns to keep, and\n// whether to pull memory or cross-chat on top.\n//\n// Fail-open: any planner error returns null and the registry falls back to\n// the layered strategy. This keeps the feature safe to enable even when\n// the planner model is misbehaving.\n//\n// Cost model: one small Haiku call per message (~300 input / ~80 output\n// tokens, ~$0.0005). Win threshold is when it avoids shipping ≥5K tokens\n// of layered history/memory the agent didn't need — typical for short\n// \"ok / yes / do it\" messages where the layered path still dumps the\n// whole 12K-char session blob.\n\nimport type { SessionStore, SessionMessage } from \"./sessions\"\nimport type { MemoryStore } from \"./memory-store\"\n\n/** Planner output. When `null`, the caller falls back to the layered\n * strategy. Otherwise each field overrides the corresponding context\n * layer — an empty string means \"skip this layer entirely\". */\nexport interface ContextPlan {\n /** Rendered session history (same shape buildHistoryContext returns),\n * truncated to the last N turns the planner picked. */\n sessionHistory: string\n /** Rendered memory context, or \"\" when the planner decided memory\n * isn't relevant to this message. */\n memoryContext: string\n /** Rendered cross-chat summary, or \"\" when not needed. */\n crossChatContext: string\n /** Debug metadata — surfaced in logs + bench output so operators can\n * see what the planner chose. Not sent to the main agent. */\n debug: {\n recentTurns: number\n memoryIncluded: boolean\n memoryQuery?: string\n crossChatIncluded: boolean\n reasoning?: string\n planLatencyMs: number\n }\n}\n\nexport interface PlanContextInput {\n agentId: string\n channel: string\n chatId: string\n message: string\n sessions: SessionStore\n memoryStore: MemoryStore\n /** Caller-controlled timeout for the Haiku planner call. Defaults to\n * 8s — the planner is in the critical path before the main agent\n * runs, so we can't afford a long timeout. Fail-open if exceeded. */\n timeoutMs?: number\n}\n\nconst PLANNER_MODEL = \"claude-haiku-4-5-20251001\"\nconst PLANNER_PROMPT = `You are a context planner for an AI agent. Given the user's current message and recent conversation tail, decide what historical context the agent needs.\n\nOutput ONE JSON object on one line, no prose, no fences:\n { \"recentTurns\": number, \"memory\": { \"include\": boolean, \"query\": string }, \"crossChat\": boolean, \"reasoning\": string }\n\nRules:\n- \"recentTurns\" in [0,6]. Short acknowledgments (\"ok\", \"yes\", \"do it\", \"continue\") need 2-3 turns to know what they refer to. New topics need 0. Technical follow-ups need 3-5.\n- \"memory.include\" = true only when the message likely references a person, past event, decision, or project the agent should recall. \"memory.query\" = 3-8 words distilling what to search for (leave \"\" when include=false).\n- \"crossChat\" = true only when the message references another chat (\"DM\", \"the group\", \"earlier conversation\", a peer agent's name). Most messages = false.\n- \"reasoning\" = one short phrase explaining the plan.\n\nBe conservative — unneeded context costs tokens. When in doubt, exclude.`\n\nexport async function planContext(input: PlanContextInput): Promise<ContextPlan | null> {\n const start = Date.now()\n const timeoutMs = input.timeoutMs ?? 8_000\n\n // Snapshot the session tail for the planner prompt. Don't send the\n // whole session — the planner only needs enough to judge whether the\n // message is a follow-up, and we pay tokens for every line we ship.\n const recentTail = sessionTail(input.sessions, input.agentId, input.channel, input.chatId, 4)\n\n const userPrompt = [\n `Agent: ${input.agentId}`,\n `Channel: ${input.channel}`,\n recentTail ? `Recent conversation:\\n${recentTail}` : \"Recent conversation: (none — first message)\",\n ``,\n `Current message: ${input.message.slice(0, 1500)}`,\n ].join(\"\\n\")\n\n // Use the Anthropic SDK path — warm HTTP client, no subprocess cold-start.\n // resolveToken now recognizes CLAUDE_CODE_OAUTH_TOKEN so this works out\n // of the box for any operator who already has Claude CLI auth set up.\n // Planner timeout is tight (8s default) because plan latency is in the\n // critical path before the main agent runs.\n let planJson: any\n try {\n const { createProvider } = await import(\"@/agent/providers\")\n const provider = createProvider(\"claude\")\n const ac = new AbortController()\n const timer = setTimeout(() => ac.abort(), timeoutMs)\n try {\n const result = await provider.generate(\n [\n { role: \"system\", content: PLANNER_PROMPT },\n { role: \"user\", content: userPrompt },\n ],\n { model: PLANNER_MODEL, maxTokens: 256 },\n )\n planJson = extractJson(result.content)\n } finally {\n clearTimeout(timer)\n }\n } catch (err: any) {\n // Temporary: surface planner errors for debugging. Will silence once\n // we're confident the SDK path is stable.\n if (process.env.AGENTX_PLANNER_DEBUG) {\n // eslint-disable-next-line no-console\n console.error(`[planner] error: ${err?.message ?? err}`)\n }\n return null\n }\n\n if (!planJson || typeof planJson !== \"object\") return null\n\n const recentTurns = clampInt(planJson.recentTurns, 0, 6, 3)\n const memoryInclude = Boolean(planJson.memory?.include)\n const memoryQuery: string = typeof planJson.memory?.query === \"string\" ? planJson.memory.query : \"\"\n const crossChatInclude = Boolean(planJson.crossChat)\n const reasoning = typeof planJson.reasoning === \"string\" ? planJson.reasoning : undefined\n\n // Resolve the plan into actual retrieval strings.\n const sessionHistory = renderSessionTail(\n input.sessions,\n input.agentId,\n input.channel,\n input.chatId,\n recentTurns,\n )\n\n let memoryContext = \"\"\n if (memoryInclude && memoryQuery) {\n try {\n const relevant = input.memoryStore.findRelevant(memoryQuery, input.agentId, 5)\n memoryContext = input.memoryStore.buildContext(relevant)\n } catch {\n // Memory retrieval is best-effort; planner proceeds with empty memory.\n }\n }\n\n let crossChatContext = \"\"\n if (crossChatInclude) {\n // Force-include via empty message arg — the planner already decided.\n crossChatContext = input.sessions.getCrossSessionSummary(\n input.agentId, input.channel, input.chatId, \"\",\n )\n }\n\n return {\n sessionHistory,\n memoryContext,\n crossChatContext,\n debug: {\n recentTurns,\n memoryIncluded: memoryInclude,\n memoryQuery: memoryQuery || undefined,\n crossChatIncluded: crossChatInclude,\n reasoning,\n planLatencyMs: Date.now() - start,\n },\n }\n}\n\n/** Render the last N message pairs as a compact prompt tail. Returns \"\" when\n * there's no session on disk yet (first message). Format mirrors\n * SessionStore.buildHistoryContext but with an N-turn cap — the planner's\n * whole point is to cap this instead of dumping the full 12K-char log. */\nfunction renderSessionTail(\n sessions: SessionStore,\n agentId: string,\n channel: string,\n chatId: string,\n turns: number,\n): string {\n if (turns <= 0) return \"\"\n const session = sessions.getSession(agentId, channel, chatId)\n if (session.messages.length === 0) return \"\"\n // N \"turns\" = N*2 messages (user+agent pairs). Round to nearest message\n // count so a partial trailing user message still gets included.\n const msgCount = Math.min(session.messages.length, turns * 2)\n const tail = session.messages.slice(-msgCount)\n const lines: string[] = [`[Recent exchange — last ${tail.length} messages]`]\n for (const msg of tail) {\n const name = msg.name || (msg.role === \"user\" ? \"User\" : \"Agent\")\n lines.push(`${name}: ${msg.content}`)\n }\n lines.push(\"[End recent exchange — respond to the latest message above]\")\n lines.push(\"\")\n return lines.join(\"\\n\")\n}\n\n/** Very small tail for the planner's own prompt — enough for the planner to\n * decide whether this message is a follow-up, not enough to bloat the\n * planner call itself. Distinct from renderSessionTail (used for the main\n * agent) because we cap more aggressively here. */\nfunction sessionTail(\n sessions: SessionStore,\n agentId: string,\n channel: string,\n chatId: string,\n maxMessages: number,\n): string {\n const session = sessions.getSession(agentId, channel, chatId)\n if (session.messages.length === 0) return \"\"\n const tail = session.messages.slice(-maxMessages)\n return tail\n .map((m: SessionMessage) => {\n const name = m.name || (m.role === \"user\" ? \"User\" : \"Agent\")\n const content = m.content.length > 200 ? m.content.slice(0, 200) + \"…\" : m.content\n return ` ${name}: ${content}`\n })\n .join(\"\\n\")\n}\n\n/** Extract the first JSON object from a text response. Haiku sometimes\n * wraps output in backticks or prose despite the instructions, so we\n * scan for the first `{...}` span. */\nfunction extractJson(text: string): unknown {\n if (!text) return null\n const trimmed = text.trim()\n // Fast path — clean JSON.\n try { return JSON.parse(trimmed) } catch { /* fall through */ }\n // Fallback — first balanced object.\n const start = trimmed.indexOf(\"{\")\n if (start < 0) return null\n let depth = 0\n for (let i = start; i < trimmed.length; i++) {\n if (trimmed[i] === \"{\") depth++\n else if (trimmed[i] === \"}\") {\n depth--\n if (depth === 0) {\n try { return JSON.parse(trimmed.slice(start, i + 1)) } catch { return null }\n }\n }\n }\n return null\n}\n\nfunction clampInt(v: unknown, min: number, max: number, fallback: number): number {\n const n = typeof v === \"number\" ? Math.floor(v) : Number.parseInt(String(v ?? \"\"), 10)\n if (!Number.isFinite(n)) return fallback\n return Math.min(max, Math.max(min, n))\n}\n"],"mappings":"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","names":["PLANNER_MODEL","PLANNER_PROMPT","planContext","input","start","timeoutMs","recentTail","sessionTail","userPrompt","planJson","createProvider","provider","ac","timer","result","extractJson","err","recentTurns","clampInt","memoryInclude","memoryQuery","crossChatInclude","reasoning","sessionHistory","renderSessionTail","memoryContext","relevant","crossChatContext","sessions","agentId","channel","chatId","turns","session","msgCount","tail","lines","msg","name","maxMessages","m","content","text","trimmed","depth","i","v","min","max","fallback","n"]}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import"./chunk-HPDHNIA3.js";var $=/\bssh\s+(?:-i\s+(\S+)\s+)?([a-z_][a-z0-9_-]*)@([a-z0-9][a-z0-9.-]+|\d{1,3}(?:\.\d{1,3}){3})/gi,R=/\b(?:\d{1,3}\.){3}\d{1,3}\b/g,y=/\b([a-z0-9][a-z0-9._-]*)@([a-z0-9][a-z0-9.-]+\.[a-z]{2,})\b/gi;var k=/groups\/([a-z0-9][a-z0-9._-]+)[^\n|]*\(?\s*ID:?\s*(\d+)\s*\)?/gi,_=/(\/Users\/[^\s)`'"\]]+|\/home\/[^\s)`'"\]]+|\/var\/www\/[^\s)`'"\]]+|\/etc\/[^\s)`'"\]]+)/g,v=/(\+\d{1,3}[\s.-]?(?:\(\d+\)[\s.-]?)?\d[\s.\d-]{6,}\d)/g,z=/^(?:127\.|10\.|192\.168\.|172\.(?:1[6-9]|2\d|3[01])\.)/;function x(i,r){let s={byKind:{ssh:[],gitlab:[],path:[],contact:[]},scannedSkills:[],diagnostics:[]},c=new Set;for(let a of i){let e=a.frontmatter,n=a.instructions??"";if(r.filter?.length){let t=[e.name,e.description,...e.tags??[],e.category??"",a.path??"",n.slice(0,500)].join(" ").toLowerCase();if(!r.filter.some(u=>t.includes(u.toLowerCase())))continue}s.scannedSkills.push(e.name);let o=e.name;for(let t of w(n,r.namespace,o))m(s,t,"ssh",c)&&s.diagnostics.push({skill:e.name,kind:"ssh",id:t.id});for(let t of E(n,r.namespace,r.gitlabHost))m(s,t,"gitlab",c)&&s.diagnostics.push({skill:e.name,kind:"gitlab",id:t.id});for(let t of I(n,r.namespace))m(s,t,"path",c)&&s.diagnostics.push({skill:e.name,kind:"path",id:t.id});for(let t of j(n,r.namespace))m(s,t,"contact",c)&&s.diagnostics.push({skill:e.name,kind:"contact",id:t.id})}return s}function m(i,r,s,c){return c.has(r.id)?!1:(c.add(r.id),i.byKind[s].push(r),!0)}function w(i,r,s){let c=[];for(let e of i.matchAll($)){let[,n,o,t]=e;if(!o||!t)continue;let d=g(`${o}-${t.replace(/[^a-z0-9-]/gi,"-")}`);c.push({id:`${r}.ssh.${d}`,kind:"ssh",summary:`SSH ${o}@${t}`,fields:D({user:o,host:t,key:n}),tags:["discovered"],ownerAgent:s})}let a=new Set(c.flatMap(e=>Object.values(e.fields)));for(let e of i.matchAll(R)){let n=e[0];if(a.has(n)||z.test(n))continue;let o=g(`host-${n.replace(/\./g,"-")}`);c.push({id:`${r}.ssh.${o}`,kind:"ssh",summary:`Host ${n} (user not detected \u2014 fill in)`,fields:{host:n},tags:["discovered","needs-review"],ownerAgent:s}),a.add(n)}return c}var C=/\b([a-z0-9][a-z0-9._-]{1,40})\/([a-z0-9][a-z0-9._-]{1,40})\b/i,A=/\b(\d{1,8})\b/g;function E(i,r,s){let c=[];if(s&&!i.includes(s.replace(/^https?:\/\//,"")))return c;for(let e of i.matchAll(k)){let[,n,o]=e;!n||!o||c.push({id:`${r}.gitlab.group.${g(n)}`,kind:"gitlab",summary:`GitLab group ${n}`,fields:{groupId:Number(o),path:n},tags:["discovered"]})}let a=new RegExp(C.source,"gi");for(let e of i.split(/\r?\n/)){if(!/^\s*\|.+\|/.test(e)&&!/\bID[:\s]/i.test(e)&&!/gitlab/i.test(e))continue;let o,t,d=e.match(/https?:\/\/[^\s)]*gitlab[^\s)]*?\/([a-z0-9][a-z0-9._-]+)\/([a-z0-9][a-z0-9._-]+)/i);if(d&&d[1]&&d[2])o=d[1],t=d[2].replace(/[).,;:]+$/,"");else for(let f of e.matchAll(a)){let p=f[1],h=f[2];!p||!h||p==="wp-content"||h==="wp-content"||p.includes(".")||S(h)||(o=p,t=h)}if(!o||!t)continue;let l=[...e.matchAll(A)].map(f=>Number(f[1])).find(f=>f>=10&&f<=9999999);l&&c.push({id:`${r}.gitlab.project.${g(t)}`,kind:"gitlab",summary:`GitLab project ${o}/${t}`,fields:{projectId:l,path:`${o}/${t}`},tags:["discovered"]})}return c}function S(i){return/\.(md|ya?ml|json|ts|tsx|js|jsx|py|sh|conf|env)$/i.test(i)}function I(i,r){let s=[];for(let c of i.matchAll(_)){let a=c[1].replace(/[.,;:)]+$/,"");if(a.length<4)continue;let e=g(a.replace(/^\//,"").replace(/[/.]/g,"-"));s.push({id:`${r}.paths.${e}`,kind:"path",summary:a,fields:{path:a},tags:["discovered"]})}return s}function j(i,r){let s=[];for(let a of i.matchAll(y)){let[,e,n]=a;if(n.endsWith(".png")||n.endsWith(".jpg"))continue;let o=g(e);s.push({id:`${r}.contacts.${o}`,kind:"contact",summary:`${e}@${n}`,fields:{email:`${e}@${n}`},tags:["discovered"]})}let c=[...i.matchAll(v)].map(a=>a[1].trim());for(let a of c){let e=g(`phone-${a.replace(/\D+/g,"")}`);s.push({id:`${r}.contacts.${e}`,kind:"contact",summary:`Phone ${a}`,fields:{mobile:a},tags:["discovered","needs-review"]})}return s}function D(i){let r={};for(let[s,c]of Object.entries(i))c===void 0||c===""||(r[s]=c);return r}function g(i){return i.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,60)||"x"}function L(i,r){let s={},c={ssh:"ssh.yaml",gitlab:"gitlab.yaml",path:"paths.yaml",contact:"contacts.yaml"};for(let[a,e]of Object.entries(i.byKind)){if(e.length===0)continue;let n=`${r}.${a==="path"?"paths":a==="contact"?"contacts":a}`,o=["# Discovered automatically by 'agentx references discover'.",'# Review every card \u2014 flagged ones (tags include "needs-review") are',"# best-effort guesses. Set lastVerified once you've confirmed.",`namespace: ${n}`,"cards:"];for(let t of e){let d=t.id.startsWith(`${n}.`)?t.id.slice(n.length+1):t.id;o.push(` - id: ${d}`),o.push(` kind: ${t.kind}`),o.push(` summary: ${b(t.summary)}`),o.push(" fields:");for(let[u,l]of Object.entries(t.fields))o.push(` ${u}: ${typeof l=="number"||typeof l=="boolean"?l:b(String(l))}`);o.push(` tags: [${t.tags.join(", ")}]`),t.ownerAgent&&o.push(` ownerAgent: ${t.ownerAgent}`)}s[c[a]]=o.join(`
|
|
2
|
+
`)+`
|
|
3
|
+
`}return s}function b(i){return/^[A-Za-z0-9_./@+:-]+$/.test(i)&&!/^\d/.test(i)?i:`"${i.replace(/"/g,'\\"')}"`}export{x as discoverFromSkills,L as renderDiscovery};
|
|
4
|
+
//# sourceMappingURL=discover-OATU7SHI.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agents/references/discover.ts"],"sourcesContent":["import type { Skill } from \"@/agent/skills/types\"\nimport type { ReferenceCard, ReferenceKind } from \"./types\"\n\n// --- References discovery ---\n//\n// Pure extractor: given a namespace and a bag of source skills, walk their\n// markdown bodies and pull out structured facts (SSH hosts, GitLab project\n// IDs, filesystem paths, contacts) as ReferenceCard[]. Deterministic regex —\n// no LLM in the loop. Operator reviews the output and edits before flipping\n// `contextReferences: true` on the agents.\n\nconst SSH_LINE_RE = /\\bssh\\s+(?:-i\\s+(\\S+)\\s+)?([a-z_][a-z0-9_-]*)@([a-z0-9][a-z0-9.-]+|\\d{1,3}(?:\\.\\d{1,3}){3})/gi\nconst IP_RE = /\\b(?:\\d{1,3}\\.){3}\\d{1,3}\\b/g\nconst EMAIL_RE = /\\b([a-z0-9][a-z0-9._-]*)@([a-z0-9][a-z0-9.-]+\\.[a-z]{2,})\\b/gi\nconst GITLAB_PROJECT_LINE_RE = /([a-z0-9][a-z0-9._-]+)\\/([a-z0-9][a-z0-9._-]+)[^\\n|]*\\(?\\s*ID:?\\s*(\\d+)\\s*\\)?/gi\nconst GITLAB_GROUP_RE = /groups\\/([a-z0-9][a-z0-9._-]+)[^\\n|]*\\(?\\s*ID:?\\s*(\\d+)\\s*\\)?/gi\nconst PATH_RE = /(\\/Users\\/[^\\s)`'\"\\]]+|\\/home\\/[^\\s)`'\"\\]]+|\\/var\\/www\\/[^\\s)`'\"\\]]+|\\/etc\\/[^\\s)`'\"\\]]+)/g\nconst PHONE_RE = /(\\+\\d{1,3}[\\s.-]?(?:\\(\\d+\\)[\\s.-]?)?\\d[\\s.\\d-]{6,}\\d)/g\nconst PRIVATE_IP_RE = /^(?:127\\.|10\\.|192\\.168\\.|172\\.(?:1[6-9]|2\\d|3[01])\\.)/\n\nexport interface DiscoveryResult {\n /** Cards keyed by kind (ssh, gitlab, path, contact). */\n byKind: Record<string, ReferenceCard[]>\n /** Skills that were scanned. */\n scannedSkills: string[]\n /** Per-source diagnostics — what was found in which skill. */\n diagnostics: Array<{ skill: string; kind: string; id: string }>\n}\n\nexport interface DiscoveryOptions {\n /** Namespace prefix applied to every generated card id. */\n namespace: string\n /** Include only skills whose name OR tags contain any of these substrings.\n * Empty = scan every skill. */\n filter?: string[]\n /** Optional GitLab host to validate project URLs against (e.g.\n * https://gitlab.noqta.tn). When set, project URLs from other hosts are\n * ignored — keeps stray GitHub mentions out of a self-hosted registry. */\n gitlabHost?: string\n}\n\nexport function discoverFromSkills(skills: Skill[], opts: DiscoveryOptions): DiscoveryResult {\n const result: DiscoveryResult = {\n byKind: { ssh: [], gitlab: [], path: [], contact: [] },\n scannedSkills: [],\n diagnostics: [],\n }\n const seen = new Set<string>() // id de-dup across skills\n\n for (const skill of skills) {\n const fm = skill.frontmatter\n const body = skill.instructions ?? \"\"\n if (opts.filter?.length) {\n const hay = [\n fm.name,\n fm.description,\n ...(fm.tags ?? []),\n (fm as any).category ?? \"\",\n skill.path ?? \"\",\n body.slice(0, 500), // first ~125 tokens of body, enough for a \"MTGL Odoo Server\" header\n ].join(\" \").toLowerCase()\n const hit = opts.filter.some(f => hay.includes(f.toLowerCase()))\n if (!hit) continue\n }\n result.scannedSkills.push(fm.name)\n\n const ownerHint = fm.name\n\n for (const card of extractSshCards(body, opts.namespace, ownerHint)) {\n if (push(result, card, \"ssh\", seen)) result.diagnostics.push({ skill: fm.name, kind: \"ssh\", id: card.id })\n }\n for (const card of extractGitlabCards(body, opts.namespace, opts.gitlabHost)) {\n if (push(result, card, \"gitlab\", seen)) result.diagnostics.push({ skill: fm.name, kind: \"gitlab\", id: card.id })\n }\n for (const card of extractPathCards(body, opts.namespace)) {\n if (push(result, card, \"path\", seen)) result.diagnostics.push({ skill: fm.name, kind: \"path\", id: card.id })\n }\n for (const card of extractContactCards(body, opts.namespace)) {\n if (push(result, card, \"contact\", seen)) result.diagnostics.push({ skill: fm.name, kind: \"contact\", id: card.id })\n }\n }\n\n return result\n}\n\nfunction push(\n result: DiscoveryResult,\n card: ReferenceCard,\n kind: ReferenceKind,\n seen: Set<string>,\n): boolean {\n if (seen.has(card.id)) return false\n seen.add(card.id)\n result.byKind[kind].push(card)\n return true\n}\n\n// ---- Extractors ----\n\nfunction extractSshCards(body: string, ns: string, owner: string): ReferenceCard[] {\n const cards: ReferenceCard[] = []\n for (const m of body.matchAll(SSH_LINE_RE)) {\n const [, key, user, host] = m\n if (!user || !host) continue\n const slug = slugify(`${user}-${host.replace(/[^a-z0-9-]/gi, \"-\")}`)\n cards.push({\n id: `${ns}.ssh.${slug}`,\n kind: \"ssh\",\n summary: `SSH ${user}@${host}`,\n fields: cleanFields({ user, host, key }),\n tags: [\"discovered\"],\n ownerAgent: owner,\n })\n }\n // Catch IPs not already paired with a user\n const claimed = new Set(cards.flatMap(c => Object.values(c.fields)))\n for (const m of body.matchAll(IP_RE)) {\n const ip = m[0]\n if (claimed.has(ip)) continue\n if (PRIVATE_IP_RE.test(ip)) continue\n const slug = slugify(`host-${ip.replace(/\\./g, \"-\")}`)\n cards.push({\n id: `${ns}.ssh.${slug}`,\n kind: \"ssh\",\n summary: `Host ${ip} (user not detected — fill in)`,\n fields: { host: ip },\n tags: [\"discovered\", \"needs-review\"],\n ownerAgent: owner,\n })\n claimed.add(ip)\n }\n return cards\n}\n\nconst PROJECT_LINE_RE = /\\b([a-z0-9][a-z0-9._-]{1,40})\\/([a-z0-9][a-z0-9._-]{1,40})\\b/i\nconst ID_NUM_RE = /\\b(\\d{1,8})\\b/g\n\nfunction extractGitlabCards(body: string, ns: string, gitlabHost?: string): ReferenceCard[] {\n const cards: ReferenceCard[] = []\n if (gitlabHost && !body.includes(gitlabHost.replace(/^https?:\\/\\//, \"\"))) return cards\n // Group lines anywhere\n for (const m of body.matchAll(GITLAB_GROUP_RE)) {\n const [, slug, id] = m\n if (!slug || !id) continue\n cards.push({\n id: `${ns}.gitlab.group.${slugify(slug)}`,\n kind: \"gitlab\",\n summary: `GitLab group ${slug}`,\n fields: { groupId: Number(id), path: slug },\n tags: [\"discovered\"],\n })\n }\n // Project lines: walk line-by-line so table rows (\"| g/p | 269 |\") are\n // handled the same way as prose (\"g/p (ID: 269)\"). For each line that\n // mentions <group>/<project>, take the rightmost plausible numeric ID.\n const projectGlobalRe = new RegExp(PROJECT_LINE_RE.source, \"gi\")\n for (const line of body.split(/\\r?\\n/)) {\n // Skip lines that look like prose mentioning a path, not a GitLab project.\n // Real GitLab callouts almost always include \"ID:\" or a gitlab.* hostname\n // in the same line, or appear inside a markdown table row. Without one of\n // those, the noise rate is too high.\n const isTableRow = /^\\s*\\|.+\\|/.test(line)\n if (!isTableRow && !/\\bID[:\\s]/i.test(line) && !/gitlab/i.test(line)) continue\n // Two-pass: prefer a GitLab URL (https://gitlab.<host>/<group>/<repo>)\n // when present on the line, otherwise fall back to the rightmost\n // <group>/<repo> token that doesn't have a hostname-shaped group.\n let group: string | undefined\n let repo: string | undefined\n const urlMatch = line.match(/https?:\\/\\/[^\\s)]*gitlab[^\\s)]*?\\/([a-z0-9][a-z0-9._-]+)\\/([a-z0-9][a-z0-9._-]+)/i)\n if (urlMatch && urlMatch[1] && urlMatch[2]) {\n group = urlMatch[1]\n repo = urlMatch[2].replace(/[).,;:]+$/, \"\")\n } else {\n // Walk every <a>/<b> on the line and keep the last one that looks like\n // a real group/repo (no dots in group, not a wp-content path, not a file).\n for (const m of line.matchAll(projectGlobalRe)) {\n const g = m[1]\n const r = m[2]\n if (!g || !r) continue\n if (g === \"wp-content\" || r === \"wp-content\") continue\n if (g.includes(\".\")) continue\n if (looksLikeFile(r)) continue\n group = g\n repo = r\n }\n }\n if (!group || !repo) continue\n const ids = [...line.matchAll(ID_NUM_RE)].map(m => Number(m[1]))\n const id = ids.find(n => n >= 10 && n <= 9_999_999)\n if (!id) continue\n cards.push({\n id: `${ns}.gitlab.project.${slugify(repo)}`,\n kind: \"gitlab\",\n summary: `GitLab project ${group}/${repo}`,\n fields: { projectId: id, path: `${group}/${repo}` },\n tags: [\"discovered\"],\n })\n }\n return cards\n}\n\nfunction looksLikeFile(s: string): boolean {\n return /\\.(md|ya?ml|json|ts|tsx|js|jsx|py|sh|conf|env)$/i.test(s)\n}\n\nfunction extractPathCards(body: string, ns: string): ReferenceCard[] {\n const cards: ReferenceCard[] = []\n for (const m of body.matchAll(PATH_RE)) {\n let p = m[1].replace(/[.,;:)]+$/, \"\") // strip trailing punctuation\n if (p.length < 4) continue\n const slug = slugify(p.replace(/^\\//, \"\").replace(/[/.]/g, \"-\"))\n cards.push({\n id: `${ns}.paths.${slug}`,\n kind: \"path\",\n summary: p,\n fields: { path: p },\n tags: [\"discovered\"],\n })\n }\n return cards\n}\n\nfunction extractContactCards(body: string, ns: string): ReferenceCard[] {\n const cards: ReferenceCard[] = []\n for (const m of body.matchAll(EMAIL_RE)) {\n const [, local, domain] = m\n if (domain.endsWith(\".png\") || domain.endsWith(\".jpg\")) continue // junk filter\n const slug = slugify(local)\n cards.push({\n id: `${ns}.contacts.${slug}`,\n kind: \"contact\",\n summary: `${local}@${domain}`,\n fields: { email: `${local}@${domain}` },\n tags: [\"discovered\"],\n })\n }\n // Best-effort: if a phone appears on the same line as a known contact email\n // we already picked, attach it. Otherwise drop a standalone phone card.\n const phones = [...body.matchAll(PHONE_RE)].map(m => m[1].trim())\n for (const phone of phones) {\n const slug = slugify(`phone-${phone.replace(/\\D+/g, \"\")}`)\n cards.push({\n id: `${ns}.contacts.${slug}`,\n kind: \"contact\",\n summary: `Phone ${phone}`,\n fields: { mobile: phone },\n tags: [\"discovered\", \"needs-review\"],\n })\n }\n return cards\n}\n\n// ---- Helpers ----\n\nfunction cleanFields(fields: Record<string, string | number | boolean | undefined>): Record<string, string | number | boolean> {\n const out: Record<string, string | number | boolean> = {}\n for (const [k, v] of Object.entries(fields)) {\n if (v === undefined || v === \"\") continue\n out[k] = v\n }\n return out\n}\n\nfunction slugify(input: string): string {\n return input\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\")\n .slice(0, 60) || \"x\"\n}\n\n/**\n * Render a discovery result as YAML-ready file contents, one per kind.\n * Output is a complete `ReferenceFile`-shaped payload, ready to write to\n * `.agentx/references/<ns>/{ssh,gitlab,paths,contacts}.yaml`.\n */\nexport function renderDiscovery(result: DiscoveryResult, namespace: string): Record<string, string> {\n const out: Record<string, string> = {}\n const fileMap: Record<string, string> = {\n ssh: \"ssh.yaml\",\n gitlab: \"gitlab.yaml\",\n path: \"paths.yaml\",\n contact: \"contacts.yaml\",\n }\n for (const [kind, cards] of Object.entries(result.byKind)) {\n if (cards.length === 0) continue\n const ns = `${namespace}.${kind === \"path\" ? \"paths\" : kind === \"contact\" ? \"contacts\" : kind}`\n const lines = [\n `# Discovered automatically by 'agentx references discover'.`,\n `# Review every card — flagged ones (tags include \"needs-review\") are`,\n `# best-effort guesses. Set lastVerified once you've confirmed.`,\n `namespace: ${ns}`,\n `cards:`,\n ]\n for (const card of cards) {\n // Strip the namespace from the id so the file's `namespace:` doesn't\n // double-apply it on load.\n const localId = card.id.startsWith(`${ns}.`) ? card.id.slice(ns.length + 1) : card.id\n lines.push(` - id: ${localId}`)\n lines.push(` kind: ${card.kind}`)\n lines.push(` summary: ${quote(card.summary)}`)\n lines.push(` fields:`)\n for (const [k, v] of Object.entries(card.fields)) {\n lines.push(` ${k}: ${typeof v === \"number\" || typeof v === \"boolean\" ? v : quote(String(v))}`)\n }\n lines.push(` tags: [${card.tags.join(\", \")}]`)\n if (card.ownerAgent) lines.push(` ownerAgent: ${card.ownerAgent}`)\n }\n out[fileMap[kind]] = lines.join(\"\\n\") + \"\\n\"\n }\n return out\n}\n\nfunction quote(s: string): string {\n if (/^[A-Za-z0-9_./@+:-]+$/.test(s) && !/^\\d/.test(s)) return s\n return `\"${s.replace(/\"/g, '\\\\\"')}\"`\n}\n"],"mappings":"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","names":["SSH_LINE_RE","IP_RE","EMAIL_RE","GITLAB_GROUP_RE","PATH_RE","PHONE_RE","PRIVATE_IP_RE","discoverFromSkills","skills","opts","result","seen","skill","fm","body","hay","f","ownerHint","card","extractSshCards","push","extractGitlabCards","extractPathCards","extractContactCards","kind","ns","owner","cards","m","SSH_LINE_RE","key","user","host","slug","slugify","cleanFields","claimed","c","IP_RE","ip","PROJECT_LINE_RE","ID_NUM_RE","gitlabHost","id","projectGlobalRe","line","group","repo","urlMatch","g","r","looksLikeFile","n","s","p","EMAIL_RE","local","domain","phones","phone","fields","out","k","v","input","renderDiscovery","namespace","fileMap","lines","localId","quote"]}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import"./chunk-HPDHNIA3.js";function u(o){let r=o.store.list().map(t=>({id:t.id,title:t.title})),s=o.actors.listActors().map(t=>t.id),n=o.actors.listRoles().map(t=>t.id),e=[];if(e.push(i),e.push(""),e.push(l),e.push(""),e.push("# Environment \u2014 only reference values that exist"),e.push(""),e.push("## Available agents (for `agent` nodes)"),o.availableAgents.length===0)e.push(" (none registered \u2014 the user must add an agent first for `agent` nodes to work)");else for(let t of o.availableAgents)e.push(` - \`${t.id}\`${t.description?` \u2014 ${t.description}`:""}`);if(e.push(""),e.push("## Available actors (for `userTask.assignTo`)"),s.length===0)e.push(" (none \u2014 user must register actors with `agentx actor add <id> \u2026` before workflows can use `userTask`)");else for(let t of s)e.push(` - \`${t}\``);if(e.push(""),e.push("## Available roles (for `userTask.assignTo`)"),n.length===0)e.push(" (none)");else for(let t of n)e.push(` - \`${t}\``);if(e.push(""),e.push("## Available channels (for `action.*` and `trigger.channel`)"),e.push(` ${o.availableChannels.length?o.availableChannels.map(t=>`\`${t}\``).join(", "):"(none live on this node)"}`),e.push(""),e.push("## Existing workflows (for `subProcess.workflowId`)"),r.length===0)e.push(" (no other workflows defined)");else for(let t of r)e.push(` - \`${t.id}\` \u2014 ${t.title}`);e.push(""),o.currentWorkflow&&(e.push("# Current workflow on canvas"),e.push(""),e.push("The user is editing this workflow. Build on it \u2014 do NOT replace it unless they explicitly ask."),e.push(""),e.push("```json"),e.push(JSON.stringify(o.currentWorkflow,null,2)),e.push("```"),e.push("")),e.push("# Conversation"),e.push("");for(let t of o.messages){let a=t.role==="user"?"USER":"ASSISTANT";e.push(`${a}: ${t.content}`),e.push("")}return e.push(c),e.join(`
|
|
2
|
+
`)}function d(o){let r=/```(?:json)?\s*\n([\s\S]*?)```/.exec(o),s=[];r&&s.push(r[1]);let n=o.match(/\{[\s\S]*\}/);n&&s.push(n[0]);for(let e of s)try{let t=JSON.parse(e);if(t&&typeof t=="object"&&"id"in t&&"nodes"in t)return t}catch{}return null}var i=`You are AgentX's workflow author. The user describes a business process in
|
|
3
|
+
plain language; you return a valid V2 workflow JSON that implements it.
|
|
4
|
+
|
|
5
|
+
Read the node catalog below. Prefer the BPM primitives (userTask,
|
|
6
|
+
subProcess, signal.emit/wait, timer.boundary, gateway.parallel, rule)
|
|
7
|
+
when modelling processes with humans, deadlines, parallel streams, or
|
|
8
|
+
nested activities. Prefer the bot primitives (agent, branch, action.*)
|
|
9
|
+
for simple message-in \u2192 reply-out automations.`,l=`# V2 Node catalog
|
|
10
|
+
|
|
11
|
+
## Triggers (exactly one per workflow)
|
|
12
|
+
- \`trigger.channel\` \u2014 inbound channel event.
|
|
13
|
+
config: { "source": "whatsapp-message" | "telegram-message" | "slack-message" | "discord-message" | "gitlab-issue" | "gitlab-pipeline", "filter": { "chat"?: "*", "project"?: "noqta/web", "labels"?: ["x"] } }
|
|
14
|
+
- \`trigger.cron\` \u2014 scheduled. config: { "spec": "0 9 * * *", "timezone": "Africa/Tunis" }
|
|
15
|
+
- \`trigger.hook\` \u2014 subscribes to any on:* hook event. config: { "event": "on:gitlab-issue" }
|
|
16
|
+
- \`trigger.manual\` \u2014 kicked off by CLI or API. config: {}
|
|
17
|
+
- \`trigger.form\` \u2014 human fills a form to start. config: { "form": FormSchema, "startableBy": "role:public" }
|
|
18
|
+
|
|
19
|
+
## Compute
|
|
20
|
+
- \`agent\` \u2014 run an agent with a templated prompt. Output: { reply, result (parsed RESULT: token), json?, durationMs }.
|
|
21
|
+
config: { "agentId": "<id>", "prompt": "Classify {{trigger.text}} \u2014 reply on one line: RESULT: a|b|c", "resultParser": "noqta-result-token" | "json", "timeoutMinutes": 2 }
|
|
22
|
+
- \`transform\` \u2014 reshape upstream context.
|
|
23
|
+
config: { "path": "trigger.contact.phone" } OR { "template": { "key": "{{trigger.text}}" } }
|
|
24
|
+
|
|
25
|
+
## Control flow
|
|
26
|
+
- \`branch\` \u2014 N-way switch (first match wins).
|
|
27
|
+
config: { "cases": [{ "when": { "kind": "equals"|"contains"|"matches"|"exists", "params": { "path": "classify.result", "value": "ok" } }, "to": "port1" }], "default": "fallbackPort" }
|
|
28
|
+
Wire with edges: { "from": "branchId", "fromPort": "port1", "to": "nextNode" }
|
|
29
|
+
- \`gateway.parallel\` \u2014 fan-out (one \u2192 many) or join (many \u2192 one).
|
|
30
|
+
config: { "mode": "fanOut" } \u2014 splits to every outgoing edge
|
|
31
|
+
config: { "mode": "join" } \u2014 waits until every incoming edge arrives, then fires once
|
|
32
|
+
- \`rule\` \u2014 DMN decision table, first matching row wins.
|
|
33
|
+
config: {
|
|
34
|
+
"inputs": ["{{trigger.tier}}", "{{trigger.amount}}"],
|
|
35
|
+
"rules": [
|
|
36
|
+
{ "when": ["gold", ">100"], "to": "vip", "output": { "route": "vip" } },
|
|
37
|
+
{ "when": ["*", ">50"], "to": "high", "output": { "route": "high" } }
|
|
38
|
+
],
|
|
39
|
+
"default": { "to": "low", "output": { "route": "low" } }
|
|
40
|
+
}
|
|
41
|
+
Cell syntax: "*" wildcard, exact "value", ">N" "<N" ">=N" "<=N", "!=x", "/regex/".
|
|
42
|
+
- \`checkpoint\` \u2014 pause until a matching event.
|
|
43
|
+
config: { "name": "await-x", "waitFor": { "source": "manual" }, "resumeMatch": {} }
|
|
44
|
+
|
|
45
|
+
## BPM
|
|
46
|
+
- \`userTask\` \u2014 assign a form to an actor or role; pauses until submission.
|
|
47
|
+
config: {
|
|
48
|
+
"assignTo": "actor:alice" | "role:reviewers",
|
|
49
|
+
"title": "Review expense {{trigger.values.amount}}",
|
|
50
|
+
"description": "\u2026",
|
|
51
|
+
"dueIn": "P2D" | "PT2H" | { "minutes": 60 },
|
|
52
|
+
"form": FormSchema
|
|
53
|
+
}
|
|
54
|
+
Output: { submittedBy, submittedAt, values: { fieldKey: value }, action: "primary" | "secondary" }
|
|
55
|
+
- \`subProcess\` \u2014 spawn a child workflow; parent pauses until child reaches end.
|
|
56
|
+
config: {
|
|
57
|
+
"workflowId": "<other workflow id>",
|
|
58
|
+
"inputMap": { "trigger": { "grantee": "{{trigger.values.name}}" } } (or "*" for full inherit),
|
|
59
|
+
"awaitCompletion": true
|
|
60
|
+
}
|
|
61
|
+
Output: { childRunId, status, output: <child's last bundle> }
|
|
62
|
+
- \`signal.emit\` \u2014 publish an event. config: { "name": "approved", "scope": "workflow"|"global", "payload": {} }
|
|
63
|
+
- \`signal.wait\` \u2014 pause until matching signal arrives. config: { "name": "approved", "scope": "workflow", "match": { "grantId": "{{trigger.id}}" } }
|
|
64
|
+
- \`timer.boundary\` \u2014 pause for a duration. config: { "after": "PT2H" | "P1D" | 60 (minutes) }
|
|
65
|
+
|
|
66
|
+
## Actions \u2014 side-effect sinks
|
|
67
|
+
- \`action.send\` \u2014 post a message to any channel. config: { "channel": "telegram"|"whatsapp"|\u2026, "chatId": "{{trigger.chatId}}", "text": "Hi {{classify.reply}}", "accountId"?: "\u2026" }
|
|
68
|
+
- \`action.createIssue\` \u2014 open a GitLab issue. config: { "channel": "gitlab", "project": "noqta/web", "title": "\u2026", "description": "\u2026", "labels": ["x"], "assignees": ["user"] }
|
|
69
|
+
- \`action.setLabel\` / \`action.readLabel\` / \`action.react\` / \`action.editMessage\` / \`action.logTime\` \u2014 self-explanatory; each maps 1:1 to a channel adapter method.
|
|
70
|
+
- \`action.callHTTP\` \u2014 outbound HTTP. config: { "method": "POST"|"GET"|\u2026, "url": "https://\u2026", "headers": {}, "body": {}, "timeoutMs": 30000 }
|
|
71
|
+
|
|
72
|
+
## Terminal
|
|
73
|
+
- \`end\` \u2014 completes the run. config: { "status": "completed" | "failed" | "canceled" }
|
|
74
|
+
|
|
75
|
+
## FormSchema (used by userTask + trigger.form)
|
|
76
|
+
{
|
|
77
|
+
"title": "Review application",
|
|
78
|
+
"description": "\u2026",
|
|
79
|
+
"fields": [
|
|
80
|
+
{ "key": "score", "label": "Score", "type": "number", "required": true, "validate": { "min": 1, "max": 10 } },
|
|
81
|
+
{ "key": "verdict", "label": "Verdict", "type": "select", "required": true, "options": ["approve", "reject"] },
|
|
82
|
+
{ "key": "notes", "label": "Notes", "type": "long-text" }
|
|
83
|
+
],
|
|
84
|
+
"submitLabel": "Approve",
|
|
85
|
+
"secondaryAction": { "key": "reject", "label": "Reject" }
|
|
86
|
+
}
|
|
87
|
+
Field types: text, long-text, number, boolean, date, select, multi-select, file.
|
|
88
|
+
A form with a \`secondaryAction\` and no required-unfilled fields renders as one-tap Approve/Reject buttons in chat.
|
|
89
|
+
|
|
90
|
+
## Templates
|
|
91
|
+
Every prompt + config value can reference \`{{nodeId.path}}\` against the run context. Examples:
|
|
92
|
+
{{trigger.values.amount}} \u2014 field from the start form
|
|
93
|
+
{{classify.result}} \u2014 parsed RESULT: token from an agent node
|
|
94
|
+
{{review.values.score}} \u2014 submitted userTask field
|
|
95
|
+
{{env.GITLAB_TOKEN}} \u2014 env var (must be listed in workflow.envAllow)
|
|
96
|
+
|
|
97
|
+
## Edges
|
|
98
|
+
[{ "from": "nodeId", "to": "nodeId" }, { "from": "branchId", "fromPort": "case-label", "to": "nodeId" }]
|
|
99
|
+
Every edge's from/to must reference an existing node. Branch + rule nodes MUST have outgoing edges whose fromPort matches a declared case/rule/default.
|
|
100
|
+
|
|
101
|
+
## Workflow shell
|
|
102
|
+
{
|
|
103
|
+
"id": "lower-kebab-id",
|
|
104
|
+
"version": 2,
|
|
105
|
+
"title": "Human readable",
|
|
106
|
+
"description": "optional",
|
|
107
|
+
"priority": 0,
|
|
108
|
+
"fanOut": false,
|
|
109
|
+
"envAllow": [],
|
|
110
|
+
"maxChildDepth": 5,
|
|
111
|
+
"retention": { "maxRuns": 500, "maxDays": 90 },
|
|
112
|
+
"nodes": [...],
|
|
113
|
+
"edges": [...]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
## Lint rules (all enforced on save)
|
|
117
|
+
- exactly one trigger.* node
|
|
118
|
+
- every edge references an existing node
|
|
119
|
+
- every node is reachable from the trigger
|
|
120
|
+
- at least one end/checkpoint/userTask/subProcess/signal.wait/timer.boundary is reachable
|
|
121
|
+
- branch + rule outgoing fromPort values match declared cases (or default)
|
|
122
|
+
- cycles are only allowed when the cycle crosses an agent/checkpoint/userTask/subProcess/signal.wait/timer.boundary`,c=`# Your reply
|
|
123
|
+
|
|
124
|
+
Respond in ONE of two ways:
|
|
125
|
+
|
|
126
|
+
1. **Clarification**: if the user's request is ambiguous, ask exactly ONE short follow-up question. No JSON.
|
|
127
|
+
|
|
128
|
+
2. **Workflow**: if you have enough to produce a workflow, reply with:
|
|
129
|
+
- 1\u20133 short lines explaining what you built
|
|
130
|
+
- a \`\`\`json code block containing the complete, valid V2 workflow
|
|
131
|
+
- (no prose after the JSON block)
|
|
132
|
+
|
|
133
|
+
Only reference agent ids, actor ids, role ids, and channel names that
|
|
134
|
+
appear in the Environment section above. If the user asks for an agent
|
|
135
|
+
that doesn't exist, tell them which agent to register first. Never
|
|
136
|
+
invent workflowIds for subProcess that aren't in the existing list.`;export{u as buildWorkflowAuthorPrompt,d as extractWorkflowJson};
|
|
137
|
+
//# sourceMappingURL=editor-chat-FUBQLMAQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/workflows/editor-chat.ts"],"sourcesContent":["import type { Workflow } from \"./types\"\nimport type { WorkflowStore } from \"./store\"\nimport type { ActorStore } from \"../actors/store\"\n\n// --- Workflow-builder chat context ---\n//\n// Produces the plain-text prompt the authoring agent reads. Packs\n// *everything* the agent needs to generate a valid V2 workflow JSON on\n// its first try: node catalog with config examples, available agent\n// ids, actor + role ids, channel adapter names, the user's existing\n// workflow list (for subProcess targets), and — when the author is\n// editing — the current workflow JSON as a starting point.\n//\n// The output is deliberately text, not JSON. Agents (LLMs) produce\n// better structured output when the schema is described as prose +\n// examples rather than as raw JSON Schema. The explicit \"reply with a\n// ```json block```\" instruction at the tail is what the API endpoint\n// parses back.\n\nexport interface ChatMessage {\n role: \"user\" | \"assistant\"\n content: string\n}\n\nexport interface BuildContextArgs {\n messages: ChatMessage[]\n store: WorkflowStore\n actors: ActorStore\n /** Full list of agent ids + optional descriptions. */\n availableAgents: Array<{ id: string; description?: string; tags?: string[] }>\n /** Adapter names the daemon has live (telegram, whatsapp, gitlab, ...). */\n availableChannels: string[]\n /** The workflow the user is currently editing (if any). */\n currentWorkflow?: Workflow\n}\n\n/** Build the single-string prompt the agent executes. */\nexport function buildWorkflowAuthorPrompt(args: BuildContextArgs): string {\n const workflows = args.store.list().map((w) => ({ id: w.id, title: w.title }))\n const actorIds = args.actors.listActors().map((a) => a.id)\n const roleIds = args.actors.listRoles().map((r) => r.id)\n\n const parts: string[] = []\n\n parts.push(SYSTEM_HEADER)\n parts.push(\"\")\n parts.push(NODE_CATALOG)\n parts.push(\"\")\n parts.push(\"# Environment — only reference values that exist\")\n parts.push(\"\")\n parts.push(`## Available agents (for \\`agent\\` nodes)`)\n if (args.availableAgents.length === 0) {\n parts.push(\" (none registered — the user must add an agent first for `agent` nodes to work)\")\n } else {\n for (const a of args.availableAgents) {\n parts.push(` - \\`${a.id}\\`${a.description ? ` — ${a.description}` : \"\"}`)\n }\n }\n parts.push(\"\")\n parts.push(\"## Available actors (for `userTask.assignTo`)\")\n if (actorIds.length === 0) {\n parts.push(\" (none — user must register actors with `agentx actor add <id> …` before workflows can use `userTask`)\")\n } else for (const id of actorIds) parts.push(` - \\`${id}\\``)\n parts.push(\"\")\n parts.push(\"## Available roles (for `userTask.assignTo`)\")\n if (roleIds.length === 0) parts.push(\" (none)\")\n else for (const id of roleIds) parts.push(` - \\`${id}\\``)\n parts.push(\"\")\n parts.push(\"## Available channels (for `action.*` and `trigger.channel`)\")\n parts.push(` ${args.availableChannels.length ? args.availableChannels.map((c) => `\\`${c}\\``).join(\", \") : \"(none live on this node)\"}`)\n parts.push(\"\")\n parts.push(\"## Existing workflows (for `subProcess.workflowId`)\")\n if (workflows.length === 0) parts.push(\" (no other workflows defined)\")\n else for (const w of workflows) parts.push(` - \\`${w.id}\\` — ${w.title}`)\n parts.push(\"\")\n\n if (args.currentWorkflow) {\n parts.push(\"# Current workflow on canvas\")\n parts.push(\"\")\n parts.push(\"The user is editing this workflow. Build on it — do NOT replace it unless they explicitly ask.\")\n parts.push(\"\")\n parts.push(\"```json\")\n parts.push(JSON.stringify(args.currentWorkflow, null, 2))\n parts.push(\"```\")\n parts.push(\"\")\n }\n\n parts.push(\"# Conversation\")\n parts.push(\"\")\n for (const m of args.messages) {\n const tag = m.role === \"user\" ? \"USER\" : \"ASSISTANT\"\n parts.push(`${tag}: ${m.content}`)\n parts.push(\"\")\n }\n\n parts.push(RESPONSE_RULES)\n return parts.join(\"\\n\")\n}\n\n/** Try to extract a workflow-shaped JSON block from an agent reply. Returns\n * null if no JSON block is present OR the JSON doesn't have the minimum\n * workflow fields (id, nodes). The caller still runs full zod validation\n * before writing. */\nexport function extractWorkflowJson(reply: string): Record<string, unknown> | null {\n // Prefer fenced ```json blocks; fall back to the largest {...} block.\n const fenced = /```(?:json)?\\s*\\n([\\s\\S]*?)```/.exec(reply)\n const candidates: string[] = []\n if (fenced) candidates.push(fenced[1])\n const braceMatch = reply.match(/\\{[\\s\\S]*\\}/)\n if (braceMatch) candidates.push(braceMatch[0])\n\n for (const raw of candidates) {\n try {\n const parsed = JSON.parse(raw)\n if (parsed && typeof parsed === \"object\" && \"id\" in parsed && \"nodes\" in parsed) {\n return parsed as Record<string, unknown>\n }\n } catch { /* try next */ }\n }\n return null\n}\n\nconst SYSTEM_HEADER = `You are AgentX's workflow author. The user describes a business process in\nplain language; you return a valid V2 workflow JSON that implements it.\n\nRead the node catalog below. Prefer the BPM primitives (userTask,\nsubProcess, signal.emit/wait, timer.boundary, gateway.parallel, rule)\nwhen modelling processes with humans, deadlines, parallel streams, or\nnested activities. Prefer the bot primitives (agent, branch, action.*)\nfor simple message-in → reply-out automations.`\n\nconst NODE_CATALOG = `# V2 Node catalog\n\n## Triggers (exactly one per workflow)\n- \\`trigger.channel\\` — inbound channel event.\n config: { \"source\": \"whatsapp-message\" | \"telegram-message\" | \"slack-message\" | \"discord-message\" | \"gitlab-issue\" | \"gitlab-pipeline\", \"filter\": { \"chat\"?: \"*\", \"project\"?: \"noqta/web\", \"labels\"?: [\"x\"] } }\n- \\`trigger.cron\\` — scheduled. config: { \"spec\": \"0 9 * * *\", \"timezone\": \"Africa/Tunis\" }\n- \\`trigger.hook\\` — subscribes to any on:* hook event. config: { \"event\": \"on:gitlab-issue\" }\n- \\`trigger.manual\\` — kicked off by CLI or API. config: {}\n- \\`trigger.form\\` — human fills a form to start. config: { \"form\": FormSchema, \"startableBy\": \"role:public\" }\n\n## Compute\n- \\`agent\\` — run an agent with a templated prompt. Output: { reply, result (parsed RESULT: token), json?, durationMs }.\n config: { \"agentId\": \"<id>\", \"prompt\": \"Classify {{trigger.text}} — reply on one line: RESULT: a|b|c\", \"resultParser\": \"noqta-result-token\" | \"json\", \"timeoutMinutes\": 2 }\n- \\`transform\\` — reshape upstream context.\n config: { \"path\": \"trigger.contact.phone\" } OR { \"template\": { \"key\": \"{{trigger.text}}\" } }\n\n## Control flow\n- \\`branch\\` — N-way switch (first match wins).\n config: { \"cases\": [{ \"when\": { \"kind\": \"equals\"|\"contains\"|\"matches\"|\"exists\", \"params\": { \"path\": \"classify.result\", \"value\": \"ok\" } }, \"to\": \"port1\" }], \"default\": \"fallbackPort\" }\n Wire with edges: { \"from\": \"branchId\", \"fromPort\": \"port1\", \"to\": \"nextNode\" }\n- \\`gateway.parallel\\` — fan-out (one → many) or join (many → one).\n config: { \"mode\": \"fanOut\" } — splits to every outgoing edge\n config: { \"mode\": \"join\" } — waits until every incoming edge arrives, then fires once\n- \\`rule\\` — DMN decision table, first matching row wins.\n config: {\n \"inputs\": [\"{{trigger.tier}}\", \"{{trigger.amount}}\"],\n \"rules\": [\n { \"when\": [\"gold\", \">100\"], \"to\": \"vip\", \"output\": { \"route\": \"vip\" } },\n { \"when\": [\"*\", \">50\"], \"to\": \"high\", \"output\": { \"route\": \"high\" } }\n ],\n \"default\": { \"to\": \"low\", \"output\": { \"route\": \"low\" } }\n }\n Cell syntax: \"*\" wildcard, exact \"value\", \">N\" \"<N\" \">=N\" \"<=N\", \"!=x\", \"/regex/\".\n- \\`checkpoint\\` — pause until a matching event.\n config: { \"name\": \"await-x\", \"waitFor\": { \"source\": \"manual\" }, \"resumeMatch\": {} }\n\n## BPM\n- \\`userTask\\` — assign a form to an actor or role; pauses until submission.\n config: {\n \"assignTo\": \"actor:alice\" | \"role:reviewers\",\n \"title\": \"Review expense {{trigger.values.amount}}\",\n \"description\": \"…\",\n \"dueIn\": \"P2D\" | \"PT2H\" | { \"minutes\": 60 },\n \"form\": FormSchema\n }\n Output: { submittedBy, submittedAt, values: { fieldKey: value }, action: \"primary\" | \"secondary\" }\n- \\`subProcess\\` — spawn a child workflow; parent pauses until child reaches end.\n config: {\n \"workflowId\": \"<other workflow id>\",\n \"inputMap\": { \"trigger\": { \"grantee\": \"{{trigger.values.name}}\" } } (or \"*\" for full inherit),\n \"awaitCompletion\": true\n }\n Output: { childRunId, status, output: <child's last bundle> }\n- \\`signal.emit\\` — publish an event. config: { \"name\": \"approved\", \"scope\": \"workflow\"|\"global\", \"payload\": {} }\n- \\`signal.wait\\` — pause until matching signal arrives. config: { \"name\": \"approved\", \"scope\": \"workflow\", \"match\": { \"grantId\": \"{{trigger.id}}\" } }\n- \\`timer.boundary\\` — pause for a duration. config: { \"after\": \"PT2H\" | \"P1D\" | 60 (minutes) }\n\n## Actions — side-effect sinks\n- \\`action.send\\` — post a message to any channel. config: { \"channel\": \"telegram\"|\"whatsapp\"|…, \"chatId\": \"{{trigger.chatId}}\", \"text\": \"Hi {{classify.reply}}\", \"accountId\"?: \"…\" }\n- \\`action.createIssue\\` — open a GitLab issue. config: { \"channel\": \"gitlab\", \"project\": \"noqta/web\", \"title\": \"…\", \"description\": \"…\", \"labels\": [\"x\"], \"assignees\": [\"user\"] }\n- \\`action.setLabel\\` / \\`action.readLabel\\` / \\`action.react\\` / \\`action.editMessage\\` / \\`action.logTime\\` — self-explanatory; each maps 1:1 to a channel adapter method.\n- \\`action.callHTTP\\` — outbound HTTP. config: { \"method\": \"POST\"|\"GET\"|…, \"url\": \"https://…\", \"headers\": {}, \"body\": {}, \"timeoutMs\": 30000 }\n\n## Terminal\n- \\`end\\` — completes the run. config: { \"status\": \"completed\" | \"failed\" | \"canceled\" }\n\n## FormSchema (used by userTask + trigger.form)\n{\n \"title\": \"Review application\",\n \"description\": \"…\",\n \"fields\": [\n { \"key\": \"score\", \"label\": \"Score\", \"type\": \"number\", \"required\": true, \"validate\": { \"min\": 1, \"max\": 10 } },\n { \"key\": \"verdict\", \"label\": \"Verdict\", \"type\": \"select\", \"required\": true, \"options\": [\"approve\", \"reject\"] },\n { \"key\": \"notes\", \"label\": \"Notes\", \"type\": \"long-text\" }\n ],\n \"submitLabel\": \"Approve\",\n \"secondaryAction\": { \"key\": \"reject\", \"label\": \"Reject\" }\n}\nField types: text, long-text, number, boolean, date, select, multi-select, file.\nA form with a \\`secondaryAction\\` and no required-unfilled fields renders as one-tap Approve/Reject buttons in chat.\n\n## Templates\nEvery prompt + config value can reference \\`{{nodeId.path}}\\` against the run context. Examples:\n {{trigger.values.amount}} — field from the start form\n {{classify.result}} — parsed RESULT: token from an agent node\n {{review.values.score}} — submitted userTask field\n {{env.GITLAB_TOKEN}} — env var (must be listed in workflow.envAllow)\n\n## Edges\n[{ \"from\": \"nodeId\", \"to\": \"nodeId\" }, { \"from\": \"branchId\", \"fromPort\": \"case-label\", \"to\": \"nodeId\" }]\nEvery edge's from/to must reference an existing node. Branch + rule nodes MUST have outgoing edges whose fromPort matches a declared case/rule/default.\n\n## Workflow shell\n{\n \"id\": \"lower-kebab-id\",\n \"version\": 2,\n \"title\": \"Human readable\",\n \"description\": \"optional\",\n \"priority\": 0,\n \"fanOut\": false,\n \"envAllow\": [],\n \"maxChildDepth\": 5,\n \"retention\": { \"maxRuns\": 500, \"maxDays\": 90 },\n \"nodes\": [...],\n \"edges\": [...]\n}\n\n## Lint rules (all enforced on save)\n- exactly one trigger.* node\n- every edge references an existing node\n- every node is reachable from the trigger\n- at least one end/checkpoint/userTask/subProcess/signal.wait/timer.boundary is reachable\n- branch + rule outgoing fromPort values match declared cases (or default)\n- cycles are only allowed when the cycle crosses an agent/checkpoint/userTask/subProcess/signal.wait/timer.boundary`\n\nconst RESPONSE_RULES = `# Your reply\n\nRespond in ONE of two ways:\n\n1. **Clarification**: if the user's request is ambiguous, ask exactly ONE short follow-up question. No JSON.\n\n2. **Workflow**: if you have enough to produce a workflow, reply with:\n - 1–3 short lines explaining what you built\n - a \\`\\`\\`json code block containing the complete, valid V2 workflow\n - (no prose after the JSON block)\n\nOnly reference agent ids, actor ids, role ids, and channel names that\nappear in the Environment section above. If the user asks for an agent\nthat doesn't exist, tell them which agent to register first. Never\ninvent workflowIds for subProcess that aren't in the existing list.`"],"mappings":"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","names":["buildWorkflowAuthorPrompt","args","workflows","w","actorIds","a","roleIds","r","parts","SYSTEM_HEADER","NODE_CATALOG","id","c","m","tag","RESPONSE_RULES","extractWorkflowJson","reply","fenced","candidates","braceMatch","raw","parsed"]}
|