tmex-cli 0.12.4 → 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.
Files changed (88) hide show
  1. package/CHANGELOG.md +16 -6
  2. package/dist/runtime/server.js +1639 -102
  3. package/package.json +1 -1
  4. package/resources/fe-dist/assets/DevicePage-Cj9jwadE.js +19 -0
  5. package/resources/fe-dist/assets/DevicesPage-BbypaGC7.js +1 -0
  6. package/resources/fe-dist/assets/{FilePage-D1t8_zlX.js → FilePage-CbHZVJbn.js} +3 -4
  7. package/resources/fe-dist/assets/SettingsPage-CCV0NSQF.js +39 -0
  8. package/resources/fe-dist/assets/agent-tab-DPwxh08T.js +38 -0
  9. package/resources/fe-dist/assets/api-DlcFE5in.js +10 -0
  10. package/resources/fe-dist/assets/{arc-Cv0H1Phu.js → arc-Bzmm6y8F.js} +1 -2
  11. package/resources/fe-dist/assets/{architectureDiagram-3BPJPVTR-DG7npZeK.js → architectureDiagram-3BPJPVTR-BdsN6XRe.js} +1 -2
  12. package/resources/fe-dist/assets/{blockDiagram-GPEHLZMM-weB7PKUK.js → blockDiagram-GPEHLZMM-CXdQhkqo.js} +1 -2
  13. package/resources/fe-dist/assets/{c4Diagram-AAUBKEIU-C0sEabtp.js → c4Diagram-AAUBKEIU-CE_W2_pV.js} +1 -2
  14. package/resources/fe-dist/assets/{card-Spfb5xRZ.js → card-B_sl9KIo.js} +1 -2
  15. package/resources/fe-dist/assets/channel-CuapxAWT.js +1 -0
  16. package/resources/fe-dist/assets/{chunk-2J33WTMH-BxOp2sIC.js → chunk-2J33WTMH-BAdEIJQO.js} +1 -2
  17. package/resources/fe-dist/assets/{chunk-4BX2VUAB-bhg4hxVT.js → chunk-4BX2VUAB-BZ0yj21c.js} +1 -2
  18. package/resources/fe-dist/assets/{chunk-55IACEB6-v6jayOak.js → chunk-55IACEB6-BknLtkSo.js} +1 -2
  19. package/resources/fe-dist/assets/{chunk-727SXJPM-IY0KVws8.js → chunk-727SXJPM-CxVON9n5.js} +1 -2
  20. package/resources/fe-dist/assets/{chunk-AQP2D5EJ-CgwymoWa.js → chunk-AQP2D5EJ-CLqOK2pR.js} +1 -2
  21. package/resources/fe-dist/assets/{chunk-FMBD7UC4-DN35690f.js → chunk-FMBD7UC4-Dt5Vot-R.js} +1 -2
  22. package/resources/fe-dist/assets/{chunk-ND2GUHAM-lJmtxdhS.js → chunk-ND2GUHAM-t9HYlb8C.js} +1 -2
  23. package/resources/fe-dist/assets/{chunk-QZHKN3VN-DFeaB8rn.js → chunk-QZHKN3VN-DZReNtLB.js} +1 -2
  24. package/resources/fe-dist/assets/classDiagram-4FO5ZUOK-C0k136Ud.js +1 -0
  25. package/resources/fe-dist/assets/classDiagram-v2-Q7XG4LA2-C0k136Ud.js +1 -0
  26. package/resources/fe-dist/assets/copy-Z9Fsj1r-.js +6 -0
  27. package/resources/fe-dist/assets/{cose-bilkent-S5V4N54A-Dw2yYQz4.js → cose-bilkent-S5V4N54A-BCTtGF2n.js} +1 -2
  28. package/resources/fe-dist/assets/cytoscape.esm-DTSO7Bv0.js +0 -1
  29. package/resources/fe-dist/assets/{dagre-BM42HDAG-BeMjJ7ln.js → dagre-BM42HDAG-BQ48EqSj.js} +1 -2
  30. package/resources/fe-dist/assets/defaultLocale-DX6XiGOO.js +0 -1
  31. package/resources/fe-dist/assets/{diagram-2AECGRRQ-Dur8_CuN.js → diagram-2AECGRRQ-BXjt2moR.js} +1 -2
  32. package/resources/fe-dist/assets/{diagram-5GNKFQAL-GJc7gVa_.js → diagram-5GNKFQAL-B2bvNTQO.js} +1 -2
  33. package/resources/fe-dist/assets/{diagram-KO2AKTUF-pxckXfjw.js → diagram-KO2AKTUF-DLHWfbBH.js} +1 -2
  34. package/resources/fe-dist/assets/{diagram-LMA3HP47-B9E8-X7C.js → diagram-LMA3HP47-BCFT3Bur.js} +1 -2
  35. package/resources/fe-dist/assets/{diagram-OG6HWLK6-DrhHw5wy.js → diagram-OG6HWLK6-bNZtxUJ2.js} +1 -2
  36. package/resources/fe-dist/assets/en_US-B07wSHDj.js +1 -0
  37. package/resources/fe-dist/assets/{erDiagram-TEJ5UH35-DY9Wg1A1.js → erDiagram-TEJ5UH35-KoAzrx4j.js} +1 -2
  38. package/resources/fe-dist/assets/files-tab-DdBDTjfs.js +70 -0
  39. package/resources/fe-dist/assets/{flowDiagram-I6XJVG4X-eq3WE9d6.js → flowDiagram-I6XJVG4X-C6wGExWt.js} +1 -2
  40. package/resources/fe-dist/assets/{ganttDiagram-6RSMTGT7-BlXISXe1.js → ganttDiagram-6RSMTGT7-BIWybjes.js} +1 -2
  41. package/resources/fe-dist/assets/{gitGraphDiagram-PVQCEYII-Bg-xHeIY.js → gitGraphDiagram-PVQCEYII-CD6tz24l.js} +1 -2
  42. package/resources/fe-dist/assets/graph-CAnANduQ.js +0 -1
  43. package/resources/fe-dist/assets/index-CUCrFB_J.js +29 -0
  44. package/resources/fe-dist/assets/index-CwH-l50P.js +299 -0
  45. package/resources/fe-dist/assets/index-D-q7dOhH.css +1 -0
  46. package/resources/fe-dist/assets/{infoDiagram-5YYISTIA-CW2drAHG.js → infoDiagram-5YYISTIA-CrI-qlYw.js} +1 -2
  47. package/resources/fe-dist/assets/init-Gi6I4Gst.js +0 -1
  48. package/resources/fe-dist/assets/{ishikawaDiagram-YF4QCWOH-Qchz0nqR.js → ishikawaDiagram-YF4QCWOH-D9v5anxN.js} +1 -2
  49. package/resources/fe-dist/assets/ja_JP-DjF6Lrmx.js +1 -0
  50. package/resources/fe-dist/assets/{journeyDiagram-JHISSGLW-Dk-UsexE.js → journeyDiagram-JHISSGLW-BH38CiJ0.js} +1 -2
  51. package/resources/fe-dist/assets/{kanban-definition-UN3LZRKU-DKeGp7b5.js → kanban-definition-UN3LZRKU-CKZ3_oXD.js} +1 -2
  52. package/resources/fe-dist/assets/layout-DGIYPm2g.js +0 -1
  53. package/resources/fe-dist/assets/{linear-Bt4f_qDW.js → linear-C_oE01xA.js} +1 -2
  54. package/resources/fe-dist/assets/{markdown-preview-DXv9GzaA.js → markdown-preview-BtNbNwlP.js} +6 -7
  55. package/resources/fe-dist/assets/{mermaid.core-B-wSkmMd.js → mermaid.core-4dbvlhht.js} +5 -6
  56. package/resources/fe-dist/assets/{mindmap-definition-RKZ34NQL-DXBwCMXu.js → mindmap-definition-RKZ34NQL-C5o4mnnX.js} +1 -2
  57. package/resources/fe-dist/assets/ordinal-BENe2yWM.js +0 -1
  58. package/resources/fe-dist/assets/{pieDiagram-4H26LBE5--KxRgbWG.js → pieDiagram-4H26LBE5-2ss3HotB.js} +1 -2
  59. package/resources/fe-dist/assets/{quadrantDiagram-W4KKPZXB-DqXsCI9E.js → quadrantDiagram-W4KKPZXB-Bolcfx55.js} +1 -2
  60. package/resources/fe-dist/assets/{requirementDiagram-4Y6WPE33-C2KeTWqB.js → requirementDiagram-4Y6WPE33-B-atSHRR.js} +1 -2
  61. package/resources/fe-dist/assets/{sankeyDiagram-5OEKKPKP-CC5tcr7d.js → sankeyDiagram-5OEKKPKP-ByynRcAV.js} +1 -2
  62. package/resources/fe-dist/assets/selection-clipboard-Dq6Zemfd.js +1 -0
  63. package/resources/fe-dist/assets/send-D0P5wgcD.js +11 -0
  64. package/resources/fe-dist/assets/{sequenceDiagram-3UESZ5HK-CqlMDtOG.js → sequenceDiagram-3UESZ5HK-CNC9IxV4.js} +1 -2
  65. package/resources/fe-dist/assets/{stateDiagram-AJRCARHV-CVcrlBiw.js → stateDiagram-AJRCARHV-CqzYDImp.js} +1 -2
  66. package/resources/fe-dist/assets/stateDiagram-v2-BHNVJYJU-C3azxyWE.js +1 -0
  67. package/resources/fe-dist/assets/{terminal-settings-panel-CYV9svzz.js → terminal-settings-panel-cjw9P_wY.js} +6 -7
  68. package/resources/fe-dist/assets/{timeline-definition-PNZ67QCA-CEBskfkL.js → timeline-definition-PNZ67QCA-C38KIECs.js} +1 -2
  69. package/resources/fe-dist/assets/transfer-toast-5tb-zYhi.js +6 -0
  70. package/resources/fe-dist/assets/triangle-alert-CQzql6mY.js +6 -0
  71. package/resources/fe-dist/assets/{vennDiagram-CIIHVFJN-CrxyVTtu.js → vennDiagram-CIIHVFJN-BJudKfzJ.js} +1 -2
  72. package/resources/fe-dist/assets/{wardley-L42UT6IY-CSmsRb8z.js → wardley-L42UT6IY-B6p_luMy.js} +1 -2
  73. package/resources/fe-dist/assets/{wardleyDiagram-YWT4CUSO-DoDzNeOA.js → wardleyDiagram-YWT4CUSO-DZJeOiWQ.js} +1 -2
  74. package/resources/fe-dist/assets/{xychartDiagram-2RQKCTM6-Dbq_OKsv.js → xychartDiagram-2RQKCTM6-DOMCute2.js} +1 -2
  75. package/resources/fe-dist/assets/zap-DWNtdoic.js +6 -0
  76. package/resources/fe-dist/assets/zh_CN-D10j4J8Y.js +1 -0
  77. package/resources/fe-dist/index.html +2 -2
  78. package/resources/gateway-drizzle/0010_lucky_kabuki.sql +32 -0
  79. package/resources/gateway-drizzle/meta/_journal.json +7 -0
  80. package/resources/fe-dist/assets/DevicePage-C4JS6baR.js +0 -15
  81. package/resources/fe-dist/assets/DevicesPage-p0CfWP1T.js +0 -2
  82. package/resources/fe-dist/assets/SettingsPage-Cq6n8Xjc.js +0 -27
  83. package/resources/fe-dist/assets/channel-DDiDKfY0.js +0 -2
  84. package/resources/fe-dist/assets/classDiagram-4FO5ZUOK-CXFjdzka.js +0 -2
  85. package/resources/fe-dist/assets/classDiagram-v2-Q7XG4LA2-CXFjdzka.js +0 -2
  86. package/resources/fe-dist/assets/index-CXIj7HZi.css +0 -1
  87. package/resources/fe-dist/assets/index-CYmVLCAD.js +0 -488
  88. package/resources/fe-dist/assets/stateDiagram-v2-BHNVJYJU-B0BDuZBM.js +0 -2
