@mastra/server 1.22.0-alpha.1 → 1.22.0-alpha.2
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/CHANGELOG.md +23 -0
- package/browser-stream.d.ts +1 -0
- package/dist/{chunk-V3CH6G5F.cjs → chunk-3I73HGCQ.cjs} +58 -63
- package/dist/chunk-3I73HGCQ.cjs.map +1 -0
- package/dist/{chunk-M7MFCJT4.js → chunk-6Q6OH2XV.js} +3 -3
- package/dist/{chunk-M7MFCJT4.js.map → chunk-6Q6OH2XV.js.map} +1 -1
- package/dist/{chunk-SEMV475G.cjs → chunk-6RCE5RPS.cjs} +3 -3
- package/dist/{chunk-SEMV475G.cjs.map → chunk-6RCE5RPS.cjs.map} +1 -1
- package/dist/{chunk-OCU2OAMS.cjs → chunk-7BGA3PV4.cjs} +3 -3
- package/dist/{chunk-OCU2OAMS.cjs.map → chunk-7BGA3PV4.cjs.map} +1 -1
- package/dist/{chunk-FCCXLAOT.js → chunk-7TKB6WYH.js} +4 -14
- package/dist/chunk-7TKB6WYH.js.map +1 -0
- package/dist/{chunk-7HOYCW7S.cjs → chunk-A4UUHYOD.cjs} +4 -4
- package/dist/{chunk-7HOYCW7S.cjs.map → chunk-A4UUHYOD.cjs.map} +1 -1
- package/dist/{chunk-7ZHBGYZB.js → chunk-ACHCR6LO.js} +3 -3
- package/dist/{chunk-7ZHBGYZB.js.map → chunk-ACHCR6LO.js.map} +1 -1
- package/dist/{chunk-QJZOWORS.js → chunk-AFIQT26M.js} +3 -3
- package/dist/{chunk-QJZOWORS.js.map → chunk-AFIQT26M.js.map} +1 -1
- package/dist/{chunk-CZUJUB46.js → chunk-BLTEJYV2.js} +3 -3
- package/dist/{chunk-CZUJUB46.js.map → chunk-BLTEJYV2.js.map} +1 -1
- package/dist/{chunk-BQXQZPYZ.js → chunk-BNI76TPM.js} +60 -23
- package/dist/chunk-BNI76TPM.js.map +1 -0
- package/dist/{chunk-7KJ4NUEH.js → chunk-C4UOZ4CO.js} +4 -4
- package/dist/{chunk-7KJ4NUEH.js.map → chunk-C4UOZ4CO.js.map} +1 -1
- package/dist/{chunk-GYD5JBBZ.js → chunk-C63OPXW5.js} +3 -3
- package/dist/{chunk-GYD5JBBZ.js.map → chunk-C63OPXW5.js.map} +1 -1
- package/dist/{chunk-ZMBEDBRB.js → chunk-CN7BQNC5.js} +8 -8
- package/dist/{chunk-ZMBEDBRB.js.map → chunk-CN7BQNC5.js.map} +1 -1
- package/dist/{chunk-FB5NRM2M.js → chunk-CQMVBP24.js} +3 -3
- package/dist/{chunk-FB5NRM2M.js.map → chunk-CQMVBP24.js.map} +1 -1
- package/dist/{chunk-PKONZNTB.js → chunk-DALTY2FC.js} +36 -6
- package/dist/chunk-DALTY2FC.js.map +1 -0
- package/dist/{chunk-22YG2467.cjs → chunk-DF6RRE5O.cjs} +4 -4
- package/dist/{chunk-22YG2467.cjs.map → chunk-DF6RRE5O.cjs.map} +1 -1
- package/dist/{chunk-37LAVKFO.cjs → chunk-E5JCKPP3.cjs} +3 -3
- package/dist/{chunk-37LAVKFO.cjs.map → chunk-E5JCKPP3.cjs.map} +1 -1
- package/dist/{chunk-B6HNPO67.cjs → chunk-EPKGVW45.cjs} +62 -23
- package/dist/chunk-EPKGVW45.cjs.map +1 -0
- package/dist/{chunk-MBZT5YVO.js → chunk-EZ447XGK.js} +47 -30
- package/dist/chunk-EZ447XGK.js.map +1 -0
- package/dist/{chunk-B5KNWY2P.js → chunk-FF3WZJPG.js} +3 -3
- package/dist/{chunk-B5KNWY2P.js.map → chunk-FF3WZJPG.js.map} +1 -1
- package/dist/{chunk-C4M6VJGY.cjs → chunk-FINYXBRW.cjs} +7 -7
- package/dist/{chunk-C4M6VJGY.cjs.map → chunk-FINYXBRW.cjs.map} +1 -1
- package/dist/{chunk-MNEANLCY.cjs → chunk-FWFMQ3KF.cjs} +3 -3
- package/dist/{chunk-MNEANLCY.cjs.map → chunk-FWFMQ3KF.cjs.map} +1 -1
- package/dist/{chunk-VVWHCAF2.cjs → chunk-G3DUGNRW.cjs} +68 -68
- package/dist/chunk-G3DUGNRW.cjs.map +1 -0
- package/dist/{chunk-M6AQTASN.js → chunk-G4H7C5TN.js} +3 -3
- package/dist/{chunk-M6AQTASN.js.map → chunk-G4H7C5TN.js.map} +1 -1
- package/dist/{chunk-XWCIHYAP.cjs → chunk-GKU3STNO.cjs} +6 -6
- package/dist/{chunk-XWCIHYAP.cjs.map → chunk-GKU3STNO.cjs.map} +1 -1
- package/dist/{chunk-6MFKRF4K.cjs → chunk-GNVZDFY5.cjs} +4 -14
- package/dist/chunk-GNVZDFY5.cjs.map +1 -0
- package/dist/{chunk-CHQVMEZ3.cjs → chunk-HGA5GPMW.cjs} +126 -2
- package/dist/chunk-HGA5GPMW.cjs.map +1 -0
- package/dist/{chunk-VUW7PJZC.cjs → chunk-IFEMVRLI.cjs} +3 -3
- package/dist/{chunk-VUW7PJZC.cjs.map → chunk-IFEMVRLI.cjs.map} +1 -1
- package/dist/{chunk-HI5EDX3F.js → chunk-IGUQV25I.js} +3 -3
- package/dist/{chunk-HI5EDX3F.js.map → chunk-IGUQV25I.js.map} +1 -1
- package/dist/{chunk-E56FRMZY.js → chunk-IKRRLVK6.js} +3 -3
- package/dist/{chunk-E56FRMZY.js.map → chunk-IKRRLVK6.js.map} +1 -1
- package/dist/{chunk-7TESOQZ3.js → chunk-JL6DSIOD.js} +3 -3
- package/dist/{chunk-7TESOQZ3.js.map → chunk-JL6DSIOD.js.map} +1 -1
- package/dist/{chunk-63O6VEOH.cjs → chunk-JLAFCQZI.cjs} +37 -37
- package/dist/{chunk-63O6VEOH.cjs.map → chunk-JLAFCQZI.cjs.map} +1 -1
- package/dist/{chunk-H6ZLN6EU.cjs → chunk-JN4QR7QO.cjs} +3 -3
- package/dist/{chunk-H6ZLN6EU.cjs.map → chunk-JN4QR7QO.cjs.map} +1 -1
- package/dist/{chunk-BPRE3HGQ.js → chunk-JWFTGPMQ.js} +3 -3
- package/dist/{chunk-BPRE3HGQ.js.map → chunk-JWFTGPMQ.js.map} +1 -1
- package/dist/{chunk-ZZQRRB35.js → chunk-JXC2NQ5E.js} +58 -63
- package/dist/chunk-JXC2NQ5E.js.map +1 -0
- package/dist/{chunk-JH576GTI.cjs → chunk-KU4DBPKJ.cjs} +3 -3
- package/dist/{chunk-JH576GTI.cjs.map → chunk-KU4DBPKJ.cjs.map} +1 -1
- package/dist/{chunk-WUCJRGTK.cjs → chunk-L3S2ZIOY.cjs} +3 -3
- package/dist/{chunk-WUCJRGTK.cjs.map → chunk-L3S2ZIOY.cjs.map} +1 -1
- package/dist/{chunk-M4BZ2B7D.js → chunk-LADXKYBC.js} +4 -4
- package/dist/{chunk-M4BZ2B7D.js.map → chunk-LADXKYBC.js.map} +1 -1
- package/dist/{chunk-SNGR4M5I.js → chunk-LPRBYE25.js} +3 -3
- package/dist/{chunk-SNGR4M5I.js.map → chunk-LPRBYE25.js.map} +1 -1
- package/dist/{chunk-6FD7UBEK.js → chunk-MENRAWPG.js} +21 -36
- package/dist/chunk-MENRAWPG.js.map +1 -0
- package/dist/{chunk-5ZQYFQ5P.js → chunk-MFBT6P7Q.js} +3 -3
- package/dist/{chunk-5ZQYFQ5P.js.map → chunk-MFBT6P7Q.js.map} +1 -1
- package/dist/{chunk-QSA443WV.js → chunk-MSQCNOSC.js} +8 -8
- package/dist/chunk-MSQCNOSC.js.map +1 -0
- package/dist/{chunk-WYAFNFFM.js → chunk-MW3K5222.js} +3 -3
- package/dist/{chunk-WYAFNFFM.js.map → chunk-MW3K5222.js.map} +1 -1
- package/dist/{chunk-CMTOQQZD.js → chunk-MXTM7NJY.js} +126 -2
- package/dist/chunk-MXTM7NJY.js.map +1 -0
- package/dist/{chunk-NL7Y763D.js → chunk-N6ERUAFR.js} +4 -4
- package/dist/{chunk-NL7Y763D.js.map → chunk-N6ERUAFR.js.map} +1 -1
- package/dist/{chunk-GANMD6GP.cjs → chunk-NHQZOCOS.cjs} +4 -4
- package/dist/{chunk-GANMD6GP.cjs.map → chunk-NHQZOCOS.cjs.map} +1 -1
- package/dist/{chunk-XKKTZVZX.cjs → chunk-NLPQD74A.cjs} +3 -3
- package/dist/{chunk-XKKTZVZX.cjs.map → chunk-NLPQD74A.cjs.map} +1 -1
- package/dist/{chunk-YWPRH3D3.cjs → chunk-O5GBRVCT.cjs} +3 -3
- package/dist/{chunk-YWPRH3D3.cjs.map → chunk-O5GBRVCT.cjs.map} +1 -1
- package/dist/{chunk-ZPVDK5G4.js → chunk-P7VIFXFY.js} +3 -3
- package/dist/{chunk-ZPVDK5G4.js.map → chunk-P7VIFXFY.js.map} +1 -1
- package/dist/{chunk-FCLPA23B.js → chunk-PQGBLNCU.js} +103 -6
- package/dist/chunk-PQGBLNCU.js.map +1 -0
- package/dist/{chunk-GVZ7VB33.cjs → chunk-PSEUD4LV.cjs} +3 -3
- package/dist/{chunk-GVZ7VB33.cjs.map → chunk-PSEUD4LV.cjs.map} +1 -1
- package/dist/{chunk-G5KXPZUB.cjs → chunk-QBAI2EOX.cjs} +3 -3
- package/dist/{chunk-G5KXPZUB.cjs.map → chunk-QBAI2EOX.cjs.map} +1 -1
- package/dist/{chunk-5IKQNFW5.cjs → chunk-QKYHAQLX.cjs} +3 -3
- package/dist/{chunk-5IKQNFW5.cjs.map → chunk-QKYHAQLX.cjs.map} +1 -1
- package/dist/{chunk-MWRVCCZE.js → chunk-QVD4QX7M.js} +4 -4
- package/dist/{chunk-MWRVCCZE.js.map → chunk-QVD4QX7M.js.map} +1 -1
- package/dist/{chunk-UUQ6I673.cjs → chunk-RJYAO24Y.cjs} +3 -3
- package/dist/{chunk-UUQ6I673.cjs.map → chunk-RJYAO24Y.cjs.map} +1 -1
- package/dist/{chunk-AYD7DM5U.cjs → chunk-SDTOJ37Y.cjs} +4 -4
- package/dist/{chunk-AYD7DM5U.cjs.map → chunk-SDTOJ37Y.cjs.map} +1 -1
- package/dist/{chunk-3TZN3YIC.cjs → chunk-SHDWWXPP.cjs} +5 -5
- package/dist/{chunk-3TZN3YIC.cjs.map → chunk-SHDWWXPP.cjs.map} +1 -1
- package/dist/{chunk-YV7FX2SA.cjs → chunk-TEOEOSGZ.cjs} +3 -3
- package/dist/{chunk-YV7FX2SA.cjs.map → chunk-TEOEOSGZ.cjs.map} +1 -1
- package/dist/{chunk-L7PDVJZD.js → chunk-TTIBLMTX.js} +3 -3
- package/dist/{chunk-L7PDVJZD.js.map → chunk-TTIBLMTX.js.map} +1 -1
- package/dist/{chunk-JMOCVI6W.cjs → chunk-U6CWJTHH.cjs} +61 -30
- package/dist/chunk-U6CWJTHH.cjs.map +1 -0
- package/dist/{chunk-LPD67DCH.js → chunk-V7HG5XL2.js} +4 -4
- package/dist/{chunk-LPD67DCH.js.map → chunk-V7HG5XL2.js.map} +1 -1
- package/dist/{chunk-FU5Z5NSF.js → chunk-VNI37F64.js} +3 -3
- package/dist/{chunk-FU5Z5NSF.js.map → chunk-VNI37F64.js.map} +1 -1
- package/dist/{chunk-NOBDUHIG.cjs → chunk-VPC53D7Z.cjs} +3 -3
- package/dist/{chunk-NOBDUHIG.cjs.map → chunk-VPC53D7Z.cjs.map} +1 -1
- package/dist/{chunk-SKGWZXFE.js → chunk-VUEC3ADB.js} +4 -4
- package/dist/{chunk-SKGWZXFE.js.map → chunk-VUEC3ADB.js.map} +1 -1
- package/dist/{chunk-HBMIUVV4.cjs → chunk-W3BHIYBR.cjs} +3 -3
- package/dist/{chunk-HBMIUVV4.cjs.map → chunk-W3BHIYBR.cjs.map} +1 -1
- package/dist/{chunk-2MQOYDF5.cjs → chunk-WP4JAWJG.cjs} +30 -45
- package/dist/chunk-WP4JAWJG.cjs.map +1 -0
- package/dist/{chunk-DNYJSPA5.cjs → chunk-WTS6S64Y.cjs} +103 -6
- package/dist/chunk-WTS6S64Y.cjs.map +1 -0
- package/dist/{chunk-M3CYE3JU.cjs → chunk-XW4H7ERP.cjs} +55 -38
- package/dist/chunk-XW4H7ERP.cjs.map +1 -0
- package/dist/{chunk-5GXB4IME.js → chunk-YFDJJN24.js} +3 -3
- package/dist/{chunk-5GXB4IME.js.map → chunk-YFDJJN24.js.map} +1 -1
- package/dist/{chunk-NAQDQGBB.cjs → chunk-YZGC47UO.cjs} +6 -6
- package/dist/{chunk-NAQDQGBB.cjs.map → chunk-YZGC47UO.cjs.map} +1 -1
- package/dist/{chunk-42ZYNYQI.cjs → chunk-Z5ETZUOK.cjs} +4 -4
- package/dist/{chunk-42ZYNYQI.cjs.map → chunk-Z5ETZUOK.cjs.map} +1 -1
- package/dist/{chunk-6CSJOAJM.js → chunk-ZIVLQ4KC.js} +4 -4
- package/dist/{chunk-6CSJOAJM.js.map → chunk-ZIVLQ4KC.js.map} +1 -1
- package/dist/docs/SKILL.md +1 -1
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/{observational-memory-WOEVNCG4-DT36JHO2.js → observational-memory-74TRS2R6-LEBM75UY.js} +3 -3
- package/dist/{observational-memory-WOEVNCG4-DT36JHO2.js.map → observational-memory-74TRS2R6-LEBM75UY.js.map} +1 -1
- package/dist/{observational-memory-WOEVNCG4-5THJIIQE.cjs → observational-memory-74TRS2R6-NL3AWYN4.cjs} +26 -26
- package/dist/{observational-memory-WOEVNCG4-5THJIIQE.cjs.map → observational-memory-74TRS2R6-NL3AWYN4.cjs.map} +1 -1
- package/dist/server/browser-stream/index.cjs +534 -0
- package/dist/server/browser-stream/index.cjs.map +1 -0
- package/dist/server/browser-stream/index.d.ts +23 -0
- package/dist/server/browser-stream/index.d.ts.map +1 -0
- package/dist/server/browser-stream/index.js +531 -0
- package/dist/server/browser-stream/index.js.map +1 -0
- package/dist/server/browser-stream/input-handler.d.ts +15 -0
- package/dist/server/browser-stream/input-handler.d.ts.map +1 -0
- package/dist/server/browser-stream/types.d.ts +110 -0
- package/dist/server/browser-stream/types.d.ts.map +1 -0
- package/dist/server/browser-stream/viewer-registry.d.ts +132 -0
- package/dist/server/browser-stream/viewer-registry.d.ts.map +1 -0
- package/dist/server/handlers/a2a.cjs +9 -9
- package/dist/server/handlers/a2a.js +1 -1
- package/dist/server/handlers/agent-builder.cjs +16 -16
- package/dist/server/handlers/agent-builder.js +1 -1
- package/dist/server/handlers/agent-versions.cjs +8 -8
- package/dist/server/handlers/agent-versions.js +1 -1
- package/dist/server/handlers/agents.cjs +34 -34
- package/dist/server/handlers/agents.js +1 -1
- package/dist/server/handlers/auth.cjs +10 -10
- package/dist/server/handlers/auth.js +1 -1
- package/dist/server/handlers/conversations.cjs +5 -5
- package/dist/server/handlers/conversations.d.ts.map +1 -1
- package/dist/server/handlers/conversations.js +1 -1
- package/dist/server/handlers/datasets.cjs +26 -26
- package/dist/server/handlers/datasets.d.ts +748 -0
- package/dist/server/handlers/datasets.d.ts.map +1 -1
- package/dist/server/handlers/datasets.js +1 -1
- package/dist/server/handlers/logs.cjs +4 -4
- package/dist/server/handlers/logs.js +1 -1
- package/dist/server/handlers/mcp-client-versions.cjs +8 -8
- package/dist/server/handlers/mcp-client-versions.js +1 -1
- package/dist/server/handlers/memory.cjs +27 -27
- package/dist/server/handlers/memory.d.ts.map +1 -1
- package/dist/server/handlers/memory.js +1 -1
- package/dist/server/handlers/observability-new-endpoints.cjs +27 -27
- package/dist/server/handlers/observability-new-endpoints.js +1 -1
- package/dist/server/handlers/observability.cjs +36 -32
- package/dist/server/handlers/observability.d.ts +16 -0
- package/dist/server/handlers/observability.d.ts.map +1 -1
- package/dist/server/handlers/observability.js +2 -2
- package/dist/server/handlers/processor-providers.cjs +3 -3
- package/dist/server/handlers/processor-providers.js +1 -1
- package/dist/server/handlers/processors.cjs +4 -4
- package/dist/server/handlers/processors.js +1 -1
- package/dist/server/handlers/prompt-block-versions.cjs +8 -8
- package/dist/server/handlers/prompt-block-versions.js +1 -1
- package/dist/server/handlers/responses.cjs +4 -4
- package/dist/server/handlers/responses.d.ts +2 -2
- package/dist/server/handlers/responses.d.ts.map +1 -1
- package/dist/server/handlers/responses.js +1 -1
- package/dist/server/handlers/responses.storage.cjs +15 -7
- package/dist/server/handlers/responses.storage.d.ts +30 -11
- package/dist/server/handlers/responses.storage.d.ts.map +1 -1
- package/dist/server/handlers/responses.storage.js +1 -1
- package/dist/server/handlers/scorer-versions.cjs +8 -8
- package/dist/server/handlers/scorer-versions.js +1 -1
- package/dist/server/handlers/scores.cjs +7 -7
- package/dist/server/handlers/scores.js +1 -1
- package/dist/server/handlers/stored-agents.cjs +7 -7
- package/dist/server/handlers/stored-agents.js +1 -1
- package/dist/server/handlers/stored-mcp-clients.cjs +6 -6
- package/dist/server/handlers/stored-mcp-clients.js +1 -1
- package/dist/server/handlers/stored-prompt-blocks.cjs +6 -6
- package/dist/server/handlers/stored-prompt-blocks.js +1 -1
- package/dist/server/handlers/stored-scorers.cjs +6 -6
- package/dist/server/handlers/stored-scorers.js +1 -1
- package/dist/server/handlers/stored-skills.cjs +7 -7
- package/dist/server/handlers/stored-skills.js +1 -1
- package/dist/server/handlers/stored-workspaces.cjs +6 -6
- package/dist/server/handlers/stored-workspaces.js +1 -1
- package/dist/server/handlers/system.cjs +2 -2
- package/dist/server/handlers/system.js +1 -1
- package/dist/server/handlers/tool-providers.cjs +5 -5
- package/dist/server/handlers/tool-providers.js +1 -1
- package/dist/server/handlers/tools.cjs +6 -6
- package/dist/server/handlers/tools.js +1 -1
- package/dist/server/handlers/vector.cjs +16 -16
- package/dist/server/handlers/vector.js +1 -1
- package/dist/server/handlers/voice.cjs +8 -8
- package/dist/server/handlers/voice.js +1 -1
- package/dist/server/handlers/workflows.cjs +24 -24
- package/dist/server/handlers/workflows.js +1 -1
- package/dist/server/handlers/workspace.cjs +26 -26
- package/dist/server/handlers/workspace.js +1 -1
- package/dist/server/handlers.cjs +37 -37
- package/dist/server/handlers.js +14 -14
- package/dist/server/schemas/datasets.d.ts +371 -0
- package/dist/server/schemas/datasets.d.ts.map +1 -1
- package/dist/server/schemas/index.cjs +299 -299
- package/dist/server/schemas/index.js +7 -7
- package/dist/server/schemas/responses.d.ts +1 -1
- package/dist/server/schemas/responses.d.ts.map +1 -1
- package/dist/server/server-adapter/index.cjs +281 -280
- package/dist/server/server-adapter/index.cjs.map +1 -1
- package/dist/server/server-adapter/index.js +33 -32
- package/dist/server/server-adapter/index.js.map +1 -1
- package/dist/server/server-adapter/routes/datasets.d.ts +748 -0
- package/dist/server/server-adapter/routes/datasets.d.ts.map +1 -1
- package/dist/server/server-adapter/routes/observability.d.ts +14 -0
- package/dist/server/server-adapter/routes/observability.d.ts.map +1 -1
- package/dist/server/server-adapter/routes/responses.d.ts +2 -2
- package/package.json +15 -5
- package/dist/chunk-2MQOYDF5.cjs.map +0 -1
- package/dist/chunk-6FD7UBEK.js.map +0 -1
- package/dist/chunk-6MFKRF4K.cjs.map +0 -1
- package/dist/chunk-B6HNPO67.cjs.map +0 -1
- package/dist/chunk-BQXQZPYZ.js.map +0 -1
- package/dist/chunk-CHQVMEZ3.cjs.map +0 -1
- package/dist/chunk-CMTOQQZD.js.map +0 -1
- package/dist/chunk-DNYJSPA5.cjs.map +0 -1
- package/dist/chunk-FCCXLAOT.js.map +0 -1
- package/dist/chunk-FCLPA23B.js.map +0 -1
- package/dist/chunk-JMOCVI6W.cjs.map +0 -1
- package/dist/chunk-M3CYE3JU.cjs.map +0 -1
- package/dist/chunk-MBZT5YVO.js.map +0 -1
- package/dist/chunk-PKONZNTB.js.map +0 -1
- package/dist/chunk-QSA443WV.js.map +0 -1
- package/dist/chunk-V3CH6G5F.cjs.map +0 -1
- package/dist/chunk-VVWHCAF2.cjs.map +0 -1
- package/dist/chunk-ZZQRRB35.js.map +0 -1
|
@@ -0,0 +1,531 @@
|
|
|
1
|
+
// src/server/browser-stream/viewer-registry.ts
|
|
2
|
+
var ViewerRegistry = class {
|
|
3
|
+
/** Map of agentId to set of connected WebSocket contexts */
|
|
4
|
+
viewers = /* @__PURE__ */ new Map();
|
|
5
|
+
/** Map of agentId to active screencast stream */
|
|
6
|
+
screencasts = /* @__PURE__ */ new Map();
|
|
7
|
+
/** Set of viewerKeys currently starting a screencast (to prevent race conditions) */
|
|
8
|
+
startingScreencasts = /* @__PURE__ */ new Set();
|
|
9
|
+
/** Map of agentId to cleanup function for onBrowserReady callback */
|
|
10
|
+
browserReadyCleanups = /* @__PURE__ */ new Map();
|
|
11
|
+
/** Map of agentId to cleanup function for onBrowserClosed callback */
|
|
12
|
+
browserClosedCleanups = /* @__PURE__ */ new Map();
|
|
13
|
+
/** Map of agentId to last known URL (for dedup) */
|
|
14
|
+
lastUrls = /* @__PURE__ */ new Map();
|
|
15
|
+
/** Map of agentId to last known viewport dimensions (for change detection) */
|
|
16
|
+
lastViewports = /* @__PURE__ */ new Map();
|
|
17
|
+
/** Map of viewerKey to last broadcast status (for replay to new viewers) */
|
|
18
|
+
lastStatuses = /* @__PURE__ */ new Map();
|
|
19
|
+
/**
|
|
20
|
+
* Add a viewer for an agent. Starts screencast if this is the first viewer.
|
|
21
|
+
*
|
|
22
|
+
* @param viewerKey - The viewer key (agentId or agentId:threadId for thread-scoped)
|
|
23
|
+
* @param ws - The WebSocket context for this viewer
|
|
24
|
+
* @param getToolset - Function to retrieve the BrowserToolset for this agent
|
|
25
|
+
* @param agentId - The actual agent ID for toolset lookup (optional, defaults to viewerKey)
|
|
26
|
+
* @param threadId - The thread ID for thread-scoped screencasts (optional)
|
|
27
|
+
*/
|
|
28
|
+
async addViewer(viewerKey, ws, getToolset, agentId, threadId) {
|
|
29
|
+
let viewerSet = this.viewers.get(viewerKey);
|
|
30
|
+
if (!viewerSet) {
|
|
31
|
+
viewerSet = /* @__PURE__ */ new Set();
|
|
32
|
+
this.viewers.set(viewerKey, viewerSet);
|
|
33
|
+
}
|
|
34
|
+
const wasEmpty = viewerSet.size === 0;
|
|
35
|
+
viewerSet.add(ws);
|
|
36
|
+
if (wasEmpty) {
|
|
37
|
+
await this.startScreencast(viewerKey, getToolset, agentId ?? viewerKey, threadId);
|
|
38
|
+
} else {
|
|
39
|
+
this.sendCurrentState(viewerKey, ws);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Send current state (URL, viewport) to a newly connected viewer.
|
|
44
|
+
*/
|
|
45
|
+
sendCurrentState(viewerKey, ws) {
|
|
46
|
+
try {
|
|
47
|
+
const lastUrl = this.lastUrls.get(viewerKey);
|
|
48
|
+
if (lastUrl) {
|
|
49
|
+
ws.send(JSON.stringify({ url: lastUrl }));
|
|
50
|
+
}
|
|
51
|
+
const lastViewport = this.lastViewports.get(viewerKey);
|
|
52
|
+
if (lastViewport) {
|
|
53
|
+
ws.send(JSON.stringify({ viewport: lastViewport }));
|
|
54
|
+
}
|
|
55
|
+
const lastStatus = this.lastStatuses.get(viewerKey);
|
|
56
|
+
if (lastStatus) {
|
|
57
|
+
ws.send(JSON.stringify({ status: lastStatus }));
|
|
58
|
+
}
|
|
59
|
+
} catch (error) {
|
|
60
|
+
console.warn("[ViewerRegistry] Error sending current state to new viewer:", error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Remove a viewer. Stops screencast if this was the last viewer.
|
|
65
|
+
*
|
|
66
|
+
* @param viewerKey - The viewer key (agentId or agentId:threadId for thread-scoped)
|
|
67
|
+
* @param ws - The WebSocket context to remove
|
|
68
|
+
*/
|
|
69
|
+
async removeViewer(viewerKey, ws) {
|
|
70
|
+
const viewerSet = this.viewers.get(viewerKey);
|
|
71
|
+
if (!viewerSet) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
viewerSet.delete(ws);
|
|
75
|
+
if (viewerSet.size === 0) {
|
|
76
|
+
this.viewers.delete(viewerKey);
|
|
77
|
+
this.lastUrls.delete(viewerKey);
|
|
78
|
+
this.lastViewports.delete(viewerKey);
|
|
79
|
+
const readyCleanup = this.browserReadyCleanups.get(viewerKey);
|
|
80
|
+
if (readyCleanup) {
|
|
81
|
+
readyCleanup();
|
|
82
|
+
this.browserReadyCleanups.delete(viewerKey);
|
|
83
|
+
}
|
|
84
|
+
const closedCleanup = this.browserClosedCleanups.get(viewerKey);
|
|
85
|
+
if (closedCleanup) {
|
|
86
|
+
closedCleanup();
|
|
87
|
+
this.browserClosedCleanups.delete(viewerKey);
|
|
88
|
+
}
|
|
89
|
+
await this.stopScreencast(viewerKey);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Broadcast a binary frame to all viewers.
|
|
94
|
+
*
|
|
95
|
+
* @param viewerKey - The viewer key (agentId or agentId:threadId for thread-scoped)
|
|
96
|
+
* @param data - The binary frame data (base64 encoded)
|
|
97
|
+
*/
|
|
98
|
+
broadcastFrame(viewerKey, data) {
|
|
99
|
+
const viewerSet = this.viewers.get(viewerKey);
|
|
100
|
+
if (!viewerSet) {
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
for (const ws of viewerSet) {
|
|
104
|
+
try {
|
|
105
|
+
ws.send(data);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
console.warn("[ViewerRegistry] Error broadcasting frame:", error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Broadcast a status message to all viewers.
|
|
113
|
+
*
|
|
114
|
+
* @param viewerKey - The viewer key (agentId or agentId:threadId for thread-scoped)
|
|
115
|
+
* @param status - The status message to send
|
|
116
|
+
*/
|
|
117
|
+
broadcastStatus(viewerKey, status) {
|
|
118
|
+
if (status.status) {
|
|
119
|
+
this.lastStatuses.set(viewerKey, status.status);
|
|
120
|
+
}
|
|
121
|
+
const viewerSet = this.viewers.get(viewerKey);
|
|
122
|
+
if (!viewerSet) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
const message = JSON.stringify(status);
|
|
126
|
+
for (const ws of viewerSet) {
|
|
127
|
+
try {
|
|
128
|
+
ws.send(message);
|
|
129
|
+
} catch (error) {
|
|
130
|
+
console.warn("[ViewerRegistry] Error broadcasting status:", error);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Broadcast a URL update to all viewers (only if changed).
|
|
136
|
+
*/
|
|
137
|
+
broadcastUrlIfChanged(viewerKey, url) {
|
|
138
|
+
if (!url) return;
|
|
139
|
+
if (this.lastUrls.get(viewerKey) === url) return;
|
|
140
|
+
this.lastUrls.set(viewerKey, url);
|
|
141
|
+
const viewerSet = this.viewers.get(viewerKey);
|
|
142
|
+
if (!viewerSet) return;
|
|
143
|
+
const message = JSON.stringify({ url });
|
|
144
|
+
for (const ws of viewerSet) {
|
|
145
|
+
try {
|
|
146
|
+
ws.send(message);
|
|
147
|
+
} catch (error) {
|
|
148
|
+
console.warn("[ViewerRegistry] Error broadcasting URL:", error);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Broadcast viewport metadata to all viewers.
|
|
154
|
+
* Only sends if dimensions have changed from last broadcast.
|
|
155
|
+
* Called on stream start and on each frame to detect dimension changes.
|
|
156
|
+
*/
|
|
157
|
+
broadcastViewportIfChanged(viewerKey, viewport) {
|
|
158
|
+
const last = this.lastViewports.get(viewerKey);
|
|
159
|
+
if (last && last.width === viewport.width && last.height === viewport.height) {
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
this.lastViewports.set(viewerKey, { width: viewport.width, height: viewport.height });
|
|
163
|
+
const viewerSet = this.viewers.get(viewerKey);
|
|
164
|
+
if (!viewerSet) return;
|
|
165
|
+
const message = { viewport: { width: viewport.width, height: viewport.height } };
|
|
166
|
+
const json = JSON.stringify(message);
|
|
167
|
+
for (const ws of viewerSet) {
|
|
168
|
+
try {
|
|
169
|
+
ws.send(json);
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.warn("[ViewerRegistry] Error broadcasting viewport:", error);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Start screencast for a viewer. Only starts if browser is already running.
|
|
177
|
+
* If browser not running, registers a callback to start when browser becomes ready.
|
|
178
|
+
*
|
|
179
|
+
* @param viewerKey - The viewer key (for registry keying)
|
|
180
|
+
* @param getToolset - Function to retrieve the BrowserToolset
|
|
181
|
+
* @param agentId - The actual agent ID for toolset lookup
|
|
182
|
+
* @param threadId - The thread ID for thread-scoped page selection (optional)
|
|
183
|
+
*/
|
|
184
|
+
async startScreencast(viewerKey, getToolset, agentId, threadId) {
|
|
185
|
+
const toolset = getToolset(agentId);
|
|
186
|
+
if (!toolset) {
|
|
187
|
+
console.info(`[ViewerRegistry] No toolset for ${viewerKey}, waiting...`);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
if (!this.browserReadyCleanups.has(viewerKey)) {
|
|
191
|
+
const cleanup = toolset.onBrowserReady(() => {
|
|
192
|
+
if (!this.viewers.has(viewerKey)) {
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const existingStream = this.screencasts.get(viewerKey);
|
|
196
|
+
if (existingStream) {
|
|
197
|
+
console.info(`[ViewerRegistry] Stopping old screencast for ${viewerKey} before reconnecting...`);
|
|
198
|
+
this.screencasts.delete(viewerKey);
|
|
199
|
+
existingStream.stop().catch(() => {
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
console.info(`[ViewerRegistry] Browser ready for ${viewerKey}, starting screencast...`);
|
|
203
|
+
this.doStartScreencast(viewerKey, toolset, threadId).catch((error) => {
|
|
204
|
+
console.error(`[ViewerRegistry] Failed to start screencast on browser ready for ${viewerKey}:`, error);
|
|
205
|
+
});
|
|
206
|
+
}, threadId);
|
|
207
|
+
this.browserReadyCleanups.set(viewerKey, cleanup);
|
|
208
|
+
}
|
|
209
|
+
if (!this.browserClosedCleanups.has(viewerKey)) {
|
|
210
|
+
const cleanup = toolset.onBrowserClosed(() => {
|
|
211
|
+
console.info(`[ViewerRegistry] Browser closed for ${viewerKey}, notifying viewers...`);
|
|
212
|
+
this.screencasts.delete(viewerKey);
|
|
213
|
+
this.broadcastStatus(viewerKey, { status: "browser_closed" });
|
|
214
|
+
}, threadId);
|
|
215
|
+
this.browserClosedCleanups.set(viewerKey, cleanup);
|
|
216
|
+
}
|
|
217
|
+
if (toolset.isBrowserRunning()) {
|
|
218
|
+
await this.doStartScreencast(viewerKey, toolset, threadId);
|
|
219
|
+
} else {
|
|
220
|
+
console.info(`[ViewerRegistry] Browser not running for ${viewerKey}, waiting for browser to start...`);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Internal method to actually start the screencast stream.
|
|
225
|
+
*
|
|
226
|
+
* @param viewerKey - The viewer key (for registry keying and logging)
|
|
227
|
+
* @param toolset - The browser toolset
|
|
228
|
+
* @param threadId - The thread ID for thread-scoped page selection (optional)
|
|
229
|
+
*/
|
|
230
|
+
async doStartScreencast(viewerKey, toolset, threadId) {
|
|
231
|
+
if (this.screencasts.has(viewerKey) || this.startingScreencasts.has(viewerKey)) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
this.startingScreencasts.add(viewerKey);
|
|
235
|
+
try {
|
|
236
|
+
this.broadcastStatus(viewerKey, { status: "browser_starting" });
|
|
237
|
+
const stream = await toolset.startScreencastIfBrowserActive(threadId ? { threadId } : void 0);
|
|
238
|
+
if (!stream) {
|
|
239
|
+
console.warn(`[ViewerRegistry] No browser session for ${viewerKey}`);
|
|
240
|
+
this.broadcastStatus(viewerKey, { status: "browser_closed" });
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
this.screencasts.set(viewerKey, stream);
|
|
244
|
+
const currentStream = stream;
|
|
245
|
+
stream.on("frame", (frame) => {
|
|
246
|
+
if (this.screencasts.get(viewerKey) !== currentStream) return;
|
|
247
|
+
this.broadcastFrame(viewerKey, frame.data);
|
|
248
|
+
this.broadcastViewportIfChanged(viewerKey, frame.viewport);
|
|
249
|
+
});
|
|
250
|
+
stream.on("url", (url) => {
|
|
251
|
+
if (this.screencasts.get(viewerKey) !== currentStream) return;
|
|
252
|
+
this.broadcastUrlIfChanged(viewerKey, url);
|
|
253
|
+
});
|
|
254
|
+
stream.on("stop", (reason) => {
|
|
255
|
+
if (this.screencasts.get(viewerKey) !== currentStream) {
|
|
256
|
+
console.info(`[ViewerRegistry] Ignoring stop from superseded stream for ${viewerKey}`);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
console.info(`[ViewerRegistry] Screencast stopped for ${viewerKey}: ${reason}`);
|
|
260
|
+
this.screencasts.delete(viewerKey);
|
|
261
|
+
this.broadcastStatus(viewerKey, { status: "browser_closed" });
|
|
262
|
+
});
|
|
263
|
+
stream.on("error", (error) => {
|
|
264
|
+
if (this.screencasts.get(viewerKey) !== currentStream) return;
|
|
265
|
+
console.error(`[ViewerRegistry] Screencast error for ${viewerKey}:`, error);
|
|
266
|
+
});
|
|
267
|
+
this.broadcastStatus(viewerKey, { status: "streaming" });
|
|
268
|
+
const initialUrl = await toolset.getCurrentUrl(threadId);
|
|
269
|
+
this.broadcastUrlIfChanged(viewerKey, initialUrl);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
console.error(`[ViewerRegistry] Failed to start screencast for ${viewerKey}:`, error);
|
|
272
|
+
} finally {
|
|
273
|
+
this.startingScreencasts.delete(viewerKey);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
/**
|
|
277
|
+
* Stop screencast for an agent. Called when last viewer disconnects.
|
|
278
|
+
*/
|
|
279
|
+
async stopScreencast(agentId) {
|
|
280
|
+
const stream = this.screencasts.get(agentId);
|
|
281
|
+
if (!stream) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
try {
|
|
285
|
+
await stream.stop();
|
|
286
|
+
} catch (error) {
|
|
287
|
+
console.warn(`[ViewerRegistry] Error stopping screencast for ${agentId}:`, error);
|
|
288
|
+
} finally {
|
|
289
|
+
this.screencasts.delete(agentId);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Get the number of viewers for an agent.
|
|
294
|
+
*
|
|
295
|
+
* @param agentId - The agent ID
|
|
296
|
+
* @returns The number of connected viewers
|
|
297
|
+
*/
|
|
298
|
+
getViewerCount(agentId) {
|
|
299
|
+
return this.viewers.get(agentId)?.size ?? 0;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Check if an agent has an active screencast.
|
|
303
|
+
*
|
|
304
|
+
* @param agentId - The agent ID
|
|
305
|
+
* @returns True if screencast is active
|
|
306
|
+
*/
|
|
307
|
+
hasActiveScreencast(agentId) {
|
|
308
|
+
return this.screencasts.has(agentId);
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Close the browser session for an agent.
|
|
312
|
+
* Stops screencast and broadcasts browser_closed status.
|
|
313
|
+
* Call this before calling toolset.close() to ensure UI is notified.
|
|
314
|
+
*
|
|
315
|
+
* @param viewerKey - The viewer key (agentId or agentId:threadId for thread-scoped)
|
|
316
|
+
*/
|
|
317
|
+
async closeBrowserSession(viewerKey) {
|
|
318
|
+
this.lastUrls.delete(viewerKey);
|
|
319
|
+
this.lastViewports.delete(viewerKey);
|
|
320
|
+
this.lastStatuses.delete(viewerKey);
|
|
321
|
+
const stream = this.screencasts.get(viewerKey);
|
|
322
|
+
if (stream) {
|
|
323
|
+
try {
|
|
324
|
+
await stream.stop();
|
|
325
|
+
} catch (error) {
|
|
326
|
+
console.warn(`[ViewerRegistry] Error stopping screencast for ${viewerKey}:`, error);
|
|
327
|
+
this.screencasts.delete(viewerKey);
|
|
328
|
+
this.broadcastStatus(viewerKey, { status: "browser_closed" });
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
this.broadcastStatus(viewerKey, { status: "browser_closed" });
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
// src/server/browser-stream/input-handler.ts
|
|
337
|
+
var VALID_MOUSE_EVENTS = /* @__PURE__ */ new Set(["mousePressed", "mouseReleased", "mouseMoved", "mouseWheel"]);
|
|
338
|
+
var VALID_KEYBOARD_EVENTS = /* @__PURE__ */ new Set(["keyDown", "keyUp", "char"]);
|
|
339
|
+
var inputQueues = /* @__PURE__ */ new Map();
|
|
340
|
+
function enqueueInput(key, fn) {
|
|
341
|
+
const current = inputQueues.get(key) ?? Promise.resolve();
|
|
342
|
+
const next = current.then(fn).catch(() => {
|
|
343
|
+
});
|
|
344
|
+
inputQueues.set(key, next);
|
|
345
|
+
}
|
|
346
|
+
var VIRTUAL_KEY_CODES = {
|
|
347
|
+
// Control keys
|
|
348
|
+
Backspace: 8,
|
|
349
|
+
Tab: 9,
|
|
350
|
+
Enter: 13,
|
|
351
|
+
Shift: 16,
|
|
352
|
+
Control: 17,
|
|
353
|
+
Alt: 18,
|
|
354
|
+
Pause: 19,
|
|
355
|
+
CapsLock: 20,
|
|
356
|
+
Escape: 27,
|
|
357
|
+
Space: 32,
|
|
358
|
+
" ": 32,
|
|
359
|
+
PageUp: 33,
|
|
360
|
+
PageDown: 34,
|
|
361
|
+
End: 35,
|
|
362
|
+
Home: 36,
|
|
363
|
+
// Arrow keys
|
|
364
|
+
ArrowLeft: 37,
|
|
365
|
+
ArrowUp: 38,
|
|
366
|
+
ArrowRight: 39,
|
|
367
|
+
ArrowDown: 40,
|
|
368
|
+
// Editing keys
|
|
369
|
+
Insert: 45,
|
|
370
|
+
Delete: 46,
|
|
371
|
+
// Function keys
|
|
372
|
+
F1: 112,
|
|
373
|
+
F2: 113,
|
|
374
|
+
F3: 114,
|
|
375
|
+
F4: 115,
|
|
376
|
+
F5: 116,
|
|
377
|
+
F6: 117,
|
|
378
|
+
F7: 118,
|
|
379
|
+
F8: 119,
|
|
380
|
+
F9: 120,
|
|
381
|
+
F10: 121,
|
|
382
|
+
F11: 122,
|
|
383
|
+
F12: 123
|
|
384
|
+
};
|
|
385
|
+
function getVirtualKeyCode(key) {
|
|
386
|
+
if (!key) return void 0;
|
|
387
|
+
if (VIRTUAL_KEY_CODES[key] !== void 0) {
|
|
388
|
+
return VIRTUAL_KEY_CODES[key];
|
|
389
|
+
}
|
|
390
|
+
if (key.length === 1) {
|
|
391
|
+
return key.toUpperCase().charCodeAt(0);
|
|
392
|
+
}
|
|
393
|
+
return void 0;
|
|
394
|
+
}
|
|
395
|
+
function handleInputMessage(data, getToolset, agentId, threadId) {
|
|
396
|
+
let message;
|
|
397
|
+
try {
|
|
398
|
+
message = JSON.parse(data);
|
|
399
|
+
} catch {
|
|
400
|
+
return;
|
|
401
|
+
}
|
|
402
|
+
if (!isValidInputMessage(message)) {
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const toolset = getToolset(agentId);
|
|
406
|
+
if (!toolset) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const queueKey = `${agentId}:${threadId ?? "default"}`;
|
|
410
|
+
switch (message.type) {
|
|
411
|
+
case "mouse":
|
|
412
|
+
enqueueInput(queueKey, async () => {
|
|
413
|
+
try {
|
|
414
|
+
await injectMouse(toolset, message, threadId);
|
|
415
|
+
} catch (err) {
|
|
416
|
+
if (isDisconnectionError(err)) {
|
|
417
|
+
notifyBrowserClosed(toolset, threadId);
|
|
418
|
+
} else if (!isExpectedInjectionError(err)) {
|
|
419
|
+
console.warn("[InputHandler] Mouse injection error:", err);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
break;
|
|
424
|
+
case "keyboard":
|
|
425
|
+
enqueueInput(queueKey, async () => {
|
|
426
|
+
try {
|
|
427
|
+
await injectKeyboard(toolset, message, threadId);
|
|
428
|
+
} catch (err) {
|
|
429
|
+
if (isDisconnectionError(err)) {
|
|
430
|
+
notifyBrowserClosed(toolset, threadId);
|
|
431
|
+
} else if (!isExpectedInjectionError(err)) {
|
|
432
|
+
console.warn("[InputHandler] Keyboard injection error:", err);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
break;
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
function isDisconnectionError(err) {
|
|
440
|
+
if (!(err instanceof Error)) return false;
|
|
441
|
+
const msg = err.message.toLowerCase();
|
|
442
|
+
return msg.includes("target closed") || msg.includes("browser has been closed") || msg.includes("page has been closed") || msg.includes("session closed") || msg.includes("browser has disconnected");
|
|
443
|
+
}
|
|
444
|
+
function isExpectedInjectionError(err) {
|
|
445
|
+
if (!(err instanceof Error)) return false;
|
|
446
|
+
const msg = err.message.toLowerCase();
|
|
447
|
+
return msg.includes("no cdp session") || msg.includes("browser not launched") || msg.includes("not connected to browser") || msg.includes("no active target") || msg.includes("target") && msg.includes("not attached");
|
|
448
|
+
}
|
|
449
|
+
function notifyBrowserClosed(toolset, threadId) {
|
|
450
|
+
const browser = toolset;
|
|
451
|
+
if (threadId && typeof browser.closeThreadSession === "function") {
|
|
452
|
+
void browser.closeThreadSession(threadId).catch(() => {
|
|
453
|
+
browser.handleBrowserDisconnected?.();
|
|
454
|
+
});
|
|
455
|
+
return;
|
|
456
|
+
}
|
|
457
|
+
browser.handleBrowserDisconnected?.();
|
|
458
|
+
}
|
|
459
|
+
async function injectMouse(toolset, msg, threadId) {
|
|
460
|
+
await toolset.injectMouseEvent(
|
|
461
|
+
{
|
|
462
|
+
type: msg.eventType,
|
|
463
|
+
x: msg.x,
|
|
464
|
+
y: msg.y,
|
|
465
|
+
button: msg.button,
|
|
466
|
+
clickCount: msg.clickCount,
|
|
467
|
+
deltaX: msg.deltaX,
|
|
468
|
+
deltaY: msg.deltaY,
|
|
469
|
+
modifiers: msg.modifiers
|
|
470
|
+
},
|
|
471
|
+
threadId
|
|
472
|
+
);
|
|
473
|
+
}
|
|
474
|
+
async function injectKeyboard(toolset, msg, threadId) {
|
|
475
|
+
const windowsVirtualKeyCode = getVirtualKeyCode(msg.key);
|
|
476
|
+
await toolset.injectKeyboardEvent(
|
|
477
|
+
{
|
|
478
|
+
type: msg.eventType,
|
|
479
|
+
key: msg.key,
|
|
480
|
+
code: msg.code,
|
|
481
|
+
text: msg.text,
|
|
482
|
+
modifiers: msg.modifiers,
|
|
483
|
+
windowsVirtualKeyCode
|
|
484
|
+
},
|
|
485
|
+
threadId
|
|
486
|
+
);
|
|
487
|
+
}
|
|
488
|
+
function isValidInputMessage(msg) {
|
|
489
|
+
if (typeof msg !== "object" || msg === null) {
|
|
490
|
+
return false;
|
|
491
|
+
}
|
|
492
|
+
const typed = msg;
|
|
493
|
+
if (typed.type === "mouse") {
|
|
494
|
+
if (typeof typed.eventType !== "string" || !VALID_MOUSE_EVENTS.has(typed.eventType)) {
|
|
495
|
+
return false;
|
|
496
|
+
}
|
|
497
|
+
if (typeof typed.x !== "number" || typeof typed.y !== "number") {
|
|
498
|
+
return false;
|
|
499
|
+
}
|
|
500
|
+
if (typed.button !== void 0 && typeof typed.button !== "string") {
|
|
501
|
+
return false;
|
|
502
|
+
}
|
|
503
|
+
if (typed.deltaX !== void 0 && typeof typed.deltaX !== "number") {
|
|
504
|
+
return false;
|
|
505
|
+
}
|
|
506
|
+
if (typed.deltaY !== void 0 && typeof typed.deltaY !== "number") {
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
if (typed.type === "keyboard") {
|
|
512
|
+
if (typeof typed.eventType !== "string" || !VALID_KEYBOARD_EVENTS.has(typed.eventType)) {
|
|
513
|
+
return false;
|
|
514
|
+
}
|
|
515
|
+
const hasKey = typeof typed.key === "string";
|
|
516
|
+
const hasCode = typeof typed.code === "string";
|
|
517
|
+
const hasText = typeof typed.text === "string";
|
|
518
|
+
if (!hasKey && !hasCode && !hasText) {
|
|
519
|
+
return false;
|
|
520
|
+
}
|
|
521
|
+
if (typed.modifiers !== void 0 && typeof typed.modifiers !== "number") {
|
|
522
|
+
return false;
|
|
523
|
+
}
|
|
524
|
+
return true;
|
|
525
|
+
}
|
|
526
|
+
return false;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
export { ViewerRegistry, handleInputMessage };
|
|
530
|
+
//# sourceMappingURL=index.js.map
|
|
531
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/server/browser-stream/viewer-registry.ts","../../../src/server/browser-stream/input-handler.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type {\n StatusMessage,\n BrowserStreamConfig,\n ViewportMessage,\n BrowserStreamWebSocket,\n ViewerRegistryLike,\n} from './types.js';\n\n/** Minimal screencast stream interface matching BrowserToolsetLike.startScreencast return type */\ninterface ScreencastStreamLike {\n on(event: 'frame', handler: (frame: { data: string; viewport: { width: number; height: number } }) => void): void;\n on(event: 'stop', handler: (reason: string) => void): void;\n on(event: 'error', handler: (error: Error) => void): void;\n on(event: 'url', handler: (url: string) => void): void;\n stop(): Promise<void>;\n}\n\n/**\n * ViewerRegistry manages WebSocket connections per agent and controls screencast lifecycle.\n *\n * Key responsibilities:\n * - Track connected viewers per agentId\n * - Start screencast when browser becomes active (not on viewer connect)\n * - Stop screencast when last viewer disconnects\n * - Broadcast frames to all viewers for an agent\n *\n * The browser is NOT launched when viewers connect - it only starts streaming\n * when the browser is already running from agent tool usage.\n *\n * @example\n * ```typescript\n * const registry = new ViewerRegistry();\n *\n * // When a viewer connects\n * registry.addViewer('agent-123', ws, getToolset);\n *\n * // When a viewer disconnects\n * registry.removeViewer('agent-123', ws);\n * ```\n */\nexport class ViewerRegistry implements ViewerRegistryLike {\n /** Map of agentId to set of connected WebSocket contexts */\n private viewers = new Map<string, Set<BrowserStreamWebSocket>>();\n\n /** Map of agentId to active screencast stream */\n private screencasts = new Map<string, ScreencastStreamLike>();\n\n /** Set of viewerKeys currently starting a screencast (to prevent race conditions) */\n private startingScreencasts = new Set<string>();\n\n /** Map of agentId to cleanup function for onBrowserReady callback */\n private browserReadyCleanups = new Map<string, () => void>();\n\n /** Map of agentId to cleanup function for onBrowserClosed callback */\n private browserClosedCleanups = new Map<string, () => void>();\n\n /** Map of agentId to last known URL (for dedup) */\n private lastUrls = new Map<string, string>();\n\n /** Map of agentId to last known viewport dimensions (for change detection) */\n private lastViewports = new Map<string, { width: number; height: number }>();\n\n /** Map of viewerKey to last broadcast status (for replay to new viewers) */\n private lastStatuses = new Map<string, string>();\n\n /**\n * Add a viewer for an agent. Starts screencast if this is the first viewer.\n *\n * @param viewerKey - The viewer key (agentId or agentId:threadId for thread-scoped)\n * @param ws - The WebSocket context for this viewer\n * @param getToolset - Function to retrieve the BrowserToolset for this agent\n * @param agentId - The actual agent ID for toolset lookup (optional, defaults to viewerKey)\n * @param threadId - The thread ID for thread-scoped screencasts (optional)\n */\n async addViewer(\n viewerKey: string,\n ws: BrowserStreamWebSocket,\n getToolset: BrowserStreamConfig['getToolset'],\n agentId?: string,\n threadId?: string,\n ): Promise<void> {\n // Get or create the viewer set for this viewer key\n let viewerSet = this.viewers.get(viewerKey);\n if (!viewerSet) {\n viewerSet = new Set();\n this.viewers.set(viewerKey, viewerSet);\n }\n\n const wasEmpty = viewerSet.size === 0;\n viewerSet.add(ws);\n\n // Start screencast if this is the first viewer\n // Use agentId for toolset lookup, viewerKey for registry keying\n if (wasEmpty) {\n await this.startScreencast(viewerKey, getToolset, agentId ?? viewerKey, threadId);\n } else {\n // Send current state to new viewer (screencast already running)\n this.sendCurrentState(viewerKey, ws);\n }\n }\n\n /**\n * Send current state (URL, viewport) to a newly connected viewer.\n */\n private sendCurrentState(viewerKey: string, ws: BrowserStreamWebSocket): void {\n try {\n // Send last known URL\n const lastUrl = this.lastUrls.get(viewerKey);\n if (lastUrl) {\n ws.send(JSON.stringify({ url: lastUrl }));\n }\n\n // Send last known viewport\n const lastViewport = this.lastViewports.get(viewerKey);\n if (lastViewport) {\n ws.send(JSON.stringify({ viewport: lastViewport }));\n }\n\n // Send actual current status (not always 'streaming')\n const lastStatus = this.lastStatuses.get(viewerKey);\n if (lastStatus) {\n ws.send(JSON.stringify({ status: lastStatus }));\n }\n } catch (error) {\n console.warn('[ViewerRegistry] Error sending current state to new viewer:', error);\n }\n }\n\n /**\n * Remove a viewer. Stops screencast if this was the last viewer.\n *\n * @param viewerKey - The viewer key (agentId or agentId:threadId for thread-scoped)\n * @param ws - The WebSocket context to remove\n */\n async removeViewer(viewerKey: string, ws: BrowserStreamWebSocket): Promise<void> {\n const viewerSet = this.viewers.get(viewerKey);\n if (!viewerSet) {\n return;\n }\n\n viewerSet.delete(ws);\n\n // Clean up if no more viewers\n if (viewerSet.size === 0) {\n this.viewers.delete(viewerKey);\n this.lastUrls.delete(viewerKey);\n this.lastViewports.delete(viewerKey);\n\n // Clean up browser callbacks if pending\n const readyCleanup = this.browserReadyCleanups.get(viewerKey);\n if (readyCleanup) {\n readyCleanup();\n this.browserReadyCleanups.delete(viewerKey);\n }\n const closedCleanup = this.browserClosedCleanups.get(viewerKey);\n if (closedCleanup) {\n closedCleanup();\n this.browserClosedCleanups.delete(viewerKey);\n }\n\n await this.stopScreencast(viewerKey);\n }\n }\n\n /**\n * Broadcast a binary frame to all viewers.\n *\n * @param viewerKey - The viewer key (agentId or agentId:threadId for thread-scoped)\n * @param data - The binary frame data (base64 encoded)\n */\n broadcastFrame(viewerKey: string, data: string): void {\n const viewerSet = this.viewers.get(viewerKey);\n if (!viewerSet) {\n return;\n }\n\n // Send as binary (base64 string)\n for (const ws of viewerSet) {\n try {\n ws.send(data);\n } catch (error) {\n console.warn('[ViewerRegistry] Error broadcasting frame:', error);\n }\n }\n }\n\n /**\n * Broadcast a status message to all viewers.\n *\n * @param viewerKey - The viewer key (agentId or agentId:threadId for thread-scoped)\n * @param status - The status message to send\n */\n broadcastStatus(viewerKey: string, status: StatusMessage): void {\n // Track last status for replay to new viewers\n if (status.status) {\n this.lastStatuses.set(viewerKey, status.status);\n }\n\n const viewerSet = this.viewers.get(viewerKey);\n if (!viewerSet) {\n return;\n }\n\n const message = JSON.stringify(status);\n for (const ws of viewerSet) {\n try {\n ws.send(message);\n } catch (error) {\n console.warn('[ViewerRegistry] Error broadcasting status:', error);\n }\n }\n }\n\n /**\n * Broadcast a URL update to all viewers (only if changed).\n */\n private broadcastUrlIfChanged(viewerKey: string, url: string | null): void {\n if (!url) return;\n if (this.lastUrls.get(viewerKey) === url) return;\n\n this.lastUrls.set(viewerKey, url);\n\n const viewerSet = this.viewers.get(viewerKey);\n if (!viewerSet) return;\n\n const message = JSON.stringify({ url });\n for (const ws of viewerSet) {\n try {\n ws.send(message);\n } catch (error) {\n console.warn('[ViewerRegistry] Error broadcasting URL:', error);\n }\n }\n }\n\n /**\n * Broadcast viewport metadata to all viewers.\n * Only sends if dimensions have changed from last broadcast.\n * Called on stream start and on each frame to detect dimension changes.\n */\n private broadcastViewportIfChanged(viewerKey: string, viewport: { width: number; height: number }): void {\n const last = this.lastViewports.get(viewerKey);\n if (last && last.width === viewport.width && last.height === viewport.height) {\n return;\n }\n\n this.lastViewports.set(viewerKey, { width: viewport.width, height: viewport.height });\n\n const viewerSet = this.viewers.get(viewerKey);\n if (!viewerSet) return;\n\n const message: ViewportMessage = { viewport: { width: viewport.width, height: viewport.height } };\n const json = JSON.stringify(message);\n for (const ws of viewerSet) {\n try {\n ws.send(json);\n } catch (error) {\n console.warn('[ViewerRegistry] Error broadcasting viewport:', error);\n }\n }\n }\n\n /**\n * Start screencast for a viewer. Only starts if browser is already running.\n * If browser not running, registers a callback to start when browser becomes ready.\n *\n * @param viewerKey - The viewer key (for registry keying)\n * @param getToolset - Function to retrieve the BrowserToolset\n * @param agentId - The actual agent ID for toolset lookup\n * @param threadId - The thread ID for thread-scoped page selection (optional)\n */\n private async startScreencast(\n viewerKey: string,\n getToolset: BrowserStreamConfig['getToolset'],\n agentId: string,\n threadId?: string,\n ): Promise<void> {\n const toolset = getToolset(agentId);\n if (!toolset) {\n // No browser available for this agent - just keep connection open\n console.info(`[ViewerRegistry] No toolset for ${viewerKey}, waiting...`);\n return;\n }\n\n // Register callback for browser restarts (external close + re-launch)\n // This ensures screencast reconnects after browser is externally closed\n // Pass threadId so callback only fires when that specific thread's browser is ready\n if (!this.browserReadyCleanups.has(viewerKey)) {\n const cleanup = toolset.onBrowserReady(() => {\n // Only start if we still have viewers\n if (!this.viewers.has(viewerKey)) {\n return;\n }\n\n // Stop any existing (likely dead) screencast before starting new one\n const existingStream = this.screencasts.get(viewerKey);\n if (existingStream) {\n console.info(`[ViewerRegistry] Stopping old screencast for ${viewerKey} before reconnecting...`);\n this.screencasts.delete(viewerKey);\n // Stop async, don't wait - the old CDP session is probably dead anyway\n existingStream.stop().catch(() => {});\n }\n\n console.info(`[ViewerRegistry] Browser ready for ${viewerKey}, starting screencast...`);\n this.doStartScreencast(viewerKey, toolset, threadId).catch(error => {\n console.error(`[ViewerRegistry] Failed to start screencast on browser ready for ${viewerKey}:`, error);\n });\n }, threadId);\n this.browserReadyCleanups.set(viewerKey, cleanup);\n }\n\n // Register callback for browser closed (external close detection)\n // This ensures UI shows \"browser closed\" overlay immediately\n // Pass threadId so callback only fires when that specific thread's browser closes\n if (!this.browserClosedCleanups.has(viewerKey)) {\n const cleanup = toolset.onBrowserClosed(() => {\n console.info(`[ViewerRegistry] Browser closed for ${viewerKey}, notifying viewers...`);\n // Clean up screencast reference (CDP session is dead)\n this.screencasts.delete(viewerKey);\n // Broadcast browser_closed status to UI\n this.broadcastStatus(viewerKey, { status: 'browser_closed' });\n }, threadId);\n this.browserClosedCleanups.set(viewerKey, cleanup);\n }\n\n // Check if browser is already running\n if (toolset.isBrowserRunning()) {\n // Browser is running, start screencast immediately\n await this.doStartScreencast(viewerKey, toolset, threadId);\n } else {\n // Browser not running - callback will fire when it becomes ready\n console.info(`[ViewerRegistry] Browser not running for ${viewerKey}, waiting for browser to start...`);\n }\n }\n\n /**\n * Internal method to actually start the screencast stream.\n *\n * @param viewerKey - The viewer key (for registry keying and logging)\n * @param toolset - The browser toolset\n * @param threadId - The thread ID for thread-scoped page selection (optional)\n */\n private async doStartScreencast(\n viewerKey: string,\n toolset: NonNullable<ReturnType<BrowserStreamConfig['getToolset']>>,\n threadId?: string,\n ): Promise<void> {\n // Skip if already streaming or currently starting (prevents race conditions)\n if (this.screencasts.has(viewerKey) || this.startingScreencasts.has(viewerKey)) {\n return;\n }\n\n // Mark as starting to prevent concurrent starts\n this.startingScreencasts.add(viewerKey);\n\n try {\n this.broadcastStatus(viewerKey, { status: 'browser_starting' });\n\n // Use startScreencastIfBrowserActive to avoid launching browser\n // Pass threadId for thread-scoped page selection\n const stream = await toolset.startScreencastIfBrowserActive(threadId ? { threadId } : undefined);\n if (!stream) {\n console.warn(`[ViewerRegistry] No browser session for ${viewerKey}`);\n // Tell the UI this thread has no browser session yet\n // Using 'browser_closed' to indicate no active browser for this thread\n this.broadcastStatus(viewerKey, { status: 'browser_closed' });\n return;\n }\n\n this.screencasts.set(viewerKey, stream);\n\n // Capture reference to guard against stale callbacks from superseded streams\n const currentStream = stream;\n\n // Wire up frame events + viewport tracking\n stream.on('frame', frame => {\n // Ignore frames from superseded streams\n if (this.screencasts.get(viewerKey) !== currentStream) return;\n this.broadcastFrame(viewerKey, frame.data);\n this.broadcastViewportIfChanged(viewerKey, frame.viewport);\n });\n\n // Wire up URL change events (emitted by browser providers on navigation)\n stream.on('url', (url: string) => {\n // Ignore URL updates from superseded streams\n if (this.screencasts.get(viewerKey) !== currentStream) return;\n this.broadcastUrlIfChanged(viewerKey, url);\n });\n\n // Wire up stop events\n stream.on('stop', reason => {\n // Ignore stop events from superseded streams (e.g., old stream stopping after reconnect)\n if (this.screencasts.get(viewerKey) !== currentStream) {\n console.info(`[ViewerRegistry] Ignoring stop from superseded stream for ${viewerKey}`);\n return;\n }\n console.info(`[ViewerRegistry] Screencast stopped for ${viewerKey}: ${reason}`);\n this.screencasts.delete(viewerKey);\n this.broadcastStatus(viewerKey, { status: 'browser_closed' });\n });\n\n // Wire up error events\n stream.on('error', error => {\n // Ignore errors from superseded streams\n if (this.screencasts.get(viewerKey) !== currentStream) return;\n console.error(`[ViewerRegistry] Screencast error for ${viewerKey}:`, error);\n });\n\n this.broadcastStatus(viewerKey, { status: 'streaming' });\n\n // Send initial URL - pass threadId to get URL from correct browser session\n const initialUrl = await toolset.getCurrentUrl(threadId);\n this.broadcastUrlIfChanged(viewerKey, initialUrl);\n } catch (error) {\n console.error(`[ViewerRegistry] Failed to start screencast for ${viewerKey}:`, error);\n // Connection stays open - user can see error status\n } finally {\n // Clear starting flag\n this.startingScreencasts.delete(viewerKey);\n }\n }\n\n /**\n * Stop screencast for an agent. Called when last viewer disconnects.\n */\n private async stopScreencast(agentId: string): Promise<void> {\n const stream = this.screencasts.get(agentId);\n if (!stream) {\n return;\n }\n\n try {\n await stream.stop();\n } catch (error) {\n console.warn(`[ViewerRegistry] Error stopping screencast for ${agentId}:`, error);\n } finally {\n this.screencasts.delete(agentId);\n }\n }\n\n /**\n * Get the number of viewers for an agent.\n *\n * @param agentId - The agent ID\n * @returns The number of connected viewers\n */\n getViewerCount(agentId: string): number {\n return this.viewers.get(agentId)?.size ?? 0;\n }\n\n /**\n * Check if an agent has an active screencast.\n *\n * @param agentId - The agent ID\n * @returns True if screencast is active\n */\n hasActiveScreencast(agentId: string): boolean {\n return this.screencasts.has(agentId);\n }\n\n /**\n * Close the browser session for an agent.\n * Stops screencast and broadcasts browser_closed status.\n * Call this before calling toolset.close() to ensure UI is notified.\n *\n * @param viewerKey - The viewer key (agentId or agentId:threadId for thread-scoped)\n */\n async closeBrowserSession(viewerKey: string): Promise<void> {\n // NOTE: Do NOT clean up the onBrowserReady callback here.\n // Viewers are still connected (WebSocket stays open), so we need\n // the callback to fire when the browser relaunches from a subsequent\n // tool call. Callback cleanup only happens in removeViewer() when\n // the last viewer disconnects.\n\n // Clear URL, viewport, and status tracking so next session sends fresh data\n this.lastUrls.delete(viewerKey);\n this.lastViewports.delete(viewerKey);\n this.lastStatuses.delete(viewerKey);\n\n // Stop screencast if active\n const stream = this.screencasts.get(viewerKey);\n if (stream) {\n try {\n await stream.stop();\n // Note: stream.stop() emits 'stop' event which triggers broadcastStatus\n } catch (error) {\n console.warn(`[ViewerRegistry] Error stopping screencast for ${viewerKey}:`, error);\n // Still broadcast browser_closed even if stop fails\n this.screencasts.delete(viewerKey);\n this.broadcastStatus(viewerKey, { status: 'browser_closed' });\n }\n } else {\n // No active screencast, but still broadcast browser_closed\n this.broadcastStatus(viewerKey, { status: 'browser_closed' });\n }\n }\n}\n","import type { MastraBrowser } from '@mastra/core/browser';\nimport type { ClientInputMessage, MouseInputMessage, KeyboardInputMessage } from './types.js';\n\n// Valid CDP mouse event types\nconst VALID_MOUSE_EVENTS = new Set(['mousePressed', 'mouseReleased', 'mouseMoved', 'mouseWheel']);\n\n// Valid CDP keyboard event types\nconst VALID_KEYBOARD_EVENTS = new Set(['keyDown', 'keyUp', 'char']);\n\n// Input dispatch queue per agent+thread to serialize input events\nconst inputQueues = new Map<string, Promise<void>>();\n\n/**\n * Serialize input dispatch to maintain event ordering.\n * Input events must be processed in order to avoid race conditions.\n */\nfunction enqueueInput(key: string, fn: () => Promise<void>): void {\n const current = inputQueues.get(key) ?? Promise.resolve();\n const next = current.then(fn).catch(() => {\n // Errors are handled inside fn, just ensure chain continues\n });\n inputQueues.set(key, next);\n}\n\n/**\n * Map of key names to Windows virtual key codes.\n * Required for non-printable keys (Enter, Tab, Arrow keys, etc.)\n * See: https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes\n */\nconst VIRTUAL_KEY_CODES: Record<string, number> = {\n // Control keys\n Backspace: 8,\n Tab: 9,\n Enter: 13,\n Shift: 16,\n Control: 17,\n Alt: 18,\n Pause: 19,\n CapsLock: 20,\n Escape: 27,\n Space: 32,\n ' ': 32,\n PageUp: 33,\n PageDown: 34,\n End: 35,\n Home: 36,\n // Arrow keys\n ArrowLeft: 37,\n ArrowUp: 38,\n ArrowRight: 39,\n ArrowDown: 40,\n // Editing keys\n Insert: 45,\n Delete: 46,\n // Function keys\n F1: 112,\n F2: 113,\n F3: 114,\n F4: 115,\n F5: 116,\n F6: 117,\n F7: 118,\n F8: 119,\n F9: 120,\n F10: 121,\n F11: 122,\n F12: 123,\n};\n\n/**\n * Get the Windows virtual key code for a key.\n * For printable characters, uses the character code.\n * For special keys, looks up in the mapping.\n */\nfunction getVirtualKeyCode(key: string | undefined): number | undefined {\n if (!key) return undefined;\n\n // Check special keys first\n if (VIRTUAL_KEY_CODES[key] !== undefined) {\n return VIRTUAL_KEY_CODES[key];\n }\n\n // For single printable characters, use the uppercase char code\n if (key.length === 1) {\n return key.toUpperCase().charCodeAt(0);\n }\n\n return undefined;\n}\n\n/**\n * Handle an incoming WebSocket message by parsing, validating,\n * and routing to the appropriate toolset injection method.\n *\n * Fire-and-forget: no acknowledgment sent back to client.\n * Silently ignores malformed or unrecognized messages.\n *\n * @param data - Raw string data from WebSocket message\n * @param getToolset - Function to retrieve MastraBrowser for an agent\n * @param agentId - The agent ID this WebSocket connection is for\n * @param threadId - The thread ID for thread-scoped operations (optional)\n */\nexport function handleInputMessage(\n data: string,\n getToolset: (agentId: string) => MastraBrowser | undefined,\n agentId: string,\n threadId?: string,\n): void {\n let message: unknown;\n try {\n message = JSON.parse(data);\n } catch {\n return;\n }\n\n if (!isValidInputMessage(message)) {\n return;\n }\n\n const toolset = getToolset(agentId);\n if (!toolset) {\n return;\n }\n\n // Serialize input dispatch per agent+thread to maintain event ordering\n const queueKey = `${agentId}:${threadId ?? 'default'}`;\n\n switch (message.type) {\n case 'mouse':\n enqueueInput(queueKey, async () => {\n try {\n await injectMouse(toolset, message, threadId);\n } catch (err) {\n if (isDisconnectionError(err)) {\n notifyBrowserClosed(toolset, threadId);\n } else if (!isExpectedInjectionError(err)) {\n console.warn('[InputHandler] Mouse injection error:', err);\n }\n }\n });\n break;\n case 'keyboard':\n enqueueInput(queueKey, async () => {\n try {\n await injectKeyboard(toolset, message, threadId);\n } catch (err) {\n if (isDisconnectionError(err)) {\n notifyBrowserClosed(toolset, threadId);\n } else if (!isExpectedInjectionError(err)) {\n console.warn('[InputHandler] Keyboard injection error:', err);\n }\n }\n });\n break;\n }\n}\n\n// --- Error handling ---\n\n/**\n * Check if an error indicates browser disconnection (target closed).\n * These errors mean the browser was externally closed.\n */\nfunction isDisconnectionError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n const msg = err.message.toLowerCase();\n // Note: 'no cdp session' is handled by isExpectedInjectionError (startup race)\n // and should not trigger browser closed state\n return (\n msg.includes('target closed') ||\n msg.includes('browser has been closed') ||\n msg.includes('page has been closed') ||\n msg.includes('session closed') ||\n msg.includes('browser has disconnected')\n );\n}\n\n/**\n * Check if an injection error is expected (browser not ready yet).\n * These are silently ignored to avoid log spam.\n */\nfunction isExpectedInjectionError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n const msg = err.message.toLowerCase();\n return (\n msg.includes('no cdp session') ||\n msg.includes('browser not launched') ||\n msg.includes('not connected to browser') ||\n msg.includes('no active target') ||\n (msg.includes('target') && msg.includes('not attached'))\n );\n}\n\n/**\n * Notify the browser that it was closed externally.\n * This triggers the onBrowserClosed callbacks to update the UI.\n * If threadId is provided and the browser supports thread-specific closing,\n * only that thread's session is closed.\n */\nfunction notifyBrowserClosed(toolset: MastraBrowser, threadId?: string): void {\n const browser = toolset as unknown as {\n closeThreadSession?: (threadId: string) => Promise<void>;\n handleBrowserDisconnected?: () => void;\n };\n\n // For thread-scoped browsers, close only the specific thread's session\n if (threadId && typeof browser.closeThreadSession === 'function') {\n void browser.closeThreadSession(threadId).catch(() => {\n // Fall back to global disconnect if thread-specific close fails\n browser.handleBrowserDisconnected?.();\n });\n return;\n }\n\n // Fall back to global disconnect handling\n browser.handleBrowserDisconnected?.();\n}\n\n// --- Input injection ---\n\n/**\n * Inject a mouse event into the browser.\n */\nasync function injectMouse(toolset: MastraBrowser, msg: MouseInputMessage, threadId?: string): Promise<void> {\n await toolset.injectMouseEvent(\n {\n type: msg.eventType,\n x: msg.x,\n y: msg.y,\n button: msg.button,\n clickCount: msg.clickCount,\n deltaX: msg.deltaX,\n deltaY: msg.deltaY,\n modifiers: msg.modifiers,\n },\n threadId,\n );\n}\n\n/**\n * Inject a keyboard event into the browser.\n */\nasync function injectKeyboard(toolset: MastraBrowser, msg: KeyboardInputMessage, threadId?: string): Promise<void> {\n const windowsVirtualKeyCode = getVirtualKeyCode(msg.key);\n\n await toolset.injectKeyboardEvent(\n {\n type: msg.eventType,\n key: msg.key,\n code: msg.code,\n text: msg.text,\n modifiers: msg.modifiers,\n windowsVirtualKeyCode,\n },\n threadId,\n );\n}\n\n// --- Validation ---\n\n/**\n * Type guard to validate incoming messages.\n * Validates structure and rejects invalid event types at the boundary.\n */\nfunction isValidInputMessage(msg: unknown): msg is ClientInputMessage {\n if (typeof msg !== 'object' || msg === null) {\n return false;\n }\n\n const typed = msg as Record<string, unknown>;\n\n if (typed.type === 'mouse') {\n // Validate mouse message structure\n if (typeof typed.eventType !== 'string' || !VALID_MOUSE_EVENTS.has(typed.eventType)) {\n return false;\n }\n if (typeof typed.x !== 'number' || typeof typed.y !== 'number') {\n return false;\n }\n // Optional fields validation\n if (typed.button !== undefined && typeof typed.button !== 'string') {\n return false;\n }\n if (typed.deltaX !== undefined && typeof typed.deltaX !== 'number') {\n return false;\n }\n if (typed.deltaY !== undefined && typeof typed.deltaY !== 'number') {\n return false;\n }\n return true;\n }\n\n if (typed.type === 'keyboard') {\n // Validate keyboard message structure\n if (typeof typed.eventType !== 'string' || !VALID_KEYBOARD_EVENTS.has(typed.eventType)) {\n return false;\n }\n // At least one of key, code, or text should be present for meaningful input\n const hasKey = typeof typed.key === 'string';\n const hasCode = typeof typed.code === 'string';\n const hasText = typeof typed.text === 'string';\n if (!hasKey && !hasCode && !hasText) {\n return false;\n }\n // Optional modifiers validation\n if (typed.modifiers !== undefined && typeof typed.modifiers !== 'number') {\n return false;\n }\n return true;\n }\n\n return false;\n}\n"]}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MastraBrowser } from '@mastra/core/browser';
|
|
2
|
+
/**
|
|
3
|
+
* Handle an incoming WebSocket message by parsing, validating,
|
|
4
|
+
* and routing to the appropriate toolset injection method.
|
|
5
|
+
*
|
|
6
|
+
* Fire-and-forget: no acknowledgment sent back to client.
|
|
7
|
+
* Silently ignores malformed or unrecognized messages.
|
|
8
|
+
*
|
|
9
|
+
* @param data - Raw string data from WebSocket message
|
|
10
|
+
* @param getToolset - Function to retrieve MastraBrowser for an agent
|
|
11
|
+
* @param agentId - The agent ID this WebSocket connection is for
|
|
12
|
+
* @param threadId - The thread ID for thread-scoped operations (optional)
|
|
13
|
+
*/
|
|
14
|
+
export declare function handleInputMessage(data: string, getToolset: (agentId: string) => MastraBrowser | undefined, agentId: string, threadId?: string): void;
|
|
15
|
+
//# sourceMappingURL=input-handler.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-handler.d.ts","sourceRoot":"","sources":["../../../src/server/browser-stream/input-handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AA0F1D;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,aAAa,GAAG,SAAS,EAC1D,OAAO,EAAE,MAAM,EACf,QAAQ,CAAC,EAAE,MAAM,GAChB,IAAI,CAgDN"}
|