agentix-cli 0.15.0 → 0.17.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 +12 -6
- package/dist/agent-4AOMVXRJ.js +2 -0
- package/dist/board-dashboard-CXIVJKDP.js +7282 -0
- package/dist/board-dashboard-CXIVJKDP.js.map +1 -0
- package/dist/chunk-34NG7UDX.js +11 -0
- package/dist/chunk-34NG7UDX.js.map +1 -0
- package/dist/chunk-4HP6UC2X.js +13 -0
- package/dist/chunk-4HP6UC2X.js.map +1 -0
- package/dist/chunk-5F3J55LG.js +2 -0
- package/dist/chunk-5F3J55LG.js.map +1 -0
- package/dist/chunk-5WNGHDFC.js +164 -0
- package/dist/chunk-5WNGHDFC.js.map +1 -0
- package/dist/{chunk-7MBTMCKY.js → chunk-63UYRJIC.js} +2 -2
- package/dist/chunk-64P4QHSZ.js +2 -0
- package/dist/chunk-64P4QHSZ.js.map +1 -0
- package/dist/chunk-6PPIFDOQ.js +1742 -0
- package/dist/chunk-6PPIFDOQ.js.map +1 -0
- package/dist/chunk-ABCHHNG6.js +625 -0
- package/dist/chunk-ABCHHNG6.js.map +1 -0
- package/dist/chunk-AFRHKA7B.js +2 -0
- package/dist/chunk-AFRHKA7B.js.map +1 -0
- package/dist/chunk-BFZUG66X.js +45 -0
- package/dist/chunk-BFZUG66X.js.map +1 -0
- package/dist/chunk-CDNWCYF7.js +2 -0
- package/dist/chunk-CDNWCYF7.js.map +1 -0
- package/dist/{chunk-6MKAXEE5.js → chunk-EKTPCSN4.js} +2 -2
- package/dist/{chunk-6MKAXEE5.js.map → chunk-EKTPCSN4.js.map} +1 -1
- package/dist/chunk-EW27SCCC.js +2 -0
- package/dist/chunk-EW27SCCC.js.map +1 -0
- package/dist/chunk-HCFSHGK4.js +46 -0
- package/dist/chunk-HCFSHGK4.js.map +1 -0
- package/dist/chunk-HHJGKD5B.js +71 -0
- package/dist/chunk-HHJGKD5B.js.map +1 -0
- package/dist/chunk-LEIYC3BF.js +104 -0
- package/dist/chunk-LEIYC3BF.js.map +1 -0
- package/dist/{chunk-HLRVDCNH.js → chunk-MBEQASP3.js} +2 -2
- package/dist/chunk-N67JZAXU.js +2 -0
- package/dist/chunk-N67JZAXU.js.map +1 -0
- package/dist/{chunk-RGUYBH77.js → chunk-SRHLXZRF.js} +2 -2
- package/dist/chunk-TT2Q7YMZ.js +118 -0
- package/dist/chunk-TT2Q7YMZ.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-WZEYII2S.js +6 -0
- package/dist/chunk-WZEYII2S.js.map +1 -0
- package/dist/chunk-XDJSYQST.js +6 -0
- package/dist/chunk-XDJSYQST.js.map +1 -0
- package/dist/chunk-YPOELN6Z.js +798 -0
- package/dist/chunk-YPOELN6Z.js.map +1 -0
- package/dist/{chunk-QZP65I2H.js → chunk-ZE2YFX4K.js} +2 -2
- package/dist/cli.js +1 -302
- package/dist/cli.js.map +1 -1
- package/dist/{compaction-JBZZ7I7Y.js → compaction-X46EQEPS.js} +6 -6
- package/dist/compaction-X46EQEPS.js.map +1 -0
- package/dist/{config-DCJYECL4.js → config-KCYHMKCT.js} +2 -2
- package/dist/config-mutator-QP4GLBOE.js +2 -0
- package/dist/context-planner-G2J7FNNU.js +17 -0
- package/dist/context-planner-G2J7FNNU.js.map +1 -0
- package/dist/editor-chat-R54E65W5.js +137 -0
- package/dist/editor-chat-R54E65W5.js.map +1 -0
- package/dist/heal-JDXUPRET.js +2 -0
- package/dist/inbox-L5ZZ623Y.js +315 -0
- package/dist/inbox-L5ZZ623Y.js.map +1 -0
- package/dist/index.d.ts +1996 -18
- package/dist/index.js +22 -49
- package/dist/index.js.map +1 -1
- package/dist/ingest-whatsapp-NS4OWDOU.js +6 -0
- package/dist/ingest-whatsapp-NS4OWDOU.js.map +1 -0
- package/dist/memory-extract-WH4FZ456.js +2 -0
- package/dist/processes-XF5CV4LK.js +256 -0
- package/dist/processes-XF5CV4LK.js.map +1 -0
- package/dist/program-FFWEILAS.js +2 -0
- package/dist/providers-AKQ4HTB4.js +2 -0
- package/dist/query-PKGYE5HR.js +2 -0
- package/dist/registry-IFMDJWQ7.js +2 -0
- package/dist/scheduler-63D5KFO7.js +2 -0
- package/dist/scheduler-63D5KFO7.js.map +1 -0
- package/dist/slack-PSQLVTGB.js +3 -0
- package/dist/slack-PSQLVTGB.js.map +1 -0
- package/dist/store-5MJOO74U.js +2 -0
- package/dist/store-5MJOO74U.js.map +1 -0
- package/dist/task-store-NM7VKNLE.js +2 -0
- package/dist/task-store-NM7VKNLE.js.map +1 -0
- package/dist/telegram-W4SLFMNY.js +3 -0
- package/dist/telegram-W4SLFMNY.js.map +1 -0
- package/dist/timers-PCB4ZIBD.js +2 -0
- package/dist/timers-PCB4ZIBD.js.map +1 -0
- package/dist/types-C7IFH42Z.js +2 -0
- package/dist/types-C7IFH42Z.js.map +1 -0
- package/dist/types-OIFU3KSQ.js +2 -0
- package/dist/types-OIFU3KSQ.js.map +1 -0
- package/dist/usage-dashboard-AXPXB2KP.js +803 -0
- package/dist/usage-dashboard-AXPXB2KP.js.map +1 -0
- package/dist/web/workflow-editor.global.js +1427 -0
- package/dist/web/workflow-editor.global.js.map +1 -0
- package/dist/whatsapp-DCHRBRVV.js +3 -0
- package/dist/whatsapp-DCHRBRVV.js.map +1 -0
- package/dist/wiki-UDPYFJBL.js +2 -0
- package/dist/wiki-UDPYFJBL.js.map +1 -0
- package/dist/workspace-setup-C4VB3QZV.js +2 -0
- package/dist/workspace-setup-C4VB3QZV.js.map +1 -0
- package/package.json +10 -3
- package/dist/agent-LRSSVFDR.js +0 -2
- package/dist/board-dashboard-DAPHL6W2.js +0 -4508
- package/dist/board-dashboard-DAPHL6W2.js.map +0 -1
- package/dist/chunk-3XULOWYB.js +0 -199
- package/dist/chunk-3XULOWYB.js.map +0 -1
- package/dist/chunk-6U2XOQHU.js +0 -7
- package/dist/chunk-6U2XOQHU.js.map +0 -1
- package/dist/chunk-AU37OREL.js +0 -108
- package/dist/chunk-AU37OREL.js.map +0 -1
- package/dist/chunk-B4Q4X7D5.js +0 -6
- package/dist/chunk-B4Q4X7D5.js.map +0 -1
- package/dist/chunk-CXY2RYNI.js +0 -11
- package/dist/chunk-CXY2RYNI.js.map +0 -1
- package/dist/chunk-IEVEMKAI.js +0 -168
- package/dist/chunk-IEVEMKAI.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-RB7265LO.js +0 -12
- package/dist/chunk-RB7265LO.js.map +0 -1
- package/dist/chunk-WVNSWBCU.js +0 -138
- package/dist/chunk-WVNSWBCU.js.map +0 -1
- package/dist/compaction-JBZZ7I7Y.js.map +0 -1
- package/dist/config-mutator-6EJHSP3Y.js +0 -2
- package/dist/heal-WKY5I4KB.js +0 -2
- package/dist/memory-extract-AD7KMRPC.js +0 -2
- package/dist/providers-VUIWO3ZK.js +0 -2
- package/dist/registry-5ND7GLFJ.js +0 -2
- package/dist/scheduler-FCXOXCN5.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-LRSSVFDR.js.map → agent-4AOMVXRJ.js.map} +0 -0
- /package/dist/{chunk-7MBTMCKY.js.map → chunk-63UYRJIC.js.map} +0 -0
- /package/dist/{chunk-HLRVDCNH.js.map → chunk-MBEQASP3.js.map} +0 -0
- /package/dist/{chunk-RGUYBH77.js.map → chunk-SRHLXZRF.js.map} +0 -0
- /package/dist/{chunk-QZP65I2H.js.map → chunk-ZE2YFX4K.js.map} +0 -0
- /package/dist/{config-DCJYECL4.js.map → config-KCYHMKCT.js.map} +0 -0
- /package/dist/{config-mutator-6EJHSP3Y.js.map → config-mutator-QP4GLBOE.js.map} +0 -0
- /package/dist/{heal-WKY5I4KB.js.map → heal-JDXUPRET.js.map} +0 -0
- /package/dist/{memory-extract-AD7KMRPC.js.map → memory-extract-WH4FZ456.js.map} +0 -0
- /package/dist/{providers-VUIWO3ZK.js.map → program-FFWEILAS.js.map} +0 -0
- /package/dist/{registry-5ND7GLFJ.js.map → providers-AKQ4HTB4.js.map} +0 -0
- /package/dist/{scheduler-FCXOXCN5.js.map → query-PKGYE5HR.js.map} +0 -0
- /package/dist/{workspace-setup-ZWTT42XT.js.map → registry-IFMDJWQ7.js.map} +0 -0
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{existsSync as a,mkdirSync as d,readdirSync as l,readFileSync as h,unlinkSync as o,writeFileSync as m}from"fs";import{resolve as s}from"path";import{randomUUID as f}from"crypto";import{z as n}from"zod";var u=n.object({id:n.string(),runId:n.string(),workflowId:n.string(),nodeId:n.string(),fireAt:n.string(),cancelKey:n.string().optional(),createdAt:n.string()}),c=class{baseDir;tickIntervalMs;log;timer;callback;firedIds=new Set;constructor(e={}){let t=e.baseDir??s(process.cwd(),".agentx/workflows");this.baseDir=s(t,"_timers"),d(this.baseDir,{recursive:!0}),this.tickIntervalMs=e.tickIntervalMs??1e3,this.log=e.log??(()=>{})}onFire(e){this.callback=e}start(){this.timer||(this.log(`[timers] ticking every ${this.tickIntervalMs}ms \u2014 scanning ${this.baseDir}`),this.tick(),this.timer=setInterval(()=>this.tick(),this.tickIntervalMs),typeof this.timer.unref=="function"&&this.timer.unref())}stop(){this.timer&&(clearInterval(this.timer),this.timer=void 0)}schedule(e){let t={id:f(),runId:e.runId,workflowId:e.workflowId,nodeId:e.nodeId,fireAt:e.fireAt,cancelKey:e.cancelKey,createdAt:new Date().toISOString()};return m(this.pathFor(t.id),JSON.stringify(t,null,2)),t}cancel(e){let t=0;for(let i of this.list())if(!(e.runId&&i.runId!==e.runId)&&!(e.nodeId&&i.nodeId!==e.nodeId)&&!(e.cancelKey&&i.cancelKey!==e.cancelKey))try{o(this.pathFor(i.id)),t++}catch{}return t}list(){if(!a(this.baseDir))return[];let e=[];for(let t of l(this.baseDir,{withFileTypes:!0}))if(!(!t.isFile()||!t.name.endsWith(".json")))try{let i=JSON.parse(h(s(this.baseDir,t.name),"utf-8")),r=u.safeParse(i);r.success&&e.push(r.data)}catch{}return e}pathFor(e){return s(this.baseDir,`${e}.json`)}tick(){if(!this.callback)return;let e=Date.now();for(let t of this.list()){if(this.firedIds.has(t.id))continue;let i=Date.parse(t.fireAt);if(!(Number.isNaN(i)||i>e)){this.firedIds.add(t.id);try{o(this.pathFor(t.id))}catch{}try{Promise.resolve(this.callback(t)).catch(r=>this.log(`[timers] fire callback threw: ${r?.message??r}`))}catch(r){this.log(`[timers] fire dispatch failed: ${r?.message??r}`)}}}}};export{u as a,c as b};
|
|
2
|
+
//# sourceMappingURL=chunk-AFRHKA7B.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/workflows/timers.ts"],"sourcesContent":["import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { randomUUID } from \"crypto\"\nimport { z } from \"zod\"\n\n// --- Durable timer service ---\n//\n// Persists pending timers under .agentx/workflows/_timers/<id>.json so a\n// daemon restart doesn't forget them. A 1s tick loop fires due timers by\n// invoking a registered callback. The workflow dispatcher registers its\n// resume-on-timer callback at boot.\n//\n// Timer model — v1:\n// - Each timer is keyed to a (runId, nodeId) pair so `timer.boundary`\n// nodes can be cancelled when the attached node completes before the\n// timer elapses.\n// - Fire-once semantics. No repeating timers at this layer (use\n// trigger.cron at the workflow level for recurrence).\n// - Best-effort delivery: a missed tick (daemon crash, clock skew) is\n// honoured on next startup by scanning persisted timers with fireAt\n// in the past.\n\nexport const timerRecordSchema = z.object({\n id: z.string(),\n runId: z.string(),\n workflowId: z.string(),\n nodeId: z.string(),\n /** ISO-8601 instant when the timer should fire. */\n fireAt: z.string(),\n /** Optional key to cancel matching timers (e.g., when the attached node\n * completes normally and the escalation timer should be cancelled). */\n cancelKey: z.string().optional(),\n createdAt: z.string(),\n})\nexport type TimerRecord = z.infer<typeof timerRecordSchema>\n\nexport interface TimerCallback {\n (t: TimerRecord): void | Promise<void>\n}\n\nexport interface TimerServiceOptions {\n baseDir?: string\n /** Tick interval, in ms. Default 1000. */\n tickIntervalMs?: number\n log?: (msg: string) => void\n}\n\nexport class TimerService {\n readonly baseDir: string\n private readonly tickIntervalMs: number\n private readonly log: (msg: string) => void\n private timer?: ReturnType<typeof setInterval>\n private callback?: TimerCallback\n private firedIds: Set<string> = new Set()\n\n constructor(opts: TimerServiceOptions = {}) {\n const root = opts.baseDir ?? resolve(process.cwd(), \".agentx/workflows\")\n this.baseDir = resolve(root, \"_timers\")\n mkdirSync(this.baseDir, { recursive: true })\n this.tickIntervalMs = opts.tickIntervalMs ?? 1000\n this.log = opts.log ?? (() => {})\n }\n\n /** Register the fire callback. Overwrites any prior callback. Typical\n * caller is the dispatcher's \"resume on timer\" path. */\n onFire(cb: TimerCallback): void {\n this.callback = cb\n }\n\n start(): void {\n if (this.timer) return\n this.log(`[timers] ticking every ${this.tickIntervalMs}ms — scanning ${this.baseDir}`)\n // Honour missed ticks on startup: scan all persisted timers once.\n this.tick()\n this.timer = setInterval(() => this.tick(), this.tickIntervalMs)\n // Don't hold the event loop open on daemon shutdown.\n if (typeof this.timer.unref === \"function\") this.timer.unref()\n }\n\n stop(): void {\n if (!this.timer) return\n clearInterval(this.timer)\n this.timer = undefined\n }\n\n schedule(args: {\n runId: string\n workflowId: string\n nodeId: string\n fireAt: string\n cancelKey?: string\n }): TimerRecord {\n const record: TimerRecord = {\n id: randomUUID(),\n runId: args.runId,\n workflowId: args.workflowId,\n nodeId: args.nodeId,\n fireAt: args.fireAt,\n cancelKey: args.cancelKey,\n createdAt: new Date().toISOString(),\n }\n writeFileSync(this.pathFor(record.id), JSON.stringify(record, null, 2))\n return record\n }\n\n /** Cancel any timers matching the filter. Returns how many were removed. */\n cancel(filter: { runId?: string; nodeId?: string; cancelKey?: string }): number {\n let removed = 0\n for (const t of this.list()) {\n if (filter.runId && t.runId !== filter.runId) continue\n if (filter.nodeId && t.nodeId !== filter.nodeId) continue\n if (filter.cancelKey && t.cancelKey !== filter.cancelKey) continue\n try { unlinkSync(this.pathFor(t.id)); removed++ } catch { /* already gone */ }\n }\n return removed\n }\n\n list(): TimerRecord[] {\n if (!existsSync(this.baseDir)) return []\n const out: TimerRecord[] = []\n for (const entry of readdirSync(this.baseDir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".json\")) continue\n try {\n const raw = JSON.parse(readFileSync(resolve(this.baseDir, entry.name), \"utf-8\"))\n const parsed = timerRecordSchema.safeParse(raw)\n if (parsed.success) out.push(parsed.data)\n } catch { /* skip malformed */ }\n }\n return out\n }\n\n private pathFor(id: string): string {\n return resolve(this.baseDir, `${id}.json`)\n }\n\n private tick(): void {\n if (!this.callback) return\n const now = Date.now()\n for (const t of this.list()) {\n if (this.firedIds.has(t.id)) continue\n const fireAtMs = Date.parse(t.fireAt)\n if (Number.isNaN(fireAtMs) || fireAtMs > now) continue\n // Mark fired BEFORE calling the callback so a callback that loops\n // back into the walk (and re-schedules siblings) doesn't re-trigger.\n this.firedIds.add(t.id)\n try { unlinkSync(this.pathFor(t.id)) } catch { /* may race with cancel */ }\n try {\n void Promise.resolve(this.callback(t)).catch((e: any) => this.log(`[timers] fire callback threw: ${e?.message ?? e}`))\n } catch (e: any) {\n this.log(`[timers] fire dispatch failed: ${e?.message ?? e}`)\n }\n }\n }\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","writeFileSync","resolve","randomUUID","z","timerRecordSchema","TimerService","opts","root","cb","args","record","filter","removed","t","out","entry","raw","parsed","id","now","fireAtMs","e"]}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import{readFileSync as v,existsSync as O}from"fs";import{resolve as k}from"path";import{execSync as C}from"child_process";async function j(r,n,i,t={}){let h=t.selectorModel??"haiku",l=t.synthModel??"sonnet",s=t.maxCandidates??3,o=t.maxHops??2,e=t.maxArticles??8,u=t.timeoutMs??6e4,p=t.log??console.error.bind(console,"[wiki-query]"),c=k(n.baseDir,"_index.md");if(!O(c))return y("no-catalog",r,"No _index.md found \u2014 run `agentx wiki status` to rebuild.");let M=v(c,"utf-8"),A=Date.now(),d=[],m="";try{m=await x(D(r,M,s),h,u),d=Q(m)}catch(a){return p("selector failed:",a?.message),{...y("error",r,`Selector: ${a?.message||"unknown"}`),trace:{selectorMs:Date.now()-A,synthesisMs:0,selectorOutput:m}}}let f=Date.now()-A;if(d.length===0)return{...y("no-candidates",r,"Selector returned no candidates."),trace:{selectorMs:f,synthesisMs:0,selectorOutput:m}};let g=W(d,n,i,o,e);if(g.length===0)return{...y("no-candidates",r,"Candidates did not resolve to readable articles."),candidates:d,trace:{selectorMs:f,synthesisMs:0,selectorOutput:m}};let b=Date.now(),S="";try{S=await x(P(r,g),l,u*2)}catch(a){return p("synthesis failed:",a?.message),{...y("error",r,`Synthesis: ${a?.message||"unknown"}`),candidates:d,walked:g.map(w=>({title:w.meta.title,path:w.path,type:w.meta.type,hop:w.hop??0})),trace:{selectorMs:f,synthesisMs:Date.now()-b,selectorOutput:m}}}let $=Date.now()-b;return{answer:S.trim(),citations:g.map(a=>({title:a.meta.title,path:a.path,type:a.meta.type})),candidates:d,walked:g.map(a=>({title:a.meta.title,path:a.path,type:a.meta.type,hop:a.hop??0})),status:"ok",trace:{selectorMs:f,synthesisMs:$,selectorOutput:m}}}function y(r,n,i){return{answer:"",citations:[],candidates:[],walked:[],status:r,error:i}}function W(r,n,i,t,h){let l=new Map;for(let e of n.listArticles(i||""))l.set(e.meta.title.toLowerCase(),e.path);let s=new Map,o=[];for(let e of r)s.has(e.path)||o.push({path:e.path,hop:0});for(;o.length&&s.size<h;){let e=o.shift();if(s.has(e.path)||e.hop>t)continue;let u=i?n.readArticleAs(e.path,i):n.readArticle(e.path);if(u&&(s.set(e.path,{...u,hop:e.hop}),!(e.hop>=t)))for(let p of u.meta.related||[]){let c=l.get(p.toLowerCase());c&&!s.has(c)&&o.push({path:c,hop:e.hop+1})}}return Array.from(s.values())}function D(r,n,i){return`You are picking candidate articles from a wiki catalog to answer a question. You do NOT answer the question. You pick which articles the answer is likely to come from.
|
|
2
|
+
|
|
3
|
+
## Question
|
|
4
|
+
|
|
5
|
+
${r}
|
|
6
|
+
|
|
7
|
+
## Catalog (_index.md \u2014 articles grouped by type, with wikilink previews)
|
|
8
|
+
|
|
9
|
+
${n}
|
|
10
|
+
|
|
11
|
+
## Your task
|
|
12
|
+
|
|
13
|
+
Pick up to ${i} articles from the catalog that are most likely to answer the question. Prefer:
|
|
14
|
+
- Articles whose title directly names a subject in the question.
|
|
15
|
+
- Articles of the right type (e.g. a "who owns X" question \u2192 person or project; a "what happened on DATE" \u2192 event).
|
|
16
|
+
- Articles whose wikilinks suggest they sit at the center of a relevant subgraph.
|
|
17
|
+
|
|
18
|
+
Return ONLY valid JSON, no markdown fencing, no prose:
|
|
19
|
+
|
|
20
|
+
[
|
|
21
|
+
{"title": "Exact Article Title", "path": "path/from/catalog.md"},
|
|
22
|
+
...
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
If none of the articles match, return [].`}function P(r,n){let i=n.map(t=>{let h=`### ${t.meta.title} \u2014 ${t.meta.type||"untyped"} (${t.path}) [hop ${t.hop}]`,l=t.meta.related?.length?`Related: ${t.meta.related.join(", ")}`:"";return[h,l,"",t.content].filter(Boolean).join(`
|
|
26
|
+
`)}).join(`
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
`);return`You are answering a question using a small subgraph of wiki articles. The articles were walked from the catalog; trust them over your prior knowledge.
|
|
31
|
+
|
|
32
|
+
## Question
|
|
33
|
+
|
|
34
|
+
${r}
|
|
35
|
+
|
|
36
|
+
## Articles (${n.length} walked from the catalog)
|
|
37
|
+
|
|
38
|
+
${i}
|
|
39
|
+
|
|
40
|
+
## Your task
|
|
41
|
+
|
|
42
|
+
Answer the question in 2\u20136 sentences. Cite articles by their title in square brackets like [Article Title]. If the articles do not answer the question, say so plainly (do not invent). Prefer concrete facts over hedging.
|
|
43
|
+
|
|
44
|
+
Output ONLY the answer \u2014 no preamble, no "here is my answer:", no markdown fencing.`}function Q(r){let n=r.match(/\[[\s\S]*?\]/);if(!n)return[];try{let i=JSON.parse(n[0]);return Array.isArray(i)?i.filter(t=>t&&typeof t.title=="string"&&typeof t.path=="string").map(t=>({title:t.title,path:t.path})):[]}catch{return[]}}async function x(r,n,i){let{writeFileSync:t,mkdirSync:h,rmSync:l}=await import("fs"),{tmpdir:s}=await import("os"),o=k(s(),`agentx-wiki-query-${process.pid}`);h(o,{recursive:!0});let e=k(o,`prompt-${Date.now()}.txt`);t(e,r);try{let u=`cat '${e}' | claude -p - --output-format json --max-turns 1 --model ${n} --disallowedTools "Bash Read Write Edit Glob Grep Agent WebSearch WebFetch NotebookEdit"`,p=C(u,{encoding:"utf-8",timeout:i,maxBuffer:4*1024*1024});try{let c=JSON.parse(p);return String(c.result||c.content||"")}catch{return p}}finally{try{l(e,{force:!0})}catch{}}}export{j as a};
|
|
45
|
+
//# sourceMappingURL=chunk-BFZUG66X.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/wiki/query.ts"],"sourcesContent":["import { readFileSync, existsSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { execSync } from \"child_process\"\nimport type { WikiArticle } from \"./types\"\nimport type { WikiStore } from \"./store\"\n\n/**\n * Agentic wiki query — the \"Farzapedia-faithful\" retrieval path.\n *\n * Two LLM calls + a deterministic graph walk:\n * 1. Selector (cheap): read `_index.md` + question → pick up to `maxCandidates`\n * candidate articles by title + type.\n * 2. Walk (pure code): starting from candidates, follow `related` wikilinks\n * up to `maxHops`, capped at `maxArticles` total.\n * 3. Synthesis (main): answer the question from the walked subgraph.\n *\n * Returns an answer with citations and a trace of which articles were walked,\n * so callers can audit the retrieval path.\n */\n\nexport interface AgenticQueryOptions {\n /** Candidate-selection model (cheap). Defaults to \"haiku\". */\n selectorModel?: string\n /** Synthesis model (main). Defaults to \"sonnet\". */\n synthModel?: string\n /** Number of candidates the selector is asked to return. Default 3. */\n maxCandidates?: number\n /** Wikilink hops from candidates. Default 2. */\n maxHops?: number\n /** Hard cap on total articles walked. Default 8. */\n maxArticles?: number\n /** Timeout per LLM call, ms. Default 60_000. */\n timeoutMs?: number\n /** Logger — defaults to console.error. */\n log?: (...args: unknown[]) => void\n}\n\nexport interface AgenticQueryResult {\n answer: string\n citations: Array<{ title: string; path: string; type?: string }>\n /** Titles/paths the selector picked. */\n candidates: Array<{ title: string; path: string }>\n /** Full article subgraph the walk produced. */\n walked: Array<{ title: string; path: string; type?: string; hop: number }>\n /** \"no-catalog\" | \"no-candidates\" | \"ok\" */\n status: \"ok\" | \"no-catalog\" | \"no-candidates\" | \"error\"\n /** For operator debugging only. */\n trace?: {\n selectorMs: number\n synthesisMs: number\n selectorOutput?: string\n }\n error?: string\n}\n\n/**\n * Run an agentic query against a specific agent's WikiStore.\n *\n * `requesterId` is used for permission checks — only articles the requester\n * can read will be included in the synthesis input.\n */\nexport async function agenticQuery(\n question: string,\n store: WikiStore,\n requesterId: string | undefined,\n opts: AgenticQueryOptions = {},\n): Promise<AgenticQueryResult> {\n const selectorModel = opts.selectorModel ?? \"haiku\"\n const synthModel = opts.synthModel ?? \"sonnet\"\n const maxCandidates = opts.maxCandidates ?? 3\n const maxHops = opts.maxHops ?? 2\n const maxArticles = opts.maxArticles ?? 8\n const timeoutMs = opts.timeoutMs ?? 60_000\n const log = opts.log ?? console.error.bind(console, \"[wiki-query]\")\n\n // --- Step 1: Load the catalog ---\n const catalogPath = resolve(store.baseDir, \"_index.md\")\n if (!existsSync(catalogPath)) {\n return emptyResult(\"no-catalog\", question, \"No _index.md found — run `agentx wiki status` to rebuild.\")\n }\n const catalog = readFileSync(catalogPath, \"utf-8\")\n\n // --- Step 2: Selector — pick candidates ---\n const selectorStart = Date.now()\n let candidates: Array<{ title: string; path: string }> = []\n let selectorOutput = \"\"\n try {\n selectorOutput = await runClaude(\n buildSelectorPrompt(question, catalog, maxCandidates),\n selectorModel,\n timeoutMs,\n )\n candidates = parseCandidates(selectorOutput)\n } catch (e: any) {\n log(\"selector failed:\", e?.message)\n return { ...emptyResult(\"error\", question, `Selector: ${e?.message || \"unknown\"}`), trace: { selectorMs: Date.now() - selectorStart, synthesisMs: 0, selectorOutput } }\n }\n const selectorMs = Date.now() - selectorStart\n\n if (candidates.length === 0) {\n return { ...emptyResult(\"no-candidates\", question, \"Selector returned no candidates.\"), trace: { selectorMs, synthesisMs: 0, selectorOutput } }\n }\n\n // --- Step 3: Walk the subgraph via `related` wikilinks ---\n const walked = walkSubgraph(candidates, store, requesterId, maxHops, maxArticles)\n\n if (walked.length === 0) {\n return { ...emptyResult(\"no-candidates\", question, \"Candidates did not resolve to readable articles.\"), candidates, trace: { selectorMs, synthesisMs: 0, selectorOutput } }\n }\n\n // --- Step 4: Synthesize the answer ---\n const synthStart = Date.now()\n let answer = \"\"\n try {\n answer = await runClaude(\n buildSynthesisPrompt(question, walked),\n synthModel,\n timeoutMs * 2,\n )\n } catch (e: any) {\n log(\"synthesis failed:\", e?.message)\n return { ...emptyResult(\"error\", question, `Synthesis: ${e?.message || \"unknown\"}`), candidates, walked: walked.map(w => ({ title: w.meta.title, path: w.path, type: w.meta.type, hop: (w as any).hop ?? 0 })), trace: { selectorMs, synthesisMs: Date.now() - synthStart, selectorOutput } }\n }\n const synthesisMs = Date.now() - synthStart\n\n return {\n answer: answer.trim(),\n citations: walked.map(a => ({ title: a.meta.title, path: a.path, type: a.meta.type })),\n candidates,\n walked: walked.map(w => ({ title: w.meta.title, path: w.path, type: w.meta.type, hop: (w as any).hop ?? 0 })),\n status: \"ok\",\n trace: { selectorMs, synthesisMs, selectorOutput },\n }\n}\n\n// --- Helpers ---\n\nfunction emptyResult(\n status: AgenticQueryResult[\"status\"],\n _question: string,\n error?: string,\n): AgenticQueryResult {\n return {\n answer: \"\",\n citations: [],\n candidates: [],\n walked: [],\n status,\n error,\n }\n}\n\nfunction walkSubgraph(\n candidates: Array<{ title: string; path: string }>,\n store: WikiStore,\n requesterId: string | undefined,\n maxHops: number,\n maxArticles: number,\n): Array<WikiArticle & { hop: number }> {\n // Build a title → path index from the store so wikilinks resolve.\n const titleIndex = new Map<string, string>()\n for (const article of store.listArticles(requesterId || \"\")) {\n titleIndex.set(article.meta.title.toLowerCase(), article.path)\n }\n\n const opened = new Map<string, WikiArticle & { hop: number }>()\n let frontier: Array<{ path: string; hop: number }> = []\n\n for (const c of candidates) {\n if (!opened.has(c.path)) frontier.push({ path: c.path, hop: 0 })\n }\n\n while (frontier.length && opened.size < maxArticles) {\n const next = frontier.shift()!\n if (opened.has(next.path)) continue\n if (next.hop > maxHops) continue\n\n const article = requesterId\n ? store.readArticleAs(next.path, requesterId)\n : store.readArticle(next.path)\n if (!article) continue\n\n opened.set(next.path, { ...article, hop: next.hop })\n if (next.hop >= maxHops) continue\n\n for (const target of article.meta.related || []) {\n const path = titleIndex.get(target.toLowerCase())\n if (path && !opened.has(path)) {\n frontier.push({ path, hop: next.hop + 1 })\n }\n }\n }\n\n return Array.from(opened.values())\n}\n\n// --- Prompts ---\n\nfunction buildSelectorPrompt(question: string, catalog: string, maxCandidates: number): string {\n return `You are picking candidate articles from a wiki catalog to answer a question. You do NOT answer the question. You pick which articles the answer is likely to come from.\n\n## Question\n\n${question}\n\n## Catalog (_index.md — articles grouped by type, with wikilink previews)\n\n${catalog}\n\n## Your task\n\nPick up to ${maxCandidates} articles from the catalog that are most likely to answer the question. Prefer:\n- Articles whose title directly names a subject in the question.\n- Articles of the right type (e.g. a \"who owns X\" question → person or project; a \"what happened on DATE\" → event).\n- Articles whose wikilinks suggest they sit at the center of a relevant subgraph.\n\nReturn ONLY valid JSON, no markdown fencing, no prose:\n\n[\n {\"title\": \"Exact Article Title\", \"path\": \"path/from/catalog.md\"},\n ...\n]\n\nIf none of the articles match, return [].`\n}\n\nfunction buildSynthesisPrompt(\n question: string,\n articles: Array<WikiArticle & { hop: number }>,\n): string {\n const articlesBlock = articles\n .map(a => {\n const header = `### ${a.meta.title} — ${a.meta.type || \"untyped\"} (${a.path}) [hop ${a.hop}]`\n const related = a.meta.related?.length ? `Related: ${a.meta.related.join(\", \")}` : \"\"\n return [header, related, \"\", a.content].filter(Boolean).join(\"\\n\")\n })\n .join(\"\\n\\n---\\n\\n\")\n\n return `You are answering a question using a small subgraph of wiki articles. The articles were walked from the catalog; trust them over your prior knowledge.\n\n## Question\n\n${question}\n\n## Articles (${articles.length} walked from the catalog)\n\n${articlesBlock}\n\n## Your task\n\nAnswer the question in 2–6 sentences. Cite articles by their title in square brackets like [Article Title]. If the articles do not answer the question, say so plainly (do not invent). Prefer concrete facts over hedging.\n\nOutput ONLY the answer — no preamble, no \"here is my answer:\", no markdown fencing.`\n}\n\nfunction parseCandidates(raw: string): Array<{ title: string; path: string }> {\n const match = raw.match(/\\[[\\s\\S]*?\\]/)\n if (!match) return []\n try {\n const parsed = JSON.parse(match[0])\n if (!Array.isArray(parsed)) return []\n return parsed\n .filter((x: any) => x && typeof x.title === \"string\" && typeof x.path === \"string\")\n .map((x: any) => ({ title: x.title, path: x.path }))\n } catch {\n return []\n }\n}\n\nasync function runClaude(prompt: string, model: string, timeoutMs: number): Promise<string> {\n // Use stdin via a temp-file pipe to avoid shell-escaping a huge prompt.\n // Prompts can exceed the `-E` arg buffer on macOS, so we always pipe.\n const { writeFileSync, mkdirSync, rmSync } = await import(\"fs\")\n const { tmpdir } = await import(\"os\")\n const dir = resolve(tmpdir(), `agentx-wiki-query-${process.pid}`)\n mkdirSync(dir, { recursive: true })\n const promptPath = resolve(dir, `prompt-${Date.now()}.txt`)\n writeFileSync(promptPath, prompt)\n try {\n const cmd = `cat '${promptPath}' | claude -p - --output-format json --max-turns 1 --model ${model} --disallowedTools \"Bash Read Write Edit Glob Grep Agent WebSearch WebFetch NotebookEdit\"`\n const raw = execSync(cmd, { encoding: \"utf-8\", timeout: timeoutMs, maxBuffer: 4 * 1024 * 1024 })\n try {\n const envelope = JSON.parse(raw)\n return String(envelope.result || envelope.content || \"\")\n } catch {\n return raw\n }\n } finally {\n try { rmSync(promptPath, { force: true }) } catch {}\n }\n}\n"],"mappings":"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","names":["readFileSync","existsSync","resolve","execSync","agenticQuery","question","store","requesterId","opts","selectorModel","synthModel","maxCandidates","maxHops","maxArticles","timeoutMs","log","catalogPath","emptyResult","catalog","selectorStart","candidates","selectorOutput","runClaude","buildSelectorPrompt","parseCandidates","e","selectorMs","walked","walkSubgraph","synthStart","answer","buildSynthesisPrompt","synthesisMs","w","status","_question","error","titleIndex","article","opened","frontier","c","next","target","path","articles","articlesBlock","a","header","related","raw","match","parsed","x","prompt","model","writeFileSync","mkdirSync","rmSync","tmpdir","dir","promptPath","cmd","envelope"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{z as e}from"zod";var t=e.enum(["text","long-text","number","boolean","date","select","multi-select","file"]),o=e.object({min:e.number().optional(),max:e.number().optional(),pattern:e.string().optional()}).strict(),i=e.object({key:e.string().min(1).regex(/^[a-zA-Z_][a-zA-Z0-9_-]*$/,"field key must be identifier-safe"),label:e.string().min(1),type:t,required:e.boolean().default(!1),options:e.array(e.string()).optional(),hint:e.string().optional(),defaultValue:e.unknown().optional(),validate:o.optional()}),r=e.object({key:e.string().min(1),label:e.string().min(1)}),a=e.object({id:e.string().optional(),title:e.string().min(1),description:e.string().optional(),fields:e.array(i).default([]),submitLabel:e.string().default("Submit"),secondaryAction:r.optional()}),m=e.object({action:e.enum(["primary","secondary"]).default("primary"),values:e.record(e.unknown()).default({})});export{t as a,o as b,i as c,r as d,a as e,m as f};
|
|
2
|
+
//# sourceMappingURL=chunk-CDNWCYF7.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/forms/types.ts"],"sourcesContent":["import { z } from \"zod\"\n\n// --- Form schema ---\n//\n// Forms are the structured-input primitive for user tasks. A form has a\n// title, a list of typed fields, and optional primary/secondary action\n// labels (for approve/reject-style flows).\n//\n// Forms can live inline on a userTask node's config OR be factored out to\n// .agentx/forms/<id>.json and referenced by id.\n//\n// Rendering happens in channel adapters (Telegram inline keyboards, Slack\n// Block Kit, WhatsApp list messages) and on the web inbox. Capability\n// negotiation is per-renderer — a date field on a channel without native\n// date input falls back to a validated text prompt.\n\nexport const formFieldTypeSchema = z.enum([\n \"text\",\n \"long-text\",\n \"number\",\n \"boolean\",\n \"date\",\n \"select\",\n \"multi-select\",\n \"file\",\n])\nexport type FormFieldType = z.infer<typeof formFieldTypeSchema>\n\nexport const formFieldValidateSchema = z.object({\n min: z.number().optional(),\n max: z.number().optional(),\n pattern: z.string().optional(),\n}).strict()\nexport type FormFieldValidate = z.infer<typeof formFieldValidateSchema>\n\nexport const formFieldSchema = z.object({\n key: z.string().min(1).regex(/^[a-zA-Z_][a-zA-Z0-9_-]*$/, \"field key must be identifier-safe\"),\n label: z.string().min(1),\n type: formFieldTypeSchema,\n required: z.boolean().default(false),\n /** Options for select / multi-select. Ignored for other types. */\n options: z.array(z.string()).optional(),\n hint: z.string().optional(),\n defaultValue: z.unknown().optional(),\n validate: formFieldValidateSchema.optional(),\n})\nexport type FormField = z.infer<typeof formFieldSchema>\n\nexport const formActionSchema = z.object({\n key: z.string().min(1),\n label: z.string().min(1),\n})\nexport type FormAction = z.infer<typeof formActionSchema>\n\nexport const formSchemaSchema = z.object({\n id: z.string().optional(),\n title: z.string().min(1),\n description: z.string().optional(),\n fields: z.array(formFieldSchema).default([]),\n /** Primary button label. Default \"Submit\". */\n submitLabel: z.string().default(\"Submit\"),\n /** Optional secondary action — used for approve/reject-style flows. */\n secondaryAction: formActionSchema.optional(),\n})\nexport type FormSchema = z.infer<typeof formSchemaSchema>\n\n// --- Submission ---\n\nexport const formSubmissionSchema = z.object({\n /** Which button the user clicked: \"primary\" (default) or \"secondary\". */\n action: z.enum([\"primary\", \"secondary\"]).default(\"primary\"),\n /** Field values keyed by field.key. Types match FormField.type. */\n values: z.record(z.unknown()).default({}),\n})\nexport type FormSubmission = z.infer<typeof formSubmissionSchema>\n"],"mappings":"AAAA,OAAS,KAAAA,MAAS,MAgBX,IAAMC,EAAsBD,EAAE,KAAK,CACxC,OACA,YACA,SACA,UACA,OACA,SACA,eACA,MACF,CAAC,EAGYE,EAA0BF,EAAE,OAAO,CAC9C,IAAKA,EAAE,OAAO,EAAE,SAAS,EACzB,IAAKA,EAAE,OAAO,EAAE,SAAS,EACzB,QAASA,EAAE,OAAO,EAAE,SAAS,CAC/B,CAAC,EAAE,OAAO,EAGGG,EAAkBH,EAAE,OAAO,CACtC,IAAKA,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,MAAM,4BAA6B,mCAAmC,EAC7F,MAAOA,EAAE,OAAO,EAAE,IAAI,CAAC,EACvB,KAAMC,EACN,SAAUD,EAAE,QAAQ,EAAE,QAAQ,EAAK,EAEnC,QAASA,EAAE,MAAMA,EAAE,OAAO,CAAC,EAAE,SAAS,EACtC,KAAMA,EAAE,OAAO,EAAE,SAAS,EAC1B,aAAcA,EAAE,QAAQ,EAAE,SAAS,EACnC,SAAUE,EAAwB,SAAS,CAC7C,CAAC,EAGYE,EAAmBJ,EAAE,OAAO,CACvC,IAAKA,EAAE,OAAO,EAAE,IAAI,CAAC,EACrB,MAAOA,EAAE,OAAO,EAAE,IAAI,CAAC,CACzB,CAAC,EAGYK,EAAmBL,EAAE,OAAO,CACvC,GAAIA,EAAE,OAAO,EAAE,SAAS,EACxB,MAAOA,EAAE,OAAO,EAAE,IAAI,CAAC,EACvB,YAAaA,EAAE,OAAO,EAAE,SAAS,EACjC,OAAQA,EAAE,MAAMG,CAAe,EAAE,QAAQ,CAAC,CAAC,EAE3C,YAAaH,EAAE,OAAO,EAAE,QAAQ,QAAQ,EAExC,gBAAiBI,EAAiB,SAAS,CAC7C,CAAC,EAKYE,EAAuBN,EAAE,OAAO,CAE3C,OAAQA,EAAE,KAAK,CAAC,UAAW,WAAW,CAAC,EAAE,QAAQ,SAAS,EAE1D,OAAQA,EAAE,OAAOA,EAAE,QAAQ,CAAC,EAAE,QAAQ,CAAC,CAAC,CAC1C,CAAC","names":["z","formFieldTypeSchema","formFieldValidateSchema","formFieldSchema","formActionSchema","formSchemaSchema","formSubmissionSchema"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{a as w,b as y,c as x}from"./chunk-SFQUP3BP.js";import{g}from"./chunk-
|
|
1
|
+
import{a as w,b as y,c as x}from"./chunk-SFQUP3BP.js";import{m as g}from"./chunk-HCFSHGK4.js";import{existsSync as d,promises as h}from"fs";import c from"path";import{execa as C}from"execa";var b={enabled:!0,maxAttempts:3,provider:"claude-code"},v=class{constructor(e,r,i){this.cwd=e;this.config={...b,...r},this.memory=i||new x(e)}config;memory;async detectAndHeal(e,r,i){if(!this.config.enabled)return{healed:!1,attempts:0,error:"Heal disabled",filesChanged:[]};let t=await this.runVerification();if(!t)return{healed:!0,attempts:0,error:"",filesChanged:[]};let n=0,s=t,a=[];for(;n<this.config.maxAttempts;){n++;let l=await this.generateFix(s,e,r,n);if(!l.files.length)break;for(let o of l.files){let m=c.isAbsolute(o.path)?o.path:c.resolve(this.cwd,o.path),p=c.dirname(m);await h.mkdir(p,{recursive:!0}),await h.writeFile(m,o.content,"utf8"),a.push(o.path)}let f=await this.runVerification();if(!f)return i&&await this.memory.recordHeal(i,t,!0,a),{healed:!0,attempts:n,error:t,fix:l.explanation,filesChanged:a};s=f}return i&&await this.memory.recordHeal(i,t,!1,a),{healed:!1,attempts:n,error:s,filesChanged:a}}async runVerification(){let e=[this.config.buildCommand,this.config.lintCommand,this.config.testCommand].filter(Boolean);if(!e.length){let r=await this.detectCommands();e.push(...r)}for(let r of e)try{let[i,...t]=r.split(" ");await C(i,t,{cwd:this.cwd,timeout:6e4,reject:!0})}catch(i){let t=i.stderr||"",n=i.stdout||"",s=i.message||"";return k(`Command failed: ${r}
|
|
2
2
|
${t}
|
|
3
3
|
${n}
|
|
4
4
|
${s}`)}return null}async detectCommands(){let e=[],r=c.resolve(this.cwd,"package.json");if(d(r))try{let t=JSON.parse(await h.readFile(r,"utf8")).scripts||{};t.build&&e.push("npm run build"),t.typecheck&&e.push("npm run typecheck"),t.lint&&e.push("npm run lint"),t.test&&e.push("npm run test")}catch{}return d(c.resolve(this.cwd,"pyproject.toml"))&&d(c.resolve(this.cwd,"conftest.py"))&&e.push("python -m pytest --tb=short -q"),e}async generateFix(e,r,i,t){let n=g(this.config.provider,this.config.apiKey),s=[];for(let o of r.slice(0,5)){let m=c.resolve(this.cwd,o);if(d(m)){let p=await h.readFile(m,"utf8");s.push(`## ${o}
|
|
@@ -29,4 +29,4 @@ Fix the error. Use create_files with the corrected file content.`}],f=await n.ge
|
|
|
29
29
|
`)+`
|
|
30
30
|
... (truncated)`:e.join(`
|
|
31
31
|
`)}export{v as a};
|
|
32
|
-
//# sourceMappingURL=chunk-
|
|
32
|
+
//# sourceMappingURL=chunk-EKTPCSN4.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/runtime/heal.ts"],"sourcesContent":["import { existsSync, promises as fs } from \"fs\"\nimport path from \"path\"\nimport { execa } from \"execa\"\nimport { createProvider, type ProviderName } from \"@/agent/providers\"\nimport type { GenerationMessage, GeneratedFile } from \"@/agent/providers/types\"\nimport { detectTechStack, formatTechStack } from \"@/agent/context/tech-stack\"\nimport { Memory } from \"./memory\"\n\n// --- Auto-Heal Engine: detect errors, diagnose, fix, verify ---\n// When generated code fails (build error, test failure, runtime crash),\n// the heal engine reads the error, understands the context, generates\n// a fix, and re-verifies it works.\n\nexport interface HealConfig {\n enabled: boolean\n testCommand?: string\n buildCommand?: string\n lintCommand?: string\n maxAttempts: number\n provider: ProviderName\n model?: string\n apiKey?: string\n}\n\nexport interface HealResult {\n healed: boolean\n attempts: number\n error: string\n fix?: string\n filesChanged: string[]\n}\n\nconst DEFAULT_HEAL_CONFIG: HealConfig = {\n enabled: true,\n maxAttempts: 3,\n provider: \"claude-code\",\n}\n\nexport class HealEngine {\n private config: HealConfig\n private memory: Memory\n\n constructor(\n private cwd: string,\n config?: Partial<HealConfig>,\n memory?: Memory\n ) {\n this.config = { ...DEFAULT_HEAL_CONFIG, ...config }\n this.memory = memory || new Memory(cwd)\n }\n\n async detectAndHeal(\n generatedFiles: string[],\n originalTask: string,\n entryId?: string\n ): Promise<HealResult> {\n if (!this.config.enabled) {\n return { healed: false, attempts: 0, error: \"Heal disabled\", filesChanged: [] }\n }\n\n // 1. Run verification commands\n const error = await this.runVerification()\n if (!error) {\n return { healed: true, attempts: 0, error: \"\", filesChanged: [] }\n }\n\n // 2. Attempt to heal\n let attempts = 0\n let currentError = error\n const allChangedFiles: string[] = []\n\n while (attempts < this.config.maxAttempts) {\n attempts++\n\n const fix = await this.generateFix(\n currentError,\n generatedFiles,\n originalTask,\n attempts\n )\n\n if (!fix.files.length) {\n break // Agent couldn't generate a fix\n }\n\n // Apply the fix\n for (const file of fix.files) {\n const filePath = path.isAbsolute(file.path)\n ? file.path\n : path.resolve(this.cwd, file.path)\n\n const dir = path.dirname(filePath)\n await fs.mkdir(dir, { recursive: true })\n await fs.writeFile(filePath, file.content, \"utf8\")\n allChangedFiles.push(file.path)\n }\n\n // Re-verify\n const newError = await this.runVerification()\n if (!newError) {\n // Healed successfully\n if (entryId) {\n await this.memory.recordHeal(entryId, error, true, allChangedFiles)\n }\n return {\n healed: true,\n attempts,\n error,\n fix: fix.explanation,\n filesChanged: allChangedFiles,\n }\n }\n\n currentError = newError\n }\n\n // Failed to heal\n if (entryId) {\n await this.memory.recordHeal(entryId, error, false, allChangedFiles)\n }\n\n return {\n healed: false,\n attempts,\n error: currentError,\n filesChanged: allChangedFiles,\n }\n }\n\n private async runVerification(): Promise<string | null> {\n const commands = [\n this.config.buildCommand,\n this.config.lintCommand,\n this.config.testCommand,\n ].filter(Boolean) as string[]\n\n // Auto-detect commands if not configured\n if (!commands.length) {\n const detected = await this.detectCommands()\n commands.push(...detected)\n }\n\n for (const cmd of commands) {\n try {\n const [bin, ...args] = cmd.split(\" \")\n await execa(bin, args, {\n cwd: this.cwd,\n timeout: 60000,\n reject: true,\n })\n } catch (error: any) {\n const stderr = error.stderr || \"\"\n const stdout = error.stdout || \"\"\n const message = error.message || \"\"\n return truncateError(`Command failed: ${cmd}\\n${stderr}\\n${stdout}\\n${message}`)\n }\n }\n\n return null // All passed\n }\n\n private async detectCommands(): Promise<string[]> {\n const commands: string[] = []\n const pkgPath = path.resolve(this.cwd, \"package.json\")\n\n if (existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(await fs.readFile(pkgPath, \"utf8\"))\n const scripts = pkg.scripts || {}\n if (scripts.build) commands.push(\"npm run build\")\n if (scripts.typecheck) commands.push(\"npm run typecheck\")\n if (scripts.lint) commands.push(\"npm run lint\")\n if (scripts.test) commands.push(\"npm run test\")\n } catch {\n // Skip if can't read package.json\n }\n }\n\n // Python projects\n if (existsSync(path.resolve(this.cwd, \"pyproject.toml\"))) {\n if (existsSync(path.resolve(this.cwd, \"conftest.py\"))) {\n commands.push(\"python -m pytest --tb=short -q\")\n }\n }\n\n return commands\n }\n\n private async generateFix(\n error: string,\n affectedFiles: string[],\n originalTask: string,\n attempt: number\n ): Promise<{ files: GeneratedFile[]; explanation: string }> {\n const provider = createProvider(this.config.provider, this.config.apiKey)\n\n // Read affected files\n const fileContents: string[] = []\n for (const file of affectedFiles.slice(0, 5)) {\n const filePath = path.resolve(this.cwd, file)\n if (existsSync(filePath)) {\n const content = await fs.readFile(filePath, \"utf8\")\n fileContents.push(`## ${file}\\n\\`\\`\\`\\n${content.slice(0, 3000)}\\n\\`\\`\\``)\n }\n }\n\n const techStack = await detectTechStack(this.cwd)\n\n const messages: GenerationMessage[] = [\n {\n role: \"system\",\n content: `You are agentx auto-heal. You fix errors in generated code.\n\nProject tech stack: ${formatTechStack(techStack)}\n\nRULES:\n- Fix ONLY the error — do not refactor or add features\n- Output the COMPLETE fixed file content via create_files\n- Be minimal — smallest change that fixes the error\n- This is attempt ${attempt} — ${attempt > 1 ? \"previous fix attempts failed, try a different approach\" : \"analyze carefully\"}`,\n },\n {\n role: \"user\",\n content: `The following code was generated for task: \"${originalTask}\"\n\n## Error\n\\`\\`\\`\n${error}\n\\`\\`\\`\n\n## Affected Files\n${fileContents.join(\"\\n\\n\")}\n\nFix the error. Use create_files with the corrected file content.`,\n },\n ]\n\n const result = await provider.generate(messages, {\n model: this.config.model,\n maxTokens: 8192,\n })\n\n return {\n files: result.files,\n explanation: result.content,\n }\n }\n}\n\nfunction truncateError(error: string): string {\n const lines = error.split(\"\\n\").filter((l) => l.trim())\n if (lines.length > 30) {\n return lines.slice(0, 30).join(\"\\n\") + \"\\n... (truncated)\"\n }\n return lines.join(\"\\n\")\n}\n"],"mappings":"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","names":["existsSync","fs","path","execa","DEFAULT_HEAL_CONFIG","HealEngine","cwd","config","memory","Memory","generatedFiles","originalTask","entryId","error","attempts","currentError","allChangedFiles","fix","file","filePath","path","dir","fs","newError","commands","detected","cmd","bin","args","execa","stderr","stdout","message","truncateError","pkgPath","existsSync","scripts","affectedFiles","attempt","provider","createProvider","fileContents","content","techStack","detectTechStack","messages","formatTechStack","result","lines","l"]}
|
|
1
|
+
{"version":3,"sources":["../src/runtime/heal.ts"],"sourcesContent":["import { existsSync, promises as fs } from \"fs\"\nimport path from \"path\"\nimport { execa } from \"execa\"\nimport { createProvider, type ProviderName } from \"@/agent/providers\"\nimport type { GenerationMessage, GeneratedFile } from \"@/agent/providers/types\"\nimport { detectTechStack, formatTechStack } from \"@/agent/context/tech-stack\"\nimport { Memory } from \"./memory\"\n\n// --- Auto-Heal Engine: detect errors, diagnose, fix, verify ---\n// When generated code fails (build error, test failure, runtime crash),\n// the heal engine reads the error, understands the context, generates\n// a fix, and re-verifies it works.\n\nexport interface HealConfig {\n enabled: boolean\n testCommand?: string\n buildCommand?: string\n lintCommand?: string\n maxAttempts: number\n provider: ProviderName\n model?: string\n apiKey?: string\n}\n\nexport interface HealResult {\n healed: boolean\n attempts: number\n error: string\n fix?: string\n filesChanged: string[]\n}\n\nconst DEFAULT_HEAL_CONFIG: HealConfig = {\n enabled: true,\n maxAttempts: 3,\n provider: \"claude-code\",\n}\n\nexport class HealEngine {\n private config: HealConfig\n private memory: Memory\n\n constructor(\n private cwd: string,\n config?: Partial<HealConfig>,\n memory?: Memory\n ) {\n this.config = { ...DEFAULT_HEAL_CONFIG, ...config }\n this.memory = memory || new Memory(cwd)\n }\n\n async detectAndHeal(\n generatedFiles: string[],\n originalTask: string,\n entryId?: string\n ): Promise<HealResult> {\n if (!this.config.enabled) {\n return { healed: false, attempts: 0, error: \"Heal disabled\", filesChanged: [] }\n }\n\n // 1. Run verification commands\n const error = await this.runVerification()\n if (!error) {\n return { healed: true, attempts: 0, error: \"\", filesChanged: [] }\n }\n\n // 2. Attempt to heal\n let attempts = 0\n let currentError = error\n const allChangedFiles: string[] = []\n\n while (attempts < this.config.maxAttempts) {\n attempts++\n\n const fix = await this.generateFix(\n currentError,\n generatedFiles,\n originalTask,\n attempts\n )\n\n if (!fix.files.length) {\n break // Agent couldn't generate a fix\n }\n\n // Apply the fix\n for (const file of fix.files) {\n const filePath = path.isAbsolute(file.path)\n ? file.path\n : path.resolve(this.cwd, file.path)\n\n const dir = path.dirname(filePath)\n await fs.mkdir(dir, { recursive: true })\n await fs.writeFile(filePath, file.content, \"utf8\")\n allChangedFiles.push(file.path)\n }\n\n // Re-verify\n const newError = await this.runVerification()\n if (!newError) {\n // Healed successfully\n if (entryId) {\n await this.memory.recordHeal(entryId, error, true, allChangedFiles)\n }\n return {\n healed: true,\n attempts,\n error,\n fix: fix.explanation,\n filesChanged: allChangedFiles,\n }\n }\n\n currentError = newError\n }\n\n // Failed to heal\n if (entryId) {\n await this.memory.recordHeal(entryId, error, false, allChangedFiles)\n }\n\n return {\n healed: false,\n attempts,\n error: currentError,\n filesChanged: allChangedFiles,\n }\n }\n\n private async runVerification(): Promise<string | null> {\n const commands = [\n this.config.buildCommand,\n this.config.lintCommand,\n this.config.testCommand,\n ].filter(Boolean) as string[]\n\n // Auto-detect commands if not configured\n if (!commands.length) {\n const detected = await this.detectCommands()\n commands.push(...detected)\n }\n\n for (const cmd of commands) {\n try {\n const [bin, ...args] = cmd.split(\" \")\n await execa(bin, args, {\n cwd: this.cwd,\n timeout: 60000,\n reject: true,\n })\n } catch (error: any) {\n const stderr = error.stderr || \"\"\n const stdout = error.stdout || \"\"\n const message = error.message || \"\"\n return truncateError(`Command failed: ${cmd}\\n${stderr}\\n${stdout}\\n${message}`)\n }\n }\n\n return null // All passed\n }\n\n private async detectCommands(): Promise<string[]> {\n const commands: string[] = []\n const pkgPath = path.resolve(this.cwd, \"package.json\")\n\n if (existsSync(pkgPath)) {\n try {\n const pkg = JSON.parse(await fs.readFile(pkgPath, \"utf8\"))\n const scripts = pkg.scripts || {}\n if (scripts.build) commands.push(\"npm run build\")\n if (scripts.typecheck) commands.push(\"npm run typecheck\")\n if (scripts.lint) commands.push(\"npm run lint\")\n if (scripts.test) commands.push(\"npm run test\")\n } catch {\n // Skip if can't read package.json\n }\n }\n\n // Python projects\n if (existsSync(path.resolve(this.cwd, \"pyproject.toml\"))) {\n if (existsSync(path.resolve(this.cwd, \"conftest.py\"))) {\n commands.push(\"python -m pytest --tb=short -q\")\n }\n }\n\n return commands\n }\n\n private async generateFix(\n error: string,\n affectedFiles: string[],\n originalTask: string,\n attempt: number\n ): Promise<{ files: GeneratedFile[]; explanation: string }> {\n const provider = createProvider(this.config.provider, this.config.apiKey)\n\n // Read affected files\n const fileContents: string[] = []\n for (const file of affectedFiles.slice(0, 5)) {\n const filePath = path.resolve(this.cwd, file)\n if (existsSync(filePath)) {\n const content = await fs.readFile(filePath, \"utf8\")\n fileContents.push(`## ${file}\\n\\`\\`\\`\\n${content.slice(0, 3000)}\\n\\`\\`\\``)\n }\n }\n\n const techStack = await detectTechStack(this.cwd)\n\n const messages: GenerationMessage[] = [\n {\n role: \"system\",\n content: `You are agentx auto-heal. You fix errors in generated code.\n\nProject tech stack: ${formatTechStack(techStack)}\n\nRULES:\n- Fix ONLY the error — do not refactor or add features\n- Output the COMPLETE fixed file content via create_files\n- Be minimal — smallest change that fixes the error\n- This is attempt ${attempt} — ${attempt > 1 ? \"previous fix attempts failed, try a different approach\" : \"analyze carefully\"}`,\n },\n {\n role: \"user\",\n content: `The following code was generated for task: \"${originalTask}\"\n\n## Error\n\\`\\`\\`\n${error}\n\\`\\`\\`\n\n## Affected Files\n${fileContents.join(\"\\n\\n\")}\n\nFix the error. Use create_files with the corrected file content.`,\n },\n ]\n\n const result = await provider.generate(messages, {\n model: this.config.model,\n maxTokens: 8192,\n })\n\n return {\n files: result.files,\n explanation: result.content,\n }\n }\n}\n\nfunction truncateError(error: string): string {\n const lines = error.split(\"\\n\").filter((l) => l.trim())\n if (lines.length > 30) {\n return lines.slice(0, 30).join(\"\\n\") + \"\\n... (truncated)\"\n }\n return lines.join(\"\\n\")\n}\n"],"mappings":"8FAAA,OAAS,cAAAA,EAAY,YAAYC,MAAU,KAC3C,OAAOC,MAAU,OACjB,OAAS,SAAAC,MAAa,QA8BtB,IAAMC,EAAkC,CACtC,QAAS,GACT,YAAa,EACb,SAAU,aACZ,EAEaC,EAAN,KAAiB,CAItB,YACUC,EACRC,EACAC,EACA,CAHQ,SAAAF,EAIR,KAAK,OAAS,CAAE,GAAGF,EAAqB,GAAGG,CAAO,EAClD,KAAK,OAASC,GAAU,IAAIC,EAAOH,CAAG,CACxC,CAVQ,OACA,OAWR,MAAM,cACJI,EACAC,EACAC,EACqB,CACrB,GAAI,CAAC,KAAK,OAAO,QACf,MAAO,CAAE,OAAQ,GAAO,SAAU,EAAG,MAAO,gBAAiB,aAAc,CAAC,CAAE,EAIhF,IAAMC,EAAQ,MAAM,KAAK,gBAAgB,EACzC,GAAI,CAACA,EACH,MAAO,CAAE,OAAQ,GAAM,SAAU,EAAG,MAAO,GAAI,aAAc,CAAC,CAAE,EAIlE,IAAIC,EAAW,EACXC,EAAeF,EACbG,EAA4B,CAAC,EAEnC,KAAOF,EAAW,KAAK,OAAO,aAAa,CACzCA,IAEA,IAAMG,EAAM,MAAM,KAAK,YACrBF,EACAL,EACAC,EACAG,CACF,EAEA,GAAI,CAACG,EAAI,MAAM,OACb,MAIF,QAAWC,KAAQD,EAAI,MAAO,CAC5B,IAAME,EAAWC,EAAK,WAAWF,EAAK,IAAI,EACtCA,EAAK,KACLE,EAAK,QAAQ,KAAK,IAAKF,EAAK,IAAI,EAE9BG,EAAMD,EAAK,QAAQD,CAAQ,EACjC,MAAMG,EAAG,MAAMD,EAAK,CAAE,UAAW,EAAK,CAAC,EACvC,MAAMC,EAAG,UAAUH,EAAUD,EAAK,QAAS,MAAM,EACjDF,EAAgB,KAAKE,EAAK,IAAI,EAIhC,IAAMK,EAAW,MAAM,KAAK,gBAAgB,EAC5C,GAAI,CAACA,EAEH,OAAIX,GACF,MAAM,KAAK,OAAO,WAAWA,EAASC,EAAO,GAAMG,CAAe,EAE7D,CACL,OAAQ,GACR,SAAAF,EACA,MAAAD,EACA,IAAKI,EAAI,YACT,aAAcD,CAChB,EAGFD,EAAeQ,EAIjB,OAAIX,GACF,MAAM,KAAK,OAAO,WAAWA,EAASC,EAAO,GAAOG,CAAe,EAG9D,CACL,OAAQ,GACR,SAAAF,EACA,MAAOC,EACP,aAAcC,CAChB,CACF,CAEA,MAAc,iBAA0C,CACtD,IAAMQ,EAAW,CACf,KAAK,OAAO,aACZ,KAAK,OAAO,YACZ,KAAK,OAAO,WACd,EAAE,OAAO,OAAO,EAGhB,GAAI,CAACA,EAAS,OAAQ,CACpB,IAAMC,EAAW,MAAM,KAAK,eAAe,EAC3CD,EAAS,KAAK,GAAGC,CAAQ,EAG3B,QAAWC,KAAOF,EAChB,GAAI,CACF,GAAM,CAACG,EAAK,GAAGC,CAAI,EAAIF,EAAI,MAAM,GAAG,EACpC,MAAMG,EAAMF,EAAKC,EAAM,CACrB,IAAK,KAAK,IACV,QAAS,IACT,OAAQ,EACV,CAAC,CACH,OAASf,EAAP,CACA,IAAMiB,EAASjB,EAAM,QAAU,GACzBkB,EAASlB,EAAM,QAAU,GACzBmB,EAAUnB,EAAM,SAAW,GACjC,OAAOoB,EAAc,mBAAmBP;AAAA,EAAQI;AAAA,EAAWC;AAAA,EAAWC,GAAS,CACjF,CAGF,OAAO,IACT,CAEA,MAAc,gBAAoC,CAChD,IAAMR,EAAqB,CAAC,EACtBU,EAAUd,EAAK,QAAQ,KAAK,IAAK,cAAc,EAErD,GAAIe,EAAWD,CAAO,EACpB,GAAI,CAEF,IAAME,EADM,KAAK,MAAM,MAAMd,EAAG,SAASY,EAAS,MAAM,CAAC,EACrC,SAAW,CAAC,EAC5BE,EAAQ,OAAOZ,EAAS,KAAK,eAAe,EAC5CY,EAAQ,WAAWZ,EAAS,KAAK,mBAAmB,EACpDY,EAAQ,MAAMZ,EAAS,KAAK,cAAc,EAC1CY,EAAQ,MAAMZ,EAAS,KAAK,cAAc,CAChD,MAAE,CAEF,CAIF,OAAIW,EAAWf,EAAK,QAAQ,KAAK,IAAK,gBAAgB,CAAC,GACjDe,EAAWf,EAAK,QAAQ,KAAK,IAAK,aAAa,CAAC,GAClDI,EAAS,KAAK,gCAAgC,EAI3CA,CACT,CAEA,MAAc,YACZX,EACAwB,EACA1B,EACA2B,EAC0D,CAC1D,IAAMC,EAAWC,EAAe,KAAK,OAAO,SAAU,KAAK,OAAO,MAAM,EAGlEC,EAAyB,CAAC,EAChC,QAAWvB,KAAQmB,EAAc,MAAM,EAAG,CAAC,EAAG,CAC5C,IAAMlB,EAAWC,EAAK,QAAQ,KAAK,IAAKF,CAAI,EAC5C,GAAIiB,EAAWhB,CAAQ,EAAG,CACxB,IAAMuB,EAAU,MAAMpB,EAAG,SAASH,EAAU,MAAM,EAClDsB,EAAa,KAAK,MAAMvB;AAAA;AAAA,EAAiBwB,EAAQ,MAAM,EAAG,GAAI;AAAA,OAAW,GAI7E,IAAMC,EAAY,MAAMC,EAAgB,KAAK,GAAG,EAE1CC,EAAgC,CACpC,CACE,KAAM,SACN,QAAS;AAAA;AAAA,sBAEKC,EAAgBH,CAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAM3BL,YAAaA,EAAU,EAAI,yDAA2D,qBACpG,EACA,CACE,KAAM,OACN,QAAS,+CAA+C3B;AAAA;AAAA;AAAA;AAAA,EAI9DE;AAAA;AAAA;AAAA;AAAA,EAIA4B,EAAa,KAAK;AAAA;AAAA,CAAM;AAAA;AAAA,iEAGpB,CACF,EAEMM,EAAS,MAAMR,EAAS,SAASM,EAAU,CAC/C,MAAO,KAAK,OAAO,MACnB,UAAW,IACb,CAAC,EAED,MAAO,CACL,MAAOE,EAAO,MACd,YAAaA,EAAO,OACtB,CACF,CACF,EAEA,SAASd,EAAcpB,EAAuB,CAC5C,IAAMmC,EAAQnC,EAAM,MAAM;AAAA,CAAI,EAAE,OAAQoC,GAAMA,EAAE,KAAK,CAAC,EACtD,OAAID,EAAM,OAAS,GACVA,EAAM,MAAM,EAAG,EAAE,EAAE,KAAK;AAAA,CAAI,EAAI;AAAA,iBAElCA,EAAM,KAAK;AAAA,CAAI,CACxB","names":["existsSync","fs","path","execa","DEFAULT_HEAL_CONFIG","HealEngine","cwd","config","memory","Memory","generatedFiles","originalTask","entryId","error","attempts","currentError","allChangedFiles","fix","file","filePath","path","dir","fs","newError","commands","detected","cmd","bin","args","execa","stderr","stdout","message","truncateError","pkgPath","existsSync","scripts","affectedFiles","attempt","provider","createProvider","fileContents","content","techStack","detectTechStack","messages","formatTechStack","result","lines","l"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{existsSync as i,mkdirSync as d,readdirSync as g,readFileSync as f,unlinkSync as m,writeFileSync as y}from"fs";import{resolve as a}from"path";import{z as n}from"zod";var A=n.object({channel:n.enum(["telegram","whatsapp","slack","discord","email"]),handle:n.string().min(1),preferredForTasks:n.boolean().optional()}),u=n.object({id:n.string().regex(/^actor:[a-z0-9][a-z0-9_-]*$/,"actor id must be 'actor:<slug>'"),name:n.string().min(1),email:n.string().email().optional(),channels:n.array(A).min(1,"actor must have at least one channel handle"),timezone:n.string().optional(),created:n.string().optional(),updated:n.string().optional()}),b=n.union([n.object({actor:n.string()}),n.object({role:n.string()})]),R=n.enum(["first-available","round-robin","all","manager-of"]),h=n.object({id:n.string().regex(/^role:[a-z0-9][a-z0-9_-]*$/,"role id must be 'role:<slug>'"),name:n.string().min(1),members:n.array(b).default([]),assignmentStrategy:R.default("first-available"),rotationCursor:n.number().int().default(0),created:n.string().optional(),updated:n.string().optional()});function D(c){return c.startsWith("actor:")?{kind:"actor",id:c}:c.startsWith("role:")?{kind:"role",id:c}:null}var S=class{actorsDir;rolesDir;constructor(t={}){let e=t.baseDir??a(process.cwd(),".agentx");this.actorsDir=a(e,"actors"),this.rolesDir=a(e,"roles"),d(this.actorsDir,{recursive:!0}),d(this.rolesDir,{recursive:!0})}actorPath(t){let e=t.replace(/^actor:/,"");return a(this.actorsDir,`${e}.json`)}listActors(){if(!i(this.actorsDir))return[];let t=[];for(let e of g(this.actorsDir,{withFileTypes:!0}))if(!(!e.isFile()||!e.name.endsWith(".json")))try{let r=JSON.parse(f(a(this.actorsDir,e.name),"utf-8")),s=u.safeParse(r);s.success&&t.push(s.data)}catch{}return t.sort((e,r)=>e.id.localeCompare(r.id))}getActor(t){let e=this.actorPath(t);if(!i(e))return null;try{let r=JSON.parse(f(e,"utf-8")),s=u.safeParse(r);return s.success?s.data:null}catch{return null}}saveActor(t){let e=u.parse({...t,created:t.created??new Date().toISOString(),updated:new Date().toISOString()});return y(this.actorPath(e.id),JSON.stringify(e,null,2)),e}deleteActor(t){let e=this.actorPath(t);return i(e)?(m(e),!0):!1}rolePath(t){let e=t.replace(/^role:/,"");return a(this.rolesDir,`${e}.json`)}listRoles(){if(!i(this.rolesDir))return[];let t=[];for(let e of g(this.rolesDir,{withFileTypes:!0}))if(!(!e.isFile()||!e.name.endsWith(".json")))try{let r=JSON.parse(f(a(this.rolesDir,e.name),"utf-8")),s=h.safeParse(r);s.success&&t.push(s.data)}catch{}return t.sort((e,r)=>e.id.localeCompare(r.id))}getRole(t){let e=this.rolePath(t);if(!i(e))return null;try{let r=JSON.parse(f(e,"utf-8")),s=h.safeParse(r);return s.success?s.data:null}catch{return null}}saveRole(t){let e=h.parse({...t,created:t.created??new Date().toISOString(),updated:new Date().toISOString()});return y(this.rolePath(e.id),JSON.stringify(e,null,2)),e}deleteRole(t){let e=this.rolePath(t);return i(e)?(m(e),!0):!1}resolveMembers(t){if(t.kind==="actor")return[t.id];let e=new Set,r=[],s=o=>{if(e.has(o))return;e.add(o);let p=this.getRole(o);if(p)for(let l of p.members)"actor"in l?r.includes(l.actor)||r.push(l.actor):s(l.role)};return s(t.id),r}pickAssignees(t){let e=this.resolveMembers(t);if(e.length===0)return[];if(t.kind==="actor")return e;let r=this.getRole(t.id);if(!r)return e.slice(0,1);switch(r.assignmentStrategy){case"all":return e;case"round-robin":{let s=r.rotationCursor%e.length,o=e[s];return this.saveRole({...r,rotationCursor:(r.rotationCursor+1)%Math.max(e.length,1)}),[o]}case"first-available":case"manager-of":default:return[e[0]]}}channelFor(t,e){let r=this.getActor(t);return r?r.channels.find(o=>o.channel===e)?.handle??null:null}preferredChannel(t){let e=this.getActor(t);return!e||e.channels.length===0?null:e.channels.find(r=>r.preferredForTasks)??e.channels[0]}};export{D as a,S as b};
|
|
2
|
+
//# sourceMappingURL=chunk-EW27SCCC.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/actors/store.ts","../src/actors/types.ts"],"sourcesContent":["import { existsSync, mkdirSync, readdirSync, readFileSync, unlinkSync, writeFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport {\n actorSchema,\n roleSchema,\n type Actor,\n type ActorChannel,\n type Role,\n type AssigneeRef,\n} from \"./types\"\n\n// --- Actor / Role filesystem store ---\n//\n// Two sibling directories under .agentx/:\n// actors/<actor-id>.json\n// roles/<role-id>.json\n//\n// The store exposes CRUD + two resolution helpers needed by the workflow\n// engine:\n// resolveMembers(assignee) — role or actor ref → flat actor id list\n// (handles nested roles, strategy picks who sees\n// the task)\n// channelFor(actorId, ch) — actor id + preferred channel → handle string\n\nexport interface ActorStoreOptions {\n baseDir?: string\n}\n\nexport class ActorStore {\n readonly actorsDir: string\n readonly rolesDir: string\n\n constructor(opts: ActorStoreOptions = {}) {\n const root = opts.baseDir ?? resolve(process.cwd(), \".agentx\")\n this.actorsDir = resolve(root, \"actors\")\n this.rolesDir = resolve(root, \"roles\")\n mkdirSync(this.actorsDir, { recursive: true })\n mkdirSync(this.rolesDir, { recursive: true })\n }\n\n // --- Actors ---\n\n private actorPath(id: string): string {\n const safe = id.replace(/^actor:/, \"\")\n return resolve(this.actorsDir, `${safe}.json`)\n }\n\n listActors(): Actor[] {\n if (!existsSync(this.actorsDir)) return []\n const out: Actor[] = []\n for (const entry of readdirSync(this.actorsDir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".json\")) continue\n try {\n const raw = JSON.parse(readFileSync(resolve(this.actorsDir, entry.name), \"utf-8\"))\n const parsed = actorSchema.safeParse(raw)\n if (parsed.success) out.push(parsed.data)\n } catch {\n // skip malformed\n }\n }\n return out.sort((a, b) => a.id.localeCompare(b.id))\n }\n\n getActor(id: string): Actor | null {\n const full = this.actorPath(id)\n if (!existsSync(full)) return null\n try {\n const raw = JSON.parse(readFileSync(full, \"utf-8\"))\n const parsed = actorSchema.safeParse(raw)\n return parsed.success ? parsed.data : null\n } catch {\n return null\n }\n }\n\n saveActor(actor: Actor): Actor {\n const parsed = actorSchema.parse({\n ...actor,\n created: actor.created ?? new Date().toISOString(),\n updated: new Date().toISOString(),\n })\n writeFileSync(this.actorPath(parsed.id), JSON.stringify(parsed, null, 2))\n return parsed\n }\n\n deleteActor(id: string): boolean {\n const full = this.actorPath(id)\n if (!existsSync(full)) return false\n unlinkSync(full)\n return true\n }\n\n // --- Roles ---\n\n private rolePath(id: string): string {\n const safe = id.replace(/^role:/, \"\")\n return resolve(this.rolesDir, `${safe}.json`)\n }\n\n listRoles(): Role[] {\n if (!existsSync(this.rolesDir)) return []\n const out: Role[] = []\n for (const entry of readdirSync(this.rolesDir, { withFileTypes: true })) {\n if (!entry.isFile() || !entry.name.endsWith(\".json\")) continue\n try {\n const raw = JSON.parse(readFileSync(resolve(this.rolesDir, entry.name), \"utf-8\"))\n const parsed = roleSchema.safeParse(raw)\n if (parsed.success) out.push(parsed.data)\n } catch {\n // skip\n }\n }\n return out.sort((a, b) => a.id.localeCompare(b.id))\n }\n\n getRole(id: string): Role | null {\n const full = this.rolePath(id)\n if (!existsSync(full)) return null\n try {\n const raw = JSON.parse(readFileSync(full, \"utf-8\"))\n const parsed = roleSchema.safeParse(raw)\n return parsed.success ? parsed.data : null\n } catch {\n return null\n }\n }\n\n saveRole(role: Role): Role {\n const parsed = roleSchema.parse({\n ...role,\n created: role.created ?? new Date().toISOString(),\n updated: new Date().toISOString(),\n })\n writeFileSync(this.rolePath(parsed.id), JSON.stringify(parsed, null, 2))\n return parsed\n }\n\n deleteRole(id: string): boolean {\n const full = this.rolePath(id)\n if (!existsSync(full)) return false\n unlinkSync(full)\n return true\n }\n\n // --- Resolution ---\n\n /** Flatten a role (or actor) reference to a concrete list of actor ids.\n * Handles nested roles (role containing role). Cycles are guarded. */\n resolveMembers(assignee: AssigneeRef): string[] {\n if (assignee.kind === \"actor\") return [assignee.id]\n const seen = new Set<string>()\n const out: string[] = []\n const walk = (roleId: string) => {\n if (seen.has(roleId)) return\n seen.add(roleId)\n const role = this.getRole(roleId)\n if (!role) return\n for (const member of role.members) {\n if (\"actor\" in member) {\n if (!out.includes(member.actor)) out.push(member.actor)\n } else {\n walk(member.role)\n }\n }\n }\n walk(assignee.id)\n return out\n }\n\n /** Pick which members of an assignee actually see a task right now,\n * obeying the role's assignment strategy. Returns one or more actor ids. */\n pickAssignees(assignee: AssigneeRef): string[] {\n const members = this.resolveMembers(assignee)\n if (members.length === 0) return []\n if (assignee.kind === \"actor\") return members\n const role = this.getRole(assignee.id)\n if (!role) return members.slice(0, 1)\n switch (role.assignmentStrategy) {\n case \"all\":\n return members\n case \"round-robin\": {\n const idx = role.rotationCursor % members.length\n const picked = members[idx]\n this.saveRole({ ...role, rotationCursor: (role.rotationCursor + 1) % Math.max(members.length, 1) })\n return [picked]\n }\n case \"first-available\":\n case \"manager-of\":\n default:\n return [members[0]]\n }\n }\n\n /** Look up an actor's handle for a given channel. Returns null if the\n * actor has no handle on that channel. */\n channelFor(actorId: string, channel: ActorChannel[\"channel\"]): string | null {\n const actor = this.getActor(actorId)\n if (!actor) return null\n const match = actor.channels.find((c) => c.channel === channel)\n return match?.handle ?? null\n }\n\n /** Pick the delivery channel for user-task notifications. Prefers the\n * channel marked preferredForTasks, else the first channel. */\n preferredChannel(actorId: string): ActorChannel | null {\n const actor = this.getActor(actorId)\n if (!actor || actor.channels.length === 0) return null\n return actor.channels.find((c) => c.preferredForTasks) ?? actor.channels[0]\n }\n}\n","import { z } from \"zod\"\n\n// --- Actor + Role primitives ---\n//\n// An Actor is a human participant in a business activity. They carry one or\n// more channel handles (a Telegram user id, a WhatsApp phone, a Slack member\n// id, an email address) so the workflow engine can deliver user tasks to\n// wherever they actually read messages.\n//\n// A Role is a named group of Actors (or nested Roles). User tasks may be\n// assigned to a Role instead of a specific Actor — the engine uses the Role's\n// assignmentStrategy to pick which member(s) actually see the task.\n//\n// Both are stored as JSON under .agentx/actors/ and .agentx/roles/.\n\nexport const actorChannelSchema = z.object({\n channel: z.enum([\"telegram\", \"whatsapp\", \"slack\", \"discord\", \"email\"]),\n /** Channel-native handle: telegram user id, phone number (E.164), slack\n * member id, discord user id, email address. Stable per channel. */\n handle: z.string().min(1),\n /** If true, user-task notifications for this actor land on this channel\n * first. At most one channel should be marked preferred; ties break in\n * array order. */\n preferredForTasks: z.boolean().optional(),\n})\nexport type ActorChannel = z.infer<typeof actorChannelSchema>\n\nexport const actorSchema = z.object({\n id: z.string().regex(/^actor:[a-z0-9][a-z0-9_-]*$/, \"actor id must be 'actor:<slug>'\"),\n name: z.string().min(1),\n email: z.string().email().optional(),\n channels: z.array(actorChannelSchema).min(1, \"actor must have at least one channel handle\"),\n timezone: z.string().optional(),\n created: z.string().optional(),\n updated: z.string().optional(),\n})\nexport type Actor = z.infer<typeof actorSchema>\n\nexport const roleMemberSchema = z.union([\n z.object({ actor: z.string() }),\n z.object({ role: z.string() }),\n])\nexport type RoleMember = z.infer<typeof roleMemberSchema>\n\nexport const assignmentStrategySchema = z.enum([\n \"first-available\", // first member in order\n \"round-robin\", // rotate across members (state persisted on the role)\n \"all\", // assign to every member simultaneously\n \"manager-of\", // resolve via actor.managerOf relationship (future)\n])\nexport type AssignmentStrategy = z.infer<typeof assignmentStrategySchema>\n\nexport const roleSchema = z.object({\n id: z.string().regex(/^role:[a-z0-9][a-z0-9_-]*$/, \"role id must be 'role:<slug>'\"),\n name: z.string().min(1),\n members: z.array(roleMemberSchema).default([]),\n assignmentStrategy: assignmentStrategySchema.default(\"first-available\"),\n /** Mutable rotation cursor for round-robin. Incremented on assign. */\n rotationCursor: z.number().int().default(0),\n created: z.string().optional(),\n updated: z.string().optional(),\n})\nexport type Role = z.infer<typeof roleSchema>\n\n// Reference shape used in workflow node configs: either an actor id or a role\n// id. Encoded as a string with the typed prefix so templating stays simple.\nexport type ActorRef = { kind: \"actor\"; id: string }\nexport type RoleRef = { kind: \"role\"; id: string }\nexport type AssigneeRef = ActorRef | RoleRef\n\nexport function parseAssigneeRef(raw: string): AssigneeRef | null {\n if (raw.startsWith(\"actor:\")) return { kind: \"actor\", id: raw }\n if (raw.startsWith(\"role:\")) return { kind: \"role\", id: raw }\n return null\n}\n"],"mappings":"AAAA,OAAS,cAAAA,EAAY,aAAAC,EAAW,eAAAC,EAAa,gBAAAC,EAAc,cAAAC,EAAY,iBAAAC,MAAqB,KAC5F,OAAS,WAAAC,MAAe,OCDxB,OAAS,KAAAC,MAAS,MAeX,IAAMC,EAAqBD,EAAE,OAAO,CACzC,QAASA,EAAE,KAAK,CAAC,WAAY,WAAY,QAAS,UAAW,OAAO,CAAC,EAGrE,OAAQA,EAAE,OAAO,EAAE,IAAI,CAAC,EAIxB,kBAAmBA,EAAE,QAAQ,EAAE,SAAS,CAC1C,CAAC,EAGYE,EAAcF,EAAE,OAAO,CAClC,GAAIA,EAAE,OAAO,EAAE,MAAM,8BAA+B,iCAAiC,EACrF,KAAMA,EAAE,OAAO,EAAE,IAAI,CAAC,EACtB,MAAOA,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EACnC,SAAUA,EAAE,MAAMC,CAAkB,EAAE,IAAI,EAAG,6CAA6C,EAC1F,SAAUD,EAAE,OAAO,EAAE,SAAS,EAC9B,QAASA,EAAE,OAAO,EAAE,SAAS,EAC7B,QAASA,EAAE,OAAO,EAAE,SAAS,CAC/B,CAAC,EAGYG,EAAmBH,EAAE,MAAM,CACtCA,EAAE,OAAO,CAAE,MAAOA,EAAE,OAAO,CAAE,CAAC,EAC9BA,EAAE,OAAO,CAAE,KAAMA,EAAE,OAAO,CAAE,CAAC,CAC/B,CAAC,EAGYI,EAA2BJ,EAAE,KAAK,CAC7C,kBACA,cACA,MACA,YACF,CAAC,EAGYK,EAAaL,EAAE,OAAO,CACjC,GAAIA,EAAE,OAAO,EAAE,MAAM,6BAA8B,+BAA+B,EAClF,KAAMA,EAAE,OAAO,EAAE,IAAI,CAAC,EACtB,QAASA,EAAE,MAAMG,CAAgB,EAAE,QAAQ,CAAC,CAAC,EAC7C,mBAAoBC,EAAyB,QAAQ,iBAAiB,EAEtE,eAAgBJ,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,EAC1C,QAASA,EAAE,OAAO,EAAE,SAAS,EAC7B,QAASA,EAAE,OAAO,EAAE,SAAS,CAC/B,CAAC,EASM,SAASM,EAAiBC,EAAiC,CAChE,OAAIA,EAAI,WAAW,QAAQ,EAAU,CAAE,KAAM,QAAS,GAAIA,CAAI,EAC1DA,EAAI,WAAW,OAAO,EAAW,CAAE,KAAM,OAAS,GAAIA,CAAI,EACvD,IACT,CD9CO,IAAMC,EAAN,KAAiB,CACb,UACA,SAET,YAAYC,EAA0B,CAAC,EAAG,CACxC,IAAMC,EAAOD,EAAK,SAAWE,EAAQ,QAAQ,IAAI,EAAG,SAAS,EAC7D,KAAK,UAAYA,EAAQD,EAAM,QAAQ,EACvC,KAAK,SAAWC,EAAQD,EAAM,OAAO,EACrCE,EAAU,KAAK,UAAW,CAAE,UAAW,EAAK,CAAC,EAC7CA,EAAU,KAAK,SAAU,CAAE,UAAW,EAAK,CAAC,CAC9C,CAIQ,UAAUC,EAAoB,CACpC,IAAMC,EAAOD,EAAG,QAAQ,UAAW,EAAE,EACrC,OAAOF,EAAQ,KAAK,UAAW,GAAGG,QAAW,CAC/C,CAEA,YAAsB,CACpB,GAAI,CAACC,EAAW,KAAK,SAAS,EAAG,MAAO,CAAC,EACzC,IAAMC,EAAe,CAAC,EACtB,QAAWC,KAASC,EAAY,KAAK,UAAW,CAAE,cAAe,EAAK,CAAC,EACrE,GAAI,GAACD,EAAM,OAAO,GAAK,CAACA,EAAM,KAAK,SAAS,OAAO,GACnD,GAAI,CACF,IAAME,EAAM,KAAK,MAAMC,EAAaT,EAAQ,KAAK,UAAWM,EAAM,IAAI,EAAG,OAAO,CAAC,EAC3EI,EAASC,EAAY,UAAUH,CAAG,EACpCE,EAAO,SAASL,EAAI,KAAKK,EAAO,IAAI,CAC1C,MAAE,CAEF,CAEF,OAAOL,EAAI,KAAK,CAACO,EAAGC,IAAMD,EAAE,GAAG,cAAcC,EAAE,EAAE,CAAC,CACpD,CAEA,SAASX,EAA0B,CACjC,IAAMY,EAAO,KAAK,UAAUZ,CAAE,EAC9B,GAAI,CAACE,EAAWU,CAAI,EAAG,OAAO,KAC9B,GAAI,CACF,IAAMN,EAAM,KAAK,MAAMC,EAAaK,EAAM,OAAO,CAAC,EAC5CJ,EAASC,EAAY,UAAUH,CAAG,EACxC,OAAOE,EAAO,QAAUA,EAAO,KAAO,IACxC,MAAE,CACA,OAAO,IACT,CACF,CAEA,UAAUK,EAAqB,CAC7B,IAAML,EAASC,EAAY,MAAM,CAC/B,GAAGI,EACH,QAASA,EAAM,SAAW,IAAI,KAAK,EAAE,YAAY,EACjD,QAAS,IAAI,KAAK,EAAE,YAAY,CAClC,CAAC,EACD,OAAAC,EAAc,KAAK,UAAUN,EAAO,EAAE,EAAG,KAAK,UAAUA,EAAQ,KAAM,CAAC,CAAC,EACjEA,CACT,CAEA,YAAYR,EAAqB,CAC/B,IAAMY,EAAO,KAAK,UAAUZ,CAAE,EAC9B,OAAKE,EAAWU,CAAI,GACpBG,EAAWH,CAAI,EACR,IAFuB,EAGhC,CAIQ,SAASZ,EAAoB,CACnC,IAAMC,EAAOD,EAAG,QAAQ,SAAU,EAAE,EACpC,OAAOF,EAAQ,KAAK,SAAU,GAAGG,QAAW,CAC9C,CAEA,WAAoB,CAClB,GAAI,CAACC,EAAW,KAAK,QAAQ,EAAG,MAAO,CAAC,EACxC,IAAMC,EAAc,CAAC,EACrB,QAAWC,KAASC,EAAY,KAAK,SAAU,CAAE,cAAe,EAAK,CAAC,EACpE,GAAI,GAACD,EAAM,OAAO,GAAK,CAACA,EAAM,KAAK,SAAS,OAAO,GACnD,GAAI,CACF,IAAME,EAAM,KAAK,MAAMC,EAAaT,EAAQ,KAAK,SAAUM,EAAM,IAAI,EAAG,OAAO,CAAC,EAC1EI,EAASQ,EAAW,UAAUV,CAAG,EACnCE,EAAO,SAASL,EAAI,KAAKK,EAAO,IAAI,CAC1C,MAAE,CAEF,CAEF,OAAOL,EAAI,KAAK,CAACO,EAAGC,IAAMD,EAAE,GAAG,cAAcC,EAAE,EAAE,CAAC,CACpD,CAEA,QAAQX,EAAyB,CAC/B,IAAMY,EAAO,KAAK,SAASZ,CAAE,EAC7B,GAAI,CAACE,EAAWU,CAAI,EAAG,OAAO,KAC9B,GAAI,CACF,IAAMN,EAAM,KAAK,MAAMC,EAAaK,EAAM,OAAO,CAAC,EAC5CJ,EAASQ,EAAW,UAAUV,CAAG,EACvC,OAAOE,EAAO,QAAUA,EAAO,KAAO,IACxC,MAAE,CACA,OAAO,IACT,CACF,CAEA,SAASS,EAAkB,CACzB,IAAMT,EAASQ,EAAW,MAAM,CAC9B,GAAGC,EACH,QAASA,EAAK,SAAW,IAAI,KAAK,EAAE,YAAY,EAChD,QAAS,IAAI,KAAK,EAAE,YAAY,CAClC,CAAC,EACD,OAAAH,EAAc,KAAK,SAASN,EAAO,EAAE,EAAG,KAAK,UAAUA,EAAQ,KAAM,CAAC,CAAC,EAChEA,CACT,CAEA,WAAWR,EAAqB,CAC9B,IAAMY,EAAO,KAAK,SAASZ,CAAE,EAC7B,OAAKE,EAAWU,CAAI,GACpBG,EAAWH,CAAI,EACR,IAFuB,EAGhC,CAMA,eAAeM,EAAiC,CAC9C,GAAIA,EAAS,OAAS,QAAS,MAAO,CAACA,EAAS,EAAE,EAClD,IAAMC,EAAO,IAAI,IACXhB,EAAgB,CAAC,EACjBiB,EAAQC,GAAmB,CAC/B,GAAIF,EAAK,IAAIE,CAAM,EAAG,OACtBF,EAAK,IAAIE,CAAM,EACf,IAAMJ,EAAO,KAAK,QAAQI,CAAM,EAChC,GAAKJ,EACL,QAAWK,KAAUL,EAAK,QACpB,UAAWK,EACRnB,EAAI,SAASmB,EAAO,KAAK,GAAGnB,EAAI,KAAKmB,EAAO,KAAK,EAEtDF,EAAKE,EAAO,IAAI,CAGtB,EACA,OAAAF,EAAKF,EAAS,EAAE,EACTf,CACT,CAIA,cAAce,EAAiC,CAC7C,IAAMK,EAAU,KAAK,eAAeL,CAAQ,EAC5C,GAAIK,EAAQ,SAAW,EAAG,MAAO,CAAC,EAClC,GAAIL,EAAS,OAAS,QAAS,OAAOK,EACtC,IAAMN,EAAO,KAAK,QAAQC,EAAS,EAAE,EACrC,GAAI,CAACD,EAAM,OAAOM,EAAQ,MAAM,EAAG,CAAC,EACpC,OAAQN,EAAK,mBAAoB,CAC/B,IAAK,MACH,OAAOM,EACT,IAAK,cAAe,CAClB,IAAMC,EAAMP,EAAK,eAAiBM,EAAQ,OACpCE,EAASF,EAAQC,CAAG,EAC1B,YAAK,SAAS,CAAE,GAAGP,EAAM,gBAAiBA,EAAK,eAAiB,GAAK,KAAK,IAAIM,EAAQ,OAAQ,CAAC,CAAE,CAAC,EAC3F,CAACE,CAAM,CAChB,CACA,IAAK,kBACL,IAAK,aACL,QACE,MAAO,CAACF,EAAQ,CAAC,CAAC,CACtB,CACF,CAIA,WAAWG,EAAiBC,EAAiD,CAC3E,IAAMd,EAAQ,KAAK,SAASa,CAAO,EACnC,OAAKb,EACSA,EAAM,SAAS,KAAMe,GAAMA,EAAE,UAAYD,CAAO,GAChD,QAAU,KAFL,IAGrB,CAIA,iBAAiBD,EAAsC,CACrD,IAAMb,EAAQ,KAAK,SAASa,CAAO,EACnC,MAAI,CAACb,GAASA,EAAM,SAAS,SAAW,EAAU,KAC3CA,EAAM,SAAS,KAAMe,GAAMA,EAAE,iBAAiB,GAAKf,EAAM,SAAS,CAAC,CAC5E,CACF","names":["existsSync","mkdirSync","readdirSync","readFileSync","unlinkSync","writeFileSync","resolve","z","actorChannelSchema","actorSchema","roleMemberSchema","assignmentStrategySchema","roleSchema","parseAssigneeRef","raw","ActorStore","opts","root","resolve","mkdirSync","id","safe","existsSync","out","entry","readdirSync","raw","readFileSync","parsed","actorSchema","a","b","full","actor","writeFileSync","unlinkSync","roleSchema","role","assignee","seen","walk","roleId","member","members","idx","picked","actorId","channel","c"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import{existsSync as Y,readFileSync as X,writeFileSync as te,mkdirSync as ne}from"fs";import P from"path";import Q from"os";import U from"chalk";import j from"prompts";import q from"chalk";var C={error(...r){console.log(q.red(...r))},warn(...r){console.log(q.yellow(...r))},info(...r){console.log(q.cyan(...r))},success(...r){console.log(q.green(...r))},break(){console.log("")}};var H=P.join(Q.homedir(),".agentx"),J=P.join(H,"auth.json");function R(){if(!Y(J))return null;try{let r=JSON.parse(X(J,"utf8"));return r.provider&&r.authType&&r.token&&r.model?r:null}catch{return null}}function oe(r){Y(H)||ne(H,{recursive:!0}),te(J,JSON.stringify(r,null,2),"utf8")}function N(r){if(r?.trim()){let y=r.startsWith("sk-ant-oat")?"oauth":"api-key";return{token:r.trim(),authType:y}}let e=process.env.ANTHROPIC_OAUTH_TOKEN;if(e?.trim())return{token:e.trim(),authType:"oauth"};let t=process.env.CLAUDE_CODE_OAUTH_TOKEN;if(t?.trim())return{token:t.trim(),authType:"oauth"};let n=process.env.ANTHROPIC_API_KEY;if(n?.trim())return{token:n.trim(),authType:"api-key"};let o=R();if(o)return{token:o.token,authType:o.authType};let s=Q.homedir(),u=[P.join(s,".openclaw","agents","main","agent","auth-profiles.json"),P.join(s,".openclaw","auth-profiles.json"),P.join(s,".openclaw","credentials","oauth.json"),P.join(s,".claude","oauth.json"),P.join(s,".config","claude","oauth.json"),P.join(s,".config","anthropic","oauth.json")];for(let y of u){let g=re(y);if(g)return{token:g,authType:"oauth"}}return null}function re(r){if(!Y(r))return"";try{let e=JSON.parse(X(r,"utf8"));if(e.profiles){let t="";for(let[n,o]of Object.entries(e.profiles))if(n.startsWith("anthropic")){if(o.type==="oauth"&&o.access){if(o.expires&&Date.now()>o.expires)continue;return o.access}o.type==="token"&&o.token&&(t=o.token)}if(t)return t}return e.anthropic?.access?e.anthropic.access:e.anthropic?.token?e.anthropic.token:""}catch{return""}}var B=[{id:"claude-sonnet-4-20250514",label:"anthropic/claude-sonnet-4",hint:"Claude Sonnet 4 \xB7 ctx 200k \xB7 recommended"},{id:"claude-opus-4-20250514",label:"anthropic/claude-opus-4-5",hint:"Claude Opus 4.5 \xB7 ctx 200k \xB7 reasoning"},{id:"claude-haiku-4-20250514",label:"anthropic/claude-haiku-4",hint:"Claude Haiku 4 \xB7 ctx 200k \xB7 fast"}];async function se(){let r=process.env.ANTHROPIC_API_KEY;if(r?.trim()){let n=r.slice(0,10)+"..."+r.slice(-4),{useExisting:o}=await j({type:"confirm",name:"useExisting",message:`Use existing ANTHROPIC_API_KEY (env, ${n})?`,initial:!0});if(o)return{token:r.trim(),authType:"api-key"}}let e=R();if(e?.authType==="api-key"&&e.token){let n=e.token.slice(0,10)+"..."+e.token.slice(-4),{useExisting:o}=await j({type:"confirm",name:"useExisting",message:`Use stored API key (${n})?`,initial:!0});if(o)return{token:e.token,authType:"api-key"}}let{apiKey:t}=await j({type:"password",name:"apiKey",message:"Enter Anthropic API key",validate:n=>n.startsWith("sk-ant-api")?n.length<40?"API key seems too short":!0:"API key must start with sk-ant-api"});return t?{token:t,authType:"api-key"}:null}async function ie(){C.break();let r=46,e="Run `claude setup-token` in your terminal.",t="Then paste the generated token below.";console.log(` ${U.cyan("Anthropic setup-token")} ${"\u2500".repeat(r-21-1)}\u256E`),console.log(` ${" ".repeat(r)}\u2502`),console.log(` ${e}${" ".repeat(r-e.length)}\u2502`),console.log(` ${t}${" ".repeat(r-t.length)}\u2502`),console.log(` ${" ".repeat(r)}\u2502`),console.log(` ${"\u2500".repeat(r)}\u256F`),C.break();let{token:n}=await j({type:"password",name:"token",message:"Paste Anthropic setup-token",validate:s=>s.trim()?s.length<40?"Token seems too short":!0:"Token is required"});if(!n)return null;let{tokenName:o}=await j({type:"text",name:"tokenName",message:"Token name (blank = default)"});return{token:n,authType:"oauth"}}async function ae(){for(;;){let{provider:r}=await j({type:"select",name:"provider",message:"Model/auth provider",choices:[{title:"Anthropic",value:"anthropic",description:"setup-token + API key"}]});if(r===void 0)return null;let{method:e}=await j({type:"select",name:"method",message:"Anthropic auth method",choices:[{title:"Anthropic token (paste setup-token)",value:"setup-token",description:"run `claude setup-token` elsewhere, then paste the token here"},{title:"Anthropic API key",value:"api-key"},{title:U.dim("Back"),value:"back"}]});if(e===void 0)return null;if(e!=="back")return e==="api-key"?se():e==="setup-token"?ie():null}}async function le(){let r=R(),e=B.map(n=>({title:n.label,value:n.id,description:n.hint+(r?.model===n.id?" \xB7 current":"")}));if(r?.model){let n=B.find(s=>s.id===r.model),o=n?n.label:r.model;e.unshift({title:`Keep current (${o})`,value:r.model,description:"no change"})}let{modelId:t}=await j({type:"select",name:"modelId",message:"Default model",choices:e});return t||null}async function ce(){let r=await ae();if(!r)return null;let e=await le();if(!e)return null;let t={provider:r.authType==="oauth"?"claude-code":"claude",authType:r.authType,token:r.token,model:e};return oe(t),C.break(),C.success("Configuration saved to ~/.agentx/auth.json"),console.log(` Provider: ${U.bold("anthropic")} (${t.authType})`),console.log(` Model: ${U.bold(B.find(n=>n.id===t.model)?.label||t.model)}`),t}async function _e(r){return N(r)?!0:(C.warn("No AI credentials configured."),C.break(),await ce()!==null)}var L=[{name:"create_files",description:"Create one or more files as output. Use this when you need to generate code, documents, configs, or any file-based output.",input_schema:{type:"object",properties:{files:{type:"array",items:{type:"object",properties:{path:{type:"string",description:"Relative file path from project root (e.g., src/components/Button.tsx)"},content:{type:"string",description:"The full content of the file"},language:{type:"string",description:"Programming language or file type"},description:{type:"string",description:"Brief description of what this file does"}},required:["path","content"]}},summary:{type:"string",description:"Brief summary of all generated files"}},required:["files"]},permission:"file-write"},{name:"ask_user",description:"Ask the user a clarifying question when you need more information to proceed. Use this when the request is ambiguous or you need to confirm important decisions.",input_schema:{type:"object",properties:{question:{type:"string",description:"The question to ask the user"},options:{type:"array",items:{type:"string"},description:"Optional list of choices for the user"}},required:["question"]},permission:"none"},{name:"read_file",description:"Read the contents of a file. Use this to inspect existing code, understand patterns, check implementations, or gather context before generating code.",input_schema:{type:"object",properties:{path:{type:"string",description:"Relative file path from project root"},max_lines:{type:"number",description:"Maximum number of lines to read. Defaults to 500. Use for large files."}},required:["path"]},permission:"file-read"},{name:"search_files",description:"Search for files matching a pattern and optionally search their content with a regex. Use this to find relevant code, understand project structure, or locate specific patterns.",input_schema:{type:"object",properties:{pattern:{type:"string",description:"Glob pattern to match files (e.g., 'src/**/*.ts', '*.json')"},content_regex:{type:"string",description:"Optional regex to search within matched files. Returns matching lines."},max_results:{type:"number",description:"Maximum number of results to return. Default: 50."}},required:["pattern"]},permission:"none"},{name:"list_directory",description:"List files and directories at a given path. Use this to explore project structure.",input_schema:{type:"object",properties:{path:{type:"string",description:"Relative directory path from project root. Defaults to '.' (root)."},recursive:{type:"boolean",description:"List recursively. Default: false."},max_depth:{type:"number",description:"Maximum depth for recursive listing. Default: 3."}}},permission:"none"},{name:"run_command",description:"Execute a shell command. Use this to run build tools, test commands, linters, or inspect the environment. Commands run in the project root directory.",input_schema:{type:"object",properties:{command:{type:"string",description:"The shell command to execute"},timeout:{type:"number",description:"Timeout in milliseconds. Default: 30000 (30 seconds)."}},required:["command"]},permission:"command"},{name:"edit_file",description:"Apply search-and-replace edits to an existing file. Use this for targeted modifications to existing code rather than rewriting entire files.",input_schema:{type:"object",properties:{path:{type:"string",description:"Relative file path from project root"},edits:{type:"array",items:{type:"object",properties:{old_text:{type:"string",description:"The exact text to find in the file"},new_text:{type:"string",description:"The replacement text"}},required:["old_text","new_text"]},description:"List of search/replace pairs to apply in order"}},required:["path","edits"]},permission:"file-write"},{name:"spawn_agent",description:"Spawn a sub-agent to handle a task in the background. The sub-agent runs in its own session and returns results. Use this for parallel research, long-running tasks, or delegating work to a specialized agent.",input_schema:{type:"object",properties:{agent_id:{type:"string",description:"The ID of the agent to spawn (must be a registered agent)"},prompt:{type:"string",description:"The task description for the sub-agent"},timeout_seconds:{type:"number",description:"Maximum time to wait for completion in seconds. Default: 300 (5 minutes)."}},required:["agent_id","prompt"]},permission:"none"}];function pe(r){return(r?L.filter(t=>r.includes(t.name)):L).map(({name:t,description:n,input_schema:o})=>({name:t,description:n,input_schema:o}))}function I(){return pe(["create_files","ask_user"])}function xe(){let r=L.filter(t=>t.name!=="create_files"&&t.name!=="ask_user");if(r.length===0)return"";let e=["# Available Capabilities","In addition to generating files, you have the following capabilities:",""];for(let t of r)e.push(`## ${t.name}`),e.push(t.description),e.push("");return e.join(`
|
|
2
|
+
`)}var Ae=L.map(r=>r.name);var V="claude-sonnet-4-20250514",W=8192,M=class{name="claude";apiKey;authType="api-key";constructor(e){if(this.apiKey=e||process.env.ANTHROPIC_API_KEY||"",!this.apiKey){let t=N();t&&(this.apiKey=t.token,this.authType=t.authType)}if(!this.apiKey)throw new Error("Anthropic API key required. Run `agentx model` to configure, set ANTHROPIC_API_KEY, or pass --api-key.")}async generate(e,t){let n=t?.model||V,o=t?.maxTokens||W,s=e.find(c=>c.role==="system"),u=e.filter(c=>c.role!=="system").map(c=>({role:c.role,content:c.content})),y={model:n,max_tokens:o,messages:u,tools:I()};s&&(y.system=s.content),t?.temperature!==void 0&&(y.temperature=t.temperature);let g=await this.callApi(y);return this.parseResponse(g)}async generateRaw(e,t,n,o){let s=o?.model||V,u=o?.maxTokens||W,y={model:s,max_tokens:u,system:t,messages:e,tools:n};o?.temperature!==void 0&&(y.temperature=o.temperature);let g=await this.callApi(y);return{content:g.content.map(d=>d.type==="text"?{type:"text",text:d.text||""}:d.type==="tool_use"?{type:"tool_use",id:d.id||"",name:d.name||"",input:d.input||{}}:{type:"text",text:""}),stop_reason:g.stop_reason,usage:g.usage}}async*stream(e,t){let n=t?.model||V,o=t?.maxTokens||W,s=e.find(f=>f.role==="system"),u=e.filter(f=>f.role!=="system").map(f=>({role:f.role,content:f.content})),y={model:n,max_tokens:o,messages:u,stream:!0,tools:I()};s&&(y.system=s.content);let g=this.buildHeaders(),c=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:g,body:JSON.stringify(y)});if(!c.ok){let f=await c.text();yield{type:"error",error:`Anthropic API error (${c.status}): ${f}`};return}let d=c.body?.getReader();if(!d){yield{type:"error",error:"No response body"};return}let m=new TextDecoder,l="",_=[],v="",w,A=0,i=null,k=f=>{let O=(f||"").trim();if(!O||O.includes("```"))return;let T=O.replace(/\r\n/g,`
|
|
3
|
+
`).split(`
|
|
4
|
+
`),x=T.slice(Math.max(0,T.length-20)),p=x.join(`
|
|
5
|
+
`);if(/\b(plan|proposal)\b/i.test(p)&&/(ready for your review|review (the )?plan|requesting plan approval|awaiting approval|waiting for (your )?approval|approve (the )?plan|approval to proceed)/i.test(p))return`The provider is requesting plan approval.
|
|
6
|
+
Reply with:
|
|
7
|
+
- approve
|
|
8
|
+
- revise: <what to change>
|
|
9
|
+
- cancel`;let h=b=>/^(question|clarification|clarify|i need|need more|before i proceed|to proceed|please (confirm|clarify)|which|what|where|when|how|do you)/i.test(b.trim()),D=-1;for(let b=x.length-1;b>=0;b--){let E=x[b].trim();if(E&&(h(E)||E.includes("?"))){D=b;break}}if(D===-1)return;let S=[];for(let b=D;b<x.length&&S.length<8;b++){let E=x[b],K=E.trim();if(S.length>0&&!K||S.length>0&&!/^(options?:|[-*]\s|\d+[\).]\s)/i.test(K)&&!K.includes("?"))break;S.push(E.trimEnd())}let $=S.join(`
|
|
10
|
+
`).trim();if(!($.length<5))return $.length>800?$.slice(0,800).trimEnd():$};try{for(;;){let{done:f,value:O}=await d.read();if(f)break;l+=m.decode(O,{stream:!0});let T=l.split(`
|
|
11
|
+
`);l=T.pop()||"";for(let x of T){if(!x.startsWith("data: "))continue;let p=x.slice(6).trim();if(p!=="[DONE]")try{let a=JSON.parse(p);if(a.type==="content_block_start"&&a.content_block?.type==="tool_use"){if(i){yield{type:"tool_use_end",name:i.name};try{let h=JSON.parse(i.json||"{}");i.name==="create_files"&&h&&(Array.isArray(h.files)&&_.push(...h.files),typeof h.summary=="string"&&h.summary.trim()&&(v+=`
|
|
12
|
+
${h.summary}`)),i.name==="ask_user"&&h&&typeof h.question=="string"&&h.question.trim()&&(w=h.question,Array.isArray(h.options)&&h.options.length&&(w+=`
|
|
13
|
+
Options: ${h.options.join(", ")}`))}catch{}finally{i=null}}i={name:a.content_block.name,id:a.content_block.id,json:""},yield{type:"tool_use_start",name:a.content_block.name,id:a.content_block.id}}if(a.type==="content_block_delta"&&(a.delta?.type==="text_delta"&&(v+=a.delta.text,yield{type:"text_delta",text:a.delta.text}),a.delta?.type==="input_json_delta"&&(i&&(i.json+=a.delta.partial_json||""),yield{type:"tool_use_delta",json:a.delta.partial_json})),a.type==="content_block_stop"&&i){yield{type:"tool_use_end",name:i.name};try{let h=JSON.parse(i.json||"{}");i.name==="create_files"&&h&&(Array.isArray(h.files)&&_.push(...h.files),typeof h.summary=="string"&&h.summary.trim()&&(v+=`
|
|
14
|
+
${h.summary}`)),i.name==="ask_user"&&h&&typeof h.question=="string"&&h.question.trim()&&(w=h.question,Array.isArray(h.options)&&h.options.length&&(w+=`
|
|
15
|
+
Options: ${h.options.join(", ")}`))}catch{}finally{i=null}}a.type==="message_delta"&&a.usage&&(A=(a.usage.input_tokens||0)+(a.usage.output_tokens||0)),a.type}catch{}}}}finally{d.releaseLock()}if(i){yield{type:"tool_use_end",name:i.name};try{let f=JSON.parse(i.json||"{}");i.name==="create_files"&&f&&(Array.isArray(f.files)&&_.push(...f.files),typeof f.summary=="string"&&f.summary.trim()&&(v+=`
|
|
16
|
+
${f.summary}`)),i.name==="ask_user"&&f&&typeof f.question=="string"&&f.question.trim()&&(w=f.question,Array.isArray(f.options)&&f.options.length&&(w+=`
|
|
17
|
+
Options: ${f.options.join(", ")}`))}catch{}i=null}!w&&_.length===0&&(w=k(v)),yield{type:"done",result:{content:v,files:_,followUp:w,tokensUsed:A}}}buildHeaders(){let e={"Content-Type":"application/json","anthropic-version":"2023-06-01"};return this.authType==="oauth"?(e.Authorization=`Bearer ${this.apiKey}`,e["anthropic-beta"]="claude-code-20250219,oauth-2025-04-20",e["user-agent"]="claude-cli/2.1.2 (external, cli)",e["x-app"]="cli",e["anthropic-dangerous-direct-browser-access"]="true"):e["x-api-key"]=this.apiKey,e}async callApi(e){let t=this.buildHeaders(),n=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:t,body:JSON.stringify(e)});if(!n.ok){let o=await n.text();throw new Error(`Anthropic API error (${n.status}): ${o}`)}return await n.json()}parseResponse(e){let t=[],n="",o;for(let s of e.content)if(s.type==="text"&&(n+=s.text||""),s.type==="tool_use"){if(s.name==="create_files"&&s.input){let u=s.input;t.push(...u.files||[]),u.summary&&(n+=`
|
|
18
|
+
${u.summary}`)}if(s.name==="ask_user"&&s.input){let u=s.input;o=u.question,u.options?.length&&(o+=`
|
|
19
|
+
Options: ${u.options.join(", ")}`)}}return{content:n,files:t,followUp:o,tokensUsed:e.usage.input_tokens+e.usage.output_tokens}}};import{execa as Z}from"execa";var F="claude-sonnet-4-20250514",z=8192,ee={"claude-sonnet-4-20250514":"sonnet","claude-opus-4-20250514":"opus","claude-haiku-4-20250514":"haiku"},G=class{name="claude-code";credential;constructor(){let e=R();if(e){this.credential={type:e.authType,token:e.token};return}let t=N();if(!t)throw new Error(`No Claude credentials found.
|
|
20
|
+
Options:
|
|
21
|
+
1. Run \`agentx model\` to configure credentials
|
|
22
|
+
2. Set ANTHROPIC_API_KEY environment variable
|
|
23
|
+
3. Set ANTHROPIC_OAUTH_TOKEN environment variable`);this.credential={type:t.authType,token:t.token}}async generate(e,t){return this.credential.type==="oauth"?this.generateViaCli(e,t):this.generateViaApi(e,t)}async generateRaw(e,t,n,o){if(this.credential.type==="oauth")throw new Error("generateRaw() not available for OAuth/CLI mode");let s=o?.model||F,u=o?.maxTokens||z,y={model:s,max_tokens:u,system:t,messages:e,tools:n};o?.temperature!==void 0&&(y.temperature=o.temperature);let g={"Content-Type":"application/json","anthropic-version":"2023-06-01","x-api-key":this.credential.token},c=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:g,body:JSON.stringify(y)});if(!c.ok){let l=await c.text();throw new Error(`Anthropic API error (${c.status}): ${l}`)}let d=await c.json();return{content:d.content.map(l=>l.type==="text"?{type:"text",text:l.text||""}:l.type==="tool_use"?{type:"tool_use",id:l.id||"",name:l.name||"",input:l.input||{}}:{type:"text",text:""}),stop_reason:d.stop_reason,usage:d.usage}}get supportsAgenticLoop(){return this.credential.type==="api-key"}async generateViaCli(e,t){let n=t?.model||R()?.model||F,o=ee[n]||n,s=e.find(l=>l.role==="system"),y=e.filter(l=>l.role==="user").map(l=>l.content).join(`
|
|
24
|
+
|
|
25
|
+
`),g=["-p","--output-format","json","--model",o,"--dangerously-skip-permissions"];s&&g.push("--append-system-prompt",s.content),g.push(y);let c={...process.env};delete c.ANTHROPIC_API_KEY,delete c.ANTHROPIC_API_KEY_OLD;let d=await Z("claude",g,{env:c,extendEnv:!1,reject:!1,timeout:3e5,stdin:"ignore"});if(d.exitCode!==0){let l=d.stderr||d.stdout||"Claude CLI failed";throw new Error(`Claude CLI error: ${l}`)}let m=d.stdout.trim();try{let l=JSON.parse(m);if(l.is_error&&l.result)throw new Error(l.result)}catch(l){if(l.message&&!l.message.includes("JSON"))throw l}return this.parseCliOutput(m)}async generateViaApi(e,t){let n=t?.model||F,o=t?.maxTokens||z,s=e.find(m=>m.role==="system"),u=e.filter(m=>m.role!=="system").map(m=>({role:m.role,content:m.content})),y={model:n,max_tokens:o,messages:u,tools:I()};s&&(y.system=s.content),t?.temperature!==void 0&&(y.temperature=t.temperature);let g={"Content-Type":"application/json","anthropic-version":"2023-06-01","x-api-key":this.credential.token},c=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:g,body:JSON.stringify(y)});if(!c.ok){let m=await c.text();throw new Error(`Anthropic API error (${c.status}): ${m}`)}let d=await c.json();return this.parseApiResponse(d)}async*stream(e,t){this.credential.type==="oauth"?yield*this.streamViaCli(e,t):yield*this.streamViaApi(e,t)}async*streamViaCli(e,t){let n=t?.model||R()?.model||F,o=ee[n]||n,s=e.find(i=>i.role==="system"),y=e.filter(i=>i.role==="user").map(i=>i.content).join(`
|
|
26
|
+
|
|
27
|
+
`),g=["-p","--output-format","stream-json","--model",o,"--dangerously-skip-permissions"];s&&g.push("--append-system-prompt",s.content),g.push(y);let c={...process.env};delete c.ANTHROPIC_API_KEY,delete c.ANTHROPIC_API_KEY_OLD;let d=Z("claude",g,{env:c,extendEnv:!1,reject:!1,timeout:3e5,stdin:"ignore"}),m="",l="",_;if(d.stderr&&d.stderr.on("data",i=>{l+=Buffer.isBuffer(i)?i.toString("utf8"):String(i)}),d.stdout){let i=new TextDecoder,k=d.stdout;for await(let f of k){let T=(typeof f=="string"?f:i.decode(f,{stream:!0})).split(`
|
|
28
|
+
`).filter(Boolean);for(let x of T)try{let p=JSON.parse(x);if(p.type==="error"){_=String(p.error||p.message||"Claude CLI error");continue}if(p.is_error&&(p.result||p.message)){_=String(p.result||p.message);continue}p.type==="assistant"&&p.message?(m+=p.message,yield{type:"text_delta",text:p.message}):p.type==="result"&&(m=p.result||m)}catch{m+=x,yield{type:"text_delta",text:x}}}}let v=await d;if(v.exitCode!==0&&!_&&(_=(l||v.stderr||v.stdout||"Claude CLI failed").toString().trim()),_){yield{type:"error",error:`Claude CLI error: ${_}`};return}let w=this.extractFilesFromText(m),A=w.length===0?this.inferFollowUpFromText(m):void 0;yield{type:"done",result:{content:m,files:w,followUp:A,tokensUsed:0}}}async*streamViaApi(e,t){let n=t?.model||F,o=t?.maxTokens||z,s=e.find(k=>k.role==="system"),u=e.filter(k=>k.role!=="system").map(k=>({role:k.role,content:k.content})),y={model:n,max_tokens:o,messages:u,tools:I(),stream:!0};s&&(y.system=s.content);let g={"Content-Type":"application/json","anthropic-version":"2023-06-01","x-api-key":this.credential.token},c=await fetch("https://api.anthropic.com/v1/messages",{method:"POST",headers:g,body:JSON.stringify(y)});if(!c.ok){let k=await c.text();yield{type:"error",error:`Anthropic API error (${c.status}): ${k}`};return}let d=c.body?.getReader();if(!d){yield{type:"error",error:"No response body"};return}let m=new TextDecoder,l="",_="",v=0,w=[],A,i=null;try{for(;;){let{done:k,value:f}=await d.read();if(k)break;l+=m.decode(f,{stream:!0});let O=l.split(`
|
|
29
|
+
`);l=O.pop()||"";for(let T of O){if(!T.startsWith("data: "))continue;let x=T.slice(6).trim();if(x!=="[DONE]")try{let p=JSON.parse(x);if(p.type==="content_block_start"&&p.content_block?.type==="tool_use"){if(i){yield{type:"tool_use_end",name:i.name};try{let a=JSON.parse(i.json||"{}");i.name==="create_files"&&a&&(Array.isArray(a.files)&&w.push(...a.files),typeof a.summary=="string"&&a.summary.trim()&&(_+=`
|
|
30
|
+
${a.summary}`)),i.name==="ask_user"&&a&&typeof a.question=="string"&&a.question.trim()&&(A=a.question,Array.isArray(a.options)&&a.options.length&&(A+=`
|
|
31
|
+
Options: ${a.options.join(", ")}`))}catch{}finally{i=null}}i={name:p.content_block.name,id:p.content_block.id,json:""},yield{type:"tool_use_start",name:i.name,id:i.id}}if(p.type==="content_block_delta"&&p.delta?.type==="text_delta"&&(_+=p.delta.text,yield{type:"text_delta",text:p.delta.text}),p.type==="content_block_delta"&&p.delta?.type==="input_json_delta"&&(i&&(i.json+=p.delta.partial_json||""),yield{type:"tool_use_delta",json:p.delta.partial_json}),p.type==="content_block_stop"&&i){yield{type:"tool_use_end",name:i.name};try{let a=JSON.parse(i.json||"{}");i.name==="create_files"&&a&&(Array.isArray(a.files)&&w.push(...a.files),typeof a.summary=="string"&&a.summary.trim()&&(_+=`
|
|
32
|
+
${a.summary}`)),i.name==="ask_user"&&a&&typeof a.question=="string"&&a.question.trim()&&(A=a.question,Array.isArray(a.options)&&a.options.length&&(A+=`
|
|
33
|
+
Options: ${a.options.join(", ")}`))}catch{}finally{i=null}}p.type==="message_delta"&&p.usage&&(v=(p.usage.input_tokens||0)+(p.usage.output_tokens||0))}catch{}}}}finally{d.releaseLock()}if(i){yield{type:"tool_use_end",name:i.name};try{let k=JSON.parse(i.json||"{}");i.name==="create_files"&&k&&(Array.isArray(k.files)&&w.push(...k.files),typeof k.summary=="string"&&k.summary.trim()&&(_+=`
|
|
34
|
+
${k.summary}`)),i.name==="ask_user"&&k&&typeof k.question=="string"&&k.question.trim()&&(A=k.question,Array.isArray(k.options)&&k.options.length&&(A+=`
|
|
35
|
+
Options: ${k.options.join(", ")}`))}catch{}i=null}w.length===0&&w.push(...this.extractFilesFromText(_)),!A&&w.length===0&&(A=this.inferFollowUpFromText(_)),yield{type:"done",result:{content:_,files:w,followUp:A,tokensUsed:v}}}parseCliOutput(e){let t;try{t=JSON.parse(e)}catch{let u=this.inferFollowUpFromText(e.trim());return{content:e.trim(),files:[],followUp:u,tokensUsed:0}}let n=t.result||t.text||t.content||"",o=this.extractFilesFromText(n),s=o.length===0?this.inferFollowUpFromText(n):void 0;return{content:n,files:o,followUp:s,tokensUsed:t.usage?(t.usage.input_tokens||0)+(t.usage.output_tokens||0):0}}extractFilesFromText(e){let t=[],n=/```[\w]*\s*([\w/._-]+\.\w+)\n([\s\S]*?)```/g,o;for(;(o=n.exec(e))!==null;){let s=o[1],u=o[2];s&&u&&t.push({path:s,content:u.trimEnd()})}return t}inferFollowUpFromText(e){let t=(e||"").trim();if(!t||t.includes("```"))return;let n=t.replace(/\r\n/g,`
|
|
36
|
+
`).split(`
|
|
37
|
+
`),o=n.slice(Math.max(0,n.length-20)),s=o.join(`
|
|
38
|
+
`);if(/\b(plan|proposal)\b/i.test(s)&&/(ready for your review|review (the )?plan|requesting plan approval|awaiting approval|waiting for (your )?approval|approve (the )?plan|approval to proceed)/i.test(s))return`The provider is requesting plan approval.
|
|
39
|
+
Reply with:
|
|
40
|
+
- approve
|
|
41
|
+
- revise: <what to change>
|
|
42
|
+
- cancel`;let y=m=>/^(question|clarification|clarify|i need|need more|before i proceed|to proceed|please (confirm|clarify)|which|what|where|when|how|do you)/i.test(m.trim()),g=-1;for(let m=o.length-1;m>=0;m--){let l=o[m].trim();if(l&&(y(l)||l.includes("?"))){g=m;break}}if(g===-1)return;let c=[];for(let m=g;m<o.length&&c.length<8;m++){let l=o[m],_=l.trim();if(c.length>0&&!_||c.length>0&&!/^(options?:|[-*]\s|\d+[\).]\s)/i.test(_)&&!_.includes("?"))break;c.push(l.trimEnd())}let d=c.join(`
|
|
43
|
+
`).trim();if(!(d.length<5))return d.length>800?d.slice(0,800).trimEnd():d}parseApiResponse(e){let t=[],n="",o;for(let s of e.content)if(s.type==="text"&&(n+=s.text||""),s.type==="tool_use"){if(s.name==="create_files"&&s.input){let u=s.input;t.push(...u.files||[]),u.summary&&(n+=`
|
|
44
|
+
${u.summary}`)}if(s.name==="ask_user"&&s.input){let u=s.input;o=u.question,u.options?.length&&(o+=`
|
|
45
|
+
Options: ${u.options.join(", ")}`)}}return{content:n,files:t,followUp:o,tokensUsed:e.usage.input_tokens+e.usage.output_tokens}}};function Ne(r="claude-code",e){let t=r;if(r==="claude-code"&&!e){let n=R();n&&(t=n.provider)}switch(t){case"claude-code":return new G;case"claude":return new M(e);case"openai":throw new Error("OpenAI provider coming soon. Set provider to 'claude-code' or contribute at github.com/anis-marrouchi/agentx");case"ollama":throw new Error("Ollama provider coming soon. Set provider to 'claude-code' or contribute at github.com/anis-marrouchi/agentx");default:throw new Error(`Unknown provider: ${t}. Supported: claude-code, claude`)}}export{C as a,R as b,oe as c,N as d,ce as e,_e as f,pe as g,I as h,xe as i,Ae as j,M as k,G as l,Ne as m};
|
|
46
|
+
//# sourceMappingURL=chunk-HCFSHGK4.js.map
|