@@ -22745,6 +22745,8 @@ var I18N_RESOURCES = {
22745
22745
  enableBrowserNotificationToast: "Enable Browser Notification Toast",
22746
22746
  enableTelegramBellPush: "Enable Telegram Bell Push",
22747
22747
  enableTelegramNotificationPush: "Enable Telegram Notification Push",
22748
+ enableWeixinBellPush: "Enable WeChat Bell Push",
22749
+ enableWeixinNotificationPush: "Enable WeChat Notification Push",
22748
22750
  sshReconnectRetries: "SSH Reconnect Retries",
22749
22751
  sshReconnectDelay: "SSH Reconnect Delay (seconds)",
22750
22752
  language: "Language",
@@ -22984,6 +22986,81 @@ Time: {{time}}`,
22984
22986
  botNotFound: "Bot not found or unavailable",
22985
22987
  botNotRunning: "Bot is not running or unavailable"
22986
22988
  },
22989
+ weixin: {
22990
+ title: "WeChat (ClawBot) Management",
22991
+ subtitle: "Push tmex alerts to your personal WeChat via the iLink bot protocol.",
22992
+ replyOnlyNotice: "iLink can only reply inside an active conversation window \u2014 message the bot first to activate; alerts then reuse that session. If it goes quiet too long the session expires; message the bot again to re-activate.",
22993
+ accountName: "Account Name",
22994
+ accountNamePlaceholder: "e.g., my-wechat",
22995
+ addAccount: "Add Account",
22996
+ editAccount: "Edit Account",
22997
+ enableAccount: "Enable Account",
22998
+ allowAuthRequests: "Allow Authorization Requests",
22999
+ scanToLogin: "Scan to Log In",
23000
+ relogin: "Re-login (Re-scan)",
23001
+ loggedIn: "Logged in",
23002
+ notLoggedIn: "Not logged in",
23003
+ scanQrcodeHint: "Open WeChat, scan the QR code, and confirm on your phone.",
23004
+ loginPending: "Waiting for scan\u2026",
23005
+ loginConfirmed: "Login confirmed.",
23006
+ loginExpired: "QR code expired. Please retry.",
23007
+ loginError: "Login failed: {{message}}",
23008
+ loginFailed: "Login failed",
23009
+ refreshQrcode: "Refresh QR Code",
23010
+ closeLogin: "Close",
23011
+ pendingUsers: "Pending Authorization",
23012
+ authorizedUsers: "Authorized Users",
23013
+ noPendingUsers: "No pending users",
23014
+ noAuthorizedUsers: "No authorized users",
23015
+ needsReactivation: "Session expired",
23016
+ reactivationHint: "Ask this user to send the bot any message to re-activate alerts.",
23017
+ approve: "Approve",
23018
+ revokeAuth: "Revoke",
23019
+ removeUser: "Remove",
23020
+ testMessage: "Test Message",
23021
+ sendTestMessage: "Send Test Message",
23022
+ deleteAccount: "Delete Account",
23023
+ accountCreated: "Account created",
23024
+ accountUpdated: "Account updated",
23025
+ accountDeleted: "Account deleted",
23026
+ authApproved: "Authorization approved",
23027
+ userRemoved: "User removed",
23028
+ testMessageSent: "Test message sent",
23029
+ createFailed: "Failed to create account",
23030
+ updateFailed: "Failed to update account",
23031
+ deleteFailed: "Failed to delete account",
23032
+ approveFailed: "Failed to approve",
23033
+ removeFailed: "Failed to remove user",
23034
+ testMessageFailed: "Failed to send test message",
23035
+ loadAccountsFailed: "Failed to load accounts",
23036
+ loadUsersFailed: "Failed to load users",
23037
+ noAccounts: "No accounts yet, add one first.",
23038
+ userCount: "{{authorized}} authorized / {{pending}} pending (max 16)",
23039
+ userId: "WeChat User ID",
23040
+ applyTime: "First Contact",
23041
+ expand: "Expand",
23042
+ collapse: "Collapse",
23043
+ accountNameRequired: "Account name is required",
23044
+ accountNotFound: "Account not found or unavailable",
23045
+ accountNotRunning: "Account is not logged in or not running",
23046
+ userNotFound: "User not found",
23047
+ authSuccess: "\u2705 Authorized. You will now receive notifications.",
23048
+ authPending: "\u23F3 Authorization request received. Please approve in tmex settings.",
23049
+ testMessageTemplate: `\uD83E\uDDEA Test Message
23050
+ Site: {{siteName}}
23051
+ Time: {{time}}`,
23052
+ approveMessageTemplate: `\u2705 Authorized via tmex.
23053
+ Account: {{accountName}}
23054
+ Time: {{time}}`,
23055
+ keepalivePrompt: "\u3010tmex\u3011To keep notifications flowing, just reply with anything to keep the session alive \uD83D\uDE4F",
23056
+ scanConfirmedSendHint: "Scan confirmed! Now send any message to this bot in WeChat to finish binding.",
23057
+ bindingInProgress: "Message received, finishing binding\u2026",
23058
+ bindSuccess: "WeChat bound. You will now receive notifications.",
23059
+ bound: "Bound",
23060
+ unbound: "Not bound",
23061
+ bindAction: "Bind (scan)",
23062
+ gatewayOnline: "\uD83D\uDFE2 tmex online @ {{siteName}}"
23063
+ },
22987
23064
  webhook: {
22988
23065
  title: "Webhooks",
22989
23066
  url: "Webhook URL",
@@ -23659,6 +23736,8 @@ Time: {{time}}`,
23659
23736
  enableBrowserNotificationToast: "\u5F00\u542F\u6D4F\u89C8\u5668\u901A\u77E5 Toast",
23660
23737
  enableTelegramBellPush: "\u5F00\u542F Telegram Bell \u63A8\u9001",
23661
23738
  enableTelegramNotificationPush: "\u5F00\u542F Telegram \u901A\u77E5\u63A8\u9001",
23739
+ enableWeixinBellPush: "\u542F\u7528\u5FAE\u4FE1\u54CD\u94C3\u63A8\u9001",
23740
+ enableWeixinNotificationPush: "\u542F\u7528\u5FAE\u4FE1\u901A\u77E5\u63A8\u9001",
23662
23741
  sshReconnectRetries: "SSH \u91CD\u8FDE\u6B21\u6570",
23663
23742
  sshReconnectDelay: "SSH \u91CD\u8FDE\u7B49\u5F85\uFF08\u79D2\uFF09",
23664
23743
  language: "\u8BED\u8A00",
@@ -23898,6 +23977,81 @@ Bot\uFF1A{{botName}}
23898
23977
  botNotFound: "Bot \u672A\u542F\u52A8\u6216\u4E0D\u53EF\u7528",
23899
23978
  botNotRunning: "Bot \u672A\u542F\u52A8\u6216\u4E0D\u53EF\u7528"
23900
23979
  },
23980
+ weixin: {
23981
+ title: "\u5FAE\u4FE1 (ClawBot) \u7BA1\u7406",
23982
+ subtitle: "\u901A\u8FC7 iLink bot \u534F\u8BAE\u628A tmex \u544A\u8B66\u63A8\u9001\u5230\u4F60\u7684\u4E2A\u4EBA\u5FAE\u4FE1\u3002",
23983
+ replyOnlyNotice: "iLink \u53EA\u80FD\u5728\u4F1A\u8BDD\u6FC0\u6D3B\u7A97\u53E3\u5185\u56DE\u590D\u2014\u2014\u5148\u7ED9 bot \u53D1\u6761\u6D88\u606F\u6FC0\u6D3B\uFF0C\u544A\u8B66\u968F\u540E\u590D\u7528\u8BE5\u4F1A\u8BDD\uFF1B\u95F2\u7F6E\u8FC7\u4E45\u4F1A\u8BDD\u4F1A\u8FC7\u671F\uFF0C\u9700\u518D\u7ED9 bot \u53D1\u6761\u6D88\u606F\u91CD\u65B0\u6FC0\u6D3B\u3002",
23984
+ accountName: "\u8D26\u53F7\u540D\u79F0",
23985
+ accountNamePlaceholder: "\u5982\uFF1A\u6211\u7684\u5FAE\u4FE1",
23986
+ addAccount: "\u6DFB\u52A0\u8D26\u53F7",
23987
+ editAccount: "\u7F16\u8F91\u8D26\u53F7",
23988
+ enableAccount: "\u542F\u7528\u8D26\u53F7",
23989
+ allowAuthRequests: "\u5141\u8BB8\u6388\u6743\u7533\u8BF7",
23990
+ scanToLogin: "\u626B\u7801\u767B\u5F55",
23991
+ relogin: "\u91CD\u65B0\u767B\u5F55\uFF08\u91CD\u65B0\u626B\u7801\uFF09",
23992
+ loggedIn: "\u5DF2\u767B\u5F55",
23993
+ notLoggedIn: "\u672A\u767B\u5F55",
23994
+ scanQrcodeHint: "\u6253\u5F00\u5FAE\u4FE1\u626B\u63CF\u4E8C\u7EF4\u7801\uFF0C\u5E76\u5728\u624B\u673A\u4E0A\u786E\u8BA4\u3002",
23995
+ loginPending: "\u7B49\u5F85\u626B\u7801\u2026",
23996
+ loginConfirmed: "\u767B\u5F55\u5DF2\u786E\u8BA4\u3002",
23997
+ loginExpired: "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u8BD5\u3002",
23998
+ loginError: "\u767B\u5F55\u5931\u8D25\uFF1A{{message}}",
23999
+ loginFailed: "\u767B\u5F55\u5931\u8D25",
24000
+ refreshQrcode: "\u5237\u65B0\u4E8C\u7EF4\u7801",
24001
+ closeLogin: "\u5173\u95ED",
24002
+ pendingUsers: "\u5F85\u6388\u6743",
24003
+ authorizedUsers: "\u5DF2\u6388\u6743\u7528\u6237",
24004
+ noPendingUsers: "\u6682\u65E0\u5F85\u6388\u6743\u7528\u6237",
24005
+ noAuthorizedUsers: "\u6682\u65E0\u5DF2\u6388\u6743\u7528\u6237",
24006
+ needsReactivation: "\u4F1A\u8BDD\u5DF2\u8FC7\u671F",
24007
+ reactivationHint: "\u8BF7\u8BE5\u7528\u6237\u7ED9 bot \u53D1\u6761\u6D88\u606F\u4EE5\u91CD\u65B0\u6FC0\u6D3B\u544A\u8B66\u3002",
24008
+ approve: "\u6279\u51C6",
24009
+ revokeAuth: "\u64A4\u9500\u6388\u6743",
24010
+ removeUser: "\u79FB\u9664",
24011
+ testMessage: "\u6D4B\u8BD5\u6D88\u606F",
24012
+ sendTestMessage: "\u53D1\u9001\u6D4B\u8BD5\u6D88\u606F",
24013
+ deleteAccount: "\u5220\u9664\u8D26\u53F7",
24014
+ accountCreated: "\u8D26\u53F7\u5DF2\u521B\u5EFA",
24015
+ accountUpdated: "\u8D26\u53F7\u5DF2\u66F4\u65B0",
24016
+ accountDeleted: "\u8D26\u53F7\u5DF2\u5220\u9664",
24017
+ authApproved: "\u6388\u6743\u5DF2\u6279\u51C6",
24018
+ userRemoved: "\u7528\u6237\u5DF2\u79FB\u9664",
24019
+ testMessageSent: "\u6D4B\u8BD5\u6D88\u606F\u5DF2\u53D1\u9001",
24020
+ createFailed: "\u521B\u5EFA\u8D26\u53F7\u5931\u8D25",
24021
+ updateFailed: "\u66F4\u65B0\u8D26\u53F7\u5931\u8D25",
24022
+ deleteFailed: "\u5220\u9664\u8D26\u53F7\u5931\u8D25",
24023
+ approveFailed: "\u6279\u51C6\u5931\u8D25",
24024
+ removeFailed: "\u79FB\u9664\u7528\u6237\u5931\u8D25",
24025
+ testMessageFailed: "\u53D1\u9001\u6D4B\u8BD5\u6D88\u606F\u5931\u8D25",
24026
+ loadAccountsFailed: "\u52A0\u8F7D\u8D26\u53F7\u5217\u8868\u5931\u8D25",
24027
+ loadUsersFailed: "\u52A0\u8F7D\u7528\u6237\u5217\u8868\u5931\u8D25",
24028
+ noAccounts: "\u8FD8\u6CA1\u6709\u8D26\u53F7\uFF0C\u5148\u6DFB\u52A0\u4E00\u4E2A\u3002",
24029
+ userCount: "{{authorized}} \u5DF2\u6388\u6743 / {{pending}} \u5F85\u6388\u6743\uFF08\u4E0A\u9650 16\uFF09",
24030
+ userId: "\u5FAE\u4FE1\u7528\u6237 ID",
24031
+ applyTime: "\u9996\u6B21\u8054\u7CFB\u65F6\u95F4",
24032
+ expand: "\u5C55\u5F00",
24033
+ collapse: "\u6536\u8D77",
24034
+ accountNameRequired: "\u8D26\u53F7\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A",
24035
+ accountNotFound: "\u8D26\u53F7\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u7528",
24036
+ accountNotRunning: "\u8D26\u53F7\u672A\u767B\u5F55\u6216\u672A\u8FD0\u884C",
24037
+ userNotFound: "\u7528\u6237\u4E0D\u5B58\u5728",
24038
+ authSuccess: "\u2705 \u5DF2\u6388\u6743\uFF0C\u4F60\u5C06\u5F00\u59CB\u6536\u5230\u901A\u77E5\u3002",
24039
+ authPending: "\u23F3 \u5DF2\u6536\u5230\u6388\u6743\u7533\u8BF7\uFF0C\u8BF7\u5728 tmex \u8BBE\u7F6E\u4E2D\u6279\u51C6\u3002",
24040
+ testMessageTemplate: `\uD83E\uDDEA \u6D4B\u8BD5\u6D88\u606F
24041
+ \u7AD9\u70B9\uFF1A{{siteName}}
24042
+ \u65F6\u95F4\uFF1A{{time}}`,
24043
+ approveMessageTemplate: `\u2705 \u5DF2\u901A\u8FC7 tmex \u6388\u6743\u3002
24044
+ \u8D26\u53F7\uFF1A{{accountName}}
24045
+ \u65F6\u95F4\uFF1A{{time}}`,
24046
+ keepalivePrompt: "\u3010tmex\u3011\u4E3A\u786E\u4FDD\u4F60\u80FD\u6B63\u5E38\u6536\u5230\u901A\u77E5\uFF0C\u8BF7\u56DE\u590D\u4EFB\u610F\u5185\u5BB9\u5373\u53EF\u4FDD\u6301\u8FDE\u63A5 \uD83D\uDE4F",
24047
+ scanConfirmedSendHint: "\u626B\u7801\u6210\u529F\uFF01\u8BF7\u5728\u5FAE\u4FE1\u91CC\u7ED9\u8BE5 bot \u53D1\u9001\u4EFB\u610F\u4E00\u6761\u6D88\u606F\u4EE5\u5B8C\u6210\u7ED1\u5B9A\u3002",
24048
+ bindingInProgress: "\u5DF2\u6536\u5230\u6D88\u606F\uFF0C\u6B63\u5728\u5B8C\u6210\u7ED1\u5B9A\u2026",
24049
+ bindSuccess: "\u5FAE\u4FE1\u5DF2\u7ED1\u5B9A\uFF0C\u5F00\u59CB\u63A5\u6536\u901A\u77E5\u3002",
24050
+ bound: "\u5DF2\u7ED1\u5B9A",
24051
+ unbound: "\u672A\u7ED1\u5B9A",
24052
+ bindAction: "\u626B\u7801\u7ED1\u5B9A",
24053
+ gatewayOnline: "\uD83D\uDFE2 tmex \u5DF2\u4E0A\u7EBF @ {{siteName}}"
24054
+ },
23901
24055
  webhook: {
23902
24056
  title: "Webhooks",
23903
24057
  url: "Webhook \u5730\u5740",
@@ -24573,6 +24727,8 @@ Bot\uFF1A{{botName}}
24573
24727
  enableBrowserNotificationToast: "\u30D6\u30E9\u30A6\u30B6\u901A\u77E5 Toast \u3092\u6709\u52B9\u306B\u3059\u308B",
24574
24728
  enableTelegramBellPush: "Telegram \u30D9\u30EB\u30D7\u30C3\u30B7\u30E5\u3092\u6709\u52B9\u306B\u3059\u308B",
24575
24729
  enableTelegramNotificationPush: "Telegram \u901A\u77E5\u30D7\u30C3\u30B7\u30E5\u3092\u6709\u52B9\u306B\u3059\u308B",
24730
+ enableWeixinBellPush: "WeChat \u30D9\u30EB\u901A\u77E5\u3092\u6709\u52B9\u5316",
24731
+ enableWeixinNotificationPush: "WeChat \u901A\u77E5\u30D7\u30C3\u30B7\u30E5\u3092\u6709\u52B9\u5316",
24576
24732
  sshReconnectRetries: "SSH \u518D\u63A5\u7D9A\u8A66\u884C\u56DE\u6570",
24577
24733
  sshReconnectDelay: "SSH \u518D\u63A5\u7D9A\u5F85\u6A5F\uFF08\u79D2\uFF09",
24578
24734
  language: "\u8A00\u8A9E",
@@ -24812,6 +24968,81 @@ Bot\uFF1A{{botName}}
24812
24968
  botNotFound: "Bot \u304C\u898B\u3064\u304B\u3089\u306A\u3044\u304B\u5229\u7528\u3067\u304D\u307E\u305B\u3093",
24813
24969
  botNotRunning: "Bot \u304C\u8D77\u52D5\u3057\u3066\u3044\u306A\u3044\u304B\u5229\u7528\u3067\u304D\u307E\u305B\u3093"
24814
24970
  },
