agentix-cli 0.17.0 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-RDVGL2TW.js +2 -0
- package/dist/audit-Z47AQFFD.js +3 -0
- package/dist/audit-Z47AQFFD.js.map +1 -0
- package/dist/{board-dashboard-CXIVJKDP.js → board-dashboard-KX2CNOSY.js} +44 -44
- package/dist/board-dashboard-KX2CNOSY.js.map +1 -0
- package/dist/{chunk-EKTPCSN4.js → chunk-4ITQP3XL.js} +2 -2
- package/dist/chunk-53NRX2NA.js +2 -0
- package/dist/chunk-53NRX2NA.js.map +1 -0
- package/dist/{chunk-5WNGHDFC.js → chunk-ASSJ4Z6Z.js} +11 -11
- package/dist/{chunk-5WNGHDFC.js.map → chunk-ASSJ4Z6Z.js.map} +1 -1
- package/dist/chunk-BLUXL6HR.js +854 -0
- package/dist/chunk-BLUXL6HR.js.map +1 -0
- package/dist/chunk-BTAEYL3P.js +72 -0
- package/dist/chunk-BTAEYL3P.js.map +1 -0
- package/dist/chunk-DJEZ2HVU.js +5 -0
- package/dist/chunk-DJEZ2HVU.js.map +1 -0
- package/dist/{chunk-SRHLXZRF.js → chunk-DSCMCVTV.js} +3 -3
- package/dist/chunk-DSCMCVTV.js.map +1 -0
- package/dist/chunk-HPDHNIA3.js +2 -0
- package/dist/chunk-IM376SD6.js +13 -0
- package/dist/chunk-IM376SD6.js.map +1 -0
- package/dist/chunk-J4WKMKTP.js +5 -0
- package/dist/{chunk-MBEQASP3.js.map → chunk-J4WKMKTP.js.map} +1 -1
- package/dist/chunk-JGXE2YNV.js +46 -0
- package/dist/chunk-JGXE2YNV.js.map +1 -0
- package/dist/{chunk-TT2Q7YMZ.js → chunk-JXEPJ3YD.js} +6 -6
- package/dist/{chunk-TT2Q7YMZ.js.map → chunk-JXEPJ3YD.js.map} +1 -1
- package/dist/chunk-LY7JGDV7.js +6 -0
- package/dist/chunk-LY7JGDV7.js.map +1 -0
- package/dist/chunk-SR3X25TN.js +3 -0
- package/dist/chunk-SR3X25TN.js.map +1 -0
- package/dist/{chunk-63UYRJIC.js → chunk-TRQSP67X.js} +2 -2
- package/dist/chunk-U364GNQX.js +719 -0
- package/dist/chunk-U364GNQX.js.map +1 -0
- package/dist/chunk-VJUTQAPJ.js +2 -0
- package/dist/chunk-VJUTQAPJ.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/{compaction-X46EQEPS.js → compaction-S5H6DTOR.js} +4 -4
- package/dist/config-UOHEG4BW.js +2 -0
- package/dist/config-mutator-KSEBWKJH.js +2 -0
- package/dist/{context-planner-G2J7FNNU.js → context-planner-2RGHJHSO.js} +3 -3
- package/dist/debug-TGAP7E7W.js +2 -0
- package/dist/discover-OATU7SHI.js +4 -0
- package/dist/discover-OATU7SHI.js.map +1 -0
- package/dist/{editor-chat-R54E65W5.js → editor-chat-FUBQLMAQ.js} +2 -2
- package/dist/heal-YP7QI7D3.js +2 -0
- package/dist/{inbox-L5ZZ623Y.js → inbox-O5UB3YQN.js} +2 -2
- package/dist/index.d.ts +462 -22
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/{ingest-whatsapp-NS4OWDOU.js → ingest-whatsapp-C3O5UPBX.js} +2 -2
- package/dist/loader-BGWA7IRY.js +2 -0
- package/dist/loader-LBHKSXBK.js +2 -0
- package/dist/memory-extract-KKPKAOPH.js +2 -0
- package/dist/{processes-XF5CV4LK.js → processes-7W27G3J3.js} +2 -2
- package/dist/program-2MDELQLX.js +2 -0
- package/dist/providers-WCQSKTNP.js +2 -0
- package/dist/query-FZAL7AQD.js +2 -0
- package/dist/recipes-LZ56TA3X.js +2 -0
- package/dist/registry-ONJSILH4.js +2 -0
- package/dist/scheduler-XRM2RJIV.js +2 -0
- package/dist/{slack-PSQLVTGB.js → slack-D27BE37Q.js} +2 -2
- package/dist/store-4UCO56K2.js +2 -0
- package/dist/{subagent-2SJHSSJS.js → subagent-EPOT3QDM.js} +2 -2
- package/dist/{task-store-NM7VKNLE.js → task-store-3NUWLYRY.js} +2 -2
- package/dist/{telegram-W4SLFMNY.js → telegram-UWV67KWG.js} +2 -2
- package/dist/timers-QB4KYNAE.js +2 -0
- package/dist/types-ECMEC5BZ.js +2 -0
- package/dist/types-TTB6DJUE.js +2 -0
- package/dist/{usage-dashboard-AXPXB2KP.js → usage-dashboard-FQGPPABD.js} +2 -2
- package/dist/web/workflow-editor.global.js +35 -12
- package/dist/web/workflow-editor.global.js.map +1 -1
- package/dist/{whatsapp-DCHRBRVV.js → whatsapp-KJ2NRKKQ.js} +2 -2
- package/dist/{whatsapp-state-6D6FHHD2.js → whatsapp-state-3ZVQ6MQD.js} +2 -2
- package/dist/{wiki-UDPYFJBL.js → wiki-OUYKM7MB.js} +2 -2
- package/dist/wiki-OUYKM7MB.js.map +1 -0
- package/dist/workspace-setup-6GDBEORT.js +2 -0
- package/dist/workspace-setup-6GDBEORT.js.map +1 -0
- package/package.json +11 -1
- package/dist/agent-4AOMVXRJ.js +0 -2
- package/dist/board-dashboard-CXIVJKDP.js.map +0 -1
- package/dist/chunk-4HP6UC2X.js +0 -13
- package/dist/chunk-4HP6UC2X.js.map +0 -1
- package/dist/chunk-ABCHHNG6.js +0 -625
- package/dist/chunk-ABCHHNG6.js.map +0 -1
- package/dist/chunk-FIOB3MEX.js +0 -2
- package/dist/chunk-HCFSHGK4.js +0 -46
- package/dist/chunk-HCFSHGK4.js.map +0 -1
- package/dist/chunk-HHJGKD5B.js +0 -71
- package/dist/chunk-HHJGKD5B.js.map +0 -1
- package/dist/chunk-MBEQASP3.js +0 -5
- package/dist/chunk-MCJUAE6V.js +0 -6
- package/dist/chunk-MCJUAE6V.js.map +0 -1
- package/dist/chunk-SRHLXZRF.js.map +0 -1
- package/dist/chunk-XDJSYQST.js +0 -6
- package/dist/chunk-XDJSYQST.js.map +0 -1
- package/dist/chunk-YPOELN6Z.js +0 -798
- package/dist/chunk-YPOELN6Z.js.map +0 -1
- package/dist/config-KCYHMKCT.js +0 -2
- package/dist/config-mutator-QP4GLBOE.js +0 -2
- package/dist/debug-3MEUO2P2.js +0 -2
- package/dist/heal-JDXUPRET.js +0 -2
- package/dist/loader-PTCXLJEX.js +0 -2
- package/dist/memory-extract-WH4FZ456.js +0 -2
- package/dist/program-FFWEILAS.js +0 -2
- package/dist/providers-AKQ4HTB4.js +0 -2
- package/dist/query-PKGYE5HR.js +0 -2
- package/dist/registry-IFMDJWQ7.js +0 -2
- package/dist/scheduler-63D5KFO7.js +0 -2
- package/dist/store-5MJOO74U.js +0 -2
- package/dist/timers-PCB4ZIBD.js +0 -2
- package/dist/types-C7IFH42Z.js +0 -2
- package/dist/types-OIFU3KSQ.js +0 -2
- package/dist/workspace-setup-C4VB3QZV.js +0 -2
- /package/dist/{agent-4AOMVXRJ.js.map → agent-RDVGL2TW.js.map} +0 -0
- /package/dist/{chunk-EKTPCSN4.js.map → chunk-4ITQP3XL.js.map} +0 -0
- /package/dist/{chunk-FIOB3MEX.js.map → chunk-HPDHNIA3.js.map} +0 -0
- /package/dist/{chunk-63UYRJIC.js.map → chunk-TRQSP67X.js.map} +0 -0
- /package/dist/{compaction-X46EQEPS.js.map → compaction-S5H6DTOR.js.map} +0 -0
- /package/dist/{config-KCYHMKCT.js.map → config-UOHEG4BW.js.map} +0 -0
- /package/dist/{config-mutator-QP4GLBOE.js.map → config-mutator-KSEBWKJH.js.map} +0 -0
- /package/dist/{context-planner-G2J7FNNU.js.map → context-planner-2RGHJHSO.js.map} +0 -0
- /package/dist/{debug-3MEUO2P2.js.map → debug-TGAP7E7W.js.map} +0 -0
- /package/dist/{editor-chat-R54E65W5.js.map → editor-chat-FUBQLMAQ.js.map} +0 -0
- /package/dist/{heal-JDXUPRET.js.map → heal-YP7QI7D3.js.map} +0 -0
- /package/dist/{inbox-L5ZZ623Y.js.map → inbox-O5UB3YQN.js.map} +0 -0
- /package/dist/{ingest-whatsapp-NS4OWDOU.js.map → ingest-whatsapp-C3O5UPBX.js.map} +0 -0
- /package/dist/{loader-PTCXLJEX.js.map → loader-BGWA7IRY.js.map} +0 -0
- /package/dist/{memory-extract-WH4FZ456.js.map → loader-LBHKSXBK.js.map} +0 -0
- /package/dist/{program-FFWEILAS.js.map → memory-extract-KKPKAOPH.js.map} +0 -0
- /package/dist/{processes-XF5CV4LK.js.map → processes-7W27G3J3.js.map} +0 -0
- /package/dist/{providers-AKQ4HTB4.js.map → program-2MDELQLX.js.map} +0 -0
- /package/dist/{query-PKGYE5HR.js.map → providers-WCQSKTNP.js.map} +0 -0
- /package/dist/{registry-IFMDJWQ7.js.map → query-FZAL7AQD.js.map} +0 -0
- /package/dist/{scheduler-63D5KFO7.js.map → recipes-LZ56TA3X.js.map} +0 -0
- /package/dist/{store-5MJOO74U.js.map → registry-ONJSILH4.js.map} +0 -0
- /package/dist/{task-store-NM7VKNLE.js.map → scheduler-XRM2RJIV.js.map} +0 -0
- /package/dist/{slack-PSQLVTGB.js.map → slack-D27BE37Q.js.map} +0 -0
- /package/dist/{timers-PCB4ZIBD.js.map → store-4UCO56K2.js.map} +0 -0
- /package/dist/{subagent-2SJHSSJS.js.map → subagent-EPOT3QDM.js.map} +0 -0
- /package/dist/{types-C7IFH42Z.js.map → task-store-3NUWLYRY.js.map} +0 -0
- /package/dist/{telegram-W4SLFMNY.js.map → telegram-UWV67KWG.js.map} +0 -0
- /package/dist/{types-OIFU3KSQ.js.map → timers-QB4KYNAE.js.map} +0 -0
- /package/dist/{wiki-UDPYFJBL.js.map → types-ECMEC5BZ.js.map} +0 -0
- /package/dist/{workspace-setup-C4VB3QZV.js.map → types-TTB6DJUE.js.map} +0 -0
- /package/dist/{usage-dashboard-AXPXB2KP.js.map → usage-dashboard-FQGPPABD.js.map} +0 -0
- /package/dist/{whatsapp-DCHRBRVV.js.map → whatsapp-KJ2NRKKQ.js.map} +0 -0
- /package/dist/{whatsapp-state-6D6FHHD2.js.map → whatsapp-state-3ZVQ6MQD.js.map} +0 -0
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import{existsSync as k,promises as w}from"fs";import u from"path";import x from"fast-glob";import L from"js-yaml";import{z as r}from"zod";var h=r.object({name:r.string(),description:r.string(),version:r.string().optional(),author:r.string().optional(),tags:r.array(r.string()).optional(),globs:r.array(r.string()).optional(),triggers:r.array(r.object({pattern:r.string(),description:r.string().optional()})).optional(),autoInject:r.boolean().optional(),references:r.array(r.string()).optional(),delegatesTo:r.array(r.string()).optional(),category:r.string().optional()});var j=[".skills",".claude/skills","skills"],d="SKILL.md";async function P(c){let e=[];for(let t of j){let l=u.resolve(c,t);if(!k(l))continue;let f=await x.glob(`**/${d}`,{cwd:l,deep:3,caseSensitiveMatch:!1});for(let s of f){let n=u.resolve(l,s),i=await S(n);i&&(i.source="local",i.path=n,e.push(i))}}let o=u.resolve(c,d);if(k(o)){let t=await S(o);t&&(t.source="local",t.path=o,e.push(t))}return e}async function S(c){try{let e=await w.readFile(c,"utf8");return F(e)}catch{return null}}function F(c){try{let e=c.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);if(!e)return{frontmatter:{name:"unnamed",description:"No description"},instructions:c.trim(),source:"local"};let[,o,t]=e,l=M(o);return{frontmatter:h.parse(l),instructions:t.trim(),source:"local"}}catch{return null}}function M(c){let e=L.load(c);if(e===null||typeof e!="object"||Array.isArray(e))return{};let o=e,t=o.metadata;return t&&typeof t=="object"&&(!o.tags&&t.tags&&(typeof t.tags=="string"?o.tags=t.tags.split(",").map(l=>l.trim()).filter(Boolean):Array.isArray(t.tags)&&(o.tags=t.tags)),!o.version&&t.version&&(o.version=t.version)),o}function b(c,e,o){let t=[],l=e.toLowerCase(),f=new Set(l.split(/\s+/));for(let s of c){let n=0,i="";if(s.frontmatter.triggers)for(let a of s.frontmatter.triggers)try{new RegExp(a.pattern,"i").test(e)&&(n=Math.max(n,.9),i=`Trigger match: ${a.description||a.pattern}`)}catch{l.includes(a.pattern.toLowerCase())&&(n=Math.max(n,.7),i=`Keyword match: ${a.pattern}`)}if(s.frontmatter.tags){let a=s.frontmatter.tags.filter(m=>f.has(m.toLowerCase())||l.includes(m.toLowerCase()));if(a.length){let m=Math.min(a.length*.3,.8);m>n&&(n=m,i=`Tag match: ${a.join(", ")}`)}}let g=s.frontmatter.name.toLowerCase().split(/[-_\s]+/),y=s.frontmatter.description.toLowerCase().split(/\s+/),v=new Set([...g,...y]),p=[...f].filter(a=>v.has(a)&&a.length>3);if(p.length>0){let a=Math.min(p.length*.2,.6);a>n&&(n=a,i=`Content match: ${p.join(", ")}`)}n>.1&&t.push({skill:s,relevance:n,matchReason:i})}return t.sort((s,n)=>n.relevance-s.relevance)}function W(c,e,o=2e3){let t=c.filter(i=>i.frontmatter.autoInject);if(t.length===0)return"";let l=b(t,e);if(l.length===0)return"";let f=o*4,s=["[Auto-Injected Skills \u2014 matched to current task]"],n=s[0].length;for(let i of l){let g=`
|
|
2
|
+
## ${i.skill.frontmatter.name} (${Math.round(i.relevance*100)}% match)
|
|
3
|
+
${i.skill.instructions}`;if(n+g.length>f)break;s.push(g),n+=g.length}return s.length===1?"":s.join(`
|
|
4
|
+
`)}export{P as a,S as b,F as c,b as d,W as e};
|
|
5
|
+
//# sourceMappingURL=chunk-DJEZ2HVU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/agent/skills/loader.ts","../src/agent/skills/types.ts"],"sourcesContent":["import { existsSync, promises as fs } from \"fs\"\nimport path from \"path\"\nimport fg from \"fast-glob\"\nimport yaml from \"js-yaml\"\nimport { logger } from \"@/utils/logger\"\nimport type { Skill, SkillFrontmatter, SkillMatch } from \"./types\"\nimport { skillFrontmatterSchema } from \"./types\"\n\n// --- Load skills from local files and remote packages ---\n\nconst SKILL_DIRS = [\".skills\", \".claude/skills\", \"skills\"]\nconst SKILL_FILE = \"SKILL.md\"\n\nexport async function loadLocalSkills(cwd: string): Promise<Skill[]> {\n const skills: Skill[] = []\n\n for (const dir of SKILL_DIRS) {\n const skillDir = path.resolve(cwd, dir)\n if (!existsSync(skillDir)) continue\n\n const skillFiles = await fg.glob(`**/${SKILL_FILE}`, {\n cwd: skillDir,\n deep: 3,\n caseSensitiveMatch: false,\n })\n\n for (const file of skillFiles) {\n const fullPath = path.resolve(skillDir, file)\n const skill = await parseSkillFile(fullPath)\n if (skill) {\n skill.source = \"local\"\n skill.path = fullPath\n skills.push(skill)\n }\n }\n }\n\n // Also check for a single SKILL.md at project root\n const rootSkill = path.resolve(cwd, SKILL_FILE)\n if (existsSync(rootSkill)) {\n const skill = await parseSkillFile(rootSkill)\n if (skill) {\n skill.source = \"local\"\n skill.path = rootSkill\n skills.push(skill)\n }\n }\n\n return skills\n}\n\nexport async function parseSkillFile(filePath: string): Promise<Skill | null> {\n try {\n const content = await fs.readFile(filePath, \"utf8\")\n return parseSkillContent(content)\n } catch {\n return null\n }\n}\n\nexport function parseSkillContent(content: string): Skill | null {\n try {\n // Parse YAML frontmatter\n const frontmatterMatch = content.match(/^---\\n([\\s\\S]*?)\\n---\\n([\\s\\S]*)$/)\n\n if (!frontmatterMatch) {\n // No frontmatter - treat entire content as instructions with minimal metadata\n return {\n frontmatter: { name: \"unnamed\", description: \"No description\" },\n instructions: content.trim(),\n source: \"local\",\n }\n }\n\n const [, frontmatterRaw, instructions] = frontmatterMatch\n const frontmatter = parseYamlFrontmatter(frontmatterRaw)\n\n const validated = skillFrontmatterSchema.parse(frontmatter)\n\n return {\n frontmatter: validated,\n instructions: instructions.trim(),\n source: \"local\",\n }\n } catch {\n return null\n }\n}\n\nfunction parseYamlFrontmatter(raw: string): Record<string, unknown> {\n // Real YAML — handles arrays (block + flow), nested objects (e.g. `metadata:`\n // sub-block used by some skills), quoted strings, and comma-separated tag\n // strings. The previous primitive parser silently dropped nested keys, which\n // caused valid skills (ksi-pm, hotmail, ksi-v1-coder) to fail Zod validation\n // and load as null.\n const parsed = yaml.load(raw)\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n return {}\n }\n const result = parsed as Record<string, unknown>\n // Some skills nest tags under `metadata.tags` as a comma-separated string.\n // Lift it to the top-level shape Zod expects (array of strings) so the\n // schema accepts the skill.\n const meta = result.metadata as Record<string, unknown> | undefined\n if (meta && typeof meta === \"object\") {\n if (!result.tags && meta.tags) {\n if (typeof meta.tags === \"string\") {\n result.tags = meta.tags.split(\",\").map(t => t.trim()).filter(Boolean)\n } else if (Array.isArray(meta.tags)) {\n result.tags = meta.tags\n }\n }\n if (!result.version && meta.version) result.version = meta.version\n }\n return result\n}\n\nexport function matchSkillsToTask(\n skills: Skill[],\n taskDescription: string,\n outputType?: string\n): SkillMatch[] {\n const matches: SkillMatch[] = []\n const taskLower = taskDescription.toLowerCase()\n const taskWords = new Set(taskLower.split(/\\s+/))\n\n for (const skill of skills) {\n let relevance = 0\n let matchReason = \"\"\n\n // Check trigger patterns\n if (skill.frontmatter.triggers) {\n for (const trigger of skill.frontmatter.triggers) {\n try {\n const regex = new RegExp(trigger.pattern, \"i\")\n if (regex.test(taskDescription)) {\n relevance = Math.max(relevance, 0.9)\n matchReason = `Trigger match: ${trigger.description || trigger.pattern}`\n }\n } catch {\n // Invalid regex - try simple string match\n if (taskLower.includes(trigger.pattern.toLowerCase())) {\n relevance = Math.max(relevance, 0.7)\n matchReason = `Keyword match: ${trigger.pattern}`\n }\n }\n }\n }\n\n // Check tags overlap\n if (skill.frontmatter.tags) {\n const tagOverlap = skill.frontmatter.tags.filter(\n (tag) => taskWords.has(tag.toLowerCase()) || taskLower.includes(tag.toLowerCase())\n )\n if (tagOverlap.length) {\n const tagRelevance = Math.min(tagOverlap.length * 0.3, 0.8)\n if (tagRelevance > relevance) {\n relevance = tagRelevance\n matchReason = `Tag match: ${tagOverlap.join(\", \")}`\n }\n }\n }\n\n // Check name/description overlap\n const nameWords = skill.frontmatter.name.toLowerCase().split(/[-_\\s]+/)\n const descWords = skill.frontmatter.description.toLowerCase().split(/\\s+/)\n const allSkillWords = new Set([...nameWords, ...descWords])\n\n const overlap = [...taskWords].filter((w) => allSkillWords.has(w) && w.length > 3)\n if (overlap.length > 0) {\n const wordRelevance = Math.min(overlap.length * 0.2, 0.6)\n if (wordRelevance > relevance) {\n relevance = wordRelevance\n matchReason = `Content match: ${overlap.join(\", \")}`\n }\n }\n\n if (relevance > 0.1) {\n matches.push({ skill, relevance, matchReason })\n }\n }\n\n return matches.sort((a, b) => b.relevance - a.relevance)\n}\n\n/**\n * Find auto-injectable skills that match the current message.\n * Only skills with `autoInject: true` in frontmatter participate.\n * Returns skill content to inject into context, capped at maxTokens.\n */\nexport function getAutoInjectSkills(\n skills: Skill[],\n message: string,\n maxTokens: number = 2000,\n): string {\n const autoSkills = skills.filter(s => s.frontmatter.autoInject)\n if (autoSkills.length === 0) return \"\"\n\n const matches = matchSkillsToTask(autoSkills, message)\n if (matches.length === 0) return \"\"\n\n // Inject matched skills within token budget\n const maxChars = maxTokens * 4\n const sections: string[] = [\"[Auto-Injected Skills — matched to current task]\"]\n let totalChars = sections[0].length\n\n for (const match of matches) {\n const content = `\\n## ${match.skill.frontmatter.name} (${Math.round(match.relevance * 100)}% match)\\n${match.skill.instructions}`\n if (totalChars + content.length > maxChars) break\n sections.push(content)\n totalChars += content.length\n }\n\n if (sections.length === 1) return \"\" // no skills fit in budget\n return sections.join(\"\\n\")\n}\n","import { z } from \"zod\"\n\n// --- Skill types aligned with skills.sh / Agent Skills spec ---\n\nexport const skillFrontmatterSchema = z.object({\n name: z.string(),\n description: z.string(),\n version: z.string().optional(),\n author: z.string().optional(),\n tags: z.array(z.string()).optional(),\n globs: z.array(z.string()).optional(),\n // When to automatically apply this skill\n triggers: z\n .array(\n z.object({\n pattern: z.string(), // regex or keyword pattern\n description: z.string().optional(),\n })\n )\n .optional(),\n /** If true, skill is auto-injected into context when triggers match (per-turn) */\n autoInject: z.boolean().optional(),\n /** Reference card ids cited by this skill (e.g. \"ksi.gitlab.project.ksi-v2\").\n * Audit lint flags FAILING when any id does not resolve. */\n references: z.array(z.string()).optional(),\n /** Skills this one delegates to (e.g. ksi-cx-email → [hotmail]). Audit lint\n * flags FAILING when a delegate has no SKILL.md. */\n delegatesTo: z.array(z.string()).optional(),\n /** Free-form category — used by recipes and audit rules. Common values:\n * deploy, ssh, email, gitlab, ops, content, marketing. */\n category: z.string().optional(),\n})\n\nexport type SkillFrontmatter = z.infer<typeof skillFrontmatterSchema>\n\nexport interface Skill {\n frontmatter: SkillFrontmatter\n instructions: string // Markdown body\n source: \"local\" | \"remote\" | \"generated\"\n path?: string // Local file path\n packageId?: string // e.g., \"intellectronica/agent-skills\"\n}\n\nexport interface SkillMatch {\n skill: Skill\n relevance: number // 0-1 how relevant to current task\n matchReason: string\n}\n\nexport interface SkillPackage {\n owner: string\n repo: string\n skills: Skill[]\n}\n"],"mappings":"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","names":["existsSync","fs","path","fg","yaml","z","skillFrontmatterSchema","SKILL_DIRS","SKILL_FILE","loadLocalSkills","cwd","skills","dir","skillDir","path","existsSync","skillFiles","fg","file","fullPath","skill","parseSkillFile","rootSkill","filePath","content","fs","parseSkillContent","frontmatterMatch","frontmatterRaw","instructions","frontmatter","parseYamlFrontmatter","skillFrontmatterSchema","raw","parsed","yaml","result","meta","t","matchSkillsToTask","taskDescription","outputType","matches","taskLower","taskWords","relevance","matchReason","trigger","tagOverlap","tag","tagRelevance","nameWords","descWords","allSkillWords","overlap","w","wordRelevance","a","b","getAutoInjectSkills","message","maxTokens","autoSkills","s","maxChars","sections","totalChars","match"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import{a as v,b}from"./chunk-
|
|
1
|
+
import{a as v,b}from"./chunk-J4WKMKTP.js";import{writeFileSync as R,mkdirSync as m,existsSync as f,readFileSync as M}from"fs";import{resolve as h}from"path";function $(a,e){if(!e)return a;let s=e*4;return`${a}
|
|
2
2
|
|
|
3
3
|
[Response budget]
|
|
4
4
|
Keep your response under ~${e} tokens (~${s} chars). Be concise \u2014 this is automated batch work, not a conversation.`}function d(a,e,s){let t=[];for(let r of a.split(","))if(r==="*")for(let i=e;i<=s;i++)t.push(i);else if(r.includes("/")){let[i,n]=r.split("/"),o=parseInt(n,10),c=i==="*"?e:parseInt(i,10);for(let l=c;l<=s;l+=o)t.push(l)}else if(r.includes("-")){let[i,n]=r.split("-").map(Number);for(let o=i;o<=n;o++)t.push(o)}else t.push(parseInt(r,10));return[...new Set(t)].sort((r,i)=>r-i)}function w(a,e,s){let t=a.trim().split(/\s+/);if(t.length!==5)throw new Error(`Invalid cron: ${a}`);let r=d(t[0],0,59),i=d(t[1],0,23),n=d(t[2],1,31),o=d(t[3],1,12),c=d(t[4],0,6),l=new Intl.DateTimeFormat("en-US",{timeZone:s,year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",hour12:!1}),u=new Date(e.getTime()+6e4);u.setSeconds(0,0);let D=new Date(u.getTime()+366*24*60*60*1e3);for(;u<D;){let S=l.formatToParts(u),g=T=>parseInt(S.find(F=>F.type===T)?.value||"0",10),k=g("minute"),x=g("hour"),C=g("day"),A=g("month"),J=u.getDay();if(r.includes(k)&&i.includes(x)&&n.includes(C)&&o.includes(A)&&c.includes(J))return u;u.setTime(u.getTime()+6e4)}throw new Error(`No next run found for cron "${a}" within 1 year`)}var p=5,y=[3e4,6e4,3e5,9e5,36e5],E=class{jobs=new Map;timers=new Map;registry;hooks;runsDir;lastRunFile;running=!1;notifyCallback;log;constructor(e,s,t,r=console.error.bind(console,"[cron]")){this.registry=s,this.hooks=t,this.log=r,this.runsDir=h(process.cwd(),".agentx/cron/runs"),this.lastRunFile=h(process.cwd(),".agentx/cron/last-runs.json");for(let[i,n]of Object.entries(e.crons))this.jobs.set(i,{id:i,enabled:n.enabled,schedule:n.schedule,timezone:n.timezone,agent:n.agent,prompt:n.prompt,timeout:n.timeout,model:n.model,maxOutputTokens:n.maxOutputTokens,onError:n.onError,consecutiveErrors:0,totalRuns:0,totalFailures:0})}setNotifyCallback(e){this.notifyCallback=e}async start(){this.running=!0,f(this.runsDir)||m(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:s,missedAt:t}of e)this.log(` "${s}" should have run at ${t.toISOString()}`);this.executeMissedRuns(e).catch(s=>{this.log(`Error executing missed runs: ${s.message}`)})}for(let[s,t]of this.jobs){if(!t.enabled){this.log(`Job "${s}" is disabled, skipping`);continue}this.scheduleNext(s)}this.log(`${this.jobs.size} cron job(s) loaded, ${Array.from(this.jobs.values()).filter(s=>s.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 s=this.jobs.get(e);if(!s||!s.enabled||!this.running)return;let t=this.timers.get(e);t&&clearTimeout(t);try{let r=w(s.schedule,new Date,s.timezone);s.nextRun=r,s.retryPending=!1;let i=r.getTime()-Date.now();this.log(`Job "${e}" next run: ${r.toISOString()} (in ${Math.round(i/1e3)}s)`);let n=setTimeout(()=>this.executeJob(e),i);this.timers.set(e,n)}catch(r){this.log(`Failed to schedule "${e}": ${r.message}`)}}scheduleRetry(e,s){let t=this.jobs.get(e);if(!t||!t.enabled||!this.running)return;if(s>=p){this.log(`Job "${e}" exhausted all ${p} retries, scheduling next regular run`),this.scheduleNext(e);return}let r=y[s]||y[y.length-1];t.retryPending=!0,this.log(`Job "${e}" retry ${s+1}/${p} in ${Math.round(r/1e3)}s`);let i=setTimeout(()=>this.executeJob(e,s+1),r);this.timers.set(e,i)}async executeJob(e,s=0){let t=this.jobs.get(e);if(!t||!this.running)return;let r=s>0;if(this.hooks?.has("pre:cron-run")){let n=await this.hooks.execute("pre:cron-run",{event:"pre:cron-run",jobId:e,agent:t.agent,prompt:t.prompt});if(n.blocked){this.log(`Job "${e}" blocked by hook: ${n.message}`),this.scheduleNext(e);return}}this.log(`${r?`[retry ${s}] `:""}Executing job "${e}" -> agent "${t.agent}"`);let i=new Date;t.lastRun=i,t.totalRuns++;try{let n=await this.registry.execute({message:$(t.prompt,t.maxOutputTokens),agentId:t.agent,model:t.model,context:{channel:"cron"}}),o={jobId:e,startedAt:i,completedAt:new Date,success:!n.error,response:n.content,error:n.error,duration:n.duration||Date.now()-i.getTime(),isRetry:r,retryAttempt:s};if(n.error){if(t.consecutiveErrors++,t.totalFailures++,t.lastError=n.error,this.log(`Job "${e}" failed (${t.consecutiveErrors} consecutive): ${n.error}`),await this.notifyFailure(t,n.error),t.onError.includes("disable")&&t.consecutiveErrors>=3){t.enabled=!1,this.log(`Job "${e}" disabled after ${t.consecutiveErrors} consecutive errors`);try{let c=await v(l=>b(l,`crons.${e}.enabled`,!1),{reload:!1});c.success||this.log(`Failed to persist disable for "${e}": ${c.error}`)}catch(c){this.log(`Failed to persist disable for "${e}": ${c.message}`)}await this.notifyDisabled(t)}this.logRun(o),this.scheduleRetry(e,s);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 ${o.duration}ms`);this.logRun(o),this.hooks?.has("post:cron-run")&&await this.hooks.execute("post:cron-run",{event:"post:cron-run",jobId:e,success:o.success,duration:o.duration,error:o.error?new Error(o.error):void 0})}catch(n){t.consecutiveErrors++,t.totalFailures++,t.lastError=n.message,this.log(`Job "${e}" threw: ${n.message}`),await this.notifyFailure(t,n.message),this.scheduleRetry(e,s);return}this.saveLastRuns(),this.scheduleNext(e)}detectMissedRuns(){let e=[],s=this.loadLastRuns();if(!s)return e;for(let[t,r]of this.jobs){if(!r.enabled)continue;let i=s[t];if(!i)continue;let n=new Date(i);try{let o=w(r.schedule,n,r.timezone);o.getTime()<Date.now()&&e.push({jobId:t,missedAt:o})}catch{}}return e}async executeMissedRuns(e){for(let{jobId:s,missedAt:t}of e){let r=this.jobs.get(s);if(!(!r||!r.enabled)){this.log(`Running missed job "${s}" (was due at ${t.toISOString()})`);try{let i=await this.registry.execute({message:$(`[MISSED RUN \u2014 was scheduled for ${t.toISOString()}]
|
|
5
5
|
|
|
6
|
-
${r.prompt}`,r.maxOutputTokens),agentId:r.agent,context:{channel:"cron"}}),n={jobId:s,startedAt:new Date,completedAt:new Date,success:!i.error,response:i.content,error:i.error,duration:i.duration||0,isRetry:!1};this.logRun(n),i.error?this.log(`Missed job "${s}" failed: ${i.error}`):(this.log(`Missed job "${s}" completed`),r.lastSuccess=new Date),r.lastRun=new Date,r.totalRuns++}catch(i){this.log(`Missed job "${s}" threw: ${i.message}`)}}}this.saveLastRuns()}async notifyFailure(e,s){if(!(!e.onError.includes("notify")&&e.consecutiveErrors<2)){if(!this.notifyCallback){this.log(`[ALERT] Cron "${e.id}" failed (${e.consecutiveErrors}x): ${s.slice(0,200)}`);return}try{await this.notifyCallback(e.id,e.agent,s,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 s=h(process.cwd(),".agentx/cron");f(s)||m(s,{recursive:!0}),R(this.lastRunFile,JSON.stringify(e,null,2))}catch{}}loadLastRuns(){try{
|
|
7
|
-
//# sourceMappingURL=chunk-
|
|
6
|
+
${r.prompt}`,r.maxOutputTokens),agentId:r.agent,context:{channel:"cron"}}),n={jobId:s,startedAt:new Date,completedAt:new Date,success:!i.error,response:i.content,error:i.error,duration:i.duration||0,isRetry:!1};this.logRun(n),i.error?this.log(`Missed job "${s}" failed: ${i.error}`):(this.log(`Missed job "${s}" completed`),r.lastSuccess=new Date),r.lastRun=new Date,r.totalRuns++}catch(i){this.log(`Missed job "${s}" threw: ${i.message}`)}}}this.saveLastRuns()}async notifyFailure(e,s){if(!(!e.onError.includes("notify")&&e.consecutiveErrors<2)){if(!this.notifyCallback){this.log(`[ALERT] Cron "${e.id}" failed (${e.consecutiveErrors}x): ${s.slice(0,200)}`);return}try{await this.notifyCallback(e.id,e.agent,s,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 s=h(process.cwd(),".agentx/cron");f(s)||m(s,{recursive:!0}),R(this.lastRunFile,JSON.stringify(e,null,2))}catch{}}loadLastRuns(){try{return f(this.lastRunFile)?JSON.parse(M(this.lastRunFile,"utf-8")):null}catch{return null}}logRun(e){try{let s=h(this.runsDir,e.jobId);f(s)||m(s,{recursive:!0});let t=`${e.startedAt.toISOString().replace(/[:.]/g,"-")}.json`;R(h(s,t),JSON.stringify(e,null,2))}catch{}}list(){return Array.from(this.jobs.values())}health(){let e=Array.from(this.jobs.values()),s=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:s.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{w as a,E as b};
|
|
7
|
+
//# sourceMappingURL=chunk-DSCMCVTV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/crons/scheduler.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\"\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 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\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.scheduleNext(jobId)\n return\n }\n }\n\n this.log(`${isRetry ? `[retry ${retryAttempt}] ` : \"\"}Executing job \"${jobId}\" -> agent \"${job.agent}\"`)\n const startedAt = new Date()\n job.lastRun = startedAt\n job.totalRuns++\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 context: { channel: \"cron\" },\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\" },\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"],"mappings":"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","names":["writeFileSync","mkdirSync","existsSync","readFileSync","resolve","withOutputCap","prompt","maxOutputTokens","approxChars","parseCronField","field","min","max","values","part","range","stepStr","step","start","i","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","retryAttempt","isRetry","hookResult","startedAt","response","result","res","applyConfigMutation","cfg","setAtPath","missed","lastRuns","lastRunStr","lastRun","shouldHaveRun","error","data","dir","writeFileSync","readFileSync","runDir","filename","jobs"]}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import{a as ue}from"./chunk-DSCMCVTV.js";import{c as L}from"./chunk-N67JZAXU.js";import{e as pe}from"./chunk-CDNWCYF7.js";import{b as M}from"./chunk-AFRHKA7B.js";import{a as ge,b as fe}from"./chunk-EW27SCCC.js";import{existsSync as j,mkdirSync as Ge,readdirSync as we,readFileSync as K,unlinkSync as Ve,writeFileSync as Ze}from"fs";import{resolve as N,relative as Ye}from"path";import{z as l}from"zod";var De=l.enum(["trigger.channel","trigger.manual","trigger.cron","trigger.hook","trigger.form","agent","transform","branch","gateway.parallel","rule","action.send","action.createIssue","action.setLabel","action.readLabel","action.react","action.editMessage","action.logTime","action.callHTTP","userTask","subProcess","signal.emit","signal.wait","timer.boundary","checkpoint","end"]),Fe=l.enum(["equals","contains","matches","exists"]),Ue=l.object({kind:Fe,params:l.record(l.unknown()).default({})}),me=l.object({id:l.string().min(1).regex(/^[a-zA-Z_][a-zA-Z0-9_-]*$/,"node id must be identifier-safe"),type:De,config:l.record(l.unknown()).default({}),position:l.object({x:l.number(),y:l.number()}).optional()}),he=l.object({from:l.string(),fromPort:l.string().optional(),to:l.string(),toPort:l.string().optional(),label:l.string().optional()}),S=l.object({id:l.string().regex(/^[a-z0-9][a-z0-9_-]*$/,"workflow id must be lower-kebab"),version:l.literal(2).default(2),title:l.string().min(1),description:l.string().optional(),priority:l.number().int().default(0),fanOut:l.boolean().default(!1),nodes:l.array(me).refine(s=>s.length>0,{message:"workflow must have at least one node"}),edges:l.array(he).default([]),envAllow:l.array(l.string()).default([]),retention:l.object({maxRuns:l.number().int().positive().default(500),maxDays:l.number().int().positive().default(90)}).default({maxRuns:500,maxDays:90}),maxChildDepth:l.number().int().positive().default(5),mesh:l.object({allowRemote:l.boolean().default(!1),peers:l.array(l.string()).optional()}).default({allowRemote:!1}),created:l.string().optional(),updated:l.string().optional()});function H(s){let e=[],t=new Map(s.nodes.map(o=>[o.id,o])),r=s.nodes.filter(o=>o.type.startsWith("trigger."));r.length===0&&e.push("workflow must have exactly one trigger.* node (found 0)"),r.length>1&&e.push(`workflow has ${r.length} trigger.* nodes; v1 supports exactly one`);for(let o of s.edges)t.has(o.from)||e.push(`edge references missing node: from="${o.from}"`),t.has(o.to)||e.push(`edge references missing node: to="${o.to}"`);let n=r[0];if(n){let o=new Map;for(let c of s.edges)o.has(c.from)||o.set(c.from,[]),o.get(c.from).push(c.to);let a=new Set,d=[n.id];for(;d.length;){let c=d.pop();if(!a.has(c)){a.add(c);for(let u of o.get(c)??[])d.push(u)}}for(let c of s.nodes)a.has(c.id)||e.push(`node "${c.id}" is unreachable from trigger "${n.id}"`);s.nodes.some(c=>a.has(c.id)&&Le(c.type))||e.push("no reachable `end`, `checkpoint`, `userTask`, `subProcess`, `signal.wait`, or `timer.boundary` node \u2014 the run cannot terminate or pause")}for(let o of s.nodes){if(o.type!=="branch"&&o.type!=="rule")continue;let a=o.type==="branch"?o.config.cases??[]:(o.config.rules??[]).filter(p=>typeof p.to=="string"),d=o.type==="branch"?o.config.default:o.config.default?.to,c=new Set(a.map(p=>p.to));d&&c.add(d);let u=s.edges.filter(p=>p.from===o.id);for(let p of u){let f=p.fromPort??"";c.has(f)||e.push(`${o.type} "${o.id}" has outgoing edge with port "${f}" not declared in cases or default`)}}let i=He(s);for(let o of i)e.push(`cycle without agent/checkpoint: ${o.join(" -> ")}`);return e}function Le(s){return s==="end"||s==="checkpoint"||s==="userTask"||s==="subProcess"||s==="signal.wait"||s==="timer.boundary"}function Me(s){return s==="agent"||s==="checkpoint"||s==="userTask"||s==="subProcess"||s==="signal.wait"||s==="timer.boundary"}function He(s){let e=new Map;for(let a of s.edges)e.has(a.from)||e.set(a.from,[]),e.get(a.from).push(a.to);let t=new Map(s.nodes.map(a=>[a.id,a])),r=[],n=new Map,i=[],o=a=>{if(n.get(a)===1){let d=i.indexOf(a);if(d>=0){let c=i.slice(d).concat(a);c.some(p=>{let f=t.get(p)?.type;return f?Me(f):!1})||r.push(c)}return}if(n.get(a)!==2){n.set(a,1),i.push(a);for(let d of e.get(a)??[])o(d);i.pop(),n.set(a,2)}};for(let a of s.nodes)n.get(a.id)!==2&&o(a.id);return r}var ye=l.enum(["running","paused","completed","failed","canceled"]),Ke=l.enum(["ok","failed","timeout","skipped","paused","resumed"]),qe=l.object({at:l.string(),nodeId:l.string(),inputKeys:l.array(l.string()).default([]),status:Ke,output:l.record(l.unknown()).optional(),idempotencyKey:l.string(),note:l.string().optional()}),ze=l.object({backend:l.string(),id:l.string()}),Be=l.discriminatedUnion("kind",[l.object({kind:l.literal("checkpoint"),nodeId:l.string(),checkpointName:l.string(),resumeMatch:l.record(l.unknown()).default({})}),l.object({kind:l.literal("userTask"),nodeId:l.string(),taskId:l.string(),assignee:l.string(),assignedTo:l.array(l.string()).default([])}),l.object({kind:l.literal("subProcess"),nodeId:l.string(),childRunId:l.string(),childWorkflowId:l.string()}),l.object({kind:l.literal("signalWait"),nodeId:l.string(),signalName:l.string(),match:l.record(l.unknown()).default({}),scope:l.enum(["workflow","global"]).default("workflow")}),l.object({kind:l.literal("timerWait"),nodeId:l.string(),fireAt:l.string()})]),Je=l.object({id:l.string(),workflowId:l.string(),workflowVersion:l.literal(2),homeNode:l.string(),status:ye,pausedAt:Be.optional(),context:l.record(l.record(l.unknown())).default({}),pending:l.array(l.string()).default([]),joinCounters:l.record(l.array(l.string())).default({}),entityRef:ze,history:l.array(qe).default([]),parentRunId:l.string().nullable().default(null),parentNodeId:l.string().nullable().default(null),rootRunId:l.string().nullable().default(null),depth:l.number().int().min(0).default(0),createdAt:l.string(),updatedAt:l.string()});var q=class{baseDir;constructor(e={}){this.baseDir=e.baseDir??N(process.cwd(),".agentx/workflows"),Ge(this.baseDir,{recursive:!0})}pathFor(e){return N(this.baseDir,`${e}.json`)}list(){if(!j(this.baseDir))return[];let e=[];for(let t of we(this.baseDir,{withFileTypes:!0})){if(!t.isFile()||!t.name.endsWith(".json")||t.name.startsWith("_"))continue;let r=N(this.baseDir,t.name);try{let n=JSON.parse(K(r,"utf-8")),i=S.safeParse(n);i.success&&e.push(i.data)}catch{}}return e.sort((t,r)=>t.id.localeCompare(r.id))}validateAll(){let e=[];if(!j(this.baseDir))return e;for(let t of we(this.baseDir,{withFileTypes:!0})){if(!t.isFile()||!t.name.endsWith(".json")||t.name.startsWith("_"))continue;let r=N(this.baseDir,t.name),n=Ye(this.baseDir,r),i;try{i=JSON.parse(K(r,"utf-8"))}catch(d){e.push({id:t.name.replace(/\.json$/,""),path:n,issues:[`parse error: ${d.message}`],isValid:!1});continue}let o=S.safeParse(i);if(!o.success){let d=o.error.issues.map(c=>`${c.path.join(".")||"<root>"}: ${c.message}`);e.push({id:String(i?.id??t.name.replace(/\.json$/,"")),path:n,issues:d,isValid:!1});continue}let a=H(o.data);e.push({workflow:o.data,issues:a,isValid:a.length===0})}return e}get(e){let t=this.pathFor(e);if(!j(t))return null;try{let r=JSON.parse(K(t,"utf-8")),n=S.safeParse(r);return n.success?n.data:null}catch{return null}}save(e){let t=new Date().toISOString().slice(0,10),r=S.parse({...e,created:e.created||t,updated:t});return Ze(this.pathFor(r.id),JSON.stringify(r,null,2)+`
|
|
2
|
+
`),r}delete(e){let t=this.pathFor(e);return j(t)?(Ve(t),!0):!1}};import{createHash as Qe,randomUUID as Xe}from"crypto";import{appendFileSync as ke,existsSync as $,mkdirSync as be,readdirSync as Ie,readFileSync as z,statSync as ve,unlinkSync as Se,writeFileSync as et}from"fs";import{resolve as I}from"path";function tt(s){return s.replace(/[^a-zA-Z0-9._:#@-]/g,"_").slice(0,200)}function w(s,e,t){return Qe("sha1").update(`${s}|${e}|${t}`).digest("hex").slice(0,16)}var B=class{baseDir;runsDir;indexDir;nodeId;constructor(e){this.baseDir=e.baseDir??I(process.cwd(),".agentx/workflows"),this.runsDir=I(this.baseDir,"_runs"),this.indexDir=I(this.baseDir,"_index"),this.nodeId=e.nodeId,be(this.runsDir,{recursive:!0}),be(this.indexDir,{recursive:!0})}runPath(e){return I(this.runsDir,`${e}.jsonl`)}indexPath(e){return I(this.indexDir,`${e.backend}__${tt(e.id)}.json`)}create(e){let t=new Date().toISOString(),r=Xe(),n={id:r,workflowId:e.workflowId,workflowVersion:2,homeNode:this.nodeId,status:"running",pausedAt:void 0,context:e.initialContext??{},pending:e.initialPending,entityRef:e.entityRef,history:[],parentRunId:e.parentRunId??null,parentNodeId:e.parentNodeId??null,rootRunId:e.rootRunId??r,depth:e.depth??0,joinCounters:{},createdAt:t,updatedAt:t};return this.appendSnapshot(n),this.writeIndex(e.entityRef,n.id),n}get(e){let t=this.runPath(e);if(!$(t))return null;let r=z(t,"utf-8").trim();if(!r)return null;let n=null;for(let i of r.split(`
|
|
3
|
+
`)){let o;try{o=JSON.parse(i)}catch{continue}if(o.v===2){if(o.kind==="snapshot")n=o.run;else if(o.kind==="exec"&&n&&n.id===o.runId){let a=n;n={...a,history:[...a.history,o.entry],pending:o.pending,status:o.status??a.status,pausedAt:o.pausedAt===null?void 0:o.pausedAt??a.pausedAt,context:o.context??a.context,joinCounters:o.joinCounters??a.joinCounters,updatedAt:o.entry.at}}}}return n}getActiveByEntity(e){let t=this.indexPath(e);if(!$(t))return null;try{return JSON.parse(z(t,"utf-8")).runId}catch{return null}}getHomeNodeByEntity(e){let t=this.indexPath(e);if(!$(t))return null;try{return JSON.parse(z(t,"utf-8")).homeNode}catch{return null}}recordExecution(e){let t=this.get(e.runId);if(!t)return null;if(t.history.some(i=>i.idempotencyKey===e.entry.idempotencyKey))return t;let r={v:2,kind:"exec",runId:e.runId,entry:e.entry,pending:e.nextPending,status:e.status,pausedAt:e.pausedAt===null?null:e.pausedAt,context:e.context,joinCounters:e.joinCounters};ke(this.runPath(e.runId),JSON.stringify(r)+`
|
|
4
|
+
`);let n=e.status??t.status;return n==="completed"||n==="failed"||n==="canceled"?this.clearIndex(t.entityRef):this.writeIndex(t.entityRef,t.id),this.get(e.runId)}setStatus(e,t){let r=this.get(e);if(!r)return null;let n={...r,status:t,updatedAt:new Date().toISOString()};return this.appendSnapshot(n),t!=="running"&&t!=="paused"&&this.clearIndex(n.entityRef),n}list(e={}){if(!$(this.runsDir))return[];let t=Ie(this.runsDir).filter(n=>n.endsWith(".jsonl")).map(n=>({name:n,mtime:ve(I(this.runsDir,n)).mtimeMs})).sort((n,i)=>i.mtime-n.mtime),r=[];for(let n of t){let i=this.get(n.name.replace(/\.jsonl$/,""));if(i&&!(e.workflowId&&i.workflowId!==e.workflowId)&&(r.push(i),e.limit&&r.length>=e.limit))break}return r}prune(e){if(!$(this.runsDir))return 0;let t=Date.now()-e.maxDays*24*60*60*1e3,r=[];for(let i of Ie(this.runsDir)){if(!i.endsWith(".jsonl"))continue;let o=I(this.runsDir,i),a=ve(o).mtimeMs,d=this.get(i.replace(/\.jsonl$/,""));d&&(d.status==="running"||d.status==="paused"||r.push({file:o,run:d,mtime:a}))}r.sort((i,o)=>o.mtime-i.mtime);let n=0;for(let i=0;i<r.length;i++){let o=r[i],a=o.mtime<t,d=i>=e.maxRuns;if(a||d)try{Se(o.file),n++}catch{}}return n}appendSnapshot(e){let t={v:2,kind:"snapshot",run:e};ke(this.runPath(e.id),JSON.stringify(t)+`
|
|
5
|
+
`)}writeIndex(e,t){et(this.indexPath(e),JSON.stringify({runId:t,homeNode:this.nodeId,updatedAt:new Date().toISOString()},null,2)+`
|
|
6
|
+
`)}clearIndex(e){let t=this.indexPath(e);if($(t))try{Se(t)}catch{}}};import{existsSync as $e,mkdirSync as nt,readFileSync as rt,unlinkSync as st,writeFileSync as ot}from"fs";import{resolve as J}from"path";import{z as R}from"zod";var Re=R.object({version:R.literal(1),nodes:R.record(R.object({x:R.number(),y:R.number()}))}),G=class{baseDir;layoutsDir;constructor(e={}){this.baseDir=e.baseDir??J(process.cwd(),".agentx/workflows"),this.layoutsDir=J(this.baseDir,"_layouts"),nt(this.layoutsDir,{recursive:!0})}pathFor(e){let t=e.replace(/[^a-zA-Z0-9._-]/g,"_");return J(this.layoutsDir,`${t}.json`)}get(e){let t=this.pathFor(e);if(!$e(t))return null;try{let r=Re.safeParse(JSON.parse(rt(t,"utf-8")));return r.success?r.data:null}catch{return null}}save(e,t){let r=Re.parse(t);return ot(this.pathFor(e),JSON.stringify(r,null,2)+`
|
|
7
|
+
`),r}sync(e,t){let r=this.get(e);if(!r)return null;let n={};for(let o of t)r.nodes[o]&&(n[o]=r.nodes[o]);let i={version:1,nodes:n};return this.save(e,i)}delete(e){let t=this.pathFor(e);return $e(t)?(st(t),!0):!1}};function V(s){let{workflow:e,fromNodeId:t,selectedPort:r}=s,n=e.nodes.find(a=>a.id===t);if(!n)return{nextPending:[]};let i=e.edges.filter(a=>a.from===t);return{nextPending:(n.type==="branch"?i.filter(a=>(a.fromPort??"")===(r??"")):i).map(a=>a.to)}}function Z(s,e){let t=s.config.cases??[],r=s.config.default??"";for(let n of t)if(it(n.when,e))return n.to;return r}function it(s,e){let t=s.params??{},r=String(t.path??""),n=W(e,r);switch(s.kind){case"equals":return n===t.value;case"contains":return typeof n=="string"?n.includes(String(t.value??"")):Array.isArray(n)?n.some(i=>i===t.value):!1;case"matches":{if(typeof n!="string")return!1;try{return new RegExp(String(t.regex??"")).test(n)}catch{return!1}}case"exists":return n!=null&&n!=="";default:return!1}}function W(s,e){if(!e)return;let t=e.split("."),r=s;for(let n of t){if(r==null||typeof r!="object")return;r=r[n]}return r}function E(s){let e=s.nodes.find(r=>r.type.startsWith("trigger."));if(!e)return null;let t=s.edges.filter(r=>r.from===e.id).map(r=>r.to);return{triggerId:e.id,pending:t}}function k(s,e){return s.nodes.find(t=>t.id===e)}function at(s,e,t){let r=s.split(".");if(r[0]==="env"){if(r.length!==2)return"";let i=r[1];if(!t.has(i))return"";let o=process.env[i];return typeof o=="string"?o:""}let n=e;for(let i of r){if(n==null||typeof n!="object")return"";n=n[i]}return n==null?"":typeof n=="object"?JSON.stringify(n):String(n)}function v(s,e,t={}){let r=new Set(t.envAllow||[]);return s.replace(/\{\{\s*([a-zA-Z_][a-zA-Z0-9_.-]*)\s*\}\}/g,(n,i)=>at(i,e,r))}function m(s,e,t={}){let r={};for(let[n,i]of Object.entries(s))typeof i=="string"?r[n]=v(i,e,t):Array.isArray(i)?r[n]=i.map(o=>typeof o=="string"?v(o,e,t):o):i&&typeof i=="object"?r[n]=m(i,e,t):r[n]=i;return r}var Y=class{active=new Map;waiters=new Map;async acquire(e,t){let r=this.active.get(e)??0;if(r<t){this.active.set(e,r+1);return}return new Promise(n=>{let i=this.waiters.get(e)??[];i.push({resolve:()=>{this.active.set(e,(this.active.get(e)??0)+1),n()}}),this.waiters.set(e,i)})}release(e){let t=this.active.get(e)??0;this.active.set(e,Math.max(0,t-1));let r=this.waiters.get(e);r&&r.length>0&&r.shift().resolve()}stats(e){return{active:this.active.get(e)??0,waiting:this.waiters.get(e)?.length??0}}},O=new Y;function Ae(s,e){return`${s}:${e}`}var T=async s=>({output:s.run.context[s.node.id]??{}}),dt=async s=>{let e=s.node.config,t=String(e.agentId??""),r=String(e.prompt??"");if(!t)return{error:`agent node "${s.node.id}" missing config.agentId`};let n=v(r,s.run.context,{envAllow:s.workflow.envAllow}),i=typeof e.timeoutMinutes=="number"?e.timeoutMinutes:void 0,o=typeof e.maxConcurrent=="number"&&e.maxConcurrent>0?e.maxConcurrent:null,a=o?Ae(s.workflow.id,s.node.id):null;if(a&&o){let c=O.stats(a);c.active>=o&&s.log(`[node:${s.node.id}] gated: ${c.active} active (cap ${o}), ${c.waiting+1} waiting`),await O.acquire(a,o)}let d=Date.now();try{let c=await s.agents.execute({agentId:t,message:n,workflowRunId:s.run.id,timeoutMinutes:i}),u=Date.now()-d;if(c.error)return s.log(`[node:${s.node.id}] agent "${t}" failed: ${c.error}`),{error:c.error};let f=String(e.resultParser??"noqta-result-token")==="json"?Nt(c.content):Pe(c.content);return{output:{reply:c.content,result:f.result,json:f.json,taskId:c.taskId,durationMs:u}}}catch(c){return s.log(`[node:${s.node.id}] agent "${t}" threw: ${c.message}`),{error:c.message}}finally{a&&O.release(a)}},ct=async s=>{let e=Z(s.node,s.run.context);return e?{output:{port:e},port:e}:(s.log(`[node:${s.node.id}] branch matched no case and no default port`),{output:{port:null}})},lt=async s=>{let e=m(s.node.config,s.run.context,{envAllow:s.workflow.envAllow}),t=String(e.channel??""),r=String(e.chatId??""),n=String(e.text??"");if(!t||!r){let a=String(s.node.config.chatId??""),d=String(s.node.config.channel??""),c=[];return t||c.push(`channel config="${d}" rendered=""`),r||c.push(`chatId config="${a}" rendered=""`),(/\{\{\s*env\./.test(a)||/\{\{\s*env\./.test(d))&&c.push("(env.* templates only resolve for names listed in workflow.envAllow and actually set in process.env)"),{error:`action.send "${s.node.id}" needs channel + chatId \u2014 ${c.join("; ")}`}}let i=X(s,e.accountId),o=s.channels[t];if(o?.send)try{return{output:{messageId:await o.send({channel:t,chatId:r,text:n,...i?{accountId:i}:{}})??null}}}catch(a){return{error:`action.send failed: ${a.message}`}}if(s.forwardChannelSend)try{return{output:{messageId:(await s.forwardChannelSend({channel:t,chatId:r,text:n,accountId:i})).messageId,viaMesh:!0}}}catch(a){return s.log(`[node:${s.node.id}] mesh-forward of "${t}" send failed: ${a.message}`),{error:`action.send via mesh failed: ${a.message}`}}return s.log(`[node:${s.node.id}] adapter "${t}" not available locally and no mesh forwarder`),{error:`channel "${t}" not available (no local adapter, no mesh route)`}},ut=async s=>{let e=m(s.node.config,s.run.context,{envAllow:s.workflow.envAllow}),t=String(e.channel??"gitlab"),r=String(e.project??""),n=String(e.title??""),i=String(e.description??""),o=Array.isArray(e.labels)?e.labels.map(String):[],a=Array.isArray(e.assignees)?e.assignees.map(String):[];if(!r||!n)return{error:`action.createIssue "${s.node.id}" needs project + title`};let d=ee(s),c=s.channels[t];if(!c?.createIssue)return{error:`channel "${t}" does not support createIssue`};try{let u=await c.createIssue({project:r,title:n,description:i,labels:o,assignees:a,agentId:d});return u?{output:{issue:{iid:u.iid,url:u.url,webUrl:u.url}}}:{error:"createIssue returned null (see adapter log)"}}catch(u){return{error:`action.createIssue failed: ${u.message}`}}},pt=async()=>({output:{}}),gt=async s=>{let e=s.node.config,t=String(e.name??s.node.id),r=e.waitFor??{},n=e.resumeMatch??{};return{paused:!0,pausedAt:{kind:"checkpoint",nodeId:s.node.id,checkpointName:t,resumeMatch:{...r,...n}}}},ft=async s=>{let e=m(s.node.config,s.run.context,{envAllow:s.workflow.envAllow}),t=String(e.channel??"gitlab"),r=String(e.project??""),n=String(e.iid??""),i=Array.isArray(e.add)?e.add.map(String):[],o=Array.isArray(e.remove)?e.remove.map(String):[];if(!r||!n)return{error:`action.setLabel "${s.node.id}" needs project + iid (try {{trigger.project}} / {{trigger.issue.iid}})`};if(i.length===0&&o.length===0)return{output:{skipped:!0,reason:"no labels to add or remove"}};let a=s.channels[t];if(!a?.setLabels)return{error:`channel "${t}" does not support setLabels`};let d=ee(s),c=e.kind==="merge_request"?"merge_request":"issue";return{output:{labels:await a.setLabels({project:r,kind:c,iid:n,add:i,remove:o,agentId:d})??[],add:i,remove:o}}},mt=async s=>{let e=m(s.node.config,s.run.context,{envAllow:s.workflow.envAllow}),t=String(e.channel??"gitlab"),r=String(e.project??""),n=String(e.iid??"");if(!r||!n)return{error:`action.readLabel "${s.node.id}" needs project + iid`};let i=s.channels[t];if(!i?.getLabels)return{error:`channel "${t}" does not support getLabels`};let o=e.kind==="merge_request"?"merge_request":"issue";return{output:{labels:await i.getLabels({project:r,kind:o,iid:n})??[]}}},ht=async s=>{let e=m(s.node.config,s.run.context,{envAllow:s.workflow.envAllow}),t=String(e.channel??""),r=String(e.chatId??""),n=String(e.messageId??""),i=String(e.emoji??"\u{1F440}");if(!t||!r||!n)return{error:`action.react "${s.node.id}" needs channel + chatId + messageId`};let o=X(s,e.accountId),a=s.channels[t];if(!a?.react)return{error:`channel "${t}" does not support react`};try{return await a.react(r,n,i,o),{output:{emoji:i}}}catch(d){return{error:`action.react failed: ${d.message}`}}},yt=async s=>{let e=m(s.node.config,s.run.context,{envAllow:s.workflow.envAllow}),t=String(e.channel??""),r=String(e.chatId??""),n=String(e.messageId??""),i=String(e.text??""),o=e.parseMode?String(e.parseMode):void 0;if(!t||!r||!n||!i)return{error:`action.editMessage "${s.node.id}" needs channel + chatId + messageId + text`};let a=X(s,e.accountId),d=s.channels[t];if(!d?.editMessage)return{error:`channel "${t}" does not support editMessage`};try{return{output:{edited:await d.editMessage(r,n,i,o,a)}}}catch(c){return{error:`action.editMessage failed: ${c.message}`}}},wt=async s=>{let e=m(s.node.config,s.run.context,{envAllow:s.workflow.envAllow}),t=String(e.channel??"gitlab"),r=String(e.chatId??""),n=Number(e.durationMs??0);if(!r||!n)return{error:`action.logTime "${s.node.id}" needs chatId + durationMs`};let i=s.channels[t];if(!i?.logTimeSpent)return{error:`channel "${t}" does not support logTimeSpent`};let o=ee(s);try{return await i.logTimeSpent(r,n,o),{output:{durationMs:n}}}catch(a){return{error:`action.logTime failed: ${a.message}`}}},kt=async s=>{let e=m(s.node.config,s.run.context,{envAllow:s.workflow.envAllow}),t=String(e.url??"");if(!t)return{error:`action.callHTTP "${s.node.id}" needs url`};let r=String(e.method??"POST").toUpperCase(),n=e.headers&&typeof e.headers=="object"?e.headers:{"Content-Type":"application/json"},i;e.body!==void 0&&(i=typeof e.body=="string"?e.body:JSON.stringify(e.body));try{let o=await fetch(t,{method:r,headers:n,body:i}),a=await o.text(),d=a;try{d=JSON.parse(a)}catch{}return{output:{ok:o.ok,status:o.status,body:d}}}catch(o){return{error:`action.callHTTP failed: ${o.message}`}}},bt=async s=>{let e=s.node.config,t=s.run.context;return typeof e.path=="string"&&e.path?{output:{value:W(t,e.path)}}:e.template&&typeof e.template=="object"?{output:m(e.template,t,{envAllow:s.workflow.envAllow})}:{output:{}}},It=async s=>{let e=s.node.config,t=m(e,s.run.context,{envAllow:s.workflow.envAllow}),r=String(t.assignTo??"");if(!r)return{error:`userTask "${s.node.id}" missing config.assignTo`};let n=ge(r);if(!n)return{error:`userTask "${s.node.id}" assignTo must be "actor:<id>" or "role:<id>", got "${r}"`};let i=e.form??t.form,o=vt(i);if(!o)return{error:`userTask "${s.node.id}" missing or invalid config.form`};let a=String(t.title??o.title??s.node.id),d=typeof t.description=="string"?t.description:void 0;if(!s.actors)return{error:`userTask "${s.node.id}" requires ActorStore in context (engine misconfigured)`};if(!s.tasks)return{error:`userTask "${s.node.id}" requires TaskStore in context (engine misconfigured)`};let c=s.actors.pickAssignees(n);if(c.length===0)return{error:`userTask "${s.node.id}" assignee "${r}" resolved to zero actors`};let u=Te(t.dueIn),p=s.tasks.create({runId:s.run.id,workflowId:s.workflow.id,nodeId:s.node.id,title:a,description:d,assignee:r,assignedTo:c,form:o,dueAt:u});return s.log(`[node:${s.node.id}] userTask created ${p.id} for ${r} \u2192 actors [${c.join(", ")}]`),{paused:!0,pausedAt:{kind:"userTask",nodeId:s.node.id,taskId:p.id,assignee:r,assignedTo:c}}};function vt(s){if(!s||typeof s!="object")return null;let e=pe.safeParse(s);return e.success?e.data:null}function Te(s){if(s!=null){if(typeof s=="number")return new Date(Date.now()+s*6e4).toISOString();if(typeof s=="object"&&s&&"minutes"in s){let e=Number(s.minutes);if(Number.isFinite(e))return new Date(Date.now()+e*6e4).toISOString()}if(typeof s=="string"){let e=/^P(?:([0-9]+)D)?(?:T(?:([0-9]+)H)?(?:([0-9]+)M)?(?:([0-9]+)S)?)?$/.exec(s);if(e){let t=Number(e[1]??0),r=Number(e[2]??0),n=Number(e[3]??0),i=Number(e[4]??0),o=((t*24+r)*60+n)*6e4+i*1e3;if(o>0)return new Date(Date.now()+o).toISOString()}}}}var St=async s=>{let e=s.node.config,t=m(e,s.run.context,{envAllow:s.workflow.envAllow}),r=String(t.workflowId??"");if(!r)return{error:`subProcess "${s.node.id}" missing config.workflowId`};let n=s.run.depth??0,i=s.workflow.maxChildDepth??5;if(n+1>=i)return{error:`max child workflow depth ${i} exceeded at ${s.workflow.id} \u2192 ${r} (parent depth ${n})`};let o=e.inputMap,a={};if(o==="*")a={...s.run.context};else if(o&&typeof o=="object")for(let[d,c]of Object.entries(o))c&&typeof c=="object"&&!Array.isArray(c)?a[d]=m(c,s.run.context,{envAllow:s.workflow.envAllow}):typeof c=="string"&&(a[d]={value:v(c,s.run.context,{envAllow:s.workflow.envAllow})});return{paused:!0,pausedAt:{kind:"subProcess",nodeId:s.node.id,childRunId:"",childWorkflowId:r},spawnChild:{workflowId:r,input:a}}},$t=T,Rt=async s=>{let e=s.node.config,t=m(e,s.run.context,{envAllow:s.workflow.envAllow}),r=String(t.name??"");if(!r)return{error:`signal.emit "${s.node.id}" missing config.name`};let n=t.scope==="global"?"global":"workflow",i=t.payload&&typeof t.payload=="object"?t.payload:{};return{output:{emittedAt:new Date().toISOString(),name:r,scope:n,payload:i},emitSignal:{name:r,scope:n,payload:i}}},At=async s=>{let e=s.node.config,t=m(e,s.run.context,{envAllow:s.workflow.envAllow}),r=String(t.name??"");if(!r)return{error:`signal.wait "${s.node.id}" missing config.name`};let n=t.scope==="global"?"global":"workflow",i=e.match&&typeof e.match=="object"?e.match:{};return{paused:!0,pausedAt:{kind:"signalWait",nodeId:s.node.id,signalName:r,match:i,scope:n}}},Tt=async s=>{let e=s.node.config,t=m(e,s.run.context,{envAllow:s.workflow.envAllow}),r=Te(t.after??e.after);return r?{paused:!0,pausedAt:{kind:"timerWait",nodeId:s.node.id,fireAt:r}}:{error:`timer.boundary "${s.node.id}" needs config.after (duration or minutes)`}},xt=async()=>({output:{}}),Pt=async s=>{let e=s.node.config,r=(Array.isArray(e.inputs)?e.inputs:[]).map(o=>typeof o=="string"?v(o,s.run.context,{envAllow:s.workflow.envAllow}):o),n=Array.isArray(e.rules)?e.rules:[];for(let o of n){let a=Array.isArray(o.when)?o.when:[];if(a.length!==r.length)continue;let d=!0;for(let c=0;c<a.length;c++)if(!jt(a[c],r[c])){d=!1;break}if(d)return{output:{...o.output??{},matchedPort:o.to??""},port:o.to}}let i=e.default;return{output:{...i?.output??{},matchedPort:i?.to??""},port:i?.to}};function jt(s,e){if(s==null||s==="*")return!0;let t=String(s),r=typeof e=="number"?e:String(e??""),n=/^(>=|<=|>|<)\s*(-?\d+(?:\.\d+)?)$/.exec(t);if(n){let i=n[1],o=Number(n[2]),a=typeof r=="number"?r:Number(r);if(Number.isNaN(a))return!1;switch(i){case">":return a>o;case"<":return a<o;case">=":return a>=o;case"<=":return a<=o}}if(t.startsWith("!="))return String(r)!==t.slice(2);if(t.startsWith("/")&&t.endsWith("/")&&t.length>2)try{return new RegExp(t.slice(1,-1)).test(String(r))}catch{return!1}return String(r)===t}var xe={"trigger.channel":T,"trigger.manual":T,"trigger.cron":T,"trigger.hook":T,"trigger.form":$t,agent:dt,branch:ct,transform:bt,"gateway.parallel":xt,rule:Pt,"action.send":lt,"action.createIssue":ut,"action.setLabel":ft,"action.readLabel":mt,"action.react":ht,"action.editMessage":yt,"action.logTime":wt,"action.callHTTP":kt,userTask:It,subProcess:St,"signal.emit":Rt,"signal.wait":At,"timer.boundary":Tt,checkpoint:gt,end:pt};function Q(s){return xe[s]}function X(s,e){if(typeof e=="string"&&e)return e;let t=s.workflow.nodes.find(i=>i.type.startsWith("trigger."));if(!t)return;let n=s.run.context[t.id]?.accountId;return typeof n=="string"&&n?n:void 0}function ee(s){for(let e=s.run.history.length-1;e>=0;e--){let t=s.run.history[e],r=s.workflow.nodes.find(n=>n.id===t.nodeId);if(r?.type==="agent")return r.config.agentId??void 0}}function Pe(s){if(!s)return{};let e=s.match(/\bRESULT:\s*([a-z][a-z0-9_-]*)/i);if(e)return{result:e[1].toLowerCase()};let t=s.match(/\[(APPROVED|REJECTED|CHANGES[-_]REQUESTED|DONE|FAILED|SKIPPED)\]/i);return t?{result:t[1].toLowerCase().replace("_","-")}:{}}function Nt(s){let{result:e}=Pe(s);if(!s)return{result:e};let t=s.match(/```(?:json)?\s*([\s\S]*?)```/);if(!t)return{result:e};try{return{result:e,json:JSON.parse(t[1])}}catch{return{result:e}}}var x=class{handlers=new Set;subscribe(e){return this.handlers.add(e),()=>this.handlers.delete(e)}emit(e){for(let t of this.handlers)try{Promise.resolve(t(e)).catch(()=>{})}catch{}}};function te(s,e){if(s.name!==e.name||s.scope==="workflow"&&s.workflowId!==e.workflowId)return!1;for(let[t,r]of Object.entries(s.match??{}))if(!(r===void 0||r===""||r==="*")&&e.payload[t]!==r)return!1;return!0}function je(s,e){let t=[],r={};for(let n of s.fields){let i=e.values[n.key];if(!(i!=null&&i!=="")){n.required?t.push({field:n.key,message:`"${n.label}" is required`}):n.defaultValue!==void 0&&(r[n.key]=n.defaultValue);continue}let a=Wt(n,i);if(a.error){t.push({field:n.key,message:a.error});continue}r[n.key]=a.value}return{ok:t.length===0,errors:t,values:r}}function Wt(s,e){let t=s.label;switch(s.type){case"text":case"long-text":{let r=String(e);return s.validate?.min!==void 0&&r.length<s.validate.min?{error:`"${t}" must be at least ${s.validate.min} characters`}:s.validate?.max!==void 0&&r.length>s.validate.max?{error:`"${t}" must be at most ${s.validate.max} characters`}:s.validate?.pattern&&!new RegExp(s.validate.pattern).test(r)?{error:`"${t}" does not match the expected format`}:{value:r}}case"number":{let r=typeof e=="number"?e:Number(String(e));return Number.isFinite(r)?s.validate?.min!==void 0&&r<s.validate.min?{error:`"${t}" must be at least ${s.validate.min}`}:s.validate?.max!==void 0&&r>s.validate.max?{error:`"${t}" must be at most ${s.validate.max}`}:{value:r}:{error:`"${t}" must be a number`}}case"boolean":{if(typeof e=="boolean")return{value:e};let r=String(e).toLowerCase();return["true","yes","y","1"].includes(r)?{value:!0}:["false","no","n","0"].includes(r)?{value:!1}:{error:`"${t}" must be yes/no`}}case"date":{let r=typeof e=="string"||typeof e=="number"?new Date(e):null;return!r||Number.isNaN(r.getTime())?{error:`"${t}" is not a valid date`}:{value:r.toISOString()}}case"select":{let r=String(e),n=s.options??[];return n.length&&!n.includes(r)?{error:`"${t}" must be one of: ${n.join(", ")}`}:{value:r}}case"multi-select":{let r=Array.isArray(e)?e.map(String):String(e).split(",").map(i=>i.trim()).filter(Boolean),n=s.options??[];if(n.length){let i=r.filter(o=>!n.includes(o));if(i.length)return{error:`"${t}" contains invalid values: ${i.join(", ")}`}}return{value:r}}case"file":return{value:String(e)}}return{error:`unknown field type: ${s.type}`}}var Et=4e3,ne=class{store;runs;nodeId;forwarder;channels;agents;log;actors;tasks;timers;signals;renderUserTask;ackTypingTimers=new Map;constructor(e){this.store=e.store,this.runs=e.runs,this.nodeId=e.nodeId,this.forwarder=e.forwarder,this.channels=e.channels,this.agents=e.agents,this.log=e.log??(()=>{}),this.actors=e.actors??new fe,this.tasks=e.tasks??new L,this.timers=e.timers??new M({log:t=>this.log(t)}),this.signals=e.signals??new x,this.renderUserTask=e.renderUserTask,this.timers.onFire(t=>this.resumeFromTimer(t)),this.signals.subscribe(t=>this.resumeFromSignal(t))}emitSignal(e){let t={name:e.name,scope:e.scope??"global",workflowId:e.workflowId??"",payload:e.payload??{},emittedAt:new Date().toISOString()};return this.signals.emit(t),t}async resumeFromSignal(e){let t=this.runs.list({limit:500});for(let r of t){if(r.status!=="paused"||!r.pausedAt||r.pausedAt.kind!=="signalWait")continue;let n={name:r.pausedAt.signalName,scope:r.pausedAt.scope,workflowId:r.workflowId,match:r.pausedAt.match??{}};if(!te(n,e))continue;let i=this.store.list().find(p=>p.id===r.workflowId);if(!i)continue;let o=k(i,r.pausedAt.nodeId),a=o?i.edges.filter(p=>p.from===o.id).map(p=>p.to):[],d={receivedAt:new Date().toISOString(),name:e.name,payload:e.payload},c={at:d.receivedAt,nodeId:r.pausedAt.nodeId,inputKeys:[],status:"resumed",output:d,idempotencyKey:w(r.id,r.pausedAt.nodeId,`signal:${e.name}:${e.emittedAt}`)},u={...r.context,[r.pausedAt.nodeId]:d};this.runs.recordExecution({runId:r.id,entry:c,nextPending:a,status:"running",pausedAt:null,context:u}),this.log(`[workflow:${i.id}] run ${r.id} resumed from signal "${e.name}"`),this.walk(i,r.id,`signal:${e.name}`).catch(p=>this.log(`[workflow:${i.id}] walk-after-signal failed: ${p.message}`))}}async resumeFromTimer(e){let t=this.runs.get(e.runId);if(!t||t.status!=="paused"||!t.pausedAt||t.pausedAt.kind!=="timerWait"||t.pausedAt.nodeId!==e.nodeId)return;let r=this.store.list().find(u=>u.id===t.workflowId);if(!r)return;let n=k(r,e.nodeId),i=n?r.edges.filter(u=>u.from===n.id).map(u=>u.to):[],o=new Date().toISOString(),a={firedAt:o,scheduledFor:e.fireAt},d={at:o,nodeId:e.nodeId,inputKeys:[],status:"resumed",output:a,idempotencyKey:w(t.id,e.nodeId,`timer:${e.id}`)},c={...t.context,[e.nodeId]:a};this.runs.recordExecution({runId:t.id,entry:d,nextPending:i,status:"running",pausedAt:null,context:c}),this.log(`[workflow:${r.id}] run ${t.id} resumed from timer "${e.nodeId}" (fired ${o})`),this.walk(r,t.id,`timer:${e.id}`).catch(u=>this.log(`[workflow:${r.id}] walk-after-timer failed: ${u.message}`))}async dispatchWorkflow(e){let t=this.store.list().find(o=>o.id===e.workflowId);if(!t)return this.log(`[workflows] dispatchWorkflow: workflow "${e.workflowId}" not found`),{claimed:!1,run:null};let n=t.nodes.find(o=>o.type.startsWith("trigger."))?.config??{},i={source:e.trigger?.source??n.source??"hook",project:e.trigger?.project,repo:e.trigger?.repo,chat:e.trigger?.chat,labels:e.trigger?.labels};return this.dispatchOne(t,e.entityRef,e.event,i)}startChannelAck(e,t){let r=String(t.channel??""),n=String(t.chatId??"");if(!r||!n)return;let i=this.channels[r];if(!i)return;let o=typeof t.accountId=="string"?t.accountId:void 0,a=t.event,d=typeof a?.id=="string"?a.id:a?.id!=null?String(a.id):void 0;if(d&&i.react)try{Promise.resolve(i.react(n,d,"\u{1F440}",o)).catch(()=>{})}catch{}if(this.ackTypingTimers.has(e)||!i.sendTyping)return;let c=()=>{try{Promise.resolve(i.sendTyping(n,o)).catch(()=>{})}catch{}};c();let u=setInterval(c,Et);this.ackTypingTimers.set(e,u)}stopChannelAck(e){let t=this.ackTypingTimers.get(e);t&&(clearInterval(t),this.ackTypingTimers.delete(e))}async dispatch(e){let t=this.matchByTrigger(e.trigger,e.fromRemote),n=t.some(a=>a.fanOut)?t:t.slice(0,1),i=[],o=[];for(let a of n){let d=await this.dispatchOne(a,e.entityRef,e.event,e.trigger);d.claimed&&i.push(a),d.run&&o.push(d.run)}return!e.fromRemote&&this.forwarder?.broadcastTrigger&&Promise.resolve(this.forwarder.broadcastTrigger({trigger:e.trigger,entityRef:e.entityRef,event:e.event})).catch(a=>this.log(`[mesh-broadcast] ${a?.message??a}`)),{claimed:i,runs:o}}matchByTrigger(e,t){let r=this.store.list(),n=[];for(let i of r){if(t){if(!i.mesh?.allowRemote)continue;let c=i.mesh.peers;if(c&&c.length>0&&!c.includes(t.peer))continue}let o=i.nodes.find(c=>c.type.startsWith("trigger."));if(!o)continue;let a=o.config;if(a.source!==e.source)continue;let d=a.filter;if(d){if(d.project&&d.project!=="*"&&d.project!==e.project||d.repo&&d.repo!=="*"&&d.repo!==e.repo||d.chat&&d.chat!=="*"&&C(e.source,d.chat)!==C(e.source,e.chat))continue;if(d.labels?.length){let c=new Set(e.labels??[]);if(!d.labels.some(p=>c.has(p)))continue}}n.push(i)}return n.sort((i,o)=>o.priority-i.priority||i.id.localeCompare(o.id))}async dispatchOne(e,t,r,n){let i=this.runs.getActiveByEntity(t),o=i?this.runs.getHomeNodeByEntity(t):null;if(i&&o&&o!==this.nodeId){if(!this.forwarder)return this.log(`[workflow:${e.id}] run ${i} home'd on "${o}" but no forwarder \u2014 dropping`),{claimed:!1,run:null};try{await this.forwarder.forwardTransition(o,{workflowId:e.id,event:r,entityRef:t})}catch(d){this.log(`[workflow:${e.id}] forward to "${o}" failed: ${d.message}`)}return{claimed:!0,run:null}}let a=i?this.runs.get(i):null;if(a)if(a.status==="paused"&&a.pausedAt){if(a.pausedAt.kind!=="checkpoint")return this.log(`[workflow:${e.id}] run ${a.id} paused on ${a.pausedAt.kind} \u2014 channel event bypasses workflow`),{claimed:!1,run:null};if(!Ct(a.pausedAt.resumeMatch,n,r))return this.log(`[workflow:${e.id}] run ${a.id} paused but event doesn't match resumeMatch \u2014 dropping`),{claimed:!1,run:null};let d=k(e,a.pausedAt.nodeId),c=d?e.edges.filter(A=>A.from===d.id).map(A=>A.to):[],u={at:new Date().toISOString(),nodeId:a.pausedAt.nodeId,inputKeys:[],status:"resumed",output:{event:r.payload},idempotencyKey:w(a.id,a.pausedAt.nodeId,`resume:${r.id}`)},p={...a.context,[a.pausedAt.nodeId]:{event:r.payload}},f=a.pausedAt.checkpointName,h=this.runs.recordExecution({runId:a.id,entry:u,nextPending:c,status:"running",pausedAt:null,context:p});h&&(a=h),this.log(`[workflow:${e.id}] run ${a.id} resumed from checkpoint "${f}"`),n.source.endsWith("-message")&&this.startChannelAck(a.id,r.payload)}else return a.status==="running"?(this.log(`[workflow:${e.id}] run ${a.id} already running; dropping concurrent event ${r.id}`),{claimed:!0,run:null}):(this.log(`[workflow:${e.id}] run ${a.id} is ${a.status}; dropping event ${r.id}`),{claimed:!1,run:null});else{let d=E(e);if(!d)return this.log(`[workflow:${e.id}] no trigger node found`),{claimed:!1,run:null};a=this.runs.create({workflowId:e.id,initialPending:d.pending,entityRef:t,initialContext:{[d.triggerId]:r.payload}}),this.log(`[workflow:${e.id}] run ${a.id} created from trigger "${d.triggerId}" for ${t.id}`),n.source.endsWith("-message")&&this.startChannelAck(a.id,r.payload)}return this.walk(e,a.id,r.id),{claimed:!0,run:a}}async walk(e,t,r){try{await this.walkInner(e,t,r)}finally{this.stopChannelAck(t)}}async walkInner(e,t,r){let n=this.runs.get(t);if(n)for(;n.status==="running"&&n.pending.length>0;){let i=n.pending[0],o=n.pending.slice(1),a=k(e,i);if(!a){this.log(`[workflow:${e.id}] missing node "${i}" \u2014 marking run failed`),this.runs.recordExecution({runId:n.id,entry:{at:new Date().toISOString(),nodeId:i,inputKeys:[],status:"failed",idempotencyKey:w(n.id,i,r),note:"node definition missing from workflow"},nextPending:o,status:"failed"});return}let d=Q(a.type);if(!d){this.log(`[workflow:${e.id}] no handler for node type "${a.type}"`),this.runs.recordExecution({runId:n.id,entry:{at:new Date().toISOString(),nodeId:i,inputKeys:[],status:"failed",idempotencyKey:w(n.id,i,r),note:`no handler for node type "${a.type}"`},nextPending:o,status:"failed"});return}let c=w(n.id,i,r);if(n.history.some(g=>g.idempotencyKey===c)){n=this.runs.recordExecution({runId:n.id,entry:{at:new Date().toISOString(),nodeId:i,inputKeys:[],status:"skipped",idempotencyKey:c+"_dedup",note:"duplicate execution dropped"},nextPending:o})??n;continue}let u=e.edges.filter(g=>g.to===i).map(g=>g.from),p;try{p=await d({workflow:e,run:n,node:a,channels:this.channels,agents:this.agents,actors:this.actors,tasks:this.tasks,forwardChannelSend:this.forwarder?.forwardChannelSend?.bind(this.forwarder),log:this.log})}catch(g){this.log(`[workflow:${e.id}] handler "${a.type}" threw: ${g.message}`),p={error:g.message}}let f=new Date().toISOString();if(p.error){let g={at:f,nodeId:i,inputKeys:u,status:"failed",idempotencyKey:c,note:p.error.slice(0,200)};n=this.runs.recordExecution({runId:n.id,entry:g,nextPending:o,status:"failed"})??n;return}if(p.paused&&p.pausedAt){let g=p.pausedAt,b=null;if(p.spawnChild&&g.kind==="subProcess"){if(b=await this.spawnChild({parent:n,parentNodeId:i,childWorkflowId:p.spawnChild.workflowId,input:p.spawnChild.input}),!b){let y={at:f,nodeId:i,inputKeys:u,status:"failed",idempotencyKey:c,note:`subProcess: child workflow "${p.spawnChild.workflowId}" not found`};n=this.runs.recordExecution({runId:n.id,entry:y,nextPending:o,status:"failed"})??n;return}g={...g,childRunId:b.id}}let le={at:f,nodeId:i,inputKeys:u,status:"paused",idempotencyKey:c};if(n=this.runs.recordExecution({runId:n.id,entry:le,nextPending:o,status:"paused",pausedAt:g})??n,b&&this.kickChildWalk(b),g.kind==="timerWait")try{this.timers.schedule({runId:n.id,workflowId:e.id,nodeId:g.nodeId,fireAt:g.fireAt,cancelKey:`${n.id}:${g.nodeId}`})}catch(y){this.log(`[workflow:${e.id}] timer schedule failed: ${y.message}`)}if(g.kind==="userTask"&&this.renderUserTask){let y=this.tasks.get(g.taskId);if(y)try{await this.renderUserTask(y)}catch(U){this.log(`[workflow:${e.id}] userTask render failed: ${U.message}`)}}return}let h=p.output??{},A={...n.context,[i]:h},{nextPending:Ee}=V({workflow:e,fromNodeId:i,selectedPort:p.port}),D=[],P={...n.joinCounters??{}};for(let g of Ee){let b=k(e,g);if(!(b?.type==="gateway.parallel"&&String(b.config?.mode??"fanOut")==="join")){D.push(g);continue}let y=P[g]??[];y.includes(i)||y.push(i),P[g]=y;let U=e.edges.filter(_e=>_e.to===g).length;y.length>=U&&(D.push(g),delete P[g])}let Oe=[...o,...D],F=a.type==="end",Ce={at:f,nodeId:i,inputKeys:u,status:"ok",output:h,idempotencyKey:c};if(n=this.runs.recordExecution({runId:n.id,entry:Ce,nextPending:F?[]:Oe,status:F?String(a.config.status??"completed"):"running",context:A,joinCounters:P})??n,p.emitSignal&&this.emitSignal({name:p.emitSignal.name,scope:p.emitSignal.scope,workflowId:e.id,payload:p.emitSignal.payload}),F){n.parentRunId&&n.parentNodeId&&this.resumeParent({parentRunId:n.parentRunId,parentNodeId:n.parentNodeId,childRun:n}).catch(g=>this.log(`[workflow:${e.id}] resumeParent failed: ${g.message}`));return}}}async spawnChild(e){let t=this.store.list().find(a=>a.id===e.childWorkflowId);if(!t)return null;let r=E(t);if(!r)return this.log(`[workflow:${t.id}] no trigger node \u2014 cannot spawn as child`),null;let n={...e.input};n[r.triggerId]={...n[r.triggerId]??{},parentRunId:e.parent.id,parentNodeId:e.parentNodeId};let i={backend:"agentx-internal",id:`subprocess:${e.parent.id}:${e.parentNodeId}`},o=this.runs.create({workflowId:t.id,initialPending:r.pending,entityRef:i,initialContext:n,parentRunId:e.parent.id,parentNodeId:e.parentNodeId,rootRunId:e.parent.rootRunId??e.parent.id,depth:(e.parent.depth??0)+1});return this.log(`[workflow:${t.id}] child run ${o.id} spawned from ${e.parent.id}/${e.parentNodeId} (depth ${o.depth})`),o}kickChildWalk(e){let t=this.store.list().find(r=>r.id===e.workflowId);t&&this.walk(t,e.id,`spawn:${e.parentRunId}:${e.parentNodeId}`).catch(r=>this.log(`[workflow:${t.id}] child walk failed: ${r.message}`))}async resumeParent(e){let t=this.runs.get(e.parentRunId);if(!t||t.status!=="paused"||!t.pausedAt||t.pausedAt.kind!=="subProcess"||t.pausedAt.nodeId!==e.parentNodeId||t.pausedAt.childRunId!==e.childRun.id)return;let r=this.store.list().find(c=>c.id===t.workflowId);if(!r)return;let n=k(r,e.parentNodeId),i=n?r.edges.filter(c=>c.from===n.id).map(c=>c.to):[],o={childRunId:e.childRun.id,status:e.childRun.status,output:Ot(e.childRun)},a={at:new Date().toISOString(),nodeId:e.parentNodeId,inputKeys:[],status:"resumed",output:o,idempotencyKey:w(t.id,e.parentNodeId,`child:${e.childRun.id}`)},d={...t.context,[e.parentNodeId]:o};this.runs.recordExecution({runId:t.id,entry:a,nextPending:i,status:"running",pausedAt:null,context:d}),this.log(`[workflow:${r.id}] parent run ${t.id} resumed from subProcess child ${e.childRun.id}`),this.walk(r,t.id,`child:${e.childRun.id}`)}async submitTask(e,t,r){let n=this.tasks.get(e);if(!n)return{ok:!1,error:`task not found: ${e}`};if(n.status!=="open")return{ok:!1,error:`task is ${n.status}`};let i=je(n.form,t);if(!i.ok)return{ok:!1,error:"form validation failed",fieldErrors:i.errors};let o=this.runs.get(n.runId);if(!o||o.status!=="paused")return{ok:!1,error:`run ${n.runId} is not paused`};if(!o.pausedAt||o.pausedAt.kind!=="userTask"||o.pausedAt.taskId!==e)return{ok:!1,error:`run ${n.runId} is not waiting on task ${e}`};let a=this.store.list().find(h=>h.id===o.workflowId);if(!a)return{ok:!1,error:`workflow ${o.workflowId} not found`};let d=k(a,n.nodeId),c=d?a.edges.filter(h=>h.from===d.id).map(h=>h.to):[],u={submittedBy:r,submittedAt:new Date().toISOString(),values:i.values,action:t.action},p={at:new Date().toISOString(),nodeId:n.nodeId,inputKeys:[],status:"resumed",output:u,idempotencyKey:w(o.id,n.nodeId,`task:${e}:${t.action}`)},f={...o.context,[n.nodeId]:u};return this.runs.recordExecution({runId:o.id,entry:p,nextPending:c,status:"running",pausedAt:null,context:f}),this.tasks.save({...n,status:"completed",submittedBy:r,submittedAt:u.submittedAt,submittedValues:i.values,submittedAction:t.action}),this.log(`[workflow:${a.id}] run ${o.id} resumed from userTask ${e}`),this.walk(a,o.id,`task:${e}`),{ok:!0,runId:o.id}}};function Ot(s){for(let e=s.history.length-1;e>=0;e--){let t=s.history[e];if(t.status==="ok"&&t.output&&Object.keys(t.output).length>0)return t.output}return{}}function Ct(s,e,t){let r=i=>{let o=s[i];if(o===void 0||o===""||o==="*")return!0;if(Array.isArray(o)){let a=new Set(e.labels??[]);return o.some(d=>a.has(String(d)))}return i==="chat"?C(e.source,String(o))===C(e.source,e.chat):e[i]===o};if(!r("source")||!r("project")||!r("repo")||!r("chat"))return!1;let n=s.eventIdLike;return!(typeof n=="string"&&n&&!t.id.includes(n))}function C(s,e){return e==null?"":s==="whatsapp-message"?e.replace(/@s\.whatsapp\.net$|@g\.us$/i,"").replace(/[\s+()]/g,""):e}function _t(s){return{"pre:channel-message":async e=>{let t=e.msg;if(!t)return{};let r=Dt(t.channel);if(!r)return{};let n=t.group?.id??t.sender.id,i={backend:t.channel,id:`${t.channel}:${n}`},{claimed:o}=await s.dispatch({trigger:{source:r,chat:n},entityRef:i,event:{id:`${t.channel}:${t.accountId}:${t.id}`,payload:Ft(t)}});return o.length===0?{}:o.every(d=>d.nodes.find(u=>u.type.startsWith("trigger."))?.config?.passthrough===!0)?{}:{blocked:!0,message:`handled by workflow${o.length>1?"s":""}: ${o.map(d=>d.id).join(", ")}`}},"on:gitlab-issue":async e=>{let t=e.project,r=e.iid,n=e.issueEvent;if(!t||r===void 0)return{};let i=Array.isArray(n?.object_attributes?.labels)?n.object_attributes.labels.map(d=>typeof d=="string"?d:d?.title).filter(Boolean):n?.labels?.map(d=>d?.title)??[],o=n?.assignees??n?.object_attributes?.assignee_ids??[],a={backend:"gitlab",id:`${t}#${r}`};return await s.dispatch({trigger:{source:"gitlab-issue",project:t,labels:i},entityRef:a,event:{id:`gitlab-issue:${t}:${r}:${n?.object_attributes?.updated_at||n?.object_attributes?.action||"evt"}`,payload:{issue:{iid:r,title:n?.object_attributes?.title,description:n?.object_attributes?.description,url:n?.object_attributes?.url,action:n?.object_attributes?.action,labels:i,assignees:o},project:t,channel:"gitlab",chatId:`${t}:issue:${r}`}}}),{}},"on:gitlab-pipeline":async e=>{let t=e.project,r=e.pipelineId,n=e.status,i=e.ref;if(!t||!n)return{};let a=e.raw?.merge_request?.iid;if(!a)return{};let d={backend:"gitlab",id:`${t}!${a}`};return await s.dispatch({trigger:{source:"gitlab-pipeline",project:t},entityRef:d,event:{id:`gitlab-pipeline:${t}:${r}:${n}`,payload:{pipeline:{id:r,status:n,ref:i},project:t,channel:"gitlab",chatId:`${t}:merge_request:${a}`}}}),{}}}}function Dt(s){switch(s){case"telegram":return"telegram-message";case"whatsapp":return"whatsapp-message";case"discord":return"discord-message";case"slack":return"slack-message";default:return null}}function Ft(s){let e=s.group?.id??s.sender.id;return{channel:s.channel,chatId:e,accountId:s.accountId,text:s.text,fromJid:s.sender.id,sender:{id:s.sender.id,name:s.sender.name,username:s.sender.username},contact:s.channelMeta?.facts?{facts:s.channelMeta.facts}:void 0,group:s.group?{id:s.group.id,name:s.group.name}:void 0,channelMeta:s.channelMeta,replyTo:s.replyTo,replyToText:s.replyToText,media:s.media,event:{id:s.id,timestamp:s.timestamp?.toISOString?.()??new Date().toISOString()}}}function Ut(s){let e=s.store.list(),t=0,r=0;for(let n of e){let i=n.nodes.find(o=>o.type.startsWith("trigger."));if(i){if(i.type==="trigger.cron"){let o=i.config;if(!o.spec){s.log(`[workflows] ${n.id} trigger.cron missing config.spec \u2014 skipping`);continue}Mt({workflowId:n.id,triggerNodeId:i.id,spec:o.spec,timezone:o.timezone??"UTC"},s.dispatcher,s.log),t++}else if(i.type==="trigger.hook"){let o=i.config;if(!o.event||!o.event.startsWith("on:")){s.log(`[workflows] ${n.id} trigger.hook config.event must start with "on:" \u2014 skipping`);continue}s.hooks.registerHandler(o.event,`workflows:${n.id}`,async a=>{try{let d=Lt(n.id,o.event,a);await s.dispatcher.dispatchWorkflow({workflowId:n.id,entityRef:{backend:"hook",id:d},event:{id:`hook:${o.event}:${Date.now().toString(36)}`,payload:{hookEvent:o.event,...a}}})}catch(d){s.log(`[workflows] ${n.id} hook dispatch failed: ${d.message}`)}return{}},60),r++}}}return{cronTimers:t,hookSubscribers:r}}function Lt(s,e,t){let r=typeof t.project=="string"?t.project:"",n=t.iid!=null?String(t.iid):"";if(e==="on:gitlab-issue"&&r&&n)return`${s}:${e}:${r}#${n}`;if(e==="on:gitlab-mr"&&r&&n)return`${s}:${e}:${r}!${n}`;if(e==="on:gitlab-pipeline"&&r){let i=t.pipelineId!=null?String(t.pipelineId):"";if(i)return`${s}:${e}:${r}@pipeline:${i}`}return`${s}:${e}:${Date.now().toString(36)}-${Math.random().toString(36).slice(2,8)}`}function Mt(s,e,t){let r=()=>{let n;try{n=ue(s.spec,new Date,s.timezone)}catch(o){t(`[workflows] ${s.workflowId} cron spec invalid: ${o.message}`);return}let i=Math.max(1e3,n.getTime()-Date.now());setTimeout(()=>{(async()=>{let o=`cron:${s.workflowId}:${n.toISOString()}`;try{await e.dispatchWorkflow({workflowId:s.workflowId,trigger:{source:"cron"},entityRef:{backend:"cron",id:`${s.workflowId}@${n.toISOString()}`},event:{id:o,payload:{now:n.toISOString(),spec:s.spec,workflowId:s.workflowId}}})}catch(a){t(`[workflows] ${s.workflowId} cron dispatch failed: ${a.message}`)}finally{r()}})()},i)};r()}import{readFileSync as re,writeFileSync as We,existsSync as se,readdirSync as Kt,statSync as qt}from"fs";import{resolve as oe,join as zt}from"path";var ie=/^(\s*)-\s*\[( |x|X)\]\s*(.*)$/,_=/@([a-z0-9_-]+)/i,Ne=/\[time:\s*([0-9hm\s]+)\]/i;function Bt(s){let e=s.match(/(\d+)\s*h/i),t=s.match(/(\d+)\s*m/i);return(e?parseInt(e[1],10)*3600:0)+(t?parseInt(t[1],10)*60:0)}var ae=class{constructor(e){this.path=e}type="backlog";capabilities={listAll:!1,create:!1,transition:!1,labels:!1,search:!1};read(){let e=oe(process.cwd(),this.path);return se(e)?re(e,"utf-8").split(`
|
|
8
|
+
`):[]}write(e){let t=oe(process.cwd(),this.path);We(t,e.join(`
|
|
9
|
+
`))}async listOpen(e){let t=this.read(),r=[];for(let n=0;n<t.length;n++){let i=t[n].match(ie);if(!i||i[2].toLowerCase()==="x")continue;let a=i[3],d=a.match(_);if(d&&d[1].toLowerCase()!==e.toLowerCase())continue;let c=a.match(Ne),u=a.replace(_,"").replace(Ne,"").trim();r.push({id:`backlog:${n}`,title:u||`Task at line ${n+1}`,assignee:d?d[1]:void 0,estimatedSeconds:c?Bt(c[1]):void 0,priority:n})}return r}async claim(e,t){}async report(e,t){if(!e.startsWith("backlog:"))throw new Error(`not a backlog id: ${e}`);let r=parseInt(e.slice(8),10),n=this.read(),i=n[r];if(!i)throw new Error(`backlog line ${r} missing`);if(!i.match(ie))throw new Error(`backlog line ${r} not a checkbox`);if(t.status==="done"){n[r]=i.replace(/- \[ \]/,"- [x]");let a=t.timeSeconds?` <!-- done by agent, ${Math.round(t.timeSeconds/60)}m -->`:" <!-- done by agent -->";n.splice(r+1,0,a)}else t.status==="blocked"?n.splice(r+1,0,` <!-- BLOCKED: ${t.blocker||t.note||"?"} -->`):t.note&&n.splice(r+1,0,` <!-- ${t.note.replace(/-->/g,"\u2014 >")} -->`);this.write(n)}},de=class{constructor(e,t){this.root=e;this.glob=t}type="wiki";capabilities={listAll:!1,create:!1,transition:!1,labels:!1,search:!1};walk(e,t){if(se(e))for(let r of Kt(e)){let n=zt(e,r);qt(n).isDirectory()?this.walk(n,t):r.endsWith(".md")&&t.push(n)}}async listOpen(e){let t=[];this.walk(oe(process.cwd(),this.root),t);let r=[];for(let n of t){let i=re(n,"utf-8").split(`
|
|
10
|
+
`);for(let o=0;o<i.length;o++){let a=i[o].match(ie);if(!a||a[2].toLowerCase()==="x")continue;let d=a[3].match(_);!d||d[1].toLowerCase()!==e.toLowerCase()||r.push({id:`wiki:${n}:${o}`,title:a[3].replace(_,"").trim(),assignee:d[1],priority:o})}}return r}async claim(){}async report(e,t){let r=e.split(":"),n=parseInt(r[r.length-1],10),i=r.slice(1,-1).join(":");if(!se(i))return;let o=re(i,"utf-8").split(`
|
|
11
|
+
`);t.status==="done"&&o[n]&&(o[n]=o[n].replace(/- \[ \]/,"- [x]"),We(i,o.join(`
|
|
12
|
+
`)))}},ce=class{constructor(e,t,r,n,i){this.host=e;this.token=t;this.projects=r;this.agentUsernames=n;this.log=i}type="gitlab";capabilities={listAll:!0,create:!0,transition:!0,labels:!0,search:!0};usernameToAgent(e){for(let[t,r]of Object.entries(this.agentUsernames))if(r.some(n=>n.toLowerCase()===e.toLowerCase()))return t}mapIssue(e,t){let r=e.references?.full?.split("#")[0]||t,n=[],i;Array.isArray(e.labels)&&(e.labels.length&&typeof e.labels[0]=="object"?(i=e.labels.map(c=>({name:c.name,color:c.color,text_color:c.text_color})),n=i.map(c=>c.name)):n=e.labels);let a=(Array.isArray(e.assignees)&&e.assignees.length?e.assignees:e.assignee?[e.assignee]:[]).map(c=>({username:c.username,name:c.name,avatarUrl:c.avatar_url})),d=a[0]?.username;return{id:`gitlab:${r}:issue:${e.iid}`,title:e.title,description:e.description||void 0,assignee:d?this.usernameToAgent(d)||d:void 0,assignees:a,url:e.web_url,priority:e.weight??99,estimatedSeconds:e.time_stats?.time_estimate||void 0,labels:n,labelDetails:i,updatedAt:e.updated_at,state:e.state==="closed"?"closed":"opened",milestone:e.milestone?.title||void 0}}async api(e,t={}){let r=await fetch(`${this.host}/api/v4${e}`,{...t,headers:{"PRIVATE-TOKEN":this.token,"Content-Type":"application/json",...t.headers||{}}});if(!r.ok)throw new Error(`GitLab ${r.status}: ${await r.text().catch(()=>"")}`);return r.json()}async listOpen(e){let t=this.agentUsernames[e]||[];if(!t.length)return[];let r=[],n=this.projects.length?this.projects:["__ALL__"];for(let i of t)for(let o of n)try{let a=`assignee_username=${encodeURIComponent(i)}&state=opened&scope=all&per_page=20`,d=o==="__ALL__"?`/issues?${a}`:`/projects/${encodeURIComponent(o)}/issues?${a}`,c=await this.api(d);for(let u of c){let p=this.mapIssue(u,o);p.assignee=e,r.push(p)}}catch(a){this.log(`[business] GitLab listOpen failed for ${i} on ${o}: ${a.message}`)}return r}async claim(e,t){let{project:r,iid:n,type:i}=this.parseId(t),o=`/projects/${encodeURIComponent(r)}/${i==="issue"?"issues":"merge_requests"}/${n}?add_labels=Doing&remove_labels=To Do`;try{await this.api(o,{method:"PUT"})}catch(a){this.log(`[business] claim label update failed: ${a.message}`)}}async report(e,t){let{project:r,iid:n,type:i}=this.parseId(e),o=i==="issue"?"issues":"merge_requests";if(t.timeSeconds&&t.timeSeconds>0){let a=Math.max(1,Math.round(t.timeSeconds/60)),d=a>=60?`${Math.floor(a/60)}h${a%60?` ${a%60}m`:""}`:`${a}m`;try{await this.api(`/projects/${encodeURIComponent(r)}/${o}/${n}/add_spent_time?duration=${encodeURIComponent(d)}`,{method:"POST"})}catch(c){this.log(`[business] add_spent_time failed: ${c.message}`)}}if(t.status==="done")try{await this.api(`/projects/${encodeURIComponent(r)}/${o}/${n}?add_labels=Done&remove_labels=Doing`,{method:"PUT"})}catch(a){this.log(`[business] done label update failed: ${a.message}`)}else if(t.status==="blocked")try{await this.api(`/projects/${encodeURIComponent(r)}/${o}/${n}?add_labels=Blocked`,{method:"PUT"})}catch(a){this.log(`[business] blocked label update failed: ${a.message}`)}if(t.note)try{await this.api(`/projects/${encodeURIComponent(r)}/${o}/${n}/notes`,{method:"POST",body:JSON.stringify({body:t.note})})}catch(a){this.log(`[business] report note failed: ${a.message}`)}}async listAll(e={}){let t=e.sinceDays??30,r=new Date(Date.now()-t*24*36e5).toISOString(),n=new URLSearchParams,i=e.state??"opened";n.set("state",i),n.set("updated_after",r),n.set("scope","all"),n.set("per_page","100"),n.set("with_labels_details","true"),n.set("order_by","updated_at"),n.set("sort","desc"),e.labels?.length&&n.set("labels",e.labels.join(",")),e.milestone&&n.set("milestone",e.milestone),e.search&&n.set("search",e.search);let o=this.projects.length?this.projects:["__ALL__"],a=[];for(let c of o)try{let u=c==="__ALL__"?`/issues?${n.toString()}`:`/projects/${encodeURIComponent(c)}/issues?${n.toString()}`,p=await this.api(u);for(let f of p)a.push(this.mapIssue(f,c))}catch(u){this.log(`[business] GitLab listAll failed on ${c}: ${u.message}`)}let d=a;if(e.notLabels?.length){let c=new Set(e.notLabels);d=d.filter(u=>!(u.labels||[]).some(p=>c.has(p)))}if(e.withoutScopedPrefix){let c=e.withoutScopedPrefix+"::";d=d.filter(u=>!(u.labels||[]).some(p=>p.startsWith(c)))}return d}async setState(e,t){let{project:r,iid:n,type:i}=this.parseId(e),o=i==="issue"?"issues":"merge_requests";await this.api(`/projects/${encodeURIComponent(r)}/${o}/${n}?state_event=${t}`,{method:"PUT"})}async getItem(e){let{project:t,iid:r}=this.parseId(e);try{let n=await this.api(`/projects/${encodeURIComponent(t)}/issues/${r}?with_labels_details=true`);return{...this.mapIssue(n,t),descriptionHtml:n.description_html||void 0,createdAt:n.created_at,author:n.author?{username:n.author.username,name:n.author.name,avatarUrl:n.author.avatar_url}:void 0,allLabels:Array.isArray(n.labels)?typeof n.labels[0]=="object"?n.labels.map(o=>o.name):n.labels:[]}}catch(n){return this.log(`[board] GitLab getItem failed for ${e}: ${n.message}`),null}}async updateItem(e,t){let{project:r,iid:n}=this.parseId(e),i;t.assigneeUsernames&&(i=(await Promise.all(t.assigneeUsernames.map(async u=>{try{let p=await this.api(`/users?username=${encodeURIComponent(u)}`);return Array.isArray(p)&&p[0]?.id?p[0].id:null}catch{return null}}))).filter(u=>typeof u=="number"));let o;if(t.milestoneTitle!==void 0)if(t.milestoneTitle===null||t.milestoneTitle==="")o=0;else try{let c=await this.api(`/projects/${encodeURIComponent(r)}/milestones?title=${encodeURIComponent(t.milestoneTitle)}`);Array.isArray(c)&&c[0]?.id&&(o=c[0].id)}catch(c){this.log(`[board] milestone lookup failed for "${t.milestoneTitle}": ${c.message}`)}let a={};t.title!==void 0&&(a.title=t.title),t.description!==void 0&&(a.description=t.description),t.labels!==void 0&&(a.labels=t.labels.join(",")),t.addLabels?.length&&(a.add_labels=t.addLabels.join(",")),t.removeLabels?.length&&(a.remove_labels=t.removeLabels.join(",")),i!==void 0&&(a.assignee_ids=i),o!==void 0&&(a.milestone_id=o);let d=await this.api(`/projects/${encodeURIComponent(r)}/issues/${n}`,{method:"PUT",body:JSON.stringify(a)});return this.mapIssue(d,r)}async listMembers(){let e=new Map;for(let t of this.projects)try{let r=await this.api(`/projects/${encodeURIComponent(t)}/members/all?per_page=100`);for(let n of r)n.username&&!e.has(n.username)&&e.set(n.username,{username:n.username,name:n.name,avatarUrl:n.avatar_url})}catch(r){this.log(`[board] listMembers failed for ${t}: ${r.message}`)}return[...e.values()].sort((t,r)=>t.username.localeCompare(r.username))}async listMilestones(){let e=new Map;for(let r of this.projects)try{let n=await this.api(`/projects/${encodeURIComponent(r)}/milestones?per_page=100`);for(let i of n)i.title&&!e.has(i.title)&&e.set(i.title,{id:i.id,title:i.title,state:i.state,dueDate:i.due_date})}catch(n){this.log(`[board] listMilestones failed for ${r}: ${n.message}`)}let t=[...e.values()];return t.sort((r,n)=>r.state!==n.state?r.state==="active"?-1:1:r.title.localeCompare(n.title)),t}async create(e){let t=e.projectHint||this.projects[0];if(!t)throw new Error("GitLabWorkSource.create requires projectHint or configured projects");let r,n=e.assigneeUsernames?.length?e.assigneeUsernames:e.assignee?(this.agentUsernames[e.assignee]||[]).slice(0,1):[];n.length&&(r=(await Promise.all(n.map(async c=>{try{let u=await this.api(`/users?username=${encodeURIComponent(c)}`);return Array.isArray(u)&&u[0]?.id?u[0].id:null}catch(u){return this.log(`[board] user lookup failed for ${c}: ${u.message}`),null}}))).filter(c=>typeof c=="number"));let i;if(e.milestoneTitle)try{let d=await this.api(`/projects/${encodeURIComponent(t)}/milestones?title=${encodeURIComponent(e.milestoneTitle)}`);Array.isArray(d)&&d[0]?.id&&(i=d[0].id)}catch(d){this.log(`[board] milestone lookup failed for "${e.milestoneTitle}": ${d.message}`)}let o={title:e.title,description:e.description,labels:(e.labels||[]).join(",")};r?.length&&(o.assignee_ids=r),i!==void 0&&(o.milestone_id=i);let a=await this.api(`/projects/${encodeURIComponent(t)}/issues`,{method:"POST",body:JSON.stringify(o)});return this.mapIssue(a,t)}async transition(e,t,r){await this.transitionMany(e,t?[t]:[],r?[r]:[])}async transitionMany(e,t,r){let{project:n,iid:i,type:o}=this.parseId(e),a=o==="issue"?"issues":"merge_requests",d=new URLSearchParams;t.length&&d.set("add_labels",t.join(",")),r.length&&d.set("remove_labels",r.join(",")),d.toString()&&await this.api(`/projects/${encodeURIComponent(n)}/${a}/${i}?${d.toString()}`,{method:"PUT"})}parseId(e){let r=e.replace(/^gitlab:/,"").split(":"),n=r.pop(),i=r.pop()==="mr"?"mr":"issue";return{project:r.join(":"),type:i,iid:n}}};function sr(s,e,t){let r=s.workSource;switch(r.type){case"backlog":return new ae(r.path);case"wiki":return new de(r.path,r.glob);case"gitlab":{let n=e.channels.gitlab;if(!n?.token)throw new Error("business.workSource.type=gitlab requires channels.gitlab.token");let i={};for(let a of n.agentMappings)a.gitlabUsernames.length&&(i[a.agentId]=a.gitlabUsernames);let o=r.projects.length?r.projects:n.routes.map(a=>a.project);return new ce(n.host,n.token,o,i,t)}}}export{S as a,H as b,q as c,B as d,G as e,ne as f,_t as g,Ut as h,ce as i,sr as j};
|
|
13
|
+
//# sourceMappingURL=chunk-IM376SD6.js.map
|