tmex-cli 0.12.4 → 0.14.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 (90) hide show
  1. package/CHANGELOG.md +22 -6
  2. package/dist/cli-node.js +492 -87
  3. package/dist/runtime/server.js +1912 -132
  4. package/package.json +1 -1
  5. package/resources/fe-dist/assets/DevicePage-Ctq31eWb.js +19 -0
  6. package/resources/fe-dist/assets/DevicesPage-DGo44NW0.js +1 -0
  7. package/resources/fe-dist/assets/{FilePage-D1t8_zlX.js → FilePage-BpubOqjN.js} +3 -4
  8. package/resources/fe-dist/assets/SettingsPage-QT6fy6XA.js +39 -0
  9. package/resources/fe-dist/assets/agent-tab-A2FeMPwL.js +38 -0
  10. package/resources/fe-dist/assets/api-CGfOwFKm.js +10 -0
  11. package/resources/fe-dist/assets/{arc-Cv0H1Phu.js → arc-B2dphCVi.js} +1 -2
  12. package/resources/fe-dist/assets/{architectureDiagram-3BPJPVTR-DG7npZeK.js → architectureDiagram-3BPJPVTR-BpvEhd8M.js} +1 -2
  13. package/resources/fe-dist/assets/{blockDiagram-GPEHLZMM-weB7PKUK.js → blockDiagram-GPEHLZMM-CI_NnIJz.js} +1 -2
  14. package/resources/fe-dist/assets/{c4Diagram-AAUBKEIU-C0sEabtp.js → c4Diagram-AAUBKEIU-Y6RrOc7Y.js} +1 -2
  15. package/resources/fe-dist/assets/{card-Spfb5xRZ.js → card-CydUB3N3.js} +1 -2
  16. package/resources/fe-dist/assets/channel-Ce3VMo9B.js +1 -0
  17. package/resources/fe-dist/assets/{chunk-2J33WTMH-BxOp2sIC.js → chunk-2J33WTMH-BQN_fYaa.js} +1 -2
  18. package/resources/fe-dist/assets/{chunk-4BX2VUAB-bhg4hxVT.js → chunk-4BX2VUAB-BExarxjk.js} +1 -2
  19. package/resources/fe-dist/assets/{chunk-55IACEB6-v6jayOak.js → chunk-55IACEB6-DeOEgcn_.js} +1 -2
  20. package/resources/fe-dist/assets/{chunk-727SXJPM-IY0KVws8.js → chunk-727SXJPM-CwkxCtPA.js} +1 -2
  21. package/resources/fe-dist/assets/{chunk-AQP2D5EJ-CgwymoWa.js → chunk-AQP2D5EJ-_IoTtvOb.js} +1 -2
  22. package/resources/fe-dist/assets/{chunk-FMBD7UC4-DN35690f.js → chunk-FMBD7UC4-6pvmQ3Lj.js} +1 -2
  23. package/resources/fe-dist/assets/{chunk-ND2GUHAM-lJmtxdhS.js → chunk-ND2GUHAM-CxpGf5zM.js} +1 -2
  24. package/resources/fe-dist/assets/{chunk-QZHKN3VN-DFeaB8rn.js → chunk-QZHKN3VN-DOMH0v_e.js} +1 -2
  25. package/resources/fe-dist/assets/classDiagram-4FO5ZUOK-BjK-bnh-.js +1 -0
  26. package/resources/fe-dist/assets/classDiagram-v2-Q7XG4LA2-BjK-bnh-.js +1 -0
  27. package/resources/fe-dist/assets/copy-DnlW35bA.js +6 -0
  28. package/resources/fe-dist/assets/{cose-bilkent-S5V4N54A-Dw2yYQz4.js → cose-bilkent-S5V4N54A-D03NElzH.js} +1 -2
  29. package/resources/fe-dist/assets/cytoscape.esm-DTSO7Bv0.js +0 -1
  30. package/resources/fe-dist/assets/{dagre-BM42HDAG-BeMjJ7ln.js → dagre-BM42HDAG-CVOxLhKQ.js} +1 -2
  31. package/resources/fe-dist/assets/defaultLocale-DX6XiGOO.js +0 -1
  32. package/resources/fe-dist/assets/{diagram-2AECGRRQ-Dur8_CuN.js → diagram-2AECGRRQ-p564GAo-.js} +1 -2
  33. package/resources/fe-dist/assets/{diagram-5GNKFQAL-GJc7gVa_.js → diagram-5GNKFQAL-C_pgnQLt.js} +1 -2
  34. package/resources/fe-dist/assets/{diagram-KO2AKTUF-pxckXfjw.js → diagram-KO2AKTUF-B36jioML.js} +1 -2
  35. package/resources/fe-dist/assets/{diagram-LMA3HP47-B9E8-X7C.js → diagram-LMA3HP47-B8P4WWeq.js} +1 -2
  36. package/resources/fe-dist/assets/{diagram-OG6HWLK6-DrhHw5wy.js → diagram-OG6HWLK6-CTL4zJlz.js} +1 -2
  37. package/resources/fe-dist/assets/en_US-DRPd4vPi.js +1 -0
  38. package/resources/fe-dist/assets/{erDiagram-TEJ5UH35-DY9Wg1A1.js → erDiagram-TEJ5UH35-DbKPL3Fa.js} +1 -2
  39. package/resources/fe-dist/assets/files-tab-CeUM00x8.js +70 -0
  40. package/resources/fe-dist/assets/{flowDiagram-I6XJVG4X-eq3WE9d6.js → flowDiagram-I6XJVG4X-DvOU6wtN.js} +1 -2
  41. package/resources/fe-dist/assets/{ganttDiagram-6RSMTGT7-BlXISXe1.js → ganttDiagram-6RSMTGT7-CwgNky8L.js} +1 -2
  42. package/resources/fe-dist/assets/{gitGraphDiagram-PVQCEYII-Bg-xHeIY.js → gitGraphDiagram-PVQCEYII-B3U-hNGe.js} +1 -2
  43. package/resources/fe-dist/assets/graph-CAnANduQ.js +0 -1
  44. package/resources/fe-dist/assets/index-D-q7dOhH.css +1 -0
  45. package/resources/fe-dist/assets/index-DAElYydw.js +299 -0
  46. package/resources/fe-dist/assets/index-DBHh6Xu1.js +29 -0
  47. package/resources/fe-dist/assets/{infoDiagram-5YYISTIA-CW2drAHG.js → infoDiagram-5YYISTIA-BmcWF7v1.js} +1 -2
  48. package/resources/fe-dist/assets/init-Gi6I4Gst.js +0 -1
  49. package/resources/fe-dist/assets/{ishikawaDiagram-YF4QCWOH-Qchz0nqR.js → ishikawaDiagram-YF4QCWOH-Dr8C0MtS.js} +1 -2
  50. package/resources/fe-dist/assets/ja_JP-DHREHMZZ.js +1 -0
  51. package/resources/fe-dist/assets/{journeyDiagram-JHISSGLW-Dk-UsexE.js → journeyDiagram-JHISSGLW-DzlvXb4K.js} +1 -2
  52. package/resources/fe-dist/assets/{kanban-definition-UN3LZRKU-DKeGp7b5.js → kanban-definition-UN3LZRKU-4aBWZlcm.js} +1 -2
  53. package/resources/fe-dist/assets/layout-DGIYPm2g.js +0 -1
  54. package/resources/fe-dist/assets/{linear-Bt4f_qDW.js → linear-_wXLepjd.js} +1 -2
  55. package/resources/fe-dist/assets/{markdown-preview-DXv9GzaA.js → markdown-preview-BX0o83rG.js} +6 -7
  56. package/resources/fe-dist/assets/{mermaid.core-B-wSkmMd.js → mermaid.core-DoE3dx_X.js} +5 -6
  57. package/resources/fe-dist/assets/{mindmap-definition-RKZ34NQL-DXBwCMXu.js → mindmap-definition-RKZ34NQL-CKpq2s43.js} +1 -2
  58. package/resources/fe-dist/assets/ordinal-BENe2yWM.js +0 -1
  59. package/resources/fe-dist/assets/{pieDiagram-4H26LBE5--KxRgbWG.js → pieDiagram-4H26LBE5-DlW1TLza.js} +1 -2
  60. package/resources/fe-dist/assets/{quadrantDiagram-W4KKPZXB-DqXsCI9E.js → quadrantDiagram-W4KKPZXB-NEwxpMVe.js} +1 -2
  61. package/resources/fe-dist/assets/{requirementDiagram-4Y6WPE33-C2KeTWqB.js → requirementDiagram-4Y6WPE33-CGysr6uH.js} +1 -2
  62. package/resources/fe-dist/assets/{sankeyDiagram-5OEKKPKP-CC5tcr7d.js → sankeyDiagram-5OEKKPKP-j8yXOgys.js} +1 -2
  63. package/resources/fe-dist/assets/selection-clipboard-Dq6Zemfd.js +1 -0
  64. package/resources/fe-dist/assets/send-BtOPYsPT.js +11 -0
  65. package/resources/fe-dist/assets/{sequenceDiagram-3UESZ5HK-CqlMDtOG.js → sequenceDiagram-3UESZ5HK-BRPWK9ZU.js} +1 -2
  66. package/resources/fe-dist/assets/{stateDiagram-AJRCARHV-CVcrlBiw.js → stateDiagram-AJRCARHV-C8ckQlA7.js} +1 -2
  67. package/resources/fe-dist/assets/stateDiagram-v2-BHNVJYJU-Cm0vyyJy.js +1 -0
  68. package/resources/fe-dist/assets/{terminal-settings-panel-CYV9svzz.js → terminal-settings-panel-ZnL_cBER.js} +6 -7
  69. package/resources/fe-dist/assets/{timeline-definition-PNZ67QCA-CEBskfkL.js → timeline-definition-PNZ67QCA-BXMFPa99.js} +1 -2
  70. package/resources/fe-dist/assets/transfer-toast-CUkPc73G.js +6 -0
  71. package/resources/fe-dist/assets/triangle-alert-DyM9tU7d.js +6 -0
  72. package/resources/fe-dist/assets/{vennDiagram-CIIHVFJN-CrxyVTtu.js → vennDiagram-CIIHVFJN-Bk8oPY9E.js} +1 -2
  73. package/resources/fe-dist/assets/{wardley-L42UT6IY-CSmsRb8z.js → wardley-L42UT6IY-Bi_GTP2_.js} +1 -2
  74. package/resources/fe-dist/assets/{wardleyDiagram-YWT4CUSO-DoDzNeOA.js → wardleyDiagram-YWT4CUSO-CaPDmxFM.js} +1 -2
  75. package/resources/fe-dist/assets/{xychartDiagram-2RQKCTM6-Dbq_OKsv.js → xychartDiagram-2RQKCTM6-EHogasXy.js} +1 -2
  76. package/resources/fe-dist/assets/zap-CBgYyGBV.js +6 -0
  77. package/resources/fe-dist/assets/zh_CN-DMEKXuFd.js +1 -0
  78. package/resources/fe-dist/index.html +2 -2
  79. package/resources/gateway-drizzle/0010_lucky_kabuki.sql +32 -0
  80. package/resources/gateway-drizzle/0011_stormy_sauron.sql +1 -0
  81. package/resources/gateway-drizzle/meta/_journal.json +14 -0
  82. package/resources/fe-dist/assets/DevicePage-C4JS6baR.js +0 -15
  83. package/resources/fe-dist/assets/DevicesPage-p0CfWP1T.js +0 -2
  84. package/resources/fe-dist/assets/SettingsPage-Cq6n8Xjc.js +0 -27
  85. package/resources/fe-dist/assets/channel-DDiDKfY0.js +0 -2
  86. package/resources/fe-dist/assets/classDiagram-4FO5ZUOK-CXFjdzka.js +0 -2
  87. package/resources/fe-dist/assets/classDiagram-v2-Q7XG4LA2-CXFjdzka.js +0 -2
  88. package/resources/fe-dist/assets/index-CXIj7HZi.css +0 -1
  89. package/resources/fe-dist/assets/index-CYmVLCAD.js +0 -488
  90. package/resources/fe-dist/assets/stateDiagram-v2-BHNVJYJU-B0BDuZBM.js +0 -2
