agentix-cli 0.22.0 → 0.25.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/LICENSE +21 -0
- package/README.md +102 -103
- package/agentx.example.json +9 -3
- package/dist/App-6Y7B3YQH.js +8 -0
- package/dist/App-6Y7B3YQH.js.map +1 -0
- package/dist/ChatApp-KIEUJ2DS.js +7 -0
- package/dist/ChatApp-KIEUJ2DS.js.map +1 -0
- package/dist/{agent-Y4U4R3ZZ.js → agent-QGZIAZMY.js} +2 -2
- package/dist/architect-SEPWMWS4.js +2 -0
- package/dist/{board-dashboard-2CPBWFOB.js → board-dashboard-CQ5F4GPD.js} +3756 -1010
- package/dist/board-dashboard-CQ5F4GPD.js.map +1 -0
- package/dist/builtin-FRKAQMFS.js +2 -0
- package/dist/chunk-2WVX3LEC.js +2 -0
- package/dist/chunk-2WVX3LEC.js.map +1 -0
- package/dist/chunk-3WQFCIB6.js +24 -0
- package/dist/chunk-3WQFCIB6.js.map +1 -0
- package/dist/chunk-53GCFZPR.js +116 -0
- package/dist/chunk-53GCFZPR.js.map +1 -0
- package/dist/{chunk-IKBHHTIT.js → chunk-5U35WAZF.js} +2 -2
- package/dist/chunk-67Q4DCPS.js +54 -0
- package/dist/chunk-67Q4DCPS.js.map +1 -0
- package/dist/chunk-76Z4VHDX.js +8 -0
- package/dist/chunk-76Z4VHDX.js.map +1 -0
- package/dist/{chunk-7P5QRX5W.js → chunk-BFXONUUN.js} +2 -2
- package/dist/{chunk-P3TJHGO5.js → chunk-FDMWOPEL.js} +2 -2
- package/dist/chunk-FDWOECJY.js +54 -0
- package/dist/chunk-FDWOECJY.js.map +1 -0
- package/dist/chunk-G57RZCXM.js +5 -0
- package/dist/chunk-G57RZCXM.js.map +1 -0
- package/dist/{chunk-NOF4HSGK.js → chunk-H5D7ATXS.js} +2 -2
- package/dist/chunk-HA3XI7Z2.js +155 -0
- package/dist/chunk-HA3XI7Z2.js.map +1 -0
- package/dist/chunk-HR4JSRZS.js +3 -0
- package/dist/{chunk-A6BKSZQA.js.map → chunk-HR4JSRZS.js.map} +1 -1
- package/dist/chunk-I3SIXTJO.js +44 -0
- package/dist/chunk-I3SIXTJO.js.map +1 -0
- package/dist/chunk-ISEBLHWX.js +118 -0
- package/dist/chunk-ISEBLHWX.js.map +1 -0
- package/dist/{chunk-4ITQP3XL.js → chunk-JP5IEXR7.js} +2 -2
- package/dist/chunk-JXWAGMI2.js +13 -0
- package/dist/chunk-JXWAGMI2.js.map +1 -0
- package/dist/chunk-KUCCW2VG.js +431 -0
- package/dist/chunk-KUCCW2VG.js.map +1 -0
- package/dist/chunk-LDHPMJ7M.js +257 -0
- package/dist/chunk-LDHPMJ7M.js.map +1 -0
- package/dist/{chunk-WHP4I44J.js → chunk-LNXCJWOU.js} +29 -30
- package/dist/chunk-LNXCJWOU.js.map +1 -0
- package/dist/chunk-M4AMWP3H.js +178 -0
- package/dist/chunk-M4AMWP3H.js.map +1 -0
- package/dist/chunk-NGSIRE4X.js +126 -0
- package/dist/chunk-NGSIRE4X.js.map +1 -0
- package/dist/{chunk-CNNLREHD.js → chunk-P4VWIVKN.js} +268 -410
- package/dist/chunk-P4VWIVKN.js.map +1 -0
- package/dist/chunk-Q6YO7LYE.js +47 -0
- package/dist/chunk-Q6YO7LYE.js.map +1 -0
- package/dist/chunk-TMKLHTCG.js +971 -0
- package/dist/chunk-TMKLHTCG.js.map +1 -0
- package/dist/chunk-TRJTTBE2.js +7 -0
- package/dist/chunk-TRJTTBE2.js.map +1 -0
- package/dist/chunk-U2CKL755.js +4 -0
- package/dist/chunk-U2CKL755.js.map +1 -0
- package/dist/chunk-UCQ2N3KO.js +9 -0
- package/dist/chunk-UCQ2N3KO.js.map +1 -0
- package/dist/chunk-W4YR2RO2.js +2 -0
- package/dist/chunk-W4YR2RO2.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/client-GGVVLOOF.js +2 -0
- package/dist/{compaction-BTUJJ3LJ.js → compaction-NWEMOSHA.js} +3 -3
- package/dist/config-7FSZFRTQ.js +2 -0
- package/dist/config-mutator-POCOYYJ4.js +2 -0
- package/dist/{context-planner-I7ID7XF3.js → context-planner-45GEWKGL.js} +2 -2
- package/dist/doctor-DHLBEKA4.js +2 -0
- package/dist/heal-HZ3EQD52.js +2 -0
- package/dist/index.d.ts +1244 -225
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/memory-extract-AENFVVO5.js +2 -0
- package/dist/processes-UIC4XXA6.js +2 -0
- package/dist/program-WVAVJFND.js +2 -0
- package/dist/projects-api-KRME2YKZ.js +2 -0
- package/dist/projects-api-KRME2YKZ.js.map +1 -0
- package/dist/projects-mutate-3NHVG437.js +7 -0
- package/dist/projects-mutate-3NHVG437.js.map +1 -0
- package/dist/providers-L27BJ5A6.js +2 -0
- package/dist/registry-T36ZIAWV.js +2 -0
- package/dist/rotation-memo-B3EXXSEH.js +13 -0
- package/dist/rotation-memo-B3EXXSEH.js.map +1 -0
- package/dist/scheduler-GH7TIKYS.js +2 -0
- package/dist/surface-usage-PRL6Z4U3.js +2 -0
- package/dist/wiki-M6YNRFC4.js +2 -0
- package/dist/workflows/templates/branching.yaml +75 -0
- package/dist/workflows/templates/extract.yaml +59 -0
- package/dist/workflows/templates/linear.yaml +37 -0
- package/dist/workflows/templates/retry.yaml +61 -0
- package/dist/workspace-setup-TBQAHYHJ.js +2 -0
- package/package.json +11 -3
- package/dist/board-dashboard-2CPBWFOB.js.map +0 -1
- package/dist/chunk-2HXSJYAJ.js +0 -15
- package/dist/chunk-2HXSJYAJ.js.map +0 -1
- package/dist/chunk-5F3J55LG.js +0 -2
- package/dist/chunk-5F3J55LG.js.map +0 -1
- package/dist/chunk-A6BKSZQA.js +0 -3
- package/dist/chunk-ABVZHJUO.js +0 -7
- package/dist/chunk-ABVZHJUO.js.map +0 -1
- package/dist/chunk-BHSPAP4U.js +0 -44
- package/dist/chunk-BHSPAP4U.js.map +0 -1
- package/dist/chunk-CDNWCYF7.js +0 -2
- package/dist/chunk-CDNWCYF7.js.map +0 -1
- package/dist/chunk-CNNLREHD.js.map +0 -1
- package/dist/chunk-CO4YCIIA.js +0 -13
- package/dist/chunk-CO4YCIIA.js.map +0 -1
- package/dist/chunk-EW27SCCC.js +0 -2
- package/dist/chunk-EW27SCCC.js.map +0 -1
- package/dist/chunk-GOLZ6NEL.js +0 -111
- package/dist/chunk-GOLZ6NEL.js.map +0 -1
- package/dist/chunk-JGXE2YNV.js +0 -46
- package/dist/chunk-JGXE2YNV.js.map +0 -1
- package/dist/chunk-KX626FN4.js +0 -6
- package/dist/chunk-KX626FN4.js.map +0 -1
- package/dist/chunk-MTRWXRFF.js +0 -380
- package/dist/chunk-MTRWXRFF.js.map +0 -1
- package/dist/chunk-N67JZAXU.js +0 -2
- package/dist/chunk-N67JZAXU.js.map +0 -1
- package/dist/chunk-OPKQNLGX.js +0 -450
- package/dist/chunk-OPKQNLGX.js.map +0 -1
- package/dist/chunk-OZOQKPJV.js +0 -102
- package/dist/chunk-OZOQKPJV.js.map +0 -1
- package/dist/chunk-VYLZ7UYM.js +0 -118
- package/dist/chunk-VYLZ7UYM.js.map +0 -1
- package/dist/chunk-WHP4I44J.js.map +0 -1
- package/dist/chunk-WJXU5F4Z.js +0 -53
- package/dist/chunk-WJXU5F4Z.js.map +0 -1
- package/dist/chunk-X5D5AV5N.js +0 -833
- package/dist/chunk-X5D5AV5N.js.map +0 -1
- package/dist/config-BLOKGMER.js +0 -2
- package/dist/config-mutator-RG7QY2EL.js +0 -2
- package/dist/doctor-ZTECTUHX.js +0 -2
- package/dist/editor-chat-RVLCKKB7.js +0 -137
- package/dist/editor-chat-RVLCKKB7.js.map +0 -1
- package/dist/heal-YEQYWX2L.js +0 -2
- package/dist/inbox-BIN2S5XN.js +0 -2
- package/dist/memory-extract-KRCHD3OP.js +0 -2
- package/dist/processes-RJ6RDP32.js +0 -2
- package/dist/program-GZOJIWTB.js +0 -2
- package/dist/providers-WVCMIW67.js +0 -2
- package/dist/registry-QZIUSQ5J.js +0 -2
- package/dist/rotation-memo-GMTZPMNE.js +0 -13
- package/dist/rotation-memo-GMTZPMNE.js.map +0 -1
- package/dist/scheduler-B2BQPV4M.js +0 -2
- package/dist/slack-6P7ZJ32Y.js +0 -3
- package/dist/slack-6P7ZJ32Y.js.map +0 -1
- package/dist/store-BAPDZHXE.js +0 -2
- package/dist/task-store-RHISEU6V.js +0 -2
- package/dist/telegram-PIYDRM46.js +0 -3
- package/dist/telegram-PIYDRM46.js.map +0 -1
- package/dist/types-ZRLHHCGB.js +0 -2
- package/dist/web/workflow-editor.global.js +0 -1452
- package/dist/web/workflow-editor.global.js.map +0 -1
- package/dist/whatsapp-PFRDMUMT.js +0 -3
- package/dist/whatsapp-PFRDMUMT.js.map +0 -1
- package/dist/wiki-53W5NLZV.js +0 -2
- package/dist/workspace-setup-DQCZX2NE.js +0 -2
- /package/dist/{agent-Y4U4R3ZZ.js.map → agent-QGZIAZMY.js.map} +0 -0
- /package/dist/{config-BLOKGMER.js.map → architect-SEPWMWS4.js.map} +0 -0
- /package/dist/{config-mutator-RG7QY2EL.js.map → builtin-FRKAQMFS.js.map} +0 -0
- /package/dist/{chunk-IKBHHTIT.js.map → chunk-5U35WAZF.js.map} +0 -0
- /package/dist/{chunk-7P5QRX5W.js.map → chunk-BFXONUUN.js.map} +0 -0
- /package/dist/{chunk-P3TJHGO5.js.map → chunk-FDMWOPEL.js.map} +0 -0
- /package/dist/{chunk-NOF4HSGK.js.map → chunk-H5D7ATXS.js.map} +0 -0
- /package/dist/{chunk-4ITQP3XL.js.map → chunk-JP5IEXR7.js.map} +0 -0
- /package/dist/{doctor-ZTECTUHX.js.map → client-GGVVLOOF.js.map} +0 -0
- /package/dist/{compaction-BTUJJ3LJ.js.map → compaction-NWEMOSHA.js.map} +0 -0
- /package/dist/{heal-YEQYWX2L.js.map → config-7FSZFRTQ.js.map} +0 -0
- /package/dist/{inbox-BIN2S5XN.js.map → config-mutator-POCOYYJ4.js.map} +0 -0
- /package/dist/{context-planner-I7ID7XF3.js.map → context-planner-45GEWKGL.js.map} +0 -0
- /package/dist/{memory-extract-KRCHD3OP.js.map → doctor-DHLBEKA4.js.map} +0 -0
- /package/dist/{processes-RJ6RDP32.js.map → heal-HZ3EQD52.js.map} +0 -0
- /package/dist/{program-GZOJIWTB.js.map → memory-extract-AENFVVO5.js.map} +0 -0
- /package/dist/{providers-WVCMIW67.js.map → processes-UIC4XXA6.js.map} +0 -0
- /package/dist/{registry-QZIUSQ5J.js.map → program-WVAVJFND.js.map} +0 -0
- /package/dist/{scheduler-B2BQPV4M.js.map → providers-L27BJ5A6.js.map} +0 -0
- /package/dist/{store-BAPDZHXE.js.map → registry-T36ZIAWV.js.map} +0 -0
- /package/dist/{task-store-RHISEU6V.js.map → scheduler-GH7TIKYS.js.map} +0 -0
- /package/dist/{types-ZRLHHCGB.js.map → surface-usage-PRL6Z4U3.js.map} +0 -0
- /package/dist/{wiki-53W5NLZV.js.map → wiki-M6YNRFC4.js.map} +0 -0
- /package/dist/{workspace-setup-DQCZX2NE.js.map → workspace-setup-TBQAHYHJ.js.map} +0 -0
package/dist/chunk-5F3J55LG.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{existsSync as c,mkdirSync as s,readFileSync as l,writeFileSync as g,renameSync as v}from"fs";import{resolve as o,dirname as u}from"path";var a=class{filePath;log;constructor(e={}){let r=e.baseDir??o(process.cwd(),".agentx");this.filePath=o(r,"handover.json"),this.log=e.log??console.error.bind(console,"[handover]"),s(r,{recursive:!0})}key(e,r,t){return`${e}::${r}::${t||"-"}`}load(){if(!c(this.filePath))return{version:1,overrides:{}};try{let e=l(this.filePath,"utf-8"),r=JSON.parse(e);return!r||typeof r!="object"?{version:1,overrides:{}}:(r.overrides||(r.overrides={}),r)}catch(e){return this.log(`load failed, starting empty: ${e?.message||e}`),{version:1,overrides:{}}}}save(e){s(u(this.filePath),{recursive:!0});let r=`${this.filePath}.tmp-${process.pid}`;g(r,JSON.stringify(e,null,2)),v(r,this.filePath)}get(e,r,t){let i=this.load().overrides[this.key(e,r,t)];if(i&&!(i.expiresAt&&Date.parse(i.expiresAt)<Date.now()))return i}set(e){if(!e.channel||!e.chatId||!e.toAgent)throw new Error("channel, chatId and toAgent are required");let r=this.load(),t={...e,createdAt:e.createdAt??new Date().toISOString()};return r.overrides[this.key(t.channel,t.chatId,t.accountId)]=t,this.save(r),t}remove(e,r,t){let n=this.load(),i=this.key(e,r,t);return i in n.overrides?(delete n.overrides[i],this.save(n),!0):!1}consumeSummary(e,r,t){let n=this.load(),i=n.overrides[this.key(e,r,t)];if(!i||!i.summary||i.summaryConsumedAt)return;let d=i.summary;return i.summaryConsumedAt=new Date().toISOString(),this.save(n),d}listActive(){let e=this.load(),r=Date.now();return Object.values(e.overrides).filter(t=>!t.expiresAt||Date.parse(t.expiresAt)>=r)}listByAgent(e){let r=this.listActive();return{incoming:r.filter(t=>t.toAgent===e),outgoing:r.filter(t=>t.fromAgent===e)}}};export{a};
|
|
2
|
-
//# sourceMappingURL=chunk-5F3J55LG.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/channels/handover-store.ts"],"sourcesContent":["import { existsSync, mkdirSync, readFileSync, writeFileSync, renameSync } from \"fs\"\nimport { resolve, dirname } from \"path\"\n\n// --- Runtime routing overrides (per-chat handovers) ------------------------\n//\n// A \"handover\" is an operator-initiated override: \"from now on, route\n// messages from <channel, chatId[, accountId]> to <toAgent>, regardless of\n// the config's static routes.\" Lightweight — persisted as plain JSON, read\n// at the top of the router's resolveAgent, consulted before config routes.\n//\n// Survival: on daemon restart, overrides stay in effect. The file IS the\n// source of truth.\n//\n// Context carryover: each override may carry a one-shot `summary` string.\n// The registry reads + clears it the first time it injects it into the\n// target agent's context, so the new agent sees \"[Handover note: …]\" once,\n// then subsequent messages just flow to the new agent without repetition.\n\nexport interface HandoverOverride {\n channel: string // \"telegram\" | \"whatsapp\" | ...\n chatId: string // DM: sender.id; group: group.id\n accountId?: string // telegram multi-account discriminator\n fromAgent: string\n toAgent: string\n summary?: string // one-shot note injected into target context\n createdAt: string // ISO\n createdBy?: string // operator that made the call\n /** When set, overrides past this time are treated as expired (ignored on\n * read, optionally pruned on access). null = indefinite. */\n expiresAt?: string | null\n /** Set after the summary has been injected once. Route stays active. */\n summaryConsumedAt?: string\n}\n\ninterface HandoverFile {\n version: number\n overrides: Record<string, HandoverOverride>\n}\n\nexport class HandoverStore {\n readonly filePath: string\n private log: (...args: unknown[]) => void\n\n constructor(opts: { baseDir?: string; log?: (...args: unknown[]) => void } = {}) {\n const base = opts.baseDir ?? resolve(process.cwd(), \".agentx\")\n this.filePath = resolve(base, \"handover.json\")\n this.log = opts.log ?? console.error.bind(console, \"[handover]\")\n mkdirSync(base, { recursive: true })\n }\n\n /** Canonical key for an override. Using \"-\" for missing accountId keeps\n * the key shape uniform across channels. */\n key(channel: string, chatId: string, accountId?: string): string {\n return `${channel}::${chatId}::${accountId || \"-\"}`\n }\n\n load(): HandoverFile {\n if (!existsSync(this.filePath)) return { version: 1, overrides: {} }\n try {\n const raw = readFileSync(this.filePath, \"utf-8\")\n const parsed = JSON.parse(raw) as HandoverFile\n if (!parsed || typeof parsed !== \"object\") return { version: 1, overrides: {} }\n if (!parsed.overrides) parsed.overrides = {}\n return parsed\n } catch (e: any) {\n this.log(`load failed, starting empty: ${e?.message || e}`)\n return { version: 1, overrides: {} }\n }\n }\n\n private save(file: HandoverFile): void {\n mkdirSync(dirname(this.filePath), { recursive: true })\n const tmp = `${this.filePath}.tmp-${process.pid}`\n writeFileSync(tmp, JSON.stringify(file, null, 2))\n renameSync(tmp, this.filePath)\n }\n\n /** Return the active override for a chat, or undefined. Skips expired. */\n get(channel: string, chatId: string, accountId?: string): HandoverOverride | undefined {\n const file = this.load()\n const o = file.overrides[this.key(channel, chatId, accountId)]\n if (!o) return undefined\n if (o.expiresAt && Date.parse(o.expiresAt) < Date.now()) return undefined\n return o\n }\n\n /** Create or replace an override. If `fromAgent` isn't supplied the caller\n * can pass \"\" and the router will fill it in from current routing. */\n set(o: Omit<HandoverOverride, \"createdAt\"> & { createdAt?: string }): HandoverOverride {\n if (!o.channel || !o.chatId || !o.toAgent) {\n throw new Error(\"channel, chatId and toAgent are required\")\n }\n const file = this.load()\n const rec: HandoverOverride = {\n ...o,\n createdAt: o.createdAt ?? new Date().toISOString(),\n }\n file.overrides[this.key(rec.channel, rec.chatId, rec.accountId)] = rec\n this.save(file)\n return rec\n }\n\n /** Remove an override (release the handover). Idempotent. */\n remove(channel: string, chatId: string, accountId?: string): boolean {\n const file = this.load()\n const k = this.key(channel, chatId, accountId)\n if (!(k in file.overrides)) return false\n delete file.overrides[k]\n this.save(file)\n return true\n }\n\n /** Mark the summary as consumed (after it's been injected once). Route\n * stays active; subsequent messages just skip the note. */\n consumeSummary(channel: string, chatId: string, accountId?: string): string | undefined {\n const file = this.load()\n const o = file.overrides[this.key(channel, chatId, accountId)]\n if (!o || !o.summary || o.summaryConsumedAt) return undefined\n const summary = o.summary\n o.summaryConsumedAt = new Date().toISOString()\n this.save(file)\n return summary\n }\n\n /** List all non-expired overrides. Used by the admin UI. */\n listActive(): HandoverOverride[] {\n const file = this.load()\n const now = Date.now()\n return Object.values(file.overrides).filter(\n (o) => !o.expiresAt || Date.parse(o.expiresAt) >= now,\n )\n }\n\n /** Overrides pointing at this agent (incoming) or away from it (outgoing). */\n listByAgent(agentId: string): { incoming: HandoverOverride[]; outgoing: HandoverOverride[] } {\n const all = this.listActive()\n return {\n incoming: all.filter((o) => o.toAgent === agentId),\n outgoing: all.filter((o) => o.fromAgent === agentId),\n }\n }\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","readFileSync","writeFileSync","renameSync","resolve","dirname","HandoverStore","opts","base","channel","chatId","accountId","raw","parsed","file","tmp","o","rec","k","summary","now","agentId","all"]}
|
package/dist/chunk-A6BKSZQA.js
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
import{readFileSync as a,existsSync as g}from"fs";import{resolve as p}from"path";function d(s){let e=[];for(let i of s.split(`
|
|
2
|
-
`)){let t=i.trim();if(!t||t.startsWith("#"))continue;let r=t.startsWith("export ")?t.slice(7).trim():t,o=r.indexOf("=");if(o===-1)continue;let c=r.slice(0,o).trim(),n=r.slice(o+1).trim();n.length>=2&&(n.startsWith('"')&&n.endsWith('"')||n.startsWith("'")&&n.endsWith("'"))&&(n=n.slice(1,-1)),c&&e.push([c,n])}return e}function u(s){let e={};for(let i of[".env",".env.gitlab"]){let t=p(s,i);if(g(t))try{for(let[r,o]of d(a(t,"utf-8")))e[r]=o}catch{}}return e}var f=new Map;function v(s=process.cwd()){let e=f.get(s);if(e)return e;let i=p(s,".env"),t;if(!g(i))t=new Set;else try{t=new Set(d(a(i,"utf-8")).map(([r])=>r))}catch{t=new Set}return f.set(s,t),t}function m(s,e=process.env,i=process.cwd()){let t=u(s),r=v(i),o={...e};for(let c of r)c in t||delete o[c];for(let[c,n]of Object.entries(t))o[c]=n;return o}export{m as a};
|
|
3
|
-
//# sourceMappingURL=chunk-A6BKSZQA.js.map
|
package/dist/chunk-ABVZHJUO.js
DELETED
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import{a as C,b as T}from"./chunk-P3TJHGO5.js";import{a as S,c as x}from"./chunk-GOLZ6NEL.js";import{a as L}from"./chunk-VJUTQAPJ.js";import{writeFileSync as P,mkdirSync as R,existsSync as D,readFileSync as q}from"fs";import{resolve as v}from"path";var H=["off","shadow","authoritative"],z={telegram:"INTENT_LEDGER_MODE_TELEGRAM",slack:"INTENT_LEDGER_MODE_SLACK",whatsapp:"INTENT_LEDGER_MODE_WHATSAPP",discord:"INTENT_LEDGER_MODE_DISCORD",gitlab:"INTENT_LEDGER_MODE_GITLAB",github:"INTENT_LEDGER_MODE_GITHUB",workflow:"INTENT_LEDGER_MODE_WORKFLOW",cron:"INTENT_LEDGER_MODE_CRON",mesh:"INTENT_LEDGER_MODE_MESH"},K="INTENT_LEDGER_MODE";function O(s,e=process.env){let n=_(e[z[s]]);if(n)return n;let t=_(e[K]);return t||"off"}function _(s){if(typeof s!="string")return null;let e=s.trim().toLowerCase();return H.includes(e)?e:null}var A;function X(s){A=s}function N(){return A}function k(s,e,n,t=Date.now,r){return s.db.transaction(i=>{let c=s.recordEvent(e),a=r??N(),u=a?.pmFor?.(c.project),d=u?`pm:${u}`:n.decidedBy,p=s.getDecisionsForEvent(c.id).find(h=>h.decidedBy===d);if(p)return p;let y=s.getActiveDecisionForSubject(c.project,c.subject);if(y){let h={eventId:c.id,decidedAt:i,decidedBy:d,agentId:null,outcome:"deduped",reason:`active dispatch in flight: ${y.decidedBy} \u2192 ${y.agentId??"?"}`};return s.recordDecision(h),h}let g=n.decide(c),l=g?g.agentId:null,f=g?g.outcome:"halted",m=g?g.reason:"no policy match";if(a?.canHandle&&f==="dispatched"&&l&&!a.canHandle(l,c.project,c.intent)){let $=l;l=null,f="halted",m=`org-chart: agent "${$}" cannot handle (${c.project??"no-project"}/${c.intent??"no-intent"})`}if(a?.withinDelegationBudget&&f==="dispatched"&&l&&!a.withinDelegationBudget(l,c.project,c.subject)){let h=l;l=null,f="halted",m=`delegation-budget: agent "${h}" depth-limit exceeded on ${c.project??"?"}/${c.subject??"?"}`}u&&f==="dispatched"&&l&&(m=m?`${m} (pm-gate: rubber-stamped by ${u})`:`pm-gate: rubber-stamped by ${u}`);let E={eventId:c.id,decidedAt:i,decidedBy:d,agentId:l,outcome:f,reason:m};return s.recordDecision(E),E})(t())}function M(s,e,n,t,r=Date.now){if(U(n,t))return!1;let o=r(),i={id:S(o),ts:o,source:e,eventId:n.eventId,decidedBy:n.decidedBy,ledgerAgentId:n.agentId,ledgerOutcome:n.outcome,ledgerReason:n.reason,legacyAgentId:t.agentId,legacyOutcome:t.outcome,legacyReason:t.reason??null};return s.recordDivergence(i),L.warn(`[ledger-divergence] source=${e} event=${n.eventId} decided_by=${n.decidedBy} ledger=${n.outcome}/${n.agentId??"null"} legacy=${t.outcome}/${t.agentId??"null"}`),!0}function U(s,e){return s.outcome===e.outcome&&s.agentId===e.agentId}function V(s,e,n=Date.now){return{ts:n(),source:"cron",sourceEventId:`${s.jobId}:${s.firedAt.getTime()}`,project:null,subject:`cron:${s.jobId}`,intent:"cron.fired",rawJson:e}}function W(s){return{decidedBy:"cron-scheduler",decide:()=>({agentId:s.agentId,outcome:s.outcome==="deduped"?"halted":s.outcome,reason:s.outcome==="deduped"?`legacy-dedup: ${s.reason??""}`.trim():s.reason??null})}}function J(s,e,n,t,r=Date.now){let o=V(e,n,r),i=W(t),c=k(s,o,i,r);return M(s,"cron",c,t,r),c}function F(s,e){if(!e)return s;let n=e*4;return`${s}
|
|
2
|
-
|
|
3
|
-
[Response budget]
|
|
4
|
-
Keep your response under ~${e} tokens (~${n} chars). Be concise \u2014 this is automated batch work, not a conversation.`}function b(s,e,n){let t=[];for(let r of s.split(","))if(r==="*")for(let o=e;o<=n;o++)t.push(o);else if(r.includes("/")){let[o,i]=r.split("/"),c=parseInt(i,10),a=o==="*"?e:parseInt(o,10);for(let u=a;u<=n;u+=c)t.push(u)}else if(r.includes("-")){let[o,i]=r.split("-").map(Number);for(let c=o;c<=i;c++)t.push(c)}else t.push(parseInt(r,10));return[...new Set(t)].sort((r,o)=>r-o)}function B(s,e,n){let t=s.trim().split(/\s+/);if(t.length!==5)throw new Error(`Invalid cron: ${s}`);let r=b(t[0],0,59),o=b(t[1],0,23),i=b(t[2],1,31),c=b(t[3],1,12),a=b(t[4],0,6),u=new Intl.DateTimeFormat("en-US",{timeZone:n,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",hour12:!1}),d=new Date(e.getTime()+6e4);d.setSeconds(0,0);let p=new Date(d.getTime()+366*24*60*60*1e3);for(;d<p;){let y=u.formatToParts(d),g=$=>parseInt(y.find(j=>j.type===$)?.value||"0",10),l=g("minute"),f=g("hour"),m=g("day"),E=g("month"),h=d.getDay();if(r.includes(l)&&o.includes(f)&&i.includes(m)&&c.includes(E)&&a.includes(h))return d;d.setTime(d.getTime()+6e4)}throw new Error(`No next run found for cron "${s}" within 1 year`)}var I=5,w=[3e4,6e4,3e5,9e5,36e5],G=class{jobs=new Map;timers=new Map;registry;hooks;runsDir;lastRunFile;running=!1;notifyCallback;log;constructor(e,n,t,r=console.error.bind(console,"[cron]")){this.registry=n,this.hooks=t,this.log=r,this.runsDir=v(process.cwd(),".agentx/cron/runs"),this.lastRunFile=v(process.cwd(),".agentx/cron/last-runs.json");for(let[o,i]of Object.entries(e.crons))this.jobs.set(o,{id:o,enabled:i.enabled,schedule:i.schedule,timezone:i.timezone,agent:i.agent,prompt:i.prompt,timeout:i.timeout,model:i.model,maxOutputTokens:i.maxOutputTokens,onError:i.onError,consecutiveErrors:0,totalRuns:0,totalFailures:0})}setNotifyCallback(e){this.notifyCallback=e}async start(){this.running=!0,D(this.runsDir)||R(this.runsDir,{recursive:!0});let e=this.detectMissedRuns();if(e.length>0){this.log(`Detected ${e.length} missed cron run(s) while daemon was down:`);for(let{jobId:n,missedAt:t}of e)this.log(` "${n}" should have run at ${t.toISOString()}`);this.executeMissedRuns(e).catch(n=>{this.log(`Error executing missed runs: ${n.message}`)})}for(let[n,t]of this.jobs){if(!t.enabled){this.log(`Job "${n}" is disabled, skipping`);continue}this.scheduleNext(n)}this.log(`${this.jobs.size} cron job(s) loaded, ${Array.from(this.jobs.values()).filter(n=>n.enabled).length} enabled`)}async stop(){this.running=!1;for(let e of this.timers.values())clearTimeout(e);this.timers.clear(),this.saveLastRuns()}scheduleNext(e){let n=this.jobs.get(e);if(!n||!n.enabled||!this.running)return;let t=this.timers.get(e);t&&clearTimeout(t);try{let r=B(n.schedule,new Date,n.timezone);n.nextRun=r,n.retryPending=!1;let o=r.getTime()-Date.now();this.log(`Job "${e}" next run: ${r.toISOString()} (in ${Math.round(o/1e3)}s)`);let i=setTimeout(()=>this.executeJob(e),o);this.timers.set(e,i)}catch(r){this.log(`Failed to schedule "${e}": ${r.message}`)}}scheduleRetry(e,n){let t=this.jobs.get(e);if(!t||!t.enabled||!this.running)return;if(n>=I){this.log(`Job "${e}" exhausted all ${I} retries, scheduling next regular run`),this.scheduleNext(e);return}let r=w[n]||w[w.length-1];t.retryPending=!0,this.log(`Job "${e}" retry ${n+1}/${I} in ${Math.round(r/1e3)}s`);let o=setTimeout(()=>this.executeJob(e,n+1),r);this.timers.set(e,o)}recordCronDecisionInLedger(e,n,t,r){if(O("cron")!=="off")try{let o=J(x(),{jobId:e,agentId:n,firedAt:t},JSON.stringify({jobId:e,agentId:n,firedAt:t.toISOString()}),r);return o.outcome==="dispatched"?{eventId:o.eventId,decidedBy:o.decidedBy}:void 0}catch(o){this.log(`[ledger] cron "${e}" record failed: ${o?.message??o}`);return}}async executeJob(e,n=0){let t=this.jobs.get(e);if(!t||!this.running)return;let r=n>0,o=new Date;if(this.hooks?.has("pre:cron-run")){let a=await this.hooks.execute("pre:cron-run",{event:"pre:cron-run",jobId:e,agent:t.agent,prompt:t.prompt});if(a.blocked){this.log(`Job "${e}" blocked by hook: ${a.message}`),this.recordCronDecisionInLedger(e,t.agent,o,{agentId:null,outcome:"halted",reason:`pre-hook blocked: ${a.message??""}`.trim()}),this.scheduleNext(e);return}}this.log(`${r?`[retry ${n}] `:""}Executing job "${e}" -> agent "${t.agent}"`);let i=o;t.lastRun=i,t.totalRuns++;let c=this.recordCronDecisionInLedger(e,t.agent,o,{agentId:t.agent,outcome:"dispatched",reason:r?`retry ${n}`:null});try{let a=await this.registry.execute({message:F(t.prompt,t.maxOutputTokens),agentId:t.agent,model:t.model,intentRef:c,context:{channel:"cron",chatId:`cron:${e}`}}),u={jobId:e,startedAt:i,completedAt:new Date,success:!a.error,response:a.content,error:a.error,duration:a.duration||Date.now()-i.getTime(),isRetry:r,retryAttempt:n};if(a.error){if(t.consecutiveErrors++,t.totalFailures++,t.lastError=a.error,this.log(`Job "${e}" failed (${t.consecutiveErrors} consecutive): ${a.error}`),await this.notifyFailure(t,a.error),t.onError.includes("disable")&&t.consecutiveErrors>=3){t.enabled=!1,this.log(`Job "${e}" disabled after ${t.consecutiveErrors} consecutive errors`);try{let d=await C(p=>T(p,`crons.${e}.enabled`,!1),{reload:!1});d.success||this.log(`Failed to persist disable for "${e}": ${d.error}`)}catch(d){this.log(`Failed to persist disable for "${e}": ${d.message}`)}await this.notifyDisabled(t)}this.logRun(u),this.scheduleRetry(e,n);return}else t.consecutiveErrors>0&&this.log(`Job "${e}" recovered after ${t.consecutiveErrors} failure(s)`),t.consecutiveErrors=0,t.lastSuccess=new Date,t.lastError=void 0,t.retryPending=!1,this.log(`Job "${e}" completed in ${u.duration}ms`);this.logRun(u),this.hooks?.has("post:cron-run")&&await this.hooks.execute("post:cron-run",{event:"post:cron-run",jobId:e,success:u.success,duration:u.duration,error:u.error?new Error(u.error):void 0})}catch(a){t.consecutiveErrors++,t.totalFailures++,t.lastError=a.message,this.log(`Job "${e}" threw: ${a.message}`),await this.notifyFailure(t,a.message),this.scheduleRetry(e,n);return}this.saveLastRuns(),this.scheduleNext(e)}detectMissedRuns(){let e=[],n=this.loadLastRuns();if(!n)return e;for(let[t,r]of this.jobs){if(!r.enabled)continue;let o=n[t];if(!o)continue;let i=new Date(o);try{let c=B(r.schedule,i,r.timezone);c.getTime()<Date.now()&&e.push({jobId:t,missedAt:c})}catch{}}return e}async executeMissedRuns(e){for(let{jobId:n,missedAt:t}of e){let r=this.jobs.get(n);if(!(!r||!r.enabled)){this.log(`Running missed job "${n}" (was due at ${t.toISOString()})`);try{let o=await this.registry.execute({message:F(`[MISSED RUN \u2014 was scheduled for ${t.toISOString()}]
|
|
5
|
-
|
|
6
|
-
${r.prompt}`,r.maxOutputTokens),agentId:r.agent,context:{channel:"cron",chatId:`cron:${n}`}}),i={jobId:n,startedAt:new Date,completedAt:new Date,success:!o.error,response:o.content,error:o.error,duration:o.duration||0,isRetry:!1};this.logRun(i),o.error?this.log(`Missed job "${n}" failed: ${o.error}`):(this.log(`Missed job "${n}" completed`),r.lastSuccess=new Date),r.lastRun=new Date,r.totalRuns++}catch(o){this.log(`Missed job "${n}" threw: ${o.message}`)}}}this.saveLastRuns()}async notifyFailure(e,n){if(!(!e.onError.includes("notify")&&e.consecutiveErrors<2)){if(!this.notifyCallback){this.log(`[ALERT] Cron "${e.id}" failed (${e.consecutiveErrors}x): ${n.slice(0,200)}`);return}try{await this.notifyCallback(e.id,e.agent,n,e.consecutiveErrors)}catch{}}}async notifyDisabled(e){if(!this.notifyCallback){this.log(`[ALERT] Cron "${e.id}" has been DISABLED after ${e.consecutiveErrors} consecutive failures`);return}try{await this.notifyCallback(e.id,e.agent,`AUTO-DISABLED after ${e.consecutiveErrors} consecutive failures. Last error: ${e.lastError||"unknown"}`,e.consecutiveErrors)}catch{}}saveLastRuns(){try{let e={};for(let[t,r]of this.jobs)r.lastRun&&(e[t]=r.lastRun.toISOString());let n=v(process.cwd(),".agentx/cron");D(n)||R(n,{recursive:!0}),P(this.lastRunFile,JSON.stringify(e,null,2))}catch{}}loadLastRuns(){try{return D(this.lastRunFile)?JSON.parse(q(this.lastRunFile,"utf-8")):null}catch{return null}}logRun(e){try{let n=v(this.runsDir,e.jobId);D(n)||R(n,{recursive:!0});let t=`${e.startedAt.toISOString().replace(/[:.]/g,"-")}.json`;P(v(n,t),JSON.stringify(e,null,2))}catch{}}list(){return Array.from(this.jobs.values())}health(){let e=Array.from(this.jobs.values()),n=this.detectMissedRuns();return{healthy:e.filter(t=>t.enabled&&t.consecutiveErrors===0).length,failing:e.filter(t=>t.enabled&&t.consecutiveErrors>0).length,disabled:e.filter(t=>!t.enabled).length,missed:n.length,jobs:e.map(t=>({id:t.id,status:t.enabled?t.retryPending?"retrying":t.consecutiveErrors>0?"failing":"healthy":"disabled",consecutiveErrors:t.consecutiveErrors,lastError:t.lastError}))}}};export{O as a,X as b,k as c,M as d,B as e,G as f};
|
|
7
|
-
//# sourceMappingURL=chunk-ABVZHJUO.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/crons/scheduler.ts","../src/intent/mode.ts","../src/intent/governance.ts","../src/intent/decide.ts","../src/intent/divergence.ts","../src/intent/sources/cron.ts"],"sourcesContent":["import type { DaemonConfig, CronJobDef } from \"@/daemon/config\"\nimport type { AgentRegistry } from \"@/agents/registry\"\nimport type { HookRegistry } from \"@/hooks\"\nimport type { CronJobState, CronRunResult } from \"./types\"\nimport { writeFileSync, mkdirSync, existsSync, readFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { applyConfigMutation, setAtPath } from \"@/daemon/config-mutator\"\nimport { getLedgerMode } from \"@/intent/mode\"\nimport { getDefaultLedger } from \"@/intent/instance\"\nimport { recordCronDispatch } from \"@/intent/sources/cron\"\n\n// --- Cron Scheduler: lightweight cron engine with timezone support ---\n// No external dependencies — uses setTimeout-based scheduling.\n\n/**\n * Append a soft output-length cap to a cron prompt when the job configured\n * `maxOutputTokens`. Claude Code CLI has no hard flag for output length,\n * but a clear instruction at the end of the prompt is reliably honored by\n * the model. Keeps a cheap knob available for operators without us needing\n * to invent our own token counter or hack a post-response truncate.\n */\nfunction withOutputCap(prompt: string, maxOutputTokens?: number): string {\n if (!maxOutputTokens) return prompt\n const approxChars = maxOutputTokens * 4\n return `${prompt}\\n\\n[Response budget]\\nKeep your response under ~${maxOutputTokens} tokens (~${approxChars} chars). Be concise — this is automated batch work, not a conversation.`\n}\n\n/**\n * Parse a cron expression into next-fire timestamp.\n * Supports standard 5-field format: minute hour day-of-month month day-of-week\n */\nfunction parseCronField(field: string, min: number, max: number): number[] {\n const values: number[] = []\n\n for (const part of field.split(\",\")) {\n if (part === \"*\") {\n for (let i = min; i <= max; i++) values.push(i)\n } else if (part.includes(\"/\")) {\n const [range, stepStr] = part.split(\"/\")\n const step = parseInt(stepStr, 10)\n const start = range === \"*\" ? min : parseInt(range, 10)\n for (let i = start; i <= max; i += step) values.push(i)\n } else if (part.includes(\"-\")) {\n const [a, b] = part.split(\"-\").map(Number)\n for (let i = a; i <= b; i++) values.push(i)\n } else {\n values.push(parseInt(part, 10))\n }\n }\n\n return [...new Set(values)].sort((a, b) => a - b)\n}\n\nexport function getNextCronDate(expression: string, after: Date, timezone: string): Date {\n const fields = expression.trim().split(/\\s+/)\n if (fields.length !== 5) throw new Error(`Invalid cron: ${expression}`)\n\n const minutes = parseCronField(fields[0], 0, 59)\n const hours = parseCronField(fields[1], 0, 23)\n const daysOfMonth = parseCronField(fields[2], 1, 31)\n const months = parseCronField(fields[3], 1, 12)\n const daysOfWeek = parseCronField(fields[4], 0, 6)\n\n // Convert to timezone-aware date\n const fmt = new Intl.DateTimeFormat(\"en-US\", {\n timeZone: timezone,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n hour12: false,\n })\n\n // Start searching from next minute\n const candidate = new Date(after.getTime() + 60_000)\n candidate.setSeconds(0, 0)\n\n // Search up to 1 year ahead\n const limit = new Date(candidate.getTime() + 366 * 24 * 60 * 60 * 1000)\n\n while (candidate < limit) {\n const parts = fmt.formatToParts(candidate)\n const get = (type: string) => parseInt(parts.find((p) => p.type === type)?.value || \"0\", 10)\n\n const m = get(\"minute\")\n const h = get(\"hour\")\n const dom = get(\"day\")\n const mon = get(\"month\")\n const dow = candidate.getDay()\n\n if (\n minutes.includes(m) &&\n hours.includes(h) &&\n daysOfMonth.includes(dom) &&\n months.includes(mon) &&\n daysOfWeek.includes(dow)\n ) {\n return candidate\n }\n\n // Advance by 1 minute\n candidate.setTime(candidate.getTime() + 60_000)\n }\n\n throw new Error(`No next run found for cron \"${expression}\" within 1 year`)\n}\n\n// Retry config (inspired by OpenClaw's exponential backoff)\nconst MAX_RETRIES = 5\nconst RETRY_DELAYS = [30_000, 60_000, 300_000, 900_000, 3_600_000] // 30s, 1m, 5m, 15m, 60m\n\n/** Notification callback — injected by daemon to send alerts via channels */\nexport type CronNotifyCallback = (jobId: string, agent: string, error: string, consecutiveErrors: number) => Promise<void>\n\nexport class CronScheduler {\n private jobs: Map<string, CronJobState> = new Map()\n private timers: Map<string, ReturnType<typeof setTimeout>> = new Map()\n private registry: AgentRegistry\n private hooks?: HookRegistry\n private runsDir: string\n private lastRunFile: string\n private running = false\n private notifyCallback?: CronNotifyCallback\n private log: (...args: unknown[]) => void\n\n constructor(\n config: DaemonConfig,\n registry: AgentRegistry,\n hooks?: HookRegistry,\n log: (...args: unknown[]) => void = console.error.bind(console, \"[cron]\"),\n ) {\n this.registry = registry\n this.hooks = hooks\n this.log = log\n this.runsDir = resolve(process.cwd(), \".agentx/cron/runs\")\n this.lastRunFile = resolve(process.cwd(), \".agentx/cron/last-runs.json\")\n\n for (const [id, def] of Object.entries(config.crons)) {\n this.jobs.set(id, {\n id,\n enabled: def.enabled,\n schedule: def.schedule,\n timezone: def.timezone,\n agent: def.agent,\n prompt: def.prompt,\n timeout: def.timeout,\n model: def.model,\n maxOutputTokens: def.maxOutputTokens,\n onError: def.onError,\n consecutiveErrors: 0,\n totalRuns: 0,\n totalFailures: 0,\n })\n }\n }\n\n /**\n * Set a notification callback for failed crons.\n * Called when onError is \"notify\" or after multiple consecutive failures.\n */\n setNotifyCallback(cb: CronNotifyCallback): void {\n this.notifyCallback = cb\n }\n\n async start(): Promise<void> {\n this.running = true\n\n if (!existsSync(this.runsDir)) {\n mkdirSync(this.runsDir, { recursive: true })\n }\n\n // Detect missed runs before scheduling\n const missedRuns = this.detectMissedRuns()\n if (missedRuns.length > 0) {\n this.log(`Detected ${missedRuns.length} missed cron run(s) while daemon was down:`)\n for (const { jobId, missedAt } of missedRuns) {\n this.log(` \"${jobId}\" should have run at ${missedAt.toISOString()}`)\n }\n // Execute missed runs (fire-and-forget, don't block startup)\n this.executeMissedRuns(missedRuns).catch((e) => {\n this.log(`Error executing missed runs: ${e.message}`)\n })\n }\n\n for (const [id, job] of this.jobs) {\n if (!job.enabled) {\n this.log(`Job \"${id}\" is disabled, skipping`)\n continue\n }\n this.scheduleNext(id)\n }\n\n this.log(`${this.jobs.size} cron job(s) loaded, ${Array.from(this.jobs.values()).filter((j) => j.enabled).length} enabled`)\n }\n\n async stop(): Promise<void> {\n this.running = false\n for (const timer of this.timers.values()) {\n clearTimeout(timer)\n }\n this.timers.clear()\n\n // Persist last run times for missed run detection on next start\n this.saveLastRuns()\n }\n\n private scheduleNext(jobId: string): void {\n const job = this.jobs.get(jobId)\n if (!job || !job.enabled || !this.running) return\n\n // Clear any existing timer\n const existing = this.timers.get(jobId)\n if (existing) clearTimeout(existing)\n\n try {\n const nextRun = getNextCronDate(job.schedule, new Date(), job.timezone)\n job.nextRun = nextRun\n job.retryPending = false\n const delay = nextRun.getTime() - Date.now()\n\n this.log(`Job \"${jobId}\" next run: ${nextRun.toISOString()} (in ${Math.round(delay / 1000)}s)`)\n\n const timer = setTimeout(() => this.executeJob(jobId), delay)\n this.timers.set(jobId, timer)\n } catch (e: any) {\n this.log(`Failed to schedule \"${jobId}\": ${e.message}`)\n }\n }\n\n private scheduleRetry(jobId: string, attempt: number): void {\n const job = this.jobs.get(jobId)\n if (!job || !job.enabled || !this.running) return\n if (attempt >= MAX_RETRIES) {\n this.log(`Job \"${jobId}\" exhausted all ${MAX_RETRIES} retries, scheduling next regular run`)\n this.scheduleNext(jobId)\n return\n }\n\n const delay = RETRY_DELAYS[attempt] || RETRY_DELAYS[RETRY_DELAYS.length - 1]\n job.retryPending = true\n\n this.log(`Job \"${jobId}\" retry ${attempt + 1}/${MAX_RETRIES} in ${Math.round(delay / 1000)}s`)\n\n const timer = setTimeout(() => this.executeJob(jobId, attempt + 1), delay)\n this.timers.set(jobId, timer)\n }\n\n /**\n * Phase 1 commit 6.d — record one cron-fire decision in the ledger\n * when the source is enabled. Wrapped in try/catch so a ledger\n * failure can never break cron dispatch — legacy stays authoritative\n * until the 1c per-source promotion lands.\n */\n private recordCronDecisionInLedger(\n jobId: string,\n agentId: string,\n firedAt: Date,\n legacy: import(\"@/intent/divergence\").LegacyOutcome,\n ): { eventId: string; decidedBy: string } | undefined {\n if (getLedgerMode(\"cron\") === \"off\") return undefined\n try {\n const decision = recordCronDispatch(\n getDefaultLedger(),\n { jobId, agentId, firedAt },\n JSON.stringify({ jobId, agentId, firedAt: firedAt.toISOString() }),\n legacy,\n )\n if (decision.outcome === \"dispatched\") {\n return { eventId: decision.eventId, decidedBy: decision.decidedBy }\n }\n return undefined\n } catch (e: any) {\n this.log(`[ledger] cron \"${jobId}\" record failed: ${e?.message ?? e}`)\n return undefined\n }\n }\n\n private async executeJob(jobId: string, retryAttempt: number = 0): Promise<void> {\n const job = this.jobs.get(jobId)\n if (!job || !this.running) return\n\n const isRetry = retryAttempt > 0\n // firedAt is captured here (rather than below at the original\n // `startedAt` declaration) so the ledger event has a stable\n // sourceEventId regardless of whether we record at the hook-block\n // path or the dispatch path.\n const firedAt = new Date()\n\n // Pre-hook\n if (this.hooks?.has(\"pre:cron-run\" as any)) {\n const hookResult = await this.hooks.execute(\"pre:cron-run\" as any, {\n event: \"pre:cron-run\" as any,\n jobId,\n agent: job.agent,\n prompt: job.prompt,\n })\n if (hookResult.blocked) {\n this.log(`Job \"${jobId}\" blocked by hook: ${hookResult.message}`)\n this.recordCronDecisionInLedger(jobId, job.agent, firedAt, {\n agentId: null, outcome: \"halted\",\n reason: `pre-hook blocked: ${hookResult.message ?? \"\"}`.trim(),\n })\n this.scheduleNext(jobId)\n return\n }\n }\n\n this.log(`${isRetry ? `[retry ${retryAttempt}] ` : \"\"}Executing job \"${jobId}\" -> agent \"${job.agent}\"`)\n const startedAt = firedAt\n job.lastRun = startedAt\n job.totalRuns++\n\n // Phase 1 commit 6.d — record the dispatch decision before the\n // (potentially long-running) registry.execute call. The returned\n // intentRef threads through to registry.execute so it records a\n // resolution on completion.\n const intentRef = this.recordCronDecisionInLedger(jobId, job.agent, firedAt, {\n agentId: job.agent, outcome: \"dispatched\", reason: isRetry ? `retry ${retryAttempt}` : null,\n })\n\n try {\n const response = await this.registry.execute({\n message: withOutputCap(job.prompt, job.maxOutputTokens),\n agentId: job.agent,\n model: job.model,\n intentRef,\n // chatId per-job so different cron jobs for the same agent don't\n // collide in one \"default\" session. Without this, the marketing\n // daily-brief and the marketing weekly-report cron share history,\n // and one job's prompt context bleeds into the other.\n context: { channel: \"cron\", chatId: `cron:${jobId}` },\n })\n\n const result: CronRunResult = {\n jobId,\n startedAt,\n completedAt: new Date(),\n success: !response.error,\n response: response.content,\n error: response.error,\n duration: response.duration || Date.now() - startedAt.getTime(),\n isRetry,\n retryAttempt,\n }\n\n if (response.error) {\n job.consecutiveErrors++\n job.totalFailures++\n job.lastError = response.error\n this.log(`Job \"${jobId}\" failed (${job.consecutiveErrors} consecutive): ${response.error}`)\n\n // Notify on failure\n await this.notifyFailure(job, response.error)\n\n if (job.onError.includes(\"disable\") && job.consecutiveErrors >= 3) {\n job.enabled = false\n this.log(`Job \"${jobId}\" disabled after ${job.consecutiveErrors} consecutive errors`)\n // Persist to agentx.json so a daemon restart doesn't resurrect the\n // broken cron (previously this was in-memory only — a repeatedly\n // failing cron would keep burning money across restarts).\n try {\n const res = await applyConfigMutation(\n (cfg) => setAtPath(cfg, `crons.${jobId}.enabled`, false),\n { reload: false },\n )\n if (!res.success) {\n this.log(`Failed to persist disable for \"${jobId}\": ${res.error}`)\n }\n } catch (e: any) {\n this.log(`Failed to persist disable for \"${jobId}\": ${e.message}`)\n }\n await this.notifyDisabled(job)\n }\n\n // Retry\n this.logRun(result)\n this.scheduleRetry(jobId, retryAttempt)\n return\n } else {\n // Success — reset error state\n if (job.consecutiveErrors > 0) {\n this.log(`Job \"${jobId}\" recovered after ${job.consecutiveErrors} failure(s)`)\n }\n job.consecutiveErrors = 0\n job.lastSuccess = new Date()\n job.lastError = undefined\n job.retryPending = false\n this.log(`Job \"${jobId}\" completed in ${result.duration}ms`)\n }\n\n // Log run\n this.logRun(result)\n\n // Post-hook\n if (this.hooks?.has(\"post:cron-run\" as any)) {\n await this.hooks.execute(\"post:cron-run\" as any, {\n event: \"post:cron-run\" as any,\n jobId,\n success: result.success,\n duration: result.duration,\n error: result.error ? new Error(result.error) : undefined,\n })\n }\n } catch (e: any) {\n job.consecutiveErrors++\n job.totalFailures++\n job.lastError = e.message\n this.log(`Job \"${jobId}\" threw: ${e.message}`)\n\n await this.notifyFailure(job, e.message)\n this.scheduleRetry(jobId, retryAttempt)\n return\n }\n\n // Persist last run time\n this.saveLastRuns()\n\n // Schedule next regular run\n this.scheduleNext(jobId)\n }\n\n // --- Missed run detection ---\n\n /**\n * Detect cron jobs that should have fired while the daemon was down.\n * Compares saved last-run times against the cron schedule.\n */\n private detectMissedRuns(): Array<{ jobId: string; missedAt: Date }> {\n const missed: Array<{ jobId: string; missedAt: Date }> = []\n\n const lastRuns = this.loadLastRuns()\n if (!lastRuns) return missed // First boot — no history\n\n for (const [jobId, job] of this.jobs) {\n if (!job.enabled) continue\n\n const lastRunStr = lastRuns[jobId]\n if (!lastRunStr) continue // Never ran before\n\n const lastRun = new Date(lastRunStr)\n try {\n // What SHOULD the next run have been after the last recorded run?\n const shouldHaveRun = getNextCronDate(job.schedule, lastRun, job.timezone)\n if (shouldHaveRun.getTime() < Date.now()) {\n missed.push({ jobId, missedAt: shouldHaveRun })\n }\n } catch {\n // Invalid schedule — skip\n }\n }\n\n return missed\n }\n\n /**\n * Execute missed runs (catch-up). Runs sequentially to avoid overwhelming agents.\n */\n private async executeMissedRuns(missed: Array<{ jobId: string; missedAt: Date }>): Promise<void> {\n for (const { jobId, missedAt } of missed) {\n const job = this.jobs.get(jobId)\n if (!job || !job.enabled) continue\n\n this.log(`Running missed job \"${jobId}\" (was due at ${missedAt.toISOString()})`)\n\n try {\n const response = await this.registry.execute({\n message: withOutputCap(\n `[MISSED RUN — was scheduled for ${missedAt.toISOString()}]\\n\\n${job.prompt}`,\n job.maxOutputTokens,\n ),\n agentId: job.agent,\n context: { channel: \"cron\", chatId: `cron:${jobId}` },\n })\n\n const result: CronRunResult = {\n jobId,\n startedAt: new Date(),\n completedAt: new Date(),\n success: !response.error,\n response: response.content,\n error: response.error,\n duration: response.duration || 0,\n isRetry: false,\n }\n\n this.logRun(result)\n\n if (response.error) {\n this.log(`Missed job \"${jobId}\" failed: ${response.error}`)\n } else {\n this.log(`Missed job \"${jobId}\" completed`)\n job.lastSuccess = new Date()\n }\n job.lastRun = new Date()\n job.totalRuns++\n } catch (e: any) {\n this.log(`Missed job \"${jobId}\" threw: ${e.message}`)\n }\n }\n\n this.saveLastRuns()\n }\n\n // --- Notification ---\n\n private async notifyFailure(job: CronJobState, error: string): Promise<void> {\n if (!job.onError.includes(\"notify\") && job.consecutiveErrors < 2) return\n if (!this.notifyCallback) {\n this.log(`[ALERT] Cron \"${job.id}\" failed (${job.consecutiveErrors}x): ${error.slice(0, 200)}`)\n return\n }\n\n try {\n await this.notifyCallback(job.id, job.agent, error, job.consecutiveErrors)\n } catch {\n // Don't fail on notification errors\n }\n }\n\n private async notifyDisabled(job: CronJobState): Promise<void> {\n if (!this.notifyCallback) {\n this.log(`[ALERT] Cron \"${job.id}\" has been DISABLED after ${job.consecutiveErrors} consecutive failures`)\n return\n }\n\n try {\n await this.notifyCallback(\n job.id,\n job.agent,\n `AUTO-DISABLED after ${job.consecutiveErrors} consecutive failures. Last error: ${job.lastError || \"unknown\"}`,\n job.consecutiveErrors,\n )\n } catch {\n // Don't fail on notification errors\n }\n }\n\n // --- Persistence ---\n\n private saveLastRuns(): void {\n try {\n const data: Record<string, string> = {}\n for (const [id, job] of this.jobs) {\n if (job.lastRun) data[id] = job.lastRun.toISOString()\n }\n const dir = resolve(process.cwd(), \".agentx/cron\")\n if (!existsSync(dir)) mkdirSync(dir, { recursive: true })\n writeFileSync(this.lastRunFile, JSON.stringify(data, null, 2))\n } catch {\n // Best-effort\n }\n }\n\n private loadLastRuns(): Record<string, string> | null {\n try {\n if (!existsSync(this.lastRunFile)) return null\n return JSON.parse(readFileSync(this.lastRunFile, \"utf-8\"))\n } catch {\n return null\n }\n }\n\n private logRun(result: CronRunResult): void {\n try {\n const runDir = resolve(this.runsDir, result.jobId)\n if (!existsSync(runDir)) {\n mkdirSync(runDir, { recursive: true })\n }\n\n const filename = `${result.startedAt.toISOString().replace(/[:.]/g, \"-\")}.json`\n writeFileSync(\n resolve(runDir, filename),\n JSON.stringify(result, null, 2),\n )\n } catch {\n // Don't fail on logging errors\n }\n }\n\n /**\n * List all jobs and their status (including failure info).\n */\n list(): CronJobState[] {\n return Array.from(this.jobs.values())\n }\n\n /**\n * Get a health summary of cron jobs.\n */\n health(): { healthy: number; failing: number; disabled: number; missed: number; jobs: Array<{ id: string; status: string; consecutiveErrors: number; lastError?: string }> } {\n const jobs = Array.from(this.jobs.values())\n const missed = this.detectMissedRuns()\n\n return {\n healthy: jobs.filter(j => j.enabled && j.consecutiveErrors === 0).length,\n failing: jobs.filter(j => j.enabled && j.consecutiveErrors > 0).length,\n disabled: jobs.filter(j => !j.enabled).length,\n missed: missed.length,\n jobs: jobs.map(j => ({\n id: j.id,\n status: !j.enabled ? \"disabled\" : j.retryPending ? \"retrying\" : j.consecutiveErrors > 0 ? \"failing\" : \"healthy\",\n consecutiveErrors: j.consecutiveErrors,\n lastError: j.lastError,\n })),\n }\n }\n}\n","import type { IntentSource } from \"./types\"\n\n// Mode-flag plumbing — Phase 1 commit 4 of the architectural rescue.\n//\n// The staged rollout in research-rescue-plan.md §7 needs three states:\n//\n// off ledger code is unreachable; legacy paths are authoritative.\n// This is the default and stays the default through 1a.\n// shadow decideAndCommit runs alongside the legacy paths on every\n// dispatch and records what *would* have happened. Legacy\n// remains authoritative; a divergence reporter (commit 5)\n// compares the two. The ≥7-day soak in 1b lives here.\n// authoritative ledger decision is the dispatch decision. Legacy paths\n// remain only as fallback gated by INTENT_LEDGER_FALLBACK\n// (commit 1c → 1d removes the legacy paths entirely).\n//\n// Mode is resolved per-source so 1c can flip sources one at a time\n// (gitlab → workflow → telegram → cron → mesh, ≥48h soak between each).\n//\n// This commit is plumbing only. No call site reads the mode yet —\n// commit 6 wires the readers behind the flag, defaulting to \"off\" so\n// production behaviour is unchanged at deploy time.\n\nexport type LedgerMode = \"off\" | \"shadow\" | \"authoritative\"\n\nconst VALID_MODES = [\"off\", \"shadow\", \"authoritative\"] as const satisfies readonly LedgerMode[]\n\n/** Per-source env-var names. Lifted to a constant so the call site (commit 6)\n * doesn't need to construct strings, and so the test matrix can drive them\n * by table rather than guessing the naming convention. */\nconst PER_SOURCE_ENV_VAR: Record<IntentSource, string> = {\n telegram: \"INTENT_LEDGER_MODE_TELEGRAM\",\n slack: \"INTENT_LEDGER_MODE_SLACK\",\n whatsapp: \"INTENT_LEDGER_MODE_WHATSAPP\",\n discord: \"INTENT_LEDGER_MODE_DISCORD\",\n gitlab: \"INTENT_LEDGER_MODE_GITLAB\",\n github: \"INTENT_LEDGER_MODE_GITHUB\",\n workflow: \"INTENT_LEDGER_MODE_WORKFLOW\",\n cron: \"INTENT_LEDGER_MODE_CRON\",\n mesh: \"INTENT_LEDGER_MODE_MESH\",\n}\n\nconst GLOBAL_ENV_VAR = \"INTENT_LEDGER_MODE\"\n\n/**\n * Resolve the ledger mode for one source. Resolution order:\n *\n * 1. Per-source env var (e.g. INTENT_LEDGER_MODE_GITLAB)\n * 2. Global env var (INTENT_LEDGER_MODE)\n * 3. Default — \"off\"\n *\n * Invalid values at any level (typos, foreign strings) are ignored — that\n * level falls through to the next. Defaulting to \"off\" on garbage means an\n * operator typo never accidentally promotes a source to authoritative; the\n * cost is silence on misconfig, but the cost of the alternative\n * (mis-promoted source) is much higher in production.\n *\n * The accepted values are case-insensitive (\"Shadow\", \"SHADOW\", \"shadow\"\n * all parse) so config copied from a doc or shell history doesn't surprise.\n *\n * Read every call rather than cached. Per-dispatch overhead is one or two\n * `process.env` lookups — negligible — and runtime flips (operator SIGHUPs\n * the daemon and changes `EnvironmentFile=` on systemd) take effect on the\n * next event without a restart.\n */\nexport function getLedgerMode(\n source: IntentSource,\n env: NodeJS.ProcessEnv = process.env,\n): LedgerMode {\n const perSource = parseLedgerMode(env[PER_SOURCE_ENV_VAR[source]])\n if (perSource) return perSource\n const global = parseLedgerMode(env[GLOBAL_ENV_VAR])\n if (global) return global\n return \"off\"\n}\n\n/** Parse a value into a LedgerMode, or return null for invalid / unset. */\nexport function parseLedgerMode(value: unknown): LedgerMode | null {\n if (typeof value !== \"string\") return null\n const normalized = value.trim().toLowerCase()\n return (VALID_MODES as readonly string[]).includes(normalized)\n ? (normalized as LedgerMode)\n : null\n}\n\n/** Convenience: is this source consulting the ledger at all? Equivalent to\n * `getLedgerMode(source) !== \"off\"`, named for callsite readability. */\nexport function isLedgerActive(\n source: IntentSource,\n env: NodeJS.ProcessEnv = process.env,\n): boolean {\n return getLedgerMode(source, env) !== \"off\"\n}\n","import type { DispatchGovernance } from \"./decide\"\n\n// Daemon-level governance singleton — Phase 3 of the architectural rescue.\n//\n// `decideAndCommit` reads this when its caller doesn't pass an explicit\n// `governance` argument. The daemon initializes it at startup from the\n// `BusinessLayer.organization` instance, gated by the\n// `INTENT_PM_GATE_ENABLED` env var. When the var is unset (the deploy\n// default), the singleton stays undefined and no PM gate / canHandle\n// check fires — production behaviour is unchanged.\n//\n// This indirection keeps governance an opt-in concern: call sites and\n// helpers (`recordGitLabTargetDispatch`, `recordRouterDispatch`, etc.)\n// don't need to know about Organization. The daemon plumbs Organization\n// → DispatchGovernance once at startup; everything else stays generic.\n//\n// Tests reset between cases via `setDefaultGovernance(undefined)` to\n// avoid leakage from one test's fixture into another's behaviour.\n\nlet _governance: DispatchGovernance | undefined\n\n/** Set the process-wide default governance. Called once at daemon\n * startup (or never, if INTENT_PM_GATE_ENABLED is off). */\nexport function setDefaultGovernance(g: DispatchGovernance | undefined): void {\n _governance = g\n}\n\n/** Read the current default. `decideAndCommit` falls back to this when\n * its caller didn't pass an explicit governance arg. Returns undefined\n * in test contexts that haven't set anything (no governance fires). */\nexport function getDefaultGovernance(): DispatchGovernance | undefined {\n return _governance\n}\n","import type { IntentLedger } from \"./ledger\"\nimport type { IntentDecision, IntentEventInput, IntentEvent } from \"./types\"\nimport { getDefaultGovernance } from \"./governance\"\n\n// decideAndCommit — Phase 1 of the architectural rescue.\n//\n// The single function every dispatch source will route through. Every event\n// entering the system flows: source-adapter → decideAndCommit → (optional\n// agent dispatch). The function's job is to:\n//\n// 1. Record the event idempotently (Inv-Idempotence)\n// 2. Consult the ledger for active-task state (Inv-ActiveTaskSafety)\n// 3. Ask the policy for a tentative decision\n// 4. Commit a single decision row to the ledger (Inv-NoSilentDrops)\n//\n// The four invariants are spelled out below and named so that Phase 2's\n// TLA+ spec can reference them by string. Tests in test/intent-decide.test.ts\n// cite these names in their assertion comments.\n//\n// **Phase 2 status**: three of the four invariants are formally verified\n// by TLC against `docs/architecture/DispatchSpec.tla` (410K distinct\n// states, depth 7, 0 violations as of 2026-04-29). The fourth\n// (Inv-Determinism) is a meta-property over traces, checked by code\n// review + the test in `intent-decide.test.ts`. See\n// `docs/architecture/dispatch-properties.md` for the full mapping\n// between code paths, invariants, and TLC results.\n//\n// Inv-Idempotence calling decideAndCommit twice with the same\n// (source, sourceEventId) and the same policy\n// produces one event row + one decision row;\n// the second call returns the row from the first.\n// Inv-Determinism given a fixed ledger snapshot, a fixed policy,\n// and a fixed clock, the resulting decision is a\n// pure function of the input event.\n// Inv-ActiveTaskSafety at most one decision with outcome=dispatched\n// that has not yet been resolved exists for any\n// (project, subject) at any point in the ledger.\n// Inv-NoSilentDrops every call to decideAndCommit appends exactly\n// one decision row to the ledger (or returns an\n// existing one under Inv-Idempotence). No code\n// path returns without a row visible in the ledger.\n//\n// What this commit does NOT do:\n// - Wire decideAndCommit into any real call site (commit 6).\n// - Add the mode flag (commit 4) — this function is reachable only from\n// tests until commit 6 wires it up behind a flag that defaults off.\n// - Read or expose the divergence reporter (commit 5).\n// - Run async or call out to network. Policies are sync; if a real call\n// site needs an LLM in the loop, that's a refactor for a later phase.\n\n/**\n * A pluggable dispatch policy. Each call site (channel router, workflow\n * dispatcher, gitlab handler, cron firing, mesh receiver) supplies its own\n * policy — the function below is purely the orchestration around the\n * policy's decision.\n *\n * The policy's `decidedBy` is the ledger's attribution string (\"who decided\n * this?\") and is what the ledger uses to scope idempotency: re-delivery of\n * the same event to the same `decidedBy` returns the existing row, while\n * the same event reaching a different `decidedBy` is a separate decision\n * (the chain-of-command case).\n */\nexport interface DispatchPolicy {\n /** Stable attribution string. Examples: `\"channel-router\"`,\n * `\"workflow:gitlab-sdlc-loop\"`, `\"gitlab-handler\"`. Must be stable\n * across calls — the ledger uses it as the per-policy idempotency key. */\n readonly decidedBy: string\n\n /**\n * Inspect the (already-recorded) event and return a tentative decision.\n * Return `null` to mean \"I have no opinion\" — the caller will record a\n * `halted` decision with reason `\"no policy match\"`.\n *\n * The policy MUST NOT write to the ledger directly. It MUST be a pure\n * function of `event` plus any read-only context the caller closed over\n * when constructing the policy. (Determinism — Inv-Determinism — depends\n * on this.)\n */\n decide(event: IntentEvent): PolicyDecision | null\n}\n\n/** What a policy returns. The `outcome` set excludes `\"deduped\"` because\n * dedup is the ledger's call, not the policy's. */\nexport interface PolicyDecision {\n agentId: string | null\n outcome: \"dispatched\" | \"halted\" | \"queued\"\n reason: string | null\n}\n\n/**\n * Phase 3 — Org-chart governance hooks. Optional. When supplied,\n * decideAndCommit consults them AFTER the policy decides:\n *\n * canHandle(agentId, project, intent) returns false → halt the\n * dispatch with reason \"org-chart: agent cannot handle\". Use to\n * enforce per-agent capability constraints (Phase 5 will add\n * typed capabilities; the v0 here is permissive).\n *\n * pmFor(project) returns the agentId of the\n * PM for `project`, or undefined when no PM is configured. When\n * defined AND the policy decided \"dispatched\", the decision's\n * `decidedBy` is rewritten to `pm:<pmId>` so the audit trail\n * attributes the dispatch to the PM gate. Per the kickoff:\n * \"a dispatch decision for (project, ...) where business.projects[].pm\n * is set never resolves to an agent without going through the PM\n * first (PM may rubber-stamp, but the decision row records\n * decided_by='pm:pm-mtgl')\".\n *\n * The full async-PM gate (synchronously dispatch to the PM agent,\n * wait up to 60s for approval, fall back to escalation on timeout)\n * is a future-phase refactor. The v0 here is the paper-trail gate:\n * the decision row is attributed to the PM, but no real call-out\n * happens. This satisfies the kickoff's property and unblocks the\n * 1c per-source promotions.\n */\nexport interface DispatchGovernance {\n canHandle?(agentId: string, project: string | null, intent: string | null): boolean\n pmFor?(project: string | null): string | undefined\n /** Phase 8 — capability-bounded security. Returns false when\n * dispatching `agentId` on `(project, subject)` would exceed the\n * agent's delegation-depth budget. Blocks cascade loops by\n * walking the ledger's prior decisions on the same slot. */\n withinDelegationBudget?(agentId: string, project: string | null, subject: string | null): boolean\n}\n\n/**\n * Append-only dispatch primitive. Returns the canonical decision row —\n * either freshly written or recovered from a prior call (Inv-Idempotence).\n *\n * The whole call runs inside a single SQLite transaction so the lookup\n * (`getDecisionsForEvent` + `getActiveDecisionForSubject`) and the write\n * (`recordDecision`) are atomic with respect to the same database. This\n * matters under concurrent-call scenarios; in the single-process daemon\n * use case, better-sqlite3 already serialises writes so the transaction\n * is belt-and-braces. Adding it now means the call-site wiring (commit 6)\n * can call decideAndCommit from anywhere without worrying about races.\n */\nexport function decideAndCommit(\n ledger: IntentLedger,\n input: IntentEventInput,\n policy: DispatchPolicy,\n now: () => number = Date.now,\n governance?: DispatchGovernance,\n): IntentDecision {\n const tx = ledger.db.transaction((decidedAt: number): IntentDecision => {\n // Step 1 — record the event. recordEvent is itself idempotent on\n // (source, sourceEventId), so this is safe under re-delivery.\n const event = ledger.recordEvent(input)\n\n // Phase 3 governance: compute the EFFECTIVE decidedBy upfront so\n // it threads through idempotency + recording. When a PM is\n // configured for the project, the decision is attributed to the\n // PM (pm:<id>) regardless of outcome — the PM is the gate, even\n // for halts and queues. canHandle is a veto applied later (forces\n // outcome=halted) but doesn't change decidedBy.\n //\n // Falls back to the daemon-level singleton (`getDefaultGovernance`)\n // when the caller doesn't pass an explicit governance — this is\n // the production path. Tests pass governance directly.\n const gov = governance ?? getDefaultGovernance()\n const pm = gov?.pmFor?.(event.project)\n const effectiveDecidedBy = pm ? `pm:${pm}` : policy.decidedBy\n\n // Step 2 — Inv-Idempotence. If this policy has already decided on this\n // event, return that decision unchanged. Two callers who race the same\n // event-policy pair are reduced to a single decision row.\n const prior = ledger\n .getDecisionsForEvent(event.id)\n .find((d) => d.decidedBy === effectiveDecidedBy)\n if (prior) return prior\n\n // Step 3 — Inv-ActiveTaskSafety. If a dispatched-and-unresolved\n // decision already exists for (project, subject), this event must\n // dedupe. The dedup outcome is ledger-managed; the policy never sees\n // a chance to override it.\n const active = ledger.getActiveDecisionForSubject(event.project, event.subject)\n if (active) {\n const decision: IntentDecision = {\n eventId: event.id,\n decidedAt,\n decidedBy: effectiveDecidedBy,\n agentId: null,\n outcome: \"deduped\",\n reason: `active dispatch in flight: ${active.decidedBy} → ${active.agentId ?? \"?\"}`,\n }\n ledger.recordDecision(decision)\n return decision\n }\n\n // Step 4 — consult the policy. A null result is a halt with explicit\n // reason rather than a silent drop (Inv-NoSilentDrops). A halted /\n // queued / dispatched policy decision flows through unchanged.\n const policyResult = policy.decide(event)\n let agentId: string | null = policyResult ? policyResult.agentId : null\n let outcome: \"dispatched\" | \"halted\" | \"queued\" = policyResult ? policyResult.outcome : \"halted\"\n let reason: string | null = policyResult ? policyResult.reason : \"no policy match\"\n\n // Step 5 — canHandle veto. Dispatches to agents the org chart says\n // can't handle the event get forced to halted.\n if (gov?.canHandle && outcome === \"dispatched\" && agentId) {\n const allowed = gov.canHandle(agentId, event.project, event.intent)\n if (!allowed) {\n const blocked = agentId\n agentId = null\n outcome = \"halted\"\n reason = `org-chart: agent \"${blocked}\" cannot handle (${event.project ?? \"no-project\"}/${event.intent ?? \"no-intent\"})`\n }\n }\n\n // Step 5b — Phase 8 delegation-depth check. Walks the ledger's\n // prior decisions on (project, subject) and refuses dispatches\n // that would push the chain past the agent's\n // `maxDelegationDepth`. Prevents cascade loops where A → B → A → ...\n // blows past sane chain depth.\n if (gov?.withinDelegationBudget && outcome === \"dispatched\" && agentId) {\n if (!gov.withinDelegationBudget(agentId, event.project, event.subject)) {\n const blocked = agentId\n agentId = null\n outcome = \"halted\"\n reason = `delegation-budget: agent \"${blocked}\" depth-limit exceeded on ${event.project ?? \"?\"}/${event.subject ?? \"?\"}`\n }\n }\n\n // Annotate the reason when PM-gate took effect, so the audit trail\n // distinguishes pm-gated dispatches from regular ones.\n if (pm && outcome === \"dispatched\" && agentId) {\n reason = reason\n ? `${reason} (pm-gate: rubber-stamped by ${pm})`\n : `pm-gate: rubber-stamped by ${pm}`\n }\n\n const decision: IntentDecision = {\n eventId: event.id,\n decidedAt,\n decidedBy: effectiveDecidedBy,\n agentId,\n outcome,\n reason,\n }\n ledger.recordDecision(decision)\n return decision\n })\n\n return tx(now())\n}\n","import { logger } from \"../utils/logger\"\nimport type { IntentLedger } from \"./ledger\"\nimport { newEventId } from \"./ulid\"\nimport type {\n IntentDecision,\n IntentDivergence,\n IntentOutcomeKind,\n IntentSource,\n} from \"./types\"\n\n// Divergence reporter — Phase 1 commit 5 of the architectural rescue.\n//\n// During shadow mode (1b in research-rescue-plan.md §7), every dispatch\n// runs through both the legacy path and decideAndCommit. The legacy path\n// remains authoritative; the ledger's decision is recorded but does not\n// drive behaviour. The reporter compares the two and:\n//\n// - on agreement: no-op (the soak's success criterion is \"zero\n// divergences for ≥7 days\" — quiet operation IS the signal).\n// - on mismatch: log a structured warning AND write a row to\n// intent_divergences so the soak dashboard has a queryable surface.\n//\n// The shape of \"the legacy outcome\" is deliberately structural rather\n// than tied to any one source's existing output type — that lets the\n// commit-6 wiring adapt each call site (channel-router's `Routed`, the\n// workflow dispatcher's `DispatchResult`, etc.) into a common\n// LegacyOutcome at the comparison boundary instead of carrying source-\n// specific shapes through the reporter.\n\n/** Minimal projection of a legacy dispatch path's outcome — only the\n * fields we compare against the ledger decision. */\nexport interface LegacyOutcome {\n agentId: string | null\n outcome: IntentOutcomeKind\n /** Optional human-readable reason; recorded for forensics, never\n * compared. (Legacy reasons and ledger reasons phrase the same\n * decision differently — comparing them would produce noise.) */\n reason?: string | null\n}\n\n/**\n * Compare a ledger decision against the legacy path's outcome. On mismatch,\n * append a divergence row and emit a `[ledger-divergence]` log line.\n * Returns `true` when a divergence was recorded, `false` on agreement.\n *\n * Comparison is structural on the load-bearing fields: `outcome` and\n * `agentId`. `reason` strings differ harmlessly between paths and are\n * forensic-only — comparing them would produce noise that drowns out\n * the actual semantic mismatches we care about.\n */\nexport function reportDivergence(\n ledger: IntentLedger,\n source: IntentSource,\n ledgerDecision: IntentDecision,\n legacy: LegacyOutcome,\n now: () => number = Date.now,\n): boolean {\n if (decisionsAgree(ledgerDecision, legacy)) return false\n\n const ts = now()\n const record: IntentDivergence = {\n id: newEventId(ts),\n ts,\n source,\n eventId: ledgerDecision.eventId,\n decidedBy: ledgerDecision.decidedBy,\n ledgerAgentId: ledgerDecision.agentId,\n ledgerOutcome: ledgerDecision.outcome,\n ledgerReason: ledgerDecision.reason,\n legacyAgentId: legacy.agentId,\n legacyOutcome: legacy.outcome,\n legacyReason: legacy.reason ?? null,\n }\n ledger.recordDivergence(record)\n\n // Single-line structured log so journalctl + grep are sufficient triage\n // tools during the soak (commit 8 adds a UI; until then, journal is it).\n logger.warn(\n `[ledger-divergence] source=${source} ` +\n `event=${ledgerDecision.eventId} ` +\n `decided_by=${ledgerDecision.decidedBy} ` +\n `ledger=${ledgerDecision.outcome}/${ledgerDecision.agentId ?? \"null\"} ` +\n `legacy=${legacy.outcome}/${legacy.agentId ?? \"null\"}`,\n )\n return true\n}\n\n/** Two outcomes agree iff they pick the same target with the same outcome\n * kind. Reasons (free-form) are not part of agreement. */\nexport function decisionsAgree(\n ledgerDecision: IntentDecision,\n legacy: LegacyOutcome,\n): boolean {\n return (\n ledgerDecision.outcome === legacy.outcome &&\n ledgerDecision.agentId === legacy.agentId\n )\n}\n","import type { IntentLedger } from \"../ledger\"\nimport { decideAndCommit, type DispatchPolicy } from \"../decide\"\nimport { reportDivergence, type LegacyOutcome } from \"../divergence\"\nimport type { IntentDecision, IntentEventInput } from \"../types\"\n\n// Source adapter — cron scheduler. Phase 1 commit 6.d.\n//\n// Cron is the cleanest 1:1 dispatch source: one fire → one agent. The\n// scheduler's `executeJob(jobId)` is the dispatch entry point. We\n// instrument the two decision branches (hook-blocked vs hook-approved)\n// and pass the legacy outcome to a pass-through policy.\n\nexport interface CronJobProjection {\n jobId: string\n agentId: string\n /** When the job fired (used as part of the ULID-friendly sourceEventId). */\n firedAt: Date\n}\n\n/** Build the IntentEventInput for one cron fire. sourceEventId is\n * per-(jobId, firedAt) so concurrent re-fires (shouldn't happen but)\n * collapse via idempotency. Subject scopes active-task safety to one\n * fire-in-flight per job — but project=null disables Inv-ActiveTaskSafety,\n * so successive fires of the same job both dispatch (matches legacy). */\nexport function buildCronEventInput(\n proj: CronJobProjection,\n rawJson: string,\n now: () => number = Date.now,\n): IntentEventInput {\n return {\n ts: now(),\n source: \"cron\",\n sourceEventId: `${proj.jobId}:${proj.firedAt.getTime()}`,\n project: null,\n subject: `cron:${proj.jobId}`,\n intent: \"cron.fired\",\n rawJson,\n }\n}\n\n/** Pass-through policy. Same shape as router/workflow: divergences come\n * from ledger MECHANICS, not policy disagreement. */\nexport function buildCronPolicyFromLegacy(legacy: LegacyOutcome): DispatchPolicy {\n return {\n decidedBy: \"cron-scheduler\",\n decide: () => ({\n agentId: legacy.agentId,\n outcome: legacy.outcome === \"deduped\" ? \"halted\" : legacy.outcome,\n reason:\n legacy.outcome === \"deduped\"\n ? `legacy-dedup: ${legacy.reason ?? \"\"}`.trim()\n : legacy.reason ?? null,\n }),\n }\n}\n\nexport function recordCronDispatch(\n ledger: IntentLedger,\n proj: CronJobProjection,\n rawJson: string,\n legacyOutcome: LegacyOutcome,\n now: () => number = Date.now,\n): IntentDecision {\n const eventInput = buildCronEventInput(proj, rawJson, now)\n const policy = buildCronPolicyFromLegacy(legacyOutcome)\n const ledgerDecision = decideAndCommit(ledger, eventInput, policy, now)\n reportDivergence(ledger, \"cron\", ledgerDecision, legacyOutcome, now)\n return ledgerDecision\n}\n"],"mappings":"sIAIA,OAAS,iBAAAA,EAAe,aAAAC,EAAW,cAAAC,EAAY,gBAAAC,MAAoB,KACnE,OAAS,WAAAC,MAAe,OCoBxB,IAAMC,EAAc,CAAC,MAAO,SAAU,eAAe,EAK/CC,EAAmD,CACvD,SAAU,8BACV,MAAO,2BACP,SAAU,8BACV,QAAS,6BACT,OAAQ,4BACR,OAAQ,4BACR,SAAU,8BACV,KAAM,0BACN,KAAM,yBACR,EAEMC,EAAiB,qBAuBhB,SAASC,EACdC,EACAC,EAAyB,QAAQ,IACrB,CACZ,IAAMC,EAAYC,EAAgBF,EAAIJ,EAAmBG,CAAM,CAAC,CAAC,EACjE,GAAIE,EAAW,OAAOA,EACtB,IAAME,EAASD,EAAgBF,EAAIH,CAAc,CAAC,EAClD,OAAIM,GACG,KACT,CAGO,SAASD,EAAgBE,EAAmC,CACjE,GAAI,OAAOA,GAAU,SAAU,OAAO,KACtC,IAAMC,EAAaD,EAAM,KAAK,EAAE,YAAY,EAC5C,OAAQT,EAAkC,SAASU,CAAU,EACxDA,EACD,IACN,CChEA,IAAIC,EAIG,SAASC,EAAqBC,EAAyC,CAC5EF,EAAcE,CAChB,CAKO,SAASC,GAAuD,CACrE,OAAOH,CACT,CCyGO,SAASI,EACdC,EACAC,EACAC,EACAC,EAAoB,KAAK,IACzBC,EACgB,CAoGhB,OAnGWJ,EAAO,GAAG,YAAaK,GAAsC,CAGtE,IAAMC,EAAQN,EAAO,YAAYC,CAAK,EAYhCM,EAAMH,GAAcI,EAAqB,EACzCC,EAAKF,GAAK,QAAQD,EAAM,OAAO,EAC/BI,EAAqBD,EAAK,MAAMA,IAAOP,EAAO,UAK9CS,EAAQX,EACX,qBAAqBM,EAAM,EAAE,EAC7B,KAAMM,GAAMA,EAAE,YAAcF,CAAkB,EACjD,GAAIC,EAAO,OAAOA,EAMlB,IAAME,EAASb,EAAO,4BAA4BM,EAAM,QAASA,EAAM,OAAO,EAC9E,GAAIO,EAAQ,CACV,IAAMC,EAA2B,CAC/B,QAASR,EAAM,GACf,UAAAD,EACA,UAAWK,EACX,QAAS,KACT,QAAS,UACT,OAAQ,8BAA8BG,EAAO,oBAAeA,EAAO,SAAW,KAChF,EACA,OAAAb,EAAO,eAAec,CAAQ,EACvBA,EAMT,IAAMC,EAAeb,EAAO,OAAOI,CAAK,EACpCU,EAAyBD,EAAeA,EAAa,QAAU,KAC/DE,EAA8CF,EAAeA,EAAa,QAAU,SACpFG,EAAwBH,EAAeA,EAAa,OAAS,kBAIjE,GAAIR,GAAK,WAAaU,IAAY,cAAgBD,GAE5C,CADYT,EAAI,UAAUS,EAASV,EAAM,QAASA,EAAM,MAAM,EACpD,CACZ,IAAMa,EAAUH,EAChBA,EAAU,KACVC,EAAU,SACVC,EAAS,qBAAqBC,qBAA2Bb,EAAM,SAAW,gBAAgBA,EAAM,QAAU,eAS9G,GAAIC,GAAK,wBAA0BU,IAAY,cAAgBD,GACzD,CAACT,EAAI,uBAAuBS,EAASV,EAAM,QAASA,EAAM,OAAO,EAAG,CACtE,IAAMa,EAAUH,EAChBA,EAAU,KACVC,EAAU,SACVC,EAAS,6BAA6BC,8BAAoCb,EAAM,SAAW,OAAOA,EAAM,SAAW,MAMnHG,GAAMQ,IAAY,cAAgBD,IACpCE,EAASA,EACL,GAAGA,iCAAsCT,KACzC,8BAA8BA,KAGpC,IAAMK,EAA2B,CAC/B,QAASR,EAAM,GACf,UAAAD,EACA,UAAWK,EACX,QAAAM,EACA,QAAAC,EACA,OAAAC,CACF,EACA,OAAAlB,EAAO,eAAec,CAAQ,EACvBA,CACT,CAAC,EAESX,EAAI,CAAC,CACjB,CClMO,SAASiB,EACdC,EACAC,EACAC,EACAC,EACAC,EAAoB,KAAK,IAChB,CACT,GAAIC,EAAeH,EAAgBC,CAAM,EAAG,MAAO,GAEnD,IAAMG,EAAKF,EAAI,EACTG,EAA2B,CAC/B,GAAIC,EAAWF,CAAE,EACjB,GAAAA,EACA,OAAAL,EACA,QAASC,EAAe,QACxB,UAAWA,EAAe,UAC1B,cAAeA,EAAe,QAC9B,cAAeA,EAAe,QAC9B,aAAcA,EAAe,OAC7B,cAAeC,EAAO,QACtB,cAAeA,EAAO,QACtB,aAAcA,EAAO,QAAU,IACjC,EACA,OAAAH,EAAO,iBAAiBO,CAAM,EAI9BE,EAAO,KACL,8BAA8BR,WACnBC,EAAe,sBACVA,EAAe,oBACnBA,EAAe,WAAWA,EAAe,SAAW,iBACpDC,EAAO,WAAWA,EAAO,SAAW,QAClD,EACO,EACT,CAIO,SAASE,EACdH,EACAC,EACS,CACT,OACED,EAAe,UAAYC,EAAO,SAClCD,EAAe,UAAYC,EAAO,OAEtC,CCzEO,SAASO,EACdC,EACAC,EACAC,EAAoB,KAAK,IACP,CAClB,MAAO,CACL,GAAIA,EAAI,EACR,OAAQ,OACR,cAAe,GAAGF,EAAK,SAASA,EAAK,QAAQ,QAAQ,IACrD,QAAS,KACT,QAAS,QAAQA,EAAK,QACtB,OAAQ,aACR,QAAAC,CACF,CACF,CAIO,SAASE,EAA0BC,EAAuC,CAC/E,MAAO,CACL,UAAW,iBACX,OAAQ,KAAO,CACb,QAASA,EAAO,QAChB,QAASA,EAAO,UAAY,UAAY,SAAWA,EAAO,QAC1D,OACEA,EAAO,UAAY,UACf,iBAAiBA,EAAO,QAAU,KAAK,KAAK,EAC5CA,EAAO,QAAU,IACzB,EACF,CACF,CAEO,SAASC,EACdC,EACAN,EACAC,EACAM,EACAL,EAAoB,KAAK,IACT,CAChB,IAAMM,EAAaT,EAAoBC,EAAMC,EAASC,CAAG,EACnDO,EAASN,EAA0BI,CAAa,EAChDG,EAAiBC,EAAgBL,EAAQE,EAAYC,EAAQP,CAAG,EACtE,OAAAU,EAAiBN,EAAQ,OAAQI,EAAgBH,EAAeL,CAAG,EAC5DQ,CACT,CL/CA,SAASG,EAAcC,EAAgBC,EAAkC,CACvE,GAAI,CAACA,EAAiB,OAAOD,EAC7B,IAAME,EAAcD,EAAkB,EACtC,MAAO,GAAGD;AAAA;AAAA;AAAA,4BAA0DC,cAA4BC,+EAClG,CAMA,SAASC,EAAeC,EAAeC,EAAaC,EAAuB,CACzE,IAAMC,EAAmB,CAAC,EAE1B,QAAWC,KAAQJ,EAAM,MAAM,GAAG,EAChC,GAAII,IAAS,IACX,QAASC,EAAIJ,EAAKI,GAAKH,EAAKG,IAAKF,EAAO,KAAKE,CAAC,UACrCD,EAAK,SAAS,GAAG,EAAG,CAC7B,GAAM,CAACE,EAAOC,CAAO,EAAIH,EAAK,MAAM,GAAG,EACjCI,EAAO,SAASD,EAAS,EAAE,EAC3BE,EAAQH,IAAU,IAAML,EAAM,SAASK,EAAO,EAAE,EACtD,QAASD,EAAII,EAAOJ,GAAKH,EAAKG,GAAKG,EAAML,EAAO,KAAKE,CAAC,UAC7CD,EAAK,SAAS,GAAG,EAAG,CAC7B,GAAM,CAACM,EAAGC,CAAC,EAAIP,EAAK,MAAM,GAAG,EAAE,IAAI,MAAM,EACzC,QAASC,EAAIK,EAAGL,GAAKM,EAAGN,IAAKF,EAAO,KAAKE,CAAC,OAE1CF,EAAO,KAAK,SAASC,EAAM,EAAE,CAAC,EAIlC,MAAO,CAAC,GAAG,IAAI,IAAID,CAAM,CAAC,EAAE,KAAK,CAACO,EAAGC,IAAMD,EAAIC,CAAC,CAClD,CAEO,SAASC,EAAgBC,EAAoBC,EAAaC,EAAwB,CACvF,IAAMC,EAASH,EAAW,KAAK,EAAE,MAAM,KAAK,EAC5C,GAAIG,EAAO,SAAW,EAAG,MAAM,IAAI,MAAM,iBAAiBH,GAAY,EAEtE,IAAMI,EAAUlB,EAAeiB,EAAO,CAAC,EAAG,EAAG,EAAE,EACzCE,EAAQnB,EAAeiB,EAAO,CAAC,EAAG,EAAG,EAAE,EACvCG,EAAcpB,EAAeiB,EAAO,CAAC,EAAG,EAAG,EAAE,EAC7CI,EAASrB,EAAeiB,EAAO,CAAC,EAAG,EAAG,EAAE,EACxCK,EAAatB,EAAeiB,EAAO,CAAC,EAAG,EAAG,CAAC,EAG3CM,EAAM,IAAI,KAAK,eAAe,QAAS,CAC3C,SAAUP,EACV,KAAM,UACN,MAAO,UACP,IAAK,UACL,KAAM,UACN,OAAQ,UACR,OAAQ,EACV,CAAC,EAGKQ,EAAY,IAAI,KAAKT,EAAM,QAAQ,EAAI,GAAM,EACnDS,EAAU,WAAW,EAAG,CAAC,EAGzB,IAAMC,EAAQ,IAAI,KAAKD,EAAU,QAAQ,EAAI,IAAM,GAAK,GAAK,GAAK,GAAI,EAEtE,KAAOA,EAAYC,GAAO,CACxB,IAAMC,EAAQH,EAAI,cAAcC,CAAS,EACnCG,EAAOC,GAAiB,SAASF,EAAM,KAAMG,GAAMA,EAAE,OAASD,CAAI,GAAG,OAAS,IAAK,EAAE,EAErFE,EAAIH,EAAI,QAAQ,EAChBI,EAAIJ,EAAI,MAAM,EACdK,EAAML,EAAI,KAAK,EACfM,EAAMN,EAAI,OAAO,EACjBO,EAAMV,EAAU,OAAO,EAE7B,GACEN,EAAQ,SAASY,CAAC,GAClBX,EAAM,SAASY,CAAC,GAChBX,EAAY,SAASY,CAAG,GACxBX,EAAO,SAASY,CAAG,GACnBX,EAAW,SAASY,CAAG,EAEvB,OAAOV,EAITA,EAAU,QAAQA,EAAU,QAAQ,EAAI,GAAM,EAGhD,MAAM,IAAI,MAAM,+BAA+BV,kBAA2B,CAC5E,CAGA,IAAMqB,EAAc,EACdC,EAAe,CAAC,IAAQ,IAAQ,IAAS,IAAS,IAAS,EAKpDC,EAAN,KAAoB,CACjB,KAAkC,IAAI,IACtC,OAAqD,IAAI,IACzD,SACA,MACA,QACA,YACA,QAAU,GACV,eACA,IAER,YACEC,EACAC,EACAC,EACAC,EAAoC,QAAQ,MAAM,KAAK,QAAS,QAAQ,EACxE,CACA,KAAK,SAAWF,EAChB,KAAK,MAAQC,EACb,KAAK,IAAMC,EACX,KAAK,QAAUC,EAAQ,QAAQ,IAAI,EAAG,mBAAmB,EACzD,KAAK,YAAcA,EAAQ,QAAQ,IAAI,EAAG,6BAA6B,EAEvE,OAAW,CAACC,EAAIC,CAAG,IAAK,OAAO,QAAQN,EAAO,KAAK,EACjD,KAAK,KAAK,IAAIK,EAAI,CAChB,GAAAA,EACA,QAASC,EAAI,QACb,SAAUA,EAAI,SACd,SAAUA,EAAI,SACd,MAAOA,EAAI,MACX,OAAQA,EAAI,OACZ,QAASA,EAAI,QACb,MAAOA,EAAI,MACX,gBAAiBA,EAAI,gBACrB,QAASA,EAAI,QACb,kBAAmB,EACnB,UAAW,EACX,cAAe,CACjB,CAAC,CAEL,CAMA,kBAAkBC,EAA8B,CAC9C,KAAK,eAAiBA,CACxB,CAEA,MAAM,OAAuB,CAC3B,KAAK,QAAU,GAEVC,EAAW,KAAK,OAAO,GAC1BC,EAAU,KAAK,QAAS,CAAE,UAAW,EAAK,CAAC,EAI7C,IAAMC,EAAa,KAAK,iBAAiB,EACzC,GAAIA,EAAW,OAAS,EAAG,CACzB,KAAK,IAAI,YAAYA,EAAW,kDAAkD,EAClF,OAAW,CAAE,MAAAC,EAAO,SAAAC,CAAS,IAAKF,EAChC,KAAK,IAAI,MAAMC,yBAA6BC,EAAS,YAAY,GAAG,EAGtE,KAAK,kBAAkBF,CAAU,EAAE,MAAOG,GAAM,CAC9C,KAAK,IAAI,gCAAgCA,EAAE,SAAS,CACtD,CAAC,EAGH,OAAW,CAACR,EAAIS,CAAG,IAAK,KAAK,KAAM,CACjC,GAAI,CAACA,EAAI,QAAS,CAChB,KAAK,IAAI,QAAQT,0BAA2B,EAC5C,SAEF,KAAK,aAAaA,CAAE,EAGtB,KAAK,IAAI,GAAG,KAAK,KAAK,4BAA4B,MAAM,KAAK,KAAK,KAAK,OAAO,CAAC,EAAE,OAAQU,GAAMA,EAAE,OAAO,EAAE,gBAAgB,CAC5H,CAEA,MAAM,MAAsB,CAC1B,KAAK,QAAU,GACf,QAAWC,KAAS,KAAK,OAAO,OAAO,EACrC,aAAaA,CAAK,EAEpB,KAAK,OAAO,MAAM,EAGlB,KAAK,aAAa,CACpB,CAEQ,aAAaL,EAAqB,CACxC,IAAMG,EAAM,KAAK,KAAK,IAAIH,CAAK,EAC/B,GAAI,CAACG,GAAO,CAACA,EAAI,SAAW,CAAC,KAAK,QAAS,OAG3C,IAAMG,EAAW,KAAK,OAAO,IAAIN,CAAK,EAClCM,GAAU,aAAaA,CAAQ,EAEnC,GAAI,CACF,IAAMC,EAAU3C,EAAgBuC,EAAI,SAAU,IAAI,KAAQA,EAAI,QAAQ,EACtEA,EAAI,QAAUI,EACdJ,EAAI,aAAe,GACnB,IAAMK,EAAQD,EAAQ,QAAQ,EAAI,KAAK,IAAI,EAE3C,KAAK,IAAI,QAAQP,gBAAoBO,EAAQ,YAAY,SAAS,KAAK,MAAMC,EAAQ,GAAI,KAAK,EAE9F,IAAMH,EAAQ,WAAW,IAAM,KAAK,WAAWL,CAAK,EAAGQ,CAAK,EAC5D,KAAK,OAAO,IAAIR,EAAOK,CAAK,CAC9B,OAASH,EAAP,CACA,KAAK,IAAI,uBAAuBF,OAAWE,EAAE,SAAS,CACxD,CACF,CAEQ,cAAcF,EAAeS,EAAuB,CAC1D,IAAMN,EAAM,KAAK,KAAK,IAAIH,CAAK,EAC/B,GAAI,CAACG,GAAO,CAACA,EAAI,SAAW,CAAC,KAAK,QAAS,OAC3C,GAAIM,GAAWvB,EAAa,CAC1B,KAAK,IAAI,QAAQc,oBAAwBd,wCAAkD,EAC3F,KAAK,aAAac,CAAK,EACvB,OAGF,IAAMQ,EAAQrB,EAAasB,CAAO,GAAKtB,EAAaA,EAAa,OAAS,CAAC,EAC3EgB,EAAI,aAAe,GAEnB,KAAK,IAAI,QAAQH,YAAgBS,EAAU,KAAKvB,QAAkB,KAAK,MAAMsB,EAAQ,GAAI,IAAI,EAE7F,IAAMH,EAAQ,WAAW,IAAM,KAAK,WAAWL,EAAOS,EAAU,CAAC,EAAGD,CAAK,EACzE,KAAK,OAAO,IAAIR,EAAOK,CAAK,CAC9B,CAQQ,2BACNL,EACAU,EACAC,EACAC,EACoD,CACpD,GAAIC,EAAc,MAAM,IAAM,MAC9B,GAAI,CACF,IAAMC,EAAWC,EACfC,EAAiB,EACjB,CAAE,MAAAhB,EAAO,QAAAU,EAAS,QAAAC,CAAQ,EAC1B,KAAK,UAAU,CAAE,MAAAX,EAAO,QAAAU,EAAS,QAASC,EAAQ,YAAY,CAAE,CAAC,EACjEC,CACF,EACA,OAAIE,EAAS,UAAY,aAChB,CAAE,QAASA,EAAS,QAAS,UAAWA,EAAS,SAAU,EAEpE,MACF,OAASZ,EAAP,CACA,KAAK,IAAI,kBAAkBF,qBAAyBE,GAAG,SAAWA,GAAG,EACrE,MACF,CACF,CAEA,MAAc,WAAWF,EAAeiB,EAAuB,EAAkB,CAC/E,IAAMd,EAAM,KAAK,KAAK,IAAIH,CAAK,EAC/B,GAAI,CAACG,GAAO,CAAC,KAAK,QAAS,OAE3B,IAAMe,EAAUD,EAAe,EAKzBN,EAAU,IAAI,KAGpB,GAAI,KAAK,OAAO,IAAI,cAAqB,EAAG,CAC1C,IAAMQ,EAAa,MAAM,KAAK,MAAM,QAAQ,eAAuB,CACjE,MAAO,eACP,MAAAnB,EACA,MAAOG,EAAI,MACX,OAAQA,EAAI,MACd,CAAC,EACD,GAAIgB,EAAW,QAAS,CACtB,KAAK,IAAI,QAAQnB,uBAA2BmB,EAAW,SAAS,EAChE,KAAK,2BAA2BnB,EAAOG,EAAI,MAAOQ,EAAS,CACzD,QAAS,KAAM,QAAS,SACxB,OAAQ,qBAAqBQ,EAAW,SAAW,KAAK,KAAK,CAC/D,CAAC,EACD,KAAK,aAAanB,CAAK,EACvB,QAIJ,KAAK,IAAI,GAAGkB,EAAU,UAAUD,MAAmB,oBAAoBjB,gBAAoBG,EAAI,QAAQ,EACvG,IAAMiB,EAAYT,EAClBR,EAAI,QAAUiB,EACdjB,EAAI,YAMJ,IAAMkB,EAAY,KAAK,2BAA2BrB,EAAOG,EAAI,MAAOQ,EAAS,CAC3E,QAASR,EAAI,MAAO,QAAS,aAAc,OAAQe,EAAU,SAASD,IAAiB,IACzF,CAAC,EAED,GAAI,CACF,IAAMK,EAAW,MAAM,KAAK,SAAS,QAAQ,CAC3C,QAAS3E,EAAcwD,EAAI,OAAQA,EAAI,eAAe,EACtD,QAASA,EAAI,MACb,MAAOA,EAAI,MACX,UAAAkB,EAKA,QAAS,CAAE,QAAS,OAAQ,OAAQ,QAAQrB,GAAQ,CACtD,CAAC,EAEKuB,EAAwB,CAC5B,MAAAvB,EACA,UAAAoB,EACA,YAAa,IAAI,KACjB,QAAS,CAACE,EAAS,MACnB,SAAUA,EAAS,QACnB,MAAOA,EAAS,MAChB,SAAUA,EAAS,UAAY,KAAK,IAAI,EAAIF,EAAU,QAAQ,EAC9D,QAAAF,EACA,aAAAD,CACF,EAEA,GAAIK,EAAS,MAAO,CASlB,GARAnB,EAAI,oBACJA,EAAI,gBACJA,EAAI,UAAYmB,EAAS,MACzB,KAAK,IAAI,QAAQtB,cAAkBG,EAAI,mCAAmCmB,EAAS,OAAO,EAG1F,MAAM,KAAK,cAAcnB,EAAKmB,EAAS,KAAK,EAExCnB,EAAI,QAAQ,SAAS,SAAS,GAAKA,EAAI,mBAAqB,EAAG,CACjEA,EAAI,QAAU,GACd,KAAK,IAAI,QAAQH,qBAAyBG,EAAI,sCAAsC,EAIpF,GAAI,CACF,IAAMqB,EAAM,MAAMC,EACfC,GAAQC,EAAUD,EAAK,SAAS1B,YAAiB,EAAK,EACvD,CAAE,OAAQ,EAAM,CAClB,EACKwB,EAAI,SACP,KAAK,IAAI,kCAAkCxB,OAAWwB,EAAI,OAAO,CAErE,OAAStB,EAAP,CACA,KAAK,IAAI,kCAAkCF,OAAWE,EAAE,SAAS,CACnE,CACA,MAAM,KAAK,eAAeC,CAAG,EAI/B,KAAK,OAAOoB,CAAM,EAClB,KAAK,cAAcvB,EAAOiB,CAAY,EACtC,YAGId,EAAI,kBAAoB,GAC1B,KAAK,IAAI,QAAQH,sBAA0BG,EAAI,8BAA8B,EAE/EA,EAAI,kBAAoB,EACxBA,EAAI,YAAc,IAAI,KACtBA,EAAI,UAAY,OAChBA,EAAI,aAAe,GACnB,KAAK,IAAI,QAAQH,mBAAuBuB,EAAO,YAAY,EAI7D,KAAK,OAAOA,CAAM,EAGd,KAAK,OAAO,IAAI,eAAsB,GACxC,MAAM,KAAK,MAAM,QAAQ,gBAAwB,CAC/C,MAAO,gBACP,MAAAvB,EACA,QAASuB,EAAO,QAChB,SAAUA,EAAO,SACjB,MAAOA,EAAO,MAAQ,IAAI,MAAMA,EAAO,KAAK,EAAI,MAClD,CAAC,CAEL,OAASrB,EAAP,CACAC,EAAI,oBACJA,EAAI,gBACJA,EAAI,UAAYD,EAAE,QAClB,KAAK,IAAI,QAAQF,aAAiBE,EAAE,SAAS,EAE7C,MAAM,KAAK,cAAcC,EAAKD,EAAE,OAAO,EACvC,KAAK,cAAcF,EAAOiB,CAAY,EACtC,MACF,CAGA,KAAK,aAAa,EAGlB,KAAK,aAAajB,CAAK,CACzB,CAQQ,kBAA6D,CACnE,IAAM4B,EAAmD,CAAC,EAEpDC,EAAW,KAAK,aAAa,EACnC,GAAI,CAACA,EAAU,OAAOD,EAEtB,OAAW,CAAC5B,EAAOG,CAAG,IAAK,KAAK,KAAM,CACpC,GAAI,CAACA,EAAI,QAAS,SAElB,IAAM2B,EAAaD,EAAS7B,CAAK,EACjC,GAAI,CAAC8B,EAAY,SAEjB,IAAMC,EAAU,IAAI,KAAKD,CAAU,EACnC,GAAI,CAEF,IAAME,EAAgBpE,EAAgBuC,EAAI,SAAU4B,EAAS5B,EAAI,QAAQ,EACrE6B,EAAc,QAAQ,EAAI,KAAK,IAAI,GACrCJ,EAAO,KAAK,CAAE,MAAA5B,EAAO,SAAUgC,CAAc,CAAC,CAElD,MAAE,CAEF,EAGF,OAAOJ,CACT,CAKA,MAAc,kBAAkBA,EAAiE,CAC/F,OAAW,CAAE,MAAA5B,EAAO,SAAAC,CAAS,IAAK2B,EAAQ,CACxC,IAAMzB,EAAM,KAAK,KAAK,IAAIH,CAAK,EAC/B,GAAI,GAACG,GAAO,CAACA,EAAI,SAEjB,MAAK,IAAI,uBAAuBH,kBAAsBC,EAAS,YAAY,IAAI,EAE/E,GAAI,CACF,IAAMqB,EAAW,MAAM,KAAK,SAAS,QAAQ,CAC3C,QAAS3E,EACP,wCAAmCsD,EAAS,YAAY;AAAA;AAAA,EAASE,EAAI,SACrEA,EAAI,eACN,EACA,QAASA,EAAI,MACb,QAAS,CAAE,QAAS,OAAQ,OAAQ,QAAQH,GAAQ,CACtD,CAAC,EAEKuB,EAAwB,CAC5B,MAAAvB,EACA,UAAW,IAAI,KACf,YAAa,IAAI,KACjB,QAAS,CAACsB,EAAS,MACnB,SAAUA,EAAS,QACnB,MAAOA,EAAS,MAChB,SAAUA,EAAS,UAAY,EAC/B,QAAS,EACX,EAEA,KAAK,OAAOC,CAAM,EAEdD,EAAS,MACX,KAAK,IAAI,eAAetB,cAAkBsB,EAAS,OAAO,GAE1D,KAAK,IAAI,eAAetB,cAAkB,EAC1CG,EAAI,YAAc,IAAI,MAExBA,EAAI,QAAU,IAAI,KAClBA,EAAI,WACN,OAASD,EAAP,CACA,KAAK,IAAI,eAAeF,aAAiBE,EAAE,SAAS,CACtD,GAGF,KAAK,aAAa,CACpB,CAIA,MAAc,cAAcC,EAAmB8B,EAA8B,CAC3E,GAAI,GAAC9B,EAAI,QAAQ,SAAS,QAAQ,GAAKA,EAAI,kBAAoB,GAC/D,IAAI,CAAC,KAAK,eAAgB,CACxB,KAAK,IAAI,iBAAiBA,EAAI,eAAeA,EAAI,wBAAwB8B,EAAM,MAAM,EAAG,GAAG,GAAG,EAC9F,OAGF,GAAI,CACF,MAAM,KAAK,eAAe9B,EAAI,GAAIA,EAAI,MAAO8B,EAAO9B,EAAI,iBAAiB,CAC3E,MAAE,CAEF,EACF,CAEA,MAAc,eAAeA,EAAkC,CAC7D,GAAI,CAAC,KAAK,eAAgB,CACxB,KAAK,IAAI,iBAAiBA,EAAI,+BAA+BA,EAAI,wCAAwC,EACzG,OAGF,GAAI,CACF,MAAM,KAAK,eACTA,EAAI,GACJA,EAAI,MACJ,uBAAuBA,EAAI,uDAAuDA,EAAI,WAAa,YACnGA,EAAI,iBACN,CACF,MAAE,CAEF,CACF,CAIQ,cAAqB,CAC3B,GAAI,CACF,IAAM+B,EAA+B,CAAC,EACtC,OAAW,CAACxC,EAAIS,CAAG,IAAK,KAAK,KACvBA,EAAI,UAAS+B,EAAKxC,CAAE,EAAIS,EAAI,QAAQ,YAAY,GAEtD,IAAMgC,EAAM1C,EAAQ,QAAQ,IAAI,EAAG,cAAc,EAC5CI,EAAWsC,CAAG,GAAGrC,EAAUqC,EAAK,CAAE,UAAW,EAAK,CAAC,EACxDC,EAAc,KAAK,YAAa,KAAK,UAAUF,EAAM,KAAM,CAAC,CAAC,CAC/D,MAAE,CAEF,CACF,CAEQ,cAA8C,CACpD,GAAI,CACF,OAAKrC,EAAW,KAAK,WAAW,EACzB,KAAK,MAAMwC,EAAa,KAAK,YAAa,OAAO,CAAC,EADf,IAE5C,MAAE,CACA,OAAO,IACT,CACF,CAEQ,OAAOd,EAA6B,CAC1C,GAAI,CACF,IAAMe,EAAS7C,EAAQ,KAAK,QAAS8B,EAAO,KAAK,EAC5C1B,EAAWyC,CAAM,GACpBxC,EAAUwC,EAAQ,CAAE,UAAW,EAAK,CAAC,EAGvC,IAAMC,EAAW,GAAGhB,EAAO,UAAU,YAAY,EAAE,QAAQ,QAAS,GAAG,SACvEa,EACE3C,EAAQ6C,EAAQC,CAAQ,EACxB,KAAK,UAAUhB,EAAQ,KAAM,CAAC,CAChC,CACF,MAAE,CAEF,CACF,CAKA,MAAuB,CACrB,OAAO,MAAM,KAAK,KAAK,KAAK,OAAO,CAAC,CACtC,CAKA,QAA6K,CAC3K,IAAMiB,EAAO,MAAM,KAAK,KAAK,KAAK,OAAO,CAAC,EACpCZ,EAAS,KAAK,iBAAiB,EAErC,MAAO,CACL,QAASY,EAAK,OAAOpC,GAAKA,EAAE,SAAWA,EAAE,oBAAsB,CAAC,EAAE,OAClE,QAASoC,EAAK,OAAOpC,GAAKA,EAAE,SAAWA,EAAE,kBAAoB,CAAC,EAAE,OAChE,SAAUoC,EAAK,OAAOpC,GAAK,CAACA,EAAE,OAAO,EAAE,OACvC,OAAQwB,EAAO,OACf,KAAMY,EAAK,IAAIpC,IAAM,CACnB,GAAIA,EAAE,GACN,OAASA,EAAE,QAAuBA,EAAE,aAAe,WAAaA,EAAE,kBAAoB,EAAI,UAAY,UAAjF,WACrB,kBAAmBA,EAAE,kBACrB,UAAWA,EAAE,SACf,EAAE,CACJ,CACF,CACF","names":["writeFileSync","mkdirSync","existsSync","readFileSync","resolve","VALID_MODES","PER_SOURCE_ENV_VAR","GLOBAL_ENV_VAR","getLedgerMode","source","env","perSource","parseLedgerMode","global","value","normalized","_governance","setDefaultGovernance","g","getDefaultGovernance","decideAndCommit","ledger","input","policy","now","governance","decidedAt","event","gov","getDefaultGovernance","pm","effectiveDecidedBy","prior","d","active","decision","policyResult","agentId","outcome","reason","blocked","reportDivergence","ledger","source","ledgerDecision","legacy","now","decisionsAgree","ts","record","newEventId","logger","buildCronEventInput","proj","rawJson","now","buildCronPolicyFromLegacy","legacy","recordCronDispatch","ledger","legacyOutcome","eventInput","policy","ledgerDecision","decideAndCommit","reportDivergence","withOutputCap","prompt","maxOutputTokens","approxChars","parseCronField","field","min","max","values","part","i","range","stepStr","step","start","a","b","getNextCronDate","expression","after","timezone","fields","minutes","hours","daysOfMonth","months","daysOfWeek","fmt","candidate","limit","parts","get","type","p","m","h","dom","mon","dow","MAX_RETRIES","RETRY_DELAYS","CronScheduler","config","registry","hooks","log","resolve","id","def","cb","existsSync","mkdirSync","missedRuns","jobId","missedAt","e","job","j","timer","existing","nextRun","delay","attempt","agentId","firedAt","legacy","getLedgerMode","decision","recordCronDispatch","getDefaultLedger","retryAttempt","isRetry","hookResult","startedAt","intentRef","response","result","res","applyConfigMutation","cfg","setAtPath","missed","lastRuns","lastRunStr","lastRun","shouldHaveRun","error","data","dir","writeFileSync","readFileSync","runDir","filename","jobs"]}
|
package/dist/chunk-BHSPAP4U.js
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import{c as k}from"./chunk-WJXU5F4Z.js";import{existsSync as p,mkdirSync as b,writeFileSync as h,readFileSync as w}from"fs";import{resolve as c}from"path";import{createHash as D}from"crypto";var d="<!-- agentx-managed: hash=",g=" -->";function v(n){return D("sha256").update(n??"").digest("hex").slice(0,16)}function S(n){return`${d}${v(n)}${g}`}function M(n){let s=n.split(`
|
|
2
|
-
`,1)[0]??"";return!s.startsWith(d)||!s.endsWith(g)?null:s.slice(d.length,s.length-g.length)}function A(n,s){let r=`${n} ${s.systemPrompt||""} ${s.name}`.toLowerCase();return/cod(e|ing|er)|develop|engineer|program/.test(r)?"coding":/pm|project.?manag|product|scrum/.test(r)?"pm":/devops|deploy|infra|ops|sre|ci.?cd/.test(r)?"devops":/qa|test|quality|forensic/.test(r)?"qa":"general"}function $(n,s,r){let t=A(n,s),e=[];switch(e.push(S(s.systemPrompt)),e.push(`# ${s.name}`),e.push(""),e.push("@AGENTS.md"),e.push(""),s.systemPrompt&&s.systemPrompt.trim().length>0&&(e.push("## Role"),e.push(""),e.push(s.systemPrompt.trimEnd()),e.push("")),e.push("## Commands"),e.push(""),p(c(s.workspace,"package.json"))?(e.push("```bash"),e.push("npm install # install dependencies"),e.push("npm test # run tests"),e.push("npm run build # build project"),e.push("```")):p(c(s.workspace,"requirements.txt"))&&(e.push("```bash"),e.push("pip install -r requirements.txt"),e.push("pytest"),e.push("```")),e.push(""),e.push("## Conventions"),e.push(""),t){case"coding":e.push("- Write tests for new code before committing"),e.push("- Keep functions small and focused"),e.push("- Follow existing patterns in the codebase"),e.push("- Run tests before pushing");break;case"pm":e.push("- Keep responses concise (3-5 lines main message)"),e.push("- Reference issues with #IID and MRs with !IID"),e.push("- Update issue labels and milestones when relevant"),e.push("- Summarize decisions, don't narrate process");break;case"devops":e.push("- Always check service status before making changes"),e.push("- Never run destructive commands without confirmation"),e.push("- Log all deployment actions"),e.push("- Verify changes in staging before production");break;case"qa":e.push("- Document reproduction steps for every bug"),e.push("- Include expected vs actual behavior"),e.push("- Check both happy path and edge cases"),e.push("- Attach screenshots when reporting UI issues");break;default:e.push("- Be concise \u2014 lead with the answer, skip preamble"),e.push("- Follow existing patterns in the codebase")}return e.push(""),e.push("## Cross-Channel Messaging"),e.push(""),e.push("You can send messages to any channel proactively:"),e.push("```bash"),e.push(`curl -X POST http://localhost:${r}/send \\`),e.push(' -H "Content-Type: application/json" \\'),e.push(` -d '{"channel":"telegram","chatId":"<id>","text":"<message>","agentId":"${n}"}'`),e.push("```"),e.push("Channels: telegram, whatsapp, gitlab, discord"),e.push(""),e.join(`
|
|
3
|
-
`)}function C(n,s){let r={autoMemoryEnabled:!0,env:{CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS:"1"}};s.permissionMode==="bypassPermissions"||(s.permissionMode==="plan"?r.permissions={deny:["Bash(rm -rf *)","Bash(drop *)","Bash(DELETE *)"]}:r.permissions={deny:["Bash(rm -rf /)","Bash(> /dev/sda*)","Bash(mkfs*)"]});let t={};return t.Notification=[{matcher:"",hooks:[{type:"command",command:`echo "[${n}] notification: $(jq -r '.type // "unknown"')" >> /tmp/agentx-${n}.log`}]}],t.PostToolUse=[{matcher:"Bash",hooks:[{type:"command",command:`jq -r '.tool_input.command // empty' >> /tmp/agentx-${n}-commands.log`}]}],t.SessionStart=[{matcher:"compact",hooks:[{type:"command",command:`echo "Reminder: You are ${s.name} (${n}). Follow the conventions in CLAUDE.md."`}]}],r.hooks=t,r}function j(n,s){let r=A(n,s),t=[];switch(r){case"coding":t.push({name:"testing.md",content:`---
|
|
4
|
-
paths:
|
|
5
|
-
- "**/*.test.{ts,tsx,js,jsx}"
|
|
6
|
-
- "**/*.spec.{ts,tsx,js,jsx}"
|
|
7
|
-
- "test/**/*"
|
|
8
|
-
---
|
|
9
|
-
|
|
10
|
-
# Testing Rules
|
|
11
|
-
|
|
12
|
-
- Every new function should have a corresponding test
|
|
13
|
-
- Test both happy path and error cases
|
|
14
|
-
- Use descriptive test names that explain what's being tested
|
|
15
|
-
- Mock external dependencies, not internal modules
|
|
16
|
-
`}),t.push({name:"code-quality.md",content:`# Code Quality
|
|
17
|
-
|
|
18
|
-
- No console.log in production code (use a logger)
|
|
19
|
-
- Handle errors explicitly \u2014 no empty catch blocks
|
|
20
|
-
- Keep files under 300 lines
|
|
21
|
-
- Extract repeated logic into shared utilities
|
|
22
|
-
`});break;case"devops":t.push({name:"deployment-safety.md",content:`# Deployment Safety
|
|
23
|
-
|
|
24
|
-
- Always verify staging before production
|
|
25
|
-
- Check disk space and memory before deploying
|
|
26
|
-
- Never expose secrets in logs or commits
|
|
27
|
-
- Use rollback-safe deployment strategies
|
|
28
|
-
`});break;case"pm":t.push({name:"communication.md",content:`# Communication Rules
|
|
29
|
-
|
|
30
|
-
- Keep GitLab comments under 5 lines for the main message
|
|
31
|
-
- Use <details> for verbose output (logs, commands, steps)
|
|
32
|
-
- Reference issues with #IID, merge requests with !IID
|
|
33
|
-
- Never mention Telegram handles on GitLab
|
|
34
|
-
`});break;case"qa":t.push({name:"bug-reports.md",content:`# Bug Report Format
|
|
35
|
-
|
|
36
|
-
Always include:
|
|
37
|
-
1. Steps to reproduce
|
|
38
|
-
2. Expected behavior
|
|
39
|
-
3. Actual behavior
|
|
40
|
-
4. Environment (browser, OS, version)
|
|
41
|
-
5. Screenshots if applicable
|
|
42
|
-
`});break}return t}function P(n,s,r="19900",t=console.error){let e=[],a=[],i=s.workspace;if(!p(i))return t(`Workspace not found: ${i}`),{created:e,skipped:a};if(s.tier!=="claude-code")return{created:e,skipped:a};let u=(o,l)=>{p(o)?a.push(o):(b(c(o,".."),{recursive:!0}),h(o,l),e.push(o))},m=(o,l,R)=>{if(!p(o)){b(c(o,".."),{recursive:!0}),h(o,l),e.push(o);return}let f;try{f=w(o,"utf8")}catch{a.push(o);return}let y=M(f);if(y===null){a.push(o);return}if(y===R){a.push(o);return}h(o,l),e.push(`${o} (refreshed)`)};u(c(i,"AGENTS.md"),k({name:s.name,id:n,role:s.systemPrompt?.split(`
|
|
43
|
-
`)[0],workspace:i,tier:s.tier})),m(c(i,"CLAUDE.md"),$(n,s,r),v(s.systemPrompt));let E=c(i,".claude/settings.json");u(E,JSON.stringify(C(n,s),null,2));let x=j(n,s);for(let o of x)u(c(i,".claude/rules",o.name),o.content);return e.length>0&&t(`[${n}] workspace setup: created ${e.length} file(s)`),{created:e,skipped:a}}function N(n,s){let r=c(n,".claude/settings.json");if(!p(r))return!1;try{let t=JSON.parse(w(r,"utf-8")),e=!1;for(let[a,i]of Object.entries(s))if(typeof i=="object"&&i!==null&&!Array.isArray(i)){t[a]||(t[a]={});for(let[u,m]of Object.entries(i))t[a][u]===void 0&&(t[a][u]=m,e=!0)}else t[a]===void 0&&(t[a]=i,e=!0);return e&&h(r,JSON.stringify(t,null,2)),e}catch{return!1}}function U(n,s="19900",r=console.error){let t=0,e=0;for(let[a,i]of Object.entries(n)){if(i.tier!=="claude-code")continue;let u=P(a,i,s,r);t+=u.created.length,N(i.workspace,{env:{CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS:"1"}})&&e++}t>0&&r(`Workspace setup: ${t} file(s) created across agent workspaces`),e>0&&r(`Workspace patch: ${e} workspace(s) updated with agent teams support`)}export{M as a,$ as b,P as c,U as d};
|
|
44
|
-
//# sourceMappingURL=chunk-BHSPAP4U.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/agents/workspace-setup.ts"],"sourcesContent":["import { existsSync, mkdirSync, writeFileSync, readFileSync } from \"fs\"\nimport { resolve } from \"path\"\nimport { createHash } from \"crypto\"\nimport type { AgentDef } from \"@/daemon/config\"\nimport { generateAgentsMd } from \"./bootstrap\"\n\n// Marker stamped into auto-generated CLAUDE.md so we can tell agentx-managed\n// files apart from user-edited ones. On daemon start, files with the marker\n// are silently regenerated when the systemPrompt hash changes; files without\n// the marker are treated as user-owned and never touched.\nconst MANAGED_MARKER_PREFIX = \"<!-- agentx-managed: hash=\"\nconst MANAGED_MARKER_SUFFIX = \" -->\"\n\nfunction systemPromptHash(systemPrompt: string | undefined): string {\n return createHash(\"sha256\")\n .update(systemPrompt ?? \"\")\n .digest(\"hex\")\n .slice(0, 16)\n}\n\nfunction buildManagedMarker(systemPrompt: string | undefined): string {\n return `${MANAGED_MARKER_PREFIX}${systemPromptHash(systemPrompt)}${MANAGED_MARKER_SUFFIX}`\n}\n\n/**\n * Read the managed marker from an existing file's first line, if present.\n * Returns the hash (truncated sha256 hex) when found, null otherwise.\n * Files without the marker are user-managed and untouchable.\n */\nexport function readManagedHash(content: string): string | null {\n const firstLine = content.split(\"\\n\", 1)[0] ?? \"\"\n if (!firstLine.startsWith(MANAGED_MARKER_PREFIX)) return null\n if (!firstLine.endsWith(MANAGED_MARKER_SUFFIX)) return null\n return firstLine.slice(MANAGED_MARKER_PREFIX.length, firstLine.length - MANAGED_MARKER_SUFFIX.length)\n}\n\n// --- Workspace Setup: align agent workspaces with Claude Code best practices ---\n//\n// Generates on daemon start (if missing):\n// CLAUDE.md — project conventions, under 200 lines, @AGENTS.md import\n// AGENTS.md — agents.md spec for cross-tool compatibility\n// .claude/settings.json — hooks, permissions, auto memory\n// .claude/rules/ — path-specific rules based on agent role\n\n/** Detect agent role from systemPrompt or agent ID */\nfunction detectRole(agentId: string, def: AgentDef): \"coding\" | \"pm\" | \"devops\" | \"qa\" | \"general\" {\n const text = `${agentId} ${def.systemPrompt || \"\"} ${def.name}`.toLowerCase()\n if (/cod(e|ing|er)|develop|engineer|program/.test(text)) return \"coding\"\n if (/pm|project.?manag|product|scrum/.test(text)) return \"pm\"\n if (/devops|deploy|infra|ops|sre|ci.?cd/.test(text)) return \"devops\"\n if (/qa|test|quality|forensic/.test(text)) return \"qa\"\n return \"general\"\n}\n\n/** Generate CLAUDE.md for an agent workspace */\nexport function generateClaudeMd(agentId: string, def: AgentDef, daemonPort: string): string {\n const role = detectRole(agentId, def)\n const lines: string[] = []\n\n // Managed-marker on line 1 — used by setupWorkspace to detect stale\n // auto-generated files and regenerate them when systemPrompt changes.\n // User-edited files (no marker) are left alone forever.\n lines.push(buildManagedMarker(def.systemPrompt))\n lines.push(`# ${def.name}`)\n lines.push(\"\")\n lines.push(\"@AGENTS.md\")\n lines.push(\"\")\n\n // Agent identity from systemPrompt — emitted verbatim. Truncating here would\n // silently drop instructions (e.g. multi-paragraph protocols), so we trade\n // a few extra tokens in CLAUDE.md for fidelity. The runtime path already\n // forwards the full systemPrompt via --append-system-prompt; this section\n // mirrors it for tools that read CLAUDE.md as workspace context.\n if (def.systemPrompt && def.systemPrompt.trim().length > 0) {\n lines.push(\"## Role\")\n lines.push(\"\")\n lines.push(def.systemPrompt.trimEnd())\n lines.push(\"\")\n }\n\n // Build/test commands\n lines.push(\"## Commands\")\n lines.push(\"\")\n if (existsSync(resolve(def.workspace, \"package.json\"))) {\n lines.push(\"```bash\")\n lines.push(\"npm install # install dependencies\")\n lines.push(\"npm test # run tests\")\n lines.push(\"npm run build # build project\")\n lines.push(\"```\")\n } else if (existsSync(resolve(def.workspace, \"requirements.txt\"))) {\n lines.push(\"```bash\")\n lines.push(\"pip install -r requirements.txt\")\n lines.push(\"pytest\")\n lines.push(\"```\")\n }\n lines.push(\"\")\n\n // Role-specific conventions\n lines.push(\"## Conventions\")\n lines.push(\"\")\n switch (role) {\n case \"coding\":\n lines.push(\"- Write tests for new code before committing\")\n lines.push(\"- Keep functions small and focused\")\n lines.push(\"- Follow existing patterns in the codebase\")\n lines.push(\"- Run tests before pushing\")\n break\n case \"pm\":\n lines.push(\"- Keep responses concise (3-5 lines main message)\")\n lines.push(\"- Reference issues with #IID and MRs with !IID\")\n lines.push(\"- Update issue labels and milestones when relevant\")\n lines.push(\"- Summarize decisions, don't narrate process\")\n break\n case \"devops\":\n lines.push(\"- Always check service status before making changes\")\n lines.push(\"- Never run destructive commands without confirmation\")\n lines.push(\"- Log all deployment actions\")\n lines.push(\"- Verify changes in staging before production\")\n break\n case \"qa\":\n lines.push(\"- Document reproduction steps for every bug\")\n lines.push(\"- Include expected vs actual behavior\")\n lines.push(\"- Check both happy path and edge cases\")\n lines.push(\"- Attach screenshots when reporting UI issues\")\n break\n default:\n lines.push(\"- Be concise — lead with the answer, skip preamble\")\n lines.push(\"- Follow existing patterns in the codebase\")\n }\n lines.push(\"\")\n\n // Cross-channel messaging capability\n lines.push(\"## Cross-Channel Messaging\")\n lines.push(\"\")\n lines.push(\"You can send messages to any channel proactively:\")\n lines.push(\"```bash\")\n lines.push(`curl -X POST http://localhost:${daemonPort}/send \\\\`)\n lines.push(` -H \"Content-Type: application/json\" \\\\`)\n lines.push(` -d '{\"channel\":\"telegram\",\"chatId\":\"<id>\",\"text\":\"<message>\",\"agentId\":\"${agentId}\"}'`)\n lines.push(\"```\")\n lines.push('Channels: telegram, whatsapp, gitlab, discord')\n lines.push(\"\")\n\n return lines.join(\"\\n\")\n}\n\n/** Generate .claude/settings.json with hooks and permissions */\nfunction generateSettings(agentId: string, def: AgentDef): Record<string, unknown> {\n const settings: Record<string, unknown> = {\n autoMemoryEnabled: true,\n env: {\n CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: \"1\",\n },\n }\n\n // Permission mode\n if (def.permissionMode === \"bypassPermissions\") {\n // No deny rules — full access\n } else if (def.permissionMode === \"plan\") {\n settings[\"permissions\"] = {\n deny: [\"Bash(rm -rf *)\", \"Bash(drop *)\", \"Bash(DELETE *)\"],\n }\n } else {\n // Default: deny destructive operations\n settings[\"permissions\"] = {\n deny: [\n \"Bash(rm -rf /)\",\n \"Bash(> /dev/sda*)\",\n \"Bash(mkfs*)\",\n ],\n }\n }\n\n // Hooks\n const hooks: Record<string, unknown[]> = {}\n\n // Notification hook — log when agent needs input\n hooks[\"Notification\"] = [{\n matcher: \"\",\n hooks: [{\n type: \"command\",\n command: `echo \"[${agentId}] notification: $(jq -r '.type // \\\"unknown\\\"')\" >> /tmp/agentx-${agentId}.log`,\n }],\n }]\n\n // PostToolUse — log tool activity\n hooks[\"PostToolUse\"] = [{\n matcher: \"Bash\",\n hooks: [{\n type: \"command\",\n command: `jq -r '.tool_input.command // empty' >> /tmp/agentx-${agentId}-commands.log`,\n }],\n }]\n\n // SessionStart after compaction — re-inject key context\n hooks[\"SessionStart\"] = [{\n matcher: \"compact\",\n hooks: [{\n type: \"command\",\n command: `echo \"Reminder: You are ${def.name} (${agentId}). Follow the conventions in CLAUDE.md.\"`,\n }],\n }]\n\n settings[\"hooks\"] = hooks\n return settings\n}\n\n/** Generate .claude/rules/ files based on agent role */\nfunction generateRules(agentId: string, def: AgentDef): Array<{ name: string; content: string }> {\n const role = detectRole(agentId, def)\n const rules: Array<{ name: string; content: string }> = []\n\n switch (role) {\n case \"coding\":\n rules.push({\n name: \"testing.md\",\n content: `---\npaths:\n - \"**/*.test.{ts,tsx,js,jsx}\"\n - \"**/*.spec.{ts,tsx,js,jsx}\"\n - \"test/**/*\"\n---\n\n# Testing Rules\n\n- Every new function should have a corresponding test\n- Test both happy path and error cases\n- Use descriptive test names that explain what's being tested\n- Mock external dependencies, not internal modules\n`,\n })\n rules.push({\n name: \"code-quality.md\",\n content: `# Code Quality\n\n- No console.log in production code (use a logger)\n- Handle errors explicitly — no empty catch blocks\n- Keep files under 300 lines\n- Extract repeated logic into shared utilities\n`,\n })\n break\n\n case \"devops\":\n rules.push({\n name: \"deployment-safety.md\",\n content: `# Deployment Safety\n\n- Always verify staging before production\n- Check disk space and memory before deploying\n- Never expose secrets in logs or commits\n- Use rollback-safe deployment strategies\n`,\n })\n break\n\n case \"pm\":\n rules.push({\n name: \"communication.md\",\n content: `# Communication Rules\n\n- Keep GitLab comments under 5 lines for the main message\n- Use <details> for verbose output (logs, commands, steps)\n- Reference issues with #IID, merge requests with !IID\n- Never mention Telegram handles on GitLab\n`,\n })\n break\n\n case \"qa\":\n rules.push({\n name: \"bug-reports.md\",\n content: `# Bug Report Format\n\nAlways include:\n1. Steps to reproduce\n2. Expected behavior\n3. Actual behavior\n4. Environment (browser, OS, version)\n5. Screenshots if applicable\n`,\n })\n break\n }\n\n return rules\n}\n\n/**\n * Set up an agent workspace with Claude Code best practices.\n * Only creates files that don't already exist (non-destructive).\n */\nexport function setupWorkspace(\n agentId: string,\n def: AgentDef,\n daemonPort: string = \"19900\",\n log: (...args: unknown[]) => void = console.error,\n): { created: string[]; skipped: string[] } {\n const created: string[] = []\n const skipped: string[] = []\n const workspace = def.workspace\n\n if (!existsSync(workspace)) {\n log(`Workspace not found: ${workspace}`)\n return { created, skipped }\n }\n\n // Only set up claude-code tier agents\n if (def.tier !== \"claude-code\") {\n return { created, skipped }\n }\n\n const writeIfMissing = (path: string, content: string) => {\n if (existsSync(path)) {\n skipped.push(path)\n } else {\n mkdirSync(resolve(path, \"..\"), { recursive: true })\n writeFileSync(path, content)\n created.push(path)\n }\n }\n\n /**\n * Write a managed file with the agentx-managed marker. If the file already\n * exists and starts with our marker, regenerate when the embedded hash\n * doesn't match the current expected hash. Files without the marker are\n * treated as user-owned and left alone.\n */\n const writeManaged = (path: string, content: string, expectedHash: string) => {\n if (!existsSync(path)) {\n mkdirSync(resolve(path, \"..\"), { recursive: true })\n writeFileSync(path, content)\n created.push(path)\n return\n }\n let existing: string\n try {\n existing = readFileSync(path, \"utf8\")\n } catch {\n skipped.push(path)\n return\n }\n const existingHash = readManagedHash(existing)\n if (existingHash === null) {\n // User-edited (no marker) — never overwrite.\n skipped.push(path)\n return\n }\n if (existingHash === expectedHash) {\n skipped.push(path)\n return\n }\n writeFileSync(path, content)\n created.push(`${path} (refreshed)`)\n }\n\n // AGENTS.md (agents.md spec) — first-line of systemPrompt only, currently\n // not managed-marker'd. Left as writeIfMissing for now.\n writeIfMissing(\n resolve(workspace, \"AGENTS.md\"),\n generateAgentsMd({ name: def.name, id: agentId, role: def.systemPrompt?.split(\"\\n\")[0], workspace, tier: def.tier }),\n )\n\n // CLAUDE.md — managed: silently refreshed when systemPrompt changes.\n writeManaged(\n resolve(workspace, \"CLAUDE.md\"),\n generateClaudeMd(agentId, def, daemonPort),\n systemPromptHash(def.systemPrompt),\n )\n\n // .claude/settings.json\n const settingsPath = resolve(workspace, \".claude/settings.json\")\n writeIfMissing(settingsPath, JSON.stringify(generateSettings(agentId, def), null, 2))\n\n // .claude/rules/\n const rules = generateRules(agentId, def)\n for (const rule of rules) {\n writeIfMissing(resolve(workspace, \".claude/rules\", rule.name), rule.content)\n }\n\n if (created.length > 0) {\n log(`[${agentId}] workspace setup: created ${created.length} file(s)`)\n }\n\n return { created, skipped }\n}\n\n/**\n * Patch existing .claude/settings.json to add missing keys (non-destructive).\n * Used to enable new features (like agent teams) on workspaces that already have settings.\n */\nfunction patchSettings(workspace: string, patches: Record<string, unknown>): boolean {\n const settingsPath = resolve(workspace, \".claude/settings.json\")\n if (!existsSync(settingsPath)) return false\n\n try {\n const existing = JSON.parse(readFileSync(settingsPath, \"utf-8\"))\n let changed = false\n\n for (const [key, value] of Object.entries(patches)) {\n if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n // Merge objects (e.g. env: { KEY: \"value\" })\n if (!existing[key]) existing[key] = {}\n for (const [subKey, subValue] of Object.entries(value as Record<string, unknown>)) {\n if (existing[key][subKey] === undefined) {\n existing[key][subKey] = subValue\n changed = true\n }\n }\n } else if (existing[key] === undefined) {\n existing[key] = value\n changed = true\n }\n }\n\n if (changed) {\n writeFileSync(settingsPath, JSON.stringify(existing, null, 2))\n }\n return changed\n } catch {\n return false\n }\n}\n\n/**\n * Set up all agent workspaces on daemon start.\n */\nexport function setupAllWorkspaces(\n agents: Record<string, AgentDef>,\n daemonPort: string = \"19900\",\n log: (...args: unknown[]) => void = console.error,\n): void {\n let totalCreated = 0\n let totalPatched = 0\n for (const [id, def] of Object.entries(agents)) {\n if (def.tier !== \"claude-code\") continue\n const result = setupWorkspace(id, def, daemonPort, log)\n totalCreated += result.created.length\n\n // Patch existing settings with new features (agent teams, etc.)\n if (patchSettings(def.workspace, {\n env: { CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: \"1\" },\n })) {\n totalPatched++\n }\n }\n if (totalCreated > 0) {\n log(`Workspace setup: ${totalCreated} file(s) created across agent workspaces`)\n }\n if (totalPatched > 0) {\n log(`Workspace patch: ${totalPatched} workspace(s) updated with agent teams support`)\n }\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","writeFileSync","readFileSync","resolve","createHash","MANAGED_MARKER_PREFIX","MANAGED_MARKER_SUFFIX","systemPromptHash","systemPrompt","createHash","buildManagedMarker","readManagedHash","content","firstLine","detectRole","agentId","def","text","generateClaudeMd","daemonPort","role","lines","existsSync","resolve","generateSettings","settings","hooks","generateRules","rules","setupWorkspace","log","created","skipped","workspace","writeIfMissing","path","mkdirSync","writeFileSync","writeManaged","expectedHash","existing","readFileSync","existingHash","generateAgentsMd","settingsPath","rule","patchSettings","patches","changed","key","value","subKey","subValue","setupAllWorkspaces","agents","totalCreated","totalPatched","id","result"]}
|
package/dist/chunk-CDNWCYF7.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
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
|
|
@@ -1 +0,0 @@
|
|
|
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"]}
|