agentix-cli 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/{App-6Y7B3YQH.js → App-U52IPUK2.js} +2 -2
- package/dist/{ChatApp-KIEUJ2DS.js → ChatApp-XP4OMHPL.js} +2 -2
- package/dist/agent-V2CA4TXD.js +2 -0
- package/dist/article-fields-L4D2BOGY.js +2 -0
- package/dist/article-quality-44ONVGLN.js +3 -0
- package/dist/article-quality-44ONVGLN.js.map +1 -0
- package/dist/{audit-JU3HWISB.js → audit-4245CX6P.js} +2 -2
- package/dist/backfill-MTD2B62I.js +6 -0
- package/dist/backfill-MTD2B62I.js.map +1 -0
- package/dist/{board-dashboard-CQ5F4GPD.js → board-dashboard-TTMAYRLG.js} +2445 -519
- package/dist/board-dashboard-TTMAYRLG.js.map +1 -0
- package/dist/{builtin-FRKAQMFS.js → builtin-B67DMJ3M.js} +2 -2
- package/dist/calibration-ILS5OI44.js +2 -0
- package/dist/chunk-2T5W3ETS.js +5 -0
- package/dist/{chunk-FDMWOPEL.js.map → chunk-2T5W3ETS.js.map} +1 -1
- package/dist/chunk-3SCPRYJB.js +5 -0
- package/dist/chunk-3SCPRYJB.js.map +1 -0
- package/dist/chunk-4BR7D2OU.js +36 -0
- package/dist/chunk-4BR7D2OU.js.map +1 -0
- package/dist/{chunk-P4VWIVKN.js → chunk-52ZPELD2.js} +403 -29
- package/dist/chunk-52ZPELD2.js.map +1 -0
- package/dist/{chunk-5U35WAZF.js → chunk-77OV7346.js} +6 -6
- package/dist/chunk-77OV7346.js.map +1 -0
- package/dist/chunk-AJ5RMU7X.js +2 -0
- package/dist/chunk-AJ5RMU7X.js.map +1 -0
- package/dist/chunk-AJONZWLT.js +2 -0
- package/dist/chunk-AJONZWLT.js.map +1 -0
- package/dist/chunk-AX2PWSRT.js +114 -0
- package/dist/chunk-AX2PWSRT.js.map +1 -0
- package/dist/chunk-BH3GTCZ6.js +2 -0
- package/dist/chunk-BH3GTCZ6.js.map +1 -0
- package/dist/chunk-BPIRO4YD.js +126 -0
- package/dist/chunk-BPIRO4YD.js.map +1 -0
- package/dist/chunk-CJQQIRPD.js +13 -0
- package/dist/chunk-CJQQIRPD.js.map +1 -0
- package/dist/chunk-DCDQZR7I.js +24 -0
- package/dist/chunk-DCDQZR7I.js.map +1 -0
- package/dist/chunk-EVWEG3VD.js +3 -0
- package/dist/chunk-EVWEG3VD.js.map +1 -0
- package/dist/chunk-F4HZTZHW.js +2 -0
- package/dist/chunk-F4HZTZHW.js.map +1 -0
- package/dist/chunk-F6CNF7V3.js +25 -0
- package/dist/chunk-F6CNF7V3.js.map +1 -0
- package/dist/chunk-FIEJDZVC.js +121 -0
- package/dist/chunk-FIEJDZVC.js.map +1 -0
- package/dist/chunk-FIOB3MEX.js +2 -0
- package/dist/{chunk-H5D7ATXS.js → chunk-FKBGJYWQ.js} +5 -5
- package/dist/chunk-FKBGJYWQ.js.map +1 -0
- package/dist/chunk-GDWJUGB5.js +9 -0
- package/dist/chunk-GDWJUGB5.js.map +1 -0
- package/dist/{chunk-TRJTTBE2.js → chunk-IB3F7VBE.js} +7 -7
- package/dist/{chunk-TRJTTBE2.js.map → chunk-IB3F7VBE.js.map} +1 -1
- package/dist/chunk-IBUSGCLQ.js +109 -0
- package/dist/chunk-IBUSGCLQ.js.map +1 -0
- package/dist/{chunk-LDHPMJ7M.js → chunk-IWMW5432.js} +3 -3
- package/dist/chunk-IWMW5432.js.map +1 -0
- package/dist/chunk-K33GIBSE.js +2 -0
- package/dist/chunk-K33GIBSE.js.map +1 -0
- package/dist/chunk-L2IZPR6P.js +45 -0
- package/dist/chunk-L2IZPR6P.js.map +1 -0
- package/dist/chunk-L2WSPY6S.js +2 -0
- package/dist/chunk-L2WSPY6S.js.map +1 -0
- package/dist/chunk-LCIREYTD.js +54 -0
- package/dist/chunk-LCIREYTD.js.map +1 -0
- package/dist/chunk-LNRB5MCU.js +530 -0
- package/dist/chunk-LNRB5MCU.js.map +1 -0
- package/dist/chunk-OPHIXXGI.js +157 -0
- package/dist/chunk-OPHIXXGI.js.map +1 -0
- package/dist/{chunk-JTJIPNXY.js → chunk-PNWNVOEZ.js} +1 -1
- package/dist/chunk-PNWNVOEZ.js.map +1 -0
- package/dist/{chunk-LNXCJWOU.js → chunk-UFAGYX73.js} +45 -47
- package/dist/chunk-UFAGYX73.js.map +1 -0
- package/dist/{chunk-DJEZ2HVU.js → chunk-VDMYBPYB.js} +1 -1
- package/dist/chunk-VDMYBPYB.js.map +1 -0
- package/dist/chunk-VTE7L6LO.js +5 -0
- package/dist/{chunk-G57RZCXM.js.map → chunk-VTE7L6LO.js.map} +1 -1
- package/dist/chunk-W5C2GDNC.js +32 -0
- package/dist/{chunk-JP5IEXR7.js.map → chunk-W5C2GDNC.js.map} +1 -1
- package/dist/chunk-WPMX3VGI.js +3 -0
- package/dist/chunk-WPMX3VGI.js.map +1 -0
- package/dist/chunk-WZ72HX5C.js +1182 -0
- package/dist/chunk-WZ72HX5C.js.map +1 -0
- package/dist/chunk-ZTEU5JVQ.js +2 -0
- package/dist/chunk-ZTEU5JVQ.js.map +1 -0
- package/dist/{chunk-2WVX3LEC.js → chunk-ZVP6GB4G.js} +1 -1
- package/dist/chunk-ZVP6GB4G.js.map +1 -0
- package/dist/cli.js +1 -1
- package/dist/cli.js.map +1 -1
- package/dist/client-PX25MD47.js +2 -0
- package/dist/{compaction-NWEMOSHA.js → compaction-FD4R23HZ.js} +4 -4
- package/dist/config-HL5F36V4.js +2 -0
- package/dist/config-mutator-HBX53OMJ.js +2 -0
- package/dist/{conflict-detector-D7VIYPDI.js → conflict-detector-ZPOT3M2C.js} +2 -2
- package/dist/conflict-detector-ZPOT3M2C.js.map +1 -0
- package/dist/context-planner-AQHJMVY2.js +17 -0
- package/dist/context-planner-AQHJMVY2.js.map +1 -0
- package/dist/debug-3MEUO2P2.js +2 -0
- package/dist/{discover-XJXQXXIO.js → discover-D4VVRNAI.js} +2 -2
- package/dist/discover-D4VVRNAI.js.map +1 -0
- package/dist/doctor-PBAH6XL3.js +2 -0
- package/dist/editor-chat-OB7IQOB5.js +137 -0
- package/dist/editor-chat-OB7IQOB5.js.map +1 -0
- package/dist/entry-triage-LK34TSQD.js +2 -0
- package/dist/entry-triage-LK34TSQD.js.map +1 -0
- package/dist/facts-YVEJOM7O.js +22 -0
- package/dist/facts-YVEJOM7O.js.map +1 -0
- package/dist/heal-FVLBHYP7.js +2 -0
- package/dist/index.d.ts +1073 -65
- package/dist/index.js +13 -13
- package/dist/index.js.map +1 -1
- package/dist/{ingest-whatsapp-LZE62INA.js → ingest-whatsapp-NS4OWDOU.js} +2 -2
- package/dist/loader-4ZGTK7LD.js +2 -0
- package/dist/loader-BOKE5M5S.js +2 -0
- package/dist/memory-extract-F5ZHWY5E.js +2 -0
- package/dist/{migrate-v2-QO7SLKIM.js → migrate-v2-V4WN2VVM.js} +2 -2
- package/dist/ntfy-X7EPB3TG.js +4 -0
- package/dist/ntfy-X7EPB3TG.js.map +1 -0
- package/dist/processes-WU23U2OJ.js +2 -0
- package/dist/program-YGXVCV2Y.js +2 -0
- package/dist/{projects-api-KRME2YKZ.js → projects-api-QE6L65SK.js} +2 -2
- package/dist/{projects-mutate-3NHVG437.js → projects-mutate-MY7JTLSZ.js} +2 -2
- package/dist/providers-KO2DODIO.js +2 -0
- package/dist/query-ZKTFJMNE.js +2 -0
- package/dist/questions-4B4KSPXG.js +3 -0
- package/dist/questions-4B4KSPXG.js.map +1 -0
- package/dist/recipes-7S4MC5OJ.js +2 -0
- package/dist/registry-PSZDPFZK.js +2 -0
- package/dist/review-candidates-AEUXRQQC.js +4 -0
- package/dist/review-candidates-AEUXRQQC.js.map +1 -0
- package/dist/review-store-B4YE3EFK.js +4 -0
- package/dist/review-store-B4YE3EFK.js.map +1 -0
- package/dist/{rotation-memo-B3EXXSEH.js → rotation-memo-7T75BLEX.js} +2 -2
- package/dist/routing-4PV66BK6.js +2 -0
- package/dist/routing-4PV66BK6.js.map +1 -0
- package/dist/runner-YRBVNYTY.js +2 -0
- package/dist/scheduler-GZZAN6ZG.js +2 -0
- package/dist/seat-VZ7AVO27.js +2 -0
- package/dist/select-F5I5PO2O.js +2 -0
- package/dist/select-F5I5PO2O.js.map +1 -0
- package/dist/store-MCOA6N4F.js +2 -0
- package/dist/{subagent-WV7QKQ3L.js → subagent-2SJHSSJS.js} +2 -2
- package/dist/{surface-usage-PRL6Z4U3.js → surface-usage-NS74AXSE.js} +2 -2
- package/dist/timers-PCB4ZIBD.js +2 -0
- package/dist/triage-backtest-JNO7RKX7.js +2 -0
- package/dist/triage-backtest-JNO7RKX7.js.map +1 -0
- package/dist/types-H5ENIUIT.js +2 -0
- package/dist/types-OIFU3KSQ.js +2 -0
- package/dist/web/workflow-editor.global.js +1542 -0
- package/dist/web/workflow-editor.global.js.map +1 -0
- package/dist/{whatsapp-state-XMTCSRXU.js → whatsapp-state-6D6FHHD2.js} +2 -2
- package/dist/wiki-N5SMP3DT.js +2 -0
- package/dist/wiki-N5SMP3DT.js.map +1 -0
- package/dist/workflows/templates/branching.yaml +1 -1
- package/dist/{workspace-setup-TBQAHYHJ.js → workspace-setup-B3F3TUDE.js} +2 -2
- package/dist/workspace-setup-B3F3TUDE.js.map +1 -0
- package/package.json +1 -1
- package/dist/agent-QGZIAZMY.js +0 -2
- package/dist/architect-SEPWMWS4.js +0 -2
- package/dist/board-dashboard-CQ5F4GPD.js.map +0 -1
- package/dist/chunk-2WVX3LEC.js.map +0 -1
- package/dist/chunk-3WQFCIB6.js +0 -24
- package/dist/chunk-3WQFCIB6.js.map +0 -1
- package/dist/chunk-4YCH6IZV.js +0 -2
- package/dist/chunk-53GCFZPR.js +0 -116
- package/dist/chunk-53GCFZPR.js.map +0 -1
- package/dist/chunk-53NRX2NA.js +0 -2
- package/dist/chunk-53NRX2NA.js.map +0 -1
- package/dist/chunk-5U35WAZF.js.map +0 -1
- package/dist/chunk-BFXONUUN.js +0 -108
- package/dist/chunk-BFXONUUN.js.map +0 -1
- package/dist/chunk-BFZUG66X.js +0 -45
- package/dist/chunk-BFZUG66X.js.map +0 -1
- package/dist/chunk-DJEZ2HVU.js.map +0 -1
- package/dist/chunk-FDMWOPEL.js +0 -5
- package/dist/chunk-FDWOECJY.js +0 -54
- package/dist/chunk-FDWOECJY.js.map +0 -1
- package/dist/chunk-G57RZCXM.js +0 -5
- package/dist/chunk-H5D7ATXS.js.map +0 -1
- package/dist/chunk-HA3XI7Z2.js +0 -155
- package/dist/chunk-HA3XI7Z2.js.map +0 -1
- package/dist/chunk-HR4JSRZS.js +0 -3
- package/dist/chunk-HR4JSRZS.js.map +0 -1
- package/dist/chunk-ISEBLHWX.js +0 -118
- package/dist/chunk-ISEBLHWX.js.map +0 -1
- package/dist/chunk-JP5IEXR7.js +0 -32
- package/dist/chunk-JTJIPNXY.js.map +0 -1
- package/dist/chunk-JXWAGMI2.js +0 -13
- package/dist/chunk-JXWAGMI2.js.map +0 -1
- package/dist/chunk-KUCCW2VG.js +0 -431
- package/dist/chunk-KUCCW2VG.js.map +0 -1
- package/dist/chunk-LDHPMJ7M.js.map +0 -1
- package/dist/chunk-LNXCJWOU.js.map +0 -1
- package/dist/chunk-NGSIRE4X.js +0 -126
- package/dist/chunk-NGSIRE4X.js.map +0 -1
- package/dist/chunk-P4VWIVKN.js.map +0 -1
- package/dist/chunk-Q6YO7LYE.js +0 -47
- package/dist/chunk-Q6YO7LYE.js.map +0 -1
- package/dist/chunk-SR3X25TN.js +0 -3
- package/dist/chunk-SR3X25TN.js.map +0 -1
- package/dist/chunk-TMKLHTCG.js +0 -971
- package/dist/chunk-TMKLHTCG.js.map +0 -1
- package/dist/chunk-VJUTQAPJ.js +0 -2
- package/dist/chunk-VJUTQAPJ.js.map +0 -1
- package/dist/client-GGVVLOOF.js +0 -2
- package/dist/config-7FSZFRTQ.js +0 -2
- package/dist/config-mutator-POCOYYJ4.js +0 -2
- package/dist/conflict-detector-D7VIYPDI.js.map +0 -1
- package/dist/context-planner-45GEWKGL.js +0 -17
- package/dist/context-planner-45GEWKGL.js.map +0 -1
- package/dist/debug-N3B5NVJU.js +0 -2
- package/dist/discover-XJXQXXIO.js.map +0 -1
- package/dist/doctor-DHLBEKA4.js +0 -2
- package/dist/heal-HZ3EQD52.js +0 -2
- package/dist/loader-5CA3O5GM.js +0 -2
- package/dist/loader-6IHUCPVO.js +0 -2
- package/dist/memory-extract-AENFVVO5.js +0 -2
- package/dist/processes-UIC4XXA6.js +0 -2
- package/dist/program-WVAVJFND.js +0 -2
- package/dist/providers-L27BJ5A6.js +0 -2
- package/dist/query-HZYYRCLU.js +0 -2
- package/dist/recipes-EPLE2SNT.js +0 -2
- package/dist/registry-T36ZIAWV.js +0 -2
- package/dist/runner-BFYDWPUO.js +0 -2
- package/dist/scheduler-GH7TIKYS.js +0 -2
- package/dist/store-I3HEWVLQ.js +0 -2
- package/dist/timers-5LPZN55J.js +0 -2
- package/dist/types-VECTMYTO.js +0 -2
- package/dist/types-Y6ZCZ52Z.js +0 -2
- package/dist/wiki-M6YNRFC4.js +0 -2
- /package/dist/{App-6Y7B3YQH.js.map → App-U52IPUK2.js.map} +0 -0
- /package/dist/{ChatApp-KIEUJ2DS.js.map → ChatApp-XP4OMHPL.js.map} +0 -0
- /package/dist/{agent-QGZIAZMY.js.map → agent-V2CA4TXD.js.map} +0 -0
- /package/dist/{architect-SEPWMWS4.js.map → article-fields-L4D2BOGY.js.map} +0 -0
- /package/dist/{audit-JU3HWISB.js.map → audit-4245CX6P.js.map} +0 -0
- /package/dist/{builtin-FRKAQMFS.js.map → builtin-B67DMJ3M.js.map} +0 -0
- /package/dist/{chunk-4YCH6IZV.js.map → calibration-ILS5OI44.js.map} +0 -0
- /package/dist/{client-GGVVLOOF.js.map → chunk-FIOB3MEX.js.map} +0 -0
- /package/dist/{config-7FSZFRTQ.js.map → client-PX25MD47.js.map} +0 -0
- /package/dist/{compaction-NWEMOSHA.js.map → compaction-FD4R23HZ.js.map} +0 -0
- /package/dist/{config-mutator-POCOYYJ4.js.map → config-HL5F36V4.js.map} +0 -0
- /package/dist/{debug-N3B5NVJU.js.map → config-mutator-HBX53OMJ.js.map} +0 -0
- /package/dist/{doctor-DHLBEKA4.js.map → debug-3MEUO2P2.js.map} +0 -0
- /package/dist/{heal-HZ3EQD52.js.map → doctor-PBAH6XL3.js.map} +0 -0
- /package/dist/{loader-5CA3O5GM.js.map → heal-FVLBHYP7.js.map} +0 -0
- /package/dist/{ingest-whatsapp-LZE62INA.js.map → ingest-whatsapp-NS4OWDOU.js.map} +0 -0
- /package/dist/{loader-6IHUCPVO.js.map → loader-4ZGTK7LD.js.map} +0 -0
- /package/dist/{memory-extract-AENFVVO5.js.map → loader-BOKE5M5S.js.map} +0 -0
- /package/dist/{processes-UIC4XXA6.js.map → memory-extract-F5ZHWY5E.js.map} +0 -0
- /package/dist/{migrate-v2-QO7SLKIM.js.map → migrate-v2-V4WN2VVM.js.map} +0 -0
- /package/dist/{program-WVAVJFND.js.map → processes-WU23U2OJ.js.map} +0 -0
- /package/dist/{providers-L27BJ5A6.js.map → program-YGXVCV2Y.js.map} +0 -0
- /package/dist/{projects-api-KRME2YKZ.js.map → projects-api-QE6L65SK.js.map} +0 -0
- /package/dist/{projects-mutate-3NHVG437.js.map → projects-mutate-MY7JTLSZ.js.map} +0 -0
- /package/dist/{query-HZYYRCLU.js.map → providers-KO2DODIO.js.map} +0 -0
- /package/dist/{recipes-EPLE2SNT.js.map → query-ZKTFJMNE.js.map} +0 -0
- /package/dist/{registry-T36ZIAWV.js.map → recipes-7S4MC5OJ.js.map} +0 -0
- /package/dist/{runner-BFYDWPUO.js.map → registry-PSZDPFZK.js.map} +0 -0
- /package/dist/{rotation-memo-B3EXXSEH.js.map → rotation-memo-7T75BLEX.js.map} +0 -0
- /package/dist/{scheduler-GH7TIKYS.js.map → runner-YRBVNYTY.js.map} +0 -0
- /package/dist/{store-I3HEWVLQ.js.map → scheduler-GZZAN6ZG.js.map} +0 -0
- /package/dist/{surface-usage-PRL6Z4U3.js.map → seat-VZ7AVO27.js.map} +0 -0
- /package/dist/{timers-5LPZN55J.js.map → store-MCOA6N4F.js.map} +0 -0
- /package/dist/{subagent-WV7QKQ3L.js.map → subagent-2SJHSSJS.js.map} +0 -0
- /package/dist/{types-VECTMYTO.js.map → surface-usage-NS74AXSE.js.map} +0 -0
- /package/dist/{types-Y6ZCZ52Z.js.map → timers-PCB4ZIBD.js.map} +0 -0
- /package/dist/{wiki-M6YNRFC4.js.map → types-H5ENIUIT.js.map} +0 -0
- /package/dist/{workspace-setup-TBQAHYHJ.js.map → types-OIFU3KSQ.js.map} +0 -0
- /package/dist/{whatsapp-state-XMTCSRXU.js.map → whatsapp-state-6D6FHHD2.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/projects/rules.ts"],"sourcesContent":["// --- Per-project webhook rules + runbook reference ---\n//\n// Project rules live in `.agentx/projects/<org>/<project>.yaml` (or any\n// nested path; the loader walks the tree). Each file declares which\n// channel events for that project may reach an agent and where the\n// project's runbook (CLAUDE.md / AGENTS.md / DEPLOY.md / RUNBOOK.md)\n// lives so the registry can inject it into the agent's system prefix.\n//\n// The store is loaded at daemon boot and watched for changes — write a\n// file, save, and the next webhook event picks up the new rules without\n// a daemon restart. Validation errors are logged and the offending file\n// is skipped (other rules keep working).\n//\n// Why per-file: agencies grow projects faster than they touch agentx\n// internals; a single project.yaml would force coordination on every\n// edit. One file per project lets each project owner edit their own\n// rules without merge conflicts.\n//\n// Schema is intentionally narrow:\n// - issue.actions / pull_request.actions / issues.actions: action allowlist\n// - requireLabels: at least one must be present (OR-of). Empty/absent = no\n// label requirement.\n// - excludeLabels: any present blocks the event.\n// - excludeStates: states that always block (e.g. \"closed\").\n// - excludeAuthors: handle prefixes that always block (bots).\n// - note.triggers: at least one must match for a comment to fire.\n// - runbook: filesystem path; the registry reads CLAUDE.md / AGENTS.md /\n// DEPLOY.md / RUNBOOK.md from this path (extensible via `runbookFiles`).\n//\n// Anything not listed by a rule passes through with the legacy default\n// (existing adapter behaviour). Rules opt INTO filtering, never opt out\n// of an event the adapter already drops.\n\nimport { existsSync, readdirSync, readFileSync, statSync, watch } from \"fs\"\nimport type { FSWatcher } from \"fs\"\nimport { extname, join, resolve } from \"path\"\nimport yaml from \"js-yaml\"\n\nexport type Channel = \"gitlab\" | \"github\"\n\n/** Declarative source of a project. Drives the Projects-page UI badge\n * and indicates which channel adapters are expected to carry events\n * for it. `gitlab` and `github` have rule clauses + live adapters;\n * `jira` and `linear` are accepted as values for tracking purposes\n * even though no adapter exists yet (events from those sources can't\n * fire workflows today). `other` is the catch-all for projects that\n * exist organisationally but aren't wired to any source. */\nexport type ProjectKind = \"gitlab\" | \"github\" | \"jira\" | \"linear\" | \"other\"\n\n/** Default runbook filenames read into the agent system prefix. Order\n * preserved so CLAUDE.md (claude convention) lands before AGENTS.md\n * (codex convention) — agents see Claude rules first. Operator can\n * override per-project via `runbookFiles`. */\nexport const DEFAULT_RUNBOOK_FILES = [\"CLAUDE.md\", \"AGENTS.md\", \"DEPLOY.md\", \"RUNBOOK.md\"] as const\n\nexport interface IssueRule {\n /** Allowed actions. Empty/absent = no action restriction. */\n actions?: string[]\n /** Issue must carry at least one of these labels. Empty/absent = no req. */\n requireLabels?: string[]\n /** Any of these labels present blocks the event. */\n excludeLabels?: string[]\n /** States that always block (gitlab: opened|closed|reopened, github: open|closed). */\n excludeStates?: string[]\n /** Username substrings/equals that always block — used for bot exclusion. */\n excludeAuthors?: string[]\n}\n\nexport interface NoteTrigger {\n /** Comment must contain this @mention to fire. Case-insensitive. */\n mention?: string\n /** Comment must contain this keyword (case-insensitive substring). */\n keyword?: string\n}\n\n/** Sentinel string that tells the matcher to accept any @-mention that\n * resolves to a configured agent (per `channels.gitlab.agentMappings`).\n * Lets a project rule say \"fire on any agent mention\" without manually\n * enumerating every handle — the canonical list lives in agentMappings.\n * Use it as a string entry in `triggers`:\n * triggers: [auto, { keyword: \"merge and deploy\" }]\n * When the channel adapter doesn't supply `knownAgentMentions`, an\n * `auto` entry is treated as a no-op (it does not block the rule). */\nexport type NoteTriggerEntry = NoteTrigger | \"auto\"\n\nexport interface NoteRule {\n /** Restrict to certain noteable types — gitlab: issue|merge_request|commit|snippet. */\n onlyOn?: string[]\n /** At least one trigger must match the comment text for the event to fire.\n * Mix explicit objects with the `\"auto\"` sentinel to delegate mention\n * enumeration to the channel's agentMappings. */\n triggers?: NoteTriggerEntry[]\n /** Username substrings/equals that always block. */\n excludeAuthors?: string[]\n}\n\nexport interface PipelineRule {\n /** Allowed pipeline statuses (success|failed|canceled|...). */\n actions?: string[]\n}\n\nexport interface ProjectRule {\n /** Project path. GitLab: `org/repo`. GitHub: `owner/repo`. Required. */\n project: string\n channel?: Channel\n /** Declarative source of this project. Optional. When unset, the\n * loader infers from clauses (presence of `gitlab:` → \"gitlab\",\n * `github:` → \"github\", neither → \"other\"). Set explicitly to track\n * jira / linear / other-source projects that don't have rule\n * clauses yet. */\n kind?: ProjectKind\n /** Human-friendly project name shown on the Projects page header. */\n displayName?: string\n /** External URL — opens the project's home (GitLab repo, GitHub repo,\n * Jira board, etc.). Rendered as a link on the project header. */\n homeUrl?: string\n /** Contact ids from `.agentx/contacts.json` associated with this\n * project. The Projects page uses these to populate the Contacts\n * section in addition to contacts that carry their own `project`\n * field. Either side may declare the link — both render. */\n contacts?: string[]\n /** Default agent for this project. Overrides channel-level resolveAgent\n * when set. Optional — leaving unset preserves existing behaviour. */\n agent?: string\n /** Filesystem path to the project root (where CLAUDE.md/AGENTS.md live).\n * When set, the registry injects those files into the agent system\n * prefix for tasks scoped to this project. */\n runbook?: string\n /** Override the default runbook file set (DEFAULT_RUNBOOK_FILES). */\n runbookFiles?: string[]\n gitlab?: {\n issue?: IssueRule\n /** Same matcher shape as issue — actions/requireLabels/excludeLabels/\n * excludeStates/excludeAuthors. GitLab MR `action` values: open,\n * reopen, update, close, approved, unapproved, approval, unapproval,\n * merge. Most W3-style review automations want `[open, reopen, update]`\n * only, with `excludeAuthors: [bot, noqta-]` to ignore agent-driven\n * MR updates. */\n merge_request?: IssueRule\n note?: NoteRule\n pipeline?: PipelineRule\n }\n github?: {\n /** GitHub uses `issues` for issue events and `pull_request` for PRs. */\n issues?: IssueRule\n pull_request?: IssueRule\n push?: { branches?: string[] }\n }\n /** Free-form notes for the operator. Ignored by the loader. */\n description?: string\n /** Internal: file path the rule was loaded from, for diagnostics. */\n _path?: string\n}\n\nexport interface FilterDecision {\n allow: boolean\n /** Reason for the decision — surfaced in logs so operators can see why\n * an event was dropped. Always present when allow=false. */\n reason?: string\n}\n\nexport class ProjectRulesStore {\n private byProject = new Map<string, ProjectRule>()\n private dir: string\n private log: (...args: unknown[]) => void\n private watcher?: FSWatcher\n private debounce?: ReturnType<typeof setTimeout>\n private loadErrors: Array<{ file: string; error: string }> = []\n\n constructor(dir: string, log: (...args: unknown[]) => void = console.error.bind(console, \"[projects]\")) {\n this.dir = dir\n this.log = log\n }\n\n load(): { count: number; errors: number } {\n this.byProject.clear()\n this.loadErrors = []\n if (!existsSync(this.dir)) {\n return { count: 0, errors: 0 }\n }\n for (const file of this.walk(this.dir)) {\n try {\n const raw = readFileSync(file, \"utf-8\")\n const parsed = yaml.load(raw) as unknown\n const rule = this.validate(parsed, file)\n if (!rule) continue\n rule._path = file\n if (this.byProject.has(rule.project)) {\n this.log(`duplicate project \"${rule.project}\" — keeping ${this.byProject.get(rule.project)!._path}, skipping ${file}`)\n continue\n }\n this.byProject.set(rule.project, rule)\n } catch (e: any) {\n this.loadErrors.push({ file, error: e?.message ?? String(e) })\n this.log(`failed to load ${file}: ${e?.message ?? e}`)\n }\n }\n return { count: this.byProject.size, errors: this.loadErrors.length }\n }\n\n startWatching(onReload?: () => void): void {\n if (!existsSync(this.dir)) return\n try {\n this.watcher = watch(this.dir, { recursive: true }, () => {\n if (this.debounce) clearTimeout(this.debounce)\n this.debounce = setTimeout(() => {\n const result = this.load()\n this.log(`rules reloaded: ${result.count} project(s), ${result.errors} error(s)`)\n onReload?.()\n }, 200)\n })\n } catch (e: any) {\n this.log(`watcher unavailable (${e.message}) — rules will not hot-reload`)\n }\n }\n\n stop(): void {\n this.watcher?.close()\n if (this.debounce) clearTimeout(this.debounce)\n }\n\n /** Look up a rule by project path. Returns undefined if no rule\n * exists — callers must treat that as \"no filter, legacy behaviour\". */\n find(project: string): ProjectRule | undefined {\n return this.byProject.get(project)\n }\n\n list(): ProjectRule[] {\n return Array.from(this.byProject.values())\n }\n\n /** Resolve a project's `kind` even when not set on the rule file. The\n * loader-side default is intentionally absent — we infer at read time\n * so an operator who flips a `gitlab:` clause to `github:` doesn't\n * also have to remember to update `kind:`. */\n static inferKind(rule: ProjectRule): ProjectKind {\n if (rule.kind) return rule.kind\n if (rule.gitlab) return \"gitlab\"\n if (rule.github) return \"github\"\n return \"other\"\n }\n\n health(): { count: number; errors: Array<{ file: string; error: string }> } {\n return { count: this.byProject.size, errors: [...this.loadErrors] }\n }\n\n // --- Filter decisions ---\n\n /** Should a GitLab issue event reach an agent? */\n shouldFireGitlabIssue(project: string, payload: {\n action?: string\n state?: string\n labels?: string[]\n authorUsername?: string\n }): FilterDecision {\n const rule = this.find(project)?.gitlab?.issue\n if (!rule) return { allow: true }\n return matchIssueRule(rule, payload, \"gitlab\")\n }\n\n /** Should a GitLab MR-or-issue note event reach an agent?\n * `opts.knownAgentMentions` carries the @-handle list resolved by the\n * channel adapter from `agentMappings.gitlabUsernames`. When the rule\n * has an `\"auto\"` trigger entry, the matcher accepts any of these\n * mentions found in the comment text — single source of truth, no\n * manual sync required between rule files and agentMappings. */\n shouldFireGitlabNote(project: string, payload: {\n noteableType?: string\n text?: string\n authorUsername?: string\n }, opts?: { knownAgentMentions?: string[] }): FilterDecision {\n const rule = this.find(project)?.gitlab?.note\n if (!rule) return { allow: true }\n return matchNoteRule(rule, payload, opts?.knownAgentMentions)\n }\n\n /** Should a GitLab MR event reach an agent? Same matcher as issue —\n * filter on action / labels / state / author. */\n shouldFireGitlabMR(project: string, payload: {\n action?: string\n state?: string\n labels?: string[]\n authorUsername?: string\n }): FilterDecision {\n const rule = this.find(project)?.gitlab?.merge_request\n if (!rule) return { allow: true }\n return matchIssueRule(rule, payload, \"gitlab\")\n }\n\n /** Should a GitLab pipeline event reach an agent? */\n shouldFireGitlabPipeline(project: string, payload: { status?: string }): FilterDecision {\n const rule = this.find(project)?.gitlab?.pipeline\n if (!rule) return { allow: true }\n if (rule.actions && rule.actions.length > 0 && payload.status && !rule.actions.includes(payload.status)) {\n return { allow: false, reason: `pipeline status=\"${payload.status}\" not in actions=${JSON.stringify(rule.actions)}` }\n }\n return { allow: true }\n }\n\n /** Should a GitHub issues event reach an agent? */\n shouldFireGithubIssue(project: string, payload: {\n action?: string\n state?: string\n labels?: string[]\n authorUsername?: string\n }): FilterDecision {\n const rule = this.find(project)?.github?.issues\n if (!rule) return { allow: true }\n return matchIssueRule(rule, payload, \"github\")\n }\n\n /** Should a GitHub pull_request event reach an agent? */\n shouldFireGithubPR(project: string, payload: {\n action?: string\n state?: string\n labels?: string[]\n authorUsername?: string\n }): FilterDecision {\n const rule = this.find(project)?.github?.pull_request\n if (!rule) return { allow: true }\n return matchIssueRule(rule, payload, \"github\")\n }\n\n // --- Internals ---\n\n private *walk(dir: string): Generator<string> {\n let entries: string[]\n try { entries = readdirSync(dir) } catch { return }\n for (const name of entries) {\n const full = join(dir, name)\n let stat\n try { stat = statSync(full) } catch { continue }\n if (stat.isDirectory()) {\n yield* this.walk(full)\n continue\n }\n const ext = extname(name).toLowerCase()\n if (ext === \".yaml\" || ext === \".yml\") yield full\n }\n }\n\n private validate(raw: unknown, file: string): ProjectRule | null {\n if (!raw || typeof raw !== \"object\") {\n throw new Error(`${file}: top-level must be an object`)\n }\n const r = raw as Record<string, unknown>\n if (typeof r.project !== \"string\" || !r.project.includes(\"/\")) {\n throw new Error(`${file}: \\`project\\` must be a string of the form \"org/repo\"`)\n }\n if (r.runbook !== undefined && typeof r.runbook !== \"string\") {\n throw new Error(`${file}: \\`runbook\\` must be a string path`)\n }\n if (r.runbook && !resolve(r.runbook as string)) {\n // resolve never returns falsy — kept for symmetry / future expansion.\n }\n return raw as ProjectRule\n }\n}\n\nfunction matchIssueRule(rule: IssueRule, payload: {\n action?: string\n state?: string\n labels?: string[]\n authorUsername?: string\n}, kind: Channel): FilterDecision {\n const labels = payload.labels ?? []\n const author = payload.authorUsername ?? \"\"\n if (rule.actions && rule.actions.length > 0) {\n if (!payload.action || !rule.actions.includes(payload.action)) {\n return { allow: false, reason: `${kind} issue action=\"${payload.action}\" not in actions=${JSON.stringify(rule.actions)}` }\n }\n }\n if (rule.excludeStates && payload.state && rule.excludeStates.includes(payload.state)) {\n return { allow: false, reason: `${kind} issue state=\"${payload.state}\" in excludeStates` }\n }\n if (rule.requireLabels && rule.requireLabels.length > 0) {\n const has = rule.requireLabels.some((req) => labels.includes(req))\n if (!has) {\n return { allow: false, reason: `${kind} issue missing requireLabels=${JSON.stringify(rule.requireLabels)} (have ${JSON.stringify(labels)})` }\n }\n }\n if (rule.excludeLabels && rule.excludeLabels.length > 0) {\n const hit = rule.excludeLabels.find((bad) => labels.includes(bad))\n if (hit) {\n return { allow: false, reason: `${kind} issue carries excluded label \"${hit}\"` }\n }\n }\n if (rule.excludeAuthors && rule.excludeAuthors.length > 0) {\n const hit = rule.excludeAuthors.find((bad) => author.toLowerCase().includes(bad.toLowerCase()))\n if (hit) {\n return { allow: false, reason: `${kind} issue authored by \"${author}\" matched excludeAuthors entry \"${hit}\"` }\n }\n }\n return { allow: true }\n}\n\nfunction matchNoteRule(rule: NoteRule, payload: {\n noteableType?: string\n text?: string\n authorUsername?: string\n}, knownAgentMentions?: string[]): FilterDecision {\n const text = (payload.text ?? \"\").toLowerCase()\n const author = payload.authorUsername ?? \"\"\n if (rule.onlyOn && rule.onlyOn.length > 0) {\n if (!payload.noteableType || !rule.onlyOn.includes(payload.noteableType)) {\n return { allow: false, reason: `note noteableType=\"${payload.noteableType}\" not in onlyOn=${JSON.stringify(rule.onlyOn)}` }\n }\n }\n if (rule.excludeAuthors && rule.excludeAuthors.length > 0) {\n const hit = rule.excludeAuthors.find((bad) => author.toLowerCase().includes(bad.toLowerCase()))\n if (hit) {\n return { allow: false, reason: `note authored by \"${author}\" matched excludeAuthors entry \"${hit}\"` }\n }\n }\n if (rule.triggers && rule.triggers.length > 0) {\n const matched = rule.triggers.some((t) => {\n // \"auto\" sentinel — matches any of the agent mentions resolved by the\n // adapter from agentMappings.gitlabUsernames. When the adapter doesn't\n // supply the list (or it's empty), `auto` neither matches nor blocks\n // — fall through to the next trigger entry.\n if (t === \"auto\") {\n if (!knownAgentMentions || knownAgentMentions.length === 0) return false\n return knownAgentMentions.some((m) => text.includes(m.toLowerCase()))\n }\n if (t.mention) {\n const m = t.mention.toLowerCase()\n if (text.includes(m)) return true\n }\n if (t.keyword) {\n if (text.includes(t.keyword.toLowerCase())) return true\n }\n return false\n })\n if (!matched) {\n return { allow: false, reason: `note text matched no trigger` }\n }\n }\n return { allow: true }\n}\n"],"mappings":"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","names":["existsSync","readdirSync","readFileSync","statSync","watch","extname","join","resolve","yaml","ProjectRulesStore","dir","log","existsSync","file","raw","readFileSync","parsed","yaml","rule","e","onReload","watch","result","project","payload","matchIssueRule","opts","matchNoteRule","entries","readdirSync","name","full","join","stat","statSync","ext","extname","resolve","kind","labels","author","req","hit","bad","knownAgentMentions","text","t","m"]}
|
package/dist/chunk-3WQFCIB6.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import{readFileSync as T,writeFileSync as J,existsSync as L,readdirSync as oe,statSync as ae}from"fs";import{resolve as C,join as le}from"path";import{existsSync as N,mkdirSync as Z,readFileSync as Q,writeFileSync as H}from"fs";import{dirname as ee,resolve as K}from"path";import{z as f}from"zod";var te=f.object({type:f.enum(["gitlab","github"]),host:f.string().optional(),project:f.string(),iid:f.number().int().positive(),url:f.string()}),se=f.object({id:f.string(),title:f.string(),description:f.string().optional(),assignee:f.string().optional(),labels:f.array(f.string()).default([]),milestone:f.string().optional(),status:f.enum(["todo","doing","blocked","done"]).default("todo"),priority:f.number().int().optional(),estimatedSeconds:f.number().int().optional(),source:te.optional(),importedAt:f.string().optional(),createdAt:f.string(),updatedAt:f.string(),blocker:f.string().optional(),doneNote:f.string().optional()}),ne=f.object({version:f.literal(1),items:f.array(se).default([])}),j=class{constructor(e){this.path=e}jsonPath(){return K(process.cwd(),this.path).replace(/\.md$/i,".json")}mdPath(){return K(process.cwd(),this.path).replace(/\.json$/i,".md")}exists(){return N(this.jsonPath())}load(){let e=this.jsonPath();if(!N(e))return{version:1,items:[]};let t=Q(e,"utf-8");try{return ne.parse(JSON.parse(t))}catch(s){throw new Error(`Backlog file invalid (${e}): ${s.message}`)}}save(e){let t=this.jsonPath();Z(ee(t),{recursive:!0}),H(t,JSON.stringify(e,null,2)+`
|
|
2
|
-
`),H(this.mdPath(),this.toMarkdown(e))}list(){return this.load().items}findById(e){return this.list().find(t=>t.id===e)}add(e){let t=this.load();if(t.items.some(r=>r.id===e.id))throw new Error(`backlog item ${e.id} already exists`);let s=new Date().toISOString(),n={...e,labels:e.labels??[],status:e.status??"todo",createdAt:e.createdAt??s,updatedAt:s};return t.items.push(n),this.save(t),n}addMany(e){let t=this.load(),s=new Set(t.items.map(o=>o.id)),n=new Date().toISOString(),r=[];for(let o of e){if(s.has(o.id))continue;let i={...o,labels:o.labels??[],status:o.status??"todo",createdAt:o.createdAt??n,updatedAt:n};t.items.push(i),r.push(i),s.add(i.id)}return r.length&&this.save(t),r}update(e,t){let s=this.load(),n=s.items.findIndex(o=>o.id===e);if(n<0)throw new Error(`backlog item ${e} not found`);let r={...s.items[n],...t,id:s.items[n].id,createdAt:s.items[n].createdAt,updatedAt:new Date().toISOString()};return s.items[n]=r,this.save(s),r}remove(e){let t=this.load(),s=t.items.length;return t.items=t.items.filter(n=>n.id!==e),t.items.length===s?!1:(this.save(t),!0)}toMarkdown(e){let t=["# Backlog","","_Generated from `backlog.json`. Do not edit by hand \u2014 use `agentx backlog` commands._",""],s={doing:[],blocked:[],todo:[],done:[]};for(let r of e.items)s[r.status].push(r);let n=[["doing","Doing"],["blocked","Blocked"],["todo","To Do"],["done","Done"]];for(let[r,o]of n)if(s[r].length){t.push(`## ${o}`,"");for(let i of s[r]){let c=r==="done"?"x":" ",a=i.assignee?`@${i.assignee} `:"",d=i.estimatedSeconds?` [time: ${Math.round(i.estimatedSeconds/60)}m]`:"",g=i.source?` (${i.source.type}: ${i.source.project}#${i.source.iid})`:"";t.push(`- [${c}] ${a}${i.title}${d}${g}`),i.source?.url&&t.push(` - <${i.source.url}>`),i.labels.length&&t.push(` - labels: ${i.labels.join(", ")}`),i.milestone&&t.push(` - milestone: ${i.milestone}`),i.blocker&&t.push(` - BLOCKED: ${i.blocker}`)}t.push("")}return t.join(`
|
|
3
|
-
`)}};function ke(l){return`${l.type}:${l.project}:${l.iid}`}function q(l,e,t){let s=[];if(e.type==="gitlab"){let n=t.channels.gitlab?.agentMappings??[];for(let r of l){let o=n.find(i=>i.agentId===r);o?.gitlabUsernames?.length?s.push(o.gitlabUsernames[0]):s.push(r)}}else if(e.type==="github"){let n=t.channels.github?.agentMappings??[];for(let r of l){let o=n.find(i=>i.agentId===r);o?.githubUsernames?.length?s.push(o.githubUsernames[0]):s.push(r)}}return s}async function _(l,e,t,s=()=>{}){return l.source?l.source.type==="gitlab"?ie(l.source,e,t,s):l.source.type==="github"?re(l.source,e,t,s):{ok:!1,error:`unknown source.type ${l.source.type}`,attempts:[]}:{ok:!0,attempts:[]}}async function ie(l,e,t,s){let n=t.channels.gitlab,r=n?.token,o=n?.host||(l.host?`https://${l.host}`:void 0);if(!r||!o)return{ok:!1,error:"gitlab token/host not configured",attempts:[]};let i=[],c=async(u,p={})=>fetch(`${o}/api/v4${u}`,{...p,headers:{"PRIVATE-TOKEN":r,"Content-Type":"application/json",...p.headers||{}}}),a=encodeURIComponent(l.project),d;e.assigneeUsernames!==void 0&&(d=(await Promise.all(e.assigneeUsernames.map(async p=>{try{let y=await fetch(`${o}/api/v4/users?username=${encodeURIComponent(p)}`,{headers:{"PRIVATE-TOKEN":r}});if(!y.ok)return null;let w=await y.json();return Array.isArray(w)&&w[0]?.id?w[0].id:null}catch{return null}}))).filter(p=>typeof p=="number"));let g;if(e.milestoneTitle!==void 0)if(e.milestoneTitle===null||e.milestoneTitle==="")g=0;else try{let u=await c(`/projects/${a}/milestones?title=${encodeURIComponent(e.milestoneTitle)}`);if(u.ok){let p=await u.json();Array.isArray(p)&&p[0]?.id&&(g=p[0].id)}}catch(u){s(`[backlog-sync] milestone lookup failed: ${u.message}`)}let m={};if(e.title!==void 0&&(m.title=e.title),e.description!==void 0&&(m.description=e.description),e.labels!==void 0&&(m.labels=e.labels.join(",")),e.addLabels?.length&&(m.add_labels=e.addLabels.join(",")),e.removeLabels?.length&&(m.remove_labels=e.removeLabels.join(",")),d!==void 0&&(m.assignee_ids=d),g!==void 0&&(m.milestone_id=g),e.state&&(m.state_event=e.state==="closed"?"close":"reopen"),Object.keys(m).length)try{let u=await c(`/projects/${a}/issues/${l.iid}`,{method:"PUT",body:JSON.stringify(m)});if(i.push({kind:"PUT issue",ok:u.ok,status:u.status}),!u.ok){let p=await u.text().catch(()=>"");return{ok:!1,attempts:i,error:`PUT issue ${u.status}: ${p.slice(0,200)}`,url:l.url}}}catch(u){return i.push({kind:"PUT issue",ok:!1,error:u.message}),{ok:!1,attempts:i,error:u.message,url:l.url}}if(e.note)try{let u=await c(`/projects/${a}/issues/${l.iid}/notes`,{method:"POST",body:JSON.stringify({body:e.note})});i.push({kind:"POST note",ok:u.ok,status:u.status})}catch(u){i.push({kind:"POST note",ok:!1,error:u.message})}return{ok:i.every(u=>u.ok),attempts:i,url:l.url}}async function re(l,e,t,s){let r=t.channels.github?.token;if(!r)return{ok:!1,error:"github token not configured",attempts:[]};let o=[],i=async(m,u={})=>fetch(`https://api.github.com${m}`,{...u,headers:{Authorization:`Bearer ${r}`,Accept:"application/vnd.github+json","X-GitHub-Api-Version":"2022-11-28",...u.headers||{}}}),c=l.project,a;if(e.milestoneTitle!==void 0)if(e.milestoneTitle===null||e.milestoneTitle==="")a=null;else try{let m=await i(`/repos/${c}/milestones?state=all&per_page=100`);if(m.ok){let u=await m.json(),p=Array.isArray(u)?u.find(y=>y.title===e.milestoneTitle):null;p?.number&&(a=p.number)}}catch(m){s(`[backlog-sync] github milestone lookup failed: ${m.message}`)}let d;if(e.labels!==void 0)d=e.labels;else if(e.addLabels?.length||e.removeLabels?.length)try{let m=await i(`/repos/${c}/issues/${l.iid}`);if(m.ok){let u=await m.json(),p=Array.isArray(u.labels)?u.labels.map(w=>typeof w=="string"?w:w.name):[],y=new Set([...p,...e.addLabels??[]]);for(let w of e.removeLabels??[])y.delete(w);d=[...y]}}catch(m){s(`[backlog-sync] github label fetch failed: ${m.message}`)}let g={};if(e.title!==void 0&&(g.title=e.title),e.description!==void 0&&(g.body=e.description),d!==void 0&&(g.labels=d),e.assigneeUsernames!==void 0&&(g.assignees=e.assigneeUsernames),a!==void 0&&(g.milestone=a),e.state&&(g.state=e.state),Object.keys(g).length)try{let m=await i(`/repos/${c}/issues/${l.iid}`,{method:"PATCH",body:JSON.stringify(g)});if(o.push({kind:"PATCH issue",ok:m.ok,status:m.status}),!m.ok){let u=await m.text().catch(()=>"");return{ok:!1,attempts:o,error:`PATCH issue ${m.status}: ${u.slice(0,200)}`,url:l.url}}}catch(m){return o.push({kind:"PATCH issue",ok:!1,error:m.message}),{ok:!1,attempts:o,error:m.message,url:l.url}}if(e.note)try{let m=await i(`/repos/${c}/issues/${l.iid}/comments`,{method:"POST",body:JSON.stringify({body:e.note})});o.push({kind:"POST comment",ok:m.ok,status:m.status})}catch(m){o.push({kind:"POST comment",ok:!1,error:m.message})}return{ok:o.every(m=>m.ok),attempts:o,url:l.url}}var E=/^(\s*)-\s*\[( |x|X)\]\s*(.*)$/,A=/@([a-z0-9_-]+)/i,G=/\[time:\s*([0-9hm\s]+)\]/i;function ce(l){let e=l.match(/(\d+)\s*h/i),t=l.match(/(\d+)\s*m/i);return(e?parseInt(e[1],10)*3600:0)+(t?parseInt(t[1],10)*60:0)}var U=class{constructor(e,t,s=()=>{}){this.path=e;this.daemon=t;this.log=s;this.store=new j(this.path)}type="backlog";capabilities={listAll:!0,create:!1,transition:!1,labels:!0,search:!1};store;getStore(){return this.store}readMd(){let e=C(process.cwd(),this.path);return L(e)?T(e,"utf-8").split(`
|
|
4
|
-
`):[]}writeMd(e){let t=C(process.cwd(),this.path);J(t,e.join(`
|
|
5
|
-
`))}async listOpen(e){if(this.store.exists())return this.store.list().filter(r=>r.status!=="done"&&(!r.assignee||r.assignee.toLowerCase()===e.toLowerCase())).map(r=>this.toWorkItem(r));let t=this.readMd(),s=[];for(let n=0;n<t.length;n++){let r=t[n].match(E);if(!r||r[2].toLowerCase()==="x")continue;let i=r[3],c=i.match(A);if(c&&c[1].toLowerCase()!==e.toLowerCase())continue;let a=i.match(G),d=i.replace(A,"").replace(G,"").trim();s.push({id:`backlog:${n}`,title:d||`Task at line ${n+1}`,assignee:c?c[1]:void 0,estimatedSeconds:a?ce(a[1]):void 0,priority:n})}return s}async listAll(e={}){return this.store.exists()?this.store.list().map(t=>this.toWorkItem(t)):[]}async claim(e,t){if(!this.store.exists())return;let s=this.store.findById(t);if(!s)return;let n=this.store.update(t,{assignee:s.assignee||e,status:"doing"});if(n.source&&this.daemon){let r=q([n.assignee||e],n.source,this.daemon);_(n,{assigneeUsernames:r,addLabels:["Doing"],removeLabels:["To Do"]},this.daemon,this.log).catch(o=>this.log(`[backlog-sync] claim failed for ${t}: ${o?.message??o}`))}}async report(e,t){if(this.store.exists()&&this.store.findById(e)){let i={};t.status==="done"?i.status="done":t.status==="blocked"?(i.status="blocked",i.blocker=t.blocker||t.note||void 0):t.status==="in-progress"&&(i.status="doing"),t.timeSeconds&&(i.estimatedSeconds=t.timeSeconds);let c=this.store.update(e,i);if(c.source&&this.daemon){let a={};t.status==="done"?(a.addLabels=["Done"],a.removeLabels=["Doing"]):t.status==="blocked"&&(a.addLabels=["Blocked"]),t.note&&(a.note=t.note),Object.keys(a).length&&_(c,a,this.daemon,this.log).catch(d=>this.log(`[backlog-sync] report failed for ${e}: ${d?.message??d}`))}return}if(!e.startsWith("backlog:"))throw new Error(`not a backlog id: ${e}`);let s=parseInt(e.slice(8),10),n=this.readMd(),r=n[s];if(!r)throw new Error(`backlog line ${s} missing`);if(!r.match(E))throw new Error(`backlog line ${s} not a checkbox`);if(t.status==="done"){n[s]=r.replace(/- \[ \]/,"- [x]");let i=t.timeSeconds?` <!-- done by agent, ${Math.round(t.timeSeconds/60)}m -->`:" <!-- done by agent -->";n.splice(s+1,0,i)}else t.status==="blocked"?n.splice(s+1,0,` <!-- BLOCKED: ${t.blocker||t.note||"?"} -->`):t.note&&n.splice(s+1,0,` <!-- ${t.note.replace(/-->/g,"\u2014 >")} -->`);this.writeMd(n)}toWorkItem(e){let t={todo:"todo",doing:"doing",blocked:"onhold",done:"done"}[e.status];return{id:e.id,title:e.title,description:e.description,assignee:e.assignee,estimatedSeconds:e.estimatedSeconds,priority:e.priority,labels:e.labels,stage:t,milestone:e.milestone,url:e.source?.url,updatedAt:e.updatedAt,state:e.status==="done"?"closed":"opened"}}},B=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(L(e))for(let s of oe(e)){let n=le(e,s);ae(n).isDirectory()?this.walk(n,t):s.endsWith(".md")&&t.push(n)}}async listOpen(e){let t=[];this.walk(C(process.cwd(),this.root),t);let s=[];for(let n of t){let r=T(n,"utf-8").split(`
|
|
6
|
-
`);for(let o=0;o<r.length;o++){let i=r[o].match(E);if(!i||i[2].toLowerCase()==="x")continue;let c=i[3].match(A);!c||c[1].toLowerCase()!==e.toLowerCase()||s.push({id:`wiki:${n}:${o}`,title:i[3].replace(A,"").trim(),assignee:c[1],priority:o})}}return s}async claim(){}async report(e,t){let s=e.split(":"),n=parseInt(s[s.length-1],10),r=s.slice(1,-1).join(":");if(!L(r))return;let o=T(r,"utf-8").split(`
|
|
7
|
-
`);t.status==="done"&&o[n]&&(o[n]=o[n].replace(/- \[ \]/,"- [x]"),J(r,o.join(`
|
|
8
|
-
`)))}},P=class{constructor(e,t,s,n,r){this.host=e;this.token=t;this.projects=s;this.agentUsernames=n;this.log=r}type="gitlab";capabilities={listAll:!0,create:!0,transition:!0,labels:!0,search:!0};usernameToAgent(e){for(let[t,s]of Object.entries(this.agentUsernames))if(s.some(n=>n.toLowerCase()===e.toLowerCase()))return t}mapIssue(e,t){let s=e.references?.full?.split("#")[0]||t,n=[],r;Array.isArray(e.labels)&&(e.labels.length&&typeof e.labels[0]=="object"?(r=e.labels.map(a=>({name:a.name,color:a.color,text_color:a.text_color})),n=r.map(a=>a.name)):n=e.labels);let i=(Array.isArray(e.assignees)&&e.assignees.length?e.assignees:e.assignee?[e.assignee]:[]).map(a=>({username:a.username,name:a.name,avatarUrl:a.avatar_url})),c=i[0]?.username;return{id:`gitlab:${s}:issue:${e.iid}`,title:e.title,description:e.description||void 0,assignee:c?this.usernameToAgent(c)||c:void 0,assignees:i,url:e.web_url,priority:e.weight??99,estimatedSeconds:e.time_stats?.time_estimate||void 0,labels:n,labelDetails:r,updatedAt:e.updated_at,state:e.state==="closed"?"closed":"opened",milestone:e.milestone?.title||void 0}}async api(e,t={}){let s=await fetch(`${this.host}/api/v4${e}`,{...t,headers:{"PRIVATE-TOKEN":this.token,"Content-Type":"application/json",...t.headers||{}}});if(!s.ok)throw new Error(`GitLab ${s.status}: ${await s.text().catch(()=>"")}`);return s.json()}async listOpen(e){let t=this.agentUsernames[e]||[];if(!t.length)return[];let s=[],n=this.projects.length?this.projects:["__ALL__"];for(let r of t)for(let o of n)try{let i=`assignee_username=${encodeURIComponent(r)}&state=opened&scope=all&per_page=20`,c=o==="__ALL__"?`/issues?${i}`:`/projects/${encodeURIComponent(o)}/issues?${i}`,a=await this.api(c);for(let d of a){let g=this.mapIssue(d,o);g.assignee=e,s.push(g)}}catch(i){this.log(`[business] GitLab listOpen failed for ${r} on ${o}: ${i.message}`)}return s}async claim(e,t){let{project:s,iid:n,type:r}=this.parseId(t),o=`/projects/${encodeURIComponent(s)}/${r==="issue"?"issues":"merge_requests"}/${n}?add_labels=Doing&remove_labels=To Do`;try{await this.api(o,{method:"PUT"})}catch(i){this.log(`[business] claim label update failed: ${i.message}`)}}async report(e,t){let{project:s,iid:n,type:r}=this.parseId(e),o=r==="issue"?"issues":"merge_requests";if(t.timeSeconds&&t.timeSeconds>0){let i=Math.max(1,Math.round(t.timeSeconds/60)),c=i>=60?`${Math.floor(i/60)}h${i%60?` ${i%60}m`:""}`:`${i}m`;try{await this.api(`/projects/${encodeURIComponent(s)}/${o}/${n}/add_spent_time?duration=${encodeURIComponent(c)}`,{method:"POST"})}catch(a){this.log(`[business] add_spent_time failed: ${a.message}`)}}if(t.status==="done")try{await this.api(`/projects/${encodeURIComponent(s)}/${o}/${n}?add_labels=Done&remove_labels=Doing`,{method:"PUT"})}catch(i){this.log(`[business] done label update failed: ${i.message}`)}else if(t.status==="blocked")try{await this.api(`/projects/${encodeURIComponent(s)}/${o}/${n}?add_labels=Blocked`,{method:"PUT"})}catch(i){this.log(`[business] blocked label update failed: ${i.message}`)}if(t.note)try{await this.api(`/projects/${encodeURIComponent(s)}/${o}/${n}/notes`,{method:"POST",body:JSON.stringify({body:t.note})})}catch(i){this.log(`[business] report note failed: ${i.message}`)}}async listAll(e={}){let t=e.sinceDays??30,s=new Date(Date.now()-t*24*36e5).toISOString(),n=new URLSearchParams,r=e.state??"opened";n.set("state",r),n.set("updated_after",s),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__"],i=[];for(let a of o)try{let d=a==="__ALL__"?`/issues?${n.toString()}`:`/projects/${encodeURIComponent(a)}/issues?${n.toString()}`,g=await this.api(d);for(let m of g)i.push(this.mapIssue(m,a))}catch(d){this.log(`[business] GitLab listAll failed on ${a}: ${d.message}`)}let c=i;if(e.notLabels?.length){let a=new Set(e.notLabels);c=c.filter(d=>!(d.labels||[]).some(g=>a.has(g)))}if(e.withoutScopedPrefix){let a=e.withoutScopedPrefix+"::";c=c.filter(d=>!(d.labels||[]).some(g=>g.startsWith(a)))}return c}async setState(e,t){let{project:s,iid:n,type:r}=this.parseId(e),o=r==="issue"?"issues":"merge_requests";await this.api(`/projects/${encodeURIComponent(s)}/${o}/${n}?state_event=${t}`,{method:"PUT"})}async getItem(e){let{project:t,iid:s}=this.parseId(e);try{let n=await this.api(`/projects/${encodeURIComponent(t)}/issues/${s}?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:s,iid:n}=this.parseId(e),r;t.assigneeUsernames&&(r=(await Promise.all(t.assigneeUsernames.map(async d=>{try{let g=await this.api(`/users?username=${encodeURIComponent(d)}`);return Array.isArray(g)&&g[0]?.id?g[0].id:null}catch{return null}}))).filter(d=>typeof d=="number"));let o;if(t.milestoneTitle!==void 0)if(t.milestoneTitle===null||t.milestoneTitle==="")o=0;else try{let a=await this.api(`/projects/${encodeURIComponent(s)}/milestones?title=${encodeURIComponent(t.milestoneTitle)}`);Array.isArray(a)&&a[0]?.id&&(o=a[0].id)}catch(a){this.log(`[board] milestone lookup failed for "${t.milestoneTitle}": ${a.message}`)}let i={};t.title!==void 0&&(i.title=t.title),t.description!==void 0&&(i.description=t.description),t.labels!==void 0&&(i.labels=t.labels.join(",")),t.addLabels?.length&&(i.add_labels=t.addLabels.join(",")),t.removeLabels?.length&&(i.remove_labels=t.removeLabels.join(",")),r!==void 0&&(i.assignee_ids=r),o!==void 0&&(i.milestone_id=o);let c=await this.api(`/projects/${encodeURIComponent(s)}/issues/${n}`,{method:"PUT",body:JSON.stringify(i)});return this.mapIssue(c,s)}async listMembers(){let e=new Map;for(let t of this.projects)try{let s=await this.api(`/projects/${encodeURIComponent(t)}/members/all?per_page=100`);for(let n of s)n.username&&!e.has(n.username)&&e.set(n.username,{username:n.username,name:n.name,avatarUrl:n.avatar_url})}catch(s){this.log(`[board] listMembers failed for ${t}: ${s.message}`)}return[...e.values()].sort((t,s)=>t.username.localeCompare(s.username))}async listMilestones(){let e=new Map;for(let s of this.projects)try{let n=await this.api(`/projects/${encodeURIComponent(s)}/milestones?per_page=100`);for(let r of n)r.title&&!e.has(r.title)&&e.set(r.title,{id:r.id,title:r.title,state:r.state,dueDate:r.due_date})}catch(n){this.log(`[board] listMilestones failed for ${s}: ${n.message}`)}let t=[...e.values()];return t.sort((s,n)=>s.state!==n.state?s.state==="active"?-1:1:s.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 s,n=e.assigneeUsernames?.length?e.assigneeUsernames:e.assignee?(this.agentUsernames[e.assignee]||[]).slice(0,1):[];n.length&&(s=(await Promise.all(n.map(async a=>{try{let d=await this.api(`/users?username=${encodeURIComponent(a)}`);return Array.isArray(d)&&d[0]?.id?d[0].id:null}catch(d){return this.log(`[board] user lookup failed for ${a}: ${d.message}`),null}}))).filter(a=>typeof a=="number"));let r;if(e.milestoneTitle)try{let c=await this.api(`/projects/${encodeURIComponent(t)}/milestones?title=${encodeURIComponent(e.milestoneTitle)}`);Array.isArray(c)&&c[0]?.id&&(r=c[0].id)}catch(c){this.log(`[board] milestone lookup failed for "${e.milestoneTitle}": ${c.message}`)}let o={title:e.title,description:e.description,labels:(e.labels||[]).join(",")};s?.length&&(o.assignee_ids=s),r!==void 0&&(o.milestone_id=r);let i=await this.api(`/projects/${encodeURIComponent(t)}/issues`,{method:"POST",body:JSON.stringify(o)});return this.mapIssue(i,t)}async transition(e,t,s){await this.transitionMany(e,t?[t]:[],s?[s]:[])}async transitionMany(e,t,s){let{project:n,iid:r,type:o}=this.parseId(e),i=o==="issue"?"issues":"merge_requests",c=new URLSearchParams;t.length&&c.set("add_labels",t.join(",")),s.length&&c.set("remove_labels",s.join(",")),c.toString()&&await this.api(`/projects/${encodeURIComponent(n)}/${i}/${r}?${c.toString()}`,{method:"PUT"})}parseId(e){let s=e.replace(/^gitlab:/,"").split(":"),n=s.pop(),r=s.pop()==="mr"?"mr":"issue";return{project:s.join(":"),type:r,iid:n}}};function je(l,e,t){let s=l.workSource;switch(s.type){case"backlog":return new U(s.path,e,t);case"wiki":return new B(s.path,s.glob);case"gitlab":{let n=e.channels.gitlab;if(!n?.token)throw new Error("business.workSource.type=gitlab requires channels.gitlab.token");let r={};for(let i of n.agentMappings)i.gitlabUsernames.length&&(r[i.agentId]=i.gitlabUsernames);let o=s.projects.length?s.projects:n.routes.map(i=>i.project);return new P(n.host,n.token,o,r,t)}}}import{existsSync as b,readFileSync as D,writeFileSync as z,mkdirSync as x,readdirSync as O,rmSync as de,statSync as k}from"fs";import{resolve as X,relative as me,dirname as M,join as S,extname as v,basename as F}from"path";var h=200*1024,V=new Set([".md",".markdown",".txt"]),I=new Set([".md",".markdown",".txt",".json",".jsonc",".yaml",".yml",".toml",".sh",".bash",".zsh",".py",".js",".mjs",".ts",".tsx",".jsx",".css",".html",".lock",".cfg",".ini",".env",".gitignore"]),ue=6,ge=new Set(["CLAUDE.md","SOUL.md","IDENTITY.md","PERSONA.md","AGENT.md","BOOTSTRAP.md"]);function $(l,e){if(!l)throw new Error("Agent workspace is not configured.");let t=X(l);if(b(t)||x(t,{recursive:!0}),!e||e.startsWith("/")||e.match(/^[a-zA-Z]:/))throw new Error(`Path must be relative to the workspace: ${e}`);let s=X(t,e);if(me(t,s).startsWith(".."))throw new Error(`Path escapes the workspace: ${e}`);return s}function Y(l){let e=v(l).toLowerCase();if(!V.has(e))throw new Error(`Only text files (.md, .markdown, .txt) can be edited from the admin panel (got ${e}).`)}function R(l,e){let t=l.split(/\r?\n/).find(s=>s.trim().startsWith("#"));return t&&t.replace(/^#+\s*/,"").trim().slice(0,120)||e}function Te(l){let e=$(l,"."),t=[],s=new Set,n=[];try{n=O(e)}catch{}for(let i of ge){let c=S(e,i);if(b(c)&&k(c).isFile()){let a=k(c).size,d=a>0&&a<h?W(c):"";t.push({path:i,title:R(d,i),size:a,exists:!0}),s.add(i)}else t.push({path:i,title:i,size:0,exists:!1}),s.add(i)}for(let i of n){if(s.has(i))continue;let c=S(e,i);try{let a=k(c);if(!a.isFile()||!V.has(v(i).toLowerCase()))continue;let d=a.size>0&&a.size<h?W(c):"";t.push({path:i,title:R(d,i),size:a.size,exists:!0})}catch{}}let r=[],o=S(e,".claude","skills");if(b(o))try{for(let i of O(o)){let c=S(o,i),a;try{a=k(c)}catch{continue}if(!a.isDirectory())continue;let d=S(c,"SKILL.md");if(!b(d))continue;let g=k(d),m=g.size<h?W(d):"";r.push({slug:i,title:R(m,i),size:g.size,path:`.claude/skills/${i}/SKILL.md`})}}catch{}return r.sort((i,c)=>i.slug.localeCompare(c.slug)),{identity:t,skills:r,workspace:e}}function W(l){try{return D(l,"utf-8")}catch{return""}}function Le(l,e){let t=$(l,e);if(Y(t),!b(t))return{path:e,content:""};if(k(t).size>h)throw new Error(`File is too large to edit in the admin panel (> ${h/1024} KB).`);return{path:e,content:D(t,"utf-8")}}function Ce(l,e,t){let s=$(l,e);Y(s);let n=Buffer.from(t,"utf-8");if(n.length>h)throw new Error(`File too large (${n.length} > ${h} bytes).`);return x(M(s),{recursive:!0}),z(s,n),{path:e,bytes:n.length}}function Ee(l,e,t={}){if(!/^[a-z0-9][a-z0-9_-]*$/.test(e))throw new Error("Skill slug must be lowercase (letters, digits, -, _) and start with a letter or digit.");let s=`.claude/skills/${e}/SKILL.md`,n=$(l,s);if(b(n))throw new Error(`Skill "${e}" already exists.`);x(M(n),{recursive:!0});let r=t.content?.trim()?t.content:fe(e,t.title);return z(n,r),{slug:e,path:s}}function Ue(l,e){if(!/^[a-z0-9][a-z0-9_-]*$/.test(e))throw new Error(`Invalid skill slug: ${e}`);let t=$(l,`.claude/skills/${e}`);if(!b(t))throw new Error(`Skill "${e}" not found.`);if(!k(t).isDirectory())throw new Error(`Skill path is not a directory: ${e}`);return de(t,{recursive:!0,force:!0}),{slug:e}}function Be(l,e){if(!/^[a-z0-9][a-z0-9_-]*$/.test(e))throw new Error(`Invalid skill slug: ${e}`);let t=$(l,`.claude/skills/${e}`);if(!b(t))throw new Error(`Skill "${e}" not found.`);let s=[];return n(t,"",0,s),s.sort((r,o)=>r.kind!==o.kind?r.kind==="dir"?-1:1:r.path.localeCompare(o.path)),s;function n(r,o,i,c){if(i>ue)return;let a=[];try{a=O(r)}catch{return}for(let d of a){if(d==="node_modules"||d===".git")continue;let g=S(r,d),m=o?`${o}/${d}`:d,u;try{u=k(g)}catch{continue}if(u.isDirectory())c.push({path:`${m}/`,kind:"dir",size:0,editable:!1}),n(g,m,i+1,c);else if(u.isFile()){let p=v(d).toLowerCase(),y=I.has(p)||I.has(d.toLowerCase());c.push({path:m,kind:"file",size:u.size,editable:y&&u.size<h})}}}}function Pe(l,e,t){if(!/^[a-z0-9][a-z0-9_-]*$/.test(e))throw new Error(`Invalid skill slug: ${e}`);if(!t||t.includes(".."))throw new Error(`Invalid file path: ${t}`);let s=$(l,`.claude/skills/${e}/${t}`);if(!b(s)||!k(s).isFile())throw new Error(`File not found: ${t}`);let n=v(s).toLowerCase(),r=F(s).toLowerCase();if(!I.has(n)&&!I.has(r))throw new Error(`File type not editable: ${t}`);if(k(s).size>h)throw new Error(`File too large to edit (> ${h/1024} KB): ${t}`);return{path:t,content:D(s,"utf-8")}}function Re(l,e,t,s){if(!/^[a-z0-9][a-z0-9_-]*$/.test(e))throw new Error(`Invalid skill slug: ${e}`);if(!t||t.includes(".."))throw new Error(`Invalid file path: ${t}`);let n=$(l,`.claude/skills/${e}/${t}`),r=v(n).toLowerCase(),o=F(n).toLowerCase();if(!I.has(r)&&!I.has(o))throw new Error(`File type not editable: ${t}`);let i=Buffer.from(s,"utf-8");if(i.length>h)throw new Error(`File too large (${i.length} > ${h} bytes).`);return x(M(n),{recursive:!0}),z(n,i),{path:t,bytes:i.length}}function We(l,e){if(!/^[a-z0-9][a-z0-9_-]*$/.test(e))throw new Error(`Invalid skill slug: ${e}`);let t=$(l,`.claude/skills/${e}`);if(!b(t))throw new Error(`Skill "${e}" not found.`);let s=n=>b(S(t,n));return s("pnpm-lock.yaml")?{manager:"pnpm",file:"pnpm-lock.yaml",command:"pnpm install"}:s("yarn.lock")?{manager:"yarn",file:"yarn.lock",command:"yarn install"}:s("package.json")?{manager:"npm",file:"package.json",command:"npm install"}:s("requirements.txt")?{manager:"pip",file:"requirements.txt",command:"pip install -r requirements.txt"}:s("Gemfile")?{manager:"bundle",file:"Gemfile",command:"bundle install"}:{manager:null,file:null,command:null}}function fe(l,e){return`# ${e?.trim()||l}
|
|
9
|
-
|
|
10
|
-
Describe what this skill does and when the agent should use it. Keep it under ~5 lines \u2014 the agent reads this to decide whether to invoke the skill.
|
|
11
|
-
|
|
12
|
-
## When to use
|
|
13
|
-
- Trigger condition 1
|
|
14
|
-
- Trigger condition 2
|
|
15
|
-
|
|
16
|
-
## How to use
|
|
17
|
-
Document the arguments, commands, or files the skill expects. Example:
|
|
18
|
-
|
|
19
|
-
\`\`\`bash
|
|
20
|
-
# Example invocation
|
|
21
|
-
claude-code ${F(l)} --arg value
|
|
22
|
-
\`\`\`
|
|
23
|
-
`}export{j as a,ke as b,q as c,_ as d,P as e,je as f,Te as g,Le as h,Ce as i,Ee as j,Ue as k,Be as l,Pe as m,Re as n,We as o};
|
|
24
|
-
//# sourceMappingURL=chunk-3WQFCIB6.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/business/work-pool.ts","../src/business/backlog-store.ts","../src/business/backlog-sync.ts","../src/daemon/file-ops.ts"],"sourcesContent":["import { readFileSync, writeFileSync, existsSync, readdirSync, statSync } from \"fs\"\nimport { resolve, join } from \"path\"\nimport type { BusinessConfig, BusinessWorkSource } from \"./config\"\nimport type { DaemonConfig } from \"@/daemon/config\"\nimport { BacklogStore } from \"./backlog-store\"\nimport { syncBacklogItemToSource, resolveAssigneesForSource, type BacklogPatch } from \"./backlog-sync\"\n\n// --- Work pool: pluggable source of tasks for the business layer ---\n\nexport interface WorkItem {\n id: string // stable id for claim/report (e.g. \"backlog:line-12\", \"gitlab:proj:issue:42\")\n title: string\n description?: string\n assignee?: string // agentId\n estimatedSeconds?: number\n url?: string\n priority?: number // lower = higher priority\n /** Board stage / kanban column id. Was a closed enum back when boards\n * had a fixed schema; widened to `string` because per-project boards\n * can declare arbitrary column ids. The well-known names below remain\n * the standard set most boards use. */\n stage?: string // typically: \"triage\" | \"todo\" | \"doing\" | \"onhold\" | \"review\" | \"done\"\n /** Raw labels from the source (GitLab). Empty array if source doesn't support labels. */\n labels?: string[]\n /** Label name+color when the source returns colored labels (GitLab with_labels_details). */\n labelDetails?: Array<{ name: string; color: string; text_color?: string }>\n /** ISO timestamp of last update on the source. */\n updatedAt?: string\n /** Open/closed state from the source. */\n state?: \"opened\" | \"closed\"\n /** Optional milestone title (GitLab: issue.milestone.title). */\n milestone?: string\n /** GitLab assignees (0..n). `assignee` above is the agentId alias for assignees[0]. */\n assignees?: Array<{ username: string; name?: string; avatarUrl?: string }>\n}\n\n/** Member of a source (GitLab project members) — used for assignee pickers. */\nexport interface WorkMember {\n username: string\n name?: string\n avatarUrl?: string\n}\n\n/** Milestone on a source — used for milestone pickers. */\nexport interface WorkMilestone {\n id: number | string\n title: string\n state?: \"active\" | \"closed\"\n dueDate?: string\n}\n\n/** Full issue detail — a superset of WorkItem for the detail view. */\nexport interface WorkDetail extends WorkItem {\n /** Raw body, may be markdown. */\n descriptionHtml?: string\n /** ISO timestamp of creation. */\n createdAt?: string\n /** Author (issue reporter). */\n author?: { username: string; name?: string; avatarUrl?: string }\n /** Full labels list (all of them). */\n allLabels?: string[]\n}\n\nexport interface WorkReport {\n status: \"in-progress\" | \"done\" | \"blocked\"\n note?: string\n timeSeconds?: number\n blocker?: string\n}\n\n/** What a WorkSource can do beyond the minimal listOpen/claim/report triple. */\nexport interface WorkSourceCapabilities {\n /** Enumerate items beyond assignee filter — required for a board view. */\n listAll: boolean\n /** Create new items (e.g. POST GitLab issue). */\n create: boolean\n /** Arbitrary column transitions (label add/remove pair). */\n transition: boolean\n /** Surfaces labels[] on returned items. */\n labels: boolean\n /** Server-side text search. */\n search: boolean\n}\n\nexport interface WorkCreateInput {\n title: string\n description?: string\n /** agentId */\n assignee?: string\n /** Raw GitLab usernames — bypass agentId resolution for UI-driven creates. */\n assigneeUsernames?: string[]\n labels?: string[]\n /** Milestone title (resolved to id). */\n milestoneTitle?: string\n /** For sources that span multiple projects (gitlab): \"group/project\". */\n projectHint?: string\n}\n\nexport interface ListAllOpts {\n sinceDays?: number\n labels?: string[]\n /** Exclude items carrying any of these labels (client-side filter). */\n notLabels?: string[]\n /** Exclude items whose labels carry this scoped prefix (e.g. \"Status\"). */\n withoutScopedPrefix?: string\n search?: string\n assignee?: string\n /** GitLab issue state. Defaults to \"opened\" (boards hide closed issues). */\n state?: \"opened\" | \"closed\" | \"all\"\n /** Milestone filter (GitLab \"milestone\" query param). */\n milestone?: string\n}\n\nexport interface WorkSource {\n type: string\n capabilities: WorkSourceCapabilities\n listOpen(agentId: string): Promise<WorkItem[]>\n claim(agentId: string, itemId: string): Promise<void>\n report(itemId: string, update: WorkReport): Promise<void>\n /** Optional — gated by capabilities.listAll. */\n listAll?(opts: ListAllOpts): Promise<WorkItem[]>\n /** Optional — gated by capabilities.create. */\n create?(input: WorkCreateInput): Promise<WorkItem>\n /**\n * Optional — gated by capabilities.transition. Add `toLabel` and remove\n * `fromLabel` (if provided) from the item. Used by the kanban board on drag.\n */\n transition?(itemId: string, toLabel: string, fromLabel?: string): Promise<void>\n}\n\n// ---------- BacklogWorkSource: GFM checklist file ----------\n// Format in .agentx/backlog.md (or configured path):\n//\n// - [ ] @alice Implement login form [time: 2h]\n// - [x] @alice Write unit tests (done)\n// - [ ] @bob Design pricing page [time: 1h30m]\n//\n// Line number is the stable id.\n\nconst CHECK_RE = /^(\\s*)-\\s*\\[( |x|X)\\]\\s*(.*)$/\nconst MENTION_RE = /@([a-z0-9_-]+)/i\nconst TIME_RE = /\\[time:\\s*([0-9hm\\s]+)\\]/i\n\nfunction parseDuration(s: string): number {\n // \"2h30m\" -> 9000 s. \"1h\" -> 3600. \"45m\" -> 2700.\n const hMatch = s.match(/(\\d+)\\s*h/i)\n const mMatch = s.match(/(\\d+)\\s*m/i)\n return (hMatch ? parseInt(hMatch[1], 10) * 3600 : 0) + (mMatch ? parseInt(mMatch[1], 10) * 60 : 0)\n}\n\nexport class BacklogWorkSource implements WorkSource {\n type = \"backlog\"\n capabilities: WorkSourceCapabilities = {\n listAll: true, create: false, transition: false, labels: true, search: false,\n }\n private store: BacklogStore\n\n constructor(\n private path: string,\n private daemon?: DaemonConfig,\n private log: (...args: unknown[]) => void = () => {},\n ) {\n this.store = new BacklogStore(this.path)\n }\n\n /** Expose the store so the CLI and sync-back can share state. */\n getStore(): BacklogStore {\n return this.store\n }\n\n private readMd(): string[] {\n const full = resolve(process.cwd(), this.path)\n if (!existsSync(full)) return []\n return readFileSync(full, \"utf-8\").split(\"\\n\")\n }\n\n private writeMd(lines: string[]): void {\n const full = resolve(process.cwd(), this.path)\n writeFileSync(full, lines.join(\"\\n\"))\n }\n\n async listOpen(agentId: string): Promise<WorkItem[]> {\n if (this.store.exists()) {\n const items = this.store.list().filter((it) =>\n it.status !== \"done\" &&\n (!it.assignee || it.assignee.toLowerCase() === agentId.toLowerCase()),\n )\n return items.map((it) => this.toWorkItem(it))\n }\n // Legacy markdown-checklist fallback (unchanged behavior).\n const lines = this.readMd()\n const items: WorkItem[] = []\n for (let i = 0; i < lines.length; i++) {\n const m = lines[i].match(CHECK_RE)\n if (!m) continue\n const checked = m[2].toLowerCase() === \"x\"\n if (checked) continue\n const body = m[3]\n const mention = body.match(MENTION_RE)\n if (mention && mention[1].toLowerCase() !== agentId.toLowerCase()) continue\n const timeMatch = body.match(TIME_RE)\n const title = body.replace(MENTION_RE, \"\").replace(TIME_RE, \"\").trim()\n items.push({\n id: `backlog:${i}`,\n title: title || `Task at line ${i + 1}`,\n assignee: mention ? mention[1] : undefined,\n estimatedSeconds: timeMatch ? parseDuration(timeMatch[1]) : undefined,\n priority: i,\n })\n }\n return items\n }\n\n async listAll(_opts: ListAllOpts = {}): Promise<WorkItem[]> {\n if (!this.store.exists()) return []\n return this.store.list().map((it) => this.toWorkItem(it))\n }\n\n async claim(agentId: string, itemId: string): Promise<void> {\n if (!this.store.exists()) return\n const item = this.store.findById(itemId)\n if (!item) return\n const updated = this.store.update(itemId, {\n assignee: item.assignee || agentId,\n status: \"doing\",\n })\n if (updated.source && this.daemon) {\n const usernames = resolveAssigneesForSource([updated.assignee || agentId], updated.source, this.daemon)\n void syncBacklogItemToSource(updated, {\n assigneeUsernames: usernames,\n addLabels: [\"Doing\"],\n removeLabels: [\"To Do\"],\n }, this.daemon, this.log).catch((e) =>\n this.log(`[backlog-sync] claim failed for ${itemId}: ${e?.message ?? e}`),\n )\n }\n }\n\n async report(itemId: string, update: WorkReport): Promise<void> {\n // Structured store path.\n if (this.store.exists() && this.store.findById(itemId)) {\n const patch: Partial<import(\"./backlog-store\").BacklogItem> = {}\n if (update.status === \"done\") patch.status = \"done\"\n else if (update.status === \"blocked\") {\n patch.status = \"blocked\"\n patch.blocker = update.blocker || update.note || undefined\n } else if (update.status === \"in-progress\") patch.status = \"doing\"\n if (update.timeSeconds) patch.estimatedSeconds = update.timeSeconds\n const item = this.store.update(itemId, patch)\n\n if (item.source && this.daemon) {\n const sourcePatch: BacklogPatch = {}\n if (update.status === \"done\") {\n sourcePatch.addLabels = [\"Done\"]\n sourcePatch.removeLabels = [\"Doing\"]\n } else if (update.status === \"blocked\") {\n sourcePatch.addLabels = [\"Blocked\"]\n }\n if (update.note) sourcePatch.note = update.note\n if (Object.keys(sourcePatch).length) {\n void syncBacklogItemToSource(item, sourcePatch, this.daemon, this.log).catch((e) =>\n this.log(`[backlog-sync] report failed for ${itemId}: ${e?.message ?? e}`),\n )\n }\n }\n return\n }\n\n // Legacy markdown path (line-number ids).\n if (!itemId.startsWith(\"backlog:\")) throw new Error(`not a backlog id: ${itemId}`)\n const lineNo = parseInt(itemId.slice(\"backlog:\".length), 10)\n const lines = this.readMd()\n const line = lines[lineNo]\n if (!line) throw new Error(`backlog line ${lineNo} missing`)\n const m = line.match(CHECK_RE)\n if (!m) throw new Error(`backlog line ${lineNo} not a checkbox`)\n\n if (update.status === \"done\") {\n lines[lineNo] = line.replace(/- \\[ \\]/, \"- [x]\")\n const note = update.timeSeconds\n ? ` <!-- done by agent, ${Math.round(update.timeSeconds / 60)}m -->`\n : \" <!-- done by agent -->\"\n lines.splice(lineNo + 1, 0, note)\n } else if (update.status === \"blocked\") {\n lines.splice(lineNo + 1, 0, ` <!-- BLOCKED: ${update.blocker || update.note || \"?\"} -->`)\n } else if (update.note) {\n lines.splice(lineNo + 1, 0, ` <!-- ${update.note.replace(/-->/g, \"— >\")} -->`)\n }\n this.writeMd(lines)\n }\n\n private toWorkItem(it: import(\"./backlog-store\").BacklogItem): WorkItem {\n const stage = ({ todo: \"todo\", doing: \"doing\", blocked: \"onhold\", done: \"done\" } as const)[it.status]\n return {\n id: it.id,\n title: it.title,\n description: it.description,\n assignee: it.assignee,\n estimatedSeconds: it.estimatedSeconds,\n priority: it.priority,\n labels: it.labels,\n stage,\n milestone: it.milestone,\n url: it.source?.url,\n updatedAt: it.updatedAt,\n state: it.status === \"done\" ? \"closed\" : \"opened\",\n }\n }\n}\n\n// ---------- WikiWorkSource: scan markdown files for @mention checkboxes ----------\n\nexport class WikiWorkSource implements WorkSource {\n type = \"wiki\"\n capabilities: WorkSourceCapabilities = {\n listAll: false, create: false, transition: false, labels: false, search: false,\n }\n constructor(private root: string, private glob: string) {}\n\n private walk(dir: string, out: string[]): void {\n if (!existsSync(dir)) return\n for (const entry of readdirSync(dir)) {\n const full = join(dir, entry)\n const st = statSync(full)\n if (st.isDirectory()) {\n this.walk(full, out)\n } else if (entry.endsWith(\".md\")) {\n out.push(full)\n }\n }\n }\n\n async listOpen(agentId: string): Promise<WorkItem[]> {\n const files: string[] = []\n this.walk(resolve(process.cwd(), this.root), files)\n const items: WorkItem[] = []\n for (const file of files) {\n const lines = readFileSync(file, \"utf-8\").split(\"\\n\")\n for (let i = 0; i < lines.length; i++) {\n const m = lines[i].match(CHECK_RE)\n if (!m || m[2].toLowerCase() === \"x\") continue\n const mention = m[3].match(MENTION_RE)\n if (!mention || mention[1].toLowerCase() !== agentId.toLowerCase()) continue\n items.push({\n id: `wiki:${file}:${i}`,\n title: m[3].replace(MENTION_RE, \"\").trim(),\n assignee: mention[1],\n priority: i,\n })\n }\n }\n return items\n }\n\n async claim(): Promise<void> { /* no-op */ }\n\n async report(itemId: string, update: WorkReport): Promise<void> {\n // wiki:<file>:<line>\n const parts = itemId.split(\":\")\n const lineNo = parseInt(parts[parts.length - 1], 10)\n const file = parts.slice(1, -1).join(\":\")\n if (!existsSync(file)) return\n const lines = readFileSync(file, \"utf-8\").split(\"\\n\")\n if (update.status === \"done\" && lines[lineNo]) {\n lines[lineNo] = lines[lineNo].replace(/- \\[ \\]/, \"- [x]\")\n writeFileSync(file, lines.join(\"\\n\"))\n }\n }\n}\n\n// ---------- GitLabWorkSource: assignee-filtered issues via GitLab API ----------\n\nexport class GitLabWorkSource implements WorkSource {\n type = \"gitlab\"\n capabilities: WorkSourceCapabilities = {\n listAll: true, create: true, transition: true, labels: true, search: true,\n }\n\n constructor(\n private host: string,\n private token: string,\n private projects: string[],\n /** Map from agentId -> GitLab username(s) */\n private agentUsernames: Record<string, string[]>,\n private log: (...args: unknown[]) => void,\n ) {}\n\n /** Reverse lookup: GitLab username -> agentId. Used when resolving board items\n * back to an agent (for the reconciler and card display). */\n private usernameToAgent(username: string): string | undefined {\n for (const [agentId, names] of Object.entries(this.agentUsernames)) {\n if (names.some((n) => n.toLowerCase() === username.toLowerCase())) return agentId\n }\n return undefined\n }\n\n private mapIssue(issue: any, project: string): WorkItem {\n const ns = issue.references?.full?.split(\"#\")[0] || project\n // `labels` may be a string[] or, with with_labels_details=true, an object[].\n let labels: string[] = []\n let labelDetails: Array<{ name: string; color: string; text_color?: string }> | undefined\n if (Array.isArray(issue.labels)) {\n if (issue.labels.length && typeof issue.labels[0] === \"object\") {\n labelDetails = issue.labels.map((l: any) => ({\n name: l.name, color: l.color, text_color: l.text_color,\n }))\n labels = labelDetails!.map((l) => l.name)\n } else {\n labels = issue.labels\n }\n }\n const rawAssignees: any[] = Array.isArray(issue.assignees) && issue.assignees.length\n ? issue.assignees\n : (issue.assignee ? [issue.assignee] : [])\n const assignees = rawAssignees.map((a) => ({\n username: a.username, name: a.name, avatarUrl: a.avatar_url,\n }))\n const firstUsername = assignees[0]?.username\n return {\n id: `gitlab:${ns}:issue:${issue.iid}`,\n title: issue.title,\n description: issue.description || undefined,\n // Prefer configured agentId mapping; fall back to the raw GitLab username\n // so cards still render an assignee even without agentMappings configured.\n assignee: firstUsername ? (this.usernameToAgent(firstUsername) || firstUsername) : undefined,\n assignees,\n url: issue.web_url,\n priority: issue.weight ?? 99,\n estimatedSeconds: issue.time_stats?.time_estimate || undefined,\n labels,\n labelDetails,\n updatedAt: issue.updated_at,\n state: issue.state === \"closed\" ? \"closed\" : \"opened\",\n milestone: issue.milestone?.title || undefined,\n }\n }\n\n private async api(path: string, init: RequestInit = {}): Promise<any> {\n const res = await fetch(`${this.host}/api/v4${path}`, {\n ...init,\n headers: {\n \"PRIVATE-TOKEN\": this.token,\n \"Content-Type\": \"application/json\",\n ...(init.headers || {}),\n },\n })\n if (!res.ok) throw new Error(`GitLab ${res.status}: ${await res.text().catch(() => \"\")}`)\n return res.json()\n }\n\n async listOpen(agentId: string): Promise<WorkItem[]> {\n const usernames = this.agentUsernames[agentId] || []\n if (!usernames.length) return []\n\n const items: WorkItem[] = []\n const projectList = this.projects.length\n ? this.projects\n : [\"__ALL__\"] // falls back to a global issues query\n\n for (const username of usernames) {\n for (const project of projectList) {\n try {\n const q = `assignee_username=${encodeURIComponent(username)}&state=opened&scope=all&per_page=20`\n const path = project === \"__ALL__\"\n ? `/issues?${q}`\n : `/projects/${encodeURIComponent(project)}/issues?${q}`\n const issues = await this.api(path)\n for (const issue of issues) {\n const item = this.mapIssue(issue, project)\n // Force assignee to the agent we queried for (listOpen is per-agent).\n item.assignee = agentId\n items.push(item)\n }\n } catch (e: any) {\n this.log(`[business] GitLab listOpen failed for ${username} on ${project}: ${e.message}`)\n }\n }\n }\n return items\n }\n\n async claim(agentId: string, itemId: string): Promise<void> {\n // itemId: \"gitlab:<project>:issue:<iid>\"\n const { project, iid, type } = this.parseId(itemId)\n // Transition labels: add \"Doing\", remove \"To Do\"\n const path = `/projects/${encodeURIComponent(project)}/${type === \"issue\" ? \"issues\" : \"merge_requests\"}/${iid}?add_labels=Doing&remove_labels=To Do`\n try {\n await this.api(path, { method: \"PUT\" })\n } catch (e: any) {\n this.log(`[business] claim label update failed: ${e.message}`)\n }\n }\n\n async report(itemId: string, update: WorkReport): Promise<void> {\n const { project, iid, type } = this.parseId(itemId)\n const seg = type === \"issue\" ? \"issues\" : \"merge_requests\"\n\n // Time tracking\n if (update.timeSeconds && update.timeSeconds > 0) {\n const mins = Math.max(1, Math.round(update.timeSeconds / 60))\n const dur = mins >= 60 ? `${Math.floor(mins / 60)}h${mins % 60 ? ` ${mins % 60}m` : \"\"}` : `${mins}m`\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}/add_spent_time?duration=${encodeURIComponent(dur)}`,\n { method: \"POST\" },\n )\n } catch (e: any) {\n this.log(`[business] add_spent_time failed: ${e.message}`)\n }\n }\n\n // Status → labels\n if (update.status === \"done\") {\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?add_labels=Done&remove_labels=Doing`,\n { method: \"PUT\" },\n )\n } catch (e: any) {\n this.log(`[business] done label update failed: ${e.message}`)\n }\n } else if (update.status === \"blocked\") {\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?add_labels=Blocked`,\n { method: \"PUT\" },\n )\n } catch (e: any) {\n this.log(`[business] blocked label update failed: ${e.message}`)\n }\n }\n\n // Note/comment\n if (update.note) {\n try {\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}/notes`,\n { method: \"POST\", body: JSON.stringify({ body: update.note }) },\n )\n } catch (e: any) {\n this.log(`[business] report note failed: ${e.message}`)\n }\n }\n }\n\n async listAll(opts: ListAllOpts = {}): Promise<WorkItem[]> {\n const sinceDays = opts.sinceDays ?? 30\n const updatedAfter = new Date(Date.now() - sinceDays * 24 * 3600_000).toISOString()\n\n const params = new URLSearchParams()\n const state = opts.state ?? \"opened\"\n params.set(\"state\", state)\n // `updated_after` makes sense for the Closed column (bounded window) and for\n // keeping the open list recent; for \"opened\" we pass it too but GitLab treats\n // the window loosely (any issue updated since counts, regardless of open-age).\n params.set(\"updated_after\", updatedAfter)\n params.set(\"scope\", \"all\")\n params.set(\"per_page\", \"100\")\n params.set(\"with_labels_details\", \"true\")\n params.set(\"order_by\", \"updated_at\")\n params.set(\"sort\", \"desc\")\n if (opts.labels?.length) params.set(\"labels\", opts.labels.join(\",\"))\n if (opts.milestone) params.set(\"milestone\", opts.milestone)\n if (opts.search) params.set(\"search\", opts.search)\n\n const projectList = this.projects.length ? this.projects : [\"__ALL__\"]\n const items: WorkItem[] = []\n for (const project of projectList) {\n try {\n const path = project === \"__ALL__\"\n ? `/issues?${params.toString()}`\n : `/projects/${encodeURIComponent(project)}/issues?${params.toString()}`\n const issues = await this.api(path)\n for (const issue of issues) items.push(this.mapIssue(issue, project))\n } catch (e: any) {\n this.log(`[business] GitLab listAll failed on ${project}: ${e.message}`)\n }\n }\n\n // Client-side negative-label filter (GitLab API supports `not[labels]` only\n // via the `not[]` nested query form, which isn't convenient via URLSearchParams).\n let result = items\n if (opts.notLabels?.length) {\n const deny = new Set(opts.notLabels)\n result = result.filter((i) => !(i.labels || []).some((l) => deny.has(l)))\n }\n if (opts.withoutScopedPrefix) {\n const prefix = opts.withoutScopedPrefix + \"::\"\n result = result.filter((i) => !(i.labels || []).some((l) => l.startsWith(prefix)))\n }\n return result\n }\n\n /** Close or reopen an issue via state_event. */\n async setState(itemId: string, to: \"close\" | \"reopen\"): Promise<void> {\n const { project, iid, type } = this.parseId(itemId)\n const seg = type === \"issue\" ? \"issues\" : \"merge_requests\"\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?state_event=${to}`,\n { method: \"PUT\" },\n )\n }\n\n /** Fetch a single issue's full detail. */\n async getItem(itemId: string): Promise<WorkDetail | null> {\n const { project, iid } = this.parseId(itemId)\n try {\n const issue = await this.api(\n `/projects/${encodeURIComponent(project)}/issues/${iid}?with_labels_details=true`,\n )\n const base = this.mapIssue(issue, project)\n return {\n ...base,\n descriptionHtml: issue.description_html || undefined,\n createdAt: issue.created_at,\n author: issue.author ? {\n username: issue.author.username, name: issue.author.name, avatarUrl: issue.author.avatar_url,\n } : undefined,\n allLabels: Array.isArray(issue.labels)\n ? (typeof issue.labels[0] === \"object\" ? issue.labels.map((l: any) => l.name) : issue.labels)\n : [],\n }\n } catch (e: any) {\n this.log(`[board] GitLab getItem failed for ${itemId}: ${e.message}`)\n return null\n }\n }\n\n /** Update fields on an issue. Accepts string usernames for assignees. */\n async updateItem(\n itemId: string,\n patch: {\n title?: string\n description?: string\n labels?: string[]\n addLabels?: string[]\n removeLabels?: string[]\n assigneeUsernames?: string[]\n milestoneTitle?: string | null\n },\n ): Promise<WorkItem> {\n const { project, iid } = this.parseId(itemId)\n\n // Resolve assignee usernames → user ids (parallel).\n let assigneeIds: number[] | undefined\n if (patch.assigneeUsernames) {\n const results = await Promise.all(patch.assigneeUsernames.map(async (u) => {\n try {\n const users = await this.api(`/users?username=${encodeURIComponent(u)}`)\n return Array.isArray(users) && users[0]?.id ? users[0].id : null\n } catch { return null }\n }))\n assigneeIds = results.filter((x): x is number => typeof x === \"number\")\n }\n\n // Resolve milestone title → id (null clears the milestone).\n let milestoneId: number | 0 | undefined\n if (patch.milestoneTitle !== undefined) {\n if (patch.milestoneTitle === null || patch.milestoneTitle === \"\") {\n milestoneId = 0\n } else {\n try {\n const ms = await this.api(\n `/projects/${encodeURIComponent(project)}/milestones?title=${encodeURIComponent(patch.milestoneTitle)}`,\n )\n if (Array.isArray(ms) && ms[0]?.id) milestoneId = ms[0].id\n } catch (e: any) {\n this.log(`[board] milestone lookup failed for \"${patch.milestoneTitle}\": ${e.message}`)\n }\n }\n }\n\n const body: Record<string, unknown> = {}\n if (patch.title !== undefined) body.title = patch.title\n if (patch.description !== undefined) body.description = patch.description\n if (patch.labels !== undefined) body.labels = patch.labels.join(\",\")\n if (patch.addLabels?.length) body.add_labels = patch.addLabels.join(\",\")\n if (patch.removeLabels?.length) body.remove_labels = patch.removeLabels.join(\",\")\n if (assigneeIds !== undefined) body.assignee_ids = assigneeIds\n if (milestoneId !== undefined) body.milestone_id = milestoneId\n\n const issue = await this.api(`/projects/${encodeURIComponent(project)}/issues/${iid}`, {\n method: \"PUT\",\n body: JSON.stringify(body),\n })\n return this.mapIssue(issue, project)\n }\n\n /** List project members (direct + inherited). Deduped by username across projects. */\n async listMembers(): Promise<WorkMember[]> {\n const byUsername = new Map<string, WorkMember>()\n for (const project of this.projects) {\n try {\n const members = await this.api(\n `/projects/${encodeURIComponent(project)}/members/all?per_page=100`,\n )\n for (const m of members) {\n if (m.username && !byUsername.has(m.username)) {\n byUsername.set(m.username, {\n username: m.username, name: m.name, avatarUrl: m.avatar_url,\n })\n }\n }\n } catch (e: any) {\n this.log(`[board] listMembers failed for ${project}: ${e.message}`)\n }\n }\n return [...byUsername.values()].sort((a, b) => a.username.localeCompare(b.username))\n }\n\n /** List milestones (active first, then closed). Deduped by title across projects. */\n async listMilestones(): Promise<WorkMilestone[]> {\n const byTitle = new Map<string, WorkMilestone>()\n for (const project of this.projects) {\n try {\n const ms = await this.api(\n `/projects/${encodeURIComponent(project)}/milestones?per_page=100`,\n )\n for (const m of ms) {\n if (m.title && !byTitle.has(m.title)) {\n byTitle.set(m.title, {\n id: m.id, title: m.title, state: m.state, dueDate: m.due_date,\n })\n }\n }\n } catch (e: any) {\n this.log(`[board] listMilestones failed for ${project}: ${e.message}`)\n }\n }\n const all = [...byTitle.values()]\n all.sort((a, b) => {\n if (a.state !== b.state) return a.state === \"active\" ? -1 : 1\n return a.title.localeCompare(b.title)\n })\n return all\n }\n\n async create(input: WorkCreateInput): Promise<WorkItem> {\n const project = input.projectHint || this.projects[0]\n if (!project) throw new Error(\"GitLabWorkSource.create requires projectHint or configured projects\")\n\n // Resolve assignee usernames → user ids. Prefer explicit `assigneeUsernames`,\n // fall back to agentId → configured mapping for legacy callers.\n let assignee_ids: number[] | undefined\n const usernames = input.assigneeUsernames?.length\n ? input.assigneeUsernames\n : (input.assignee ? (this.agentUsernames[input.assignee] || []).slice(0, 1) : [])\n if (usernames.length) {\n const results = await Promise.all(usernames.map(async (u) => {\n try {\n const users = await this.api(`/users?username=${encodeURIComponent(u)}`)\n return Array.isArray(users) && users[0]?.id ? users[0].id : null\n } catch (e: any) {\n this.log(`[board] user lookup failed for ${u}: ${e.message}`)\n return null\n }\n }))\n assignee_ids = results.filter((x): x is number => typeof x === \"number\")\n }\n\n // Resolve milestone title → id.\n let milestone_id: number | undefined\n if (input.milestoneTitle) {\n try {\n const ms = await this.api(\n `/projects/${encodeURIComponent(project)}/milestones?title=${encodeURIComponent(input.milestoneTitle)}`,\n )\n if (Array.isArray(ms) && ms[0]?.id) milestone_id = ms[0].id\n } catch (e: any) {\n this.log(`[board] milestone lookup failed for \"${input.milestoneTitle}\": ${e.message}`)\n }\n }\n\n const body: Record<string, unknown> = {\n title: input.title,\n description: input.description,\n labels: (input.labels || []).join(\",\"),\n }\n if (assignee_ids?.length) body.assignee_ids = assignee_ids\n if (milestone_id !== undefined) body.milestone_id = milestone_id\n\n const issue = await this.api(`/projects/${encodeURIComponent(project)}/issues`, {\n method: \"POST\",\n body: JSON.stringify(body),\n })\n return this.mapIssue(issue, project)\n }\n\n async transition(itemId: string, toLabel: string, fromLabel?: string): Promise<void> {\n // Legacy single add/remove — boards use transitionMany.\n await this.transitionMany(itemId, toLabel ? [toLabel] : [], fromLabel ? [fromLabel] : [])\n }\n\n /** Multi-label transition; used by board drag-drop (scoped labels produce pairs). */\n async transitionMany(itemId: string, add: string[], remove: string[]): Promise<void> {\n const { project, iid, type } = this.parseId(itemId)\n const seg = type === \"issue\" ? \"issues\" : \"merge_requests\"\n const params = new URLSearchParams()\n if (add.length) params.set(\"add_labels\", add.join(\",\"))\n if (remove.length) params.set(\"remove_labels\", remove.join(\",\"))\n if (!params.toString()) return\n await this.api(\n `/projects/${encodeURIComponent(project)}/${seg}/${iid}?${params.toString()}`,\n { method: \"PUT\" },\n )\n }\n\n private parseId(itemId: string): { project: string; type: \"issue\" | \"mr\"; iid: string } {\n // \"gitlab:<project>:issue:<iid>\" (project may contain a slash → it's joined)\n const body = itemId.replace(/^gitlab:/, \"\")\n const parts = body.split(\":\")\n const iid = parts.pop() as string\n const type = parts.pop() === \"mr\" ? \"mr\" : \"issue\"\n const project = parts.join(\":\")\n return { project, type, iid }\n }\n}\n\n// ---------- Factory ----------\n\nexport function createWorkSource(\n business: BusinessConfig,\n daemon: DaemonConfig,\n log: (...args: unknown[]) => void,\n): WorkSource {\n const ws: BusinessWorkSource = business.workSource\n switch (ws.type) {\n case \"backlog\":\n return new BacklogWorkSource(ws.path, daemon, log)\n\n case \"wiki\":\n return new WikiWorkSource(ws.path, ws.glob)\n\n case \"gitlab\": {\n const gl = daemon.channels.gitlab\n if (!gl?.token) {\n throw new Error(\"business.workSource.type=gitlab requires channels.gitlab.token\")\n }\n const agentUsernames: Record<string, string[]> = {}\n for (const mapping of gl.agentMappings) {\n if (mapping.gitlabUsernames.length) {\n agentUsernames[mapping.agentId] = mapping.gitlabUsernames\n }\n }\n const projects = ws.projects.length\n ? ws.projects\n : gl.routes.map((r) => r.project)\n return new GitLabWorkSource(gl.host, gl.token, projects, agentUsernames, log)\n }\n }\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from \"fs\"\nimport { dirname, resolve } from \"path\"\nimport { randomUUID } from \"crypto\"\nimport { z } from \"zod\"\n\n// --- Structured backlog store (Plan B + import) ---\n//\n// Replaces the legacy `.agentx/backlog.md` GFM checklist as the canonical\n// store when `.agentx/backlog.json` is present. The markdown view is\n// regenerated on every save so humans still get a readable diff.\n//\n// Why structured: importing issues from gitlab/github needs a stable\n// linkage back to the source (project + iid + url) so mutations\n// (assignee, labels, status, milestone) can be pushed upstream by the\n// sync-back layer. Encoding that in markdown comments is brittle.\n\nconst sourceSchema = z.object({\n type: z.enum([\"gitlab\", \"github\"]),\n host: z.string().optional(),\n project: z.string(),\n iid: z.number().int().positive(),\n url: z.string(),\n})\n\nconst itemSchema = z.object({\n id: z.string(),\n title: z.string(),\n description: z.string().optional(),\n assignee: z.string().optional(),\n labels: z.array(z.string()).default([]),\n milestone: z.string().optional(),\n status: z.enum([\"todo\", \"doing\", \"blocked\", \"done\"]).default(\"todo\"),\n priority: z.number().int().optional(),\n estimatedSeconds: z.number().int().optional(),\n source: sourceSchema.optional(),\n importedAt: z.string().optional(),\n createdAt: z.string(),\n updatedAt: z.string(),\n blocker: z.string().optional(),\n doneNote: z.string().optional(),\n})\n\nconst fileSchema = z.object({\n version: z.literal(1),\n items: z.array(itemSchema).default([]),\n})\n\nexport type BacklogSource = z.infer<typeof sourceSchema>\nexport type BacklogItem = z.infer<typeof itemSchema>\nexport type BacklogFile = z.infer<typeof fileSchema>\n\nexport class BacklogStore {\n constructor(private path: string) {}\n\n /** Absolute path of the JSON store. */\n jsonPath(): string {\n return resolve(process.cwd(), this.path).replace(/\\.md$/i, \".json\")\n }\n\n /** Absolute path of the markdown view (alongside the JSON store). */\n mdPath(): string {\n return resolve(process.cwd(), this.path).replace(/\\.json$/i, \".md\")\n }\n\n exists(): boolean {\n return existsSync(this.jsonPath())\n }\n\n load(): BacklogFile {\n const p = this.jsonPath()\n if (!existsSync(p)) return { version: 1, items: [] }\n const raw = readFileSync(p, \"utf-8\")\n try {\n return fileSchema.parse(JSON.parse(raw))\n } catch (e: any) {\n throw new Error(`Backlog file invalid (${p}): ${e.message}`)\n }\n }\n\n save(file: BacklogFile): void {\n const jsonP = this.jsonPath()\n mkdirSync(dirname(jsonP), { recursive: true })\n writeFileSync(jsonP, JSON.stringify(file, null, 2) + \"\\n\")\n writeFileSync(this.mdPath(), this.toMarkdown(file))\n }\n\n list(): BacklogItem[] {\n return this.load().items\n }\n\n findById(id: string): BacklogItem | undefined {\n return this.list().find((i) => i.id === id)\n }\n\n /** Add an item; rejects duplicates by id. Returns the saved item. */\n add(input: Omit<BacklogItem, \"createdAt\" | \"updatedAt\"> & { createdAt?: string }): BacklogItem {\n const file = this.load()\n if (file.items.some((i) => i.id === input.id)) {\n throw new Error(`backlog item ${input.id} already exists`)\n }\n const now = new Date().toISOString()\n const item: BacklogItem = {\n ...input,\n labels: input.labels ?? [],\n status: input.status ?? \"todo\",\n createdAt: input.createdAt ?? now,\n updatedAt: now,\n }\n file.items.push(item)\n this.save(file)\n return item\n }\n\n /** Add many; skips items whose id already exists, returns the inserted set. */\n addMany(inputs: Array<Omit<BacklogItem, \"createdAt\" | \"updatedAt\"> & { createdAt?: string }>): BacklogItem[] {\n const file = this.load()\n const existing = new Set(file.items.map((i) => i.id))\n const now = new Date().toISOString()\n const added: BacklogItem[] = []\n for (const input of inputs) {\n if (existing.has(input.id)) continue\n const item: BacklogItem = {\n ...input,\n labels: input.labels ?? [],\n status: input.status ?? \"todo\",\n createdAt: input.createdAt ?? now,\n updatedAt: now,\n }\n file.items.push(item)\n added.push(item)\n existing.add(item.id)\n }\n if (added.length) this.save(file)\n return added\n }\n\n update(id: string, patch: Partial<BacklogItem>): BacklogItem {\n const file = this.load()\n const idx = file.items.findIndex((i) => i.id === id)\n if (idx < 0) throw new Error(`backlog item ${id} not found`)\n const updated: BacklogItem = {\n ...file.items[idx],\n ...patch,\n id: file.items[idx].id,\n createdAt: file.items[idx].createdAt,\n updatedAt: new Date().toISOString(),\n }\n file.items[idx] = updated\n this.save(file)\n return updated\n }\n\n remove(id: string): boolean {\n const file = this.load()\n const before = file.items.length\n file.items = file.items.filter((i) => i.id !== id)\n if (file.items.length === before) return false\n this.save(file)\n return true\n }\n\n /** Render a human-readable markdown view (generated, not authoritative). */\n toMarkdown(file: BacklogFile): string {\n const lines: string[] = [\n \"# Backlog\",\n \"\",\n \"_Generated from `backlog.json`. Do not edit by hand — use `agentx backlog` commands._\",\n \"\",\n ]\n const buckets: Record<BacklogItem[\"status\"], BacklogItem[]> = {\n doing: [], blocked: [], todo: [], done: [],\n }\n for (const item of file.items) buckets[item.status].push(item)\n const sections: Array<[BacklogItem[\"status\"], string]> = [\n [\"doing\", \"Doing\"], [\"blocked\", \"Blocked\"], [\"todo\", \"To Do\"], [\"done\", \"Done\"],\n ]\n for (const [key, label] of sections) {\n if (!buckets[key].length) continue\n lines.push(`## ${label}`, \"\")\n for (const it of buckets[key]) {\n const check = key === \"done\" ? \"x\" : \" \"\n const mention = it.assignee ? `@${it.assignee} ` : \"\"\n const time = it.estimatedSeconds\n ? ` [time: ${Math.round(it.estimatedSeconds / 60)}m]`\n : \"\"\n const src = it.source ? ` (${it.source.type}: ${it.source.project}#${it.source.iid})` : \"\"\n lines.push(`- [${check}] ${mention}${it.title}${time}${src}`)\n if (it.source?.url) lines.push(` - <${it.source.url}>`)\n if (it.labels.length) lines.push(` - labels: ${it.labels.join(\", \")}`)\n if (it.milestone) lines.push(` - milestone: ${it.milestone}`)\n if (it.blocker) lines.push(` - BLOCKED: ${it.blocker}`)\n }\n lines.push(\"\")\n }\n return lines.join(\"\\n\")\n }\n}\n\n/** Generate a stable BacklogItem.id for a fresh manual item. */\nexport function manualBacklogId(): string {\n return `manual:${randomUUID()}`\n}\n\n/** Build the canonical id for an imported source issue. */\nexport function sourceBacklogId(source: BacklogSource): string {\n return `${source.type}:${source.project}:${source.iid}`\n}\n","import type { BacklogItem, BacklogSource } from \"./backlog-store\"\nimport type { DaemonConfig } from \"@/daemon/config\"\n\n// --- Sync-back layer: backlog item mutations → upstream issue ---\n//\n// When a backlog item carries a `source` ref (gitlab or github), changes\n// applied via the backlog work-source need to flow back to the original\n// issue: assignees, labels, title, description, milestone, open/close.\n// Otherwise the imported view drifts from upstream and humans browsing\n// the source see stale state.\n//\n// This module is intentionally stateless — callers pass the patch they\n// just applied to the local item; we forward the same shape to the\n// matching upstream API.\n\nexport interface BacklogPatch {\n title?: string\n description?: string\n /** Replace labels wholesale. Use addLabels/removeLabels for incremental. */\n labels?: string[]\n addLabels?: string[]\n removeLabels?: string[]\n /** Username on the source (NOT agentId — caller resolves the mapping). */\n assigneeUsernames?: string[]\n /** Title of the milestone, or empty string to clear. */\n milestoneTitle?: string | null\n /** Open/close transition. */\n state?: \"open\" | \"closed\"\n /** Optional comment to post on the upstream issue. */\n note?: string\n}\n\nexport interface SyncBackResult {\n ok: boolean\n url?: string\n error?: string\n /** Each upstream call we attempted, in order. */\n attempts: Array<{ kind: string; ok: boolean; status?: number; error?: string }>\n}\n\n/** Resolve agentIds in a patch to upstream usernames using the daemon mappings. */\nexport function resolveAssigneesForSource(\n agentIds: string[],\n source: BacklogSource,\n daemon: DaemonConfig,\n): string[] {\n const out: string[] = []\n if (source.type === \"gitlab\") {\n const mappings = daemon.channels.gitlab?.agentMappings ?? []\n for (const agentId of agentIds) {\n const m = mappings.find((x) => x.agentId === agentId)\n if (m?.gitlabUsernames?.length) out.push(m.gitlabUsernames[0])\n else out.push(agentId)\n }\n } else if (source.type === \"github\") {\n const mappings = daemon.channels.github?.agentMappings ?? []\n for (const agentId of agentIds) {\n const m = mappings.find((x) => x.agentId === agentId)\n if (m?.githubUsernames?.length) out.push(m.githubUsernames[0])\n else out.push(agentId)\n }\n }\n return out\n}\n\n/** Push a backlog patch to the upstream source referenced by the item. */\nexport async function syncBacklogItemToSource(\n item: BacklogItem,\n patch: BacklogPatch,\n daemon: DaemonConfig,\n log: (...args: unknown[]) => void = () => {},\n): Promise<SyncBackResult> {\n if (!item.source) return { ok: true, attempts: [] }\n if (item.source.type === \"gitlab\") return syncToGitLab(item.source, patch, daemon, log)\n if (item.source.type === \"github\") return syncToGitHub(item.source, patch, daemon, log)\n return { ok: false, error: `unknown source.type ${(item.source as any).type}`, attempts: [] }\n}\n\n// ---------- GitLab ----------\n\nasync function syncToGitLab(\n source: BacklogSource,\n patch: BacklogPatch,\n daemon: DaemonConfig,\n log: (...args: unknown[]) => void,\n): Promise<SyncBackResult> {\n const gl = daemon.channels.gitlab\n const token = gl?.token\n const host = gl?.host || (source.host ? `https://${source.host}` : undefined)\n if (!token || !host) {\n return { ok: false, error: \"gitlab token/host not configured\", attempts: [] }\n }\n const attempts: SyncBackResult[\"attempts\"] = []\n\n const api = async (path: string, init: RequestInit = {}): Promise<Response> => {\n return fetch(`${host}/api/v4${path}`, {\n ...init,\n headers: {\n \"PRIVATE-TOKEN\": token,\n \"Content-Type\": \"application/json\",\n ...(init.headers || {}),\n },\n })\n }\n\n const projectEnc = encodeURIComponent(source.project)\n\n // Resolve assignee usernames → ids (parallel) — only when we need them.\n let assigneeIds: number[] | undefined\n if (patch.assigneeUsernames !== undefined) {\n const ids = await Promise.all(patch.assigneeUsernames.map(async (u) => {\n try {\n const res = await fetch(`${host}/api/v4/users?username=${encodeURIComponent(u)}`, {\n headers: { \"PRIVATE-TOKEN\": token },\n })\n if (!res.ok) return null\n const arr: any = await res.json()\n return Array.isArray(arr) && arr[0]?.id ? arr[0].id : null\n } catch { return null }\n }))\n assigneeIds = ids.filter((x): x is number => typeof x === \"number\")\n }\n\n // Resolve milestone title → id.\n let milestoneId: number | 0 | undefined\n if (patch.milestoneTitle !== undefined) {\n if (patch.milestoneTitle === null || patch.milestoneTitle === \"\") {\n milestoneId = 0\n } else {\n try {\n const res = await api(`/projects/${projectEnc}/milestones?title=${encodeURIComponent(patch.milestoneTitle)}`)\n if (res.ok) {\n const arr: any = await res.json()\n if (Array.isArray(arr) && arr[0]?.id) milestoneId = arr[0].id\n }\n } catch (e: any) {\n log(`[backlog-sync] milestone lookup failed: ${e.message}`)\n }\n }\n }\n\n // Build the PUT body.\n const body: Record<string, unknown> = {}\n if (patch.title !== undefined) body.title = patch.title\n if (patch.description !== undefined) body.description = patch.description\n if (patch.labels !== undefined) body.labels = patch.labels.join(\",\")\n if (patch.addLabels?.length) body.add_labels = patch.addLabels.join(\",\")\n if (patch.removeLabels?.length) body.remove_labels = patch.removeLabels.join(\",\")\n if (assigneeIds !== undefined) body.assignee_ids = assigneeIds\n if (milestoneId !== undefined) body.milestone_id = milestoneId\n if (patch.state) body.state_event = patch.state === \"closed\" ? \"close\" : \"reopen\"\n\n if (Object.keys(body).length) {\n try {\n const res = await api(`/projects/${projectEnc}/issues/${source.iid}`, {\n method: \"PUT\",\n body: JSON.stringify(body),\n })\n attempts.push({ kind: \"PUT issue\", ok: res.ok, status: res.status })\n if (!res.ok) {\n const text = await res.text().catch(() => \"\")\n return { ok: false, attempts, error: `PUT issue ${res.status}: ${text.slice(0, 200)}`, url: source.url }\n }\n } catch (e: any) {\n attempts.push({ kind: \"PUT issue\", ok: false, error: e.message })\n return { ok: false, attempts, error: e.message, url: source.url }\n }\n }\n\n if (patch.note) {\n try {\n const res = await api(`/projects/${projectEnc}/issues/${source.iid}/notes`, {\n method: \"POST\",\n body: JSON.stringify({ body: patch.note }),\n })\n attempts.push({ kind: \"POST note\", ok: res.ok, status: res.status })\n } catch (e: any) {\n attempts.push({ kind: \"POST note\", ok: false, error: e.message })\n }\n }\n\n return { ok: attempts.every((a) => a.ok), attempts, url: source.url }\n}\n\n// ---------- GitHub ----------\n\nasync function syncToGitHub(\n source: BacklogSource,\n patch: BacklogPatch,\n daemon: DaemonConfig,\n log: (...args: unknown[]) => void,\n): Promise<SyncBackResult> {\n const gh = daemon.channels.github\n const token = gh?.token\n if (!token) {\n return { ok: false, error: \"github token not configured\", attempts: [] }\n }\n const attempts: SyncBackResult[\"attempts\"] = []\n\n const api = async (path: string, init: RequestInit = {}): Promise<Response> => {\n return fetch(`https://api.github.com${path}`, {\n ...init,\n headers: {\n \"Authorization\": `Bearer ${token}`,\n \"Accept\": \"application/vnd.github+json\",\n \"X-GitHub-Api-Version\": \"2022-11-28\",\n ...(init.headers || {}),\n },\n })\n }\n\n const repo = source.project // GitHub IDs use \"owner/repo\" verbatim in the URL path.\n\n // Resolve milestone title → number.\n let milestoneNumber: number | null | undefined\n if (patch.milestoneTitle !== undefined) {\n if (patch.milestoneTitle === null || patch.milestoneTitle === \"\") {\n milestoneNumber = null\n } else {\n try {\n const res = await api(`/repos/${repo}/milestones?state=all&per_page=100`)\n if (res.ok) {\n const arr: any = await res.json()\n const found = Array.isArray(arr) ? arr.find((m: any) => m.title === patch.milestoneTitle) : null\n if (found?.number) milestoneNumber = found.number\n }\n } catch (e: any) {\n log(`[backlog-sync] github milestone lookup failed: ${e.message}`)\n }\n }\n }\n\n // GitHub doesn't support add/remove labels in the issue PATCH the way\n // GitLab does — labels is a wholesale set. If only addLabels/removeLabels\n // were provided we read+merge.\n let nextLabels: string[] | undefined\n if (patch.labels !== undefined) {\n nextLabels = patch.labels\n } else if (patch.addLabels?.length || patch.removeLabels?.length) {\n try {\n const res = await api(`/repos/${repo}/issues/${source.iid}`)\n if (res.ok) {\n const issue: any = await res.json()\n const current: string[] = Array.isArray(issue.labels)\n ? issue.labels.map((l: any) => (typeof l === \"string\" ? l : l.name))\n : []\n const adds = new Set([...current, ...(patch.addLabels ?? [])])\n for (const r of patch.removeLabels ?? []) adds.delete(r)\n nextLabels = [...adds]\n }\n } catch (e: any) {\n log(`[backlog-sync] github label fetch failed: ${e.message}`)\n }\n }\n\n const body: Record<string, unknown> = {}\n if (patch.title !== undefined) body.title = patch.title\n if (patch.description !== undefined) body.body = patch.description\n if (nextLabels !== undefined) body.labels = nextLabels\n if (patch.assigneeUsernames !== undefined) body.assignees = patch.assigneeUsernames\n if (milestoneNumber !== undefined) body.milestone = milestoneNumber\n if (patch.state) body.state = patch.state\n\n if (Object.keys(body).length) {\n try {\n const res = await api(`/repos/${repo}/issues/${source.iid}`, {\n method: \"PATCH\",\n body: JSON.stringify(body),\n })\n attempts.push({ kind: \"PATCH issue\", ok: res.ok, status: res.status })\n if (!res.ok) {\n const text = await res.text().catch(() => \"\")\n return { ok: false, attempts, error: `PATCH issue ${res.status}: ${text.slice(0, 200)}`, url: source.url }\n }\n } catch (e: any) {\n attempts.push({ kind: \"PATCH issue\", ok: false, error: e.message })\n return { ok: false, attempts, error: e.message, url: source.url }\n }\n }\n\n if (patch.note) {\n try {\n const res = await api(`/repos/${repo}/issues/${source.iid}/comments`, {\n method: \"POST\",\n body: JSON.stringify({ body: patch.note }),\n })\n attempts.push({ kind: \"POST comment\", ok: res.ok, status: res.status })\n } catch (e: any) {\n attempts.push({ kind: \"POST comment\", ok: false, error: e.message })\n }\n }\n\n return { ok: attempts.every((a) => a.ok), attempts, url: source.url }\n}\n","import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, rmSync, statSync } from \"fs\"\nimport { resolve, relative, dirname, join, extname, basename } from \"path\"\n\n// --- Scoped file operations for the admin panel ---\n//\n// Lets the dashboard read/write identity files (CLAUDE.md / SOUL.md /\n// IDENTITY.md) and skill files (.claude/skills/<slug>/SKILL.md) WITHIN an\n// agent's workspace without ever touching the rest of the filesystem.\n// Every public function resolves the requested path against the agent's\n// workspace and refuses anything that would escape via .. symlinks, etc.\n\nconst MAX_FILE_BYTES = 200 * 1024 // 200 KB — plenty for CLAUDE.md / skills.\nconst ALLOWED_EXTENSIONS = new Set([\".md\", \".markdown\", \".txt\"])\n/** Extensions the skill-tree viewer is allowed to read/write. Broader than\n * identity editing — skills include shell scripts, lockfiles, configs. Kept\n * as an allow-list so a malicious prompt can't touch an arbitrary binary. */\nconst SKILL_EDITABLE_EXTENSIONS = new Set([\n \".md\", \".markdown\", \".txt\", \".json\", \".jsonc\", \".yaml\", \".yml\", \".toml\",\n \".sh\", \".bash\", \".zsh\", \".py\", \".js\", \".mjs\", \".ts\", \".tsx\", \".jsx\",\n \".css\", \".html\", \".lock\", \".cfg\", \".ini\", \".env\", \".gitignore\",\n])\n/** Depth limit when walking a skill dir tree — defensive against loops. */\nconst SKILL_TREE_MAX_DEPTH = 6\nconst IDENTITY_FILENAMES = new Set([\n \"CLAUDE.md\", \"SOUL.md\", \"IDENTITY.md\", \"PERSONA.md\", \"AGENT.md\", \"BOOTSTRAP.md\",\n])\n\nexport interface FileOverview {\n identity: Array<{\n path: string // relative to workspace, e.g. \"CLAUDE.md\"\n title: string\n size: number\n exists: boolean // canonical files can be listed as templates even if missing\n }>\n skills: Array<{\n slug: string // dir name under .claude/skills/\n title: string // first # heading in SKILL.md or fallback to slug\n size: number\n path: string // relative to workspace, e.g. \".claude/skills/foo/SKILL.md\"\n }>\n workspace: string // absolute, for display only\n}\n\n/**\n * Resolve a user-supplied path to an absolute path strictly within `workspace`.\n * Throws if the result would escape. Returns the normalised absolute path.\n */\nfunction resolveInWorkspace(workspace: string, relPath: string): string {\n if (!workspace) throw new Error(\"Agent workspace is not configured.\")\n const wsAbs = resolve(workspace)\n if (!existsSync(wsAbs)) {\n // Create the workspace lazily — a fresh agent scaffolded via the wizard\n // may have an empty directory created but not yet with file structure.\n mkdirSync(wsAbs, { recursive: true })\n }\n // Reject absolute-path attempts outright.\n if (!relPath || relPath.startsWith(\"/\") || relPath.match(/^[a-zA-Z]:/)) {\n throw new Error(`Path must be relative to the workspace: ${relPath}`)\n }\n const target = resolve(wsAbs, relPath)\n const rel = relative(wsAbs, target)\n // rel === \"\" means target IS the workspace dir — fine for list ops.\n // rel starts with \"..\" iff target is outside the workspace.\n if (rel.startsWith(\"..\")) {\n throw new Error(`Path escapes the workspace: ${relPath}`)\n }\n return target\n}\n\nfunction assertEditable(absPath: string): void {\n const ext = extname(absPath).toLowerCase()\n if (!ALLOWED_EXTENSIONS.has(ext)) {\n throw new Error(`Only text files (.md, .markdown, .txt) can be edited from the admin panel (got ${ext}).`)\n }\n}\n\nfunction extractTitle(markdown: string, fallback: string): string {\n const line = markdown.split(/\\r?\\n/).find((l) => l.trim().startsWith(\"#\"))\n if (!line) return fallback\n return line.replace(/^#+\\s*/, \"\").trim().slice(0, 120) || fallback\n}\n\n/**\n * List the identity files + skills for an agent. Identity \"canonical\" names\n * that don't exist are still reported (exists: false) so the UI can offer to\n * create them with a sensible default.\n */\nexport function listAgentFiles(workspace: string): FileOverview {\n const wsAbs = resolveInWorkspace(workspace, \".\")\n const identity: FileOverview[\"identity\"] = []\n const seen = new Set<string>()\n\n // Every .md in the workspace root is identity material; canonical names\n // sort first.\n let rootEntries: string[] = []\n try { rootEntries = readdirSync(wsAbs) } catch { /* empty */ }\n for (const name of IDENTITY_FILENAMES) {\n const abs = join(wsAbs, name)\n if (existsSync(abs) && statSync(abs).isFile()) {\n const size = statSync(abs).size\n const content = size > 0 && size < MAX_FILE_BYTES ? safeRead(abs) : \"\"\n identity.push({ path: name, title: extractTitle(content, name), size, exists: true })\n seen.add(name)\n } else {\n identity.push({ path: name, title: name, size: 0, exists: false })\n seen.add(name)\n }\n }\n for (const name of rootEntries) {\n if (seen.has(name)) continue\n const abs = join(wsAbs, name)\n try {\n const st = statSync(abs)\n if (!st.isFile()) continue\n if (!ALLOWED_EXTENSIONS.has(extname(name).toLowerCase())) continue\n const content = st.size > 0 && st.size < MAX_FILE_BYTES ? safeRead(abs) : \"\"\n identity.push({ path: name, title: extractTitle(content, name), size: st.size, exists: true })\n } catch { /* skip unreadable */ }\n }\n\n // Skills live under .claude/skills/<slug>/SKILL.md\n const skills: FileOverview[\"skills\"] = []\n const skillsRoot = join(wsAbs, \".claude\", \"skills\")\n if (existsSync(skillsRoot)) {\n try {\n for (const slug of readdirSync(skillsRoot)) {\n const dir = join(skillsRoot, slug)\n let st\n try { st = statSync(dir) } catch { continue }\n if (!st.isDirectory()) continue\n const file = join(dir, \"SKILL.md\")\n if (!existsSync(file)) continue\n const stf = statSync(file)\n const content = stf.size < MAX_FILE_BYTES ? safeRead(file) : \"\"\n skills.push({\n slug,\n title: extractTitle(content, slug),\n size: stf.size,\n path: `.claude/skills/${slug}/SKILL.md`,\n })\n }\n } catch { /* */ }\n }\n skills.sort((a, b) => a.slug.localeCompare(b.slug))\n\n return { identity, skills, workspace: wsAbs }\n}\n\nfunction safeRead(abs: string): string {\n try { return readFileSync(abs, \"utf-8\") } catch { return \"\" }\n}\n\n/**\n * Read a single file within the workspace. Throws if the path is outside the\n * workspace, has a disallowed extension, or doesn't exist.\n */\nexport function readAgentFile(workspace: string, relPath: string): { path: string; content: string } {\n const abs = resolveInWorkspace(workspace, relPath)\n assertEditable(abs)\n if (!existsSync(abs)) return { path: relPath, content: \"\" }\n if (statSync(abs).size > MAX_FILE_BYTES) {\n throw new Error(`File is too large to edit in the admin panel (> ${MAX_FILE_BYTES / 1024} KB).`)\n }\n return { path: relPath, content: readFileSync(abs, \"utf-8\") }\n}\n\n/**\n * Write a file. Creates parent dirs as needed. Rejects extensions / size\n * outside the allow-list the same way readAgentFile does.\n */\nexport function writeAgentFile(workspace: string, relPath: string, content: string): { path: string; bytes: number } {\n const abs = resolveInWorkspace(workspace, relPath)\n assertEditable(abs)\n const buf = Buffer.from(content, \"utf-8\")\n if (buf.length > MAX_FILE_BYTES) {\n throw new Error(`File too large (${buf.length} > ${MAX_FILE_BYTES} bytes).`)\n }\n mkdirSync(dirname(abs), { recursive: true })\n writeFileSync(abs, buf)\n return { path: relPath, bytes: buf.length }\n}\n\n/**\n * Create a new skill scaffold at .claude/skills/<slug>/SKILL.md.\n */\nexport function createAgentSkill(\n workspace: string,\n slug: string,\n opts: { title?: string; content?: string } = {},\n): { slug: string; path: string } {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) {\n throw new Error(\"Skill slug must be lowercase (letters, digits, -, _) and start with a letter or digit.\")\n }\n const relPath = `.claude/skills/${slug}/SKILL.md`\n const abs = resolveInWorkspace(workspace, relPath)\n if (existsSync(abs)) throw new Error(`Skill \"${slug}\" already exists.`)\n mkdirSync(dirname(abs), { recursive: true })\n const body = opts.content?.trim()\n ? opts.content\n : defaultSkillTemplate(slug, opts.title)\n writeFileSync(abs, body)\n return { slug, path: relPath }\n}\n\n/**\n * Remove a skill directory (.claude/skills/<slug>). Scoped to the workspace\n * so we can never touch anything else.\n */\nexport function deleteAgentSkill(workspace: string, slug: string): { slug: string } {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n const abs = resolveInWorkspace(workspace, `.claude/skills/${slug}`)\n if (!existsSync(abs)) throw new Error(`Skill \"${slug}\" not found.`)\n const st = statSync(abs)\n if (!st.isDirectory()) throw new Error(`Skill path is not a directory: ${slug}`)\n rmSync(abs, { recursive: true, force: true })\n return { slug }\n}\n\n// --- Skill directory tree + per-file I/O ---\n//\n// A skill is `.claude/skills/<slug>/` — usually SKILL.md plus scripts,\n// lockfiles, configs. The admin UI lets the operator browse the directory\n// tree, edit any text file, and dispatch \"install deps\" through the agent.\n\nexport interface SkillTreeEntry {\n /** Path relative to the skill dir root. Directories end with \"/\". */\n path: string\n kind: \"file\" | \"dir\"\n size: number\n editable: boolean // false for binaries or disallowed extensions\n}\n\nexport interface SkillDepsHint {\n /** Which install command the operator should run (or have an agent run). */\n manager: \"npm\" | \"pnpm\" | \"yarn\" | \"pip\" | \"bundle\" | null\n /** Filename that triggered the detection, relative to skill dir. */\n file: string | null\n /** The exact command we'd recommend. */\n command: string | null\n}\n\n/** List every file in `.claude/skills/<slug>/`, recursively. Scoped so the\n * caller can't escape the skill dir via symlinks or \"..\" segments. */\nexport function listSkillTree(workspace: string, slug: string): SkillTreeEntry[] {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n const skillRoot = resolveInWorkspace(workspace, `.claude/skills/${slug}`)\n if (!existsSync(skillRoot)) throw new Error(`Skill \"${slug}\" not found.`)\n const out: SkillTreeEntry[] = []\n walk(skillRoot, \"\", 0, out)\n out.sort((a, b) => {\n // dirs first, then alphabetical\n if (a.kind !== b.kind) return a.kind === \"dir\" ? -1 : 1\n return a.path.localeCompare(b.path)\n })\n return out\n\n function walk(absDir: string, relDir: string, depth: number, acc: SkillTreeEntry[]): void {\n if (depth > SKILL_TREE_MAX_DEPTH) return\n let entries: string[] = []\n try { entries = readdirSync(absDir) } catch { return }\n for (const name of entries) {\n if (name === \"node_modules\" || name === \".git\") continue\n const abs = join(absDir, name)\n const rel = relDir ? `${relDir}/${name}` : name\n let st\n try { st = statSync(abs) } catch { continue }\n if (st.isDirectory()) {\n acc.push({ path: `${rel}/`, kind: \"dir\", size: 0, editable: false })\n walk(abs, rel, depth + 1, acc)\n } else if (st.isFile()) {\n const ext = extname(name).toLowerCase()\n // name-only allowlist for dotfiles like .env / .gitignore (no extension)\n const allowedByName = SKILL_EDITABLE_EXTENSIONS.has(ext) ||\n SKILL_EDITABLE_EXTENSIONS.has(name.toLowerCase())\n acc.push({\n path: rel,\n kind: \"file\",\n size: st.size,\n editable: allowedByName && st.size < MAX_FILE_BYTES,\n })\n }\n }\n }\n}\n\n/** Read any file in a skill dir. Respects the skill-editable allowlist. */\nexport function readSkillFile(workspace: string, slug: string, relPath: string): { path: string; content: string } {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n if (!relPath || relPath.includes(\"..\")) throw new Error(`Invalid file path: ${relPath}`)\n const abs = resolveInWorkspace(workspace, `.claude/skills/${slug}/${relPath}`)\n if (!existsSync(abs) || !statSync(abs).isFile()) {\n throw new Error(`File not found: ${relPath}`)\n }\n const ext = extname(abs).toLowerCase()\n const name = basename(abs).toLowerCase()\n if (!SKILL_EDITABLE_EXTENSIONS.has(ext) && !SKILL_EDITABLE_EXTENSIONS.has(name)) {\n throw new Error(`File type not editable: ${relPath}`)\n }\n if (statSync(abs).size > MAX_FILE_BYTES) {\n throw new Error(`File too large to edit (> ${MAX_FILE_BYTES / 1024} KB): ${relPath}`)\n }\n return { path: relPath, content: readFileSync(abs, \"utf-8\") }\n}\n\n/** Write any allowed file inside a skill dir. Creates parents. */\nexport function writeSkillFile(\n workspace: string,\n slug: string,\n relPath: string,\n content: string,\n): { path: string; bytes: number } {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n if (!relPath || relPath.includes(\"..\")) throw new Error(`Invalid file path: ${relPath}`)\n const abs = resolveInWorkspace(workspace, `.claude/skills/${slug}/${relPath}`)\n const ext = extname(abs).toLowerCase()\n const name = basename(abs).toLowerCase()\n if (!SKILL_EDITABLE_EXTENSIONS.has(ext) && !SKILL_EDITABLE_EXTENSIONS.has(name)) {\n throw new Error(`File type not editable: ${relPath}`)\n }\n const buf = Buffer.from(content, \"utf-8\")\n if (buf.length > MAX_FILE_BYTES) {\n throw new Error(`File too large (${buf.length} > ${MAX_FILE_BYTES} bytes).`)\n }\n mkdirSync(dirname(abs), { recursive: true })\n writeFileSync(abs, buf)\n return { path: relPath, bytes: buf.length }\n}\n\n/** Detect which dependency manager a skill needs based on the files present. */\nexport function detectSkillDeps(workspace: string, slug: string): SkillDepsHint {\n if (!/^[a-z0-9][a-z0-9_-]*$/.test(slug)) throw new Error(`Invalid skill slug: ${slug}`)\n const skillRoot = resolveInWorkspace(workspace, `.claude/skills/${slug}`)\n if (!existsSync(skillRoot)) throw new Error(`Skill \"${slug}\" not found.`)\n const check = (name: string): boolean => existsSync(join(skillRoot, name))\n if (check(\"pnpm-lock.yaml\")) return { manager: \"pnpm\", file: \"pnpm-lock.yaml\", command: \"pnpm install\" }\n if (check(\"yarn.lock\")) return { manager: \"yarn\", file: \"yarn.lock\", command: \"yarn install\" }\n if (check(\"package.json\")) return { manager: \"npm\", file: \"package.json\", command: \"npm install\" }\n if (check(\"requirements.txt\")) return { manager: \"pip\", file: \"requirements.txt\", command: \"pip install -r requirements.txt\" }\n if (check(\"Gemfile\")) return { manager: \"bundle\", file: \"Gemfile\", command: \"bundle install\" }\n return { manager: null, file: null, command: null }\n}\n\nfunction defaultSkillTemplate(slug: string, title?: string): string {\n const displayTitle = title?.trim() || slug\n return `# ${displayTitle}\n\nDescribe what this skill does and when the agent should use it. Keep it under ~5 lines — the agent reads this to decide whether to invoke the skill.\n\n## When to use\n- Trigger condition 1\n- Trigger condition 2\n\n## How to use\nDocument the arguments, commands, or files the skill expects. Example:\n\n\\`\\`\\`bash\n# Example invocation\nclaude-code ${basename(slug)} --arg value\n\\`\\`\\`\n`\n}\n"],"mappings":"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","names":["readFileSync","writeFileSync","existsSync","readdirSync","statSync","resolve","join","existsSync","mkdirSync","readFileSync","writeFileSync","dirname","resolve","z","sourceSchema","itemSchema","fileSchema","BacklogStore","path","resolve","existsSync","p","raw","readFileSync","e","file","jsonP","mkdirSync","dirname","writeFileSync","id","i","input","now","item","inputs","existing","added","patch","idx","updated","before","lines","buckets","sections","key","label","it","check","mention","time","src","sourceBacklogId","source","resolveAssigneesForSource","agentIds","source","daemon","out","mappings","agentId","m","x","syncBacklogItemToSource","item","patch","log","syncToGitLab","syncToGitHub","gl","token","host","attempts","api","path","init","projectEnc","assigneeIds","u","res","arr","milestoneId","e","body","text","a","repo","milestoneNumber","found","nextLabels","issue","current","l","adds","r","CHECK_RE","MENTION_RE","TIME_RE","parseDuration","s","hMatch","mMatch","BacklogWorkSource","path","daemon","log","BacklogStore","full","resolve","existsSync","readFileSync","lines","writeFileSync","agentId","it","items","i","m","body","mention","timeMatch","title","_opts","itemId","item","updated","usernames","resolveAssigneesForSource","syncBacklogItemToSource","e","update","patch","sourcePatch","lineNo","line","note","stage","WikiWorkSource","root","glob","dir","out","entry","readdirSync","join","statSync","files","file","parts","GitLabWorkSource","host","token","projects","agentUsernames","username","names","issue","project","ns","labels","labelDetails","l","assignees","firstUsername","init","res","projectList","q","issues","iid","type","seg","mins","dur","opts","sinceDays","updatedAfter","params","state","result","deny","prefix","to","assigneeIds","u","users","x","milestoneId","ms","byUsername","members","a","b","byTitle","all","input","assignee_ids","milestone_id","toLabel","fromLabel","add","remove","createWorkSource","business","ws","gl","mapping","r","existsSync","readFileSync","writeFileSync","mkdirSync","readdirSync","rmSync","statSync","resolve","relative","dirname","join","extname","basename","MAX_FILE_BYTES","ALLOWED_EXTENSIONS","SKILL_EDITABLE_EXTENSIONS","SKILL_TREE_MAX_DEPTH","IDENTITY_FILENAMES","resolveInWorkspace","workspace","relPath","wsAbs","target","assertEditable","absPath","ext","extractTitle","markdown","fallback","line","l","listAgentFiles","identity","seen","rootEntries","name","abs","size","content","safeRead","st","skills","skillsRoot","slug","dir","file","stf","a","b","readAgentFile","writeAgentFile","buf","createAgentSkill","opts","body","defaultSkillTemplate","deleteAgentSkill","listSkillTree","skillRoot","out","walk","absDir","relDir","depth","acc","entries","rel","allowedByName","readSkillFile","writeSkillFile","detectSkillDeps","check","title"]}
|
package/dist/chunk-4YCH6IZV.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
var d=Object.defineProperty;var e=(a,b,c)=>b in a?d(a,b,{enumerable:!0,configurable:!0,writable:!0,value:c}):a[b]=c;var f=(a=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(a,{get:(b,c)=>(typeof require<"u"?require:b)[c]}):a)(function(a){if(typeof require<"u")return require.apply(this,arguments);throw new Error('Dynamic require of "'+a+'" is not supported')});var g=(a,b,c)=>(e(a,typeof b!="symbol"?b+"":b,c),c);export{f as a,g as b};
|
|
2
|
-
//# sourceMappingURL=chunk-4YCH6IZV.js.map
|
package/dist/chunk-53GCFZPR.js
DELETED
|
@@ -1,116 +0,0 @@
|
|
|
1
|
-
import{a as A,b as w}from"./chunk-FDMWOPEL.js";import{a as O}from"./chunk-VJUTQAPJ.js";import{writeFileSync as Y,mkdirSync as N,existsSync as R,readFileSync as _e}from"fs";import{resolve as f}from"path";var z=["off","shadow","authoritative"],q={telegram:"INTENT_LEDGER_MODE_TELEGRAM",slack:"INTENT_LEDGER_MODE_SLACK",whatsapp:"INTENT_LEDGER_MODE_WHATSAPP",discord:"INTENT_LEDGER_MODE_DISCORD",gitlab:"INTENT_LEDGER_MODE_GITLAB",github:"INTENT_LEDGER_MODE_GITHUB",workflow:"INTENT_LEDGER_MODE_WORKFLOW",cron:"INTENT_LEDGER_MODE_CRON",mesh:"INTENT_LEDGER_MODE_MESH"},Q="INTENT_LEDGER_MODE";function C(r,e=process.env){let n=$(e[q[r]]);if(n)return n;let t=$(e[Q]);return t||"off"}function $(r){if(typeof r!="string")return null;let e=r.trim().toLowerCase();return z.includes(e)?e:null}import re from"better-sqlite3";import{existsSync as Ie,mkdirSync as ie}from"fs";import{dirname as se,resolve as oe}from"path";import{randomBytes as Z}from"crypto";var M="0123456789ABCDEFGHJKMNPQRSTVWXYZ",ee=10,x=16;function te(r){let e="";for(let n=ee-1;n>=0;n--)e=M[r&31]+e,r=Math.floor(r/32);return e}function ne(){let r=Z(x),e="";for(let n=0;n<x;n++)e+=M[r[n]&31];return e}function I(r=Date.now()){return te(r)+ne()}var T=class{db;path;constructor(e={}){this.path=oe(process.cwd(),e.path??".agentx/intent/ledger.sqlite"),ie(se(this.path),{recursive:!0}),this.db=new re(this.path),this.db.pragma("journal_mode = WAL"),this.db.pragma("synchronous = NORMAL"),this.db.pragma("foreign_keys = ON"),ae(this.db)}close(){this.db.close()}schemaVersion(){return this.db.prepare("SELECT MAX(v) AS v FROM schema_version").get().v??0}recordEvent(e){if(e.sourceEventId!==null){let t=this.findEventBySourceId(e.source,e.sourceEventId);if(t)return t}let n={id:e.id??I(e.ts),ts:e.ts,source:e.source,sourceEventId:e.sourceEventId,project:e.project,subject:e.subject,intent:e.intent,rawJson:e.rawJson};return this.db.prepare(`INSERT INTO intent_events (id, ts, source, source_event_id, project, subject, intent, raw_json)
|
|
2
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(n.id,n.ts,n.source,n.sourceEventId,n.project,n.subject,n.intent,n.rawJson),n}findEventBySourceId(e,n){let t=this.db.prepare(`SELECT id, ts, source, source_event_id, project, subject, intent, raw_json
|
|
3
|
-
FROM intent_events WHERE source = ? AND source_event_id = ?`).get(e,n);return t?F(t):null}getEventById(e){let n=this.db.prepare(`SELECT id, ts, source, source_event_id, project, subject, intent, raw_json
|
|
4
|
-
FROM intent_events WHERE id = ?`).get(e);return n?F(n):null}recordDecision(e){this.db.prepare(`INSERT INTO intent_decisions (event_id, decided_at, decided_by, agent_id, outcome, reason)
|
|
5
|
-
VALUES (?, ?, ?, ?, ?, ?)`).run(e.eventId,e.decidedAt,e.decidedBy,e.agentId,e.outcome,e.reason)}getDecisionsForEvent(e){return this.db.prepare(`SELECT event_id, decided_at, decided_by, agent_id, outcome, reason
|
|
6
|
-
FROM intent_decisions WHERE event_id = ? ORDER BY decided_at ASC`).all(e).map(X)}getActiveDecisionForSubject(e,n){if(e===null||n===null)return null;let t=this.db.prepare(`SELECT d.event_id, d.decided_at, d.decided_by, d.agent_id, d.outcome, d.reason
|
|
7
|
-
FROM intent_decisions d
|
|
8
|
-
JOIN intent_events e ON e.id = d.event_id
|
|
9
|
-
LEFT JOIN intent_resolutions r
|
|
10
|
-
ON r.decision_event_id = d.event_id AND r.decision_decided_by = d.decided_by
|
|
11
|
-
WHERE e.project = ? AND e.subject = ?
|
|
12
|
-
AND d.outcome = 'dispatched'
|
|
13
|
-
AND r.decision_event_id IS NULL
|
|
14
|
-
ORDER BY d.decided_at DESC
|
|
15
|
-
LIMIT 1`).get(e,n);return t?X(t):null}recordResolution(e){this.db.prepare(`INSERT INTO intent_resolutions (decision_event_id, decision_decided_by, resolved_at, status, duration_ms, result_summary)
|
|
16
|
-
VALUES (?, ?, ?, ?, ?, ?)`).run(e.decisionEventId,e.decisionDecidedBy,e.resolvedAt,e.status,e.durationMs,e.resultSummary)}cleanupOrphanedDispatches(e="daemon-restart",n=Date.now){let t=this.db.prepare(`
|
|
17
|
-
SELECT d.event_id, d.decided_by FROM intent_decisions d
|
|
18
|
-
LEFT JOIN intent_resolutions r
|
|
19
|
-
ON r.decision_event_id = d.event_id AND r.decision_decided_by = d.decided_by
|
|
20
|
-
WHERE d.outcome = 'dispatched' AND r.decision_event_id IS NULL
|
|
21
|
-
`).all(),i=n(),s=0;for(let o of t)try{this.recordResolution({decisionEventId:o.event_id,decisionDecidedBy:o.decided_by,resolvedAt:i,status:"canceled",durationMs:null,resultSummary:e}),s++}catch{}return s}getResolution(e,n){let t=this.db.prepare(`SELECT decision_event_id, decision_decided_by, resolved_at, status, duration_ms, result_summary
|
|
22
|
-
FROM intent_resolutions WHERE decision_event_id = ? AND decision_decided_by = ?`).get(e,n);return t?ce(t):null}recordDivergence(e){this.db.prepare(`INSERT INTO intent_divergences (
|
|
23
|
-
id, ts, source, event_id, decided_by,
|
|
24
|
-
ledger_agent_id, ledger_outcome, ledger_reason,
|
|
25
|
-
legacy_agent_id, legacy_outcome, legacy_reason
|
|
26
|
-
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(e.id,e.ts,e.source,e.eventId,e.decidedBy,e.ledgerAgentId,e.ledgerOutcome,e.ledgerReason,e.legacyAgentId,e.legacyOutcome,e.legacyReason)}getDivergences(e={}){let n=[],t=[];e.source!==void 0&&(n.push("source = ?"),t.push(e.source)),e.since!==void 0&&(n.push("ts >= ?"),t.push(e.since));let i=n.length?`WHERE ${n.join(" AND ")}`:"",s=e.limit!==void 0?"LIMIT ?":"";return e.limit!==void 0&&t.push(e.limit),this.db.prepare(`SELECT id, ts, source, event_id, decided_by,
|
|
27
|
-
ledger_agent_id, ledger_outcome, ledger_reason,
|
|
28
|
-
legacy_agent_id, legacy_outcome, legacy_reason
|
|
29
|
-
FROM intent_divergences
|
|
30
|
-
${i}
|
|
31
|
-
ORDER BY ts DESC
|
|
32
|
-
${s}`).all(...t).map(de)}};function F(r){return{id:r.id,ts:r.ts,source:r.source,sourceEventId:r.source_event_id,project:r.project,subject:r.subject,intent:r.intent,rawJson:r.raw_json}}function X(r){return{eventId:r.event_id,decidedAt:r.decided_at,decidedBy:r.decided_by,agentId:r.agent_id,outcome:r.outcome,reason:r.reason}}function ce(r){return{decisionEventId:r.decision_event_id,decisionDecidedBy:r.decision_decided_by,resolvedAt:r.resolved_at,status:r.status,durationMs:r.duration_ms,resultSummary:r.result_summary}}function de(r){return{id:r.id,ts:r.ts,source:r.source,eventId:r.event_id,decidedBy:r.decided_by,ledgerAgentId:r.ledger_agent_id,ledgerOutcome:r.ledger_outcome,ledgerReason:r.ledger_reason,legacyAgentId:r.legacy_agent_id,legacyOutcome:r.legacy_outcome,legacyReason:r.legacy_reason}}function ae(r){r.exec(`
|
|
33
|
-
CREATE TABLE IF NOT EXISTS schema_version (
|
|
34
|
-
v INTEGER PRIMARY KEY
|
|
35
|
-
);
|
|
36
|
-
`);let e=r.prepare("SELECT MAX(v) AS v FROM schema_version").get().v??0;e<1&&ue(r),e<2&&le(r)}function ue(r){r.exec(`
|
|
37
|
-
CREATE TABLE IF NOT EXISTS intent_events (
|
|
38
|
-
-- TEXT PRIMARY KEY isn't auto-NOT-NULL in SQLite; spell it out so the
|
|
39
|
-
-- ULID invariant is enforced at the schema level rather than relying on
|
|
40
|
-
-- the application layer.
|
|
41
|
-
id TEXT NOT NULL PRIMARY KEY,
|
|
42
|
-
ts INTEGER NOT NULL,
|
|
43
|
-
source TEXT NOT NULL,
|
|
44
|
-
source_event_id TEXT,
|
|
45
|
-
project TEXT,
|
|
46
|
-
subject TEXT,
|
|
47
|
-
intent TEXT,
|
|
48
|
-
raw_json TEXT NOT NULL
|
|
49
|
-
);
|
|
50
|
-
|
|
51
|
-
CREATE INDEX IF NOT EXISTS idx_intent_events_subject
|
|
52
|
-
ON intent_events (project, subject);
|
|
53
|
-
CREATE INDEX IF NOT EXISTS idx_intent_events_source_event_id
|
|
54
|
-
ON intent_events (source, source_event_id);
|
|
55
|
-
CREATE INDEX IF NOT EXISTS idx_intent_events_ts
|
|
56
|
-
ON intent_events (ts);
|
|
57
|
-
|
|
58
|
-
CREATE TABLE IF NOT EXISTS intent_decisions (
|
|
59
|
-
event_id TEXT NOT NULL REFERENCES intent_events(id),
|
|
60
|
-
decided_at INTEGER NOT NULL,
|
|
61
|
-
decided_by TEXT NOT NULL,
|
|
62
|
-
agent_id TEXT,
|
|
63
|
-
outcome TEXT NOT NULL,
|
|
64
|
-
reason TEXT,
|
|
65
|
-
PRIMARY KEY (event_id, decided_by)
|
|
66
|
-
);
|
|
67
|
-
|
|
68
|
-
CREATE INDEX IF NOT EXISTS idx_intent_decisions_agent
|
|
69
|
-
ON intent_decisions (agent_id, decided_at);
|
|
70
|
-
CREATE INDEX IF NOT EXISTS idx_intent_decisions_outcome
|
|
71
|
-
ON intent_decisions (outcome, decided_at);
|
|
72
|
-
|
|
73
|
-
CREATE TABLE IF NOT EXISTS intent_resolutions (
|
|
74
|
-
decision_event_id TEXT NOT NULL,
|
|
75
|
-
decision_decided_by TEXT NOT NULL,
|
|
76
|
-
resolved_at INTEGER NOT NULL,
|
|
77
|
-
status TEXT NOT NULL,
|
|
78
|
-
duration_ms INTEGER,
|
|
79
|
-
result_summary TEXT,
|
|
80
|
-
PRIMARY KEY (decision_event_id, decision_decided_by),
|
|
81
|
-
FOREIGN KEY (decision_event_id, decision_decided_by)
|
|
82
|
-
REFERENCES intent_decisions(event_id, decided_by)
|
|
83
|
-
);
|
|
84
|
-
|
|
85
|
-
CREATE INDEX IF NOT EXISTS idx_intent_resolutions_status
|
|
86
|
-
ON intent_resolutions (status, resolved_at);
|
|
87
|
-
`),r.prepare("INSERT INTO schema_version (v) VALUES (1)").run()}function le(r){r.exec(`
|
|
88
|
-
CREATE TABLE IF NOT EXISTS intent_divergences (
|
|
89
|
-
id TEXT NOT NULL PRIMARY KEY,
|
|
90
|
-
ts INTEGER NOT NULL,
|
|
91
|
-
source TEXT NOT NULL,
|
|
92
|
-
event_id TEXT NOT NULL,
|
|
93
|
-
decided_by TEXT NOT NULL,
|
|
94
|
-
ledger_agent_id TEXT,
|
|
95
|
-
ledger_outcome TEXT NOT NULL,
|
|
96
|
-
ledger_reason TEXT,
|
|
97
|
-
legacy_agent_id TEXT,
|
|
98
|
-
legacy_outcome TEXT NOT NULL,
|
|
99
|
-
legacy_reason TEXT,
|
|
100
|
-
FOREIGN KEY (event_id, decided_by)
|
|
101
|
-
REFERENCES intent_decisions(event_id, decided_by)
|
|
102
|
-
);
|
|
103
|
-
|
|
104
|
-
-- "show me recent divergences from gitlab" \u2014 the soak's primary query
|
|
105
|
-
CREATE INDEX IF NOT EXISTS idx_intent_divergences_source_ts
|
|
106
|
-
ON intent_divergences (source, ts);
|
|
107
|
-
-- "are any deciders consistently wrong" \u2014 per-decider analysis
|
|
108
|
-
CREATE INDEX IF NOT EXISTS idx_intent_divergences_decided_by
|
|
109
|
-
ON intent_divergences (decided_by, ts);
|
|
110
|
-
`),r.prepare("INSERT INTO schema_version (v) VALUES (2)").run()}var b;function k(r={}){return b||(b=new T({path:r.path}),b)}var B;function Le(r){B=r}function j(){return B}function J(r,e,n,t=Date.now,i){return r.db.transaction(o=>{let c=r.recordEvent(e),d=i??j(),a=d?.pmFor?.(c.project),u=a?`pm:${a}`:n.decidedBy,h=r.getDecisionsForEvent(c.id).find(m=>m.decidedBy===u);if(h)return h;let p=r.getActiveDecisionForSubject(c.project,c.subject);if(p){let m={eventId:c.id,decidedAt:o,decidedBy:u,agentId:null,outcome:"deduped",reason:`active dispatch in flight: ${p.decidedBy} \u2192 ${p.agentId??"?"}`};return r.recordDecision(m),m}let g=n.decide(c),l=g?g.agentId:null,E=g?g.outcome:"halted",_=g?g.reason:"no policy match";if(d?.canHandle&&E==="dispatched"&&l&&!d.canHandle(l,c.project,c.intent)){let D=l;l=null,E="halted",_=`org-chart: agent "${D}" cannot handle (${c.project??"no-project"}/${c.intent??"no-intent"})`}if(d?.withinDelegationBudget&&E==="dispatched"&&l&&!d.withinDelegationBudget(l,c.project,c.subject)){let m=l;l=null,E="halted",_=`delegation-budget: agent "${m}" depth-limit exceeded on ${c.project??"?"}/${c.subject??"?"}`}a&&E==="dispatched"&&l&&(_=_?`${_} (pm-gate: rubber-stamped by ${a})`:`pm-gate: rubber-stamped by ${a}`);let y={eventId:c.id,decidedAt:o,decidedBy:u,agentId:l,outcome:E,reason:_};return r.recordDecision(y),y})(t())}function U(r,e,n,t,i=Date.now){if(ge(n,t))return!1;let s=i(),o={id:I(s),ts:s,source:e,eventId:n.eventId,decidedBy:n.decidedBy,ledgerAgentId:n.agentId,ledgerOutcome:n.outcome,ledgerReason:n.reason,legacyAgentId:t.agentId,legacyOutcome:t.outcome,legacyReason:t.reason??null};return r.recordDivergence(o),O.warn(`[ledger-divergence] source=${e} event=${n.eventId} decided_by=${n.decidedBy} ledger=${n.outcome}/${n.agentId??"null"} legacy=${t.outcome}/${t.agentId??"null"}`),!0}function ge(r,e){return r.outcome===e.outcome&&P(r.agentId)===P(e.agentId)}function P(r){if(!r)return null;let e=r.match(/^(workflow-run|mesh-fwd):/);return e?`${e[1]}:*`:r}function me(r,e,n=Date.now){return{ts:n(),source:"cron",sourceEventId:`${r.jobId}:${r.firedAt.getTime()}`,project:null,subject:`cron:${r.jobId}`,intent:"cron.fired",rawJson:e}}function Ee(r){return{decidedBy:"cron-scheduler",decide:()=>({agentId:r.agentId,outcome:r.outcome==="deduped"?"halted":r.outcome,reason:r.outcome==="deduped"?`legacy-dedup: ${r.reason??""}`.trim():r.reason??null})}}function G(r,e,n,t,i=Date.now){let s=me(e,n,i),o=Ee(t),c=J(r,s,o,i);return U(r,"cron",c,t,i),c}function H(r,e){if(!e)return r;let n=e*4;return`${r}
|
|
111
|
-
|
|
112
|
-
[Response budget]
|
|
113
|
-
Keep your response under ~${e} tokens (~${n} chars). Be concise \u2014 this is automated batch work, not a conversation.`}function v(r,e,n){let t=[];for(let i of r.split(","))if(i==="*")for(let s=e;s<=n;s++)t.push(s);else if(i.includes("/")){let[s,o]=i.split("/"),c=parseInt(o,10),d=s==="*"?e:parseInt(s,10);for(let a=d;a<=n;a+=c)t.push(a)}else if(i.includes("-")){let[s,o]=i.split("-").map(Number);for(let c=s;c<=o;c++)t.push(c)}else t.push(parseInt(i,10));return[...new Set(t)].sort((i,s)=>i-s)}function V(r,e,n){let t=r.trim().split(/\s+/);if(t.length!==5)throw new Error(`Invalid cron: ${r}`);let i=v(t[0],0,59),s=v(t[1],0,23),o=v(t[2],1,31),c=v(t[3],1,12),d=v(t[4],0,6),a=new Intl.DateTimeFormat("en-US",{timeZone:n,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 h=new Date(u.getTime()+366*24*60*60*1e3);for(;u<h;){let p=a.formatToParts(u),g=D=>parseInt(p.find(W=>W.type===D)?.value||"0",10),l=g("minute"),E=g("hour"),_=g("day"),y=g("month"),m=u.getDay();if(i.includes(l)&&s.includes(E)&&o.includes(_)&&c.includes(y)&&d.includes(m))return u;u.setTime(u.getTime()+6e4)}throw new Error(`No next run found for cron "${r}" within 1 year`)}var L=5,S=[3e4,6e4,3e5,9e5,36e5],K=class{jobs=new Map;timers=new Map;registry;hooks;runsDir;lastRunFile;running=!1;notifyCallback;log;constructor(e,n,t,i=console.error.bind(console,"[cron]")){this.registry=n,this.hooks=t,this.log=i,this.runsDir=f(process.cwd(),".agentx/cron/runs"),this.lastRunFile=f(process.cwd(),".agentx/cron/last-runs.json");for(let[s,o]of Object.entries(e.crons))this.jobs.set(s,{id:s,enabled:o.enabled,schedule:o.schedule,timezone:o.timezone,agent:o.agent,prompt:o.prompt,timeout:o.timeout,model:o.model,maxOutputTokens:o.maxOutputTokens,onError:o.onError,consecutiveErrors:0,totalRuns:0,totalFailures:0})}setNotifyCallback(e){this.notifyCallback=e}async start(){this.running=!0,R(this.runsDir)||N(this.runsDir,{recursive:!0});let e=this.detectMissedRuns();if(e.length>0){this.log(`Detected ${e.length} missed cron run(s) while daemon was down:`);for(let{jobId:n,missedAt:t}of e)this.log(` "${n}" should have run at ${t.toISOString()}`);this.executeMissedRuns(e).catch(n=>{this.log(`Error executing missed runs: ${n.message}`)})}for(let[n,t]of this.jobs){if(!t.enabled){this.log(`Job "${n}" is disabled, skipping`);continue}this.scheduleNext(n)}this.log(`${this.jobs.size} cron job(s) loaded, ${Array.from(this.jobs.values()).filter(n=>n.enabled).length} enabled`)}async stop(){this.running=!1;for(let e of this.timers.values())clearTimeout(e);this.timers.clear(),this.saveLastRuns()}scheduleNext(e){let n=this.jobs.get(e);if(!n||!n.enabled||!this.running)return;let t=this.timers.get(e);t&&clearTimeout(t);try{let i=V(n.schedule,new Date,n.timezone);n.nextRun=i,n.retryPending=!1;let s=i.getTime()-Date.now();this.log(`Job "${e}" next run: ${i.toISOString()} (in ${Math.round(s/1e3)}s)`);let o=setTimeout(()=>this.executeJob(e),s);this.timers.set(e,o)}catch(i){this.log(`Failed to schedule "${e}": ${i.message}`)}}scheduleRetry(e,n){let t=this.jobs.get(e);if(!t||!t.enabled||!this.running)return;if(n>=L){this.log(`Job "${e}" exhausted all ${L} retries, scheduling next regular run`),this.scheduleNext(e);return}let i=S[n]||S[S.length-1];t.retryPending=!0,this.log(`Job "${e}" retry ${n+1}/${L} in ${Math.round(i/1e3)}s`);let s=setTimeout(()=>this.executeJob(e,n+1),i);this.timers.set(e,s)}recordCronDecisionInLedger(e,n,t,i){if(C("cron")!=="off")try{let s=G(k(),{jobId:e,agentId:n,firedAt:t},JSON.stringify({jobId:e,agentId:n,firedAt:t.toISOString()}),i);return s.outcome==="dispatched"?{eventId:s.eventId,decidedBy:s.decidedBy}:void 0}catch(s){this.log(`[ledger] cron "${e}" record failed: ${s?.message??s}`);return}}async executeJob(e,n=0){let t=this.jobs.get(e);if(!t||!this.running)return;let i=n>0,s=new Date;if(this.hooks?.has("pre:cron-run")){let d=await this.hooks.execute("pre:cron-run",{event:"pre:cron-run",jobId:e,agent:t.agent,prompt:t.prompt});if(d.blocked){this.log(`Job "${e}" blocked by hook: ${d.message}`),this.recordCronDecisionInLedger(e,t.agent,s,{agentId:null,outcome:"halted",reason:`pre-hook blocked: ${d.message??""}`.trim()}),this.scheduleNext(e);return}}this.log(`${i?`[retry ${n}] `:""}Executing job "${e}" -> agent "${t.agent}"`);let o=s;t.lastRun=o,t.totalRuns++;let c=this.recordCronDecisionInLedger(e,t.agent,s,{agentId:t.agent,outcome:"dispatched",reason:i?`retry ${n}`:null});try{let d=await this.registry.execute({message:H(t.prompt,t.maxOutputTokens),agentId:t.agent,model:t.model,intentRef:c,context:{channel:"cron",chatId:`cron:${e}`}}),a={jobId:e,startedAt:o,completedAt:new Date,success:!d.error,response:d.content,error:d.error,duration:d.duration||Date.now()-o.getTime(),isRetry:i,retryAttempt:n};if(d.error){if(t.consecutiveErrors++,t.totalFailures++,t.lastError=d.error,this.log(`Job "${e}" failed (${t.consecutiveErrors} consecutive): ${d.error}`),await this.notifyFailure(t,d.error),t.onError.includes("disable")&&t.consecutiveErrors>=3){t.enabled=!1,this.log(`Job "${e}" disabled after ${t.consecutiveErrors} consecutive errors`);try{let u=await A(h=>w(h,`crons.${e}.enabled`,!1),{reload:!1});u.success||this.log(`Failed to persist disable for "${e}": ${u.error}`)}catch(u){this.log(`Failed to persist disable for "${e}": ${u.message}`)}await this.notifyDisabled(t)}this.logRun(a),this.scheduleRetry(e,n);return}else t.consecutiveErrors>0&&this.log(`Job "${e}" recovered after ${t.consecutiveErrors} failure(s)`),t.consecutiveErrors=0,t.lastSuccess=new Date,t.lastError=void 0,t.retryPending=!1,this.log(`Job "${e}" completed in ${a.duration}ms`);this.logRun(a),this.hooks?.has("post:cron-run")&&await this.hooks.execute("post:cron-run",{event:"post:cron-run",jobId:e,success:a.success,duration:a.duration,error:a.error?new Error(a.error):void 0})}catch(d){t.consecutiveErrors++,t.totalFailures++,t.lastError=d.message,this.log(`Job "${e}" threw: ${d.message}`),await this.notifyFailure(t,d.message),this.scheduleRetry(e,n);return}this.saveLastRuns(),this.scheduleNext(e)}detectMissedRuns(){let e=[],n=this.loadLastRuns();if(!n)return e;for(let[t,i]of this.jobs){if(!i.enabled)continue;let s=n[t];if(!s)continue;let o=new Date(s);try{let c=V(i.schedule,o,i.timezone);c.getTime()<Date.now()&&e.push({jobId:t,missedAt:c})}catch{}}return e}async executeMissedRuns(e){for(let{jobId:n,missedAt:t}of e){let i=this.jobs.get(n);if(!(!i||!i.enabled)){this.log(`Running missed job "${n}" (was due at ${t.toISOString()})`);try{let s=await this.registry.execute({message:H(`[MISSED RUN \u2014 was scheduled for ${t.toISOString()}]
|
|
114
|
-
|
|
115
|
-
${i.prompt}`,i.maxOutputTokens),agentId:i.agent,context:{channel:"cron",chatId:`cron:${n}`}}),o={jobId:n,startedAt:new Date,completedAt:new Date,success:!s.error,response:s.content,error:s.error,duration:s.duration||0,isRetry:!1};this.logRun(o),s.error?this.log(`Missed job "${n}" failed: ${s.error}`):(this.log(`Missed job "${n}" completed`),i.lastSuccess=new Date),i.lastRun=new Date,i.totalRuns++}catch(s){this.log(`Missed job "${n}" threw: ${s.message}`)}}}this.saveLastRuns()}async notifyFailure(e,n){if(!(!e.onError.includes("notify")&&e.consecutiveErrors<2)){if(!this.notifyCallback){this.log(`[ALERT] Cron "${e.id}" failed (${e.consecutiveErrors}x): ${n.slice(0,200)}`);return}try{await this.notifyCallback(e.id,e.agent,n,e.consecutiveErrors)}catch{}}}async notifyDisabled(e){if(!this.notifyCallback){this.log(`[ALERT] Cron "${e.id}" has been DISABLED after ${e.consecutiveErrors} consecutive failures`);return}try{await this.notifyCallback(e.id,e.agent,`AUTO-DISABLED after ${e.consecutiveErrors} consecutive failures. Last error: ${e.lastError||"unknown"}`,e.consecutiveErrors)}catch{}}saveLastRuns(){try{let e={};for(let[t,i]of this.jobs)i.lastRun&&(e[t]=i.lastRun.toISOString());let n=f(process.cwd(),".agentx/cron");R(n)||N(n,{recursive:!0}),Y(this.lastRunFile,JSON.stringify(e,null,2))}catch{}}loadLastRuns(){try{return R(this.lastRunFile)?JSON.parse(_e(this.lastRunFile,"utf-8")):null}catch{return null}}logRun(e){try{let n=f(this.runsDir,e.jobId);R(n)||N(n,{recursive:!0});let t=`${e.startedAt.toISOString().replace(/[:.]/g,"-")}.json`;Y(f(n,t),JSON.stringify(e,null,2))}catch{}}list(){return Array.from(this.jobs.values())}health(){let e=Array.from(this.jobs.values()),n=this.detectMissedRuns();return{healthy:e.filter(t=>t.enabled&&t.consecutiveErrors===0).length,failing:e.filter(t=>t.enabled&&t.consecutiveErrors>0).length,disabled:e.filter(t=>!t.enabled).length,missed:n.length,jobs:e.map(t=>({id:t.id,status:t.enabled?t.retryPending?"retrying":t.consecutiveErrors>0?"failing":"healthy":"disabled",consecutiveErrors:t.consecutiveErrors,lastError:t.lastError}))}}};export{I as a,T as b,k as c,C as d,Le as e,J as f,U as g,V as h,K as i};
|
|
116
|
-
//# sourceMappingURL=chunk-53GCFZPR.js.map
|