ftown-bridge 0.11.2 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/centrifugo-client.d.ts +4 -1
- package/dist/centrifugo-client.js +27 -0
- package/dist/centrifugo-client.js.map +1 -1
- package/dist/codex-installer.js +8 -2
- package/dist/codex-installer.js.map +1 -1
- package/dist/create-ftown-session.d.ts +1 -0
- package/dist/create-ftown-session.js +4 -3
- package/dist/create-ftown-session.js.map +1 -1
- package/dist/ftown-sessions-cli.js +334 -0
- package/dist/ftown-sessions-cli.js.map +1 -1
- package/dist/index.js +167 -11
- package/dist/index.js.map +1 -1
- package/dist/install-ftown-skill.d.ts +2 -0
- package/dist/install-ftown-skill.js +39 -0
- package/dist/install-ftown-skill.js.map +1 -1
- package/dist/local-api-server.d.ts +10 -0
- package/dist/local-api-server.js +147 -0
- package/dist/local-api-server.js.map +1 -1
- package/dist/loop-run-store.d.ts +9 -0
- package/dist/loop-run-store.js +159 -0
- package/dist/loop-run-store.js.map +1 -0
- package/dist/loop-schedule.d.ts +18 -0
- package/dist/loop-schedule.js +35 -0
- package/dist/loop-schedule.js.map +1 -0
- package/dist/loop-scheduler.d.ts +148 -0
- package/dist/loop-scheduler.js +534 -0
- package/dist/loop-scheduler.js.map +1 -0
- package/dist/loop-store.d.ts +36 -0
- package/dist/loop-store.js +128 -0
- package/dist/loop-store.js.map +1 -0
- package/dist/loop-validation.d.ts +14 -0
- package/dist/loop-validation.js +95 -0
- package/dist/loop-validation.js.map +1 -0
- package/dist/types.d.ts +107 -2
- package/package.json +2 -1
- package/skills/ftown/SKILL.md +48 -0
- package/skills/ftown/agents/openai.yaml +4 -0
- package/skills/ftown/references/loops.md +88 -0
- package/skills/{ftown-orchestrator/SKILL.md → ftown/references/orchestrator.md} +22 -13
- package/skills/{ftown-sessions/SKILL.md → ftown/references/sessions.md} +22 -16
- package/skills/{ftown-workflows/SKILL.md → ftown/references/workflows.md} +13 -20
- package/skills/ftown/scripts/ftown +4 -0
- package/skills/ftown-sessions/scripts/ftown-sessions +0 -4
- package/skills/ftown-workflows/scripts/ftown-workflows +0 -4
- /package/skills/{ftown-workflows → ftown}/scripts/example.flow.mjs +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop-scheduler.js","sourceRoot":"","sources":["../src/loop-scheduler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,cAAc,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3D,OAAO,EACL,mBAAmB,EACnB,gBAAgB,EAChB,gBAAgB,EAChB,mBAAmB,GACpB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAA2B,MAAM,iBAAiB,CAAC;AAMxF,mGAAmG;AACnG,MAAM,CAAC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AAE5C,MAAM,GAAG,GAAG,CAAC,EAAU,EAAU,EAAE,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC;AAqE/D;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,gBAAgB,CAC9B,OAAe,EACf,GAAuB,EACvB,SAAS,GAAG,MAAM,EAClB,QAAiC;IAEjC,MAAM,GAAG,GAAG,QAAQ,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,QAAQ,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC;IACrE,MAAM,WAAW,GAAG,IAAI,GAAG,IAAI,CAAC;IAChC,OAAO,IAAI,OAAO,CAAe,CAAC,OAAO,EAAE,EAAE;QAC3C,IAAI,OAAO,GAAG,KAAK,CAAC;QACpB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,MAAM,GAAG,EAAE,CAAC;QAChB,IAAI,QAAQ,GAAkB,IAAI,CAAC;QACnC,IAAI,eAAe,GAAG,KAAK,CAAC;QAC5B,IAAI,SAAoD,CAAC;QAEzD,4EAA4E;QAC5E,2EAA2E;QAC3E,4EAA4E;QAC5E,yDAAyD;QACzD,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE;YAC9C,GAAG,EAAE,GAAG,IAAI,OAAO,CAAC,GAAG,EAAE;YACzB,GAAG;YACH,QAAQ,EAAE,IAAI;SACf,CAAC,CAAC;QAEH,MAAM,MAAM,GAAG,GAAS,EAAE;YACxB,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,GAAG,IAAI,CAAC;YACf,YAAY,CAAC,SAAS,CAAC,CAAC;YACxB,YAAY,CAAC,SAAS,CAAC,CAAC;YACxB,yEAAyE;YACzE,2EAA2E;YAC3E,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;YACxB,KAAK,CAAC,MAAM,EAAE,OAAO,EAAE,CAAC;YACxB,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;YAChB,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC;QACjF,CAAC,CAAC;QACF,MAAM,UAAU,GAAG,CAAC,IAAmB,EAAE,MAA6B,EAAQ,EAAE;YAC9E,IAAI,QAAQ,KAAK,IAAI;gBAAE,QAAQ,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACrF,CAAC,CAAC;QAEF,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE;YACrC,IAAI,MAAM,CAAC,MAAM,GAAG,WAAW;gBAAE,MAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAChE,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,CAAS,EAAE,EAAE;YACrC,IAAI,MAAM,CAAC,MAAM,GAAG,WAAW;gBAAE,MAAM,IAAI,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;QAChE,CAAC,CAAC,CAAC;QAEH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACrB,IAAI,QAAQ,KAAK,IAAI;gBAAE,QAAQ,GAAG,CAAC,CAAC;YACpC,MAAM,EAAE,CAAC;QACX,CAAC,CAAC,CAAC;QACH,2EAA2E;QAC3E,yEAAyE;QACzE,6EAA6E;QAC7E,2EAA2E;QAC3E,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;YAChC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACzB,SAAS,GAAG,UAAU,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YACpC,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC;QACtB,CAAC,CAAC,CAAC;QACH,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE;YACjC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;YACzB,MAAM,EAAE,CAAC;QACX,CAAC,CAAC,CAAC;QAEH,MAAM,SAAS,GAAG,UAAU,CAAC,GAAG,EAAE;YAChC,eAAe,GAAG,IAAI,CAAC;YACvB,IAAI,KAAK,CAAC,GAAG,EAAE,CAAC;gBACd,IAAI,CAAC;oBACH,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC,CAAC,sBAAsB;gBAC7D,CAAC;gBAAC,MAAM,CAAC;oBACP,IAAI,CAAC;wBACH,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;oBACxB,CAAC;oBAAC,MAAM,CAAC;wBACP,kBAAkB;oBACpB,CAAC;gBACH,CAAC;YACH,CAAC;YACD,MAAM,EAAE,CAAC;QACX,CAAC,EAAE,SAAS,CAAC,CAAC;QACd,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC;IACtB,CAAC,CAAC,CAAC;AACL,CAAC;AAED,sFAAsF;AACtF,SAAS,YAAY,CAAC,IAAY,EAAE,QAAgB;IAClD,MAAM,GAAG,GAAG,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACtC,IAAI,GAAG,CAAC,MAAM,IAAI,QAAQ;QAAE,OAAO,IAAI,CAAC;IACxC,OAAO,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,GAAG,QAAQ,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAC9D,CAAC;AAED;;;;;GAKG;AACH,SAAS,gBAAgB,CAAC,GAAmB;IAC3C,OAAO,GAAG,EAAE,MAAM,KAAK,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC;AACtD,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,aAAa;IACP,KAAK,CAAiB;IACtB,MAAM,CAAkB;IACxB,UAAU,CAAsB;IAChC,MAAM,CAAS;IACf,YAAY,CAAe;IAC3B,aAAa,CAAgB;IAC7B,KAAK,CAAe;IACpB,UAAU,CAAwB;IAClC,SAAS,CAAY;IACrB,GAAG,CAAe;IAEnC,oDAAoD;IAC5C,WAAW,GAAG,KAAK,CAAC;IAC5B;gGAC4F;IACpF,OAAO,GAAG,KAAK,CAAC;IACxB,wEAAwE;IACvD,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IACjD;;qGAEiG;IAChF,QAAQ,GAAG,IAAI,GAAG,EAA+B,CAAC;IAC3D,KAAK,CAA6C;IAE1D,YAAY,IAAmB;QAC7B,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACxB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,CAAC;QAClC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM,CAAC;QAC1B,IAAI,CAAC,YAAY,GAAG,IAAI,CAAC,YAAY,CAAC;QACtC,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,aAAa,CAAC;QACxC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,KAAK,IAAI,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAC;QAC5D,IAAI,CAAC,UAAU,GAAG,IAAI,CAAC,UAAU,IAAI,EAAE,mBAAmB,EAAE,mBAAmB,EAAE,CAAC;QAClF,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,IAAI,gBAAgB,CAAC;QACpD,IAAI,CAAC,GAAG,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC,GAAW,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC;IACpD,CAAC;IAED,KAAK;QACH,IAAI,CAAC,OAAO,GAAG,IAAI,CAAC;QACpB,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;YAC5B,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;QACnB,CAAC,EAAE,qBAAqB,CAAC,CAAC;IAC5B,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACf,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;YAC1B,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;QACzB,CAAC;IACH,CAAC;IAED;qFACiF;IACjF,IAAI;QACF,IAAI,CAAC,IAAI,CAAC,OAAO;YAAE,OAAO;QAC1B,IAAI,CAAC,IAAI,CAAC,WAAW;YAAE,KAAK,IAAI,CAAC,IAAI,EAAE,CAAC;IAC1C,CAAC;IAED;4FACwF;IACxF,aAAa,CAAC,IAAU;QACtB,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3C,IAAI,OAAO;YAAE,KAAK,MAAM,EAAE,IAAI,OAAO,CAAC,IAAI,EAAE;gBAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC1D,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,IAAI,IAAI,CAAC,aAAa;YAAE,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACrF,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;YACrB,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,EAAE,CAAC;gBAAE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACtD,CAAC;QACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAChC,CAAC;IAED;;;;;;;OAOG;IACH,KAAK,CAAC,gBAAgB,CAAC,MAAc,IAAI,CAAC,GAAG,EAAE;QAC7C,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,CAAC;YAC1C,IAAI,CAAC;gBACH,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC;gBAC1E,IAAI,CAAC,OAAO;oBAAE,SAAS;gBACvB,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,8BAA8B;gBACpF,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE;oBAChC,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC;oBAC7B,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;gBACzB,CAAC,CAAC,CAAC;YACL,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CAAC,6CAA6C,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;YAC9E,CAAC;QACH,CAAC;IACH,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,MAAc,IAAI,CAAC,GAAG,EAAE;QACjC,IAAI,IAAI,CAAC,WAAW;YAAE,OAAO,CAAC,4BAA4B;QAC1D,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC;YACH,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,EAAE,CAAC;gBAC1C,IAAI,CAAC;oBACH,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;gBACpC,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,mEAAmE;oBACnE,OAAO,CAAC,KAAK,CAAC,wBAAwB,IAAI,CAAC,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC;gBAChE,CAAC;YACH,CAAC;QACH,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC;QAC3B,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,WAAW,CAAC,IAAU,EAAE,GAAW;QAC/C,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,iCAAiC;QACtE,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,UAAU;IAC7C,CAAC;IAED;;oDAEgD;IACxC,KAAK,CAAC,aAAa,CAAC,IAAU,EAAE,GAAW;QACjD,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QACzB,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3C,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO;QAE3C,0DAA0D;QAC1D,KAAK,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,EAAE,CAAC;YAC9C,MAAM,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YAC7C,MAAM,OAAO,GAAG,GAAG,GAAG,SAAS,CAAC;YAChC,IAAI,OAAO,IAAI,IAAI,CAAC,YAAY,IAAI,OAAO,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;gBAChE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;gBACxB,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;YACjD,CAAC;iBAAM,IAAI,CAAC,OAAO,IAAI,OAAO,IAAI,qBAAqB,EAAE,CAAC;gBACxD,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,CAAC,CAAC;YAClD,CAAC;YACD,oEAAoE;QACtE,CAAC;IACH,CAAC;IAED;;sCAEkC;IAC1B,aAAa,CAAC,IAAU;QAC9B,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,IAAI,CAAC,IAAI,CAAC,aAAa;YAAE,OAAO;QACjE,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC5C,IAAI,QAAQ,IAAI,QAAQ,CAAC,IAAI,GAAG,CAAC;YAAE,OAAO,CAAC,yCAAyC;QACpF,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;QAChD,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC;QAC7D,MAAM,GAAG,GAAG,QAAQ,IAAI,IAAI,GAAG,EAAkB,CAAC;QAClD,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;QACvC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;IAClC,CAAC;IAEO,KAAK,CAAC,MAAc,EAAE,SAAiB,EAAE,SAAiB;QAChE,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,IAAI,GAAG,EAAkB,CAAC;QACnE,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC;QAC9B,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IACjC,CAAC;IAED,wFAAwF;IAChF,KAAK,CAAC,SAAS,CAAC,IAAU,EAAE,GAAW;QAC7C,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,CAAC;YAAE,OAAO;QAC9B,IAAI,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;YAAE,OAAO;QAE1C,6EAA6E;QAC7E,wEAAwE;QACxE,0EAA0E;QAC1E,4EAA4E;QAC5E,iDAAiD;QACjD,IAAI,IAAI,CAAC,UAAU,KAAK,SAAS,IAAI,IAAI,CAAC,aAAa,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;YACtG,MAAM,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,GAAG,EAAE,IAAI,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;YAC7D,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC,CAAC;YACnE,IAAI,CAAC,KAAK;gBAAE,OAAO,CAAC,0BAA0B;YAC9C,IAAI,GAAG,KAAK,CAAC;QACf,CAAC;QAED,+EAA+E;QAC/E,4EAA4E;QAC5E,mBAAmB;QACnB,IACE,IAAI,CAAC,aAAa,KAAK,MAAM;YAC7B,IAAI,CAAC,UAAU,KAAK,SAAS;YAC7B,IAAI,CAAC,aAAa;YAClB,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,aAAa,CAAC,EACzC,CAAC;YACD,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE;gBAChC,IAAI,CAAC;oBACH,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,cAAc,CAAC,CAAC,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC;gBACrD,CAAC;gBAAC,MAAM,CAAC;oBACP,sEAAsE;gBACxE,CAAC;gBACD,CAAC,CAAC,eAAe,GAAG,KAAK,CAAC;gBAC1B,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC9B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACjC,CAAC;gBAAS,CAAC;YACT,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACnC,CAAC;IACH,CAAC;IAED,iGAAiG;IACzF,KAAK,CAAC,QAAQ,CAAC,IAAU,EAAE,GAAW;QAC5C,gFAAgF;QAChF,6EAA6E;QAC7E,IAAI,SAAiB,CAAC;QACtB,IAAI,CAAC;YACH,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;QACjD,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE;gBAChC,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;gBACvB,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,GAAG,qBAAqB,CAAC,CAAC,CAAC,gCAAgC;gBAChF,CAAC,CAAC,eAAe,GAAG,KAAK,CAAC;gBAC1B,CAAC,CAAC,UAAU,GAAG,OAAO,CAAC;gBACvB,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC,CAAC,CAAC;YACH,OAAO,CAAC,KAAK,CAAC,yCAAyC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;YACxE,OAAO;QACT,CAAC;QAED,IAAI,CAAC;YACH,IAAI,YAAY,GAAG,EAAE,CAAC;YACtB,IAAI,IAAI,CAAC,SAAS,EAAE,CAAC;gBACnB,MAAM,CAAC,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;gBAC/F,YAAY,GAAG,CAAC,CAAC,MAAM,CAAC;gBACxB,IAAI,CAAC,CAAC,QAAQ,KAAK,CAAC,EAAE,CAAC;oBACrB,MAAM,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;oBAC3B,MAAM,OAAO,GAAG;wBACd,8BAA8B,CAAC,CAAC,QAAQ,GAAG;wBAC3C,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE;wBACxC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE;qBACzC,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;oBACX,oDAAoD;oBACpD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE;wBAChD,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC;wBACxB,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC;wBAC7B,CAAC,CAAC,eAAe,GAAG,KAAK,CAAC;wBAC1B,CAAC,CAAC,UAAU,GAAG,SAAS,CAAC;wBACzB,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC;wBACjB,CAAC,CAAC,SAAS,GAAG,SAAS,CAAC;oBAC1B,CAAC,CAAC,CAAC;oBACH,IAAI,OAAO,EAAE,CAAC;wBACZ,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;oBAClF,CAAC;oBACD,IAAI,OAAO,IAAI,IAAI,CAAC,UAAU,EAAE,SAAS,EAAE,CAAC;wBAC1C,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,CAAC,CAAC;oBACnF,CAAC;oBACD,OAAO;gBACT,CAAC;YACH,CAAC;YAED,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,eAAe,EAAE,YAAY,CAAC,CAAC;YACjE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,YAAY,CAAC;gBACtC,SAAS,EAAE,IAAI,CAAC,OAAO;gBACvB,MAAM,EAAE,IAAI;gBACZ,UAAU,EAAE,IAAI,CAAC,OAAO;gBACxB,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,GAAG,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,sBAAsB,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,SAAS;gBACxE,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,gBAAgB,EAAE,IAAI,EAAE,uDAAuD;gBAC/E,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,MAAM,GAAG,CAAC,GAAG,CAAC,EAAE;gBAClC,2EAA2E;aAC5E,CAAC,CAAC;YAEH,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE;gBAChD,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;gBACvB,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC;gBAC7B,CAAC,CAAC,eAAe,GAAG,KAAK,CAAC;gBAC1B,CAAC,CAAC,aAAa,GAAG,OAAO,CAAC,EAAE,CAAC;gBAC7B,CAAC,CAAC,UAAU,GAAG,SAAS,CAAC;gBACzB,CAAC,CAAC,QAAQ,IAAI,CAAC,CAAC;gBAChB,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC,CAAC,CAAC;YAEH,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,0EAA0E;gBAC1E,qEAAqE;gBACrE,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBAC7B,MAAM,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;gBAC5D,OAAO;YACT,CAAC;YACD,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;YAClF,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,OAAO,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC;QACvC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,2EAA2E;YAC3E,oEAAoE;YACpE,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE;gBAChC,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;gBACvB,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC;gBAC7B,CAAC,CAAC,eAAe,GAAG,KAAK,CAAC;gBAC1B,CAAC,CAAC,UAAU,GAAG,OAAO,CAAC;gBACvB,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC,CAAC,CAAC;YACH,OAAO,CAAC,KAAK,CAAC,wCAAwC,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QACzE,CAAC;IACH,CAAC;IAED;8EAC0E;IAClE,KAAK,CAAC,WAAW,CAAC,IAAU,EAAE,GAAW,EAAE,KAAa,EAAE,WAAoB;QACpF,MAAM,OAAO,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC3C,MAAM,SAAS,GAAG,OAAO,EAAE,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;QAChF,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACtB,IAAI,OAAO,CAAC,IAAI,KAAK,CAAC;gBAAE,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxD,CAAC;QAED,MAAM,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;QAC/D,MAAM,MAAM,GAAmB,WAAW,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAC7E,MAAM,UAAU,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;QACxE,MAAM,MAAM,GAAG,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAChD,MAAM,WAAW,GAAG,MAAM,CAAC,UAAU,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QAC1D,MAAM,SAAS,GAAG,MAAM,CAAC,UAAU,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;QAEpD,8EAA8E;QAC9E,0EAA0E;QAC1E,uEAAuE;QACvE,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE;YAChD,IAAI,CAAC,CAAC,aAAa,KAAK,KAAK;gBAAE,CAAC,CAAC,UAAU,GAAG,MAAM,CAAC;YACrD,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;QACzB,CAAC,CAAC,CAAC;QACH,KAAK,OAAO,CAAC;QAEb,MAAM,UAAU,GAAG,GAAG;YACpB,CAAC,CAAC,gBAAgB,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC;YAC1F,CAAC,CAAC;gBACE,EAAE,EAAE,KAAK;gBACT,MAAM,EAAE,IAAI,CAAC,EAAE;gBACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,SAAS,EAAE,KAAK;gBAChB,IAAI,EAAE,GAAG,IAAI,CAAC,IAAI,MAAM,KAAK,EAAE;gBAC/B,MAAM;gBACN,SAAS,EAAE,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;gBACjE,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC;gBACnB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,OAAO,EAAE,IAAI,CAAC,OAAO;gBACrB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,aAAa,EAAE,SAAS;aACzB,CAAC;QACN,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC;YAClC,GAAG,UAAU;YACb,MAAM;YACN,aAAa,EAAE,GAAG,EAAE,MAAM;YAC1B,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,sBAAsB,CAAC,CAAC,CAAC,GAAG,EAAE,WAAW;YACpE,SAAS,EAAE,GAAG,CAAC,GAAG,CAAC;YACnB,UAAU,EAAE,GAAG,CAAC,GAAG,CAAC;YACpB,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;YAC7E,OAAO,EAAE,MAAM;YACf,QAAQ,EAAE,WAAW;YACrB,YAAY,EAAE,WAAW,GAAG,SAAS;SACtC,CAAC,CAAC;QAEH,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACpB,MAAM,IAAI,CAAC,aAAa,CAAC,IAAI,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QACvE,CAAC;QACD,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC7B,CAAC;IAEO,KAAK,CAAC,aAAa,CACzB,IAAU,EACV,GAA8E;QAE9E,IAAI,CAAC,IAAI,CAAC,UAAU;YAAE,OAAO;QAC7B,MAAM,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,SAAS,EAAE;YACrF,gBAAgB,EAAE,GAAG,CAAC,MAAM;YAC5B,oBAAoB,EAAE,GAAG,CAAC,SAAS;YACnC,gBAAgB,EAAE,GAAG,CAAC,MAAM;SAC7B,CAAC,CAAC;IACL,CAAC;IAED,+EAA+E;IACvE,KAAK,CAAC,SAAS,CAAC,IAAU;QAChC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,kBAAkB,CAAC;QAC/C,IAAI,IAAI,IAAI,IAAI;YAAE,OAAO;QAEzB,MAAM,IAAI,GAAG,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,YAAY,EAAE,CAAC;aAC3C,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,KAAK,IAAI,CAAC,EAAE,CAAC;aACnC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;QAErE,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YACnC,IAAI,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,SAAS;YAC5C,IAAI,GAAG,CAAC,EAAE,KAAK,IAAI,CAAC,aAAa;gBAAE,SAAS;YAC5C,MAAM,IAAI,CAAC,aAAa,CAAC,GAAG,CAAC,EAAE,EAAE,EAAE,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC;QAC7D,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,mBAAmB,CAAC,IAAI,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,IAAI,CAAC,aAAa,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACxJ,CAAC;IAEO,KAAK,CAAC,OAAO,CAAC,EAAU,EAAE,EAAsB;QACtD,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,iBAAiB,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC;QACrD,IAAI,OAAO;YAAE,MAAM,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;QACzC,OAAO,OAAO,CAAC;IACjB,CAAC;IAEO,KAAK,CAAC,OAAO,CAAC,IAAU;QAC9B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,UAAU,CAAC,iBAAiB,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC7D,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,6EAA6E;YAC7E,OAAO,CAAC,KAAK,CAAC,qDAAqD,IAAI,CAAC,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QACtF,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Loop, LoopDraft } from './types.js';
|
|
2
|
+
export declare function listLoops(): Loop[];
|
|
3
|
+
export declare function getLoop(id: string): Loop | undefined;
|
|
4
|
+
/**
|
|
5
|
+
* Mint a Loop from a client draft: fresh id/timestamps, zeroed counters, and a
|
|
6
|
+
* nextRunAt computed from now — even when created disabled, so re-enabling has a
|
|
7
|
+
* target. Throws (via computeNextRun) on a malformed cron expression.
|
|
8
|
+
*/
|
|
9
|
+
export declare function createLoop(draft: LoopDraft): Loop;
|
|
10
|
+
/**
|
|
11
|
+
* Merge `patch` over an existing loop. id + createdAt are immutable; updatedAt is
|
|
12
|
+
* bumped. A schedule change recomputes nextRunAt from now (the old target is
|
|
13
|
+
* stale). Returns null when no loop has that id.
|
|
14
|
+
*/
|
|
15
|
+
export declare function updateLoop(id: string, patch: Partial<LoopDraft>): Loop | null;
|
|
16
|
+
export declare function deleteLoop(id: string): boolean;
|
|
17
|
+
/** Insert a loop, or replace the existing one with the same id, in place. */
|
|
18
|
+
export declare function upsertLoop(loop: Loop): void;
|
|
19
|
+
/** The scheduler-owned runtime fields a tick is allowed to mutate. Everything
|
|
20
|
+
* else on a Loop (name, schedule, enabled, task, retention, …) is user-owned
|
|
21
|
+
* and edited exclusively through updateLoop. */
|
|
22
|
+
export type LoopRuntimeMutator = (loop: Loop) => void;
|
|
23
|
+
/**
|
|
24
|
+
* Atomically apply a scheduler-owned mutation: reload the loop FRESH from disk,
|
|
25
|
+
* apply `fn`, then save. Returns the merged loop, or null when the id no longer
|
|
26
|
+
* exists (deleted concurrently) — the caller then skips its publish so a deleted
|
|
27
|
+
* loop is never resurrected.
|
|
28
|
+
*
|
|
29
|
+
* The scheduler holds a detached Loop snapshot across long awaits (a 20s
|
|
30
|
+
* preflight, an in-process spawn). Writing that whole stale snapshot back would
|
|
31
|
+
* (a) resurrect a loop deleted mid-flight and (b) clobber a concurrent
|
|
32
|
+
* update_loop patch to user-owned fields. Because `fn` runs on the
|
|
33
|
+
* freshly-loaded record and touches only runtime fields, both hazards are gone:
|
|
34
|
+
* a delete wins (null), and an unrelated enabled/schedule/task patch survives.
|
|
35
|
+
*/
|
|
36
|
+
export declare function mutateLoopRuntime(id: string, fn: LoopRuntimeMutator): Loop | null;
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
5
|
+
import { computeNextRun } from './loop-schedule.js';
|
|
6
|
+
function loopsPath() {
|
|
7
|
+
return join(homedir(), '.ftown', 'loops.json');
|
|
8
|
+
}
|
|
9
|
+
/** Tolerant loader: returns { loops: [] } on a missing OR corrupt file. Never throws. */
|
|
10
|
+
function loadLoops() {
|
|
11
|
+
try {
|
|
12
|
+
const path = loopsPath();
|
|
13
|
+
if (!existsSync(path))
|
|
14
|
+
return { loops: [] };
|
|
15
|
+
const parsed = JSON.parse(readFileSync(path, 'utf8'));
|
|
16
|
+
return { loops: Array.isArray(parsed.loops) ? parsed.loops : [] };
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return { loops: [] };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
function saveLoops(data) {
|
|
23
|
+
mkdirSync(join(homedir(), '.ftown'), { recursive: true, mode: 0o700 });
|
|
24
|
+
const path = loopsPath();
|
|
25
|
+
const tmp = `${path}.tmp`;
|
|
26
|
+
writeFileSync(tmp, JSON.stringify(data, null, 2) + '\n', { mode: 0o600 });
|
|
27
|
+
renameSync(tmp, path); // atomic
|
|
28
|
+
}
|
|
29
|
+
export function listLoops() {
|
|
30
|
+
return loadLoops().loops;
|
|
31
|
+
}
|
|
32
|
+
export function getLoop(id) {
|
|
33
|
+
return loadLoops().loops.find((loop) => loop.id === id);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Mint a Loop from a client draft: fresh id/timestamps, zeroed counters, and a
|
|
37
|
+
* nextRunAt computed from now — even when created disabled, so re-enabling has a
|
|
38
|
+
* target. Throws (via computeNextRun) on a malformed cron expression.
|
|
39
|
+
*/
|
|
40
|
+
export function createLoop(draft) {
|
|
41
|
+
const data = loadLoops();
|
|
42
|
+
const now = new Date();
|
|
43
|
+
const loop = {
|
|
44
|
+
...draft,
|
|
45
|
+
id: uuidv4(),
|
|
46
|
+
createdAt: now.toISOString(),
|
|
47
|
+
updatedAt: now.toISOString(),
|
|
48
|
+
nextRunAt: new Date(computeNextRun(draft.schedule, now.getTime())).toISOString(),
|
|
49
|
+
runCount: 0,
|
|
50
|
+
skipCount: 0,
|
|
51
|
+
};
|
|
52
|
+
data.loops.push(loop);
|
|
53
|
+
saveLoops(data);
|
|
54
|
+
return loop;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Merge `patch` over an existing loop. id + createdAt are immutable; updatedAt is
|
|
58
|
+
* bumped. A schedule change recomputes nextRunAt from now (the old target is
|
|
59
|
+
* stale). Returns null when no loop has that id.
|
|
60
|
+
*/
|
|
61
|
+
export function updateLoop(id, patch) {
|
|
62
|
+
const data = loadLoops();
|
|
63
|
+
const index = data.loops.findIndex((loop) => loop.id === id);
|
|
64
|
+
if (index === -1)
|
|
65
|
+
return null;
|
|
66
|
+
const existing = data.loops[index];
|
|
67
|
+
const updated = {
|
|
68
|
+
...existing,
|
|
69
|
+
...patch,
|
|
70
|
+
id: existing.id,
|
|
71
|
+
// A loop is owned by exactly one bridge (the one it is persisted on); its
|
|
72
|
+
// bridgeId is the RPC routing key. Never let a patch move it — a stray
|
|
73
|
+
// patch.bridgeId would otherwise desync the record from the routing guard.
|
|
74
|
+
bridgeId: existing.bridgeId,
|
|
75
|
+
createdAt: existing.createdAt,
|
|
76
|
+
updatedAt: new Date().toISOString(),
|
|
77
|
+
};
|
|
78
|
+
if (patch.schedule) {
|
|
79
|
+
updated.nextRunAt = new Date(computeNextRun(updated.schedule, Date.now())).toISOString();
|
|
80
|
+
}
|
|
81
|
+
data.loops[index] = updated;
|
|
82
|
+
saveLoops(data);
|
|
83
|
+
return updated;
|
|
84
|
+
}
|
|
85
|
+
export function deleteLoop(id) {
|
|
86
|
+
const data = loadLoops();
|
|
87
|
+
const remaining = data.loops.filter((loop) => loop.id !== id);
|
|
88
|
+
if (remaining.length === data.loops.length)
|
|
89
|
+
return false;
|
|
90
|
+
data.loops = remaining;
|
|
91
|
+
saveLoops(data);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
/** Insert a loop, or replace the existing one with the same id, in place. */
|
|
95
|
+
export function upsertLoop(loop) {
|
|
96
|
+
const data = loadLoops();
|
|
97
|
+
const index = data.loops.findIndex((existing) => existing.id === loop.id);
|
|
98
|
+
if (index === -1)
|
|
99
|
+
data.loops.push(loop);
|
|
100
|
+
else
|
|
101
|
+
data.loops[index] = loop;
|
|
102
|
+
saveLoops(data);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Atomically apply a scheduler-owned mutation: reload the loop FRESH from disk,
|
|
106
|
+
* apply `fn`, then save. Returns the merged loop, or null when the id no longer
|
|
107
|
+
* exists (deleted concurrently) — the caller then skips its publish so a deleted
|
|
108
|
+
* loop is never resurrected.
|
|
109
|
+
*
|
|
110
|
+
* The scheduler holds a detached Loop snapshot across long awaits (a 20s
|
|
111
|
+
* preflight, an in-process spawn). Writing that whole stale snapshot back would
|
|
112
|
+
* (a) resurrect a loop deleted mid-flight and (b) clobber a concurrent
|
|
113
|
+
* update_loop patch to user-owned fields. Because `fn` runs on the
|
|
114
|
+
* freshly-loaded record and touches only runtime fields, both hazards are gone:
|
|
115
|
+
* a delete wins (null), and an unrelated enabled/schedule/task patch survives.
|
|
116
|
+
*/
|
|
117
|
+
export function mutateLoopRuntime(id, fn) {
|
|
118
|
+
const data = loadLoops();
|
|
119
|
+
const index = data.loops.findIndex((loop) => loop.id === id);
|
|
120
|
+
if (index === -1)
|
|
121
|
+
return null;
|
|
122
|
+
const loop = data.loops[index];
|
|
123
|
+
fn(loop);
|
|
124
|
+
data.loops[index] = loop;
|
|
125
|
+
saveLoops(data);
|
|
126
|
+
return loop;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=loop-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop-store.js","sourceRoot":"","sources":["../src/loop-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { LoopDraft, LoopSchedule } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Loop payload validation, extracted from index.ts so it is unit-testable
|
|
4
|
+
* without importing the CLI entrypoint (which calls program.parse() on load).
|
|
5
|
+
* Every validator returns an error string, or null when the input is valid.
|
|
6
|
+
*/
|
|
7
|
+
export declare const LOOP_HARNESSES: ReadonlySet<string>;
|
|
8
|
+
/** Validate a loop schedule (interval floor + cron parseability). */
|
|
9
|
+
export declare function validateSchedule(schedule: LoopSchedule | undefined): string | null;
|
|
10
|
+
export declare function validateRetention(retention: LoopDraft['retention'] | undefined): string | null;
|
|
11
|
+
/** Full-draft validation for create_loop. */
|
|
12
|
+
export declare function validateLoopDraft(draft: Partial<LoopDraft>): string | null;
|
|
13
|
+
/** Partial validation for update_loop — only the fields present in the patch. */
|
|
14
|
+
export declare function validateLoopPatch(patch: Partial<LoopDraft>): string | null;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { computeNextRun } from './loop-schedule.js';
|
|
2
|
+
/**
|
|
3
|
+
* Loop payload validation, extracted from index.ts so it is unit-testable
|
|
4
|
+
* without importing the CLI entrypoint (which calls program.parse() on load).
|
|
5
|
+
* Every validator returns an error string, or null when the input is valid.
|
|
6
|
+
*/
|
|
7
|
+
export const LOOP_HARNESSES = new Set([
|
|
8
|
+
'claude',
|
|
9
|
+
'cursor',
|
|
10
|
+
'codex',
|
|
11
|
+
'opencode',
|
|
12
|
+
'shell',
|
|
13
|
+
]);
|
|
14
|
+
/** Validate a loop schedule (interval floor + cron parseability). */
|
|
15
|
+
export function validateSchedule(schedule) {
|
|
16
|
+
if (!schedule || typeof schedule !== 'object')
|
|
17
|
+
return 'schedule is required';
|
|
18
|
+
if (schedule.kind === 'interval') {
|
|
19
|
+
if (typeof schedule.everyMs !== 'number' || !Number.isFinite(schedule.everyMs) || schedule.everyMs < 1000) {
|
|
20
|
+
return 'interval everyMs must be a finite number >= 1000';
|
|
21
|
+
}
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
if (schedule.kind === 'cron') {
|
|
25
|
+
if (typeof schedule.expression !== 'string' || !schedule.expression.trim()) {
|
|
26
|
+
return 'cron expression is required';
|
|
27
|
+
}
|
|
28
|
+
try {
|
|
29
|
+
computeNextRun(schedule, Date.now());
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return `Invalid cron expression: ${schedule.expression}`;
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
return 'schedule.kind must be "interval" or "cron"';
|
|
37
|
+
}
|
|
38
|
+
export function validateRetention(retention) {
|
|
39
|
+
const value = retention?.autoClearAfterRuns;
|
|
40
|
+
const ok = value === null || (typeof value === 'number' && Number.isFinite(value) && value >= 0);
|
|
41
|
+
return retention && ok ? null : 'retention.autoClearAfterRuns must be null or a non-negative number';
|
|
42
|
+
}
|
|
43
|
+
/** Full-draft validation for create_loop. */
|
|
44
|
+
export function validateLoopDraft(draft) {
|
|
45
|
+
if (!draft || typeof draft !== 'object')
|
|
46
|
+
return 'Invalid loop payload';
|
|
47
|
+
// Required so a create can never slip past the bridge-routing guard and get
|
|
48
|
+
// duplicated across every connected bridge (create mints a fresh id).
|
|
49
|
+
if (typeof draft.bridgeId !== 'string' || !draft.bridgeId.trim())
|
|
50
|
+
return 'bridgeId is required';
|
|
51
|
+
if (typeof draft.name !== 'string' || !draft.name.trim())
|
|
52
|
+
return 'Loop name is required';
|
|
53
|
+
if (typeof draft.task !== 'string' || !draft.task.trim())
|
|
54
|
+
return 'Loop task is required';
|
|
55
|
+
if (typeof draft.harness !== 'string' || !LOOP_HARNESSES.has(draft.harness)) {
|
|
56
|
+
return `Invalid harness: ${String(draft.harness)}`;
|
|
57
|
+
}
|
|
58
|
+
if (draft.overlapPolicy !== 'skip' && draft.overlapPolicy !== 'allow') {
|
|
59
|
+
return 'overlapPolicy must be "skip" or "allow"';
|
|
60
|
+
}
|
|
61
|
+
if (typeof draft.enabled !== 'boolean')
|
|
62
|
+
return 'enabled must be a boolean';
|
|
63
|
+
const retentionError = validateRetention(draft.retention);
|
|
64
|
+
if (retentionError)
|
|
65
|
+
return retentionError;
|
|
66
|
+
return validateSchedule(draft.schedule);
|
|
67
|
+
}
|
|
68
|
+
/** Partial validation for update_loop — only the fields present in the patch. */
|
|
69
|
+
export function validateLoopPatch(patch) {
|
|
70
|
+
if (!patch || typeof patch !== 'object')
|
|
71
|
+
return 'Invalid patch';
|
|
72
|
+
if ('name' in patch && (typeof patch.name !== 'string' || !patch.name.trim())) {
|
|
73
|
+
return 'Loop name must be a non-empty string';
|
|
74
|
+
}
|
|
75
|
+
if ('task' in patch && (typeof patch.task !== 'string' || !patch.task.trim())) {
|
|
76
|
+
return 'Loop task must be a non-empty string';
|
|
77
|
+
}
|
|
78
|
+
if ('harness' in patch && (typeof patch.harness !== 'string' || !LOOP_HARNESSES.has(patch.harness))) {
|
|
79
|
+
return `Invalid harness: ${String(patch.harness)}`;
|
|
80
|
+
}
|
|
81
|
+
if ('overlapPolicy' in patch && patch.overlapPolicy !== 'skip' && patch.overlapPolicy !== 'allow') {
|
|
82
|
+
return 'overlapPolicy must be "skip" or "allow"';
|
|
83
|
+
}
|
|
84
|
+
if ('enabled' in patch && typeof patch.enabled !== 'boolean')
|
|
85
|
+
return 'enabled must be a boolean';
|
|
86
|
+
if ('retention' in patch) {
|
|
87
|
+
const retentionError = validateRetention(patch.retention);
|
|
88
|
+
if (retentionError)
|
|
89
|
+
return retentionError;
|
|
90
|
+
}
|
|
91
|
+
if ('schedule' in patch)
|
|
92
|
+
return validateSchedule(patch.schedule);
|
|
93
|
+
return null;
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=loop-validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop-validation.js","sourceRoot":"","sources":["../src/loop-validation.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
CHANGED
|
@@ -19,12 +19,94 @@ export interface Session {
|
|
|
19
19
|
parentSessionId?: string;
|
|
20
20
|
runtime?: SessionRuntime;
|
|
21
21
|
errorReason?: string;
|
|
22
|
+
loopId?: string;
|
|
22
23
|
}
|
|
23
24
|
export type SessionStatus = 'pending' | 'running' | 'completed' | 'error';
|
|
24
25
|
/** Tombstone written to <dataDir>/archive.jsonl when a session is removed. */
|
|
25
26
|
export interface ArchivedSession extends Session {
|
|
26
27
|
removedAt: string;
|
|
27
28
|
}
|
|
29
|
+
export type LoopHarness = 'claude' | 'cursor' | 'codex' | 'opencode' | 'shell';
|
|
30
|
+
export type LoopRunStatus = 'ok' | 'error' | 'running' | 'skipped';
|
|
31
|
+
export type LoopSchedule = {
|
|
32
|
+
kind: 'interval';
|
|
33
|
+
everyMs: number;
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'cron';
|
|
36
|
+
expression: string;
|
|
37
|
+
tz?: string;
|
|
38
|
+
};
|
|
39
|
+
export interface LoopFlight {
|
|
40
|
+
command: string;
|
|
41
|
+
timeoutMs?: number;
|
|
42
|
+
}
|
|
43
|
+
export interface LoopPostflight {
|
|
44
|
+
command: string;
|
|
45
|
+
timeoutMs?: number;
|
|
46
|
+
/** Run postflight even when the fire was preflight-skipped. Default false. */
|
|
47
|
+
runOnSkip?: boolean;
|
|
48
|
+
}
|
|
49
|
+
export interface LoopRetention {
|
|
50
|
+
/** Keep newest N run-sessions; prune older ones. null = keep all. Default 10. */
|
|
51
|
+
autoClearAfterRuns: number | null;
|
|
52
|
+
}
|
|
53
|
+
/** Client-authored fields (create/edit form). */
|
|
54
|
+
export interface LoopDraft {
|
|
55
|
+
name: string;
|
|
56
|
+
bridgeId: string;
|
|
57
|
+
schedule: LoopSchedule;
|
|
58
|
+
harness: LoopHarness;
|
|
59
|
+
workdir?: string;
|
|
60
|
+
task: string;
|
|
61
|
+
model?: string;
|
|
62
|
+
enabled: boolean;
|
|
63
|
+
overlapPolicy: 'skip' | 'allow';
|
|
64
|
+
retention: LoopRetention;
|
|
65
|
+
preflight?: LoopFlight;
|
|
66
|
+
postflight?: LoopPostflight;
|
|
67
|
+
/** Optional deterministic backstop: force-stop + mark 'error' if a flight runs longer. */
|
|
68
|
+
maxRuntimeMs?: number;
|
|
69
|
+
}
|
|
70
|
+
/** Full server-authoritative record (LoopDraft + runtime state). */
|
|
71
|
+
export interface Loop extends LoopDraft {
|
|
72
|
+
id: string;
|
|
73
|
+
createdAt: string;
|
|
74
|
+
updatedAt: string;
|
|
75
|
+
lastRunAt?: string;
|
|
76
|
+
nextRunAt?: string;
|
|
77
|
+
lastStatus?: LoopRunStatus;
|
|
78
|
+
lastSessionId?: string;
|
|
79
|
+
runCount: number;
|
|
80
|
+
skipCount: number;
|
|
81
|
+
/** Transient manual-fire flag; set by run_loop_now, cleared on the next tick. */
|
|
82
|
+
runNowRequested?: boolean;
|
|
83
|
+
}
|
|
84
|
+
/** Legacy shape: older bridges exposed loop runs as sessions tagged with loopId. */
|
|
85
|
+
export type LoopRun = Session & {
|
|
86
|
+
loopId: string;
|
|
87
|
+
};
|
|
88
|
+
export interface LoopRunRecord {
|
|
89
|
+
/** Stable UI id. For spawned runs this is the session id; skipped runs mint their own id. */
|
|
90
|
+
id: string;
|
|
91
|
+
loopId: string;
|
|
92
|
+
bridgeId: string;
|
|
93
|
+
sessionId?: string;
|
|
94
|
+
name: string;
|
|
95
|
+
status: LoopRunStatus;
|
|
96
|
+
startedAt: string;
|
|
97
|
+
updatedAt: string;
|
|
98
|
+
finishedAt?: string;
|
|
99
|
+
durationMs?: number;
|
|
100
|
+
harness?: LoopHarness;
|
|
101
|
+
workdir?: string;
|
|
102
|
+
task?: string;
|
|
103
|
+
model?: string;
|
|
104
|
+
sessionStatus?: SessionStatus;
|
|
105
|
+
errorReason?: string;
|
|
106
|
+
logTail?: string;
|
|
107
|
+
logBytes?: number;
|
|
108
|
+
logTruncated?: boolean;
|
|
109
|
+
}
|
|
28
110
|
/** Inter-agent mail stored in <dataDir>/sessions/<id>/inbox.jsonl. */
|
|
29
111
|
export interface MailMessage {
|
|
30
112
|
id: string;
|
|
@@ -52,7 +134,7 @@ export interface Command {
|
|
|
52
134
|
payload: CommandPayload;
|
|
53
135
|
requestId: string;
|
|
54
136
|
}
|
|
55
|
-
export type CommandType = 'create_session' | 'stop_session' | 'list_sessions' | 'get_history' | 'retry_session' | 'send_message' | 'rename_session' | 'remove_session' | 'bridge_exec' | 'clear_terminal' | 'update_session_parent';
|
|
137
|
+
export type CommandType = 'create_session' | 'stop_session' | 'list_sessions' | 'get_history' | 'retry_session' | 'send_message' | 'rename_session' | 'remove_session' | 'bridge_exec' | 'clear_terminal' | 'update_session_parent' | 'create_loop' | 'list_loops' | 'update_loop' | 'delete_loop' | 'run_loop_now' | 'get_loop_runs';
|
|
56
138
|
export interface CreateSessionPayload {
|
|
57
139
|
command: string;
|
|
58
140
|
prompt?: string;
|
|
@@ -101,7 +183,30 @@ export interface RemoveSessionPayload {
|
|
|
101
183
|
export interface ClearTerminalPayload {
|
|
102
184
|
sessionId: string;
|
|
103
185
|
}
|
|
104
|
-
export
|
|
186
|
+
export interface CreateLoopPayload extends LoopDraft {
|
|
187
|
+
bridgeId: string;
|
|
188
|
+
}
|
|
189
|
+
export interface ListLoopsPayload {
|
|
190
|
+
bridgeId?: string;
|
|
191
|
+
}
|
|
192
|
+
export interface UpdateLoopPayload {
|
|
193
|
+
bridgeId: string;
|
|
194
|
+
loopId: string;
|
|
195
|
+
patch: Partial<LoopDraft>;
|
|
196
|
+
}
|
|
197
|
+
export interface DeleteLoopPayload {
|
|
198
|
+
bridgeId: string;
|
|
199
|
+
loopId: string;
|
|
200
|
+
}
|
|
201
|
+
export interface RunLoopNowPayload {
|
|
202
|
+
bridgeId: string;
|
|
203
|
+
loopId: string;
|
|
204
|
+
}
|
|
205
|
+
export interface GetLoopRunsPayload {
|
|
206
|
+
bridgeId: string;
|
|
207
|
+
loopId: string;
|
|
208
|
+
}
|
|
209
|
+
export type CommandPayload = CreateSessionPayload | StopSessionPayload | GetHistoryPayload | RenameSessionPayload | RemoveSessionPayload | BridgeExecPayload | ClearTerminalPayload | UpdateSessionParentPayload | CreateLoopPayload | ListLoopsPayload | UpdateLoopPayload | DeleteLoopPayload | RunLoopNowPayload | GetLoopRunsPayload | Record<string, unknown>;
|
|
105
210
|
export interface CommandResponse {
|
|
106
211
|
requestId: string;
|
|
107
212
|
success: boolean;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ftown-bridge",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "CLI bridge for ftown — generic PTY-over-Centrifugo relay",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -48,6 +48,7 @@
|
|
|
48
48
|
"@xterm/headless": "^6.0.0",
|
|
49
49
|
"centrifuge": "^5.2.2",
|
|
50
50
|
"commander": "^13.1.0",
|
|
51
|
+
"cron-parser": "^4.9.0",
|
|
51
52
|
"node-pty": "^1.2.0-beta.12",
|
|
52
53
|
"uuid": "^11.1.0",
|
|
53
54
|
"ws": "^8.18.0"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ftown
|
|
3
|
+
description: >-
|
|
4
|
+
Control ftown from inside agent sessions: create/list/read/drive sibling
|
|
5
|
+
sessions, send ftown mail, manage recurring scheduled loops, coordinate worker
|
|
6
|
+
agents, and run deterministic ftown-workflows. Use when a task mentions ftown,
|
|
7
|
+
ftown sessions, bridge sessions, child/sibling agents, mail/inbox, scheduled
|
|
8
|
+
loops, recurring agent runs, orchestration, fan-out, or ftown-workflows.
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# ftown
|
|
12
|
+
|
|
13
|
+
Use the local bridge CLIs installed under `~/.ftown`. They read
|
|
14
|
+
`~/.ftown/bridge.json`; anyone who can read that file can control the bridge's
|
|
15
|
+
sessions and loops.
|
|
16
|
+
|
|
17
|
+
## Pick The Reference
|
|
18
|
+
|
|
19
|
+
- Session control, mail, terminal output, archive/revive, or local API:
|
|
20
|
+
read `references/sessions.md`.
|
|
21
|
+
- Recurring scheduled work, cron/interval loops, loop run history:
|
|
22
|
+
read `references/loops.md`.
|
|
23
|
+
- Ad-hoc multi-agent coordination from an orchestrator session:
|
|
24
|
+
read `references/orchestrator.md`.
|
|
25
|
+
- Scripted, resumable, deterministic multi-session fan-out:
|
|
26
|
+
read `references/workflows.md`.
|
|
27
|
+
|
|
28
|
+
## Quick Commands
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
# Sessions
|
|
32
|
+
~/.ftown/ftown-sessions list
|
|
33
|
+
~/.ftown/ftown-sessions create --shell codex --prompt "Run tests" --workdir "$PWD" --parent
|
|
34
|
+
~/.ftown/ftown-sessions screen <session-id> --limit 200
|
|
35
|
+
~/.ftown/ftown-sessions tell <session-id> --type task "continue with the API client"
|
|
36
|
+
|
|
37
|
+
# Loops
|
|
38
|
+
~/.ftown/ftown-sessions loops
|
|
39
|
+
~/.ftown/ftown-sessions loop create --name repo-watch --every 30m --shell codex --workdir "$PWD" --task "Review recent changes"
|
|
40
|
+
~/.ftown/ftown-sessions loop run <loop-id>
|
|
41
|
+
|
|
42
|
+
# Workflows
|
|
43
|
+
~/.ftown/ftown-workflows run path/to/script.mjs --workdir "$PWD"
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Prefer loops for unattended recurrence. Prefer workflows for deterministic
|
|
47
|
+
control flow inside one run. Prefer orchestrator guidance for ad-hoc
|
|
48
|
+
human-in-the-loop multi-agent coordination.
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# ftown scheduled loops
|
|
2
|
+
|
|
3
|
+
Use this skill to manage bridge-owned recurring session producers. Each loop
|
|
4
|
+
stores a schedule, harness, task prompt, optional shell preflight/postflight
|
|
5
|
+
hooks, overlap policy, and retention. Every fire creates a normal ftown session
|
|
6
|
+
tagged with `loopId` and grouped under the loop in the dashboard.
|
|
7
|
+
|
|
8
|
+
Prefer loops for unattended recurrence over sleeping/polling inside a long-lived
|
|
9
|
+
agent. Prefer `ftown-workflows` for deterministic control flow inside one run.
|
|
10
|
+
|
|
11
|
+
## CLI
|
|
12
|
+
|
|
13
|
+
The bridge installs loop commands through `~/.ftown/ftown-sessions loop ...`.
|
|
14
|
+
The unified skill also bundles `scripts/ftown`, which delegates to the top-level
|
|
15
|
+
`~/.ftown/ftown` dispatcher.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
CLI=~/.ftown/ftown-sessions
|
|
19
|
+
|
|
20
|
+
# List loops
|
|
21
|
+
$CLI loops
|
|
22
|
+
$CLI loop list --plain
|
|
23
|
+
|
|
24
|
+
# Create an interval loop
|
|
25
|
+
$CLI loop create \
|
|
26
|
+
--name repo-watch \
|
|
27
|
+
--every 30m \
|
|
28
|
+
--shell codex \
|
|
29
|
+
--workdir /path/to/repo \
|
|
30
|
+
--task "Inspect recent changes, run the focused checks, and report issues" \
|
|
31
|
+
--retention 10
|
|
32
|
+
|
|
33
|
+
# Create a cron loop
|
|
34
|
+
$CLI loop create \
|
|
35
|
+
--name weekday-triage \
|
|
36
|
+
--cron "0 9 * * 1-5" \
|
|
37
|
+
--tz America/New_York \
|
|
38
|
+
--task "Triage new issues and summarize priority"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Common Operations
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Inspect
|
|
45
|
+
~/.ftown/ftown-sessions loop get <loop-id>
|
|
46
|
+
~/.ftown/ftown-sessions loop runs <loop-id>
|
|
47
|
+
|
|
48
|
+
# Manual fire; skip-policy loops refuse this while a prior run is still alive
|
|
49
|
+
~/.ftown/ftown-sessions loop run <loop-id>
|
|
50
|
+
|
|
51
|
+
# Pause / resume
|
|
52
|
+
~/.ftown/ftown-sessions loop pause <loop-id>
|
|
53
|
+
~/.ftown/ftown-sessions loop resume <loop-id>
|
|
54
|
+
|
|
55
|
+
# Update schedule, prompt, harness, retention, or runtime guard
|
|
56
|
+
~/.ftown/ftown-sessions loop update <loop-id> --every 1h --task "updated task"
|
|
57
|
+
~/.ftown/ftown-sessions loop update <loop-id> --retention all
|
|
58
|
+
~/.ftown/ftown-sessions loop update <loop-id> --max-runtime 20m
|
|
59
|
+
|
|
60
|
+
# Delete. Any in-flight run owned by the loop is stopped by the bridge.
|
|
61
|
+
~/.ftown/ftown-sessions loop delete <loop-id>
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Options
|
|
65
|
+
|
|
66
|
+
| Flag | Meaning |
|
|
67
|
+
| --- | --- |
|
|
68
|
+
| `--every <duration>` | Interval schedule such as `30s`, `5m`, `2h`, `1d`. Minimum is `1s`. |
|
|
69
|
+
| `--cron <expr>` / `--tz <zone>` | Cron schedule with optional IANA timezone. |
|
|
70
|
+
| `--shell <type>` | `claude`, `cursor`, `codex`, `opencode`, or `shell`. |
|
|
71
|
+
| `--workdir <path>` | Working directory for each run. |
|
|
72
|
+
| `--model <name>` | Harness model override when supported. |
|
|
73
|
+
| `--disabled` / `--enabled` | Create/update enabled state. |
|
|
74
|
+
| `--allow-overlap` / `--skip-overlap` | Allow concurrent runs, or skip while the previous run is alive. Default is skip. |
|
|
75
|
+
| `--retention <n|all>` | Keep newest N run sessions, or keep all. Default create value is `10`. |
|
|
76
|
+
| `--preflight <cmd>` | Shell guard before a run. Non-zero exit records `skipped` and spawns no session. |
|
|
77
|
+
| `--postflight <cmd>` | Shell hook after a run. Receives `FTOWN_RUN_STATUS`, `FTOWN_RUN_SESSION_ID`, and `FTOWN_RUN_OUTPUT`. |
|
|
78
|
+
| `--postflight-on-skip` | Also run postflight after a preflight skip. |
|
|
79
|
+
| `--max-runtime <duration>` | Force-stop a run and mark the loop error after this duration. |
|
|
80
|
+
|
|
81
|
+
## Notes
|
|
82
|
+
|
|
83
|
+
- `loop run` sets a one-shot manual request. It bypasses the enabled flag but
|
|
84
|
+
still honors skip-overlap when the prior run is alive.
|
|
85
|
+
- `{{preflight}}` in the task prompt is replaced with preflight stdout, and the
|
|
86
|
+
same stdout is available to the run as `FTOWN_PREFLIGHT_OUTPUT`.
|
|
87
|
+
- Loop state persists in `~/.ftown/loops.json` on the bridge machine. Anyone who
|
|
88
|
+
can read `~/.ftown/bridge.json` can control loops through the local API.
|