{"version":3,"sources":["../src/presence/registry.ts","../src/sharing/token.ts","../src/sharing/sse-relay.ts"],"names":["emitActivity","onActivity","readActivityHistory","resetActivityRegistry"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA,IAAA,gBAAA,GAAA,EAAA;AAAA,QAAA,CAAA,gBAAA,EAAA;AAAA,EAAA,YAAA,EAAA,MAAAA,4BAAA;AAAA,EAAA,UAAA,EAAA,MAAAC,0BAAA;AAAA,EAAA,mBAAA,EAAA,MAAAC,mCAAA;AAAA,EAAA,qBAAA,EAAA,MAAAC;AAAA,CAAA,CAAA;AAAA,IAAA,aAAA,GAAA,KAAA,CAAA;AAAA,EAAA,0BAAA,GAAA;AAAA,EAAA;AAAA,CAAA,CAAA;;;ACQA,IAAM,WAAA,GAAc,EAAA;AAWb,SAAS,uBAAA,GAA6C;AAC3D,EAAA,MAAM,EAAA,GAAK,SAAS,CAAC,CAAA;AACrB,EAAA,MAAM,QAAQ,WAAA,EAAY;AAC1B,EAAA,OAAO,EAAE,IAAI,KAAA,EAAO,OAAA,EAAS,MAAM,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA,EAAE;AACjD;AAEO,SAAS,eAAA,CAAgB,IAAY,KAAA,EAAkC;AAC5E,EAAA,OAAO,EAAE,IAAI,KAAA,EAAO,OAAA,EAAS,MAAM,KAAA,CAAM,CAAA,EAAG,CAAC,CAAA,EAAE;AACjD;AAYO,SAAS,aAAA,CACd,UAAA,EACA,OAAA,GAAkB,OAAO,WAAW,WAAA,GAAc,MAAA,CAAO,QAAA,CAAS,IAAA,CAAK,KAAA,CAAM,GAAG,CAAA,CAAE,CAAC,IAAI,EAAA,EAC/E;AACR,EAAA,MAAM,CAAA,GAAI,IAAI,GAAA,CAAI,OAAO,CAAA;AACzB,EAAA,CAAA,CAAE,IAAA,GAAO,IAAI,eAAA,CAAgB,EAAE,OAAA,EAAS,UAAA,CAAW,EAAA,EAAI,KAAA,EAAO,UAAA,CAAW,KAAA,EAAO,CAAA,CAAE,QAAA,EAAS;AAC3F,EAAA,OAAO,EAAE,QAAA,EAAS;AACpB;AAGO,SAAS,gBAAA,CAAiB,UAAA,EAA+B,SAAA,GAAY,mBAAA,EAAqB;AAC/F,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,CAAA,WAAA,EAAc,UAAA,CAAW,EAAE,CAAA,CAAA;AAAA,IACjC,SAAA;AAAA,IACA,SAAS,UAAA,CAAW,EAAA;AAAA,IACpB,OAAO,UAAA,CAAW,KAAA;AAAA,IAClB,OAAA,EAAS,CAAA,UAAA,EAAa,UAAA,CAAW,EAAE,CAAA,CAAA;AAAA,IACnC,gBAAA,EAAkB;AAAA,GACpB;AACF;AAOO,SAAS,kBAAA,GAA+C;AAC7D,EAAA,IAAI,OAAO,MAAA,KAAW,WAAA,EAAa,OAAO,IAAA;AAC1C,EAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,MAAA,CAAO,SAAS,IAAI,CAAA;AACxC,EAAA,MAAM,IAAA,GAAO,IAAI,eAAA,CAAgB,GAAA,CAAI,KAAK,OAAA,CAAQ,IAAA,EAAM,EAAE,CAAC,CAAA;AAC3D,EAAA,MAAM,EAAA,GAAK,KAAK,GAAA,CAAI,SAAS,KAAK,GAAA,CAAI,YAAA,CAAa,IAAI,SAAS,CAAA;AAChE,EAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,CAAI,OAAO,KAAK,GAAA,CAAI,YAAA,CAAa,IAAI,OAAO,CAAA;AAC/D,EAAA,IAAI,CAAC,EAAA,IAAM,CAAC,KAAA,EAAO,OAAO,IAAA;AAC1B,EAAA,OAAO,eAAA,CAAgB,IAAI,KAAK,CAAA;AAClC;AAEA,SAAS,WAAA,GAAsB;AAC7B,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,WAAW,CAAA;AACxC,EAAA,MAAA,CAAO,gBAAgB,KAAK,CAAA;AAC5B,EAAA,OAAO,UAAU,KAAK,CAAA;AACxB;AAEA,SAAS,SAAS,GAAA,EAAqB;AACrC,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,IAAA,CAAK,KAAM,GAAA,GAAM,CAAA,GAAK,CAAC,CAAC,CAAA;AACrD,EAAA,MAAA,CAAO,gBAAgB,KAAK,CAAA;AAC5B,EAAA,OAAO,SAAA,CAAU,KAAK,CAAA,CAAE,KAAA,CAAM,GAAG,GAAG,CAAA;AACtC;AAEA,SAAS,UAAU,KAAA,EAA2B;AAC5C,EAAA,IAAI,CAAA,GAAI,EAAA;AACR,EAAA,KAAA,MAAW,CAAA,IAAK,KAAA,EAAO,CAAA,IAAK,MAAA,CAAO,aAAa,CAAC,CAAA;AACjD,EAAA,OAAO,IAAA,CAAK,CAAC,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,KAAA,EAAO,GAAG,CAAA,CAAE,OAAA,CAAQ,OAAO,EAAE,CAAA;AAC1E;AAGO,SAAS,iBAAA,CAAkB,GAAW,CAAA,EAAoB;AAC/D,EAAA,IAAI,CAAA,CAAE,MAAA,KAAW,CAAA,CAAE,MAAA,EAAQ,OAAO,KAAA;AAClC,EAAA,IAAI,IAAA,GAAO,CAAA;AACX,EAAA,KAAA,IAAS,CAAA,GAAI,CAAA,EAAG,CAAA,GAAI,CAAA,CAAE,MAAA,EAAQ,CAAA,EAAA,EAAK,IAAA,IAAQ,CAAA,CAAE,UAAA,CAAW,CAAC,CAAA,GAAI,CAAA,CAAE,WAAW,CAAC,CAAA;AAC3E,EAAA,OAAO,IAAA,KAAS,CAAA;AAClB;;;AC7FA,aAAA,EAAA;AAkDO,IAAM,oBAAN,MAA6C;AAAA,EAclD,YAAY,OAAA,EAA0B;AAVtC,IAAA,IAAA,CAAQ,QAAA,GAAW,KAAA;AAEnB,IAAA,IAAA,CAAQ,YAA8B,EAAC;AACvC,IAAA,IAAA,CAAQ,SAAA,GAAY,KAAA;AACpB,IAAA,IAAA,CAAQ,SAAA,uBAAgB,GAAA,EAAiC;AACzD,IAAA,IAAA,CAAQ,KAAA,GAAoB,MAAA;AAE5B,IAAA,IAAA,CAAQ,KAAA,uBAAY,GAAA,EAAY;AAChC,IAAA,IAAA,CAAQ,aAAA,uBAAoB,GAAA,EAA6B;AAGvD,IAAA,IAAA,CAAK,IAAA,GAAO,OAAA;AACZ,IAAA,IAAA,CAAK,gBAAgB,OAAA,CAAQ,KAAA;AAAA,EAC/B;AAAA,EAEA,WAAW,MAAA,EAA8B;AACvC,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA;AAAA,EAGA,KAAA,GAAc;AACZ,IAAA,IAAI,KAAK,SAAA,IAAa,IAAA,CAAK,QAAA,IAAY,OAAO,WAAW,WAAA,EAAa;AACtE,IAAA,IAAA,CAAK,SAAS,YAAY,CAAA;AAK1B,IAAA,KAAK,OAAO,kCAAkC,CAAA,CAC3C,KAAK,CAAC,EAAE,oBAAmB,KAAM;AAChC,MAAA,IAAI,IAAA,CAAK,SAAA,IAAa,IAAA,CAAK,QAAA,EAAU;AACrC,MAAA,IAAA,CAAK,UAAU,kBAAA,CAAmB;AAAA,QAChC,OAAA,EAAS,KAAK,IAAA,CAAK,OAAA;AAAA,QACnB,OAAA,EAAS,KAAK,IAAA,CAAK,SAAA;AAAA,QACnB,KAAA,EAAO,KAAK,IAAA,CAAK,KAAA;AAAA,QACjB,SAAA,EAAW,MAAA;AAAA,QACX,gBAAA,EAAkB,SAAA;AAAA,QAClB,QAAA,EAAU,QAAA;AAAA,QACV,OAAA,EAAS,CAAC,GAAA,KAAQ,IAAA,CAAK,cAAc,GAAG,CAAA;AAAA,QACxC,MAAA,EAAQ,MAAM,IAAA,CAAK,QAAA,EAAS;AAAA,QAC5B,OAAA,EAAS,MAAM,IAAA,CAAK,QAAA,CAAS,OAAO,CAAA;AAAA,QACpC,SAAA,EAAW,KAAK,IAAA,CAAK;AAAA,OACtB,CAAA;AACD,MAAA,IAAA,CAAK,QAAQ,KAAA,EAAM;AAAA,IACrB,CAAC,CAAA,CACA,KAAA,CAAM,MAAM;AACX,MAAA,IAAI,CAAC,IAAA,CAAK,QAAA,EAAU,IAAA,CAAK,QAAA,EAAS;AAAA,IACpC,CAAC,CAAA;AAAA,EACL;AAAA;AAAA,EAGQ,QAAA,GAAiB;AACvB,IAAA,IAAI,IAAA,CAAK,SAAA,IAAa,IAAA,CAAK,QAAA,EAAU;AACrC,IAAA,MAAM,MAAM,CAAA,EAAG,IAAA,CAAK,IAAA,CAAK,OAAO,IAAI,kBAAA,CAAmB,IAAA,CAAK,IAAA,CAAK,SAAS,CAAC,CAAA,cAAA,EAAiB,kBAAA,CAAmB,IAAA,CAAK,IAAA,CAAK,KAAK,CAAC,CAAA,CAAA;AAC/H,IAAA,MAAM,KAAK,IAAI,WAAA,CAAY,KAAK,EAAE,eAAA,EAAiB,OAAO,CAAA;AAC1D,IAAA,IAAA,CAAK,EAAA,GAAK,EAAA;AACV,IAAA,EAAA,CAAG,gBAAA,CAAiB,MAAA,EAAQ,MAAM,IAAA,CAAK,UAAU,CAAA;AACjD,IAAA,EAAA,CAAG,gBAAA,CAAiB,OAAO,CAAC,EAAA,KAAqB,KAAK,aAAA,CAAc,EAAA,CAAG,IAAI,CAAC,CAAA;AAC5E,IAAA,EAAA,CAAG,iBAAiB,OAAA,EAAS,MAAM,IAAA,CAAK,QAAA,CAAS,OAAO,CAAC,CAAA;AAAA,EAC3D;AAAA;AAAA,EAGQ,QAAA,GAAiB;AACvB,IAAA,IAAA,CAAK,SAAA,GAAY,IAAA;AACjB,IAAA,IAAA,CAAK,SAAS,MAAM,CAAA;AACpB,IAAA,MAAM,MAAA,GAAS,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,CAAC,CAAA;AACtC,IAAA,KAAA,MAAW,GAAA,IAAO,MAAA,EAAQ,IAAA,CAAK,OAAA,CAAQ,GAAG,CAAA;AAAA,EAC5C;AAAA,EAEA,KAAK,OAAA,EAA+B;AAClC,IAAA,IAAI,CAAC,KAAK,SAAA,EAAW;AACnB,MAAA,IAAA,CAAK,SAAA,CAAU,KAAK,OAAO,CAAA;AAC3B,MAAA;AAAA,IACF;AACA,IAAA,IAAA,CAAK,QAAQ,OAAO,CAAA;AAAA,EACtB;AAAA,EAEA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,QAAA,GAAW,IAAA;AAChB,IAAA,IAAA,CAAK,SAAS,IAAA,EAAK;AACnB,IAAA,IAAA,CAAK,OAAA,GAAU,MAAA;AACf,IAAA,IAAA,CAAK,IAAI,KAAA,EAAM;AACf,IAAA,IAAA,CAAK,EAAA,GAAK,MAAA;AACV,IAAA,IAAA,CAAK,SAAA,GAAY,KAAA;AACjB,IAAA,IAAA,CAAK,MAAM,KAAA,EAAM;AACjB,IAAA,IAAA,CAAK,WAAA,EAAY;AACjB,IAAA,IAAA,CAAK,SAAS,QAAQ,CAAA;AAAA,EACxB;AAAA,EAEA,cAAc,QAAA,EAAmD;AAC/D,IAAA,IAAA,CAAK,SAAA,CAAU,IAAI,QAAQ,CAAA;AAC3B,IAAA,QAAA,CAAS,KAAK,KAAK,CAAA;AACnB,IAAA,OAAO,MAAM,IAAA,CAAK,SAAA,CAAU,MAAA,CAAO,QAAQ,CAAA;AAAA,EAC7C;AAAA;AAAA,EAGA,SAAA,GAAoB;AAClB,IAAA,OAAO,KAAK,KAAA,CAAM,IAAA;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,cAAc,QAAA,EAA+C;AAC3D,IAAA,IAAA,CAAK,aAAA,CAAc,IAAI,QAAQ,CAAA;AAC/B,IAAA,QAAA,CAAS,IAAA,CAAK,MAAM,IAAI,CAAA;AACxB,IAAA,OAAO,MAAM,IAAA,CAAK,aAAA,CAAc,MAAA,CAAO,QAAQ,CAAA;AAAA,EACjD;AAAA,EAEQ,WAAA,GAAoB;AAC1B,IAAA,KAAA,MAAW,KAAK,IAAA,CAAK,aAAA,EAAe,CAAA,CAAE,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,iBAAA,CAAkB,OAAA,EAAkC,KAAA,EAA+B;AACvF,IAAA,IAAI,UAAU,MAAA,IAAa,CAAC,kBAAkB,KAAA,EAAO,IAAA,CAAK,aAAa,CAAA,EAAG;AAC1E,IAAA,IAAI,CAAC,IAAA,CAAK,MAAA,EAAQ,MAAM,IAAI,MAAM,uCAAuC,CAAA;AACzE,IAAA,MAAM,UAA0B,OAAO,OAAA,KAAY,WAAW,IAAA,CAAK,KAAA,CAAM,OAAO,CAAA,GAAI,OAAA;AACpF,IAAA,MAAM,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,IAAA,EAAM,OAAO,CAAA;AAAA,EACzC;AAAA,EAEA,MAAc,QAAQ,OAAA,EAAwC;AAC5D,IAAA,MAAM,MAAM,CAAA,EAAG,IAAA,CAAK,IAAA,CAAK,OAAO,IAAI,kBAAA,CAAmB,IAAA,CAAK,IAAA,CAAK,SAAS,CAAC,CAAA,cAAA,EAAiB,kBAAA,CAAmB,IAAA,CAAK,IAAA,CAAK,KAAK,CAAC,CAAA,CAAA;AAC/H,IAAA,MAAM,CAAA,GAAI,IAAA,CAAK,IAAA,CAAK,KAAA,IAAS,KAAA;AAC7B,IAAA,IAAI;AACF,MAAA,MAAM,EAAE,GAAA,EAAK;AAAA,QACX,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS,EAAE,cAAA,EAAgB,kBAAA,EAAoB,UAAU,kBAAA,EAAmB;AAAA,QAC5E,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,OAAO;AAAA,OAC7B,CAAA;AAAA,IACH,CAAA,CAAA,MAAQ;AAAA,IAER;AAAA,EACF;AAAA,EAEA,MAAc,cAAc,GAAA,EAA4B;AACtD,IAAA,IAAI,CAAC,KAAK,MAAA,EAAQ;AAClB,IAAA,IAAI,OAAA;AACJ,IAAA,IAAI;AACF,MAAA,OAAA,GAAU,IAAA,CAAK,MAAM,GAAG,CAAA;AAAA,IAC1B,CAAA,CAAA,MAAQ;AACN,MAAA;AAAA,IACF;AACA,IAAA,IAAI,QAAA,IAAY,OAAA,IAAW,OAAA,CAAQ,MAAA,KAAW,2BAAA,EAA6B;AACzE,MAAA,MAAM,YAAA,GAAe,MAAA,CAAQ,OAAA,CAAQ,MAAA,EAAmD,gBAAgB,MAAM,CAAA;AAC9G,MAAA,IAAA,CAAK,KAAA,CAAM,IAAI,YAAY,CAAA;AAC3B,MAAA,IAAA,CAAK,WAAA,EAAY;AACjB,MAAAH,4BAAA,CAAa;AAAA,QACX,OAAA,EAAS,IAAA,CAAK,IAAA,CAAK,KAAA,EAAO,EAAA,IAAM,OAAA;AAAA,QAChC,SAAA,EAAW,IAAA,CAAK,IAAA,CAAK,KAAA,EAAO,IAAA,IAAQ,OAAA;AAAA,QACpC,UAAA,EAAY,IAAA,CAAK,IAAA,CAAK,KAAA,EAAO,KAAA,IAAS,SAAA;AAAA,QACtC,MAAA,EAAQ,iBAAA;AAAA,QACR,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,QACpB,MAAA,EAAQ,EAAE,IAAA,EAAM,YAAA,EAAc,OAAO,iBAAA,EAAkB;AAAA,QACvD,IAAA,EAAM,EAAE,YAAA;AAAa,OACtB,CAAA;AACD,MAAA;AAAA,IACF;AACA,IAAA,IAAI,QAAA,IAAY,OAAA,IAAW,OAAA,CAAQ,MAAA,KAAW,yBAAA,EAA2B;AACvE,MAAA,MAAM,YAAA,GAAe,MAAA,CAAQ,OAAA,CAAQ,MAAA,EAAmD,gBAAgB,MAAM,CAAA;AAC9G,MAAA,IAAA,CAAK,KAAA,CAAM,OAAO,YAAY,CAAA;AAC9B,MAAA,IAAA,CAAK,WAAA,EAAY;AACjB,MAAA,IAAI,IAAA,CAAK,KAAA,CAAM,IAAA,KAAS,CAAA,EAAG;AACzB,QAAAA,4BAAA,CAAa;AAAA,UACX,OAAA,EAAS,IAAA,CAAK,IAAA,CAAK,KAAA,EAAO,EAAA,IAAM,OAAA;AAAA,UAChC,SAAA,EAAW,IAAA,CAAK,IAAA,CAAK,KAAA,EAAO,IAAA,IAAQ,OAAA;AAAA,UACpC,UAAA,EAAY,IAAA,CAAK,IAAA,CAAK,KAAA,EAAO,KAAA,IAAS,SAAA;AAAA,UACtC,MAAA,EAAQ,oBAAA;AAAA,UACR,SAAA,EAAW,KAAK,GAAA,EAAI;AAAA,UACpB,MAAA,EAAQ,EAAE,IAAA,EAAM,YAAA,EAAc,OAAO,oBAAA,EAAqB;AAAA,UAC1D,IAAA,EAAM,EAAE,YAAA;AAAa,SACtB,CAAA;AAAA,MACH;AACA,MAAA;AAAA,IACF;AACA,IAAA,MAAM,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,IAAA,EAAM,OAAO,CAAA;AAAA,EACzC;AAAA,EAEQ,SAAS,KAAA,EAAyB;AACxC,IAAA,IAAA,CAAK,KAAA,GAAQ,KAAA;AACb,IAAA,KAAA,MAAW,CAAA,IAAK,IAAA,CAAK,SAAA,EAAW,CAAA,CAAE,KAAK,CAAA;AAAA,EACzC;AACF;AAIO,SAAS,cAAA,CAAe,QAAwB,OAAA,EAA6C;AAClG,EAAA,MAAM,SAAA,GAAY,IAAI,iBAAA,CAAkB,OAAO,CAAA;AAC/C,EAAA,SAAA,CAAU,WAAW,MAAM,CAAA;AAC3B,EAAA,MAAA,CAAO,OAAO,SAAS,CAAA;AACvB,EAAA,SAAA,CAAU,KAAA,EAAM;AAMhB,EAAA,OAAA,CAAA,OAAA,EAAA,CAAA,IAAA,CAAA,OAAA,aAAA,EAAA,EAAA,gBAAA,CAAA,CAAA,CAA+B,IAAA,CAAK,CAAC,EAAE,UAAA,EAAAC,aAAW,KAAM;AACtD,IAAA,MAAM,GAAA,GAAMA,WAAAA,CAAW,CAAC,KAAA,KAAU;AAChC,MAAA,IAAI,QAAQ,cAAA,EAAgB;AAC1B,QAAA,IAAI,IAAA;AACJ,QAAA,IAAI;AACF,UAAA,IAAA,GAAO,OAAA,CAAQ,eAAe,KAAK,CAAA;AAAA,QACrC,CAAA,CAAA,MAAQ;AAIN,UAAA;AAAA,QACF;AACA,QAAA,IAAI,CAAC,IAAA,EAAM;AAAA,MACb;AACA,MAAA,SAAA,CAAU,IAAA,CAAK;AAAA,QACb,OAAA,EAAS,KAAA;AAAA,QACT,MAAA,EAAQ,8BAAA;AAAA,QACR,MAAA,EAAQ;AAAA,OACT,CAAA;AAAA,IACH,CAAC,CAAA;AAED,IAAA,MAAM,SAAA,GAAY,SAAA,CAAU,KAAA,CAAM,IAAA,CAAK,SAAS,CAAA;AAChD,IAAA,SAAA,CAAU,QAAQ,MAAM;AACtB,MAAA,GAAA,EAAI;AACJ,MAAA,SAAA,EAAU;AAAA,IACZ,CAAA;AAAA,EACF,CAAC,CAAA,CAAE,KAAA,CAAM,MAAM;AAAA,EAEf,CAAC,CAAA;AAED,EAAA,OAAO,SAAA;AACT","file":"sharing.cjs","sourcesContent":["/**\n * In-process registry of activity events. Now backed by the shared bus in\n * `@particle-academy/fancy-auto-common`, so agent activity (here) and flow\n * activity (fancy-flow's FlowRunnerUx) land in ONE stream — presence layers\n * render both. Re-exported under the same names for back-compat.\n */\nexport {\n  emitActivity,\n  onActivity,\n  readActivityHistory,\n  resetActivityRegistry,\n} from \"@particle-academy/fancy-auto-common\";\n","/**\n * Session-token utilities. The token is a high-entropy secret; possession\n * grants read/write on the session. We don't HMAC frames — frames carry\n * the token directly (which is fine for in-process / same-origin / TLS\n * transports). For lower-trust transports, host apps can layer signing\n * on top of the BroadcastChannelTransport.\n */\n\nconst TOKEN_BYTES = 24; // 192 bits, base64url-encoded → 32 chars\n\nexport type SessionDescriptor = {\n  /** Stable session identifier. Channel name = `fai:share:${id}`. */\n  id: string;\n  /** Secret token. Treat as a password — anyone with it can read/write. */\n  token: string;\n  /** Pretty hash for display (first 8 chars of token). */\n  display: string;\n};\n\nexport function createSessionDescriptor(): SessionDescriptor {\n  const id = randomId(8);\n  const token = randomToken();\n  return { id, token, display: token.slice(0, 8) };\n}\n\nexport function describeSession(id: string, token: string): SessionDescriptor {\n  return { id, token, display: token.slice(0, 8) };\n}\n\n/**\n * Build the shareable URL for the current page (preserves path, adds\n * session+token in the URL FRAGMENT).\n *\n * The token goes in the `#fragment`, never the `?query`: a fragment is never\n * transmitted to the server, so it can't leak through access/proxy logs or the\n * `Referer` header sent to third-party assets on the page. The recipient reads\n * it back with {@link readSessionFromUrl}. (The on-wire relay leg still sends\n * `?token=` today — moving that off the query is the larger transport change.)\n */\nexport function buildShareUrl(\n  descriptor: SessionDescriptor,\n  baseUrl: string = typeof window !== \"undefined\" ? window.location.href.split(\"?\")[0] : \"\",\n): string {\n  const u = new URL(baseUrl);\n  u.hash = new URLSearchParams({ session: descriptor.id, token: descriptor.token }).toString();\n  return u.toString();\n}\n\n/** Build the JSON config form (suitable for Claude Desktop / Cline / etc.). */\nexport function buildShareConfig(descriptor: SessionDescriptor, transport = \"broadcast-channel\") {\n  return {\n    name: `whiteboard-${descriptor.id}`,\n    transport,\n    session: descriptor.id,\n    token: descriptor.token,\n    channel: `fai:share:${descriptor.id}`,\n    protocol_version: \"2025-06-18\",\n  };\n}\n\n/**\n * Read session descriptor from the current URL, or null if not a shared link.\n * Prefers the fragment (the non-leaking form {@link buildShareUrl} now emits),\n * falling back to the query string for links minted before that change.\n */\nexport function readSessionFromUrl(): SessionDescriptor | null {\n  if (typeof window === \"undefined\") return null;\n  const url = new URL(window.location.href);\n  const frag = new URLSearchParams(url.hash.replace(/^#/, \"\"));\n  const id = frag.get(\"session\") ?? url.searchParams.get(\"session\");\n  const token = frag.get(\"token\") ?? url.searchParams.get(\"token\");\n  if (!id || !token) return null;\n  return describeSession(id, token);\n}\n\nfunction randomToken(): string {\n  const bytes = new Uint8Array(TOKEN_BYTES);\n  crypto.getRandomValues(bytes);\n  return base64Url(bytes);\n}\n\nfunction randomId(len: number): string {\n  const bytes = new Uint8Array(Math.ceil((len * 3) / 4));\n  crypto.getRandomValues(bytes);\n  return base64Url(bytes).slice(0, len);\n}\n\nfunction base64Url(bytes: Uint8Array): string {\n  let s = \"\";\n  for (const b of bytes) s += String.fromCharCode(b);\n  return btoa(s).replace(/\\+/g, \"-\").replace(/\\//g, \"_\").replace(/=+$/, \"\");\n}\n\n/** Constant-time string compare so a mismatched token leaks no timing info. */\nexport function constantTimeEqual(a: string, b: string): boolean {\n  if (a.length !== b.length) return false;\n  let diff = 0;\n  for (let i = 0; i < a.length; i++) diff |= a.charCodeAt(i) ^ b.charCodeAt(i);\n  return diff === 0;\n}\n","import type { JsonRpcMessage } from \"../mcp/types\";\nimport type { Transport } from \"../mcp/server\";\nimport type { MicroMcpServer } from \"../mcp/server\";\nimport type { RelayChannelHandle } from \"@particle-academy/fancy-cf-relay\";\nimport { constantTimeEqual } from \"./token\";\nimport type { AgentActivityEvent } from \"../presence/types\";\nimport { emitActivity } from \"../presence/registry\";\n\n/**\n * SseRelayTransport — bridges the in-page MicroMcpServer to a host-app\n * relay broker over Server-Sent Events (inbound) + POST (outbound).\n *\n * Wire model:\n *   - Browser opens an EventSource at `${baseUrl}/${sessionId}/events?token=…`.\n *     Each `event: mcp` carries one JSON-RPC frame from a remote client.\n *   - Browser POSTs JSON-RPC frames to `${baseUrl}/${sessionId}/outbox?token=…`\n *     when the local server has a response/notification to send.\n *\n * The host provides the relay endpoint (any HTTP server). See the demo\n * `WhiteboardShareController` for the reference implementation.\n *\n * Token authentication is the host's job — this transport just carries the\n * token in the query string. For lower-trust deployments, layer signing on\n * top by wrapping `send` / `deliverFromRemote`.\n */\nexport type SseRelayOptions = {\n  /**\n   * Which in-process activity events this relay forwards to ITS agent.\n   *\n   * The activity bus is global to the page, so with two concurrent sessions —\n   * site co-browse and the agent playground — each relay forwarded the other's\n   * traffic and every agent saw every other agent's navigations. Returning\n   * `false` drops the event for this relay only.\n   *\n   * Omitted means forward everything, which is the historical behaviour and\n   * correct for the common single-session case. A throwing predicate drops that\n   * one event and keeps the subscription: a host predicate is host code, and\n   * one bad event must not silently end forwarding for the session — that looks\n   * exactly like the agent going quiet.\n   */\n  activityFilter?: (event: AgentActivityEvent) => boolean;\n  /**\n   * Identity stamped on the relay's own connect/disconnect activity.\n   *\n   * These were emitted with a literal `agentId: \"agent\"`, which made two\n   * sessions' connect events indistinguishable — and unfilterable, since there\n   * was nothing to tell them apart by.\n   */\n  agent?: { id: string; name?: string; color?: string };\n  baseUrl: string;\n  sessionId: string;\n  token: string;\n  /** Override fetch (testing / non-browser). Defaults to global fetch. */\n  fetch?: typeof fetch;\n};\n\nexport class SseRelayTransport implements Transport {\n  private server?: MicroMcpServer;\n  private es?: EventSource;\n  private channel?: RelayChannelHandle;\n  private disposed = false;\n  private opts: SseRelayOptions;\n  private sendQueue: JsonRpcMessage[] = [];\n  private connected = false;\n  private listeners = new Set<(state: RelayState) => void>();\n  private state: RelayState = \"idle\";\n  private expectedToken: string;\n  private peers = new Set<string>();\n  private peerListeners = new Set<(count: number) => void>();\n\n  constructor(options: SseRelayOptions) {\n    this.opts = options;\n    this.expectedToken = options.token;\n  }\n\n  bindServer(server: MicroMcpServer): void {\n    this.server = server;\n  }\n\n  /** Open the receive channel. Idempotent. */\n  start(): void {\n    if (this.connected || this.disposed || typeof window === \"undefined\") return;\n    this.setState(\"connecting\");\n    // Prefer @particle-academy/fancy-cf-relay's adaptive channel (auto SSE↔\n    // long-poll with Cloudflare detection) so the receive leg survives a\n    // Cloudflare HTTP/3 (QUIC) edge that resets long-lived SSE streams. It's an\n    // OPTIONAL peer: if it isn't installed, fall back to a plain EventSource.\n    void import(\"@particle-academy/fancy-cf-relay\")\n      .then(({ createRelayChannel }) => {\n        if (this.connected || this.disposed) return;\n        this.channel = createRelayChannel({\n          baseUrl: this.opts.baseUrl,\n          session: this.opts.sessionId,\n          token: this.opts.token,\n          transport: \"auto\",\n          receiveDirection: \"inbound\",\n          sendPath: \"outbox\",\n          onFrame: (raw) => this.handleInbound(raw),\n          onOpen: () => this.markOpen(),\n          onError: () => this.setState(\"error\"),\n          fetchImpl: this.opts.fetch,\n        });\n        this.channel.start();\n      })\n      .catch(() => {\n        if (!this.disposed) this.startSse();\n      });\n  }\n\n  /** Fallback receive leg: a plain EventSource (no fancy-cf-relay installed). */\n  private startSse(): void {\n    if (this.connected || this.disposed) return;\n    const url = `${this.opts.baseUrl}/${encodeURIComponent(this.opts.sessionId)}/events?token=${encodeURIComponent(this.opts.token)}`;\n    const es = new EventSource(url, { withCredentials: false });\n    this.es = es;\n    es.addEventListener(\"open\", () => this.markOpen());\n    es.addEventListener(\"mcp\", (ev: MessageEvent) => this.handleInbound(ev.data));\n    es.addEventListener(\"error\", () => this.setState(\"error\")); // EventSource auto-reconnects\n  }\n\n  /** Mark the channel live + flush any queued outbound frames. */\n  private markOpen(): void {\n    this.connected = true;\n    this.setState(\"open\");\n    const queued = this.sendQueue.splice(0);\n    for (const msg of queued) this.postOut(msg);\n  }\n\n  send(message: JsonRpcMessage): void {\n    if (!this.connected) {\n      this.sendQueue.push(message);\n      return;\n    }\n    this.postOut(message);\n  }\n\n  close(): void {\n    this.disposed = true;\n    this.channel?.stop();\n    this.channel = undefined;\n    this.es?.close();\n    this.es = undefined;\n    this.connected = false;\n    this.peers.clear();\n    this.notifyPeers();\n    this.setState(\"closed\");\n  }\n\n  onStateChange(listener: (state: RelayState) => void): () => void {\n    this.listeners.add(listener);\n    listener(this.state);\n    return () => this.listeners.delete(listener);\n  }\n\n  /** How many remote peers (agents) are currently attached to this session. */\n  peerCount(): number {\n    return this.peers.size;\n  }\n\n  /**\n   * Subscribe to peer arrivals/departures. Fires immediately with the current\n   * count.\n   *\n   * This is the ONLY honest answer to \"is an agent actually here?\".\n   * `onStateChange` reports the BROWSER's own channel to the relay, which opens\n   * the instant sharing starts — so a UI keyed on it announces \"Agent is\n   * driving\" to a human who has not yet handed the link to anybody, and can\n   * never tell them when one really arrives.\n   */\n  onPeersChange(listener: (count: number) => void): () => void {\n    this.peerListeners.add(listener);\n    listener(this.peers.size);\n    return () => this.peerListeners.delete(listener);\n  }\n\n  private notifyPeers(): void {\n    for (const l of this.peerListeners) l(this.peers.size);\n  }\n\n  /**\n   * For relays that wrap each frame with auth metadata: hosts can call this\n   * directly when a frame arrives via a non-SSE path. The transport will\n   * dispatch it to the bound server.\n   */\n  async deliverFromRemote(payload: JsonRpcMessage | string, token?: string): Promise<void> {\n    if (token !== undefined && !constantTimeEqual(token, this.expectedToken)) return;\n    if (!this.server) throw new Error(\"SseRelayTransport has no bound server\");\n    const message: JsonRpcMessage = typeof payload === \"string\" ? JSON.parse(payload) : payload;\n    await this.server.receive(this, message);\n  }\n\n  private async postOut(message: JsonRpcMessage): Promise<void> {\n    const url = `${this.opts.baseUrl}/${encodeURIComponent(this.opts.sessionId)}/outbox?token=${encodeURIComponent(this.opts.token)}`;\n    const f = this.opts.fetch ?? fetch;\n    try {\n      await f(url, {\n        method: \"POST\",\n        headers: { \"content-type\": \"application/json\", \"accept\": \"application/json\" },\n        body: JSON.stringify(message),\n      });\n    } catch {\n      // Drop — relay errors are surfaced via state change separately.\n    }\n  }\n\n  private async handleInbound(raw: string): Promise<void> {\n    if (!this.server) return;\n    let message: JsonRpcMessage;\n    try {\n      message = JSON.parse(raw);\n    } catch {\n      return;\n    }\n    if (\"method\" in message && message.method === \"notifications/peer_joined\") {\n      const subscriberId = String((message.params as { subscriberId?: unknown } | undefined)?.subscriberId ?? \"peer\");\n      this.peers.add(subscriberId);\n      this.notifyPeers();\n      emitActivity({\n        agentId: this.opts.agent?.id ?? \"agent\",\n        agentName: this.opts.agent?.name ?? \"Agent\",\n        agentColor: this.opts.agent?.color ?? \"#a855f7\",\n        action: \"agent_connected\",\n        timestamp: Date.now(),\n        target: { kind: \"navigation\", label: \"Agent connected\" },\n        meta: { subscriberId },\n      });\n      return;\n    }\n    if (\"method\" in message && message.method === \"notifications/peer_left\") {\n      const subscriberId = String((message.params as { subscriberId?: unknown } | undefined)?.subscriberId ?? \"peer\");\n      this.peers.delete(subscriberId);\n      this.notifyPeers();\n      if (this.peers.size === 0) {\n        emitActivity({\n          agentId: this.opts.agent?.id ?? \"agent\",\n          agentName: this.opts.agent?.name ?? \"Agent\",\n          agentColor: this.opts.agent?.color ?? \"#a855f7\",\n          action: \"agent_disconnected\",\n          timestamp: Date.now(),\n          target: { kind: \"navigation\", label: \"Agent disconnected\" },\n          meta: { subscriberId },\n        });\n      }\n      return;\n    }\n    await this.server.receive(this, message);\n  }\n\n  private setState(state: RelayState): void {\n    this.state = state;\n    for (const l of this.listeners) l(state);\n  }\n}\n\nexport type RelayState = \"idle\" | \"connecting\" | \"open\" | \"closed\" | \"error\";\n\nexport function attachSseRelay(server: MicroMcpServer, options: SseRelayOptions): SseRelayTransport {\n  const transport = new SseRelayTransport(options);\n  transport.bindServer(server);\n  server.attach(transport);\n  transport.start();\n\n  // Forward in-process agent activity events out over the relay so external\n  // subscribers can render presence indicators in real time. Uses a dynamic\n  // import so the relay doesn't hard-depend on the presence module if it's\n  // tree-shaken out.\n  import(\"../presence/registry\").then(({ onActivity }) => {\n    const off = onActivity((event) => {\n      if (options.activityFilter) {\n        let keep: boolean;\n        try {\n          keep = options.activityFilter(event);\n        } catch {\n          // Host code threw. Drop this event, keep the subscription — the\n          // alternative is a session that goes permanently silent because one\n          // event was shaped unexpectedly.\n          return;\n        }\n        if (!keep) return;\n      }\n      transport.send({\n        jsonrpc: \"2.0\",\n        method: \"notifications/agent_activity\",\n        params: event as any,\n      });\n    });\n    // Tear down the subscription when the transport closes.\n    const origClose = transport.close.bind(transport);\n    transport.close = () => {\n      off();\n      origClose();\n    };\n  }).catch(() => {\n    // Presence module unavailable — silently no-op (relay still works).\n  });\n\n  return transport;\n}\n"]}