24971
+ weixin: {
24972
+ title: "WeChat (ClawBot) \u7BA1\u7406",
24973
+ subtitle: "iLink bot \u30D7\u30ED\u30C8\u30B3\u30EB\u3067 tmex \u306E\u30A2\u30E9\u30FC\u30C8\u3092\u500B\u4EBA WeChat \u3078\u9001\u4FE1\u3057\u307E\u3059\u3002",
24974
+ replyOnlyNotice: "iLink \u306F\u30A2\u30AF\u30C6\u30A3\u30D6\u306A\u4F1A\u8A71\u30A6\u30A3\u30F3\u30C9\u30A6\u5185\u3067\u306E\u307F\u8FD4\u4FE1\u3067\u304D\u307E\u3059\u3002\u307E\u305A bot \u306B\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u9001\u3063\u3066\u6709\u52B9\u5316\u3059\u308B\u3068\u3001\u4EE5\u964D\u306E\u30A2\u30E9\u30FC\u30C8\u306F\u305D\u306E\u30BB\u30C3\u30B7\u30E7\u30F3\u3092\u518D\u5229\u7528\u3057\u307E\u3059\u3002\u4E00\u5B9A\u6642\u9593\u64CD\u4F5C\u304C\u306A\u3044\u3068\u30BB\u30C3\u30B7\u30E7\u30F3\u306F\u5931\u52B9\u3059\u308B\u305F\u3081\u3001\u518D\u5EA6 bot \u306B\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u9001\u3063\u3066\u518D\u6709\u52B9\u5316\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
24975
+ accountName: "\u30A2\u30AB\u30A6\u30F3\u30C8\u540D",
24976
+ accountNamePlaceholder: "\u4F8B\uFF1Amy-wechat",
24977
+ addAccount: "\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u8FFD\u52A0",
24978
+ editAccount: "\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u7DE8\u96C6",
24979
+ enableAccount: "\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u6709\u52B9\u5316",
24980
+ allowAuthRequests: "\u8A8D\u8A3C\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u8A31\u53EF",
24981
+ scanToLogin: "\u30B9\u30AD\u30E3\u30F3\u3057\u3066\u30ED\u30B0\u30A4\u30F3",
24982
+ relogin: "\u518D\u30ED\u30B0\u30A4\u30F3\uFF08\u518D\u30B9\u30AD\u30E3\u30F3\uFF09",
24983
+ loggedIn: "\u30ED\u30B0\u30A4\u30F3\u6E08\u307F",
24984
+ notLoggedIn: "\u672A\u30ED\u30B0\u30A4\u30F3",
24985
+ scanQrcodeHint: "WeChat \u3092\u958B\u3044\u3066 QR \u30B3\u30FC\u30C9\u3092\u30B9\u30AD\u30E3\u30F3\u3057\u3001\u30B9\u30DE\u30DB\u3067\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
24986
+ loginPending: "\u30B9\u30AD\u30E3\u30F3\u5F85\u3061\u2026",
24987
+ loginConfirmed: "\u30ED\u30B0\u30A4\u30F3\u304C\u78BA\u8A8D\u3055\u308C\u307E\u3057\u305F\u3002",
24988
+ loginExpired: "QR \u30B3\u30FC\u30C9\u306E\u6709\u52B9\u671F\u9650\u304C\u5207\u308C\u307E\u3057\u305F\u3002\u518D\u8A66\u884C\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
24989
+ loginError: "\u30ED\u30B0\u30A4\u30F3\u5931\u6557\uFF1A{{message}}",
24990
+ loginFailed: "\u30ED\u30B0\u30A4\u30F3\u5931\u6557",
24991
+ refreshQrcode: "QR \u30B3\u30FC\u30C9\u3092\u66F4\u65B0",
24992
+ closeLogin: "\u9589\u3058\u308B",
24993
+ pendingUsers: "\u627F\u8A8D\u5F85\u3061",
24994
+ authorizedUsers: "\u627F\u8A8D\u6E08\u307F\u30E6\u30FC\u30B6\u30FC",
24995
+ noPendingUsers: "\u627F\u8A8D\u5F85\u3061\u306E\u30E6\u30FC\u30B6\u30FC\u306F\u3044\u307E\u305B\u3093",
24996
+ noAuthorizedUsers: "\u627F\u8A8D\u6E08\u307F\u306E\u30E6\u30FC\u30B6\u30FC\u306F\u3044\u307E\u305B\u3093",
24997
+ needsReactivation: "\u30BB\u30C3\u30B7\u30E7\u30F3\u5931\u52B9",
24998
+ reactivationHint: "\u3053\u306E\u30E6\u30FC\u30B6\u30FC\u306B bot \u3078\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u9001\u3063\u3066\u3082\u3089\u3044\u3001\u30A2\u30E9\u30FC\u30C8\u3092\u518D\u6709\u52B9\u5316\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
24999
+ approve: "\u627F\u8A8D",
25000
+ revokeAuth: "\u627F\u8A8D\u3092\u53D6\u308A\u6D88\u3059",
25001
+ removeUser: "\u524A\u9664",
25002
+ testMessage: "\u30C6\u30B9\u30C8\u30E1\u30C3\u30BB\u30FC\u30B8",
25003
+ sendTestMessage: "\u30C6\u30B9\u30C8\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u9001\u4FE1",
25004
+ deleteAccount: "\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u524A\u9664",
25005
+ accountCreated: "\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u4F5C\u6210\u3057\u307E\u3057\u305F",
25006
+ accountUpdated: "\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u66F4\u65B0\u3057\u307E\u3057\u305F",
25007
+ accountDeleted: "\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u524A\u9664\u3057\u307E\u3057\u305F",
25008
+ authApproved: "\u8A8D\u8A3C\u3092\u627F\u8A8D\u3057\u307E\u3057\u305F",
25009
+ userRemoved: "\u30E6\u30FC\u30B6\u30FC\u3092\u524A\u9664\u3057\u307E\u3057\u305F",
25010
+ testMessageSent: "\u30C6\u30B9\u30C8\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u9001\u4FE1\u3057\u307E\u3057\u305F",
25011
+ createFailed: "\u30A2\u30AB\u30A6\u30F3\u30C8\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25012
+ updateFailed: "\u30A2\u30AB\u30A6\u30F3\u30C8\u306E\u66F4\u65B0\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25013
+ deleteFailed: "\u30A2\u30AB\u30A6\u30F3\u30C8\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25014
+ approveFailed: "\u627F\u8A8D\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25015
+ removeFailed: "\u30E6\u30FC\u30B6\u30FC\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25016
+ testMessageFailed: "\u30C6\u30B9\u30C8\u30E1\u30C3\u30BB\u30FC\u30B8\u306E\u9001\u4FE1\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25017
+ loadAccountsFailed: "\u30A2\u30AB\u30A6\u30F3\u30C8\u4E00\u89A7\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25018
+ loadUsersFailed: "\u30E6\u30FC\u30B6\u30FC\u4E00\u89A7\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25019
+ noAccounts: "\u30A2\u30AB\u30A6\u30F3\u30C8\u304C\u307E\u3060\u3042\u308A\u307E\u305B\u3093\u3002\u307E\u305A\u8FFD\u52A0\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
25020
+ userCount: "{{authorized}} \u627F\u8A8D\u6E08\u307F / {{pending}} \u627F\u8A8D\u5F85\u3061\uFF08\u6700\u5927 16\uFF09",
25021
+ userId: "WeChat \u30E6\u30FC\u30B6\u30FC ID",
25022
+ applyTime: "\u521D\u56DE\u9023\u7D61",
25023
+ expand: "\u5C55\u958B",
25024
+ collapse: "\u6298\u308A\u305F\u305F\u3080",
25025
+ accountNameRequired: "\u30A2\u30AB\u30A6\u30F3\u30C8\u540D\u306F\u5FC5\u9808\u3067\u3059",
25026
+ accountNotFound: "\u30A2\u30AB\u30A6\u30F3\u30C8\u304C\u898B\u3064\u304B\u3089\u306A\u3044\u304B\u5229\u7528\u3067\u304D\u307E\u305B\u3093",
25027
+ accountNotRunning: "\u30A2\u30AB\u30A6\u30F3\u30C8\u304C\u672A\u30ED\u30B0\u30A4\u30F3\u307E\u305F\u306F\u672A\u8D77\u52D5\u3067\u3059",
25028
+ userNotFound: "\u30E6\u30FC\u30B6\u30FC\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093",
25029
+ authSuccess: "\u2705 \u8A8D\u8A3C\u3055\u308C\u307E\u3057\u305F\u3002\u901A\u77E5\u3092\u53D7\u3051\u53D6\u308C\u307E\u3059\u3002",
25030
+ authPending: "\u23F3 \u8A8D\u8A3C\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u53D7\u3051\u53D6\u308A\u307E\u3057\u305F\u3002tmex \u306E\u8A2D\u5B9A\u3067\u627F\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
25031
+ testMessageTemplate: `\uD83E\uDDEA \u30C6\u30B9\u30C8\u30E1\u30C3\u30BB\u30FC\u30B8
25032
+ \u30B5\u30A4\u30C8\uFF1A{{siteName}}
25033
+ \u6642\u523B\uFF1A{{time}}`,
25034
+ approveMessageTemplate: `\u2705 tmex \u7D4C\u7531\u3067\u8A8D\u8A3C\u3057\u307E\u3057\u305F\u3002
25035
+ \u30A2\u30AB\u30A6\u30F3\u30C8\uFF1A{{accountName}}
25036
+ \u6642\u523B\uFF1A{{time}}`,
25037
+ keepalivePrompt: "\u3010tmex\u3011\u901A\u77E5\u3092\u78BA\u5B9F\u306B\u53D7\u3051\u53D6\u308B\u305F\u3081\u3001\u4EFB\u610F\u306E\u5185\u5BB9\u3092\u8FD4\u4FE1\u3057\u3066\u63A5\u7D9A\u3092\u7DAD\u6301\u3057\u3066\u304F\u3060\u3055\u3044 \uD83D\uDE4F",
25038
+ scanConfirmedSendHint: "\u30B9\u30AD\u30E3\u30F3\u5B8C\u4E86\uFF01WeChat \u3067\u3053\u306E bot \u306B\u4EFB\u610F\u306E\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u9001\u4FE1\u3057\u3066\u7D10\u4ED8\u3051\u3092\u5B8C\u4E86\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
25039
+ bindingInProgress: "\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u53D7\u4FE1\u3001\u7D10\u4ED8\u3051\u3092\u5B8C\u4E86\u3057\u3066\u3044\u307E\u3059\u2026",
25040
+ bindSuccess: "WeChat \u3092\u7D10\u4ED8\u3051\u307E\u3057\u305F\u3002\u901A\u77E5\u3092\u53D7\u3051\u53D6\u308C\u307E\u3059\u3002",
25041
+ bound: "\u7D10\u4ED8\u3051\u6E08\u307F",
25042
+ unbound: "\u672A\u7D10\u4ED8\u3051",
25043
+ bindAction: "\u30B9\u30AD\u30E3\u30F3\u3057\u3066\u7D10\u4ED8\u3051",
25044
+ gatewayOnline: "\uD83D\uDFE2 tmex \u30AA\u30F3\u30E9\u30A4\u30F3 @ {{siteName}}"
25045
+ },
24815
25046
  webhook: {
24816
25047
  title: "Webhooks",
24817
25048
  url: "Webhook URL",
@@ -32855,6 +33086,8 @@ function drizzle(...params) {
32855
33086
  // ../../apps/gateway/src/db/schema.ts
32856
33087
  var exports_schema2 = {};
32857
33088
  __export(exports_schema2, {
33089
+ weixinAccounts: () => weixinAccounts,
33090
+ weixinAccountUsers: () => weixinAccountUsers,
32858
33091
  webhookEndpoints: () => webhookEndpoints,
32859
33092
  watchRules: () => watchRules,
32860
33093
  watchRuleState: () => watchRuleState,
@@ -32887,6 +33120,10 @@ var siteSettings = sqliteTable("site_settings", {
32887
33120
  enableTelegramNotificationPush: integer("enable_telegram_notification_push", {
32888
33121
  mode: "boolean"
32889
33122
  }).notNull().default(true),
33123
+ enableWeixinBellPush: integer("enable_weixin_bell_push", { mode: "boolean" }).notNull().default(false),
33124
+ enableWeixinNotificationPush: integer("enable_weixin_notification_push", {
33125
+ mode: "boolean"
33126
+ }).notNull().default(false),
32890
33127
  sshReconnectMaxRetries: integer("ssh_reconnect_max_retries").notNull(),
32891
33128
  sshReconnectDelaySeconds: integer("ssh_reconnect_delay_seconds").notNull(),
32892
33129
  language: text("language").notNull().default("en_US"),
@@ -33091,6 +33328,34 @@ var telegramBotChats = sqliteTable("telegram_bot_chats", {
33091
33328
  check("telegram_bot_chats_status_check", sql`${table.status} in ('pending', 'authorized')`),
33092
33329
  check("telegram_bot_chats_chat_type_check", sql`${table.chatType} in ('private', 'group', 'supergroup', 'channel', 'unknown')`)
33093
33330
  ]);
33331
+ var weixinAccounts = sqliteTable("weixin_accounts", {
33332
+ id: text("id").primaryKey(),
33333
+ name: text("name").notNull(),
33334
+ enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
33335
+ allowAuthRequests: integer("allow_auth_requests", { mode: "boolean" }).notNull().default(true),
33336
+ weixinUin: text("weixin_uin"),
33337
+ botTokenEnc: text("bot_token_enc"),
33338
+ baseUrl: text("base_url"),
33339
+ syncBuf: text("sync_buf"),
33340
+ createdAt: text("created_at").notNull(),
33341
+ updatedAt: text("updated_at").notNull()
33342
+ });
33343
+ var weixinAccountUsers = sqliteTable("weixin_account_users", {
33344
+ id: text("id").primaryKey(),
33345
+ accountId: text("account_id").notNull().references(() => weixinAccounts.id, { onDelete: "cascade" }),
33346
+ userId: text("user_id").notNull(),
33347
+ displayName: text("display_name").notNull(),
33348
+ status: text("status").notNull(),
33349
+ lastContextToken: text("last_context_token"),
33350
+ lastInboundAt: text("last_inbound_at"),
33351
+ needsReactivation: integer("needs_reactivation", { mode: "boolean" }).notNull().default(false),
33352
+ appliedAt: text("applied_at").notNull(),
33353
+ authorizedAt: text("authorized_at"),
33354
+ updatedAt: text("updated_at").notNull()
33355
+ }, (table) => [
33356
+ unique("weixin_account_users_account_user_unique").on(table.accountId, table.userId),
33357
+ check("weixin_account_users_status_check", sql`${table.status} in ('pending', 'authorized')`)
33358
+ ]);
33094
33359
 
33095
33360
  // ../../apps/gateway/src/db/client.ts
33096
33361
  var sqliteClient = null;
@@ -33151,6 +33416,8 @@ function toSiteSettings(row) {
33151
33416
  enableBrowserNotificationToast: row.enableBrowserNotificationToast,
33152
33417
  enableTelegramBellPush: row.enableTelegramBellPush,
33153
33418
  enableTelegramNotificationPush: row.enableTelegramNotificationPush,
33419
+ enableWeixinBellPush: row.enableWeixinBellPush,
33420
+ enableWeixinNotificationPush: row.enableWeixinNotificationPush,
33154
33421
  sshReconnectMaxRetries: row.sshReconnectMaxRetries,
33155
33422
  sshReconnectDelaySeconds: row.sshReconnectDelaySeconds,
33156
33423
  language: normalizeLocale(row.language),
@@ -33182,6 +33449,35 @@ function toTelegramChat(row) {
33182
33449
  updatedAt: row.updatedAt
33183
33450
  };
33184
33451
  }
33452
+ function toWeixinAccountRecord(row) {
33453
+ return {
33454
+ id: row.id,
33455
+ name: row.name,
33456
+ enabled: row.enabled,
33457
+ allowAuthRequests: row.allowAuthRequests,
33458
+ loggedIn: row.botTokenEnc != null,
33459
+ weixinUin: row.weixinUin ?? null,
33460
+ botTokenEnc: row.botTokenEnc ?? null,
33461
+ baseUrl: row.baseUrl ?? null,
33462
+ syncBuf: row.syncBuf ?? null,
33463
+ createdAt: row.createdAt,
33464
+ updatedAt: row.updatedAt
33465
+ };
33466
+ }
33467
+ function toWeixinAccountUser(row) {
33468
+ return {
33469
+ id: row.id,
33470
+ accountId: row.accountId,
33471
+ userId: row.userId,
33472
+ displayName: row.displayName,
33473
+ status: row.status,
33474
+ needsReactivation: row.needsReactivation,
33475
+ lastInboundAt: row.lastInboundAt ?? null,
33476
+ appliedAt: row.appliedAt,
33477
+ authorizedAt: row.authorizedAt ?? null,
33478
+ updatedAt: row.updatedAt
33479
+ };
33480
+ }
33185
33481
  function toWebhookEndpoint(row) {
33186
33482
  return {
33187
33483
  id: row.id,
@@ -33206,6 +33502,8 @@ function ensureSiteSettingsInitialized() {
33206
33502
  enableBrowserNotificationToast: true,
33207
33503
  enableTelegramBellPush: true,
33208
33504
  enableTelegramNotificationPush: true,
33505
+ enableWeixinBellPush: false,
33506
+ enableWeixinNotificationPush: false,
33209
33507
  sshReconnectMaxRetries: config.sshReconnectMaxRetriesDefault,
33210
33508
  sshReconnectDelaySeconds: config.sshReconnectDelaySecondsDefault,
33211
33509
  language: normalizeLocale(config.languageDefault),
@@ -33402,6 +33700,8 @@ function updateSiteSettings(updates) {
33402
33700
  enableBrowserNotificationToast: updates.enableBrowserNotificationToast ?? current.enableBrowserNotificationToast,
33403
33701
  enableTelegramBellPush: updates.enableTelegramBellPush ?? current.enableTelegramBellPush,
33404
33702
  enableTelegramNotificationPush: updates.enableTelegramNotificationPush ?? current.enableTelegramNotificationPush,
33703
+ enableWeixinBellPush: updates.enableWeixinBellPush ?? current.enableWeixinBellPush,
33704
+ enableWeixinNotificationPush: updates.enableWeixinNotificationPush ?? current.enableWeixinNotificationPush,
33405
33705
  sshReconnectMaxRetries: updates.sshReconnectMaxRetries ?? current.sshReconnectMaxRetries,
33406
33706
  sshReconnectDelaySeconds: updates.sshReconnectDelaySeconds ?? current.sshReconnectDelaySeconds,
33407
33707
  language: updates.language ? normalizeLocale(updates.language) : current.language,
@@ -33417,6 +33717,8 @@ function updateSiteSettings(updates) {
33417
33717
  enableBrowserNotificationToast: next.enableBrowserNotificationToast,
33418
33718
  enableTelegramBellPush: next.enableTelegramBellPush,
33419
33719
  enableTelegramNotificationPush: next.enableTelegramNotificationPush,
33720
+ enableWeixinBellPush: next.enableWeixinBellPush,
33721
+ enableWeixinNotificationPush: next.enableWeixinNotificationPush,
33420
33722
  sshReconnectMaxRetries: next.sshReconnectMaxRetries,
33421
33723
  sshReconnectDelaySeconds: next.sshReconnectDelaySeconds,
33422
33724
  language: next.language,
@@ -33650,6 +33952,200 @@ function deleteTelegramChat(botId, chatId) {
33650
33952
  const orm = getDb();
33651
33953
  orm.delete(telegramBotChats).where(and(eq(telegramBotChats.botId, botId), eq(telegramBotChats.chatId, chatId))).run();
33652
33954
  }
33955
+ var WEIXIN_USER_CAP = 16;
33956
+ function createWeixinAccount(record) {
33957
+ const orm = getDb();
33958
+ orm.insert(weixinAccounts).values({
33959
+ id: record.id,
33960
+ name: record.name,
33961
+ enabled: record.enabled,
33962
+ allowAuthRequests: record.allowAuthRequests,
33963
+ weixinUin: record.weixinUin,
33964
+ botTokenEnc: record.botTokenEnc,
33965
+ baseUrl: record.baseUrl,
33966
+ syncBuf: record.syncBuf,
33967
+ createdAt: record.createdAt,
33968
+ updatedAt: record.updatedAt
33969
+ }).run();
33970
+ }
33971
+ function getWeixinAccountById(accountId) {
33972
+ const orm = getDb();
33973
+ const row = orm.select().from(weixinAccounts).where(eq(weixinAccounts.id, accountId)).get();
33974
+ if (!row) {
33975
+ return null;
33976
+ }
33977
+ return toWeixinAccountRecord(row);
33978
+ }
33979
+ function getAllWeixinAccounts() {
33980
+ const orm = getDb();
33981
+ return orm.select().from(weixinAccounts).orderBy(desc(weixinAccounts.createdAt)).all().map(toWeixinAccountRecord);
33982
+ }
33983
+ function getWeixinAccountsWithStats() {
33984
+ const orm = getDb();
33985
+ const accounts = orm.select().from(weixinAccounts).orderBy(desc(weixinAccounts.createdAt)).all();
33986
+ const counters = new Map;
33987
+ const userRows = orm.select({
33988
+ accountId: weixinAccountUsers.accountId,
33989
+ status: weixinAccountUsers.status,
33990
+ needsReactivation: weixinAccountUsers.needsReactivation
33991
+ }).from(weixinAccountUsers).all();
33992
+ for (const row of userRows) {
33993
+ const current = counters.get(row.accountId) ?? {
33994
+ pending: 0,
33995
+ authorized: 0,
33996
+ needsReactivation: 0
33997
+ };
33998
+ if (row.status === "pending") {
33999
+ current.pending += 1;
34000
+ }
34001
+ if (row.status === "authorized") {
34002
+ current.authorized += 1;
34003
+ if (row.needsReactivation) {
34004
+ current.needsReactivation += 1;
34005
+ }
34006
+ }
34007
+ counters.set(row.accountId, current);
34008
+ }
34009
+ return accounts.map((account) => {
34010
+ const counter = counters.get(account.id) ?? {
34011
+ pending: 0,
34012
+ authorized: 0,
34013
+ needsReactivation: 0
34014
+ };
34015
+ return {
34016
+ id: account.id,
34017
+ name: account.name,
34018
+ enabled: account.enabled,
34019
+ allowAuthRequests: account.allowAuthRequests,
34020
+ loggedIn: account.botTokenEnc != null,
34021
+ createdAt: account.createdAt,
34022
+ updatedAt: account.updatedAt,
34023
+ pendingCount: counter.pending,
34024
+ authorizedCount: counter.authorized,
34025
+ needsReactivationCount: counter.needsReactivation
34026
+ };
34027
+ });
34028
+ }
34029
+ function updateWeixinAccount(accountId, updates) {
34030
+ const orm = getDb();
34031
+ const setValues = {
34032
+ updatedAt: new Date().toISOString()
34033
+ };
34034
+ if (updates.name !== undefined) {
34035
+ setValues.name = updates.name;
34036
+ }
34037
+ if (updates.enabled !== undefined) {
34038
+ setValues.enabled = updates.enabled;
34039
+ }
34040
+ if (updates.allowAuthRequests !== undefined) {
34041
+ setValues.allowAuthRequests = updates.allowAuthRequests;
34042
+ }
34043
+ if (updates.weixinUin !== undefined) {
34044
+ setValues.weixinUin = updates.weixinUin;
34045
+ }
34046
+ if (updates.botTokenEnc !== undefined) {
34047
+ setValues.botTokenEnc = updates.botTokenEnc;
34048
+ }
34049
+ if (updates.baseUrl !== undefined) {
34050
+ setValues.baseUrl = updates.baseUrl;
34051
+ }
34052
+ if (updates.syncBuf !== undefined) {
34053
+ setValues.syncBuf = updates.syncBuf;
34054
+ }
34055
+ orm.update(weixinAccounts).set(setValues).where(eq(weixinAccounts.id, accountId)).run();
34056
+ return getWeixinAccountById(accountId);
34057
+ }
34058
+ function deleteWeixinAccount(accountId) {
34059
+ const orm = getDb();
34060
+ orm.delete(weixinAccounts).where(eq(weixinAccounts.id, accountId)).run();
34061
+ }
34062
+ function getWeixinUserCount(accountId) {
34063
+ const orm = getDb();
34064
+ const row = orm.select({ total: count() }).from(weixinAccountUsers).where(eq(weixinAccountUsers.accountId, accountId)).get();
34065
+ return Number(row?.total ?? 0);
34066
+ }
34067
+ function getWeixinUserByAccountAndUserId(accountId, userId) {
34068
+ const orm = getDb();
34069
+ const row = orm.select().from(weixinAccountUsers).where(and(eq(weixinAccountUsers.accountId, accountId), eq(weixinAccountUsers.userId, userId))).get();
34070
+ if (!row) {
34071
+ return null;
34072
+ }
34073
+ return toWeixinAccountUser(row);
34074
+ }
34075
+ function upsertWeixinUserOnInbound(params) {
34076
+ const existing = getWeixinUserByAccountAndUserId(params.accountId, params.userId);
34077
+ const orm = getDb();
34078
+ if (existing) {
34079
+ const setValues = {
34080
+ displayName: params.displayName,
34081
+ lastInboundAt: params.at,
34082
+ needsReactivation: false,
34083
+ updatedAt: params.at
34084
+ };
34085
+ if (params.contextToken != null) {
34086
+ setValues.lastContextToken = params.contextToken;
34087
+ }
34088
+ orm.update(weixinAccountUsers).set(setValues).where(eq(weixinAccountUsers.id, existing.id)).run();
34089
+ return getWeixinUserByAccountAndUserId(params.accountId, params.userId);
34090
+ }
34091
+ if (!params.allowAuthRequests) {
34092
+ return null;
34093
+ }
34094
+ if (getWeixinUserCount(params.accountId) >= WEIXIN_USER_CAP) {
34095
+ throw new Error(instance.t("apiError.invalidRequest"));
34096
+ }
34097
+ orm.insert(weixinAccountUsers).values({
34098
+ id: crypto.randomUUID(),
34099
+ accountId: params.accountId,
34100
+ userId: params.userId,
34101
+ displayName: params.displayName,
34102
+ status: "pending",
34103
+ lastContextToken: params.contextToken,
34104
+ lastInboundAt: params.at,
34105
+ needsReactivation: false,
34106
+ appliedAt: params.at,
34107
+ authorizedAt: null,
34108
+ updatedAt: params.at
34109
+ }).run();
34110
+ return getWeixinUserByAccountAndUserId(params.accountId, params.userId);
34111
+ }
34112
+ function listWeixinUsersByAccount(accountId) {
34113
+ const orm = getDb();
34114
+ return orm.select().from(weixinAccountUsers).where(eq(weixinAccountUsers.accountId, accountId)).orderBy(desc(weixinAccountUsers.appliedAt)).all().map(toWeixinAccountUser);
34115
+ }
34116
+ function listAuthorizedWeixinUsersByAccount(accountId) {
34117
+ const orm = getDb();
34118
+ return orm.select().from(weixinAccountUsers).where(and(eq(weixinAccountUsers.accountId, accountId), eq(weixinAccountUsers.status, "authorized"))).orderBy(desc(weixinAccountUsers.authorizedAt)).all().map(toWeixinAccountUser);
34119
+ }
34120
+ function getWeixinUserContextTokens(accountId) {
34121
+ const orm = getDb();
34122
+ return orm.select({
34123
+ userId: weixinAccountUsers.userId,
34124
+ contextToken: weixinAccountUsers.lastContextToken
34125
+ }).from(weixinAccountUsers).where(eq(weixinAccountUsers.accountId, accountId)).all().filter((row) => row.contextToken != null);
34126
+ }
34127
+ function approveWeixinUser(accountId, userId) {
34128
+ const existing = getWeixinUserByAccountAndUserId(accountId, userId);
34129
+ if (!existing) {
34130
+ return null;
34131
+ }
34132
+ const now = new Date().toISOString();
34133
+ const orm = getDb();
34134
+ orm.update(weixinAccountUsers).set({
34135
+ status: "authorized",
34136
+ authorizedAt: now,
34137
+ updatedAt: now
34138
+ }).where(eq(weixinAccountUsers.id, existing.id)).run();
34139
+ return getWeixinUserByAccountAndUserId(accountId, userId);
34140
+ }
34141
+ function deleteWeixinUser(accountId, userId) {
34142
+ const orm = getDb();
34143
+ orm.delete(weixinAccountUsers).where(and(eq(weixinAccountUsers.accountId, accountId), eq(weixinAccountUsers.userId, userId))).run();
34144
+ }
34145
+ function setWeixinUserNeedsReactivation(accountId, userId, value) {
34146
+ const orm = getDb();
34147
+ orm.update(weixinAccountUsers).set({ needsReactivation: value, updatedAt: new Date().toISOString() }).where(and(eq(weixinAccountUsers.accountId, accountId), eq(weixinAccountUsers.userId, userId))).run();
34148
+ }
33653
34149
 
33654
34150
  // ../../apps/gateway/src/db/agent.ts
33655
34151
  function ensureAgentSettingsInitialized() {
@@ -87048,13 +87544,7 @@ var _a21;
87048
87544
  _a21 = symbol21;
87049
87545
  var defaultDownload2 = createDownload();
87050
87546
 
87051
- // ../../apps/gateway/src/events/index.ts
87052
- function escapeTelegramHtmlText(input) {
87053
- return input.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
87054
- }
87055
- function escapeTelegramHtmlAttribute(input) {
87056
- return escapeTelegramHtmlText(input).replace(/"/g, "&quot;");
87057
- }
87547
+ // ../../apps/gateway/src/events/channels/pane-url.ts
87058
87548
  function trimTrailingSlash(url2) {
87059
87549
  return url2.endsWith("/") ? url2.slice(0, -1) : url2;
87060
87550
  }
@@ -87083,100 +87573,17 @@ function normalizeHttpUrl(input) {
87083
87573
  }
87084
87574
  }
87085
87575
 
87086
- class EventNotifier {
87087
- webhooks = [];
87088
- lastRefresh = 0;
87089
- REFRESH_INTERVAL = 60000;
87090
- bellThrottleMap = new Map;
87091
- notificationThrottleMap = new Map;
87092
- refreshConfig() {
87093
- const now2 = Date.now();
87094
- if (now2 - this.lastRefresh < this.REFRESH_INTERVAL)
87095
- return;
87096
- this.webhooks = getAllWebhookEndpoints().filter((w) => w.enabled);
87097
- this.lastRefresh = now2;
87098
- console.log(`[events] refreshed config: ${this.webhooks.length} webhooks`);
87099
- }
87576
+ // ../../apps/gateway/src/events/channels/telegram.ts
87577
+ function escapeTelegramHtmlText(input) {
87578
+ return input.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
87579
+ }
87580
+ function escapeTelegramHtmlAttribute(input) {
87581
+ return escapeTelegramHtmlText(input).replace(/"/g, "&quot;");
87582
+ }
87583
+
87584
+ class TelegramChannel {
87585
+ id = "telegram";
87100
87586
  async notify(eventType, event) {
87101
- this.refreshConfig();
87102
- const fullEvent = {
87103
- ...event,
87104
- eventType,
87105
- timestamp: new Date().toISOString()
87106
- };
87107
- if (eventType === "terminal_bell") {
87108
- if (!this.shouldPassBellThrottle(fullEvent)) {
87109
- return;
87110
- }
87111
- } else if (eventType === "terminal_notification") {
87112
- if (!this.shouldPassNotificationThrottle(fullEvent)) {
87113
- return;
87114
- }
87115
- }
87116
- await Promise.all([
87117
- this.sendWebhooks(eventType, fullEvent),
87118
- this.sendTelegramNotifications(eventType, fullEvent)
87119
- ]);
87120
- }
87121
- shouldPassBellThrottle(event) {
87122
- const settings = getSiteSettings();
87123
- const throttleMs = Math.max(0, settings.bellThrottleSeconds) * 1000;
87124
- if (throttleMs === 0) {
87125
- return true;
87126
- }
87127
- const key = `${event.device.id}:${event.tmux?.paneId ?? "-"}:${event.eventType}`;
87128
- const now2 = Date.now();
87129
- const previous = this.bellThrottleMap.get(key) ?? 0;
87130
- if (now2 - previous < throttleMs) {
87131
- return false;
87132
- }
87133
- this.bellThrottleMap.set(key, now2);
87134
- return true;
87135
- }
87136
- shouldPassNotificationThrottle(event) {
87137
- const settings = getSiteSettings();
87138
- const throttleMs = Math.max(0, settings.notificationThrottleSeconds) * 1000;
87139
- if (throttleMs === 0) {
87140
- return true;
87141
- }
87142
- const source = typeof event.payload?.source === "string" ? event.payload.source : "unknown";
87143
- const key = `${event.device.id}:${event.tmux?.paneId ?? "-"}:notification:${source}`;
87144
- const now2 = Date.now();
87145
- const previous = this.notificationThrottleMap.get(key) ?? 0;
87146
- if (now2 - previous < throttleMs) {
87147
- return false;
87148
- }
87149
- this.notificationThrottleMap.set(key, now2);
87150
- return true;
87151
- }
87152
- async sendWebhooks(eventType, event) {
87153
- const targets = this.webhooks.filter((w) => w.eventMask.includes(eventType));
87154
- await Promise.all(targets.map(async (webhook) => {
87155
- try {
87156
- await this.sendWebhook(webhook, event);
87157
- } catch (err) {
87158
- console.error(`[webhook] failed to send to ${webhook.url}:`, err);
87159
- }
87160
- }));
87161
- }
87162
- async sendWebhook(webhook, event) {
87163
- const body = JSON.stringify(event);
87164
- const signature = await this.generateHmac(webhook.secret, body);
87165
- const response = await fetch(webhook.url, {
87166
- method: "POST",
87167
- headers: {
87168
- "Content-Type": "application/json",
87169
- "X-Tmex-Signature": `sha256=${signature}`,
87170
- "X-Tmex-Event": event.eventType,
87171
- "X-Tmex-Timestamp": event.timestamp
87172
- },
87173
- body
87174
- });
87175
- if (!response.ok) {
87176
- console.error(`[webhook] ${webhook.url} returned ${response.status}`);
87177
- }
87178
- }
87179
- async sendTelegramNotifications(eventType, event) {
87180
87587
  const settings = getSiteSettings();
87181
87588
  if (eventType === "terminal_bell") {
87182
87589
  if (!settings.enableTelegramBellPush) {
@@ -87290,6 +87697,51 @@ class EventNotifier {
87290
87697
  return lines.join(`
87291
87698
  `);
87292
87699
  }
87700
+ }
87701
+ var telegramChannel = new TelegramChannel;
87702
+
87703
+ // ../../apps/gateway/src/events/channels/webhook.ts
87704
+ class WebhookChannel {
87705
+ id = "webhook";
87706
+ webhooks = [];
87707
+ lastRefresh = 0;
87708
+ REFRESH_INTERVAL = 60000;
87709
+ refreshConfig() {
87710
+ const now2 = Date.now();
87711
+ if (now2 - this.lastRefresh < this.REFRESH_INTERVAL)
87712
+ return;
87713
+ this.webhooks = getAllWebhookEndpoints().filter((w) => w.enabled);
87714
+ this.lastRefresh = now2;
87715
+ console.log(`[events] refreshed config: ${this.webhooks.length} webhooks`);
87716
+ }
87717
+ async notify(eventType, event) {
87718
+ this.refreshConfig();
87719
+ const targets = this.webhooks.filter((w) => w.eventMask.includes(eventType));
87720
+ await Promise.all(targets.map(async (webhook) => {
87721
+ try {
87722
+ await this.sendWebhook(webhook, event);
87723
+ } catch (err) {
87724
+ console.error(`[webhook] failed to send to ${webhook.url}:`, err);
87725
+ }
87726
+ }));
87727
+ }
87728
+ async sendWebhook(webhook, event) {
87729
+ const body = JSON.stringify(event);
87730
+ const signature = await this.generateHmac(webhook.secret, body);
87731
+ const response = await fetch(webhook.url, {
87732
+ method: "POST",
87733
+ headers: {
87734
+ "Content-Type": "application/json",
87735
+ "X-Tmex-Signature": `sha256=${signature}`,
87736
+ "X-Tmex-Event": event.eventType,
87737
+ "X-Tmex-Timestamp": event.timestamp
87738
+ },
87739
+ body
87740
+ });
87741
+ if (!response.ok) {
87742
+ console.error(`[webhook] ${webhook.url} returned ${response.status}`);
87743
+ }
87744
+ }
87293
87745
  async generateHmac(secret, message) {
87294
87746
  const encoder2 = new TextEncoder;
87295
87747
  const key = await crypto.subtle.importKey("raw", encoder2.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
@@ -87297,6 +87749,897 @@ class EventNotifier {
87297
87749
  return Buffer.from(signature).toString("hex");
87298
87750
  }
87299
87751
  }
87752
+ var webhookChannel = new WebhookChannel;
87753
+
87754
+ // ../../apps/gateway/src/weixin/ilink/types.ts
87755
+ var ILINK_LOGIN_HOST = "https://ilinkai.weixin.qq.com";
87756
+ var ILINK_BOT_TYPE = 3;
87757
+ var CHANNEL_VERSION = "1.0.3";
87758
+ var CLIENT_ID_PREFIX = "openclaw-weixin-";
87759
+ var MESSAGE_TYPE_BOT = 2;
87760
+ var MESSAGE_STATE_FINISH = 2;
87761
+ var ITEM_TYPE_TEXT = 1;
87762
+ var SESSION_EXPIRED_ERRCODE = -14;
87763
+
87764
+ // ../../apps/gateway/src/weixin/ilink/api.ts
87765
+ function resolveFetch(fetchImpl) {
87766
+ return fetchImpl ?? globalThis.fetch;
87767
+ }
87768
+ function trimTrailingSlash2(url2) {
87769
+ return url2.endsWith("/") ? url2.slice(0, -1) : url2;
87770
+ }
87771
+ function generateWechatUin() {
87772
+ const buf = new Uint32Array(1);
87773
+ crypto.getRandomValues(buf);
87774
+ const decimal = String(buf[0]);
87775
+ return Buffer.from(decimal, "utf8").toString("base64");
87776
+ }
87777
+ function buildAuthHeaders(botToken) {
87778
+ const headers = {
87779
+ "Content-Type": "application/json",
87780
+ AuthorizationType: "ilink_bot_token",
87781
+ "X-WECHAT-UIN": generateWechatUin()
87782
+ };
87783
+ const token = botToken?.trim();
87784
+ if (token) {
87785
+ headers.Authorization = `Bearer ${token}`;
87786
+ }
87787
+ return headers;
87788
+ }
87789
+ function buildBaseInfo() {
87790
+ return { channel_version: CHANNEL_VERSION };
87791
+ }
87792
+ async function readJson(resp) {
87793
+ if (!resp.ok) {
87794
+ const body = await resp.text().catch(() => "");
87795
+ throw new Error(`iLink HTTP ${resp.status} ${resp.statusText}${body ? `: ${body.slice(0, 200)}` : ""}`);
87796
+ }
87797
+ const text3 = await resp.text();
87798
+ if (!text3) {
87799
+ return {};
87800
+ }
87801
+ return JSON.parse(text3);
87802
+ }
87803
+ async function getBotQrcode(opts = {}) {
87804
+ const host = trimTrailingSlash2(opts.host ?? ILINK_LOGIN_HOST);
87805
+ const botType = opts.botType ?? ILINK_BOT_TYPE;
87806
+ const url2 = `${host}/ilink/bot/get_bot_qrcode?bot_type=${botType}`;
87807
+ const resp = await resolveFetch(opts.fetchImpl)(url2, {
87808
+ method: "GET",
87809
+ headers: buildAuthHeaders(),
87810
+ signal: opts.signal
87811
+ });
87812
+ return readJson(resp);
87813
+ }
87814
+ async function getQrcodeStatus(qrcode, opts = {}) {
87815
+ const host = trimTrailingSlash2(opts.host ?? ILINK_LOGIN_HOST);
87816
+ const url2 = `${host}/ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrcode)}`;
87817
+ const resp = await resolveFetch(opts.fetchImpl)(url2, {
87818
+ method: "GET",
87819
+ headers: buildAuthHeaders(),
87820
+ signal: opts.signal
87821
+ });
87822
+ return readJson(resp);
87823
+ }
87824
+ async function getUpdates(opts) {
87825
+ const baseUrl = trimTrailingSlash2(opts.baseUrl);
87826
+ const url2 = `${baseUrl}/ilink/bot/getupdates`;
87827
+ const body = {
87828
+ get_updates_buf: opts.getUpdatesBuf ?? "",
87829
+ base_info: buildBaseInfo()
87830
+ };
87831
+ const resp = await resolveFetch(opts.fetchImpl)(url2, {
87832
+ method: "POST",
87833
+ headers: buildAuthHeaders(opts.botToken),
87834
+ body: JSON.stringify(body),
87835
+ signal: opts.signal
87836
+ });
87837
+ return readJson(resp);
87838
+ }
87839
+ async function sendMessage(opts) {
87840
+ const baseUrl = trimTrailingSlash2(opts.baseUrl);
87841
+ const url2 = `${baseUrl}/ilink/bot/sendmessage`;
87842
+ const itemList = opts.items.map((item) => ({
87843
+ type: ITEM_TYPE_TEXT,
87844
+ text_item: { text: item.text }
87845
+ }));
87846
+ const req = {
87847
+ msg: {
87848
+ to_user_id: opts.toUserId,
87849
+ message_type: MESSAGE_TYPE_BOT,
87850
+ message_state: MESSAGE_STATE_FINISH,
87851
+ context_token: opts.contextToken,
87852
+ client_id: opts.clientId,
87853
+ item_list: itemList
87854
+ },
87855
+ base_info: buildBaseInfo()
87856
+ };
87857
+ const resp = await resolveFetch(opts.fetchImpl)(url2, {
87858
+ method: "POST",
87859
+ headers: buildAuthHeaders(opts.botToken),
87860
+ body: JSON.stringify(req),
87861
+ signal: opts.signal
87862
+ });
87863
+ return readJson(resp);
87864
+ }
87865
+
87866
+ // ../../apps/gateway/src/weixin/ilink/client.ts
87867
+ class WeixinNoContextTokenError extends Error {
87868
+ constructor(toUserId) {
87869
+ super(`No context_token for user ${toUserId}. Receive a message from them first.`);
87870
+ this.name = "WeixinNoContextTokenError";
87871
+ }
87872
+ }
87873
+
87874
+ class WeixinSessionExpiredError extends Error {
87875
+ constructor() {
87876
+ super("iLink bot session expired; re-login required.");
87877
+ this.name = "WeixinSessionExpiredError";
87878
+ }
87879
+ }
87880
+ var DEFAULT_LOGIN_TIMEOUT_MS = 480000;
87881
+ var DEFAULT_QRCODE_POLL_INTERVAL_MS = 1000;
87882
+ var MAX_QRCODE_REFRESH = 3;
87883
+ var RETRY_DELAY_MS = 2000;
87884
+ var BACKOFF_DELAY_MS = 30000;
87885
+ var DEFAULT_LONGPOLL_TIMEOUT_MS = 60000;
87886
+ var LONGPOLL_TIMEOUT_MARGIN_MS = 1e4;
87887
+ function generateClientId() {
87888
+ const bytes = new Uint8Array(16);
87889
+ crypto.getRandomValues(bytes);
87890
+ const hex3 = Array.from(bytes, (b3) => b3.toString(16).padStart(2, "0")).join("");
87891
+ return `${CLIENT_ID_PREFIX}${hex3}`;
87892
+ }
87893
+
87894
+ class AbortError extends Error {
87895
+ constructor() {
87896
+ super("aborted");
87897
+ this.name = "AbortError";
87898
+ }
87899
+ }
87900
+ function sleep3(ms, signal) {
87901
+ return new Promise((resolve3, reject) => {
87902
+ if (signal?.aborted) {
87903
+ reject(new AbortError);
87904
+ return;
87905
+ }
87906
+ const timer = setTimeout(() => {
87907
+ signal?.removeEventListener("abort", onAbort);
87908
+ resolve3();
87909
+ }, ms);
87910
+ const onAbort = () => {
87911
+ clearTimeout(timer);
87912
+ reject(new AbortError);
87913
+ };
87914
+ signal?.addEventListener("abort", onAbort, { once: true });
87915
+ });
87916
+ }
87917
+ function isAbort(err, signal) {
87918
+ if (signal?.aborted)
87919
+ return true;
87920
+ if (err instanceof AbortError)
87921
+ return true;
87922
+ if (err instanceof DOMException && err.name === "AbortError")
87923
+ return true;
87924
+ if (err instanceof Error && err.name === "AbortError")
87925
+ return true;
87926
+ return false;
87927
+ }
87928
+ function isSessionExpired(resp) {
87929
+ return resp.ret === SESSION_EXPIRED_ERRCODE || resp.errcode === SESSION_EXPIRED_ERRCODE;
87930
+ }
87931
+
87932
+ class WeixinClient {
87933
+ creds = null;
87934
+ fetchImpl;
87935
+ contextTokens = new Map;
87936
+ running = false;
87937
+ internalAbort = null;
87938
+ constructor(opts = {}) {
87939
+ this.fetchImpl = opts.fetchImpl;
87940
+ if (opts.accountId && opts.botToken && opts.baseUrl) {
87941
+ this.creds = {
87942
+ accountId: opts.accountId,
87943
+ botToken: opts.botToken,
87944
+ baseUrl: opts.baseUrl
87945
+ };
87946
+ }
87947
+ }
87948
+ get credentials() {
87949
+ return this.creds;
87950
+ }
87951
+ getContextToken(userId) {
87952
+ return this.contextTokens.get(userId);
87953
+ }
87954
+ setContextToken(userId, token) {
87955
+ this.contextTokens.set(userId, token);
87956
+ }
87957
+ isRunning() {
87958
+ return this.running;
87959
+ }
87960
+ static extractText(raw) {
87961
+ const msg = raw;
87962
+ if (!msg || typeof msg !== "object" || !Array.isArray(msg.item_list)) {
87963
+ return "";
87964
+ }
87965
+ const parts = [];
87966
+ for (const item of msg.item_list) {
87967
+ if (item?.type === ITEM_TYPE_TEXT && typeof item.text_item?.text === "string") {
87968
+ parts.push(item.text_item.text);
87969
+ }
87970
+ }
87971
+ return parts.join("");
87972
+ }
87973
+ async login(opts) {
87974
+ const { onQrcode, signal } = opts;
87975
+ const timeoutMs = Math.max(1000, opts.timeoutMs ?? DEFAULT_LOGIN_TIMEOUT_MS);
87976
+ const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_QRCODE_POLL_INTERVAL_MS;
87977
+ const deadline = Date.now() + timeoutMs;
87978
+ let qrcodeId = await this.fetchQrcode(onQrcode, signal);
87979
+ let refreshes = 0;
87980
+ while (Date.now() < deadline) {
87981
+ if (signal?.aborted)
87982
+ throw new AbortError;
87983
+ await sleep3(pollIntervalMs, signal);
87984
+ let status;
87985
+ try {
87986
+ status = await getQrcodeStatus(qrcodeId, {
87987
+ fetchImpl: this.fetchImpl,
87988
+ signal
87989
+ });
87990
+ } catch (err) {
87991
+ if (isAbort(err, signal))
87992
+ throw err;
87993
+ continue;
87994
+ }
87995
+ switch (status.status) {
87996
+ case "confirmed": {
87997
+ if (!status.bot_token || !status.baseurl) {
87998
+ throw new Error("iLink login confirmed but bot_token/baseurl missing.");
87999
+ }
88000
+ const accountId = status.ilink_bot_id ?? status.ilink_user_id ?? status.bot_token;
88001
+ this.creds = {
88002
+ accountId,
88003
+ botToken: status.bot_token,
88004
+ baseUrl: status.baseurl
88005
+ };
88006
+ return this.creds;
88007
+ }
88008
+ case "expired": {
88009
+ if (refreshes >= MAX_QRCODE_REFRESH) {
88010
+ throw new Error("iLink qrcode expired and refresh limit reached.");
88011
+ }
88012
+ refreshes += 1;
88013
+ qrcodeId = await this.fetchQrcode(onQrcode, signal);
88014
+ break;
88015
+ }
88016
+ default:
88017
+ break;
88018
+ }
88019
+ }
88020
+ throw new Error("iLink login timed out.");
88021
+ }
88022
+ async fetchQrcode(onQrcode, signal) {
88023
+ const resp = await getBotQrcode({ fetchImpl: this.fetchImpl, signal });
88024
+ if (!resp.qrcode) {
88025
+ throw new Error("iLink get_bot_qrcode returned no qrcode.");
88026
+ }
88027
+ if (!resp.qrcode_img_content) {
88028
+ throw new Error("iLink get_bot_qrcode returned no qrcode content.");
88029
+ }
88030
+ onQrcode({
88031
+ url: resp.qrcode_img_content,
88032
+ qrcodeId: resp.qrcode
88033
+ });
88034
+ return resp.qrcode;
88035
+ }
88036
+ async start(opts = {}) {
88037
+ if (!this.creds) {
88038
+ throw new Error("WeixinClient.start called without credentials; login first.");
88039
+ }
88040
+ if (this.running) {
88041
+ throw new Error("WeixinClient already running.");
88042
+ }
88043
+ if (opts.initialContextTokens) {
88044
+ for (const [userId, token] of Object.entries(opts.initialContextTokens)) {
88045
+ this.contextTokens.set(userId, token);
88046
+ }
88047
+ }
88048
+ this.internalAbort = new AbortController;
88049
+ const signal = this.linkSignals(opts.signal, this.internalAbort.signal);
88050
+ this.running = true;
88051
+ let getUpdatesBuf = await opts.loadSyncBuf?.() ?? "";
88052
+ let failures = 0;
88053
+ let longpollTimeoutMs = opts.longpollTimeoutMs ?? DEFAULT_LONGPOLL_TIMEOUT_MS;
88054
+ try {
88055
+ while (!signal.aborted) {
88056
+ let resp;
88057
+ try {
88058
+ const perRequestSignal = AbortSignal.any([
88059
+ signal,
88060
+ AbortSignal.timeout(longpollTimeoutMs)
88061
+ ]);
88062
+ resp = await getUpdates({
88063
+ baseUrl: this.creds.baseUrl,
88064
+ botToken: this.creds.botToken,
88065
+ getUpdatesBuf,
88066
+ fetchImpl: this.fetchImpl,
88067
+ signal: perRequestSignal
88068
+ });
88069
+ } catch (err) {
88070
+ if (signal.aborted)
88071
+ break;
88072
+ opts.onError?.(err);
88073
+ failures += 1;
88074
+ await this.backoffSleep(failures, signal);
88075
+ continue;
88076
+ }
88077
+ if (isSessionExpired(resp)) {
88078
+ opts.onSessionExpired?.();
88079
+ throw new WeixinSessionExpiredError;
88080
+ }
88081
+ if (typeof resp.ret === "number" && resp.ret !== 0) {
88082
+ opts.onError?.(new Error(`getupdates ret=${resp.ret} errmsg=${resp.errmsg ?? ""}`));
88083
+ failures += 1;
88084
+ await this.backoffSleep(failures, signal);
88085
+ continue;
88086
+ }
88087
+ failures = 0;
88088
+ if (typeof resp.longpolling_timeout_ms === "number" && resp.longpolling_timeout_ms > 0) {
88089
+ longpollTimeoutMs = resp.longpolling_timeout_ms + LONGPOLL_TIMEOUT_MARGIN_MS;
88090
+ }
88091
+ const msgs = resp.msgs ?? [];
88092
+ for (const msg of msgs) {
88093
+ if (signal.aborted)
88094
+ break;
88095
+ const inbound = this.toInbound(msg);
88096
+ if (msg.from_user_id && msg.context_token) {
88097
+ this.contextTokens.set(msg.from_user_id, msg.context_token);
88098
+ }
88099
+ try {
88100
+ await opts.onMessage?.(inbound);
88101
+ } catch (err) {
88102
+ opts.onError?.(err);
88103
+ }
88104
+ }
88105
+ if (resp.get_updates_buf != null && resp.get_updates_buf !== "") {
88106
+ getUpdatesBuf = resp.get_updates_buf;
88107
+ await opts.saveSyncBuf?.(getUpdatesBuf);
88108
+ }
88109
+ }
88110
+ } finally {
88111
+ this.running = false;
88112
+ this.internalAbort = null;
88113
+ }
88114
+ }
88115
+ stop() {
88116
+ this.internalAbort?.abort();
88117
+ }
88118
+ async sendText(toUserId, text3, contextToken) {
88119
+ if (!this.creds) {
88120
+ throw new Error("WeixinClient.sendText called without credentials; login first.");
88121
+ }
88122
+ const token = contextToken ?? this.contextTokens.get(toUserId);
88123
+ if (!token) {
88124
+ throw new WeixinNoContextTokenError(toUserId);
88125
+ }
88126
+ const resp = await sendMessage({
88127
+ baseUrl: this.creds.baseUrl,
88128
+ botToken: this.creds.botToken,
88129
+ toUserId,
88130
+ contextToken: token,
88131
+ clientId: generateClientId(),
88132
+ items: [{ text: text3 }],
88133
+ fetchImpl: this.fetchImpl
88134
+ });
88135
+ if (resp.ret === SESSION_EXPIRED_ERRCODE || resp.errcode === SESSION_EXPIRED_ERRCODE) {
88136
+ throw new WeixinSessionExpiredError;
88137
+ }
88138
+ if (typeof resp.ret === "number" && resp.ret !== 0) {
88139
+ throw new Error(`sendmessage ret=${resp.ret} errmsg=${resp.errmsg ?? ""}`);
88140
+ }
88141
+ }
88142
+ toInbound(msg) {
88143
+ return {
88144
+ fromUserId: msg.from_user_id ?? "",
88145
+ contextToken: msg.context_token ?? null,
88146
+ text: WeixinClient.extractText(msg),
88147
+ raw: msg
88148
+ };
88149
+ }
88150
+ async backoffSleep(failures, signal) {
88151
+ const delay2 = Math.min(RETRY_DELAY_MS * 2 ** Math.max(0, failures - 1), BACKOFF_DELAY_MS);
88152
+ try {
88153
+ await sleep3(delay2, signal);
88154
+ } catch {}
88155
+ }
88156
+ linkSignals(external3, internal) {
88157
+ if (!external3)
88158
+ return internal;
88159
+ const controller = new AbortController;
88160
+ const onAbort = () => controller.abort();
88161
+ if (external3.aborted || internal.aborted) {
88162
+ controller.abort();
88163
+ return controller.signal;
88164
+ }
88165
+ external3.addEventListener("abort", onAbort, { once: true });
88166
+ internal.addEventListener("abort", onAbort, { once: true });
88167
+ return controller.signal;
88168
+ }
88169
+ }
88170
+
88171
+ // ../../apps/gateway/src/weixin/service.ts
88172
+ var KEEPALIVE_INTERVAL_MS = 8 * 60 * 60 * 1000;
88173
+ var KEEPALIVE_SWEEP_MS = 30 * 60 * 1000;
88174
+
88175
+ class WeixinService {
88176
+ createClient;
88177
+ runningAccounts = new Map;
88178
+ loginSessions = new Map;
88179
+ sessionExpiredNotified = new Set;
88180
+ refreshChain = Promise.resolve();
88181
+ keepaliveTimer = null;
88182
+ lastKeepaliveAt = new Map;
88183
+ constructor(createClient = (opts) => new WeixinClient(opts)) {
88184
+ this.createClient = createClient;
88185
+ }
88186
+ async refresh() {
88187
+ const run = this.refreshChain.then(() => this.doRefresh());
88188
+ this.refreshChain = run.catch(() => {});
88189
+ return run;
88190
+ }
88191
+ async doRefresh() {
88192
+ this.ensureKeepaliveTimer();
88193
+ const accounts = getAllWeixinAccounts();
88194
+ const activeIds = new Set(accounts.map((a) => a.id));
88195
+ for (const [id, session] of this.loginSessions) {
88196
+ if (!activeIds.has(id)) {
88197
+ session.abort.abort();
88198
+ this.loginSessions.delete(id);
88199
+ }
88200
+ }
88201
+ const toStop = [];
88202
+ for (const [id] of this.runningAccounts) {
88203
+ if (!activeIds.has(id)) {
88204
+ toStop.push(id);
88205
+ }
88206
+ }
88207
+ await Promise.all(toStop.map((id) => this.stopAccount(id)));
88208
+ for (const account of accounts) {
88209
+ const shouldRun = account.enabled && account.botTokenEnc != null && account.baseUrl != null;
88210
+ if (!shouldRun) {
88211
+ await this.stopAccount(account.id);
88212
+ continue;
88213
+ }
88214
+ let botToken;
88215
+ try {
88216
+ botToken = await decryptWithContext(account.botTokenEnc, {
88217
+ scope: "weixin_account",
88218
+ entityId: account.id,
88219
+ field: "bot_token_enc"
88220
+ });
88221
+ } catch (err) {
88222
+ console.error(`[weixin] failed to decrypt token for ${account.id}:`, err);
88223
+ continue;
88224
+ }
88225
+ const running = this.runningAccounts.get(account.id);
88226
+ if (running && running.botToken === botToken) {
88227
+ continue;
88228
+ }
88229
+ if (running) {
88230
+ await this.stopAccount(account.id);
88231
+ }
88232
+ this.startAccount(account, botToken);
88233
+ }
88234
+ }
88235
+ startAccount(account, botToken) {
88236
+ const baseUrl = account.baseUrl ?? "";
88237
+ const weixinUin = account.weixinUin ?? account.id;
88238
+ const client = this.createClient({ accountId: weixinUin, botToken, baseUrl });
88239
+ for (const { userId, contextToken } of getWeixinUserContextTokens(account.id)) {
88240
+ client.setContextToken(userId, contextToken);
88241
+ }
88242
+ this.runningAccounts.set(account.id, { id: account.id, botToken, client });
88243
+ this.sessionExpiredNotified.delete(account.id);
88244
+ client.start({
88245
+ loadSyncBuf: () => getWeixinAccountById(account.id)?.syncBuf ?? undefined,
88246
+ saveSyncBuf: (buf) => {
88247
+ updateWeixinAccount(account.id, { syncBuf: buf });
88248
+ },
88249
+ onMessage: (msg) => this.handleInbound(account.id, msg),
88250
+ onSessionExpired: () => this.handleSessionExpired(account.id),
88251
+ onError: (err) => console.error(`[weixin] account ${account.id} poll error:`, err)
88252
+ }).catch((err) => {
88253
+ if (!(err instanceof WeixinSessionExpiredError)) {
88254
+ console.error(`[weixin] account ${account.id} loop crashed:`, err);
88255
+ }
88256
+ }).finally(() => {
88257
+ const cur = this.runningAccounts.get(account.id);
88258
+ if (cur && cur.client === client) {
88259
+ this.runningAccounts.delete(account.id);
88260
+ }
88261
+ });
88262
+ console.log(`[weixin] account started: ${account.name} (${account.id})`);
88263
+ }
88264
+ async stopAccount(accountId) {
88265
+ const running = this.runningAccounts.get(accountId);
88266
+ if (!running) {
88267
+ return;
88268
+ }
88269
+ running.client.stop();
88270
+ this.runningAccounts.delete(accountId);
88271
+ console.log(`[weixin] account stopped: ${accountId}`);
88272
+ }
88273
+ async handleInbound(accountId, msg) {
88274
+ const account = getWeixinAccountById(accountId);
88275
+ if (!account) {
88276
+ return;
88277
+ }
88278
+ const userId = msg.fromUserId;
88279
+ if (!userId) {
88280
+ return;
88281
+ }
88282
+ const now2 = new Date().toISOString();
88283
+ try {
88284
+ upsertWeixinUserOnInbound({
88285
+ accountId,
88286
+ userId,
88287
+ displayName: userId,
88288
+ contextToken: msg.contextToken,
88289
+ allowAuthRequests: account.allowAuthRequests,
88290
+ at: now2
88291
+ });
88292
+ } catch (err) {
88293
+ console.error("[weixin] failed to upsert user on inbound:", err);
88294
+ }
88295
+ }
88296
+ handleSessionExpired(accountId) {
88297
+ if (!this.sessionExpiredNotified.has(accountId)) {
88298
+ this.sessionExpiredNotified.add(accountId);
88299
+ const account = getWeixinAccountById(accountId);
88300
+ console.warn(`[weixin] account ${account?.name ?? accountId} session expired; re-login required.`);
88301
+ for (const user of listAuthorizedWeixinUsersByAccount(accountId)) {
88302
+ setWeixinUserNeedsReactivation(accountId, user.userId, true);
88303
+ }
88304
+ }
88305
+ updateWeixinAccount(accountId, { botTokenEnc: null, baseUrl: null, weixinUin: null });
88306
+ }
88307
+ async sendGatewayOnlineMessage(siteName) {
88308
+ await this.sendToAuthorizedUsers({ text: t2("weixin.gatewayOnline", { siteName }) });
88309
+ }
88310
+ async sendToAuthorizedUsers(params) {
88311
+ for (const [accountId, running] of this.runningAccounts) {
88312
+ for (const user of listAuthorizedWeixinUsersByAccount(accountId)) {
88313
+ const cont = await this.sendToUser(accountId, running, user, params.text);
88314
+ if (!cont)
88315
+ break;
88316
+ }
88317
+ }
88318
+ }
88319
+ async sendToUser(accountId, running, user, text3) {
88320
+ try {
88321
+ await running.client.sendText(user.userId, text3);
88322
+ if (user.needsReactivation) {
88323
+ setWeixinUserNeedsReactivation(accountId, user.userId, false);
88324
+ }
88325
+ return true;
88326
+ } catch (err) {
88327
+ if (err instanceof WeixinSessionExpiredError) {
88328
+ this.handleSessionExpired(accountId);
88329
+ return false;
88330
+ }
88331
+ if (!user.needsReactivation) {
88332
+ console.warn(`[weixin] send to ${user.userId} failed; marking needs-reactivation:`, err instanceof Error ? err.message : err);
88333
+ }
88334
+ setWeixinUserNeedsReactivation(accountId, user.userId, true);
88335
+ return true;
88336
+ }
88337
+ }
88338
+ ensureKeepaliveTimer() {
88339
+ if (this.keepaliveTimer) {
88340
+ return;
88341
+ }
88342
+ this.keepaliveTimer = setInterval(() => void this.runKeepaliveSweep(), KEEPALIVE_SWEEP_MS);
88343
+ this.keepaliveTimer.unref?.();
88344
+ }
88345
+ async runKeepaliveSweep() {
88346
+ const now2 = Date.now();
88347
+ for (const [accountId, running] of this.runningAccounts) {
88348
+ for (const user of listAuthorizedWeixinUsersByAccount(accountId)) {
88349
+ const key = `${accountId}:${user.userId}`;
88350
+ const lastInbound = user.lastInboundAt ? Date.parse(user.lastInboundAt) : 0;
88351
+ const since = Math.max(lastInbound, this.lastKeepaliveAt.get(key) ?? 0);
88352
+ if (now2 - since < KEEPALIVE_INTERVAL_MS) {
88353
+ continue;
88354
+ }
88355
+ this.lastKeepaliveAt.set(key, now2);
88356
+ const cont = await this.sendToUser(accountId, running, user, t2("weixin.keepalivePrompt"));
88357
+ if (!cont)
88358
+ break;
88359
+ }
88360
+ }
88361
+ }
88362
+ async sendTestMessage(accountId, userId, text3) {
88363
+ const running = this.runningAccounts.get(accountId);
88364
+ if (!running) {
88365
+ throw new Error(t2("weixin.accountNotRunning"));
88366
+ }
88367
+ await running.client.sendText(userId, text3);
88368
+ }
88369
+ async sendTestMessageToBoundUser(accountId, text3) {
88370
+ const [user] = listAuthorizedWeixinUsersByAccount(accountId);
88371
+ if (!user) {
88372
+ throw new Error(t2("weixin.userNotFound"));
88373
+ }
88374
+ await this.sendTestMessage(accountId, user.userId, text3);
88375
+ }
88376
+ async startLogin(accountId) {
88377
+ const account = getWeixinAccountById(accountId);
88378
+ if (!account) {
88379
+ throw new Error(t2("weixin.accountNotFound"));
88380
+ }
88381
+ const prev = this.loginSessions.get(accountId);
88382
+ if (prev) {
88383
+ prev.abort.abort();
88384
+ this.loginSessions.delete(accountId);
88385
+ }
88386
+ const abort = new AbortController;
88387
+ const client = this.createClient({});
88388
+ return await new Promise((resolve3, reject) => {
88389
+ let resolved = false;
88390
+ const loginPromise = client.login({
88391
+ signal: abort.signal,
88392
+ onQrcode: (qr) => {
88393
+ this.loginSessions.set(accountId, {
88394
+ status: "pending",
88395
+ qrcodeUrl: qr.url,
88396
+ qrcodeId: qr.qrcodeId,
88397
+ client,
88398
+ abort
88399
+ });
88400
+ if (!resolved) {
88401
+ resolved = true;
88402
+ resolve3({ qrcodeUrl: qr.url, qrcodeId: qr.qrcodeId });
88403
+ }
88404
+ }
88405
+ });
88406
+ loginPromise.then(async (creds) => {
88407
+ await this.persistLogin(accountId, creds);
88408
+ const session = this.loginSessions.get(accountId);
88409
+ if (session) {
88410
+ session.status = "confirmed";
88411
+ }
88412
+ await this.refresh();
88413
+ }).catch((err) => {
88414
+ const message = err instanceof Error ? err.message : String(err);
88415
+ const status = /expired|timed out/i.test(message) ? "expired" : "error";
88416
+ const session = this.loginSessions.get(accountId);
88417
+ if (session) {
88418
+ session.status = status;
88419
+ session.message = message;
88420
+ } else {
88421
+ this.loginSessions.set(accountId, {
88422
+ status,
88423
+ message,
88424
+ qrcodeUrl: "",
88425
+ qrcodeId: "",
88426
+ client,
88427
+ abort
88428
+ });
88429
+ }
88430
+ if (!resolved) {
88431
+ resolved = true;
88432
+ reject(err);
88433
+ }
88434
+ });
88435
+ });
88436
+ }
88437
+ getLoginStatus(accountId) {
88438
+ const account = getWeixinAccountById(accountId);
88439
+ const loggedIn = account?.botTokenEnc != null;
88440
+ const session = this.loginSessions.get(accountId);
88441
+ if (!session) {
88442
+ return { status: loggedIn ? "confirmed" : "pending", loggedIn };
88443
+ }
88444
+ return { status: session.status, loggedIn, message: session.message };
88445
+ }
88446
+ async persistLogin(accountId, creds) {
88447
+ const botTokenEnc = await encrypt(creds.botToken);
88448
+ updateWeixinAccount(accountId, {
88449
+ weixinUin: creds.accountId,
88450
+ botTokenEnc,
88451
+ baseUrl: creds.baseUrl
88452
+ });
88453
+ this.sessionExpiredNotified.delete(accountId);
88454
+ }
88455
+ async stopAll() {
88456
+ if (this.keepaliveTimer) {
88457
+ clearInterval(this.keepaliveTimer);
88458
+ this.keepaliveTimer = null;
88459
+ }
88460
+ for (const session of this.loginSessions.values()) {
88461
+ session.abort.abort();
88462
+ }
88463
+ this.loginSessions.clear();
88464
+ const ids = Array.from(this.runningAccounts.keys());
88465
+ await Promise.all(ids.map((id) => this.stopAccount(id)));
88466
+ }
88467
+ }
88468
+ var weixinService = new WeixinService;
88469
+
88470
+ // ../../apps/gateway/src/events/channels/weixin.ts
88471
+ var EMOJI_MAP = {
88472
+ terminal_bell: "\uD83D\uDD14",
88473
+ terminal_notification: "\uD83D\uDD14",
88474
+ tmux_window_close: "\uD83E\uDE9F",
88475
+ tmux_pane_close: "\uD83D\uDCF1",
88476
+ device_tmux_missing: "\u26A0\uFE0F",
88477
+ device_disconnect: "\uD83D\uDD0C",
88478
+ session_created: "\uD83C\uDD95",
88479
+ session_closed: "\uD83D\uDEAA",
88480
+ agent_confirmation_pending: "\uD83E\uDD16",
88481
+ agent_turn_finished: "\uD83E\uDD16",
88482
+ agent_error: "\uD83E\uDD16",
88483
+ watch_triggered: "\uD83D\uDC41\uFE0F",
88484
+ watch_model_unavailable: "\uD83D\uDC41\uFE0F",
88485
+ watch_rule_error: "\uD83D\uDC41\uFE0F"
88486
+ };
88487
+
88488
+ class WeixinChannel {
88489
+ id = "weixin";
88490
+ async notify(eventType, event) {
88491
+ const settings = getSiteSettings();
88492
+ if (eventType === "terminal_bell") {
88493
+ if (!settings.enableWeixinBellPush) {
88494
+ return;
88495
+ }
88496
+ await weixinService.sendToAuthorizedUsers({ text: this.formatBellMessage(event) });
88497
+ return;
88498
+ }
88499
+ if (!settings.enableWeixinNotificationPush) {
88500
+ return;
88501
+ }
88502
+ const text3 = eventType === "terminal_notification" ? this.formatNotificationMessage(event) : this.formatGenericMessage(event, settings);
88503
+ await weixinService.sendToAuthorizedUsers({ text: text3 });
88504
+ }
88505
+ buildTerminalTopbarLabel(event) {
88506
+ const windowLabel = typeof event.tmux?.windowIndex === "number" ? `${event.tmux.windowIndex}` : event.tmux?.windowId ?? "?";
88507
+ const paneLabel = typeof event.tmux?.paneIndex === "number" ? `${event.tmux.paneIndex}` : event.tmux?.paneId ?? "?";
88508
+ return t2("notification.telegramBell.terminalTopbarLabel", {
88509
+ window: windowLabel,
88510
+ pane: paneLabel,
88511
+ device: event.device.name
88512
+ });
88513
+ }
88514
+ buildPaneMetaLines(event) {
88515
+ const lines = [];
88516
+ if (event.tmux?.paneTitle) {
88517
+ lines.push(`${t2("notification.paneTitle")}\uFF1A${event.tmux.paneTitle}`);
88518
+ }
88519
+ if (event.tmux?.paneCurrentCommand) {
88520
+ lines.push(`${t2("notification.process")}\uFF1A${event.tmux.paneCurrentCommand}`);
88521
+ }
88522
+ return lines;
88523
+ }
88524
+ formatBellMessage(event) {
88525
+ const lines = [
88526
+ t2("notification.telegramBell.title", {
88527
+ siteName: event.site.name,
88528
+ terminalTopbarLabel: this.buildTerminalTopbarLabel(event)
88529
+ })
88530
+ ];
88531
+ lines.push(...this.buildPaneMetaLines(event));
88532
+ const paneUrl = normalizeHttpUrl(buildPaneUrl(event));
88533
+ if (paneUrl) {
88534
+ lines.push("", paneUrl);
88535
+ }
88536
+ return lines.join(`
88537
+ `);
88538
+ }
88539
+ formatNotificationMessage(event) {
88540
+ const title = typeof event.payload?.title === "string" ? event.payload.title : "";
88541
+ const body = typeof event.payload?.message === "string" ? event.payload.message : "";
88542
+ const lines = [];
88543
+ if (title) {
88544
+ lines.push(title);
88545
+ }
88546
+ if (body) {
88547
+ lines.push(body);
88548
+ }
88549
+ lines.push(...this.buildPaneMetaLines(event));
88550
+ const footer = `from ${event.site.name}: ${this.buildTerminalTopbarLabel(event)}`;
88551
+ lines.push("", footer);
88552
+ const paneUrl = normalizeHttpUrl(buildPaneUrl(event));
88553
+ if (paneUrl) {
88554
+ lines.push(paneUrl);
88555
+ }
88556
+ return lines.join(`
88557
+ `);
88558
+ }
88559
+ formatGenericMessage(event, settings) {
88560
+ const eventTypeLabel = t2(`notification.eventType.${event.eventType}`);
88561
+ const windowLabel = event.tmux?.windowIndex !== undefined ? `${event.tmux.windowIndex} (${event.tmux.windowId ?? "-"})` : event.tmux?.windowId ?? "-";
88562
+ const paneLabel = event.tmux?.paneIndex !== undefined ? `${event.tmux.paneIndex} (${event.tmux.paneId ?? "-"})` : event.tmux?.paneId ?? "-";
88563
+ const lines = [
88564
+ `${EMOJI_MAP[event.eventType] ?? "\uD83D\uDCE2"} ${eventTypeLabel}`,
88565
+ `${t2("notification.site")}\uFF1A${event.site.name}`,
88566
+ `${t2("notification.time")}\uFF1A${new Date(event.timestamp).toLocaleString(toBCP47(settings.language))}`,
88567
+ `${t2("notification.device")}\uFF1A${event.device.name} (${event.device.type})`,
88568
+ `${t2("notification.window")}\uFF1A${windowLabel}`,
88569
+ `${t2("notification.pane")}\uFF1A${paneLabel}`
88570
+ ];
88571
+ lines.push(...this.buildPaneMetaLines(event));
88572
+ if (typeof event.payload?.message === "string") {
88573
+ lines.push(`${t2("notification.message")}\uFF1A${event.payload.message}`);
88574
+ }
88575
+ const paneUrl = normalizeHttpUrl(buildPaneUrl(event));
88576
+ if (paneUrl) {
88577
+ lines.push("", paneUrl);
88578
+ }
88579
+ return lines.join(`
88580
+ `);
88581
+ }
88582
+ }
88583
+ var weixinChannel = new WeixinChannel;
88584
+
88585
+ // ../../apps/gateway/src/events/index.ts
88586
+ class EventNotifier {
88587
+ bellThrottleMap = new Map;
88588
+ notificationThrottleMap = new Map;
88589
+ channels = [
88590
+ webhookChannel,
88591
+ telegramChannel,
88592
+ weixinChannel
88593
+ ];
88594
+ async notify(eventType, event) {
88595
+ const fullEvent = {
88596
+ ...event,
88597
+ eventType,
88598
+ timestamp: new Date().toISOString()
88599
+ };
88600
+ if (eventType === "terminal_bell") {
88601
+ if (!this.shouldPassBellThrottle(fullEvent)) {
88602
+ return;
88603
+ }
88604
+ } else if (eventType === "terminal_notification") {
88605
+ if (!this.shouldPassNotificationThrottle(fullEvent)) {
88606
+ return;
88607
+ }
88608
+ }
88609
+ await Promise.all(this.channels.map((channel) => channel.notify(eventType, fullEvent)));
88610
+ }
88611
+ shouldPassBellThrottle(event) {
88612
+ const settings = getSiteSettings();
88613
+ const throttleMs = Math.max(0, settings.bellThrottleSeconds) * 1000;
88614
+ if (throttleMs === 0) {
88615
+ return true;
88616
+ }
88617
+ const key = `${event.device.id}:${event.tmux?.paneId ?? "-"}:${event.eventType}`;
88618
+ const now2 = Date.now();
88619
+ const previous = this.bellThrottleMap.get(key) ?? 0;
88620
+ if (now2 - previous < throttleMs) {
88621
+ return false;
88622
+ }
88623
+ this.bellThrottleMap.set(key, now2);
88624
+ return true;
88625
+ }
88626
+ shouldPassNotificationThrottle(event) {
88627
+ const settings = getSiteSettings();
88628
+ const throttleMs = Math.max(0, settings.notificationThrottleSeconds) * 1000;
88629
+ if (throttleMs === 0) {
88630
+ return true;
88631
+ }
88632
+ const source = typeof event.payload?.source === "string" ? event.payload.source : "unknown";
88633
+ const key = `${event.device.id}:${event.tmux?.paneId ?? "-"}:notification:${source}`;
88634
+ const now2 = Date.now();
88635
+ const previous = this.notificationThrottleMap.get(key) ?? 0;
88636
+ if (now2 - previous < throttleMs) {
88637
+ return false;
88638
+ }
88639
+ this.notificationThrottleMap.set(key, now2);
88640
+ return true;
88641
+ }
88642
+ }
87300
88643
  var eventNotifier = new EventNotifier;
87301
88644
 
87302
88645
  // ../../node_modules/.bun/@ai-sdk+openai@3.0.71+68a1e3a0c4588df3/node_modules/@ai-sdk/openai/dist/index.mjs
@@ -105079,8 +106422,8 @@ function getBaseVersion() {
105079
106422
  if (cachedBase !== undefined)
105080
106423
  return cachedBase;
105081
106424
  let base = null;
105082
- if ("0.12.4") {
105083
- base = "0.12.4";
106425
+ if ("0.13.0") {
106426
+ base = "0.13.0";
105084
106427
  }
105085
106428
  if (!base && config.isProd) {
105086
106429
  base = readInstallMeta()?.cliVersion ?? null;
@@ -106878,6 +108221,18 @@ function normalizeSiteSettingsInput(body) {
106878
108221
  }
106879
108222
  updates.enableTelegramNotificationPush = body.enableTelegramNotificationPush;
106880
108223
  }
108224
+ if (body.enableWeixinBellPush !== undefined) {
108225
+ if (typeof body.enableWeixinBellPush !== "boolean") {
108226
+ throw new Error(t2("apiError.invalidRequest"));
108227
+ }
108228
+ updates.enableWeixinBellPush = body.enableWeixinBellPush;
108229
+ }
108230
+ if (body.enableWeixinNotificationPush !== undefined) {
108231
+ if (typeof body.enableWeixinNotificationPush !== "boolean") {
108232
+ throw new Error(t2("apiError.invalidRequest"));
108233
+ }
108234
+ updates.enableWeixinNotificationPush = body.enableWeixinNotificationPush;
108235
+ }
106881
108236
  if (body.sshReconnectMaxRetries !== undefined) {
106882
108237
  const value = Math.floor(Number(body.sshReconnectMaxRetries));
106883
108238
  if (Number.isNaN(value) || value < 0 || value > 20) {
@@ -106964,6 +108319,39 @@ function handleApiRequest(req, _server) {
106964
108319
  if (path.match(/^\/api\/settings\/telegram\/bots\/[^/]+\/chats\/[^/]+$/) && req.method === "DELETE") {
106965
108320
  return handleDeleteTelegramChat(path.split("/")[5], decodeURIComponent(path.split("/")[7]));
106966
108321
  }
108322
+ if (path === "/api/settings/weixin/accounts" && req.method === "GET") {
108323
+ return handleGetWeixinAccounts();
108324
+ }
108325
+ if (path === "/api/settings/weixin/accounts" && req.method === "POST") {
108326
+ return handleCreateWeixinAccount(req);
108327
+ }
108328
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+$/) && req.method === "PATCH") {
108329
+ return handleUpdateWeixinAccount(req, path.split("/")[5]);
108330
+ }
108331
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+$/) && req.method === "DELETE") {
108332
+ return handleDeleteWeixinAccount(path.split("/")[5]);
108333
+ }
108334
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+\/login\/start$/) && req.method === "POST") {
108335
+ return handleStartWeixinLogin(path.split("/")[5]);
108336
+ }
108337
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+\/login\/status$/) && req.method === "GET") {
108338
+ return handleGetWeixinLoginStatus(path.split("/")[5]);
108339
+ }
108340
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+\/test$/) && req.method === "POST") {
108341
+ return handleTestWeixinAccount(path.split("/")[5]);
108342
+ }
108343
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+\/users$/) && req.method === "GET") {
108344
+ return handleListWeixinUsers(path.split("/")[5]);
108345
+ }
108346
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+\/users\/[^/]+\/approve$/) && req.method === "POST") {
108347
+ return handleApproveWeixinUser(path.split("/")[5], decodeURIComponent(path.split("/")[7]));
108348
+ }
108349
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+\/users\/[^/]+\/test$/) && req.method === "POST") {
108350
+ return handleTestWeixinUser(path.split("/")[5], decodeURIComponent(path.split("/")[7]));
108351
+ }
108352
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+\/users\/[^/]+$/) && req.method === "DELETE") {
108353
+ return handleDeleteWeixinUser(path.split("/")[5], decodeURIComponent(path.split("/")[7]));
108354
+ }
106967
108355
  if (path.startsWith("/api/llm/")) {
106968
108356
  const llmResponse = handleLlmApiRequest(req, path);
106969
108357
  if (llmResponse) {
@@ -107266,6 +108654,152 @@ async function handleTestTelegramChat(botId, chatId) {
107266
108654
  }));
107267
108655
  return json9({ success: true });
107268
108656
  }
108657
+ async function handleGetWeixinAccounts() {
108658
+ const accounts = getWeixinAccountsWithStats();
108659
+ return json9({ accounts });
108660
+ }
108661
+ async function handleCreateWeixinAccount(req) {
108662
+ const body = await req.json();
108663
+ if (!body.name?.trim()) {
108664
+ return json9({ error: t2("weixin.accountNameRequired") }, 400);
108665
+ }
108666
+ const now2 = new Date().toISOString();
108667
+ const id = v4_default();
108668
+ createWeixinAccount({
108669
+ id,
108670
+ name: body.name.trim(),
108671
+ enabled: body.enabled ?? true,
108672
+ allowAuthRequests: body.allowAuthRequests ?? true,
108673
+ loggedIn: false,
108674
+ weixinUin: null,
108675
+ botTokenEnc: null,
108676
+ baseUrl: null,
108677
+ syncBuf: null,
108678
+ createdAt: now2,
108679
+ updatedAt: now2
108680
+ });
108681
+ return json9({ success: true, accountId: id }, 201);
108682
+ }
108683
+ async function handleUpdateWeixinAccount(req, accountId) {
108684
+ const existing = getWeixinAccountById(accountId);
108685
+ if (!existing) {
108686
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108687
+ }
108688
+ const body = await req.json();
108689
+ const updates = {};
108690
+ if (body.name !== undefined) {
108691
+ const value = body.name.trim();
108692
+ if (!value) {
108693
+ return json9({ error: t2("weixin.accountNameRequired") }, 400);
108694
+ }
108695
+ updates.name = value;
108696
+ }
108697
+ if (body.enabled !== undefined) {
108698
+ updates.enabled = body.enabled;
108699
+ }
108700
+ if (body.allowAuthRequests !== undefined) {
108701
+ updates.allowAuthRequests = body.allowAuthRequests;
108702
+ }
108703
+ updateWeixinAccount(accountId, updates);
108704
+ await weixinService.refresh();
108705
+ return json9({ success: true });
108706
+ }
108707
+ async function handleDeleteWeixinAccount(accountId) {
108708
+ const existing = getWeixinAccountById(accountId);
108709
+ if (!existing) {
108710
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108711
+ }
108712
+ deleteWeixinAccount(accountId);
108713
+ await weixinService.refresh();
108714
+ return json9({ success: true });
108715
+ }
108716
+ async function handleStartWeixinLogin(accountId) {
108717
+ const existing = getWeixinAccountById(accountId);
108718
+ if (!existing) {
108719
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108720
+ }
108721
+ try {
108722
+ const result = await weixinService.startLogin(accountId);
108723
+ return json9(result);
108724
+ } catch (err) {
108725
+ return json9({ error: err instanceof Error ? err.message : t2("weixin.loginFailed") }, 502);
108726
+ }
108727
+ }
108728
+ async function handleGetWeixinLoginStatus(accountId) {
108729
+ const existing = getWeixinAccountById(accountId);
108730
+ if (!existing) {
108731
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108732
+ }
108733
+ return json9(weixinService.getLoginStatus(accountId));
108734
+ }
108735
+ async function handleListWeixinUsers(accountId) {
108736
+ const existing = getWeixinAccountById(accountId);
108737
+ if (!existing) {
108738
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108739
+ }
108740
+ const users = listWeixinUsersByAccount(accountId);
108741
+ return json9({ users });
108742
+ }
108743
+ async function handleApproveWeixinUser(accountId, userId) {
108744
+ const existing = getWeixinAccountById(accountId);
108745
+ if (!existing) {
108746
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108747
+ }
108748
+ const user = approveWeixinUser(accountId, userId);
108749
+ if (!user) {
108750
+ return json9({ error: t2("weixin.userNotFound") }, 404);
108751
+ }
108752
+ const settings = getSiteSettings();
108753
+ try {
108754
+ await weixinService.sendTestMessage(accountId, userId, t2("weixin.approveMessageTemplate", {
108755
+ accountName: existing.name,
108756
+ time: new Date().toLocaleString(toBCP47(settings.language))
108757
+ }));
108758
+ } catch (err) {
108759
+ console.error("[weixin] approve ack failed:", err);
108760
+ }
108761
+ return json9({ user });
108762
+ }
108763
+ async function handleTestWeixinUser(accountId, userId) {
108764
+ const existing = getWeixinAccountById(accountId);
108765
+ if (!existing) {
108766
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108767
+ }
108768
+ const settings = getSiteSettings();
108769
+ try {
108770
+ await weixinService.sendTestMessage(accountId, userId, t2("weixin.testMessageTemplate", {
108771
+ siteName: settings.siteName,
108772
+ time: new Date().toLocaleString(toBCP47(settings.language))
108773
+ }));
108774
+ } catch (err) {
108775
+ return json9({ error: err instanceof Error ? err.message : t2("weixin.testMessageFailed") }, 400);
108776
+ }
108777
+ return json9({ success: true });
108778
+ }
108779
+ async function handleTestWeixinAccount(accountId) {
108780
+ const existing = getWeixinAccountById(accountId);
108781
+ if (!existing) {
108782
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108783
+ }
108784
+ const settings = getSiteSettings();
108785
+ try {
108786
+ await weixinService.sendTestMessageToBoundUser(accountId, t2("weixin.testMessageTemplate", {
108787
+ siteName: settings.siteName,
108788
+ time: new Date().toLocaleString(toBCP47(settings.language))
108789
+ }));
108790
+ } catch (err) {
108791
+ return json9({ error: err instanceof Error ? err.message : t2("weixin.testMessageFailed") }, 400);
108792
+ }
108793
+ return json9({ success: true });
108794
+ }
108795
+ async function handleDeleteWeixinUser(accountId, userId) {
108796
+ const existing = getWeixinAccountById(accountId);
108797
+ if (!existing) {
108798
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108799
+ }
108800
+ deleteWeixinUser(accountId, userId);
108801
+ return json9({ success: true });
108802
+ }
107269
108803
  async function handleGetWebhooks() {
107270
108804
  const webhooks = getAllWebhookEndpoints();
107271
108805
  return json9({ webhooks });
@@ -108899,12 +110433,14 @@ async function createGatewayRuntime(options = {}) {
108899
110433
  wsServer.broadcastDeviceError(deviceId, payload);
108900
110434
  });
108901
110435
  await telegramService.refresh();
110436
+ await weixinService.refresh();
108902
110437
  await pushSupervisor.start();
108903
110438
  await agentSupervisor.start();
108904
110439
  await watchService.start();
108905
110440
  try {
108906
110441
  const settings = getSiteSettings();
108907
110442
  await telegramService.sendGatewayOnlineMessage(settings.siteName);
110443
+ await weixinService.sendGatewayOnlineMessage(settings.siteName);
108908
110444
  } catch (err) {
108909
110445
  console.error("[gateway] failed to push startup message:", err);
108910
110446
  }
@@ -108949,6 +110485,7 @@ async function createGatewayRuntime(options = {}) {
108949
110485
  await pushSupervisor.stopAll();
108950
110486
  await tmuxRuntimeRegistry.shutdownAll();
108951
110487
  await telegramService.stopAll();
110488
+ await weixinService.stopAll();
108952
110489
  }
108953
110490
  };
108954
110491
  }