@@ -22654,6 +22654,8 @@ var I18N_RESOURCES = {
22654
22654
  session: "Tmux Session Name",
22655
22655
  sessionPlaceholder: "tmex",
22656
22656
  sessionHint: 'Leave empty to use default "tmex"',
22657
+ defaultWorkingDir: "Default Working Directory",
22658
+ defaultWorkingDirPlaceholder: "Leave empty for user home directory",
22657
22659
  authMode: "Authentication Mode",
22658
22660
  authPassword: "Password",
22659
22661
  authKey: "Private Key",
@@ -22708,9 +22710,12 @@ var I18N_RESOURCES = {
22708
22710
  noDeviceSelected: "No device selected",
22709
22711
  windowClosed: "Current window has been closed, please select a window from the sidebar.",
22710
22712
  paneClosed: "Current pane has been closed, please select a pane from the sidebar.",
22711
- bellNotification: "Terminal Bell Notification",
22712
- bellDescription: "Window {{window}} \xB7 Pane {{pane}}",
22713
+ bellNotification: "Terminal Bell",
22714
+ bellDescriptionWithTitle: "Window {{window}} \xB7 {{paneLabel}}",
22713
22715
  bellFallback: "Received tmux bell",
22716
+ notificationFallbackTitle: "Terminal Notification",
22717
+ notificationSourceLabel: "From {{source}}",
22718
+ notificationFallbackDetail: "Terminal notification",
22714
22719
  paneTitle: "Pane {{index}}",
22715
22720
  activePane: "Current Pane",
22716
22721
  activeWindow: "Current Window",
@@ -22745,6 +22750,8 @@ var I18N_RESOURCES = {
22745
22750
  enableBrowserNotificationToast: "Enable Browser Notification Toast",
22746
22751
  enableTelegramBellPush: "Enable Telegram Bell Push",
22747
22752
  enableTelegramNotificationPush: "Enable Telegram Notification Push",
22753
+ enableWeixinBellPush: "Enable WeChat Bell Push",
22754
+ enableWeixinNotificationPush: "Enable WeChat Notification Push",
22748
22755
  sshReconnectRetries: "SSH Reconnect Retries",
22749
22756
  sshReconnectDelay: "SSH Reconnect Delay (seconds)",
22750
22757
  language: "Language",
@@ -22984,6 +22991,81 @@ Time: {{time}}`,
22984
22991
  botNotFound: "Bot not found or unavailable",
22985
22992
  botNotRunning: "Bot is not running or unavailable"
22986
22993
  },
22994
+ weixin: {
22995
+ title: "WeChat (ClawBot) Management",
22996
+ subtitle: "Push tmex alerts to your personal WeChat via the iLink bot protocol.",
22997
+ 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.",
22998
+ accountName: "Account Name",
22999
+ accountNamePlaceholder: "e.g., my-wechat",
23000
+ addAccount: "Add Account",
23001
+ editAccount: "Edit Account",
23002
+ enableAccount: "Enable Account",
23003
+ allowAuthRequests: "Allow Authorization Requests",
23004
+ scanToLogin: "Scan to Log In",
23005
+ relogin: "Re-login (Re-scan)",
23006
+ loggedIn: "Logged in",
23007
+ notLoggedIn: "Not logged in",
23008
+ scanQrcodeHint: "Open WeChat, scan the QR code, and confirm on your phone.",
23009
+ loginPending: "Waiting for scan\u2026",
23010
+ loginConfirmed: "Login confirmed.",
23011
+ loginExpired: "QR code expired. Please retry.",
23012
+ loginError: "Login failed: {{message}}",
23013
+ loginFailed: "Login failed",
23014
+ refreshQrcode: "Refresh QR Code",
23015
+ closeLogin: "Close",
23016
+ pendingUsers: "Pending Authorization",
23017
+ authorizedUsers: "Authorized Users",
23018
+ noPendingUsers: "No pending users",
23019
+ noAuthorizedUsers: "No authorized users",
23020
+ needsReactivation: "Session expired",
23021
+ reactivationHint: "Ask this user to send the bot any message to re-activate alerts.",
23022
+ approve: "Approve",
23023
+ revokeAuth: "Revoke",
23024
+ removeUser: "Remove",
23025
+ testMessage: "Test Message",
23026
+ sendTestMessage: "Send Test Message",
23027
+ deleteAccount: "Delete Account",
23028
+ accountCreated: "Account created",
23029
+ accountUpdated: "Account updated",
23030
+ accountDeleted: "Account deleted",
23031
+ authApproved: "Authorization approved",
23032
+ userRemoved: "User removed",
23033
+ testMessageSent: "Test message sent",
23034
+ createFailed: "Failed to create account",
23035
+ updateFailed: "Failed to update account",
23036
+ deleteFailed: "Failed to delete account",
23037
+ approveFailed: "Failed to approve",
23038
+ removeFailed: "Failed to remove user",
23039
+ testMessageFailed: "Failed to send test message",
23040
+ loadAccountsFailed: "Failed to load accounts",
23041
+ loadUsersFailed: "Failed to load users",
23042
+ noAccounts: "No accounts yet, add one first.",
23043
+ userCount: "{{authorized}} authorized / {{pending}} pending (max 16)",
23044
+ userId: "WeChat User ID",
23045
+ applyTime: "First Contact",
23046
+ expand: "Expand",
23047
+ collapse: "Collapse",
23048
+ accountNameRequired: "Account name is required",
23049
+ accountNotFound: "Account not found or unavailable",
23050
+ accountNotRunning: "Account is not logged in or not running",
23051
+ userNotFound: "User not found",
23052
+ authSuccess: "\u2705 Authorized. You will now receive notifications.",
23053
+ authPending: "\u23F3 Authorization request received. Please approve in tmex settings.",
23054
+ testMessageTemplate: `\uD83E\uDDEA Test Message
23055
+ Site: {{siteName}}
23056
+ Time: {{time}}`,
23057
+ approveMessageTemplate: `\u2705 Authorized via tmex.
23058
+ Account: {{accountName}}
23059
+ Time: {{time}}`,
23060
+ keepalivePrompt: "\u3010tmex\u3011To keep notifications flowing, just reply with anything to keep the session alive \uD83D\uDE4F",
23061
+ scanConfirmedSendHint: "Scan confirmed! Now send any message to this bot in WeChat to finish binding.",
23062
+ bindingInProgress: "Message received, finishing binding\u2026",
23063
+ bindSuccess: "WeChat bound. You will now receive notifications.",
23064
+ bound: "Bound",
23065
+ unbound: "Not bound",
23066
+ bindAction: "Bind (scan)",
23067
+ gatewayOnline: "\uD83D\uDFE2 tmex online @ {{siteName}}"
23068
+ },
22987
23069
  webhook: {
22988
23070
  title: "Webhooks",
22989
23071
  url: "Webhook URL",
@@ -23568,6 +23650,8 @@ Time: {{time}}`,
23568
23650
  session: "Tmux \u4F1A\u8BDD\u540D\u79F0",
23569
23651
  sessionPlaceholder: "tmex",
23570
23652
  sessionHint: '\u7559\u7A7A\u5C06\u4F7F\u7528\u9ED8\u8BA4\u503C "tmex"',
23653
+ defaultWorkingDir: "\u9ED8\u8BA4\u5DE5\u4F5C\u76EE\u5F55",
23654
+ defaultWorkingDirPlaceholder: "\u7559\u7A7A\u4F7F\u7528\u7528\u6237 home \u76EE\u5F55",
23571
23655
  authMode: "\u8BA4\u8BC1\u65B9\u5F0F",
23572
23656
  authPassword: "\u5BC6\u7801",
23573
23657
  authKey: "\u79C1\u94A5",
@@ -23622,9 +23706,12 @@ Time: {{time}}`,
23622
23706
  noDeviceSelected: "\u672A\u9009\u62E9\u8BBE\u5907",
23623
23707
  windowClosed: "\u5F53\u524D\u7A97\u53E3\u5DF2\u5173\u95ED\uFF0C\u8BF7\u5728\u4FA7\u8FB9\u680F\u91CD\u65B0\u9009\u62E9\u7A97\u53E3\u3002",
23624
23708
  paneClosed: "\u5F53\u524D Pane \u5DF2\u5173\u95ED\uFF0C\u8BF7\u5728\u4FA7\u8FB9\u680F\u91CD\u65B0\u9009\u62E9 Pane\u3002",
23625
- bellNotification: "\u7EC8\u7AEF Bell \u63D0\u9192",
23626
- bellDescription: "\u7A97\u53E3 {{window}} \xB7 Pane {{pane}}",
23709
+ bellNotification: "\u7EC8\u7AEF Bell",
23710
+ bellDescriptionWithTitle: "\u7A97\u53E3 {{window}} \xB7 {{paneLabel}}",
23627
23711
  bellFallback: "\u6536\u5230 tmux bell",
23712
+ notificationFallbackTitle: "\u7EC8\u7AEF\u901A\u77E5",
23713
+ notificationSourceLabel: "\u6765\u81EA {{source}}",
23714
+ notificationFallbackDetail: "\u7EC8\u7AEF\u901A\u77E5",
23628
23715
  paneTitle: "Pane {{index}}",
23629
23716
  activePane: "\u5F53\u524D pane",
23630
23717
  activeWindow: "\u5F53\u524D\u7A97\u53E3",
@@ -23659,6 +23746,8 @@ Time: {{time}}`,
23659
23746
  enableBrowserNotificationToast: "\u5F00\u542F\u6D4F\u89C8\u5668\u901A\u77E5 Toast",
23660
23747
  enableTelegramBellPush: "\u5F00\u542F Telegram Bell \u63A8\u9001",
23661
23748
  enableTelegramNotificationPush: "\u5F00\u542F Telegram \u901A\u77E5\u63A8\u9001",
23749
+ enableWeixinBellPush: "\u542F\u7528\u5FAE\u4FE1\u54CD\u94C3\u63A8\u9001",
23750
+ enableWeixinNotificationPush: "\u542F\u7528\u5FAE\u4FE1\u901A\u77E5\u63A8\u9001",
23662
23751
  sshReconnectRetries: "SSH \u91CD\u8FDE\u6B21\u6570",
23663
23752
  sshReconnectDelay: "SSH \u91CD\u8FDE\u7B49\u5F85\uFF08\u79D2\uFF09",
23664
23753
  language: "\u8BED\u8A00",
@@ -23898,6 +23987,81 @@ Bot\uFF1A{{botName}}
23898
23987
  botNotFound: "Bot \u672A\u542F\u52A8\u6216\u4E0D\u53EF\u7528",
23899
23988
  botNotRunning: "Bot \u672A\u542F\u52A8\u6216\u4E0D\u53EF\u7528"
23900
23989
  },
23990
+ weixin: {
23991
+ title: "\u5FAE\u4FE1 (ClawBot) \u7BA1\u7406",
23992
+ subtitle: "\u901A\u8FC7 iLink bot \u534F\u8BAE\u628A tmex \u544A\u8B66\u63A8\u9001\u5230\u4F60\u7684\u4E2A\u4EBA\u5FAE\u4FE1\u3002",
23993
+ 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",
23994
+ accountName: "\u8D26\u53F7\u540D\u79F0",
23995
+ accountNamePlaceholder: "\u5982\uFF1A\u6211\u7684\u5FAE\u4FE1",
23996
+ addAccount: "\u6DFB\u52A0\u8D26\u53F7",
23997
+ editAccount: "\u7F16\u8F91\u8D26\u53F7",
23998
+ enableAccount: "\u542F\u7528\u8D26\u53F7",
23999
+ allowAuthRequests: "\u5141\u8BB8\u6388\u6743\u7533\u8BF7",
24000
+ scanToLogin: "\u626B\u7801\u767B\u5F55",
24001
+ relogin: "\u91CD\u65B0\u767B\u5F55\uFF08\u91CD\u65B0\u626B\u7801\uFF09",
24002
+ loggedIn: "\u5DF2\u767B\u5F55",
24003
+ notLoggedIn: "\u672A\u767B\u5F55",
24004
+ scanQrcodeHint: "\u6253\u5F00\u5FAE\u4FE1\u626B\u63CF\u4E8C\u7EF4\u7801\uFF0C\u5E76\u5728\u624B\u673A\u4E0A\u786E\u8BA4\u3002",
24005
+ loginPending: "\u7B49\u5F85\u626B\u7801\u2026",
24006
+ loginConfirmed: "\u767B\u5F55\u5DF2\u786E\u8BA4\u3002",
24007
+ loginExpired: "\u4E8C\u7EF4\u7801\u5DF2\u8FC7\u671F\uFF0C\u8BF7\u91CD\u8BD5\u3002",
24008
+ loginError: "\u767B\u5F55\u5931\u8D25\uFF1A{{message}}",
24009
+ loginFailed: "\u767B\u5F55\u5931\u8D25",
24010
+ refreshQrcode: "\u5237\u65B0\u4E8C\u7EF4\u7801",
24011
+ closeLogin: "\u5173\u95ED",
24012
+ pendingUsers: "\u5F85\u6388\u6743",
24013
+ authorizedUsers: "\u5DF2\u6388\u6743\u7528\u6237",
24014
+ noPendingUsers: "\u6682\u65E0\u5F85\u6388\u6743\u7528\u6237",
24015
+ noAuthorizedUsers: "\u6682\u65E0\u5DF2\u6388\u6743\u7528\u6237",
24016
+ needsReactivation: "\u4F1A\u8BDD\u5DF2\u8FC7\u671F",
24017
+ reactivationHint: "\u8BF7\u8BE5\u7528\u6237\u7ED9 bot \u53D1\u6761\u6D88\u606F\u4EE5\u91CD\u65B0\u6FC0\u6D3B\u544A\u8B66\u3002",
24018
+ approve: "\u6279\u51C6",
24019
+ revokeAuth: "\u64A4\u9500\u6388\u6743",
24020
+ removeUser: "\u79FB\u9664",
24021
+ testMessage: "\u6D4B\u8BD5\u6D88\u606F",
24022
+ sendTestMessage: "\u53D1\u9001\u6D4B\u8BD5\u6D88\u606F",
24023
+ deleteAccount: "\u5220\u9664\u8D26\u53F7",
24024
+ accountCreated: "\u8D26\u53F7\u5DF2\u521B\u5EFA",
24025
+ accountUpdated: "\u8D26\u53F7\u5DF2\u66F4\u65B0",
24026
+ accountDeleted: "\u8D26\u53F7\u5DF2\u5220\u9664",
24027
+ authApproved: "\u6388\u6743\u5DF2\u6279\u51C6",
24028
+ userRemoved: "\u7528\u6237\u5DF2\u79FB\u9664",
24029
+ testMessageSent: "\u6D4B\u8BD5\u6D88\u606F\u5DF2\u53D1\u9001",
24030
+ createFailed: "\u521B\u5EFA\u8D26\u53F7\u5931\u8D25",
24031
+ updateFailed: "\u66F4\u65B0\u8D26\u53F7\u5931\u8D25",
24032
+ deleteFailed: "\u5220\u9664\u8D26\u53F7\u5931\u8D25",
24033
+ approveFailed: "\u6279\u51C6\u5931\u8D25",
24034
+ removeFailed: "\u79FB\u9664\u7528\u6237\u5931\u8D25",
24035
+ testMessageFailed: "\u53D1\u9001\u6D4B\u8BD5\u6D88\u606F\u5931\u8D25",
24036
+ loadAccountsFailed: "\u52A0\u8F7D\u8D26\u53F7\u5217\u8868\u5931\u8D25",
24037
+ loadUsersFailed: "\u52A0\u8F7D\u7528\u6237\u5217\u8868\u5931\u8D25",
24038
+ noAccounts: "\u8FD8\u6CA1\u6709\u8D26\u53F7\uFF0C\u5148\u6DFB\u52A0\u4E00\u4E2A\u3002",
24039
+ userCount: "{{authorized}} \u5DF2\u6388\u6743 / {{pending}} \u5F85\u6388\u6743\uFF08\u4E0A\u9650 16\uFF09",
24040
+ userId: "\u5FAE\u4FE1\u7528\u6237 ID",
24041
+ applyTime: "\u9996\u6B21\u8054\u7CFB\u65F6\u95F4",
24042
+ expand: "\u5C55\u5F00",
24043
+ collapse: "\u6536\u8D77",
24044
+ accountNameRequired: "\u8D26\u53F7\u540D\u79F0\u4E0D\u80FD\u4E3A\u7A7A",
24045
+ accountNotFound: "\u8D26\u53F7\u4E0D\u5B58\u5728\u6216\u4E0D\u53EF\u7528",
24046
+ accountNotRunning: "\u8D26\u53F7\u672A\u767B\u5F55\u6216\u672A\u8FD0\u884C",
24047
+ userNotFound: "\u7528\u6237\u4E0D\u5B58\u5728",
24048
+ authSuccess: "\u2705 \u5DF2\u6388\u6743\uFF0C\u4F60\u5C06\u5F00\u59CB\u6536\u5230\u901A\u77E5\u3002",
24049
+ authPending: "\u23F3 \u5DF2\u6536\u5230\u6388\u6743\u7533\u8BF7\uFF0C\u8BF7\u5728 tmex \u8BBE\u7F6E\u4E2D\u6279\u51C6\u3002",
24050
+ testMessageTemplate: `\uD83E\uDDEA \u6D4B\u8BD5\u6D88\u606F
24051
+ \u7AD9\u70B9\uFF1A{{siteName}}
24052
+ \u65F6\u95F4\uFF1A{{time}}`,
24053
+ approveMessageTemplate: `\u2705 \u5DF2\u901A\u8FC7 tmex \u6388\u6743\u3002
24054
+ \u8D26\u53F7\uFF1A{{accountName}}
24055
+ \u65F6\u95F4\uFF1A{{time}}`,
24056
+ 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",
24057
+ 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",
24058
+ bindingInProgress: "\u5DF2\u6536\u5230\u6D88\u606F\uFF0C\u6B63\u5728\u5B8C\u6210\u7ED1\u5B9A\u2026",
24059
+ bindSuccess: "\u5FAE\u4FE1\u5DF2\u7ED1\u5B9A\uFF0C\u5F00\u59CB\u63A5\u6536\u901A\u77E5\u3002",
24060
+ bound: "\u5DF2\u7ED1\u5B9A",
24061
+ unbound: "\u672A\u7ED1\u5B9A",
24062
+ bindAction: "\u626B\u7801\u7ED1\u5B9A",
24063
+ gatewayOnline: "\uD83D\uDFE2 tmex \u5DF2\u4E0A\u7EBF @ {{siteName}}"
24064
+ },
23901
24065
  webhook: {
23902
24066
  title: "Webhooks",
23903
24067
  url: "Webhook \u5730\u5740",
@@ -24482,6 +24646,8 @@ Bot\uFF1A{{botName}}
24482
24646
  session: "Tmux \u30BB\u30C3\u30B7\u30E7\u30F3\u540D",
24483
24647
  sessionPlaceholder: "tmex",
24484
24648
  sessionHint: "\u7A7A\u306E\u5834\u5408\u3001\u30C7\u30D5\u30A9\u30EB\u30C8\u306E\u300Ctmex\u300D\u304C\u4F7F\u7528\u3055\u308C\u307E\u3059",
24649
+ defaultWorkingDir: "\u30C7\u30D5\u30A9\u30EB\u30C8\u4F5C\u696D\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA",
24650
+ defaultWorkingDirPlaceholder: "\u7A7A\u306E\u5834\u5408\u3001\u30E6\u30FC\u30B6\u30FC\u306E\u30DB\u30FC\u30E0\u30C7\u30A3\u30EC\u30AF\u30C8\u30EA\u3092\u4F7F\u7528",
24485
24651
  authMode: "\u8A8D\u8A3C\u30E2\u30FC\u30C9",
24486
24652
  authPassword: "\u30D1\u30B9\u30EF\u30FC\u30C9",
24487
24653
  authKey: "\u79D8\u5BC6\u9375",
@@ -24536,9 +24702,12 @@ Bot\uFF1A{{botName}}
24536
24702
  noDeviceSelected: "\u30C7\u30D0\u30A4\u30B9\u304C\u9078\u629E\u3055\u308C\u3066\u3044\u307E\u305B\u3093",
24537
24703
  windowClosed: "\u73FE\u5728\u306E\u30A6\u30A3\u30F3\u30C9\u30A6\u306F\u9589\u3058\u3089\u308C\u307E\u3057\u305F\u3002\u30B5\u30A4\u30C9\u30D0\u30FC\u304B\u3089\u30A6\u30A3\u30F3\u30C9\u30A6\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
24538
24704
  paneClosed: "\u73FE\u5728\u306E\u30DA\u30A4\u30F3\u306F\u9589\u3058\u3089\u308C\u307E\u3057\u305F\u3002\u30B5\u30A4\u30C9\u30D0\u30FC\u304B\u3089\u30DA\u30A4\u30F3\u3092\u9078\u629E\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
24539
- bellNotification: "\u30BF\u30FC\u30DF\u30CA\u30EB\u30D9\u30EB\u901A\u77E5",
24540
- bellDescription: "\u30A6\u30A3\u30F3\u30C9\u30A6 {{window}} \xB7 \u30DA\u30A4\u30F3 {{pane}}",
24705
+ bellNotification: "\u30BF\u30FC\u30DF\u30CA\u30EB\u30D9\u30EB",
24706
+ bellDescriptionWithTitle: "\u30A6\u30A3\u30F3\u30C9\u30A6 {{window}} \xB7 {{paneLabel}}",
24541
24707
  bellFallback: "tmux \u30D9\u30EB\u3092\u53D7\u4FE1",
24708
+ notificationFallbackTitle: "\u30BF\u30FC\u30DF\u30CA\u30EB\u901A\u77E5",
24709
+ notificationSourceLabel: "{{source}} \u304B\u3089",
24710
+ notificationFallbackDetail: "\u30BF\u30FC\u30DF\u30CA\u30EB\u901A\u77E5",
24542
24711
  paneTitle: "\u30DA\u30A4\u30F3 {{index}}",
24543
24712
  activePane: "\u73FE\u5728\u306E\u30DA\u30A4\u30F3",
24544
24713
  activeWindow: "\u73FE\u5728\u306E\u30A6\u30A3\u30F3\u30C9\u30A6",
@@ -24573,6 +24742,8 @@ Bot\uFF1A{{botName}}
24573
24742
  enableBrowserNotificationToast: "\u30D6\u30E9\u30A6\u30B6\u901A\u77E5 Toast \u3092\u6709\u52B9\u306B\u3059\u308B",
24574
24743
  enableTelegramBellPush: "Telegram \u30D9\u30EB\u30D7\u30C3\u30B7\u30E5\u3092\u6709\u52B9\u306B\u3059\u308B",
24575
24744
  enableTelegramNotificationPush: "Telegram \u901A\u77E5\u30D7\u30C3\u30B7\u30E5\u3092\u6709\u52B9\u306B\u3059\u308B",
24745
+ enableWeixinBellPush: "WeChat \u30D9\u30EB\u901A\u77E5\u3092\u6709\u52B9\u5316",
24746
+ enableWeixinNotificationPush: "WeChat \u901A\u77E5\u30D7\u30C3\u30B7\u30E5\u3092\u6709\u52B9\u5316",
24576
24747
  sshReconnectRetries: "SSH \u518D\u63A5\u7D9A\u8A66\u884C\u56DE\u6570",
24577
24748
  sshReconnectDelay: "SSH \u518D\u63A5\u7D9A\u5F85\u6A5F\uFF08\u79D2\uFF09",
24578
24749
  language: "\u8A00\u8A9E",
@@ -24812,6 +24983,81 @@ Bot\uFF1A{{botName}}
24812
24983
  botNotFound: "Bot \u304C\u898B\u3064\u304B\u3089\u306A\u3044\u304B\u5229\u7528\u3067\u304D\u307E\u305B\u3093",
24813
24984
  botNotRunning: "Bot \u304C\u8D77\u52D5\u3057\u3066\u3044\u306A\u3044\u304B\u5229\u7528\u3067\u304D\u307E\u305B\u3093"
24814
24985
  },
24986
+ weixin: {
24987
+ title: "WeChat (ClawBot) \u7BA1\u7406",
24988
+ 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",
24989
+ 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",
24990
+ accountName: "\u30A2\u30AB\u30A6\u30F3\u30C8\u540D",
24991
+ accountNamePlaceholder: "\u4F8B\uFF1Amy-wechat",
24992
+ addAccount: "\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u8FFD\u52A0",
24993
+ editAccount: "\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u7DE8\u96C6",
24994
+ enableAccount: "\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u6709\u52B9\u5316",
24995
+ allowAuthRequests: "\u8A8D\u8A3C\u30EA\u30AF\u30A8\u30B9\u30C8\u3092\u8A31\u53EF",
24996
+ scanToLogin: "\u30B9\u30AD\u30E3\u30F3\u3057\u3066\u30ED\u30B0\u30A4\u30F3",
24997
+ relogin: "\u518D\u30ED\u30B0\u30A4\u30F3\uFF08\u518D\u30B9\u30AD\u30E3\u30F3\uFF09",
24998
+ loggedIn: "\u30ED\u30B0\u30A4\u30F3\u6E08\u307F",
24999
+ notLoggedIn: "\u672A\u30ED\u30B0\u30A4\u30F3",
25000
+ 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",
25001
+ loginPending: "\u30B9\u30AD\u30E3\u30F3\u5F85\u3061\u2026",
25002
+ loginConfirmed: "\u30ED\u30B0\u30A4\u30F3\u304C\u78BA\u8A8D\u3055\u308C\u307E\u3057\u305F\u3002",
25003
+ 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",
25004
+ loginError: "\u30ED\u30B0\u30A4\u30F3\u5931\u6557\uFF1A{{message}}",
25005
+ loginFailed: "\u30ED\u30B0\u30A4\u30F3\u5931\u6557",
25006
+ refreshQrcode: "QR \u30B3\u30FC\u30C9\u3092\u66F4\u65B0",
25007
+ closeLogin: "\u9589\u3058\u308B",
25008
+ pendingUsers: "\u627F\u8A8D\u5F85\u3061",
25009
+ authorizedUsers: "\u627F\u8A8D\u6E08\u307F\u30E6\u30FC\u30B6\u30FC",
25010
+ noPendingUsers: "\u627F\u8A8D\u5F85\u3061\u306E\u30E6\u30FC\u30B6\u30FC\u306F\u3044\u307E\u305B\u3093",
25011
+ noAuthorizedUsers: "\u627F\u8A8D\u6E08\u307F\u306E\u30E6\u30FC\u30B6\u30FC\u306F\u3044\u307E\u305B\u3093",
25012
+ needsReactivation: "\u30BB\u30C3\u30B7\u30E7\u30F3\u5931\u52B9",
25013
+ 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",
25014
+ approve: "\u627F\u8A8D",
25015
+ revokeAuth: "\u627F\u8A8D\u3092\u53D6\u308A\u6D88\u3059",
25016
+ removeUser: "\u524A\u9664",
25017
+ testMessage: "\u30C6\u30B9\u30C8\u30E1\u30C3\u30BB\u30FC\u30B8",
25018
+ sendTestMessage: "\u30C6\u30B9\u30C8\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u9001\u4FE1",
25019
+ deleteAccount: "\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u524A\u9664",
25020
+ accountCreated: "\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u4F5C\u6210\u3057\u307E\u3057\u305F",
25021
+ accountUpdated: "\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u66F4\u65B0\u3057\u307E\u3057\u305F",
25022
+ accountDeleted: "\u30A2\u30AB\u30A6\u30F3\u30C8\u3092\u524A\u9664\u3057\u307E\u3057\u305F",
25023
+ authApproved: "\u8A8D\u8A3C\u3092\u627F\u8A8D\u3057\u307E\u3057\u305F",
25024
+ userRemoved: "\u30E6\u30FC\u30B6\u30FC\u3092\u524A\u9664\u3057\u307E\u3057\u305F",
25025
+ testMessageSent: "\u30C6\u30B9\u30C8\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u9001\u4FE1\u3057\u307E\u3057\u305F",
25026
+ createFailed: "\u30A2\u30AB\u30A6\u30F3\u30C8\u306E\u4F5C\u6210\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25027
+ updateFailed: "\u30A2\u30AB\u30A6\u30F3\u30C8\u306E\u66F4\u65B0\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25028
+ deleteFailed: "\u30A2\u30AB\u30A6\u30F3\u30C8\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25029
+ approveFailed: "\u627F\u8A8D\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25030
+ removeFailed: "\u30E6\u30FC\u30B6\u30FC\u306E\u524A\u9664\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25031
+ testMessageFailed: "\u30C6\u30B9\u30C8\u30E1\u30C3\u30BB\u30FC\u30B8\u306E\u9001\u4FE1\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25032
+ loadAccountsFailed: "\u30A2\u30AB\u30A6\u30F3\u30C8\u4E00\u89A7\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25033
+ loadUsersFailed: "\u30E6\u30FC\u30B6\u30FC\u4E00\u89A7\u306E\u8AAD\u307F\u8FBC\u307F\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
25034
+ 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",
25035
+ userCount: "{{authorized}} \u627F\u8A8D\u6E08\u307F / {{pending}} \u627F\u8A8D\u5F85\u3061\uFF08\u6700\u5927 16\uFF09",
25036
+ userId: "WeChat \u30E6\u30FC\u30B6\u30FC ID",
25037
+ applyTime: "\u521D\u56DE\u9023\u7D61",
25038
+ expand: "\u5C55\u958B",
25039
+ collapse: "\u6298\u308A\u305F\u305F\u3080",
25040
+ accountNameRequired: "\u30A2\u30AB\u30A6\u30F3\u30C8\u540D\u306F\u5FC5\u9808\u3067\u3059",
25041
+ accountNotFound: "\u30A2\u30AB\u30A6\u30F3\u30C8\u304C\u898B\u3064\u304B\u3089\u306A\u3044\u304B\u5229\u7528\u3067\u304D\u307E\u305B\u3093",
25042
+ accountNotRunning: "\u30A2\u30AB\u30A6\u30F3\u30C8\u304C\u672A\u30ED\u30B0\u30A4\u30F3\u307E\u305F\u306F\u672A\u8D77\u52D5\u3067\u3059",
25043
+ userNotFound: "\u30E6\u30FC\u30B6\u30FC\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093",
25044
+ authSuccess: "\u2705 \u8A8D\u8A3C\u3055\u308C\u307E\u3057\u305F\u3002\u901A\u77E5\u3092\u53D7\u3051\u53D6\u308C\u307E\u3059\u3002",
25045
+ 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",
25046
+ testMessageTemplate: `\uD83E\uDDEA \u30C6\u30B9\u30C8\u30E1\u30C3\u30BB\u30FC\u30B8
25047
+ \u30B5\u30A4\u30C8\uFF1A{{siteName}}
25048
+ \u6642\u523B\uFF1A{{time}}`,
25049
+ approveMessageTemplate: `\u2705 tmex \u7D4C\u7531\u3067\u8A8D\u8A3C\u3057\u307E\u3057\u305F\u3002
25050
+ \u30A2\u30AB\u30A6\u30F3\u30C8\uFF1A{{accountName}}
25051
+ \u6642\u523B\uFF1A{{time}}`,
25052
+ 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",
25053
+ 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",
25054
+ bindingInProgress: "\u30E1\u30C3\u30BB\u30FC\u30B8\u3092\u53D7\u4FE1\u3001\u7D10\u4ED8\u3051\u3092\u5B8C\u4E86\u3057\u3066\u3044\u307E\u3059\u2026",
25055
+ bindSuccess: "WeChat \u3092\u7D10\u4ED8\u3051\u307E\u3057\u305F\u3002\u901A\u77E5\u3092\u53D7\u3051\u53D6\u308C\u307E\u3059\u3002",
25056
+ bound: "\u7D10\u4ED8\u3051\u6E08\u307F",
25057
+ unbound: "\u672A\u7D10\u4ED8\u3051",
25058
+ bindAction: "\u30B9\u30AD\u30E3\u30F3\u3057\u3066\u7D10\u4ED8\u3051",
25059
+ gatewayOnline: "\uD83D\uDFE2 tmex \u30AA\u30F3\u30E9\u30A4\u30F3 @ {{siteName}}"
25060
+ },
24815
25061
  webhook: {
24816
25062
  title: "Webhooks",
24817
25063
  url: "Webhook URL",
@@ -25396,6 +25642,7 @@ __export(exports_ws_borsh, {
25396
25642
  KIND_DEVICE_DISCONNECT: () => KIND_DEVICE_DISCONNECT,
25397
25643
  KIND_DEVICE_CONNECTED: () => KIND_DEVICE_CONNECTED,
25398
25644
  KIND_DEVICE_CONNECT: () => KIND_DEVICE_CONNECT,
25645
+ KIND_CLIPBOARD_WRITE: () => KIND_CLIPBOARD_WRITE,
25399
25646
  KIND_CHUNK: () => KIND_CHUNK,
25400
25647
  KIND_AGENT_UNSUBSCRIBE: () => KIND_AGENT_UNSUBSCRIBE,
25401
25648
  KIND_AGENT_SUBSCRIBE: () => KIND_AGENT_SUBSCRIBE,
@@ -25467,6 +25714,7 @@ var KIND_TERM_RESIZE = 771;
25467
25714
  var KIND_TERM_SYNC_SIZE = 772;
25468
25715
  var KIND_TERM_OUTPUT = 773;
25469
25716
  var KIND_TERM_HISTORY = 774;
25717
+ var KIND_CLIPBOARD_WRITE = 775;
25470
25718
  var KIND_SWITCH_ACK = 1025;
25471
25719
  var KIND_LIVE_RESUME = 1026;
25472
25720
  var KIND_CHUNK = 1281;
@@ -25503,6 +25751,7 @@ var VALID_KINDS = new Set([
25503
25751
  KIND_TERM_SYNC_SIZE,
25504
25752
  KIND_TERM_OUTPUT,
25505
25753
  KIND_TERM_HISTORY,
25754
+ KIND_CLIPBOARD_WRITE,
25506
25755
  KIND_SWITCH_ACK,
25507
25756
  KIND_LIVE_RESUME,
25508
25757
  KIND_CHUNK,
@@ -25544,6 +25793,7 @@ function kindToString(kind) {
25544
25793
  [KIND_TERM_SYNC_SIZE]: "TERM_SYNC_SIZE",
25545
25794
  [KIND_TERM_OUTPUT]: "TERM_OUTPUT",
25546
25795
  [KIND_TERM_HISTORY]: "TERM_HISTORY",
25796
+ [KIND_CLIPBOARD_WRITE]: "CLIPBOARD_WRITE",
25547
25797
  [KIND_SWITCH_ACK]: "SWITCH_ACK",
25548
25798
  [KIND_LIVE_RESUME]: "LIVE_RESUME",
25549
25799
  [KIND_CHUNK]: "CHUNK",
@@ -25661,6 +25911,7 @@ __export(exports_schema, {
25661
25911
  DeviceDisconnectSchema: () => DeviceDisconnectSchema,
25662
25912
  DeviceConnectedSchema: () => DeviceConnectedSchema,
25663
25913
  DeviceConnectSchema: () => DeviceConnectSchema,
25914
+ ClipboardWriteSchema: () => ClipboardWriteSchema,
25664
25915
  ChunkSchema: () => ChunkSchema,
25665
25916
  BellEventSchema: () => BellEventSchema,
25666
25917
  AgentUnsubscribeSchema: () => AgentUnsubscribeSchema,
@@ -25806,6 +26057,11 @@ var TermHistorySchema = import_zorsh.b.struct({
25806
26057
  alternateScreen: import_zorsh.b.bool(),
25807
26058
  data: import_zorsh.b.bytes()
25808
26059
  });
26060
+ var ClipboardWriteSchema = import_zorsh.b.struct({
26061
+ deviceId: import_zorsh.b.string(),
26062
+ paneId: import_zorsh.b.string(),
26063
+ text: import_zorsh.b.string()
26064
+ });
25809
26065
  var SwitchAckSchema = import_zorsh.b.struct({
25810
26066
  deviceId: import_zorsh.b.string(),
25811
26067
  windowId: import_zorsh.b.string(),
@@ -25910,7 +26166,9 @@ var BellEventSchema = import_zorsh.b.struct({
25910
26166
  paneId: OptionStringSchema,
25911
26167
  windowIndex: OptionU16Schema,
25912
26168
  paneIndex: OptionU16Schema,
25913
- paneUrl: OptionStringSchema
26169
+ paneUrl: OptionStringSchema,
26170
+ paneTitle: OptionStringSchema,
26171
+ paneCurrentCommand: OptionStringSchema
25914
26172
  });
25915
26173
  var NotificationEventSchema = import_zorsh.b.struct({
25916
26174
  source: import_zorsh.b.u8(),
@@ -25920,7 +26178,9 @@ var NotificationEventSchema = import_zorsh.b.struct({
25920
26178
  paneId: OptionStringSchema,
25921
26179
  windowIndex: OptionU16Schema,
25922
26180
  paneIndex: OptionU16Schema,
25923
- paneUrl: OptionStringSchema
26181
+ paneUrl: OptionStringSchema,
26182
+ paneTitle: OptionStringSchema,
26183
+ paneCurrentCommand: OptionStringSchema
25924
26184
  });
25925
26185
  // ../shared/src/ws-borsh/codec.ts
25926
26186
  var MAGIC = new Uint8Array([84, 88]);
@@ -26237,7 +26497,9 @@ function encodeEventData(type, data) {
26237
26497
  paneId: d.paneId ?? null,
26238
26498
  windowIndex: d.windowIndex ?? null,
26239
26499
  paneIndex: d.paneIndex ?? null,
26240
- paneUrl: d.paneUrl ?? null
26500
+ paneUrl: d.paneUrl ?? null,
26501
+ paneTitle: d.paneTitle ?? null,
26502
+ paneCurrentCommand: d.paneCurrentCommand ?? null
26241
26503
  });
26242
26504
  }
26243
26505
  case "output":
@@ -26252,7 +26514,9 @@ function encodeEventData(type, data) {
26252
26514
  paneId: d.paneId ?? null,
26253
26515
  windowIndex: d.windowIndex ?? null,
26254
26516
  paneIndex: d.paneIndex ?? null,
26255
- paneUrl: d.paneUrl ?? null
26517
+ paneUrl: d.paneUrl ?? null,
26518
+ paneTitle: d.paneTitle ?? null,
26519
+ paneCurrentCommand: d.paneCurrentCommand ?? null
26256
26520
  });
26257
26521
  }
26258
26522
  default:
@@ -26363,7 +26627,9 @@ function decodeEventData(type, data) {
26363
26627
  paneId: bell.paneId ?? undefined,
26364
26628
  windowIndex: bell.windowIndex ?? undefined,
26365
26629
  paneIndex: bell.paneIndex ?? undefined,
26366
- paneUrl: bell.paneUrl ?? undefined
26630
+ paneUrl: bell.paneUrl ?? undefined,
26631
+ paneTitle: bell.paneTitle ?? undefined,
26632
+ paneCurrentCommand: bell.paneCurrentCommand ?? undefined
26367
26633
  };
26368
26634
  }
26369
26635
  case "notification": {
@@ -26376,7 +26642,9 @@ function decodeEventData(type, data) {
26376
26642
  paneId: notification.paneId ?? undefined,
26377
26643
  windowIndex: notification.windowIndex ?? undefined,
26378
26644
  paneIndex: notification.paneIndex ?? undefined,
26379
- paneUrl: notification.paneUrl ?? undefined
26645
+ paneUrl: notification.paneUrl ?? undefined,
26646
+ paneTitle: notification.paneTitle ?? undefined,
26647
+ paneCurrentCommand: notification.paneCurrentCommand ?? undefined
26380
26648
  };
26381
26649
  }
26382
26650
  default:
@@ -32855,6 +33123,8 @@ function drizzle(...params) {
32855
33123
  // ../../apps/gateway/src/db/schema.ts
32856
33124
  var exports_schema2 = {};
32857
33125
  __export(exports_schema2, {
33126
+ weixinAccounts: () => weixinAccounts,
33127
+ weixinAccountUsers: () => weixinAccountUsers,
32858
33128
  webhookEndpoints: () => webhookEndpoints,
32859
33129
  watchRules: () => watchRules,
32860
33130
  watchRuleState: () => watchRuleState,
@@ -32887,6 +33157,10 @@ var siteSettings = sqliteTable("site_settings", {
32887
33157
  enableTelegramNotificationPush: integer("enable_telegram_notification_push", {
32888
33158
  mode: "boolean"
32889
33159
  }).notNull().default(true),
33160
+ enableWeixinBellPush: integer("enable_weixin_bell_push", { mode: "boolean" }).notNull().default(false),
33161
+ enableWeixinNotificationPush: integer("enable_weixin_notification_push", {
33162
+ mode: "boolean"
33163
+ }).notNull().default(false),
32890
33164
  sshReconnectMaxRetries: integer("ssh_reconnect_max_retries").notNull(),
32891
33165
  sshReconnectDelaySeconds: integer("ssh_reconnect_delay_seconds").notNull(),
32892
33166
  language: text("language").notNull().default("en_US"),
@@ -32911,6 +33185,7 @@ var devices = sqliteTable("devices", {
32911
33185
  passwordEnc: text("password_enc"),
32912
33186
  privateKeyEnc: text("private_key_enc"),
32913
33187
  privateKeyPassphraseEnc: text("private_key_passphrase_enc"),
33188
+ defaultWorkingDir: text("default_working_dir"),
32914
33189
  sortOrder: integer("sort_order").notNull().default(0),
32915
33190
  createdAt: text("created_at").notNull(),
32916
33191
  updatedAt: text("updated_at").notNull()
@@ -33091,6 +33366,34 @@ var telegramBotChats = sqliteTable("telegram_bot_chats", {
33091
33366
  check("telegram_bot_chats_status_check", sql`${table.status} in ('pending', 'authorized')`),
33092
33367
  check("telegram_bot_chats_chat_type_check", sql`${table.chatType} in ('private', 'group', 'supergroup', 'channel', 'unknown')`)
33093
33368
  ]);
33369
+ var weixinAccounts = sqliteTable("weixin_accounts", {
33370
+ id: text("id").primaryKey(),
33371
+ name: text("name").notNull(),
33372
+ enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
33373
+ allowAuthRequests: integer("allow_auth_requests", { mode: "boolean" }).notNull().default(true),
33374
+ weixinUin: text("weixin_uin"),
33375
+ botTokenEnc: text("bot_token_enc"),
33376
+ baseUrl: text("base_url"),
33377
+ syncBuf: text("sync_buf"),
33378
+ createdAt: text("created_at").notNull(),
33379
+ updatedAt: text("updated_at").notNull()
33380
+ });
33381
+ var weixinAccountUsers = sqliteTable("weixin_account_users", {
33382
+ id: text("id").primaryKey(),
33383
+ accountId: text("account_id").notNull().references(() => weixinAccounts.id, { onDelete: "cascade" }),
33384
+ userId: text("user_id").notNull(),
33385
+ displayName: text("display_name").notNull(),
33386
+ status: text("status").notNull(),
33387
+ lastContextToken: text("last_context_token"),
33388
+ lastInboundAt: text("last_inbound_at"),
33389
+ needsReactivation: integer("needs_reactivation", { mode: "boolean" }).notNull().default(false),
33390
+ appliedAt: text("applied_at").notNull(),
33391
+ authorizedAt: text("authorized_at"),
33392
+ updatedAt: text("updated_at").notNull()
33393
+ }, (table) => [
33394
+ unique("weixin_account_users_account_user_unique").on(table.accountId, table.userId),
33395
+ check("weixin_account_users_status_check", sql`${table.status} in ('pending', 'authorized')`)
33396
+ ]);
33094
33397
 
33095
33398
  // ../../apps/gateway/src/db/client.ts
33096
33399
  var sqliteClient = null;
@@ -33136,6 +33439,7 @@ function toDevice(row) {
33136
33439
  passwordEnc: optional(row.passwordEnc),
33137
33440
  privateKeyEnc: optional(row.privateKeyEnc),
33138
33441
  privateKeyPassphraseEnc: optional(row.privateKeyPassphraseEnc),
33442
+ defaultWorkingDir: optional(row.defaultWorkingDir),
33139
33443
  sortOrder: row.sortOrder,
33140
33444
  createdAt: row.createdAt,
33141
33445
  updatedAt: row.updatedAt
@@ -33151,6 +33455,8 @@ function toSiteSettings(row) {
33151
33455
  enableBrowserNotificationToast: row.enableBrowserNotificationToast,
33152
33456
  enableTelegramBellPush: row.enableTelegramBellPush,
33153
33457
  enableTelegramNotificationPush: row.enableTelegramNotificationPush,
33458
+ enableWeixinBellPush: row.enableWeixinBellPush,
33459
+ enableWeixinNotificationPush: row.enableWeixinNotificationPush,
33154
33460
  sshReconnectMaxRetries: row.sshReconnectMaxRetries,
33155
33461
  sshReconnectDelaySeconds: row.sshReconnectDelaySeconds,
33156
33462
  language: normalizeLocale(row.language),
@@ -33182,6 +33488,35 @@ function toTelegramChat(row) {
33182
33488
  updatedAt: row.updatedAt
33183
33489
  };
33184
33490
  }
33491
+ function toWeixinAccountRecord(row) {
33492
+ return {
33493
+ id: row.id,
33494
+ name: row.name,
33495
+ enabled: row.enabled,
33496
+ allowAuthRequests: row.allowAuthRequests,
33497
+ loggedIn: row.botTokenEnc != null,
33498
+ weixinUin: row.weixinUin ?? null,
33499
+ botTokenEnc: row.botTokenEnc ?? null,
33500
+ baseUrl: row.baseUrl ?? null,
33501
+ syncBuf: row.syncBuf ?? null,
33502
+ createdAt: row.createdAt,
33503
+ updatedAt: row.updatedAt
33504
+ };
33505
+ }
33506
+ function toWeixinAccountUser(row) {
33507
+ return {
33508
+ id: row.id,
33509
+ accountId: row.accountId,
33510
+ userId: row.userId,
33511
+ displayName: row.displayName,
33512
+ status: row.status,
33513
+ needsReactivation: row.needsReactivation,
33514
+ lastInboundAt: row.lastInboundAt ?? null,
33515
+ appliedAt: row.appliedAt,
33516
+ authorizedAt: row.authorizedAt ?? null,
33517
+ updatedAt: row.updatedAt
33518
+ };
33519
+ }
33185
33520
  function toWebhookEndpoint(row) {
33186
33521
  return {
33187
33522
  id: row.id,
@@ -33206,6 +33541,8 @@ function ensureSiteSettingsInitialized() {
33206
33541
  enableBrowserNotificationToast: true,
33207
33542
  enableTelegramBellPush: true,
33208
33543
  enableTelegramNotificationPush: true,
33544
+ enableWeixinBellPush: false,
33545
+ enableWeixinNotificationPush: false,
33209
33546
  sshReconnectMaxRetries: config.sshReconnectMaxRetriesDefault,
33210
33547
  sshReconnectDelaySeconds: config.sshReconnectDelaySecondsDefault,
33211
33548
  language: normalizeLocale(config.languageDefault),
@@ -33230,6 +33567,7 @@ function createDevice(device) {
33230
33567
  passwordEnc: device.passwordEnc ?? null,
33231
33568
  privateKeyEnc: device.privateKeyEnc ?? null,
33232
33569
  privateKeyPassphraseEnc: device.privateKeyPassphraseEnc ?? null,
33570
+ defaultWorkingDir: device.defaultWorkingDir ?? null,
33233
33571
  sortOrder: nextSortOrder,
33234
33572
  createdAt: device.createdAt,
33235
33573
  updatedAt: device.updatedAt
@@ -33299,6 +33637,9 @@ function updateDevice(id, updates) {
33299
33637
  if (updates.privateKeyPassphraseEnc !== undefined) {
33300
33638
  setValues.privateKeyPassphraseEnc = updates.privateKeyPassphraseEnc;
33301
33639
  }
33640
+ if (updates.defaultWorkingDir !== undefined) {
33641
+ setValues.defaultWorkingDir = updates.defaultWorkingDir || null;
33642
+ }
33302
33643
  orm.update(devices).set(setValues).where(eq(devices.id, id)).run();
33303
33644
  }
33304
33645
  function deleteDevice(id) {
@@ -33402,6 +33743,8 @@ function updateSiteSettings(updates) {
33402
33743
  enableBrowserNotificationToast: updates.enableBrowserNotificationToast ?? current.enableBrowserNotificationToast,
33403
33744
  enableTelegramBellPush: updates.enableTelegramBellPush ?? current.enableTelegramBellPush,
33404
33745
  enableTelegramNotificationPush: updates.enableTelegramNotificationPush ?? current.enableTelegramNotificationPush,
33746
+ enableWeixinBellPush: updates.enableWeixinBellPush ?? current.enableWeixinBellPush,
33747
+ enableWeixinNotificationPush: updates.enableWeixinNotificationPush ?? current.enableWeixinNotificationPush,
33405
33748
  sshReconnectMaxRetries: updates.sshReconnectMaxRetries ?? current.sshReconnectMaxRetries,
33406
33749
  sshReconnectDelaySeconds: updates.sshReconnectDelaySeconds ?? current.sshReconnectDelaySeconds,
33407
33750
  language: updates.language ? normalizeLocale(updates.language) : current.language,
@@ -33417,6 +33760,8 @@ function updateSiteSettings(updates) {
33417
33760
  enableBrowserNotificationToast: next.enableBrowserNotificationToast,
33418
33761
  enableTelegramBellPush: next.enableTelegramBellPush,
33419
33762
  enableTelegramNotificationPush: next.enableTelegramNotificationPush,
33763
+ enableWeixinBellPush: next.enableWeixinBellPush,
33764
+ enableWeixinNotificationPush: next.enableWeixinNotificationPush,
33420
33765
  sshReconnectMaxRetries: next.sshReconnectMaxRetries,
33421
33766
  sshReconnectDelaySeconds: next.sshReconnectDelaySeconds,
33422
33767
  language: next.language,
@@ -33650,6 +33995,200 @@ function deleteTelegramChat(botId, chatId) {
33650
33995
  const orm = getDb();
33651
33996
  orm.delete(telegramBotChats).where(and(eq(telegramBotChats.botId, botId), eq(telegramBotChats.chatId, chatId))).run();
33652
33997
  }
33998
+ var WEIXIN_USER_CAP = 16;
33999
+ function createWeixinAccount(record) {
34000
+ const orm = getDb();
34001
+ orm.insert(weixinAccounts).values({
34002
+ id: record.id,
34003
+ name: record.name,
34004
+ enabled: record.enabled,
34005
+ allowAuthRequests: record.allowAuthRequests,
34006
+ weixinUin: record.weixinUin,
34007
+ botTokenEnc: record.botTokenEnc,
34008
+ baseUrl: record.baseUrl,
34009
+ syncBuf: record.syncBuf,
34010
+ createdAt: record.createdAt,
34011
+ updatedAt: record.updatedAt
34012
+ }).run();
34013
+ }
34014
+ function getWeixinAccountById(accountId) {
34015
+ const orm = getDb();
34016
+ const row = orm.select().from(weixinAccounts).where(eq(weixinAccounts.id, accountId)).get();
34017
+ if (!row) {
34018
+ return null;
34019
+ }
34020
+ return toWeixinAccountRecord(row);
34021
+ }
34022
+ function getAllWeixinAccounts() {
34023
+ const orm = getDb();
34024
+ return orm.select().from(weixinAccounts).orderBy(desc(weixinAccounts.createdAt)).all().map(toWeixinAccountRecord);
34025
+ }
34026
+ function getWeixinAccountsWithStats() {
34027
+ const orm = getDb();
34028
+ const accounts = orm.select().from(weixinAccounts).orderBy(desc(weixinAccounts.createdAt)).all();
34029
+ const counters = new Map;
34030
+ const userRows = orm.select({
34031
+ accountId: weixinAccountUsers.accountId,
34032
+ status: weixinAccountUsers.status,
34033
+ needsReactivation: weixinAccountUsers.needsReactivation
34034
+ }).from(weixinAccountUsers).all();
34035
+ for (const row of userRows) {
34036
+ const current = counters.get(row.accountId) ?? {
34037
+ pending: 0,
34038
+ authorized: 0,
34039
+ needsReactivation: 0
34040
+ };
34041
+ if (row.status === "pending") {
34042
+ current.pending += 1;
34043
+ }
34044
+ if (row.status === "authorized") {
34045
+ current.authorized += 1;
34046
+ if (row.needsReactivation) {
34047
+ current.needsReactivation += 1;
34048
+ }
34049
+ }
34050
+ counters.set(row.accountId, current);
34051
+ }
34052
+ return accounts.map((account) => {
34053
+ const counter = counters.get(account.id) ?? {
34054
+ pending: 0,
34055
+ authorized: 0,
34056
+ needsReactivation: 0
34057
+ };
34058
+ return {
34059
+ id: account.id,
34060
+ name: account.name,
34061
+ enabled: account.enabled,
34062
+ allowAuthRequests: account.allowAuthRequests,
34063
+ loggedIn: account.botTokenEnc != null,
34064
+ createdAt: account.createdAt,
34065
+ updatedAt: account.updatedAt,
34066
+ pendingCount: counter.pending,
34067
+ authorizedCount: counter.authorized,
34068
+ needsReactivationCount: counter.needsReactivation
34069
+ };
34070
+ });
34071
+ }
34072
+ function updateWeixinAccount(accountId, updates) {
34073
+ const orm = getDb();
34074
+ const setValues = {
34075
+ updatedAt: new Date().toISOString()
34076
+ };
34077
+ if (updates.name !== undefined) {
34078
+ setValues.name = updates.name;
34079
+ }
34080
+ if (updates.enabled !== undefined) {
34081
+ setValues.enabled = updates.enabled;
34082
+ }
34083
+ if (updates.allowAuthRequests !== undefined) {
34084
+ setValues.allowAuthRequests = updates.allowAuthRequests;
34085
+ }
34086
+ if (updates.weixinUin !== undefined) {
34087
+ setValues.weixinUin = updates.weixinUin;
34088
+ }
34089
+ if (updates.botTokenEnc !== undefined) {
34090
+ setValues.botTokenEnc = updates.botTokenEnc;
34091
+ }
34092
+ if (updates.baseUrl !== undefined) {
34093
+ setValues.baseUrl = updates.baseUrl;
34094
+ }
34095
+ if (updates.syncBuf !== undefined) {
34096
+ setValues.syncBuf = updates.syncBuf;
34097
+ }
34098
+ orm.update(weixinAccounts).set(setValues).where(eq(weixinAccounts.id, accountId)).run();
34099
+ return getWeixinAccountById(accountId);
34100
+ }
34101
+ function deleteWeixinAccount(accountId) {
34102
+ const orm = getDb();
34103
+ orm.delete(weixinAccounts).where(eq(weixinAccounts.id, accountId)).run();
34104
+ }
34105
+ function getWeixinUserCount(accountId) {
34106
+ const orm = getDb();
34107
+ const row = orm.select({ total: count() }).from(weixinAccountUsers).where(eq(weixinAccountUsers.accountId, accountId)).get();
34108
+ return Number(row?.total ?? 0);
34109
+ }
34110
+ function getWeixinUserByAccountAndUserId(accountId, userId) {
34111
+ const orm = getDb();
34112
+ const row = orm.select().from(weixinAccountUsers).where(and(eq(weixinAccountUsers.accountId, accountId), eq(weixinAccountUsers.userId, userId))).get();
34113
+ if (!row) {
34114
+ return null;
34115
+ }
34116
+ return toWeixinAccountUser(row);
34117
+ }
34118
+ function upsertWeixinUserOnInbound(params) {
34119
+ const existing = getWeixinUserByAccountAndUserId(params.accountId, params.userId);
34120
+ const orm = getDb();
34121
+ if (existing) {
34122
+ const setValues = {
34123
+ displayName: params.displayName,
34124
+ lastInboundAt: params.at,
34125
+ needsReactivation: false,
34126
+ updatedAt: params.at
34127
+ };
34128
+ if (params.contextToken != null) {
34129
+ setValues.lastContextToken = params.contextToken;
34130
+ }
34131
+ orm.update(weixinAccountUsers).set(setValues).where(eq(weixinAccountUsers.id, existing.id)).run();
34132
+ return getWeixinUserByAccountAndUserId(params.accountId, params.userId);
34133
+ }
34134
+ if (!params.allowAuthRequests) {
34135
+ return null;
34136
+ }
34137
+ if (getWeixinUserCount(params.accountId) >= WEIXIN_USER_CAP) {
34138
+ throw new Error(instance.t("apiError.invalidRequest"));
34139
+ }
34140
+ orm.insert(weixinAccountUsers).values({
34141
+ id: crypto.randomUUID(),
34142
+ accountId: params.accountId,
34143
+ userId: params.userId,
34144
+ displayName: params.displayName,
34145
+ status: "pending",
34146
+ lastContextToken: params.contextToken,
34147
+ lastInboundAt: params.at,
34148
+ needsReactivation: false,
34149
+ appliedAt: params.at,
34150
+ authorizedAt: null,
34151
+ updatedAt: params.at
34152
+ }).run();
34153
+ return getWeixinUserByAccountAndUserId(params.accountId, params.userId);
34154
+ }
34155
+ function listWeixinUsersByAccount(accountId) {
34156
+ const orm = getDb();
34157
+ return orm.select().from(weixinAccountUsers).where(eq(weixinAccountUsers.accountId, accountId)).orderBy(desc(weixinAccountUsers.appliedAt)).all().map(toWeixinAccountUser);
34158
+ }
34159
+ function listAuthorizedWeixinUsersByAccount(accountId) {
34160
+ const orm = getDb();
34161
+ return orm.select().from(weixinAccountUsers).where(and(eq(weixinAccountUsers.accountId, accountId), eq(weixinAccountUsers.status, "authorized"))).orderBy(desc(weixinAccountUsers.authorizedAt)).all().map(toWeixinAccountUser);
34162
+ }
34163
+ function getWeixinUserContextTokens(accountId) {
34164
+ const orm = getDb();
34165
+ return orm.select({
34166
+ userId: weixinAccountUsers.userId,
34167
+ contextToken: weixinAccountUsers.lastContextToken
34168
+ }).from(weixinAccountUsers).where(eq(weixinAccountUsers.accountId, accountId)).all().filter((row) => row.contextToken != null);
34169
+ }
34170
+ function approveWeixinUser(accountId, userId) {
34171
+ const existing = getWeixinUserByAccountAndUserId(accountId, userId);
34172
+ if (!existing) {
34173
+ return null;
34174
+ }
34175
+ const now = new Date().toISOString();
34176
+ const orm = getDb();
34177
+ orm.update(weixinAccountUsers).set({
34178
+ status: "authorized",
34179
+ authorizedAt: now,
34180
+ updatedAt: now
34181
+ }).where(eq(weixinAccountUsers.id, existing.id)).run();
34182
+ return getWeixinUserByAccountAndUserId(accountId, userId);
34183
+ }
34184
+ function deleteWeixinUser(accountId, userId) {
34185
+ const orm = getDb();
34186
+ orm.delete(weixinAccountUsers).where(and(eq(weixinAccountUsers.accountId, accountId), eq(weixinAccountUsers.userId, userId))).run();
34187
+ }
34188
+ function setWeixinUserNeedsReactivation(accountId, userId, value) {
34189
+ const orm = getDb();
34190
+ orm.update(weixinAccountUsers).set({ needsReactivation: value, updatedAt: new Date().toISOString() }).where(and(eq(weixinAccountUsers.accountId, accountId), eq(weixinAccountUsers.userId, userId))).run();
34191
+ }
33653
34192
 
33654
34193
  // ../../apps/gateway/src/db/agent.ts
33655
34194
  function ensureAgentSettingsInitialized() {
@@ -87048,13 +87587,7 @@ var _a21;
87048
87587
  _a21 = symbol21;
87049
87588
  var defaultDownload2 = createDownload();
87050
87589
 
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
- }
87590
+ // ../../apps/gateway/src/events/channels/pane-url.ts
87058
87591
  function trimTrailingSlash(url2) {
87059
87592
  return url2.endsWith("/") ? url2.slice(0, -1) : url2;
87060
87593
  }
@@ -87083,100 +87616,17 @@ function normalizeHttpUrl(input) {
87083
87616
  }
87084
87617
  }
87085
87618
 
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
- }
87619
+ // ../../apps/gateway/src/events/channels/telegram.ts
87620
+ function escapeTelegramHtmlText(input) {
87621
+ return input.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
87622
+ }
87623
+ function escapeTelegramHtmlAttribute(input) {
87624
+ return escapeTelegramHtmlText(input).replace(/"/g, "&quot;");
87625
+ }
87626
+
87627
+ class TelegramChannel {
87628
+ id = "telegram";
87100
87629
  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
87630
  const settings = getSiteSettings();
87181
87631
  if (eventType === "terminal_bell") {
87182
87632
  if (!settings.enableTelegramBellPush) {
@@ -87290,6 +87740,51 @@ class EventNotifier {
87290
87740
  return lines.join(`
87291
87741
  `);
87292
87742
  }
87743
+ }
87744
+ var telegramChannel = new TelegramChannel;
87745
+
87746
+ // ../../apps/gateway/src/events/channels/webhook.ts
87747
+ class WebhookChannel {
87748
+ id = "webhook";
87749
+ webhooks = [];
87750
+ lastRefresh = 0;
87751
+ REFRESH_INTERVAL = 60000;
87752
+ refreshConfig() {
87753
+ const now2 = Date.now();
87754
+ if (now2 - this.lastRefresh < this.REFRESH_INTERVAL)
87755
+ return;
87756
+ this.webhooks = getAllWebhookEndpoints().filter((w) => w.enabled);
87757
+ this.lastRefresh = now2;
87758
+ console.log(`[events] refreshed config: ${this.webhooks.length} webhooks`);
87759
+ }
87760
+ async notify(eventType, event) {
87761
+ this.refreshConfig();
87762
+ const targets = this.webhooks.filter((w) => w.eventMask.includes(eventType));
87763
+ await Promise.all(targets.map(async (webhook) => {
87764
+ try {
87765
+ await this.sendWebhook(webhook, event);
87766
+ } catch (err) {
87767
+ console.error(`[webhook] failed to send to ${webhook.url}:`, err);
87768
+ }
87769
+ }));
87770
+ }
87771
+ async sendWebhook(webhook, event) {
87772
+ const body = JSON.stringify(event);
87773
+ const signature = await this.generateHmac(webhook.secret, body);
87774
+ const response = await fetch(webhook.url, {
87775
+ method: "POST",
87776
+ headers: {
87777
+ "Content-Type": "application/json",
87778
+ "X-Tmex-Signature": `sha256=${signature}`,
87779
+ "X-Tmex-Event": event.eventType,
87780
+ "X-Tmex-Timestamp": event.timestamp
87781
+ },
87782
+ body
87783
+ });
87784
+ if (!response.ok) {
87785
+ console.error(`[webhook] ${webhook.url} returned ${response.status}`);
87786
+ }
87787
+ }
87293
87788
  async generateHmac(secret, message) {
87294
87789
  const encoder2 = new TextEncoder;
87295
87790
  const key = await crypto.subtle.importKey("raw", encoder2.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
@@ -87297,6 +87792,897 @@ class EventNotifier {
87297
87792
  return Buffer.from(signature).toString("hex");
87298
87793
  }
87299
87794
  }
87795
+ var webhookChannel = new WebhookChannel;
87796
+
87797
+ // ../../apps/gateway/src/weixin/ilink/types.ts
87798
+ var ILINK_LOGIN_HOST = "https://ilinkai.weixin.qq.com";
87799
+ var ILINK_BOT_TYPE = 3;
87800
+ var CHANNEL_VERSION = "1.0.3";
87801
+ var CLIENT_ID_PREFIX = "openclaw-weixin-";
87802
+ var MESSAGE_TYPE_BOT = 2;
87803
+ var MESSAGE_STATE_FINISH = 2;
87804
+ var ITEM_TYPE_TEXT = 1;
87805
+ var SESSION_EXPIRED_ERRCODE = -14;
87806
+
87807
+ // ../../apps/gateway/src/weixin/ilink/api.ts
87808
+ function resolveFetch(fetchImpl) {
87809
+ return fetchImpl ?? globalThis.fetch;
87810
+ }
87811
+ function trimTrailingSlash2(url2) {
87812
+ return url2.endsWith("/") ? url2.slice(0, -1) : url2;
87813
+ }
87814
+ function generateWechatUin() {
87815
+ const buf = new Uint32Array(1);
87816
+ crypto.getRandomValues(buf);
87817
+ const decimal = String(buf[0]);
87818
+ return Buffer.from(decimal, "utf8").toString("base64");
87819
+ }
87820
+ function buildAuthHeaders(botToken) {
87821
+ const headers = {
87822
+ "Content-Type": "application/json",
87823
+ AuthorizationType: "ilink_bot_token",
87824
+ "X-WECHAT-UIN": generateWechatUin()
87825
+ };
87826
+ const token = botToken?.trim();
87827
+ if (token) {
87828
+ headers.Authorization = `Bearer ${token}`;
87829
+ }
87830
+ return headers;
87831
+ }
87832
+ function buildBaseInfo() {
87833
+ return { channel_version: CHANNEL_VERSION };
87834
+ }
87835
+ async function readJson(resp) {
87836
+ if (!resp.ok) {
87837
+ const body = await resp.text().catch(() => "");
87838
+ throw new Error(`iLink HTTP ${resp.status} ${resp.statusText}${body ? `: ${body.slice(0, 200)}` : ""}`);
87839
+ }
87840
+ const text3 = await resp.text();
87841
+ if (!text3) {
87842
+ return {};
87843
+ }
87844
+ return JSON.parse(text3);
87845
+ }
87846
+ async function getBotQrcode(opts = {}) {
87847
+ const host = trimTrailingSlash2(opts.host ?? ILINK_LOGIN_HOST);
87848
+ const botType = opts.botType ?? ILINK_BOT_TYPE;
87849
+ const url2 = `${host}/ilink/bot/get_bot_qrcode?bot_type=${botType}`;
87850
+ const resp = await resolveFetch(opts.fetchImpl)(url2, {
87851
+ method: "GET",
87852
+ headers: buildAuthHeaders(),
87853
+ signal: opts.signal
87854
+ });
87855
+ return readJson(resp);
87856
+ }
87857
+ async function getQrcodeStatus(qrcode, opts = {}) {
87858
+ const host = trimTrailingSlash2(opts.host ?? ILINK_LOGIN_HOST);
87859
+ const url2 = `${host}/ilink/bot/get_qrcode_status?qrcode=${encodeURIComponent(qrcode)}`;
87860
+ const resp = await resolveFetch(opts.fetchImpl)(url2, {
87861
+ method: "GET",
87862
+ headers: buildAuthHeaders(),
87863
+ signal: opts.signal
87864
+ });
87865
+ return readJson(resp);
87866
+ }
87867
+ async function getUpdates(opts) {
87868
+ const baseUrl = trimTrailingSlash2(opts.baseUrl);
87869
+ const url2 = `${baseUrl}/ilink/bot/getupdates`;
87870
+ const body = {
87871
+ get_updates_buf: opts.getUpdatesBuf ?? "",
87872
+ base_info: buildBaseInfo()
87873
+ };
87874
+ const resp = await resolveFetch(opts.fetchImpl)(url2, {
87875
+ method: "POST",
87876
+ headers: buildAuthHeaders(opts.botToken),
87877
+ body: JSON.stringify(body),
87878
+ signal: opts.signal
87879
+ });
87880
+ return readJson(resp);
87881
+ }
87882
+ async function sendMessage(opts) {
87883
+ const baseUrl = trimTrailingSlash2(opts.baseUrl);
87884
+ const url2 = `${baseUrl}/ilink/bot/sendmessage`;
87885
+ const itemList = opts.items.map((item) => ({
87886
+ type: ITEM_TYPE_TEXT,
87887
+ text_item: { text: item.text }
87888
+ }));
87889
+ const req = {
87890
+ msg: {
87891
+ to_user_id: opts.toUserId,
87892
+ message_type: MESSAGE_TYPE_BOT,
87893
+ message_state: MESSAGE_STATE_FINISH,
87894
+ context_token: opts.contextToken,
87895
+ client_id: opts.clientId,
87896
+ item_list: itemList
87897
+ },
87898
+ base_info: buildBaseInfo()
87899
+ };
87900
+ const resp = await resolveFetch(opts.fetchImpl)(url2, {
87901
+ method: "POST",
87902
+ headers: buildAuthHeaders(opts.botToken),
87903
+ body: JSON.stringify(req),
87904
+ signal: opts.signal
87905
+ });
87906
+ return readJson(resp);
87907
+ }
87908
+
87909
+ // ../../apps/gateway/src/weixin/ilink/client.ts
87910
+ class WeixinNoContextTokenError extends Error {
87911
+ constructor(toUserId) {
87912
+ super(`No context_token for user ${toUserId}. Receive a message from them first.`);
87913
+ this.name = "WeixinNoContextTokenError";
87914
+ }
87915
+ }
87916
+
87917
+ class WeixinSessionExpiredError extends Error {
87918
+ constructor() {
87919
+ super("iLink bot session expired; re-login required.");
87920
+ this.name = "WeixinSessionExpiredError";
87921
+ }
87922
+ }
87923
+ var DEFAULT_LOGIN_TIMEOUT_MS = 480000;
87924
+ var DEFAULT_QRCODE_POLL_INTERVAL_MS = 1000;
87925
+ var MAX_QRCODE_REFRESH = 3;
87926
+ var RETRY_DELAY_MS = 2000;
87927
+ var BACKOFF_DELAY_MS = 30000;
87928
+ var DEFAULT_LONGPOLL_TIMEOUT_MS = 60000;
87929
+ var LONGPOLL_TIMEOUT_MARGIN_MS = 1e4;
87930
+ function generateClientId() {
87931
+ const bytes = new Uint8Array(16);
87932
+ crypto.getRandomValues(bytes);
87933
+ const hex3 = Array.from(bytes, (b3) => b3.toString(16).padStart(2, "0")).join("");
87934
+ return `${CLIENT_ID_PREFIX}${hex3}`;
87935
+ }
87936
+
87937
+ class AbortError extends Error {
87938
+ constructor() {
87939
+ super("aborted");
87940
+ this.name = "AbortError";
87941
+ }
87942
+ }
87943
+ function sleep3(ms, signal) {
87944
+ return new Promise((resolve3, reject) => {
87945
+ if (signal?.aborted) {
87946
+ reject(new AbortError);
87947
+ return;
87948
+ }
87949
+ const timer = setTimeout(() => {
87950
+ signal?.removeEventListener("abort", onAbort);
87951
+ resolve3();
87952
+ }, ms);
87953
+ const onAbort = () => {
87954
+ clearTimeout(timer);
87955
+ reject(new AbortError);
87956
+ };
87957
+ signal?.addEventListener("abort", onAbort, { once: true });
87958
+ });
87959
+ }
87960
+ function isAbort(err, signal) {
87961
+ if (signal?.aborted)
87962
+ return true;
87963
+ if (err instanceof AbortError)
87964
+ return true;
87965
+ if (err instanceof DOMException && err.name === "AbortError")
87966
+ return true;
87967
+ if (err instanceof Error && err.name === "AbortError")
87968
+ return true;
87969
+ return false;
87970
+ }
87971
+ function isSessionExpired(resp) {
87972
+ return resp.ret === SESSION_EXPIRED_ERRCODE || resp.errcode === SESSION_EXPIRED_ERRCODE;
87973
+ }
87974
+
87975
+ class WeixinClient {
87976
+ creds = null;
87977
+ fetchImpl;
87978
+ contextTokens = new Map;
87979
+ running = false;
87980
+ internalAbort = null;
87981
+ constructor(opts = {}) {
87982
+ this.fetchImpl = opts.fetchImpl;
87983
+ if (opts.accountId && opts.botToken && opts.baseUrl) {
87984
+ this.creds = {
87985
+ accountId: opts.accountId,
87986
+ botToken: opts.botToken,
87987
+ baseUrl: opts.baseUrl
87988
+ };
87989
+ }
87990
+ }
87991
+ get credentials() {
87992
+ return this.creds;
87993
+ }
87994
+ getContextToken(userId) {
87995
+ return this.contextTokens.get(userId);
87996
+ }
87997
+ setContextToken(userId, token) {
87998
+ this.contextTokens.set(userId, token);
87999
+ }
88000
+ isRunning() {
88001
+ return this.running;
88002
+ }
88003
+ static extractText(raw) {
88004
+ const msg = raw;
88005
+ if (!msg || typeof msg !== "object" || !Array.isArray(msg.item_list)) {
88006
+ return "";
88007
+ }
88008
+ const parts = [];
88009
+ for (const item of msg.item_list) {
88010
+ if (item?.type === ITEM_TYPE_TEXT && typeof item.text_item?.text === "string") {
88011
+ parts.push(item.text_item.text);
88012
+ }
88013
+ }
88014
+ return parts.join("");
88015
+ }
88016
+ async login(opts) {
88017
+ const { onQrcode, signal } = opts;
88018
+ const timeoutMs = Math.max(1000, opts.timeoutMs ?? DEFAULT_LOGIN_TIMEOUT_MS);
88019
+ const pollIntervalMs = opts.pollIntervalMs ?? DEFAULT_QRCODE_POLL_INTERVAL_MS;
88020
+ const deadline = Date.now() + timeoutMs;
88021
+ let qrcodeId = await this.fetchQrcode(onQrcode, signal);
88022
+ let refreshes = 0;
88023
+ while (Date.now() < deadline) {
88024
+ if (signal?.aborted)
88025
+ throw new AbortError;
88026
+ await sleep3(pollIntervalMs, signal);
88027
+ let status;
88028
+ try {
88029
+ status = await getQrcodeStatus(qrcodeId, {
88030
+ fetchImpl: this.fetchImpl,
88031
+ signal
88032
+ });
88033
+ } catch (err) {
88034
+ if (isAbort(err, signal))
88035
+ throw err;
88036
+ continue;
88037
+ }
88038
+ switch (status.status) {
88039
+ case "confirmed": {
88040
+ if (!status.bot_token || !status.baseurl) {
88041
+ throw new Error("iLink login confirmed but bot_token/baseurl missing.");
88042
+ }
88043
+ const accountId = status.ilink_bot_id ?? status.ilink_user_id ?? status.bot_token;
88044
+ this.creds = {
88045
+ accountId,
88046
+ botToken: status.bot_token,
88047
+ baseUrl: status.baseurl
88048
+ };
88049
+ return this.creds;
88050
+ }
88051
+ case "expired": {
88052
+ if (refreshes >= MAX_QRCODE_REFRESH) {
88053
+ throw new Error("iLink qrcode expired and refresh limit reached.");
88054
+ }
88055
+ refreshes += 1;
88056
+ qrcodeId = await this.fetchQrcode(onQrcode, signal);
88057
+ break;
88058
+ }
88059
+ default:
88060
+ break;
88061
+ }
88062
+ }
88063
+ throw new Error("iLink login timed out.");
88064
+ }
88065
+ async fetchQrcode(onQrcode, signal) {
88066
+ const resp = await getBotQrcode({ fetchImpl: this.fetchImpl, signal });
88067
+ if (!resp.qrcode) {
88068
+ throw new Error("iLink get_bot_qrcode returned no qrcode.");
88069
+ }
88070
+ if (!resp.qrcode_img_content) {
88071
+ throw new Error("iLink get_bot_qrcode returned no qrcode content.");
88072
+ }
88073
+ onQrcode({
88074
+ url: resp.qrcode_img_content,
88075
+ qrcodeId: resp.qrcode
88076
+ });
88077
+ return resp.qrcode;
88078
+ }
88079
+ async start(opts = {}) {
88080
+ if (!this.creds) {
88081
+ throw new Error("WeixinClient.start called without credentials; login first.");
88082
+ }
88083
+ if (this.running) {
88084
+ throw new Error("WeixinClient already running.");
88085
+ }
88086
+ if (opts.initialContextTokens) {
88087
+ for (const [userId, token] of Object.entries(opts.initialContextTokens)) {
88088
+ this.contextTokens.set(userId, token);
88089
+ }
88090
+ }
88091
+ this.internalAbort = new AbortController;
88092
+ const signal = this.linkSignals(opts.signal, this.internalAbort.signal);
88093
+ this.running = true;
88094
+ let getUpdatesBuf = await opts.loadSyncBuf?.() ?? "";
88095
+ let failures = 0;
88096
+ let longpollTimeoutMs = opts.longpollTimeoutMs ?? DEFAULT_LONGPOLL_TIMEOUT_MS;
88097
+ try {
88098
+ while (!signal.aborted) {
88099
+ let resp;
88100
+ try {
88101
+ const perRequestSignal = AbortSignal.any([
88102
+ signal,
88103
+ AbortSignal.timeout(longpollTimeoutMs)
88104
+ ]);
88105
+ resp = await getUpdates({
88106
+ baseUrl: this.creds.baseUrl,
88107
+ botToken: this.creds.botToken,
88108
+ getUpdatesBuf,
88109
+ fetchImpl: this.fetchImpl,
88110
+ signal: perRequestSignal
88111
+ });
88112
+ } catch (err) {
88113
+ if (signal.aborted)
88114
+ break;
88115
+ opts.onError?.(err);
88116
+ failures += 1;
88117
+ await this.backoffSleep(failures, signal);
88118
+ continue;
88119
+ }
88120
+ if (isSessionExpired(resp)) {
88121
+ opts.onSessionExpired?.();
88122
+ throw new WeixinSessionExpiredError;
88123
+ }
88124
+ if (typeof resp.ret === "number" && resp.ret !== 0) {
88125
+ opts.onError?.(new Error(`getupdates ret=${resp.ret} errmsg=${resp.errmsg ?? ""}`));
88126
+ failures += 1;
88127
+ await this.backoffSleep(failures, signal);
88128
+ continue;
88129
+ }
88130
+ failures = 0;
88131
+ if (typeof resp.longpolling_timeout_ms === "number" && resp.longpolling_timeout_ms > 0) {
88132
+ longpollTimeoutMs = resp.longpolling_timeout_ms + LONGPOLL_TIMEOUT_MARGIN_MS;
88133
+ }
88134
+ const msgs = resp.msgs ?? [];
88135
+ for (const msg of msgs) {
88136
+ if (signal.aborted)
88137
+ break;
88138
+ const inbound = this.toInbound(msg);
88139
+ if (msg.from_user_id && msg.context_token) {
88140
+ this.contextTokens.set(msg.from_user_id, msg.context_token);
88141
+ }
88142
+ try {
88143
+ await opts.onMessage?.(inbound);
88144
+ } catch (err) {
88145
+ opts.onError?.(err);
88146
+ }
88147
+ }
88148
+ if (resp.get_updates_buf != null && resp.get_updates_buf !== "") {
88149
+ getUpdatesBuf = resp.get_updates_buf;
88150
+ await opts.saveSyncBuf?.(getUpdatesBuf);
88151
+ }
88152
+ }
88153
+ } finally {
88154
+ this.running = false;
88155
+ this.internalAbort = null;
88156
+ }
88157
+ }
88158
+ stop() {
88159
+ this.internalAbort?.abort();
88160
+ }
88161
+ async sendText(toUserId, text3, contextToken) {
88162
+ if (!this.creds) {
88163
+ throw new Error("WeixinClient.sendText called without credentials; login first.");
88164
+ }
88165
+ const token = contextToken ?? this.contextTokens.get(toUserId);
88166
+ if (!token) {
88167
+ throw new WeixinNoContextTokenError(toUserId);
88168
+ }
88169
+ const resp = await sendMessage({
88170
+ baseUrl: this.creds.baseUrl,
88171
+ botToken: this.creds.botToken,
88172
+ toUserId,
88173
+ contextToken: token,
88174
+ clientId: generateClientId(),
88175
+ items: [{ text: text3 }],
88176
+ fetchImpl: this.fetchImpl
88177
+ });
88178
+ if (resp.ret === SESSION_EXPIRED_ERRCODE || resp.errcode === SESSION_EXPIRED_ERRCODE) {
88179
+ throw new WeixinSessionExpiredError;
88180
+ }
88181
+ if (typeof resp.ret === "number" && resp.ret !== 0) {
88182
+ throw new Error(`sendmessage ret=${resp.ret} errmsg=${resp.errmsg ?? ""}`);
88183
+ }
88184
+ }
88185
+ toInbound(msg) {
88186
+ return {
88187
+ fromUserId: msg.from_user_id ?? "",
88188
+ contextToken: msg.context_token ?? null,
88189
+ text: WeixinClient.extractText(msg),
88190
+ raw: msg
88191
+ };
88192
+ }
88193
+ async backoffSleep(failures, signal) {
88194
+ const delay2 = Math.min(RETRY_DELAY_MS * 2 ** Math.max(0, failures - 1), BACKOFF_DELAY_MS);
88195
+ try {
88196
+ await sleep3(delay2, signal);
88197
+ } catch {}
88198
+ }
88199
+ linkSignals(external3, internal) {
88200
+ if (!external3)
88201
+ return internal;
88202
+ const controller = new AbortController;
88203
+ const onAbort = () => controller.abort();
88204
+ if (external3.aborted || internal.aborted) {
88205
+ controller.abort();
88206
+ return controller.signal;
88207
+ }
88208
+ external3.addEventListener("abort", onAbort, { once: true });
88209
+ internal.addEventListener("abort", onAbort, { once: true });
88210
+ return controller.signal;
88211
+ }
88212
+ }
88213
+
88214
+ // ../../apps/gateway/src/weixin/service.ts
88215
+ var KEEPALIVE_INTERVAL_MS = 8 * 60 * 60 * 1000;
88216
+ var KEEPALIVE_SWEEP_MS = 30 * 60 * 1000;
88217
+
88218
+ class WeixinService {
88219
+ createClient;
88220
+ runningAccounts = new Map;
88221
+ loginSessions = new Map;
88222
+ sessionExpiredNotified = new Set;
88223
+ refreshChain = Promise.resolve();
88224
+ keepaliveTimer = null;
88225
+ lastKeepaliveAt = new Map;
88226
+ constructor(createClient = (opts) => new WeixinClient(opts)) {
88227
+ this.createClient = createClient;
88228
+ }
88229
+ async refresh() {
88230
+ const run = this.refreshChain.then(() => this.doRefresh());
88231
+ this.refreshChain = run.catch(() => {});
88232
+ return run;
88233
+ }
88234
+ async doRefresh() {
88235
+ this.ensureKeepaliveTimer();
88236
+ const accounts = getAllWeixinAccounts();
88237
+ const activeIds = new Set(accounts.map((a) => a.id));
88238
+ for (const [id, session] of this.loginSessions) {
88239
+ if (!activeIds.has(id)) {
88240
+ session.abort.abort();
88241
+ this.loginSessions.delete(id);
88242
+ }
88243
+ }
88244
+ const toStop = [];
88245
+ for (const [id] of this.runningAccounts) {
88246
+ if (!activeIds.has(id)) {
88247
+ toStop.push(id);
88248
+ }
88249
+ }
88250
+ await Promise.all(toStop.map((id) => this.stopAccount(id)));
88251
+ for (const account of accounts) {
88252
+ const shouldRun = account.enabled && account.botTokenEnc != null && account.baseUrl != null;
88253
+ if (!shouldRun) {
88254
+ await this.stopAccount(account.id);
88255
+ continue;
88256
+ }
88257
+ let botToken;
88258
+ try {
88259
+ botToken = await decryptWithContext(account.botTokenEnc, {
88260
+ scope: "weixin_account",
88261
+ entityId: account.id,
88262
+ field: "bot_token_enc"
88263
+ });
88264
+ } catch (err) {
88265
+ console.error(`[weixin] failed to decrypt token for ${account.id}:`, err);
88266
+ continue;
88267
+ }
88268
+ const running = this.runningAccounts.get(account.id);
88269
+ if (running && running.botToken === botToken) {
88270
+ continue;
88271
+ }
88272
+ if (running) {
88273
+ await this.stopAccount(account.id);
88274
+ }
88275
+ this.startAccount(account, botToken);
88276
+ }
88277
+ }
88278
+ startAccount(account, botToken) {
88279
+ const baseUrl = account.baseUrl ?? "";
88280
+ const weixinUin = account.weixinUin ?? account.id;
88281
+ const client = this.createClient({ accountId: weixinUin, botToken, baseUrl });
88282
+ for (const { userId, contextToken } of getWeixinUserContextTokens(account.id)) {
88283
+ client.setContextToken(userId, contextToken);
88284
+ }
88285
+ this.runningAccounts.set(account.id, { id: account.id, botToken, client });
88286
+ this.sessionExpiredNotified.delete(account.id);
88287
+ client.start({
88288
+ loadSyncBuf: () => getWeixinAccountById(account.id)?.syncBuf ?? undefined,
88289
+ saveSyncBuf: (buf) => {
88290
+ updateWeixinAccount(account.id, { syncBuf: buf });
88291
+ },
88292
+ onMessage: (msg) => this.handleInbound(account.id, msg),
88293
+ onSessionExpired: () => this.handleSessionExpired(account.id),
88294
+ onError: (err) => console.error(`[weixin] account ${account.id} poll error:`, err)
88295
+ }).catch((err) => {
88296
+ if (!(err instanceof WeixinSessionExpiredError)) {
88297
+ console.error(`[weixin] account ${account.id} loop crashed:`, err);
88298
+ }
88299
+ }).finally(() => {
88300
+ const cur = this.runningAccounts.get(account.id);
88301
+ if (cur && cur.client === client) {
88302
+ this.runningAccounts.delete(account.id);
88303
+ }
88304
+ });
88305
+ console.log(`[weixin] account started: ${account.name} (${account.id})`);
88306
+ }
88307
+ async stopAccount(accountId) {
88308
+ const running = this.runningAccounts.get(accountId);
88309
+ if (!running) {
88310
+ return;
88311
+ }
88312
+ running.client.stop();
88313
+ this.runningAccounts.delete(accountId);
88314
+ console.log(`[weixin] account stopped: ${accountId}`);
88315
+ }
88316
+ async handleInbound(accountId, msg) {
88317
+ const account = getWeixinAccountById(accountId);
88318
+ if (!account) {
88319
+ return;
88320
+ }
88321
+ const userId = msg.fromUserId;
88322
+ if (!userId) {
88323
+ return;
88324
+ }
88325
+ const now2 = new Date().toISOString();
88326
+ try {
88327
+ upsertWeixinUserOnInbound({
88328
+ accountId,
88329
+ userId,
88330
+ displayName: userId,
88331
+ contextToken: msg.contextToken,
88332
+ allowAuthRequests: account.allowAuthRequests,
88333
+ at: now2
88334
+ });
88335
+ } catch (err) {
88336
+ console.error("[weixin] failed to upsert user on inbound:", err);
88337
+ }
88338
+ }
88339
+ handleSessionExpired(accountId) {
88340
+ if (!this.sessionExpiredNotified.has(accountId)) {
88341
+ this.sessionExpiredNotified.add(accountId);
88342
+ const account = getWeixinAccountById(accountId);
88343
+ console.warn(`[weixin] account ${account?.name ?? accountId} session expired; re-login required.`);
88344
+ for (const user of listAuthorizedWeixinUsersByAccount(accountId)) {
88345
+ setWeixinUserNeedsReactivation(accountId, user.userId, true);
88346
+ }
88347
+ }
88348
+ updateWeixinAccount(accountId, { botTokenEnc: null, baseUrl: null, weixinUin: null });
88349
+ }
88350
+ async sendGatewayOnlineMessage(siteName) {
88351
+ await this.sendToAuthorizedUsers({ text: t2("weixin.gatewayOnline", { siteName }) });
88352
+ }
88353
+ async sendToAuthorizedUsers(params) {
88354
+ for (const [accountId, running] of this.runningAccounts) {
88355
+ for (const user of listAuthorizedWeixinUsersByAccount(accountId)) {
88356
+ const cont = await this.sendToUser(accountId, running, user, params.text);
88357
+ if (!cont)
88358
+ break;
88359
+ }
88360
+ }
88361
+ }
88362
+ async sendToUser(accountId, running, user, text3) {
88363
+ try {
88364
+ await running.client.sendText(user.userId, text3);
88365
+ if (user.needsReactivation) {
88366
+ setWeixinUserNeedsReactivation(accountId, user.userId, false);
88367
+ }
88368
+ return true;
88369
+ } catch (err) {
88370
+ if (err instanceof WeixinSessionExpiredError) {
88371
+ this.handleSessionExpired(accountId);
88372
+ return false;
88373
+ }
88374
+ if (!user.needsReactivation) {
88375
+ console.warn(`[weixin] send to ${user.userId} failed; marking needs-reactivation:`, err instanceof Error ? err.message : err);
88376
+ }
88377
+ setWeixinUserNeedsReactivation(accountId, user.userId, true);
88378
+ return true;
88379
+ }
88380
+ }
88381
+ ensureKeepaliveTimer() {
88382
+ if (this.keepaliveTimer) {
88383
+ return;
88384
+ }
88385
+ this.keepaliveTimer = setInterval(() => void this.runKeepaliveSweep(), KEEPALIVE_SWEEP_MS);
88386
+ this.keepaliveTimer.unref?.();
88387
+ }
88388
+ async runKeepaliveSweep() {
88389
+ const now2 = Date.now();
88390
+ for (const [accountId, running] of this.runningAccounts) {
88391
+ for (const user of listAuthorizedWeixinUsersByAccount(accountId)) {
88392
+ const key = `${accountId}:${user.userId}`;
88393
+ const lastInbound = user.lastInboundAt ? Date.parse(user.lastInboundAt) : 0;
88394
+ const since = Math.max(lastInbound, this.lastKeepaliveAt.get(key) ?? 0);
88395
+ if (now2 - since < KEEPALIVE_INTERVAL_MS) {
88396
+ continue;
88397
+ }
88398
+ this.lastKeepaliveAt.set(key, now2);
88399
+ const cont = await this.sendToUser(accountId, running, user, t2("weixin.keepalivePrompt"));
88400
+ if (!cont)
88401
+ break;
88402
+ }
88403
+ }
88404
+ }
88405
+ async sendTestMessage(accountId, userId, text3) {
88406
+ const running = this.runningAccounts.get(accountId);
88407
+ if (!running) {
88408
+ throw new Error(t2("weixin.accountNotRunning"));
88409
+ }
88410
+ await running.client.sendText(userId, text3);
88411
+ }
88412
+ async sendTestMessageToBoundUser(accountId, text3) {
88413
+ const [user] = listAuthorizedWeixinUsersByAccount(accountId);
88414
+ if (!user) {
88415
+ throw new Error(t2("weixin.userNotFound"));
88416
+ }
88417
+ await this.sendTestMessage(accountId, user.userId, text3);
88418
+ }
88419
+ async startLogin(accountId) {
88420
+ const account = getWeixinAccountById(accountId);
88421
+ if (!account) {
88422
+ throw new Error(t2("weixin.accountNotFound"));
88423
+ }
88424
+ const prev = this.loginSessions.get(accountId);
88425
+ if (prev) {
88426
+ prev.abort.abort();
88427
+ this.loginSessions.delete(accountId);
88428
+ }
88429
+ const abort = new AbortController;
88430
+ const client = this.createClient({});
88431
+ return await new Promise((resolve3, reject) => {
88432
+ let resolved = false;
88433
+ const loginPromise = client.login({
88434
+ signal: abort.signal,
88435
+ onQrcode: (qr) => {
88436
+ this.loginSessions.set(accountId, {
88437
+ status: "pending",
88438
+ qrcodeUrl: qr.url,
88439
+ qrcodeId: qr.qrcodeId,
88440
+ client,
88441
+ abort
88442
+ });
88443
+ if (!resolved) {
88444
+ resolved = true;
88445
+ resolve3({ qrcodeUrl: qr.url, qrcodeId: qr.qrcodeId });
88446
+ }
88447
+ }
88448
+ });
88449
+ loginPromise.then(async (creds) => {
88450
+ await this.persistLogin(accountId, creds);
88451
+ const session = this.loginSessions.get(accountId);
88452
+ if (session) {
88453
+ session.status = "confirmed";
88454
+ }
88455
+ await this.refresh();
88456
+ }).catch((err) => {
88457
+ const message = err instanceof Error ? err.message : String(err);
88458
+ const status = /expired|timed out/i.test(message) ? "expired" : "error";
88459
+ const session = this.loginSessions.get(accountId);
88460
+ if (session) {
88461
+ session.status = status;
88462
+ session.message = message;
88463
+ } else {
88464
+ this.loginSessions.set(accountId, {
88465
+ status,
88466
+ message,
88467
+ qrcodeUrl: "",
88468
+ qrcodeId: "",
88469
+ client,
88470
+ abort
88471
+ });
88472
+ }
88473
+ if (!resolved) {
88474
+ resolved = true;
88475
+ reject(err);
88476
+ }
88477
+ });
88478
+ });
88479
+ }
88480
+ getLoginStatus(accountId) {
88481
+ const account = getWeixinAccountById(accountId);
88482
+ const loggedIn = account?.botTokenEnc != null;
88483
+ const session = this.loginSessions.get(accountId);
88484
+ if (!session) {
88485
+ return { status: loggedIn ? "confirmed" : "pending", loggedIn };
88486
+ }
88487
+ return { status: session.status, loggedIn, message: session.message };
88488
+ }
88489
+ async persistLogin(accountId, creds) {
88490
+ const botTokenEnc = await encrypt(creds.botToken);
88491
+ updateWeixinAccount(accountId, {
88492
+ weixinUin: creds.accountId,
88493
+ botTokenEnc,
88494
+ baseUrl: creds.baseUrl
88495
+ });
88496
+ this.sessionExpiredNotified.delete(accountId);
88497
+ }
88498
+ async stopAll() {
88499
+ if (this.keepaliveTimer) {
88500
+ clearInterval(this.keepaliveTimer);
88501
+ this.keepaliveTimer = null;
88502
+ }
88503
+ for (const session of this.loginSessions.values()) {
88504
+ session.abort.abort();
88505
+ }
88506
+ this.loginSessions.clear();
88507
+ const ids = Array.from(this.runningAccounts.keys());
88508
+ await Promise.all(ids.map((id) => this.stopAccount(id)));
88509
+ }
88510
+ }
88511
+ var weixinService = new WeixinService;
88512
+
88513
+ // ../../apps/gateway/src/events/channels/weixin.ts
88514
+ var EMOJI_MAP = {
88515
+ terminal_bell: "\uD83D\uDD14",
88516
+ terminal_notification: "\uD83D\uDD14",
88517
+ tmux_window_close: "\uD83E\uDE9F",
88518
+ tmux_pane_close: "\uD83D\uDCF1",
88519
+ device_tmux_missing: "\u26A0\uFE0F",
88520
+ device_disconnect: "\uD83D\uDD0C",
88521
+ session_created: "\uD83C\uDD95",
88522
+ session_closed: "\uD83D\uDEAA",
88523
+ agent_confirmation_pending: "\uD83E\uDD16",
88524
+ agent_turn_finished: "\uD83E\uDD16",
88525
+ agent_error: "\uD83E\uDD16",
88526
+ watch_triggered: "\uD83D\uDC41\uFE0F",
88527
+ watch_model_unavailable: "\uD83D\uDC41\uFE0F",
88528
+ watch_rule_error: "\uD83D\uDC41\uFE0F"
88529
+ };
88530
+
88531
+ class WeixinChannel {
88532
+ id = "weixin";
88533
+ async notify(eventType, event) {
88534
+ const settings = getSiteSettings();
88535
+ if (eventType === "terminal_bell") {
88536
+ if (!settings.enableWeixinBellPush) {
88537
+ return;
88538
+ }
88539
+ await weixinService.sendToAuthorizedUsers({ text: this.formatBellMessage(event) });
88540
+ return;
88541
+ }
88542
+ if (!settings.enableWeixinNotificationPush) {
88543
+ return;
88544
+ }
88545
+ const text3 = eventType === "terminal_notification" ? this.formatNotificationMessage(event) : this.formatGenericMessage(event, settings);
88546
+ await weixinService.sendToAuthorizedUsers({ text: text3 });
88547
+ }
88548
+ buildTerminalTopbarLabel(event) {
88549
+ const windowLabel = typeof event.tmux?.windowIndex === "number" ? `${event.tmux.windowIndex}` : event.tmux?.windowId ?? "?";
88550
+ const paneLabel = typeof event.tmux?.paneIndex === "number" ? `${event.tmux.paneIndex}` : event.tmux?.paneId ?? "?";
88551
+ return t2("notification.telegramBell.terminalTopbarLabel", {
88552
+ window: windowLabel,
88553
+ pane: paneLabel,
88554
+ device: event.device.name
88555
+ });
88556
+ }
88557
+ buildPaneMetaLines(event) {
88558
+ const lines = [];
88559
+ if (event.tmux?.paneTitle) {
88560
+ lines.push(`${t2("notification.paneTitle")}\uFF1A${event.tmux.paneTitle}`);
88561
+ }
88562
+ if (event.tmux?.paneCurrentCommand) {
88563
+ lines.push(`${t2("notification.process")}\uFF1A${event.tmux.paneCurrentCommand}`);
88564
+ }
88565
+ return lines;
88566
+ }
88567
+ formatBellMessage(event) {
88568
+ const lines = [
88569
+ t2("notification.telegramBell.title", {
88570
+ siteName: event.site.name,
88571
+ terminalTopbarLabel: this.buildTerminalTopbarLabel(event)
88572
+ })
88573
+ ];
88574
+ lines.push(...this.buildPaneMetaLines(event));
88575
+ const paneUrl = normalizeHttpUrl(buildPaneUrl(event));
88576
+ if (paneUrl) {
88577
+ lines.push("", paneUrl);
88578
+ }
88579
+ return lines.join(`
88580
+ `);
88581
+ }
88582
+ formatNotificationMessage(event) {
88583
+ const title = typeof event.payload?.title === "string" ? event.payload.title : "";
88584
+ const body = typeof event.payload?.message === "string" ? event.payload.message : "";
88585
+ const lines = [];
88586
+ if (title) {
88587
+ lines.push(title);
88588
+ }
88589
+ if (body) {
88590
+ lines.push(body);
88591
+ }
88592
+ lines.push(...this.buildPaneMetaLines(event));
88593
+ const footer = `from ${event.site.name}: ${this.buildTerminalTopbarLabel(event)}`;
88594
+ lines.push("", footer);
88595
+ const paneUrl = normalizeHttpUrl(buildPaneUrl(event));
88596
+ if (paneUrl) {
88597
+ lines.push(paneUrl);
88598
+ }
88599
+ return lines.join(`
88600
+ `);
88601
+ }
88602
+ formatGenericMessage(event, settings) {
88603
+ const eventTypeLabel = t2(`notification.eventType.${event.eventType}`);
88604
+ const windowLabel = event.tmux?.windowIndex !== undefined ? `${event.tmux.windowIndex} (${event.tmux.windowId ?? "-"})` : event.tmux?.windowId ?? "-";
88605
+ const paneLabel = event.tmux?.paneIndex !== undefined ? `${event.tmux.paneIndex} (${event.tmux.paneId ?? "-"})` : event.tmux?.paneId ?? "-";
88606
+ const lines = [
88607
+ `${EMOJI_MAP[event.eventType] ?? "\uD83D\uDCE2"} ${eventTypeLabel}`,
88608
+ `${t2("notification.site")}\uFF1A${event.site.name}`,
88609
+ `${t2("notification.time")}\uFF1A${new Date(event.timestamp).toLocaleString(toBCP47(settings.language))}`,
88610
+ `${t2("notification.device")}\uFF1A${event.device.name} (${event.device.type})`,
88611
+ `${t2("notification.window")}\uFF1A${windowLabel}`,
88612
+ `${t2("notification.pane")}\uFF1A${paneLabel}`
88613
+ ];
88614
+ lines.push(...this.buildPaneMetaLines(event));
88615
+ if (typeof event.payload?.message === "string") {
88616
+ lines.push(`${t2("notification.message")}\uFF1A${event.payload.message}`);
88617
+ }
88618
+ const paneUrl = normalizeHttpUrl(buildPaneUrl(event));
88619
+ if (paneUrl) {
88620
+ lines.push("", paneUrl);
88621
+ }
88622
+ return lines.join(`
88623
+ `);
88624
+ }
88625
+ }
88626
+ var weixinChannel = new WeixinChannel;
88627
+
88628
+ // ../../apps/gateway/src/events/index.ts
88629
+ class EventNotifier {
88630
+ bellThrottleMap = new Map;
88631
+ notificationThrottleMap = new Map;
88632
+ channels = [
88633
+ webhookChannel,
88634
+ telegramChannel,
88635
+ weixinChannel
88636
+ ];
88637
+ async notify(eventType, event) {
88638
+ const fullEvent = {
88639
+ ...event,
88640
+ eventType,
88641
+ timestamp: new Date().toISOString()
88642
+ };
88643
+ if (eventType === "terminal_bell") {
88644
+ if (!this.shouldPassBellThrottle(fullEvent)) {
88645
+ return;
88646
+ }
88647
+ } else if (eventType === "terminal_notification") {
88648
+ if (!this.shouldPassNotificationThrottle(fullEvent)) {
88649
+ return;
88650
+ }
88651
+ }
88652
+ await Promise.all(this.channels.map((channel) => channel.notify(eventType, fullEvent)));
88653
+ }
88654
+ shouldPassBellThrottle(event) {
88655
+ const settings = getSiteSettings();
88656
+ const throttleMs = Math.max(0, settings.bellThrottleSeconds) * 1000;
88657
+ if (throttleMs === 0) {
88658
+ return true;
88659
+ }
88660
+ const key = `${event.device.id}:${event.tmux?.paneId ?? "-"}:${event.eventType}`;
88661
+ const now2 = Date.now();
88662
+ const previous = this.bellThrottleMap.get(key) ?? 0;
88663
+ if (now2 - previous < throttleMs) {
88664
+ return false;
88665
+ }
88666
+ this.bellThrottleMap.set(key, now2);
88667
+ return true;
88668
+ }
88669
+ shouldPassNotificationThrottle(event) {
88670
+ const settings = getSiteSettings();
88671
+ const throttleMs = Math.max(0, settings.notificationThrottleSeconds) * 1000;
88672
+ if (throttleMs === 0) {
88673
+ return true;
88674
+ }
88675
+ const source = typeof event.payload?.source === "string" ? event.payload.source : "unknown";
88676
+ const key = `${event.device.id}:${event.tmux?.paneId ?? "-"}:notification:${source}`;
88677
+ const now2 = Date.now();
88678
+ const previous = this.notificationThrottleMap.get(key) ?? 0;
88679
+ if (now2 - previous < throttleMs) {
88680
+ return false;
88681
+ }
88682
+ this.notificationThrottleMap.set(key, now2);
88683
+ return true;
88684
+ }
88685
+ }
87300
88686
  var eventNotifier = new EventNotifier;
87301
88687
 
87302
88688
  // ../../node_modules/.bun/@ai-sdk+openai@3.0.71+68a1e3a0c4588df3/node_modules/@ai-sdk/openai/dist/index.mjs
@@ -96906,6 +98292,28 @@ function createPaneStreamParser(options) {
96906
98292
  options.onNotification({ source: "osc1337", body: "RequestAttention" });
96907
98293
  }
96908
98294
  return;
98295
+ case "52": {
98296
+ const separatorIndex = payload.indexOf(";");
98297
+ if (separatorIndex < 0) {
98298
+ return;
98299
+ }
98300
+ const base64Data = payload.slice(separatorIndex + 1);
98301
+ if (!base64Data || base64Data === "?") {
98302
+ return;
98303
+ }
98304
+ try {
98305
+ const binaryString = atob(base64Data);
98306
+ const bytes = new Uint8Array(binaryString.length);
98307
+ for (let i = 0;i < binaryString.length; i++) {
98308
+ bytes[i] = binaryString.charCodeAt(i);
98309
+ }
98310
+ const text3 = new TextDecoder("utf-8", { fatal: false }).decode(bytes);
98311
+ if (text3) {
98312
+ options.onClipboardWrite?.(text3);
98313
+ }
98314
+ } catch {}
98315
+ return;
98316
+ }
96909
98317
  case "133": {
96910
98318
  const parts = payload.split(";");
96911
98319
  const kind = parts[0];
@@ -97045,7 +98453,7 @@ function createPaneStreamParser(options) {
97045
98453
  }
97046
98454
  if (phase === "osc-params") {
97047
98455
  if (byte === 59) {
97048
- phase = oscKind === "0" || oscKind === "1" || oscKind === "2" || oscKind === "9" || oscKind === "99" || oscKind === "133" || oscKind === "777" || oscKind === "1337" ? "osc-body" : "osc-body-ignore";
98456
+ phase = oscKind === "0" || oscKind === "1" || oscKind === "2" || oscKind === "9" || oscKind === "52" || oscKind === "99" || oscKind === "133" || oscKind === "777" || oscKind === "1337" ? "osc-body" : "osc-body-ignore";
97049
98457
  return;
97050
98458
  }
97051
98459
  if (byte === 7) {
@@ -97173,7 +98581,8 @@ function createControlModeSubscription(callbacks) {
97173
98581
  onTitle: (title) => callbacks.onTitle(paneId, title),
97174
98582
  onBell: () => callbacks.onBell(paneId),
97175
98583
  onNotification: (notification) => callbacks.onNotification(paneId, notification),
97176
- onPromptMarker: (marker24) => callbacks.onPromptMarker?.(paneId, marker24)
98584
+ onPromptMarker: (marker24) => callbacks.onPromptMarker?.(paneId, marker24),
98585
+ onClipboardWrite: (text3) => callbacks.onClipboardWrite?.(paneId, text3)
97177
98586
  });
97178
98587
  paneParsers.set(paneId, parser2);
97179
98588
  return parser2;
@@ -97676,7 +99085,10 @@ class LocalExternalTmuxConnection {
97676
99085
  if (!this.connected) {
97677
99086
  return;
97678
99087
  }
97679
- const argv = name24 ? ["new-window", "-t", this.sessionName, "-n", name24] : ["new-window", "-t", this.sessionName];
99088
+ const argv = ["new-window", "-t", this.sessionName, "-c", this.resolveDefaultWorkingDir()];
99089
+ if (name24) {
99090
+ argv.push("-n", name24);
99091
+ }
97680
99092
  this.runAndRefresh(argv).catch((error51) => {
97681
99093
  this.callbacks.onError(error51);
97682
99094
  });
@@ -97705,6 +99117,20 @@ class LocalExternalTmuxConnection {
97705
99117
  this.callbacks.onError(error51);
97706
99118
  });
97707
99119
  }
99120
+ updateDefaultWorkingDir(dir2) {
99121
+ if (this.device) {
99122
+ this.device = { ...this.device, defaultWorkingDir: dir2 };
99123
+ }
99124
+ if (this.connected) {
99125
+ this.runTmuxAllowFailure([
99126
+ "set-option",
99127
+ "-t",
99128
+ this.sessionName,
99129
+ "default-path",
99130
+ this.resolveDefaultWorkingDir()
99131
+ ]);
99132
+ }
99133
+ }
97708
99134
  setWindowStyle(style) {
97709
99135
  if (!this.connected) {
97710
99136
  return;
@@ -97734,12 +99160,15 @@ class LocalExternalTmuxConnection {
97734
99160
  const { stdout } = await this.runTmux(["display-message", "-p", "-t", paneId, PANE_META_FORMAT], "silent");
97735
99161
  return parsePaneMeta(stdout);
97736
99162
  }
99163
+ resolveDefaultWorkingDir() {
99164
+ return this.device?.defaultWorkingDir?.trim() || homedir();
99165
+ }
97737
99166
  async ensureSession() {
97738
99167
  const exists3 = await this.runTmuxAllowFailure(["has-session", "-t", this.sessionName]);
97739
99168
  if (exists3.exitCode === 0) {
97740
99169
  return;
97741
99170
  }
97742
- await this.runTmux(["new-session", "-d", "-c", homedir(), "-s", this.sessionName]);
99171
+ await this.runTmux(["new-session", "-d", "-c", this.resolveDefaultWorkingDir(), "-s", this.sessionName]);
97743
99172
  }
97744
99173
  async configureSessionOptions() {
97745
99174
  await this.runTmuxAllowFailure([
@@ -97793,6 +99222,13 @@ class LocalExternalTmuxConnection {
97793
99222
  "COLORTERM",
97794
99223
  "truecolor"
97795
99224
  ]);
99225
+ await this.runTmuxAllowFailure([
99226
+ "set-option",
99227
+ "-t",
99228
+ this.sessionName,
99229
+ "default-path",
99230
+ this.resolveDefaultWorkingDir()
99231
+ ]);
97796
99232
  await this.configureWindowStyle();
97797
99233
  }
97798
99234
  async configureWindowStyle(styleValue = config.tmuxWindowStyle) {
@@ -97911,6 +99347,9 @@ class LocalExternalTmuxConnection {
97911
99347
  onPromptMarker: (paneId, marker24) => {
97912
99348
  this.callbacks.onPromptMarker?.(paneId, marker24);
97913
99349
  },
99350
+ onClipboardWrite: (paneId, text3) => {
99351
+ this.callbacks.onClipboardWrite?.(paneId, text3);
99352
+ },
97914
99353
  onStructureChanged: () => {
97915
99354
  this.requestSnapshot();
97916
99355
  },
@@ -98054,7 +99493,7 @@ class LocalExternalTmuxConnection {
98054
99493
  async closeWindowInternal(windowId) {
98055
99494
  const count2 = Number.parseInt((await this.runTmux(["display-message", "-p", "-t", this.sessionName, "#{session_windows}"])).stdout.trim() || "0", 10);
98056
99495
  if (count2 <= 1) {
98057
- await this.runTmux(["new-window", "-d", "-t", this.sessionName]);
99496
+ await this.runTmux(["new-window", "-d", "-t", this.sessionName, "-c", this.resolveDefaultWorkingDir()]);
98058
99497
  }
98059
99498
  await this.runAndRefresh(["kill-window", "-t", windowId], true);
98060
99499
  }
@@ -98878,7 +100317,10 @@ class SshExternalTmuxConnection {
98878
100317
  if (!this.connected) {
98879
100318
  return;
98880
100319
  }
98881
- const argv = name24 ? ["new-window", "-t", this.sessionName, "-n", name24] : ["new-window", "-t", this.sessionName];
100320
+ const argv = ["new-window", "-t", this.sessionName, "-c", this.resolveDefaultWorkingDir()];
100321
+ if (name24) {
100322
+ argv.push("-n", name24);
100323
+ }
98882
100324
  this.runAndRefresh(argv).catch((error51) => {
98883
100325
  this.callbacks.onError(error51);
98884
100326
  });
@@ -98907,6 +100349,20 @@ class SshExternalTmuxConnection {
98907
100349
  this.callbacks.onError(error51);
98908
100350
  });
98909
100351
  }
100352
+ updateDefaultWorkingDir(dir2) {
100353
+ if (this.device) {
100354
+ this.device = { ...this.device, defaultWorkingDir: dir2 };
100355
+ }
100356
+ if (this.connected) {
100357
+ this.runTmuxAllowFailure([
100358
+ "set-option",
100359
+ "-t",
100360
+ this.sessionName,
100361
+ "default-path",
100362
+ this.resolveDefaultWorkingDir()
100363
+ ]);
100364
+ }
100365
+ }
98910
100366
  setWindowStyle(style) {
98911
100367
  if (!this.connected) {
98912
100368
  return;
@@ -99042,12 +100498,15 @@ class SshExternalTmuxConnection {
99042
100498
  throw new Error(message);
99043
100499
  }
99044
100500
  }
100501
+ resolveDefaultWorkingDir() {
100502
+ return this.device?.defaultWorkingDir?.trim() || this.remoteHomeDir;
100503
+ }
99045
100504
  async ensureSession() {
99046
100505
  const exists3 = await this.runTmuxAllowFailure(["has-session", "-t", this.sessionName]);
99047
100506
  if (exists3.exitCode === 0) {
99048
100507
  return;
99049
100508
  }
99050
- await this.runTmux(["new-session", "-d", "-c", this.remoteHomeDir, "-s", this.sessionName]);
100509
+ await this.runTmux(["new-session", "-d", "-c", this.resolveDefaultWorkingDir(), "-s", this.sessionName]);
99051
100510
  }
99052
100511
  async configureSessionOptions() {
99053
100512
  await this.runTmuxAllowFailure([
@@ -99101,6 +100560,13 @@ class SshExternalTmuxConnection {
99101
100560
  "COLORTERM",
99102
100561
  "truecolor"
99103
100562
  ]);
100563
+ await this.runTmuxAllowFailure([
100564
+ "set-option",
100565
+ "-t",
100566
+ this.sessionName,
100567
+ "default-path",
100568
+ this.resolveDefaultWorkingDir()
100569
+ ]);
99104
100570
  await this.configureWindowStyle();
99105
100571
  }
99106
100572
  async configureWindowStyle(styleValue = config.tmuxWindowStyle) {
@@ -99215,6 +100681,9 @@ class SshExternalTmuxConnection {
99215
100681
  onPromptMarker: (paneId, marker24) => {
99216
100682
  this.callbacks.onPromptMarker?.(paneId, marker24);
99217
100683
  },
100684
+ onClipboardWrite: (paneId, text3) => {
100685
+ this.callbacks.onClipboardWrite?.(paneId, text3);
100686
+ },
99218
100687
  onStructureChanged: () => {
99219
100688
  this.requestSnapshot();
99220
100689
  },
@@ -99322,7 +100791,7 @@ class SshExternalTmuxConnection {
99322
100791
  async closeWindowInternal(windowId) {
99323
100792
  const count2 = Number.parseInt((await this.runTmux(["display-message", "-p", "-t", this.sessionName, "#{session_windows}"])).stdout.trim() || "0", 10);
99324
100793
  if (count2 <= 1) {
99325
- await this.runTmux(["new-window", "-d", "-t", this.sessionName]);
100794
+ await this.runTmux(["new-window", "-d", "-t", this.sessionName, "-c", this.resolveDefaultWorkingDir()]);
99326
100795
  }
99327
100796
  await this.runAndRefresh(["kill-window", "-t", windowId], true);
99328
100797
  }
@@ -99792,6 +101261,9 @@ class DeviceSessionRuntime {
99792
101261
  onPromptMarker: (paneId, marker24) => {
99793
101262
  this.broadcast((listener) => listener.onPromptMarker?.(paneId, marker24));
99794
101263
  },
101264
+ onClipboardWrite: (paneId, text3) => {
101265
+ this.broadcast((listener) => listener.onClipboardWrite?.(paneId, text3));
101266
+ },
99795
101267
  onSnapshot: (payload) => {
99796
101268
  this.broadcast((listener) => listener.onSnapshot?.(payload));
99797
101269
  },
@@ -99862,6 +101334,9 @@ class DeviceSessionRuntime {
99862
101334
  selectWindow(windowId) {
99863
101335
  this.connection.selectWindow(windowId);
99864
101336
  }
101337
+ updateDefaultWorkingDir(dir2) {
101338
+ this.connection.updateDefaultWorkingDir(dir2);
101339
+ }
99865
101340
  createWindow(name24) {
99866
101341
  this.connection.createWindow(name24);
99867
101342
  }
@@ -102432,6 +103907,10 @@ class PushSupervisor {
102432
103907
  }
102433
103908
  await this.upsert(deviceId);
102434
103909
  }
103910
+ updateDefaultWorkingDir(deviceId, dir2) {
103911
+ const entry = this.entries.get(deviceId);
103912
+ entry?.runtime?.updateDefaultWorkingDir(dir2);
103913
+ }
102435
103914
  remove(deviceId) {
102436
103915
  const entry = this.entries.get(deviceId);
102437
103916
  if (!entry) {
@@ -103566,6 +105045,49 @@ function typeFromPerms(perms) {
103566
105045
  return "other";
103567
105046
  }
103568
105047
  }
105048
+ function unescapeOctal(input) {
105049
+ if (!input.includes("\\"))
105050
+ return input;
105051
+ const result = [];
105052
+ let pendingBytes = [];
105053
+ const flushBytes = () => {
105054
+ if (pendingBytes.length === 0)
105055
+ return;
105056
+ result.push(new TextDecoder().decode(new Uint8Array(pendingBytes)));
105057
+ pendingBytes = [];
105058
+ };
105059
+ let i = 0;
105060
+ while (i < input.length) {
105061
+ if (input[i] !== "\\") {
105062
+ flushBytes();
105063
+ result.push(input[i]);
105064
+ i++;
105065
+ continue;
105066
+ }
105067
+ if (input[i + 1] === "\\") {
105068
+ flushBytes();
105069
+ result.push("\\");
105070
+ i += 2;
105071
+ continue;
105072
+ }
105073
+ if (i + 3 < input.length) {
105074
+ const digits = input.slice(i + 1, i + 4);
105075
+ if (/^[0-7]{3}$/.test(digits)) {
105076
+ const val = parseInt(digits, 8);
105077
+ if (val <= 255) {
105078
+ pendingBytes.push(val);
105079
+ i += 4;
105080
+ continue;
105081
+ }
105082
+ }
105083
+ }
105084
+ flushBytes();
105085
+ result.push("\\");
105086
+ i++;
105087
+ }
105088
+ flushBytes();
105089
+ return result.join("");
105090
+ }
103569
105091
  function parseListOnly(stdout) {
103570
105092
  const entries = [];
103571
105093
  for (const rawLine of stdout.split(`
@@ -103575,7 +105097,7 @@ function parseListOnly(stdout) {
103575
105097
  if (!m)
103576
105098
  continue;
103577
105099
  const type = typeFromPerms(m[1]);
103578
- let name24 = m[9];
105100
+ let name24 = unescapeOctal(m[9]);
103579
105101
  if (type === "symlink") {
103580
105102
  const arrow = name24.indexOf(" -> ");
103581
105103
  if (arrow >= 0)
@@ -103724,7 +105246,7 @@ function rsyncTargetArg(spec, remotePath) {
103724
105246
  return `${spec.targetPrefix}${quoteShellArg(remotePath)}`;
103725
105247
  }
103726
105248
  function rsyncListArgs(spec, remotePath) {
103727
- const args = ["--list-only"];
105249
+ const args = ["--list-only", "--8-bit-output"];
103728
105250
  if (spec.rsh)
103729
105251
  args.push("-e", spec.rsh);
103730
105252
  args.push(rsyncTargetArg(spec, remotePath));
@@ -105079,8 +106601,8 @@ function getBaseVersion() {
105079
106601
  if (cachedBase !== undefined)
105080
106602
  return cachedBase;
105081
106603
  let base = null;
105082
- if ("0.12.4") {
105083
- base = "0.12.4";
106604
+ if ("0.14.0") {
106605
+ base = "0.14.0";
105084
106606
  }
105085
106607
  if (!base && config.isProd) {
105086
106608
  base = readInstallMeta()?.cliVersion ?? null;
@@ -106878,6 +108400,18 @@ function normalizeSiteSettingsInput(body) {
106878
108400
  }
106879
108401
  updates.enableTelegramNotificationPush = body.enableTelegramNotificationPush;
106880
108402
  }
108403
+ if (body.enableWeixinBellPush !== undefined) {
108404
+ if (typeof body.enableWeixinBellPush !== "boolean") {
108405
+ throw new Error(t2("apiError.invalidRequest"));
108406
+ }
108407
+ updates.enableWeixinBellPush = body.enableWeixinBellPush;
108408
+ }
108409
+ if (body.enableWeixinNotificationPush !== undefined) {
108410
+ if (typeof body.enableWeixinNotificationPush !== "boolean") {
108411
+ throw new Error(t2("apiError.invalidRequest"));
108412
+ }
108413
+ updates.enableWeixinNotificationPush = body.enableWeixinNotificationPush;
108414
+ }
106881
108415
  if (body.sshReconnectMaxRetries !== undefined) {
106882
108416
  const value = Math.floor(Number(body.sshReconnectMaxRetries));
106883
108417
  if (Number.isNaN(value) || value < 0 || value > 20) {
@@ -106964,6 +108498,39 @@ function handleApiRequest(req, _server) {
106964
108498
  if (path.match(/^\/api\/settings\/telegram\/bots\/[^/]+\/chats\/[^/]+$/) && req.method === "DELETE") {
106965
108499
  return handleDeleteTelegramChat(path.split("/")[5], decodeURIComponent(path.split("/")[7]));
106966
108500
  }
108501
+ if (path === "/api/settings/weixin/accounts" && req.method === "GET") {
108502
+ return handleGetWeixinAccounts();
108503
+ }
108504
+ if (path === "/api/settings/weixin/accounts" && req.method === "POST") {
108505
+ return handleCreateWeixinAccount(req);
108506
+ }
108507
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+$/) && req.method === "PATCH") {
108508
+ return handleUpdateWeixinAccount(req, path.split("/")[5]);
108509
+ }
108510
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+$/) && req.method === "DELETE") {
108511
+ return handleDeleteWeixinAccount(path.split("/")[5]);
108512
+ }
108513
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+\/login\/start$/) && req.method === "POST") {
108514
+ return handleStartWeixinLogin(path.split("/")[5]);
108515
+ }
108516
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+\/login\/status$/) && req.method === "GET") {
108517
+ return handleGetWeixinLoginStatus(path.split("/")[5]);
108518
+ }
108519
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+\/test$/) && req.method === "POST") {
108520
+ return handleTestWeixinAccount(path.split("/")[5]);
108521
+ }
108522
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+\/users$/) && req.method === "GET") {
108523
+ return handleListWeixinUsers(path.split("/")[5]);
108524
+ }
108525
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+\/users\/[^/]+\/approve$/) && req.method === "POST") {
108526
+ return handleApproveWeixinUser(path.split("/")[5], decodeURIComponent(path.split("/")[7]));
108527
+ }
108528
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+\/users\/[^/]+\/test$/) && req.method === "POST") {
108529
+ return handleTestWeixinUser(path.split("/")[5], decodeURIComponent(path.split("/")[7]));
108530
+ }
108531
+ if (path.match(/^\/api\/settings\/weixin\/accounts\/[^/]+\/users\/[^/]+$/) && req.method === "DELETE") {
108532
+ return handleDeleteWeixinUser(path.split("/")[5], decodeURIComponent(path.split("/")[7]));
108533
+ }
106967
108534
  if (path.startsWith("/api/llm/")) {
106968
108535
  const llmResponse = handleLlmApiRequest(req, path);
106969
108536
  if (llmResponse) {
@@ -107054,6 +108621,7 @@ async function handleCreateDevice(req) {
107054
108621
  username: body.username,
107055
108622
  sshConfigRef: body.sshConfigRef,
107056
108623
  session: body.session ?? "tmex",
108624
+ defaultWorkingDir: body.defaultWorkingDir?.trim() || undefined,
107057
108625
  authMode: body.authMode,
107058
108626
  passwordEnc: body.password ? await encrypt(body.password) : undefined,
107059
108627
  privateKeyEnc: body.privateKey ? await encrypt(body.privateKey) : undefined,
@@ -107085,6 +108653,8 @@ async function handleUpdateDevice(req, id) {
107085
108653
  updates.sshConfigRef = body.sshConfigRef;
107086
108654
  if (body.session !== undefined)
107087
108655
  updates.session = body.session;
108656
+ if (body.defaultWorkingDir !== undefined)
108657
+ updates.defaultWorkingDir = body.defaultWorkingDir.trim() || undefined;
107088
108658
  if (body.authMode !== undefined)
107089
108659
  updates.authMode = body.authMode;
107090
108660
  if (body.password !== undefined)
@@ -107097,6 +108667,8 @@ async function handleUpdateDevice(req, id) {
107097
108667
  updateDevice(id, updates);
107098
108668
  if (shouldReconnectPushSupervisor(existing, updates)) {
107099
108669
  await pushSupervisor.reconnect(id);
108670
+ } else if (updates.defaultWorkingDir !== undefined && updates.defaultWorkingDir !== existing.defaultWorkingDir) {
108671
+ pushSupervisor.updateDefaultWorkingDir(id, updates.defaultWorkingDir);
107100
108672
  }
107101
108673
  const device = getDeviceById(id);
107102
108674
  return json9({ device });
@@ -107266,6 +108838,152 @@ async function handleTestTelegramChat(botId, chatId) {
107266
108838
  }));
107267
108839
  return json9({ success: true });
107268
108840
  }
108841
+ async function handleGetWeixinAccounts() {
108842
+ const accounts = getWeixinAccountsWithStats();
108843
+ return json9({ accounts });
108844
+ }
108845
+ async function handleCreateWeixinAccount(req) {
108846
+ const body = await req.json();
108847
+ if (!body.name?.trim()) {
108848
+ return json9({ error: t2("weixin.accountNameRequired") }, 400);
108849
+ }
108850
+ const now2 = new Date().toISOString();
108851
+ const id = v4_default();
108852
+ createWeixinAccount({
108853
+ id,
108854
+ name: body.name.trim(),
108855
+ enabled: body.enabled ?? true,
108856
+ allowAuthRequests: body.allowAuthRequests ?? true,
108857
+ loggedIn: false,
108858
+ weixinUin: null,
108859
+ botTokenEnc: null,
108860
+ baseUrl: null,
108861
+ syncBuf: null,
108862
+ createdAt: now2,
108863
+ updatedAt: now2
108864
+ });
108865
+ return json9({ success: true, accountId: id }, 201);
108866
+ }
108867
+ async function handleUpdateWeixinAccount(req, accountId) {
108868
+ const existing = getWeixinAccountById(accountId);
108869
+ if (!existing) {
108870
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108871
+ }
108872
+ const body = await req.json();
108873
+ const updates = {};
108874
+ if (body.name !== undefined) {
108875
+ const value = body.name.trim();
108876
+ if (!value) {
108877
+ return json9({ error: t2("weixin.accountNameRequired") }, 400);
108878
+ }
108879
+ updates.name = value;
108880
+ }
108881
+ if (body.enabled !== undefined) {
108882
+ updates.enabled = body.enabled;
108883
+ }
108884
+ if (body.allowAuthRequests !== undefined) {
108885
+ updates.allowAuthRequests = body.allowAuthRequests;
108886
+ }
108887
+ updateWeixinAccount(accountId, updates);
108888
+ await weixinService.refresh();
108889
+ return json9({ success: true });
108890
+ }
108891
+ async function handleDeleteWeixinAccount(accountId) {
108892
+ const existing = getWeixinAccountById(accountId);
108893
+ if (!existing) {
108894
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108895
+ }
108896
+ deleteWeixinAccount(accountId);
108897
+ await weixinService.refresh();
108898
+ return json9({ success: true });
108899
+ }
108900
+ async function handleStartWeixinLogin(accountId) {
108901
+ const existing = getWeixinAccountById(accountId);
108902
+ if (!existing) {
108903
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108904
+ }
108905
+ try {
108906
+ const result = await weixinService.startLogin(accountId);
108907
+ return json9(result);
108908
+ } catch (err) {
108909
+ return json9({ error: err instanceof Error ? err.message : t2("weixin.loginFailed") }, 502);
108910
+ }
108911
+ }
108912
+ async function handleGetWeixinLoginStatus(accountId) {
108913
+ const existing = getWeixinAccountById(accountId);
108914
+ if (!existing) {
108915
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108916
+ }
108917
+ return json9(weixinService.getLoginStatus(accountId));
108918
+ }
108919
+ async function handleListWeixinUsers(accountId) {
108920
+ const existing = getWeixinAccountById(accountId);
108921
+ if (!existing) {
108922
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108923
+ }
108924
+ const users = listWeixinUsersByAccount(accountId);
108925
+ return json9({ users });
108926
+ }
108927
+ async function handleApproveWeixinUser(accountId, userId) {
108928
+ const existing = getWeixinAccountById(accountId);
108929
+ if (!existing) {
108930
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108931
+ }
108932
+ const user = approveWeixinUser(accountId, userId);
108933
+ if (!user) {
108934
+ return json9({ error: t2("weixin.userNotFound") }, 404);
108935
+ }
108936
+ const settings = getSiteSettings();
108937
+ try {
108938
+ await weixinService.sendTestMessage(accountId, userId, t2("weixin.approveMessageTemplate", {
108939
+ accountName: existing.name,
108940
+ time: new Date().toLocaleString(toBCP47(settings.language))
108941
+ }));
108942
+ } catch (err) {
108943
+ console.error("[weixin] approve ack failed:", err);
108944
+ }
108945
+ return json9({ user });
108946
+ }
108947
+ async function handleTestWeixinUser(accountId, userId) {
108948
+ const existing = getWeixinAccountById(accountId);
108949
+ if (!existing) {
108950
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108951
+ }
108952
+ const settings = getSiteSettings();
108953
+ try {
108954
+ await weixinService.sendTestMessage(accountId, userId, t2("weixin.testMessageTemplate", {
108955
+ siteName: settings.siteName,
108956
+ time: new Date().toLocaleString(toBCP47(settings.language))
108957
+ }));
108958
+ } catch (err) {
108959
+ return json9({ error: err instanceof Error ? err.message : t2("weixin.testMessageFailed") }, 400);
108960
+ }
108961
+ return json9({ success: true });
108962
+ }
108963
+ async function handleTestWeixinAccount(accountId) {
108964
+ const existing = getWeixinAccountById(accountId);
108965
+ if (!existing) {
108966
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108967
+ }
108968
+ const settings = getSiteSettings();
108969
+ try {
108970
+ await weixinService.sendTestMessageToBoundUser(accountId, t2("weixin.testMessageTemplate", {
108971
+ siteName: settings.siteName,
108972
+ time: new Date().toLocaleString(toBCP47(settings.language))
108973
+ }));
108974
+ } catch (err) {
108975
+ return json9({ error: err instanceof Error ? err.message : t2("weixin.testMessageFailed") }, 400);
108976
+ }
108977
+ return json9({ success: true });
108978
+ }
108979
+ async function handleDeleteWeixinUser(accountId, userId) {
108980
+ const existing = getWeixinAccountById(accountId);
108981
+ if (!existing) {
108982
+ return json9({ error: t2("weixin.accountNotFound") }, 404);
108983
+ }
108984
+ deleteWeixinUser(accountId, userId);
108985
+ return json9({ success: true });
108986
+ }
107269
108987
  async function handleGetWebhooks() {
107270
108988
  const webhooks = getAllWebhookEndpoints();
107271
108989
  return json9({ webhooks });
@@ -108080,6 +109798,9 @@ class WebSocketServer {
108080
109798
  onTerminalHistory: (paneId, data, alternateScreen) => {
108081
109799
  this.broadcastTerminalHistory(deviceId, paneId, data, alternateScreen);
108082
109800
  },
109801
+ onClipboardWrite: (paneId, text3) => {
109802
+ this.broadcastClipboardWrite(deviceId, paneId, text3);
109803
+ },
108083
109804
  onSnapshot: (payload) => {
108084
109805
  this.broadcastStateSnapshot(deviceId, payload);
108085
109806
  },
@@ -108207,6 +109928,10 @@ class WebSocketServer {
108207
109928
  this.connections.delete(deviceId);
108208
109929
  }
108209
109930
  }
109931
+ updateDefaultWorkingDir(deviceId, dir2) {
109932
+ const entry = this.connections.get(deviceId);
109933
+ entry?.runtime.updateDefaultWorkingDir(dir2);
109934
+ }
108210
109935
  closeAll() {
108211
109936
  for (const [deviceId, entry] of this.connections) {
108212
109937
  this.releaseConnectionEntry(deviceId, entry);
@@ -108764,6 +110489,22 @@ class WebSocketServer {
108764
110489
  this.sendChunked(client, exports_ws_borsh.KIND_TERM_OUTPUT, payloadBytes);
108765
110490
  }
108766
110491
  }
110492
+ broadcastClipboardWrite(deviceId, paneId, text3) {
110493
+ const entry = this.connections.get(deviceId);
110494
+ if (!entry)
110495
+ return;
110496
+ const payloadBytes = exports_ws_borsh.encodePayload(exports_ws_borsh.schema.ClipboardWriteSchema, {
110497
+ deviceId,
110498
+ paneId,
110499
+ text: text3
110500
+ });
110501
+ for (const client of entry.clients) {
110502
+ if (client.data.borshState.selectedPanes[deviceId] !== paneId) {
110503
+ continue;
110504
+ }
110505
+ this.sendEnvelope(client, exports_ws_borsh.KIND_CLIPBOARD_WRITE, payloadBytes);
110506
+ }
110507
+ }
108767
110508
  broadcastTerminalHistory(deviceId, paneId, data, alternateScreen) {
108768
110509
  const entry = this.connections.get(deviceId);
108769
110510
  if (!entry)
@@ -108899,12 +110640,14 @@ async function createGatewayRuntime(options = {}) {
108899
110640
  wsServer.broadcastDeviceError(deviceId, payload);
108900
110641
  });
108901
110642
  await telegramService.refresh();
110643
+ await weixinService.refresh();
108902
110644
  await pushSupervisor.start();
108903
110645
  await agentSupervisor.start();
108904
110646
  await watchService.start();
108905
110647
  try {
108906
110648
  const settings = getSiteSettings();
108907
110649
  await telegramService.sendGatewayOnlineMessage(settings.siteName);
110650
+ await weixinService.sendGatewayOnlineMessage(settings.siteName);
108908
110651
  } catch (err) {
108909
110652
  console.error("[gateway] failed to push startup message:", err);
108910
110653
  }
@@ -108949,6 +110692,7 @@ async function createGatewayRuntime(options = {}) {
108949
110692
  await pushSupervisor.stopAll();
108950
110693
  await tmuxRuntimeRegistry.shutdownAll();
108951
110694
  await telegramService.stopAll();
110695
+ await weixinService.stopAll();
108952
110696
  }
108953
110697
  };
108954
110698
  }
@@ -108959,8 +110703,8 @@ var MESSAGES = {
108959
110703
  "cli.help": `tmex CLI
108960
110704
 
108961
110705
  Usage:
108962
- tmex init [--no-interactive --install-dir <path> --host <host> --port <port> --db-path <path> --autostart <true|false> --bun-path <path>]
108963
- tmex doctor [--install-dir <path>] [--json] [--bun-path <path>]
110706
+ tmex init [--no-interactive --install-dir <path> --host <host> --port <port> --db-path <path> --autostart <true|false> --bun-path <path> --install-deps --skip-dep-check]
110707
+ tmex doctor [--install-dir <path>] [--json] [--bun-path <path>] [--fix]
108964
110708
  tmex upgrade [--version <version>] [--install-dir <path>] [--bun-path <path>]
108965
110709
  tmex uninstall [--install-dir <path>] [--yes] [--purge]
108966
110710
 
@@ -109004,6 +110748,7 @@ Global flags:
109004
110748
  "init.prompt.serviceName": "Service name (service-name)",
109005
110749
  "init.prompt.dirExistsConfirm": "Directory {{installDir}} already exists. Continue (will not delete existing config/db)?",
109006
110750
  "init.error.installDirNotEmpty": "Install directory is not empty: {{installDir}}. Use --force to overwrite.",
110751
+ "init.error.noServiceManager": "No supported service manager found (platform: {{platform}}). tmex requires systemd (Linux) or launchd (macOS).",
109007
110752
  "init.warning.noServiceManager": "Service manager is not supported on platform {{platform}}. Files are deployed but autostart is not configured.",
109008
110753
  "init.done": "Initialization completed.",
109009
110754
  "init.summary.installDir": "Install dir",
@@ -109017,8 +110762,12 @@ Global flags:
109017
110762
  "doctor.platform.unsupported": "Platform {{platform}} is not officially supported (only macOS and common Linux distros are guaranteed).",
109018
110763
  "doctor.bun.ok": "Bun installed: {{version}}",
109019
110764
  "doctor.bun.fail": "Bun check failed: {{reason}}",
109020
- "doctor.tmux.ok": "tmux installed",
109021
- "doctor.tmux.fail": "tmux not found (tmex requires tmux).",
110765
+ "doctor.tmux.ok": "tmux installed: {{version}}",
110766
+ "doctor.tmux.fail": "tmux not found (tmex requires tmux >= 3.0).",
110767
+ "doctor.tmux.versionLow": "tmux version too low: {{version}} (requires >= 3.0)",
110768
+ "doctor.fix.header": "Attempting to fix issues...",
110769
+ "doctor.fix.skip": "Skipping unfixable item: {{id}}",
110770
+ "doctor.fix.hint": 'Run "tmex doctor --fix" to attempt automatic installation.',
109022
110771
  "doctor.ssh.ok": "ssh installed",
109023
110772
  "doctor.ssh.missing": "ssh not found; SSH devices will not work.",
109024
110773
  "doctor.installDir.exists": "Install directory exists: {{installDir}}",
@@ -109049,6 +110798,19 @@ Global flags:
109049
110798
  "uninstall.done": "Uninstall completed.",
109050
110799
  "uninstall.summary.installDir": "Install dir",
109051
110800
  "uninstall.summary.serviceName": "Service name",
110801
+ "tmux.notFound": "tmux not found. tmex requires tmux >= 3.0 to operate.",
110802
+ "tmux.versionTooLow": "tmux version too low: current {{version}}, required >= 3.0",
110803
+ "deps.install.confirm": "Install {{dep}} now?",
110804
+ "deps.install.running": "Installing {{dep}}...",
110805
+ "deps.install.success": "{{dep}} installed successfully.",
110806
+ "deps.install.failed": "Failed to install {{dep}}.",
110807
+ "deps.install.manual": "Please install manually and retry.",
110808
+ "deps.install.sudoRequired": "This operation requires sudo.",
110809
+ "deps.install.sudoUnavailable": "sudo is not available. Please run as root or install sudo.",
110810
+ "deps.install.nonInteractive": "Missing dependency: {{dep}}. Use --install-deps to install automatically.",
110811
+ "deps.install.hint": "Suggested install command: {{command}}",
110812
+ "deps.install.brewMissing": "Homebrew not found. Install Homebrew first: https://brew.sh",
110813
+ "deps.install.unknownDistro": "Unable to detect Linux distribution. Please install {{dep}} manually.",
109052
110814
  "runtime.restartRequested": "Restart requested; exiting for service manager restart.",
109053
110815
  "runtime.started": "Service started on {{url}}",
109054
110816
  "runtime.frontendMissing": "Frontend assets not found.",
@@ -109060,8 +110822,8 @@ Global flags:
109060
110822
  "cli.help": `tmex CLI
109061
110823
 
109062
110824
  \u7528\u6CD5\uFF1A
109063
- tmex init [--no-interactive --install-dir <path> --host <host> --port <port> --db-path <path> --autostart <true|false> --bun-path <path>]
109064
- tmex doctor [--install-dir <path>] [--json] [--bun-path <path>]
110825
+ tmex init [--no-interactive --install-dir <path> --host <host> --port <port> --db-path <path> --autostart <true|false> --bun-path <path> --install-deps --skip-dep-check]
110826
+ tmex doctor [--install-dir <path>] [--json] [--bun-path <path>] [--fix]
109065
110827
  tmex upgrade [--version <version>] [--install-dir <path>] [--bun-path <path>]
109066
110828
  tmex uninstall [--install-dir <path>] [--yes] [--purge]
109067
110829
 
@@ -109105,6 +110867,7 @@ Global flags:
109105
110867
  "init.prompt.serviceName": "\u670D\u52A1\u540D\u79F0\uFF08service-name\uFF09",
109106
110868
  "init.prompt.dirExistsConfirm": "\u76EE\u5F55 {{installDir}} \u5DF2\u5B58\u5728\uFF0C\u662F\u5426\u7EE7\u7EED\uFF08\u4E0D\u4F1A\u5220\u9664\u73B0\u6709\u914D\u7F6E\u4E0E\u6570\u636E\u5E93\uFF09\uFF1F",
109107
110869
  "init.error.installDirNotEmpty": "\u5B89\u88C5\u76EE\u5F55\u5DF2\u5B58\u5728\u4E14\u975E\u7A7A\uFF1A{{installDir}}\u3002\u5982\u9700\u8986\u76D6\u8BF7\u52A0 --force",
110870
+ "init.error.noServiceManager": "\u672A\u68C0\u6D4B\u5230\u53EF\u7528\u7684\u670D\u52A1\u7BA1\u7406\u5668\uFF08\u5E73\u53F0\uFF1A{{platform}}\uFF09\u3002tmex \u9700\u8981 systemd\uFF08Linux\uFF09\u6216 launchd\uFF08macOS\uFF09\u3002",
109108
110871
  "init.warning.noServiceManager": "\u5F53\u524D\u5E73\u53F0 {{platform}} \u672A\u5B9E\u73B0\u81EA\u52A8\u670D\u52A1\u5B89\u88C5\uFF0C\u5DF2\u5B8C\u6210\u6587\u4EF6\u90E8\u7F72\u3002",
109109
110872
  "init.done": "\u521D\u59CB\u5316\u5B8C\u6210\u3002",
109110
110873
  "init.summary.installDir": "\u5B89\u88C5\u76EE\u5F55",
@@ -109118,8 +110881,12 @@ Global flags:
109118
110881
  "doctor.platform.unsupported": "\u5F53\u524D\u5E73\u53F0 {{platform}} \u975E\u5B98\u65B9\u652F\u6301\u8303\u56F4\uFF08\u4EC5\u4FDD\u8BC1 macOS \u4E0E\u5E38\u89C1 Linux \u53D1\u884C\u7248\uFF09\u3002",
109119
110882
  "doctor.bun.ok": "Bun \u5DF2\u5B89\u88C5\uFF1A{{version}}",
109120
110883
  "doctor.bun.fail": "Bun \u68C0\u67E5\u5931\u8D25\uFF1A{{reason}}",
109121
- "doctor.tmux.ok": "tmux \u5DF2\u5B89\u88C5",
109122
- "doctor.tmux.fail": "\u672A\u68C0\u6D4B\u5230 tmux\uFF08tmex \u9700\u8981 tmux \u624D\u80FD\u5DE5\u4F5C\uFF09\u3002",
110884
+ "doctor.tmux.ok": "tmux \u5DF2\u5B89\u88C5\uFF1A{{version}}",
110885
+ "doctor.tmux.fail": "\u672A\u68C0\u6D4B\u5230 tmux\uFF08tmex \u9700\u8981 tmux >= 3.0 \u624D\u80FD\u5DE5\u4F5C\uFF09\u3002",
110886
+ "doctor.tmux.versionLow": "tmux \u7248\u672C\u8FC7\u4F4E\uFF1A{{version}}\uFF08\u8981\u6C42 >= 3.0\uFF09",
110887
+ "doctor.fix.header": "\u6B63\u5728\u5C1D\u8BD5\u4FEE\u590D\u95EE\u9898...",
110888
+ "doctor.fix.skip": "\u8DF3\u8FC7\u65E0\u6CD5\u81EA\u52A8\u4FEE\u590D\u7684\u9879\u76EE\uFF1A{{id}}",
110889
+ "doctor.fix.hint": '\u8FD0\u884C "tmex doctor --fix" \u5C1D\u8BD5\u81EA\u52A8\u5B89\u88C5\u7F3A\u5931\u7684\u4F9D\u8D56\u3002',
109123
110890
  "doctor.ssh.ok": "ssh \u5DF2\u5B89\u88C5",
109124
110891
  "doctor.ssh.missing": "\u672A\u68C0\u6D4B\u5230 ssh\uFF0C\u8FDC\u7A0B\u8BBE\u5907\u5C06\u4E0D\u53EF\u7528\u3002",
109125
110892
  "doctor.installDir.exists": "\u5B89\u88C5\u76EE\u5F55\u5B58\u5728\uFF1A{{installDir}}",
@@ -109150,6 +110917,19 @@ Global flags:
109150
110917
  "uninstall.done": "\u5378\u8F7D\u5B8C\u6210\u3002",
109151
110918
  "uninstall.summary.installDir": "\u5B89\u88C5\u76EE\u5F55",
109152
110919
  "uninstall.summary.serviceName": "\u670D\u52A1\u540D\u79F0",
110920
+ "tmux.notFound": "\u672A\u68C0\u6D4B\u5230 tmux\u3002tmex \u9700\u8981 tmux >= 3.0 \u624D\u80FD\u5DE5\u4F5C\u3002",
110921
+ "tmux.versionTooLow": "tmux \u7248\u672C\u8FC7\u4F4E\uFF1A\u5F53\u524D {{version}}\uFF0C\u8981\u6C42 >= 3.0",
110922
+ "deps.install.confirm": "\u662F\u5426\u73B0\u5728\u5B89\u88C5 {{dep}}\uFF1F",
110923
+ "deps.install.running": "\u6B63\u5728\u5B89\u88C5 {{dep}}...",
110924
+ "deps.install.success": "{{dep}} \u5B89\u88C5\u6210\u529F\u3002",
110925
+ "deps.install.failed": "\u5B89\u88C5 {{dep}} \u5931\u8D25\u3002",
110926
+ "deps.install.manual": "\u8BF7\u624B\u52A8\u5B89\u88C5\u540E\u91CD\u8BD5\u3002",
110927
+ "deps.install.sudoRequired": "\u6B64\u64CD\u4F5C\u9700\u8981 sudo \u6743\u9650\u3002",
110928
+ "deps.install.sudoUnavailable": "sudo \u4E0D\u53EF\u7528\uFF0C\u8BF7\u4EE5 root \u8EAB\u4EFD\u6267\u884C\u6216\u5B89\u88C5 sudo\u3002",
110929
+ "deps.install.nonInteractive": "\u7F3A\u5C11\u4F9D\u8D56\uFF1A{{dep}}\u3002\u4F7F\u7528 --install-deps \u81EA\u52A8\u5B89\u88C5\u3002",
110930
+ "deps.install.hint": "\u5EFA\u8BAE\u5B89\u88C5\u547D\u4EE4\uFF1A{{command}}",
110931
+ "deps.install.brewMissing": "\u672A\u68C0\u6D4B\u5230 Homebrew\uFF0C\u8BF7\u5148\u5B89\u88C5 Homebrew\uFF1Ahttps://brew.sh",
110932
+ "deps.install.unknownDistro": "\u65E0\u6CD5\u68C0\u6D4B Linux \u53D1\u884C\u7248\uFF0C\u8BF7\u624B\u52A8\u5B89\u88C5 {{dep}}\u3002",
109153
110933
  "runtime.restartRequested": "\u6536\u5230\u91CD\u542F\u8BF7\u6C42\uFF0C\u9000\u51FA\u5E76\u7B49\u5F85\u670D\u52A1\u7BA1\u7406\u5668\u62C9\u8D77\u3002",
109154
110934
  "runtime.started": "\u670D\u52A1\u5DF2\u542F\u52A8\uFF1A{{url}}",
109155
110935
  "runtime.frontendMissing": "\u672A\u627E\u5230\u524D\u7AEF\u9759\u6001\u8D44\u6E90\u3002",