tmex-cli 0.16.5 → 0.17.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 (84) hide show
  1. package/CHANGELOG.md +40 -4
  2. package/dist/runtime/server.js +702 -337
  3. package/package.json +2 -1
  4. package/resources/fe-dist/assets/DevicePage-B-4Wp5gx.js +24 -0
  5. package/resources/fe-dist/assets/{DevicesPage-XMtf7VIy.js → DevicesPage-D3GQd_MI.js} +1 -1
  6. package/resources/fe-dist/assets/{FilePage-C1_JLsEc.js → FilePage-C45pl7_F.js} +2 -2
  7. package/resources/fe-dist/assets/SettingsPage-DEkQrJzi.js +39 -0
  8. package/resources/fe-dist/assets/{agent-tab-BgvIAv35.js → agent-tab-BnKrqJP2.js} +2 -2
  9. package/resources/fe-dist/assets/api-5IhJ49aS.js +5 -0
  10. package/resources/fe-dist/assets/{arc-BX-Po9p7.js → arc-DBxiTdY2.js} +1 -1
  11. package/resources/fe-dist/assets/{architectureDiagram-3BPJPVTR-BneOtjGd.js → architectureDiagram-3BPJPVTR-Dcd70ntw.js} +1 -1
  12. package/resources/fe-dist/assets/{blockDiagram-GPEHLZMM-Dcf0shYG.js → blockDiagram-GPEHLZMM-LsFpH20h.js} +1 -1
  13. package/resources/fe-dist/assets/{c4Diagram-AAUBKEIU-Cwhji_wO.js → c4Diagram-AAUBKEIU-BswGzO6B.js} +1 -1
  14. package/resources/fe-dist/assets/{card-IkOsEv2J.js → card-Bav-X9c4.js} +1 -1
  15. package/resources/fe-dist/assets/channel-hAJp4tg-.js +1 -0
  16. package/resources/fe-dist/assets/{chunk-2J33WTMH-IFwQoyns.js → chunk-2J33WTMH-C0yl1OR1.js} +1 -1
  17. package/resources/fe-dist/assets/{chunk-4BX2VUAB-BZhwH7K2.js → chunk-4BX2VUAB-Dwo9vDDk.js} +1 -1
  18. package/resources/fe-dist/assets/{chunk-55IACEB6-pYNojBsx.js → chunk-55IACEB6-Dz9e7vav.js} +1 -1
  19. package/resources/fe-dist/assets/{chunk-727SXJPM-BwBn-X-G.js → chunk-727SXJPM-ZkqwcZ6G.js} +1 -1
  20. package/resources/fe-dist/assets/{chunk-AQP2D5EJ-Cv5U78aO.js → chunk-AQP2D5EJ-79MMuY3h.js} +1 -1
  21. package/resources/fe-dist/assets/{chunk-FMBD7UC4-DnaDXP2z.js → chunk-FMBD7UC4-Vw8e6XCy.js} +1 -1
  22. package/resources/fe-dist/assets/{chunk-ND2GUHAM-BCRcaTWl.js → chunk-ND2GUHAM-um7O2-xi.js} +1 -1
  23. package/resources/fe-dist/assets/{chunk-QZHKN3VN-1sPBtZCH.js → chunk-QZHKN3VN-D8FuV91c.js} +1 -1
  24. package/resources/fe-dist/assets/classDiagram-4FO5ZUOK-SSiS3KfC.js +1 -0
  25. package/resources/fe-dist/assets/classDiagram-v2-Q7XG4LA2-SSiS3KfC.js +1 -0
  26. package/resources/fe-dist/assets/{copy-CJIriuQt.js → copy-Cawnv2-o.js} +1 -1
  27. package/resources/fe-dist/assets/{cose-bilkent-S5V4N54A-CY6ERT7s.js → cose-bilkent-S5V4N54A-gidx-z0G.js} +1 -1
  28. package/resources/fe-dist/assets/{dagre-BM42HDAG-c_oJjVCK.js → dagre-BM42HDAG-C54CUHrk.js} +1 -1
  29. package/resources/fe-dist/assets/{diagram-2AECGRRQ-g-DJAnzv.js → diagram-2AECGRRQ-DFGI1MJu.js} +1 -1
  30. package/resources/fe-dist/assets/{diagram-5GNKFQAL-0DikWL30.js → diagram-5GNKFQAL-D7POJY8A.js} +1 -1
  31. package/resources/fe-dist/assets/{diagram-KO2AKTUF-wWVJH7kq.js → diagram-KO2AKTUF-CvV1GMLL.js} +1 -1
  32. package/resources/fe-dist/assets/{diagram-LMA3HP47-UwQ9OpsG.js → diagram-LMA3HP47-BbfJVUKI.js} +1 -1
  33. package/resources/fe-dist/assets/{diagram-OG6HWLK6-ZFNNtnbo.js → diagram-OG6HWLK6-CV6Q6IxH.js} +1 -1
  34. package/resources/fe-dist/assets/download-ZSRF6UU6.js +6 -0
  35. package/resources/fe-dist/assets/en_US-xzwBD2oK.js +1 -0
  36. package/resources/fe-dist/assets/{erDiagram-TEJ5UH35-D3na7WWE.js → erDiagram-TEJ5UH35-BPgvqV_L.js} +1 -1
  37. package/resources/fe-dist/assets/{files-tab-1FVCHULV.js → files-tab-BQhhr5Wi.js} +15 -15
  38. package/resources/fe-dist/assets/{flowDiagram-I6XJVG4X-Dsbf9z99.js → flowDiagram-I6XJVG4X-CKXjL0zg.js} +1 -1
  39. package/resources/fe-dist/assets/{ganttDiagram-6RSMTGT7-CF3CKflH.js → ganttDiagram-6RSMTGT7-D58QnW85.js} +1 -1
  40. package/resources/fe-dist/assets/{gitGraphDiagram-PVQCEYII-CRpX3WPf.js → gitGraphDiagram-PVQCEYII-BwB9zJYk.js} +1 -1
  41. package/resources/fe-dist/assets/{index-U4iZ6Kxr.js → index-CxD8veUJ.js} +16 -16
  42. package/resources/fe-dist/assets/{index-C-RSAqjj.js → index-DDvqV24J.js} +1 -1
  43. package/resources/fe-dist/assets/{infoDiagram-5YYISTIA-DMCNm7PZ.js → infoDiagram-5YYISTIA-DGZZJ51K.js} +1 -1
  44. package/resources/fe-dist/assets/{ishikawaDiagram-YF4QCWOH-CMe-J41c.js → ishikawaDiagram-YF4QCWOH-DKEV0i-O.js} +1 -1
  45. package/resources/fe-dist/assets/ja_JP-Dd1FrxOk.js +1 -0
  46. package/resources/fe-dist/assets/{journeyDiagram-JHISSGLW-CxFZDT81.js → journeyDiagram-JHISSGLW-Dvt5MIiS.js} +1 -1
  47. package/resources/fe-dist/assets/{kanban-definition-UN3LZRKU-DwBxD_Gg.js → kanban-definition-UN3LZRKU-BrLCK6I8.js} +1 -1
  48. package/resources/fe-dist/assets/{linear-CBM17AV1.js → linear-LNJ3BF_d.js} +1 -1
  49. package/resources/fe-dist/assets/{markdown-preview-CVeuDkuY.js → markdown-preview-Cqdqjc1H.js} +3 -3
  50. package/resources/fe-dist/assets/{mermaid.core-CvSv2dqw.js → mermaid.core-Cy4PFTl-.js} +5 -5
  51. package/resources/fe-dist/assets/{mindmap-definition-RKZ34NQL-DUu53RHy.js → mindmap-definition-RKZ34NQL-JOcL1ZTP.js} +1 -1
  52. package/resources/fe-dist/assets/{pieDiagram-4H26LBE5-CAbw9YBY.js → pieDiagram-4H26LBE5-_1o-TTd6.js} +1 -1
  53. package/resources/fe-dist/assets/{quadrantDiagram-W4KKPZXB-NmJUURAz.js → quadrantDiagram-W4KKPZXB-7Ggy2jhO.js} +1 -1
  54. package/resources/fe-dist/assets/{requirementDiagram-4Y6WPE33-B45iu7jV.js → requirementDiagram-4Y6WPE33-CH_udJzB.js} +1 -1
  55. package/resources/fe-dist/assets/{sankeyDiagram-5OEKKPKP-af9JvT_p.js → sankeyDiagram-5OEKKPKP-DO9ymHJb.js} +1 -1
  56. package/resources/fe-dist/assets/{send-e_FTy5nC.js → send-D0SbMcl5.js} +1 -1
  57. package/resources/fe-dist/assets/{sequenceDiagram-3UESZ5HK-CSpSOeHp.js → sequenceDiagram-3UESZ5HK-BfxbUo5S.js} +1 -1
  58. package/resources/fe-dist/assets/{stateDiagram-AJRCARHV-vM6MSoBd.js → stateDiagram-AJRCARHV-ChJ5OE86.js} +1 -1
  59. package/resources/fe-dist/assets/stateDiagram-v2-BHNVJYJU-yafZZKAc.js +1 -0
  60. package/resources/fe-dist/assets/terminal-settings-panel-CFkXHe7W.js +25 -0
  61. package/resources/fe-dist/assets/{timeline-definition-PNZ67QCA-A63tNc2d.js → timeline-definition-PNZ67QCA-CIlpfkBn.js} +1 -1
  62. package/resources/fe-dist/assets/{transfer-toast-CmSO0e95.js → transfer-toast-DvU8-v2e.js} +1 -1
  63. package/resources/fe-dist/assets/{triangle-alert-jNMwsJMj.js → triangle-alert-BI3hbdo6.js} +1 -1
  64. package/resources/fe-dist/assets/{vennDiagram-CIIHVFJN-BktPK2LC.js → vennDiagram-CIIHVFJN-NqSGMHBa.js} +1 -1
  65. package/resources/fe-dist/assets/{wardley-L42UT6IY-viU1xsE2.js → wardley-L42UT6IY-bOJt96SM.js} +1 -1
  66. package/resources/fe-dist/assets/{wardleyDiagram-YWT4CUSO-DICOr7Mv.js → wardleyDiagram-YWT4CUSO-DAgFPWOP.js} +1 -1
  67. package/resources/fe-dist/assets/{xychartDiagram-2RQKCTM6-bjue7pS9.js → xychartDiagram-2RQKCTM6-D5rVqEFB.js} +1 -1
  68. package/resources/fe-dist/assets/{zap-ClXbg92C.js → zap-D-Szn4JY.js} +1 -1
  69. package/resources/fe-dist/assets/zh_CN-DVIa9MQX.js +1 -0
  70. package/resources/fe-dist/index.html +1 -1
  71. package/resources/gateway-drizzle/0015_wise_mongu.sql +1 -0
  72. package/resources/gateway-drizzle/0016_cheerful_scarecrow.sql +17 -0
  73. package/resources/gateway-drizzle/meta/_journal.json +14 -0
  74. package/resources/fe-dist/assets/DevicePage-BIjGEtCP.js +0 -24
  75. package/resources/fe-dist/assets/SettingsPage-BAx39LyM.js +0 -39
  76. package/resources/fe-dist/assets/api-Dto6Spgl.js +0 -10
  77. package/resources/fe-dist/assets/channel-DD10rSnh.js +0 -1
  78. package/resources/fe-dist/assets/classDiagram-4FO5ZUOK-CEWAB_yV.js +0 -1
  79. package/resources/fe-dist/assets/classDiagram-v2-Q7XG4LA2-CEWAB_yV.js +0 -1
  80. package/resources/fe-dist/assets/en_US-BihUhDmr.js +0 -1
  81. package/resources/fe-dist/assets/ja_JP-f5sXmz8W.js +0 -1
  82. package/resources/fe-dist/assets/stateDiagram-v2-BHNVJYJU-8FQLtPkV.js +0 -1
  83. package/resources/fe-dist/assets/terminal-settings-panel-DU9rOuZd.js +0 -25
  84. package/resources/fe-dist/assets/zh_CN-CPdvelFW.js +0 -1
@@ -22735,7 +22735,8 @@ var I18N_RESOURCES = {
22735
22735
  copied: "Copied to clipboard",
22736
22736
  copyFailed: "Copy failed",
22737
22737
  pasteFailed: "Could not read clipboard, check browser permissions",
22738
- clearSelection: "Dismiss selection"
22738
+ clearSelection: "Dismiss selection",
22739
+ fileLinkNotFound: "File does not exist or is not accessible"
22739
22740
  },
22740
22741
  settings: {
22741
22742
  title: "System Settings",
@@ -23751,7 +23752,8 @@ Time: {{time}}`,
23751
23752
  copied: "\u5DF2\u590D\u5236\u5230\u526A\u8D34\u677F",
23752
23753
  copyFailed: "\u590D\u5236\u5931\u8D25",
23753
23754
  pasteFailed: "\u65E0\u6CD5\u8BFB\u53D6\u526A\u8D34\u677F\uFF0C\u8BF7\u68C0\u67E5\u6D4F\u89C8\u5668\u6743\u9650",
23754
- clearSelection: "\u53D6\u6D88\u9009\u62E9"
23755
+ clearSelection: "\u53D6\u6D88\u9009\u62E9",
23756
+ fileLinkNotFound: "\u6587\u4EF6\u4E0D\u5B58\u5728\u6216\u65E0\u6CD5\u8BBF\u95EE"
23755
23757
  },
23756
23758
  settings: {
23757
23759
  title: "\u7CFB\u7EDF\u8BBE\u7F6E",
@@ -24767,7 +24769,8 @@ Bot\uFF1A{{botName}}
24767
24769
  copied: "\u30AF\u30EA\u30C3\u30D7\u30DC\u30FC\u30C9\u306B\u30B3\u30D4\u30FC\u3057\u307E\u3057\u305F",
24768
24770
  copyFailed: "\u30B3\u30D4\u30FC\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
24769
24771
  pasteFailed: "\u30AF\u30EA\u30C3\u30D7\u30DC\u30FC\u30C9\u3092\u8AAD\u307F\u53D6\u308C\u307E\u305B\u3093\u3002\u30D6\u30E9\u30A6\u30B6\u306E\u6A29\u9650\u3092\u78BA\u8A8D\u3057\u3066\u304F\u3060\u3055\u3044",
24770
- clearSelection: "\u9078\u629E\u3092\u89E3\u9664"
24772
+ clearSelection: "\u9078\u629E\u3092\u89E3\u9664",
24773
+ fileLinkNotFound: "\u30D5\u30A1\u30A4\u30EB\u304C\u5B58\u5728\u3057\u306A\u3044\u304B\u30A2\u30AF\u30BB\u30B9\u3067\u304D\u307E\u305B\u3093"
24771
24774
  },
24772
24775
  settings: {
24773
24776
  title: "\u30B7\u30B9\u30C6\u30E0\u8A2D\u5B9A",
@@ -25651,12 +25654,14 @@ __export(exports_ws_borsh, {
25651
25654
  encodeTmuxEventPayload: () => encodeTmuxEventPayload,
25652
25655
  encodeStateSnapshot: () => encodeStateSnapshot,
25653
25656
  encodePayload: () => encodePayload,
25657
+ encodePaneModes: () => encodePaneModes,
25654
25658
  encodeEnvelope: () => encodeEnvelope,
25655
25659
  encodeDeviceEventPayload: () => encodeDeviceEventPayload,
25656
25660
  encodeChunk: () => encodeChunk,
25657
25661
  decodeTmuxEventPayload: () => decodeTmuxEventPayload,
25658
25662
  decodeStateSnapshot: () => decodeStateSnapshot,
25659
25663
  decodePayload: () => decodePayload,
25664
+ decodePaneModes: () => decodePaneModes,
25660
25665
  decodeEnvelopeAndPayload: () => decodeEnvelopeAndPayload,
25661
25666
  decodeEnvelope: () => decodeEnvelope,
25662
25667
  decodeDeviceEventPayload: () => decodeDeviceEventPayload,
@@ -25670,6 +25675,11 @@ __export(exports_ws_borsh, {
25670
25675
  WATCH_EVENT_MODEL_UNAVAILABLE: () => WATCH_EVENT_MODEL_UNAVAILABLE,
25671
25676
  SITE_THEME_LIGHT: () => SITE_THEME_LIGHT,
25672
25677
  SITE_THEME_DARK: () => SITE_THEME_DARK,
25678
+ PANE_MODE_MOUSE_UTF8: () => PANE_MODE_MOUSE_UTF8,
25679
+ PANE_MODE_MOUSE_STANDARD: () => PANE_MODE_MOUSE_STANDARD,
25680
+ PANE_MODE_MOUSE_SGR: () => PANE_MODE_MOUSE_SGR,
25681
+ PANE_MODE_MOUSE_BUTTON: () => PANE_MODE_MOUSE_BUTTON,
25682
+ PANE_MODE_MOUSE_ALL: () => PANE_MODE_MOUSE_ALL,
25673
25683
  MAX_CHUNK_STREAMS: () => MAX_CHUNK_STREAMS,
25674
25684
  MAX_CHUNKS_PER_MESSAGE: () => MAX_CHUNKS_PER_MESSAGE,
25675
25685
  MAGIC: () => MAGIC,
@@ -25703,6 +25713,7 @@ __export(exports_ws_borsh, {
25703
25713
  KIND_STATE_SNAPSHOT_DIFF: () => KIND_STATE_SNAPSHOT_DIFF,
25704
25714
  KIND_STATE_SNAPSHOT: () => KIND_STATE_SNAPSHOT,
25705
25715
  KIND_SITE_THEME_UPDATE: () => KIND_SITE_THEME_UPDATE,
25716
+ KIND_SETTINGS_UPDATE: () => KIND_SETTINGS_UPDATE,
25706
25717
  KIND_PONG: () => KIND_PONG,
25707
25718
  KIND_PING: () => KIND_PING,
25708
25719
  KIND_LIVE_RESUME: () => KIND_LIVE_RESUME,
@@ -25737,6 +25748,7 @@ __export(exports_ws_borsh, {
25737
25748
  ERROR_FRAME_TOO_LARGE: () => ERROR_FRAME_TOO_LARGE,
25738
25749
  ERROR_DEVICE_NOT_FOUND: () => ERROR_DEVICE_NOT_FOUND,
25739
25750
  ERROR_DEVICE_CONNECT_FAILED: () => ERROR_DEVICE_CONNECT_FAILED,
25751
+ EMPTY_PANE_MODE_FLAGS: () => EMPTY_PANE_MODE_FLAGS,
25740
25752
  DEFAULT_MAX_FRAME_BYTES: () => DEFAULT_MAX_FRAME_BYTES,
25741
25753
  ChunkReassembler: () => ChunkReassembler,
25742
25754
  CURRENT_VERSION: () => CURRENT_VERSION,
@@ -25804,6 +25816,7 @@ var KIND_AGENT_UNSUBSCRIBE = 1538;
25804
25816
  var KIND_AGENT_EVENT = 1539;
25805
25817
  var KIND_WATCH_EVENT = 1793;
25806
25818
  var KIND_SITE_THEME_UPDATE = 2049;
25819
+ var KIND_SETTINGS_UPDATE = 2050;
25807
25820
  var VALID_KINDS = new Set([
25808
25821
  KIND_HELLO_C2S,
25809
25822
  KIND_HELLO_S2C,
@@ -25850,7 +25863,8 @@ var VALID_KINDS = new Set([
25850
25863
  KIND_AGENT_UNSUBSCRIBE,
25851
25864
  KIND_AGENT_EVENT,
25852
25865
  KIND_WATCH_EVENT,
25853
- KIND_SITE_THEME_UPDATE
25866
+ KIND_SITE_THEME_UPDATE,
25867
+ KIND_SETTINGS_UPDATE
25854
25868
  ]);
25855
25869
  function isValidKind(kind) {
25856
25870
  return VALID_KINDS.has(kind);
@@ -25902,7 +25916,8 @@ function kindToString(kind) {
25902
25916
  [KIND_AGENT_UNSUBSCRIBE]: "AGENT_UNSUBSCRIBE",
25903
25917
  [KIND_AGENT_EVENT]: "AGENT_EVENT",
25904
25918
  [KIND_WATCH_EVENT]: "WATCH_EVENT",
25905
- [KIND_SITE_THEME_UPDATE]: "SITE_THEME_UPDATE"
25919
+ [KIND_SITE_THEME_UPDATE]: "SITE_THEME_UPDATE",
25920
+ [KIND_SETTINGS_UPDATE]: "SETTINGS_UPDATE"
25906
25921
  };
25907
25922
  return kindMap[kind] ?? `UNKNOWN(0x${kind.toString(16).padStart(4, "0")})`;
25908
25923
  }
@@ -25945,6 +25960,7 @@ __export(exports_schema, {
25945
25960
  StateSnapshotDiffSchema: () => StateSnapshotDiffSchema,
25946
25961
  SiteThemeUpdateS2CSchema: () => SiteThemeUpdateS2CSchema,
25947
25962
  SiteThemeUpdateC2SSchema: () => SiteThemeUpdateC2SSchema,
25963
+ SettingsUpdateS2CSchema: () => SettingsUpdateS2CSchema,
25948
25964
  SessionWireSchema: () => SessionWireSchema,
25949
25965
  SITE_THEME_LIGHT: () => SITE_THEME_LIGHT,
25950
25966
  SITE_THEME_DARK: () => SITE_THEME_DARK,
@@ -26160,6 +26176,7 @@ var TermHistorySchema = import_zorsh.b.struct({
26160
26176
  selectToken: import_zorsh.b.bytes(16),
26161
26177
  encoding: import_zorsh.b.u8(),
26162
26178
  alternateScreen: import_zorsh.b.bool(),
26179
+ modes: import_zorsh.b.u8(),
26163
26180
  data: import_zorsh.b.bytes()
26164
26181
  });
26165
26182
  var ClipboardWriteSchema = import_zorsh.b.struct({
@@ -26302,6 +26319,10 @@ var SiteThemeUpdateS2CSchema = import_zorsh.b.struct({
26302
26319
  theme: import_zorsh.b.u8(),
26303
26320
  serverTimestamp: import_zorsh.b.u64()
26304
26321
  });
26322
+ var SettingsUpdateS2CSchema = import_zorsh.b.struct({
26323
+ namespace: import_zorsh.b.string(),
26324
+ serverTimestamp: import_zorsh.b.u64()
26325
+ });
26305
26326
  // ../shared/src/ws-borsh/agent.ts
26306
26327
  var AGENT_EVENT_SYNC = 1;
26307
26328
  var AGENT_EVENT_STATUS = 2;
@@ -26360,6 +26381,31 @@ class WsBorshError extends Error {
26360
26381
  this.name = "WsBorshError";
26361
26382
  }
26362
26383
  }
26384
+ // ../shared/src/ws-borsh/pane-modes.ts
26385
+ var PANE_MODE_MOUSE_STANDARD = 1 << 0;
26386
+ var PANE_MODE_MOUSE_BUTTON = 1 << 1;
26387
+ var PANE_MODE_MOUSE_ALL = 1 << 2;
26388
+ var PANE_MODE_MOUSE_SGR = 1 << 3;
26389
+ var PANE_MODE_MOUSE_UTF8 = 1 << 4;
26390
+ var EMPTY_PANE_MODE_FLAGS = {
26391
+ mouseStandard: false,
26392
+ mouseButton: false,
26393
+ mouseAll: false,
26394
+ mouseSgr: false,
26395
+ mouseUtf8: false
26396
+ };
26397
+ function encodePaneModes(flags) {
26398
+ return (flags.mouseStandard ? PANE_MODE_MOUSE_STANDARD : 0) | (flags.mouseButton ? PANE_MODE_MOUSE_BUTTON : 0) | (flags.mouseAll ? PANE_MODE_MOUSE_ALL : 0) | (flags.mouseSgr ? PANE_MODE_MOUSE_SGR : 0) | (flags.mouseUtf8 ? PANE_MODE_MOUSE_UTF8 : 0);
26399
+ }
26400
+ function decodePaneModes(bits) {
26401
+ return {
26402
+ mouseStandard: (bits & PANE_MODE_MOUSE_STANDARD) !== 0,
26403
+ mouseButton: (bits & PANE_MODE_MOUSE_BUTTON) !== 0,
26404
+ mouseAll: (bits & PANE_MODE_MOUSE_ALL) !== 0,
26405
+ mouseSgr: (bits & PANE_MODE_MOUSE_SGR) !== 0,
26406
+ mouseUtf8: (bits & PANE_MODE_MOUSE_UTF8) !== 0
26407
+ };
26408
+ }
26363
26409
  // ../shared/src/ws-borsh/codec.ts
26364
26410
  var MAGIC = new Uint8Array([84, 88]);
26365
26411
  var CURRENT_VERSION = 1;
@@ -28369,6 +28415,7 @@ function getBooleanEnv(key, defaultValue) {
28369
28415
  var config = {
28370
28416
  masterKey: process.env.TMEX_MASTER_KEY,
28371
28417
  port: Number.parseInt(getEnv("GATEWAY_PORT", "9663"), 10),
28418
+ bindHost: getEnv("TMEX_BIND_HOST", "0.0.0.0"),
28372
28419
  baseUrl: getEnv("TMEX_BASE_URL", "http://127.0.0.1:8085"),
28373
28420
  siteNameDefault: getEnv("TMEX_SITE_NAME", "tmex"),
28374
28421
  databaseUrl: getEnv("DATABASE_URL", "./tmex.db"),
@@ -33403,6 +33450,7 @@ var siteSettings = sqliteTable("site_settings", {
33403
33450
  sshReconnectDelaySeconds: integer("ssh_reconnect_delay_seconds").notNull(),
33404
33451
  language: text("language").notNull().default("en_US"),
33405
33452
  theme: text("theme").notNull().default("dark"),
33453
+ disabledNotificationChannels: text("disabled_notification_channels", { mode: "json" }).$type().notNull().default([]),
33406
33454
  updatedAt: text("updated_at").notNull()
33407
33455
  }, (table) => [
33408
33456
  check("site_settings_singleton_check", sql`${table.id} = 1`),
@@ -33487,10 +33535,7 @@ var agentSettings = sqliteTable("agent_settings", {
33487
33535
  }),
33488
33536
  defaultModelId: text("default_model_id"),
33489
33537
  updatedAt: text("updated_at").notNull()
33490
- }, (table) => [
33491
- check("agent_settings_singleton_check", sql`${table.id} = 1`),
33492
- check("agent_settings_search_provider_check", sql`${table.searchProvider} in ('none', 'tavily', 'brave')`)
33493
- ]);
33538
+ }, (table) => [check("agent_settings_singleton_check", sql`${table.id} = 1`)]);
33494
33539
  var agentSessions = sqliteTable("agent_sessions", {
33495
33540
  id: text("id").primaryKey(),
33496
33541
  title: text("title").notNull(),
@@ -33702,6 +33747,7 @@ function toSiteSettings(row) {
33702
33747
  sshReconnectDelaySeconds: row.sshReconnectDelaySeconds,
33703
33748
  language: normalizeLocale(row.language),
33704
33749
  theme: row.theme,
33750
+ disabledNotificationChannels: Array.isArray(row.disabledNotificationChannels) ? row.disabledNotificationChannels : [],
33705
33751
  updatedAt: row.updatedAt
33706
33752
  };
33707
33753
  }
@@ -33786,6 +33832,7 @@ function ensureSiteSettingsInitialized() {
33786
33832
  sshReconnectMaxRetries: config.sshReconnectMaxRetriesDefault,
33787
33833
  sshReconnectDelaySeconds: config.sshReconnectDelaySecondsDefault,
33788
33834
  language: normalizeLocale(config.languageDefault),
33835
+ disabledNotificationChannels: [],
33789
33836
  updatedAt: now
33790
33837
  }).onConflictDoNothing({ target: siteSettings.id }).run();
33791
33838
  }
@@ -33996,6 +34043,7 @@ function updateSiteSettings(updates) {
33996
34043
  sshReconnectDelaySeconds: updates.sshReconnectDelaySeconds ?? current.sshReconnectDelaySeconds,
33997
34044
  language: updates.language ? normalizeLocale(updates.language) : current.language,
33998
34045
  theme: updates.theme ?? current.theme,
34046
+ disabledNotificationChannels: updates.disabledNotificationChannels ?? current.disabledNotificationChannels,
33999
34047
  updatedAt: new Date().toISOString()
34000
34048
  };
34001
34049
  const orm = getDb();
@@ -34008,7 +34056,8 @@ function updateSiteSettings(updates) {
34008
34056
  enableNotificationPush: next.enableNotificationPush,
34009
34057
  enableBellPush: next.enableBellPush,
34010
34058
  enableBellSound: next.enableBellSound,
34011
- theme: next.theme
34059
+ theme: next.theme,
34060
+ disabledNotificationChannels: next.disabledNotificationChannels
34012
34061
  }).where(eq(siteSettings.id, 1)).run();
34013
34062
  siteSettingsCache = { value: next, expiresAt: Date.now() + SITE_SETTINGS_TTL_MS };
34014
34063
  if (instance.language !== next.language) {
@@ -88882,11 +88931,18 @@ var weixinChannel = new WeixinChannel;
88882
88931
  class EventNotifier {
88883
88932
  bellThrottleMap = new Map;
88884
88933
  notificationThrottleMap = new Map;
88885
- channels = [
88886
- webhookChannel,
88887
- telegramChannel,
88888
- weixinChannel
88889
- ];
88934
+ channels = new Map;
88935
+ constructor() {
88936
+ this.registerChannel(webhookChannel);
88937
+ this.registerChannel(telegramChannel);
88938
+ this.registerChannel(weixinChannel);
88939
+ }
88940
+ registerChannel(channel) {
88941
+ if (this.channels.has(channel.id)) {
88942
+ throw new Error(`notification channel already registered: ${channel.id}`);
88943
+ }
88944
+ this.channels.set(channel.id, channel);
88945
+ }
88890
88946
  async notify(eventType, event) {
88891
88947
  const fullEvent = {
88892
88948
  ...event,
@@ -88902,7 +88958,9 @@ class EventNotifier {
88902
88958
  return;
88903
88959
  }
88904
88960
  }
88905
- await Promise.all(this.channels.map((channel) => channel.notify(eventType, fullEvent)));
88961
+ const disabled = new Set(getSiteSettings().disabledNotificationChannels);
88962
+ const active = [...this.channels.values()].filter((channel) => !disabled.has(channel.id));
88963
+ await Promise.all(active.map((channel) => channel.notify(eventType, fullEvent)));
88906
88964
  }
88907
88965
  shouldPassBellThrottle(event) {
88908
88966
  const settings = getSiteSettings();
@@ -98120,7 +98178,7 @@ function buildLocalTmuxEnv(resolvedPath, baseEnv = process.env) {
98120
98178
  }
98121
98179
 
98122
98180
  // ../../apps/gateway/src/tmux-client/capture-history.ts
98123
- var PANE_SCREEN_INFO_FORMAT = "#{alternate_on} #{cursor_x} #{cursor_y} #{pane_height}";
98181
+ var PANE_SCREEN_INFO_FORMAT = "#{alternate_on} #{cursor_x} #{cursor_y} #{pane_height}" + " #{mouse_standard_flag} #{mouse_button_flag} #{mouse_all_flag} #{mouse_sgr_flag} #{mouse_utf8_flag}";
98124
98182
  function parsePaneScreenInfo(stdout) {
98125
98183
  const parts = stdout.trim().split(/\s+/);
98126
98184
  const toInt = (value) => {
@@ -98134,7 +98192,14 @@ function parsePaneScreenInfo(stdout) {
98134
98192
  alternateScreen: parts[0] === "1",
98135
98193
  cursorX: toInt(parts[1]),
98136
98194
  cursorY: toInt(parts[2]),
98137
- paneHeight: toInt(parts[3])
98195
+ paneHeight: toInt(parts[3]),
98196
+ modes: {
98197
+ mouseStandard: parts[4] === "1",
98198
+ mouseButton: parts[5] === "1",
98199
+ mouseAll: parts[6] === "1",
98200
+ mouseSgr: parts[7] === "1",
98201
+ mouseUtf8: parts[8] === "1"
98202
+ }
98138
98203
  };
98139
98204
  }
98140
98205
  var PANE_META_FORMAT = "#{pane_width} #{pane_height} #{alternate_on} #{cursor_x} #{cursor_y} #{pane_current_command}";
@@ -100235,14 +100300,25 @@ class LocalExternalTmuxConnection {
100235
100300
  await this.capturePaneHistory(paneId);
100236
100301
  await this.requestSnapshotInternal();
100237
100302
  }
100238
- async capturePaneHistory(paneId) {
100303
+ async fetchPaneHistory(paneId) {
100239
100304
  const screenInfo = parsePaneScreenInfo((await this.runTmux(["display-message", "-p", "-t", paneId, PANE_SCREEN_INFO_FORMAT], true)).stdout);
100240
100305
  const alternateScreen = screenInfo.alternateScreen;
100241
100306
  const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", "-", "-E", "-", "-e", "-J", "-N", "-p"], true)).stdout;
100242
100307
  const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", "-", "-E", "-", "-e", "-J", "-N", "-p", "-q"], true)).stdout;
100243
100308
  const history = alternateScreen ? hasRenderableTerminalContent(normal) ? normal : alternate : normal || alternate;
100244
- if (history) {
100245
- this.callbacks.onTerminalHistory(paneId, appendCursorRestore(history, screenInfo), alternateScreen);
100309
+ if (!history) {
100310
+ return null;
100311
+ }
100312
+ return {
100313
+ data: appendCursorRestore(history, screenInfo),
100314
+ alternateScreen,
100315
+ modes: encodePaneModes(screenInfo.modes)
100316
+ };
100317
+ }
100318
+ async capturePaneHistory(paneId) {
100319
+ const captured = await this.fetchPaneHistory(paneId);
100320
+ if (captured) {
100321
+ this.callbacks.onTerminalHistory(paneId, captured.data, captured.alternateScreen, captured.modes);
100246
100322
  }
100247
100323
  }
100248
100324
  async requestSnapshotInternal() {
@@ -101825,14 +101901,25 @@ class SshExternalTmuxConnection {
101825
101901
  await this.capturePaneHistory(paneId);
101826
101902
  await this.requestSnapshotInternal();
101827
101903
  }
101828
- async capturePaneHistory(paneId) {
101904
+ async fetchPaneHistory(paneId) {
101829
101905
  const screenInfo = parsePaneScreenInfo((await this.runTmux(["display-message", "-p", "-t", paneId, PANE_SCREEN_INFO_FORMAT], true)).stdout);
101830
101906
  const alternateScreen = screenInfo.alternateScreen;
101831
101907
  const normal = (await this.runTmux(["capture-pane", "-t", paneId, "-S", "-", "-E", "-", "-e", "-J", "-N", "-p"], true, 30000)).stdout;
101832
101908
  const alternate = (await this.runTmux(["capture-pane", "-t", paneId, "-a", "-S", "-", "-E", "-", "-e", "-J", "-N", "-p", "-q"], true, 30000)).stdout;
101833
101909
  const history = alternateScreen ? hasRenderableTerminalContent2(normal) ? normal : alternate : normal || alternate;
101834
- if (history) {
101835
- this.callbacks.onTerminalHistory(paneId, appendCursorRestore(history, screenInfo), alternateScreen);
101910
+ if (!history) {
101911
+ return null;
101912
+ }
101913
+ return {
101914
+ data: appendCursorRestore(history, screenInfo),
101915
+ alternateScreen,
101916
+ modes: encodePaneModes(screenInfo.modes)
101917
+ };
101918
+ }
101919
+ async capturePaneHistory(paneId) {
101920
+ const captured = await this.fetchPaneHistory(paneId);
101921
+ if (captured) {
101922
+ this.callbacks.onTerminalHistory(paneId, captured.data, captured.alternateScreen, captured.modes);
101836
101923
  }
101837
101924
  }
101838
101925
  async requestSnapshotInternal() {
@@ -102269,8 +102356,8 @@ class DeviceSessionRuntime {
102269
102356
  onTerminalOutput: (paneId, data) => {
102270
102357
  this.broadcast((listener) => listener.onTerminalOutput?.(paneId, data));
102271
102358
  },
102272
- onTerminalHistory: (paneId, data, alternateScreen) => {
102273
- this.broadcast((listener) => listener.onTerminalHistory?.(paneId, data, alternateScreen));
102359
+ onTerminalHistory: (paneId, data, alternateScreen, modes) => {
102360
+ this.broadcast((listener) => listener.onTerminalHistory?.(paneId, data, alternateScreen, modes));
102274
102361
  },
102275
102362
  onPromptMarker: (paneId, marker24) => {
102276
102363
  this.broadcast((listener) => listener.onPromptMarker?.(paneId, marker24));
@@ -102384,6 +102471,9 @@ class DeviceSessionRuntime {
102384
102471
  async requestPaneHistory(paneId) {
102385
102472
  return this.connection.requestPaneHistory(paneId);
102386
102473
  }
102474
+ async fetchPaneHistory(paneId) {
102475
+ return this.connection.fetchPaneHistory(paneId);
102476
+ }
102387
102477
  renameWindow(windowId, name24) {
102388
102478
  this.connection.renameWindow(windowId, name24);
102389
102479
  }
@@ -103589,68 +103679,98 @@ function validateFetchUrl(rawUrl) {
103589
103679
  }
103590
103680
  return { url: url2 };
103591
103681
  }
103592
- async function searchTavily(apiKey, query, deps) {
103593
- const response = await deps.fetchImpl(deps.tavilyEndpoint, {
103594
- method: "POST",
103595
- headers: {
103596
- "Content-Type": "application/json",
103597
- Authorization: `Bearer ${apiKey}`
103598
- },
103599
- body: JSON.stringify({
103600
- api_key: apiKey,
103601
- query,
103602
- max_results: WEB_SEARCH_MAX_RESULTS
103603
- }),
103604
- signal: AbortSignal.timeout(FETCH_URL_TIMEOUT_MS)
103605
- });
103606
- if (!response.ok) {
103607
- throw new Error(`Tavily search failed: HTTP ${response.status}`);
103682
+ var searchProviderRegistry = new Map;
103683
+ function registerSearchProvider(provider) {
103684
+ if (searchProviderRegistry.has(provider.id)) {
103685
+ throw new Error(`search provider already registered: ${provider.id}`);
103686
+ }
103687
+ searchProviderRegistry.set(provider.id, provider);
103688
+ }
103689
+ function getSearchProviders() {
103690
+ return [...searchProviderRegistry.values()];
103691
+ }
103692
+ function getSearchProvider(id) {
103693
+ return searchProviderRegistry.get(id);
103694
+ }
103695
+ var TAVILY_DEFAULT_ENDPOINT = "https://api.tavily.com/search";
103696
+ var BRAVE_DEFAULT_ENDPOINT = "https://api.search.brave.com/res/v1/web/search";
103697
+ var tavilyProvider = {
103698
+ id: "tavily",
103699
+ label: "Tavily",
103700
+ isConfigured: (settings) => Boolean(settings.tavilyApiKeyEnc),
103701
+ async search(query, settings, deps) {
103702
+ const apiKey = await decrypt(settings.tavilyApiKeyEnc ?? "");
103703
+ const fetchImpl = deps?.fetchImpl ?? fetch;
103704
+ const endpoint = deps?.endpoint ?? TAVILY_DEFAULT_ENDPOINT;
103705
+ const response = await fetchImpl(endpoint, {
103706
+ method: "POST",
103707
+ headers: {
103708
+ "Content-Type": "application/json",
103709
+ Authorization: `Bearer ${apiKey}`
103710
+ },
103711
+ body: JSON.stringify({
103712
+ api_key: apiKey,
103713
+ query,
103714
+ max_results: WEB_SEARCH_MAX_RESULTS
103715
+ }),
103716
+ signal: AbortSignal.timeout(FETCH_URL_TIMEOUT_MS)
103717
+ });
103718
+ if (!response.ok) {
103719
+ throw new Error(`Tavily search failed: HTTP ${response.status}`);
103720
+ }
103721
+ const payload = await response.json();
103722
+ return (payload.results ?? []).map((item) => ({
103723
+ title: item.title ?? "",
103724
+ url: item.url ?? "",
103725
+ snippet: item.content ?? ""
103726
+ }));
103608
103727
  }
103609
- const payload = await response.json();
103610
- return (payload.results ?? []).map((item) => ({
103611
- title: item.title ?? "",
103612
- url: item.url ?? "",
103613
- snippet: item.content ?? ""
103614
- }));
103615
- }
103616
- async function searchBrave(apiKey, query, deps) {
103617
- const url2 = new URL(deps.braveEndpoint);
103618
- url2.searchParams.set("q", query);
103619
- url2.searchParams.set("count", String(WEB_SEARCH_MAX_RESULTS));
103620
- const response = await deps.fetchImpl(url2.toString(), {
103621
- headers: {
103622
- Accept: "application/json",
103623
- "X-Subscription-Token": apiKey
103624
- },
103625
- signal: AbortSignal.timeout(FETCH_URL_TIMEOUT_MS)
103626
- });
103627
- if (!response.ok) {
103628
- throw new Error(`Brave search failed: HTTP ${response.status}`);
103728
+ };
103729
+ var braveProvider = {
103730
+ id: "brave",
103731
+ label: "Brave",
103732
+ isConfigured: (settings) => Boolean(settings.braveApiKeyEnc),
103733
+ async search(query, settings, deps) {
103734
+ const apiKey = await decrypt(settings.braveApiKeyEnc ?? "");
103735
+ const fetchImpl = deps?.fetchImpl ?? fetch;
103736
+ const url2 = new URL(deps?.endpoint ?? BRAVE_DEFAULT_ENDPOINT);
103737
+ url2.searchParams.set("q", query);
103738
+ url2.searchParams.set("count", String(WEB_SEARCH_MAX_RESULTS));
103739
+ const response = await fetchImpl(url2.toString(), {
103740
+ headers: {
103741
+ Accept: "application/json",
103742
+ "X-Subscription-Token": apiKey
103743
+ },
103744
+ signal: AbortSignal.timeout(FETCH_URL_TIMEOUT_MS)
103745
+ });
103746
+ if (!response.ok) {
103747
+ throw new Error(`Brave search failed: HTTP ${response.status}`);
103748
+ }
103749
+ const payload = await response.json();
103750
+ return (payload.web?.results ?? []).map((item) => ({
103751
+ title: item.title ?? "",
103752
+ url: item.url ?? "",
103753
+ snippet: item.description ?? ""
103754
+ }));
103629
103755
  }
103630
- const payload = await response.json();
103631
- return (payload.web?.results ?? []).map((item) => ({
103632
- title: item.title ?? "",
103633
- url: item.url ?? "",
103634
- snippet: item.description ?? ""
103635
- }));
103636
- }
103756
+ };
103757
+ registerSearchProvider(tavilyProvider);
103758
+ registerSearchProvider(braveProvider);
103637
103759
  async function createWebSearchTool(options = {}) {
103638
103760
  const settings = options.settings ?? getAgentSettings();
103639
- const fetchImpl = options.fetchImpl ?? fetch;
103640
- const tavilyEndpoint = options.tavilyEndpoint ?? "https://api.tavily.com/search";
103641
- const braveEndpoint = options.braveEndpoint ?? "https://api.search.brave.com/res/v1/web/search";
103642
- let search = null;
103643
- if (settings.searchProvider === "tavily" && settings.tavilyApiKeyEnc) {
103644
- const apiKey = await decrypt(settings.tavilyApiKeyEnc);
103645
- search = (query) => searchTavily(apiKey, query, { fetchImpl, tavilyEndpoint });
103646
- } else if (settings.searchProvider === "brave" && settings.braveApiKeyEnc) {
103647
- const apiKey = await decrypt(settings.braveApiKeyEnc);
103648
- search = (query) => searchBrave(apiKey, query, { fetchImpl, braveEndpoint });
103649
- }
103650
- if (!search) {
103761
+ if (settings.searchProvider === "none") {
103762
+ return null;
103763
+ }
103764
+ const provider = searchProviderRegistry.get(settings.searchProvider);
103765
+ if (!provider || !provider.isConfigured(settings)) {
103651
103766
  return null;
103652
103767
  }
103653
- const searchFn = search;
103768
+ const endpoint = options.endpointOverrides?.[provider.id] ?? (provider.id === "tavily" ? options.tavilyEndpoint : provider.id === "brave" ? options.braveEndpoint : undefined);
103769
+ const deps = {
103770
+ fetchImpl: options.fetchImpl ?? fetch,
103771
+ endpoint
103772
+ };
103773
+ const searchFn = (query) => provider.search(query, settings, deps);
103654
103774
  return tool({
103655
103775
  description: "Search the web. Returns a JSON array of results with title, url and snippet.",
103656
103776
  inputSchema: exports_external.object({
@@ -105414,6 +105534,22 @@ class PushSupervisor {
105414
105534
  }
105415
105535
  var pushSupervisor = new PushSupervisor;
105416
105536
 
105537
+ // ../../apps/gateway/src/settings/broadcaster.ts
105538
+ var broadcaster = null;
105539
+ function registerSettingsBroadcaster(fn) {
105540
+ broadcaster = fn;
105541
+ }
105542
+ function broadcastSettingsUpdate(namespace) {
105543
+ broadcaster?.(namespace);
105544
+ }
105545
+ var treeOverlayBridge = null;
105546
+ function registerTreeOverlayBridge(bridge) {
105547
+ treeOverlayBridge = bridge;
105548
+ }
105549
+ function getTreeOverlayBridge() {
105550
+ return treeOverlayBridge;
105551
+ }
105552
+
105417
105553
  // ../../apps/gateway/src/api/agent.ts
105418
105554
  var WRITE_MODES = ["confirm", "auto"];
105419
105555
  var MAX_STEPS_MIN = 1;
@@ -105953,6 +106089,126 @@ function json3(data, status = 200) {
105953
106089
  });
105954
106090
  }
105955
106091
 
106092
+ // ../../apps/gateway/src/capabilities.ts
106093
+ var API_VERSION = 1;
106094
+ var GATEWAY_CAPABILITIES = ["tmex-ws-borsh-v1", "tmex-agent-v1", "tmex-split-v1"];
106095
+
106096
+ // ../../apps/gateway/src/system/version.ts
106097
+ import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
106098
+ import { resolve as resolve4 } from "path";
106099
+
106100
+ // ../../apps/gateway/src/system/install-info.ts
106101
+ import { existsSync as existsSync4, readFileSync as readFileSync3 } from "fs";
106102
+ import { resolve as resolve3 } from "path";
106103
+ function resolveInstallDir() {
106104
+ const feDist = process.env.TMEX_FE_DIST_DIR;
106105
+ if (feDist) {
106106
+ return resolve3(feDist, "..", "..");
106107
+ }
106108
+ return process.cwd();
106109
+ }
106110
+ function readInstallMeta() {
106111
+ const metaPath = resolve3(resolveInstallDir(), "install-meta.json");
106112
+ try {
106113
+ if (!existsSync4(metaPath))
106114
+ return null;
106115
+ return JSON.parse(readFileSync3(metaPath, "utf8"));
106116
+ } catch {
106117
+ return null;
106118
+ }
106119
+ }
106120
+ function deploymentFromPlatform(platform) {
106121
+ if (platform === "darwin")
106122
+ return "launchd";
106123
+ if (platform === "linux")
106124
+ return "systemd";
106125
+ return "none";
106126
+ }
106127
+ function getInstallInfo() {
106128
+ if (!config.isProd) {
106129
+ return {
106130
+ installedViaCli: false,
106131
+ deployment: "none",
106132
+ installDir: null,
106133
+ serviceName: null,
106134
+ cliVersion: null,
106135
+ bunPath: null
106136
+ };
106137
+ }
106138
+ const meta3 = readInstallMeta();
106139
+ if (!meta3) {
106140
+ return {
106141
+ installedViaCli: false,
106142
+ deployment: "none",
106143
+ installDir: resolveInstallDir(),
106144
+ serviceName: null,
106145
+ cliVersion: null,
106146
+ bunPath: null
106147
+ };
106148
+ }
106149
+ return {
106150
+ installedViaCli: true,
106151
+ deployment: deploymentFromPlatform(meta3.platform ?? process.platform),
106152
+ installDir: meta3.installDir ?? resolveInstallDir(),
106153
+ serviceName: meta3.serviceName ?? null,
106154
+ cliVersion: meta3.cliVersion ?? null,
106155
+ bunPath: meta3.bunPath ?? null
106156
+ };
106157
+ }
106158
+
106159
+ // ../../apps/gateway/src/system/version.ts
106160
+ var cachedBase;
106161
+ function readRepoPackageVersion() {
106162
+ const dir2 = import.meta.dir;
106163
+ if (!dir2)
106164
+ return null;
106165
+ const path = resolve4(dir2, "../../../../packages/app/package.json");
106166
+ try {
106167
+ if (!existsSync5(path))
106168
+ return null;
106169
+ const pkg = JSON.parse(readFileSync4(path, "utf8"));
106170
+ return pkg.version ?? null;
106171
+ } catch {
106172
+ return null;
106173
+ }
106174
+ }
106175
+ function getBaseVersion() {
106176
+ if (cachedBase !== undefined)
106177
+ return cachedBase;
106178
+ let base = null;
106179
+ if ("0.17.0") {
106180
+ base = "0.17.0";
106181
+ }
106182
+ if (!base && config.isProd) {
106183
+ base = readInstallMeta()?.cliVersion ?? null;
106184
+ }
106185
+ if (!base) {
106186
+ base = readRepoPackageVersion();
106187
+ }
106188
+ cachedBase = base ?? "unknown";
106189
+ return cachedBase;
106190
+ }
106191
+ function getDisplayVersion() {
106192
+ return formatDisplayVersion(getBaseVersion(), config.isProd);
106193
+ }
106194
+
106195
+ // ../../apps/gateway/src/api/capabilities.ts
106196
+ function handleCapabilitiesApiRequest(req, path) {
106197
+ if (path === "/api/capabilities" && req.method === "GET") {
106198
+ return handleGetCapabilities();
106199
+ }
106200
+ return null;
106201
+ }
106202
+ function handleGetCapabilities() {
106203
+ return new Response(JSON.stringify({
106204
+ serverImpl: "tmex-gateway",
106205
+ serverVersion: getDisplayVersion(),
106206
+ apiVersion: API_VERSION,
106207
+ wsProtocolVersion: exports_ws_borsh.CURRENT_VERSION,
106208
+ capabilities: [...GATEWAY_CAPABILITIES]
106209
+ }), { status: 200, headers: { "Content-Type": "application/json" } });
106210
+ }
106211
+
105956
106212
  // ../../apps/gateway/src/db/file-roots.ts
105957
106213
  function getFileRoots() {
105958
106214
  const orm = getDb();
@@ -106000,7 +106256,7 @@ function deleteFileRoot(id) {
106000
106256
  }
106001
106257
 
106002
106258
  // ../../apps/gateway/src/files/device-storage.ts
106003
- import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync3, realpathSync, rmSync as rmSync2, statSync } from "fs";
106259
+ import { mkdtempSync as mkdtempSync2, readFileSync as readFileSync5, realpathSync, rmSync as rmSync2, statSync } from "fs";
106004
106260
  import { tmpdir as tmpdir2 } from "os";
106005
106261
  import { join as join5 } from "path";
106006
106262
 
@@ -106201,10 +106457,10 @@ function acquireGlobalSlot() {
106201
106457
  active += 1;
106202
106458
  return Promise.resolve();
106203
106459
  }
106204
- return new Promise((resolve3) => {
106460
+ return new Promise((resolve5) => {
106205
106461
  globalWaiters.push(() => {
106206
106462
  active += 1;
106207
- resolve3();
106463
+ resolve5();
106208
106464
  });
106209
106465
  });
106210
106466
  }
@@ -106812,7 +107068,7 @@ async function copyToBuffer(spec, normPath) {
106812
107068
  }
106813
107069
  if (res.exitCode !== 0)
106814
107070
  return fail(classifyRsyncFailure(res.exitCode, res.stderr), res.stderr);
106815
- return ok(readFileSync3(dest));
107071
+ return ok(readFileSync5(dest));
106816
107072
  } finally {
106817
107073
  try {
106818
107074
  rmSync2(dir2, { recursive: true, force: true });
@@ -107209,6 +107465,7 @@ async function handleCreateRoot(req) {
107209
107465
  return json4({ error: t2("apiError.fileRootDuplicate") }, 400);
107210
107466
  }
107211
107467
  const record2 = createFileRoot({ deviceId, path, enabled: body.enabled ?? true });
107468
+ broadcastSettingsUpdate("file-roots");
107212
107469
  return json4({ root: toRootDto(record2) }, 201);
107213
107470
  }
107214
107471
  async function handleUpdateRoot(req, id) {
@@ -107244,12 +107501,14 @@ async function handleUpdateRoot(req, id) {
107244
107501
  const updated = updateFileRoot(id, updates);
107245
107502
  if (!updated)
107246
107503
  return json4({ error: t2("apiError.notFound") }, 404);
107504
+ broadcastSettingsUpdate("file-roots");
107247
107505
  return json4({ root: toRootDto(updated) });
107248
107506
  }
107249
107507
  function handleDeleteRoot(id) {
107250
107508
  const okDelete = deleteFileRoot(id);
107251
107509
  if (!okDelete)
107252
107510
  return json4({ error: t2("apiError.notFound") }, 404);
107511
+ broadcastSettingsUpdate("file-roots");
107253
107512
  return json4({ success: true });
107254
107513
  }
107255
107514
  async function handleList(url2) {
@@ -107569,7 +107828,19 @@ function handleFilesApiRequest(req, path) {
107569
107828
 
107570
107829
  // ../../apps/gateway/src/api/llm.ts
107571
107830
  var PROTOCOLS = ["openai-chat", "openai-responses"];
107572
- var SEARCH_PROVIDERS = ["none", "tavily", "brave"];
107831
+ function isValidSearchProvider(value) {
107832
+ if (typeof value !== "string") {
107833
+ return false;
107834
+ }
107835
+ return value === "none" || getSearchProvider(value) !== undefined;
107836
+ }
107837
+ function toSearchProviderInfos(settings) {
107838
+ return getSearchProviders().map((provider) => ({
107839
+ id: provider.id,
107840
+ label: provider.label,
107841
+ isConfigured: provider.isConfigured(settings)
107842
+ }));
107843
+ }
107573
107844
  function handleLlmApiRequest(req, path) {
107574
107845
  if (path === "/api/llm/providers" && req.method === "GET") {
107575
107846
  return handleListProviders();
@@ -107693,6 +107964,7 @@ async function handleCreateProvider(req) {
107693
107964
  apiKeyEnc: await encrypt(apiKey),
107694
107965
  enabled: body.enabled ?? true
107695
107966
  });
107967
+ broadcastSettingsUpdate("llm");
107696
107968
  const { provider, modelsError } = await refreshModelsCache(created);
107697
107969
  return json5({ provider: toProviderDto(provider), ...modelsError ? { modelsError } : {} }, 201);
107698
107970
  }
@@ -107757,6 +108029,7 @@ async function handleUpdateProvider(req, id) {
107757
108029
  if (!provider) {
107758
108030
  return json5({ error: t2("apiError.llmProviderNotFound") }, 404);
107759
108031
  }
108032
+ broadcastSettingsUpdate("llm");
107760
108033
  const credentialsChanged = updates.baseUrl !== undefined && updates.baseUrl !== existing.baseUrl || updates.apiKeyEnc !== undefined;
107761
108034
  let modelsError;
107762
108035
  if (credentialsChanged) {
@@ -107772,6 +108045,7 @@ async function handleDeleteProvider(id) {
107772
108045
  return json5({ error: t2("apiError.llmProviderNotFound") }, 404);
107773
108046
  }
107774
108047
  deleteLlmProvider(id);
108048
+ broadcastSettingsUpdate("llm");
107775
108049
  return json5({ success: true });
107776
108050
  }
107777
108051
  async function handleRefreshProviderModels(id) {
@@ -107786,7 +108060,11 @@ async function handleRefreshProviderModels(id) {
107786
108060
  return json5({ models });
107787
108061
  }
107788
108062
  async function handleGetSettings() {
107789
- return json5({ settings: toSettingsDto(getAgentSettings()) });
108063
+ const record2 = getAgentSettings();
108064
+ return json5({
108065
+ settings: toSettingsDto(record2),
108066
+ searchProviders: toSearchProviderInfos(record2)
108067
+ });
107790
108068
  }
107791
108069
  async function handleUpdateSettings(req) {
107792
108070
  const raw = await readJsonObjectBody2(req);
@@ -107796,7 +108074,7 @@ async function handleUpdateSettings(req) {
107796
108074
  const body = raw;
107797
108075
  const updates = {};
107798
108076
  if (body.searchProvider !== undefined) {
107799
- if (!SEARCH_PROVIDERS.includes(body.searchProvider)) {
108077
+ if (!isValidSearchProvider(body.searchProvider)) {
107800
108078
  return json5({ error: t2("apiError.llmSearchProviderInvalid") }, 400);
107801
108079
  }
107802
108080
  updates.searchProvider = body.searchProvider;
@@ -107831,6 +108109,7 @@ async function handleUpdateSettings(req) {
107831
108109
  updates.braveApiKeyEnc = value ? await encrypt(value) : null;
107832
108110
  }
107833
108111
  const settings = updateAgentSettings(updates);
108112
+ broadcastSettingsUpdate("llm");
107834
108113
  return json5({ settings: toSettingsDto(settings) });
107835
108114
  }
107836
108115
  function json5(data, status = 200) {
@@ -107841,103 +108120,6 @@ function json5(data, status = 200) {
107841
108120
  }
107842
108121
  });
107843
108122
  }
107844
-
107845
- // ../../apps/gateway/src/system/install-info.ts
107846
- import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
107847
- import { resolve as resolve3 } from "path";
107848
- function resolveInstallDir() {
107849
- const feDist = process.env.TMEX_FE_DIST_DIR;
107850
- if (feDist) {
107851
- return resolve3(feDist, "..", "..");
107852
- }
107853
- return process.cwd();
107854
- }
107855
- function readInstallMeta() {
107856
- const metaPath = resolve3(resolveInstallDir(), "install-meta.json");
107857
- try {
107858
- if (!existsSync4(metaPath))
107859
- return null;
107860
- return JSON.parse(readFileSync4(metaPath, "utf8"));
107861
- } catch {
107862
- return null;
107863
- }
107864
- }
107865
- function deploymentFromPlatform(platform) {
107866
- if (platform === "darwin")
107867
- return "launchd";
107868
- if (platform === "linux")
107869
- return "systemd";
107870
- return "none";
107871
- }
107872
- function getInstallInfo() {
107873
- if (!config.isProd) {
107874
- return {
107875
- installedViaCli: false,
107876
- deployment: "none",
107877
- installDir: null,
107878
- serviceName: null,
107879
- cliVersion: null,
107880
- bunPath: null
107881
- };
107882
- }
107883
- const meta3 = readInstallMeta();
107884
- if (!meta3) {
107885
- return {
107886
- installedViaCli: false,
107887
- deployment: "none",
107888
- installDir: resolveInstallDir(),
107889
- serviceName: null,
107890
- cliVersion: null,
107891
- bunPath: null
107892
- };
107893
- }
107894
- return {
107895
- installedViaCli: true,
107896
- deployment: deploymentFromPlatform(meta3.platform ?? process.platform),
107897
- installDir: meta3.installDir ?? resolveInstallDir(),
107898
- serviceName: meta3.serviceName ?? null,
107899
- cliVersion: meta3.cliVersion ?? null,
107900
- bunPath: meta3.bunPath ?? null
107901
- };
107902
- }
107903
-
107904
- // ../../apps/gateway/src/system/version.ts
107905
- import { existsSync as existsSync5, readFileSync as readFileSync5 } from "fs";
107906
- import { resolve as resolve4 } from "path";
107907
- var cachedBase;
107908
- function readRepoPackageVersion() {
107909
- const dir2 = import.meta.dir;
107910
- if (!dir2)
107911
- return null;
107912
- const path = resolve4(dir2, "../../../../packages/app/package.json");
107913
- try {
107914
- if (!existsSync5(path))
107915
- return null;
107916
- const pkg = JSON.parse(readFileSync5(path, "utf8"));
107917
- return pkg.version ?? null;
107918
- } catch {
107919
- return null;
107920
- }
107921
- }
107922
- function getBaseVersion() {
107923
- if (cachedBase !== undefined)
107924
- return cachedBase;
107925
- let base = null;
107926
- if ("0.16.5") {
107927
- base = "0.16.5";
107928
- }
107929
- if (!base && config.isProd) {
107930
- base = readInstallMeta()?.cliVersion ?? null;
107931
- }
107932
- if (!base) {
107933
- base = readRepoPackageVersion();
107934
- }
107935
- cachedBase = base ?? "unknown";
107936
- return cachedBase;
107937
- }
107938
- function getDisplayVersion() {
107939
- return formatDisplayVersion(getBaseVersion(), config.isProd);
107940
- }
107941
108123
  // ../../apps/gateway/src/system/semver.ts
107942
108124
  function parse5(input) {
107943
108125
  const match = input.trim().match(/^(\d+)\.(\d+)\.(\d+)(?:-(.+))?$/);
@@ -108346,6 +108528,7 @@ async function handleUpdateTheme(req) {
108346
108528
  updateSiteSettings({ theme });
108347
108529
  broadcastThemeChange(theme);
108348
108530
  broadcastSiteThemeUpdateS2C(theme);
108531
+ broadcastSettingsUpdate("theme");
108349
108532
  return json8({ theme, serverTimestamp });
108350
108533
  }
108351
108534
  function json8(data, status = 200) {
@@ -108355,6 +108538,126 @@ function json8(data, status = 200) {
108355
108538
  });
108356
108539
  }
108357
108540
 
108541
+ // ../../apps/gateway/src/api/tree-order.ts
108542
+ function handleTreeOrderApiRequest(req, path) {
108543
+ const treeOrderMatch = path.match(/^\/api\/devices\/([^/]+)\/tree-order$/);
108544
+ if (treeOrderMatch && req.method === "GET") {
108545
+ return handleGetTreeOrder(decodeURIComponent(treeOrderMatch[1]));
108546
+ }
108547
+ if (treeOrderMatch && req.method === "PUT") {
108548
+ return handlePutTreeOrder(req, decodeURIComponent(treeOrderMatch[1]));
108549
+ }
108550
+ const windowNameMatch = path.match(/^\/api\/devices\/([^/]+)\/windows\/([^/]+)\/name$/);
108551
+ if (windowNameMatch && req.method === "PATCH") {
108552
+ return handlePatchWindowName(req, decodeURIComponent(windowNameMatch[1]), decodeURIComponent(windowNameMatch[2]));
108553
+ }
108554
+ const paneNameMatch = path.match(/^\/api\/devices\/([^/]+)\/panes\/([^/]+)\/name$/);
108555
+ if (paneNameMatch && req.method === "PATCH") {
108556
+ return handlePatchPaneName(req, decodeURIComponent(paneNameMatch[1]), decodeURIComponent(paneNameMatch[2]));
108557
+ }
108558
+ return null;
108559
+ }
108560
+ function isStringArray(value) {
108561
+ return Array.isArray(value) && value.every((item) => typeof item === "string");
108562
+ }
108563
+ async function handleGetTreeOrder(deviceId) {
108564
+ if (!getDeviceById(deviceId)) {
108565
+ return json9({ error: t2("apiError.deviceNotFound") }, 404);
108566
+ }
108567
+ const order = getDeviceTreeOrder(deviceId);
108568
+ const names = getTreeOverlayBridge()?.getCustomNames(deviceId) ?? { windows: {}, panes: {} };
108569
+ return json9({
108570
+ deviceId,
108571
+ windows: order.windows,
108572
+ panes: order.panes,
108573
+ windowNames: names.windows,
108574
+ paneNames: names.panes
108575
+ });
108576
+ }
108577
+ async function handlePutTreeOrder(req, deviceId) {
108578
+ if (!getDeviceById(deviceId)) {
108579
+ return json9({ error: t2("apiError.deviceNotFound") }, 404);
108580
+ }
108581
+ let body;
108582
+ try {
108583
+ body = await req.json();
108584
+ } catch {
108585
+ return json9({ error: t2("apiError.invalidRequest") }, 400);
108586
+ }
108587
+ const hasWindows = body.windows !== undefined;
108588
+ const hasPanes = body.panes !== undefined;
108589
+ if (!hasWindows && !hasPanes) {
108590
+ return json9({ error: t2("apiError.invalidRequest") }, 400);
108591
+ }
108592
+ if (hasWindows && !isStringArray(body.windows)) {
108593
+ return json9({ error: t2("apiError.invalidRequest") }, 400);
108594
+ }
108595
+ if (hasPanes && (typeof body.panes !== "object" || body.panes === null || Array.isArray(body.panes) || !Object.values(body.panes).every(isStringArray))) {
108596
+ return json9({ error: t2("apiError.invalidRequest") }, 400);
108597
+ }
108598
+ const bridge = getTreeOverlayBridge();
108599
+ if (!bridge) {
108600
+ return json9({ error: "settings service not ready" }, 503);
108601
+ }
108602
+ if (hasWindows) {
108603
+ bridge.reorderWindows(deviceId, body.windows);
108604
+ }
108605
+ if (hasPanes) {
108606
+ for (const [windowId, paneIds] of Object.entries(body.panes)) {
108607
+ bridge.reorderPanes(deviceId, windowId, paneIds);
108608
+ }
108609
+ }
108610
+ const order = getDeviceTreeOrder(deviceId);
108611
+ return json9({ deviceId, windows: order.windows, panes: order.panes });
108612
+ }
108613
+ async function handlePatchWindowName(req, deviceId, windowId) {
108614
+ if (!getDeviceById(deviceId)) {
108615
+ return json9({ error: t2("apiError.deviceNotFound") }, 404);
108616
+ }
108617
+ const name24 = await readNameBody(req);
108618
+ if (name24 === null) {
108619
+ return json9({ error: t2("apiError.invalidRequest") }, 400);
108620
+ }
108621
+ const bridge = getTreeOverlayBridge();
108622
+ if (!bridge) {
108623
+ return json9({ error: "settings service not ready" }, 503);
108624
+ }
108625
+ bridge.renameWindow(deviceId, windowId, name24);
108626
+ return json9({ deviceId, windowId, name: name24.trim().slice(0, 64) });
108627
+ }
108628
+ async function handlePatchPaneName(req, deviceId, paneId) {
108629
+ if (!getDeviceById(deviceId)) {
108630
+ return json9({ error: t2("apiError.deviceNotFound") }, 404);
108631
+ }
108632
+ if (!isTmuxPaneId(paneId)) {
108633
+ return json9({ error: t2("apiError.invalidRequest") }, 400);
108634
+ }
108635
+ const name24 = await readNameBody(req);
108636
+ if (name24 === null) {
108637
+ return json9({ error: t2("apiError.invalidRequest") }, 400);
108638
+ }
108639
+ const bridge = getTreeOverlayBridge();
108640
+ if (!bridge) {
108641
+ return json9({ error: "settings service not ready" }, 503);
108642
+ }
108643
+ bridge.renamePane(deviceId, paneId, name24);
108644
+ return json9({ deviceId, paneId, name: name24.trim().slice(0, 64) });
108645
+ }
108646
+ async function readNameBody(req) {
108647
+ try {
108648
+ const body = await req.json();
108649
+ return typeof body.name === "string" ? body.name : null;
108650
+ } catch {
108651
+ return null;
108652
+ }
108653
+ }
108654
+ function json9(data, status = 200) {
108655
+ return new Response(JSON.stringify(data), {
108656
+ status,
108657
+ headers: { "Content-Type": "application/json" }
108658
+ });
108659
+ }
108660
+
108358
108661
  // ../../apps/gateway/src/db/watch.ts
108359
108662
  function createWatchRule(input) {
108360
108663
  const orm = getDb();
@@ -109453,35 +109756,35 @@ async function handleListRules(req) {
109453
109756
  if (paneId) {
109454
109757
  rules = rules.filter((rule) => rule.paneId === paneId);
109455
109758
  }
109456
- return json9({ rules: rules.map(toRuleDto) });
109759
+ return json10({ rules: rules.map(toRuleDto) });
109457
109760
  }
109458
109761
  async function handleCreateRule(req, deps) {
109459
109762
  const raw = await readJsonObjectBody3(req);
109460
109763
  if (!raw) {
109461
- return json9({ error: t2("apiError.invalidRequest") }, 400);
109764
+ return json10({ error: t2("apiError.invalidRequest") }, 400);
109462
109765
  }
109463
109766
  const body = raw;
109464
109767
  const name24 = typeof body.name === "string" ? body.name.trim() : "";
109465
109768
  if (!name24) {
109466
- return json9({ error: t2("apiError.watchNameRequired") }, 400);
109769
+ return json10({ error: t2("apiError.watchNameRequired") }, 400);
109467
109770
  }
109468
109771
  const deviceId = typeof body.deviceId === "string" ? body.deviceId.trim() : "";
109469
109772
  if (!deviceId) {
109470
- return json9({ error: t2("apiError.agentDeviceRequired") }, 400);
109773
+ return json10({ error: t2("apiError.agentDeviceRequired") }, 400);
109471
109774
  }
109472
109775
  if (!getDeviceById(deviceId)) {
109473
- return json9({ error: t2("apiError.deviceNotFound") }, 404);
109776
+ return json10({ error: t2("apiError.deviceNotFound") }, 404);
109474
109777
  }
109475
109778
  const paneId = typeof body.paneId === "string" ? body.paneId.trim() : "";
109476
109779
  if (!paneId) {
109477
- return json9({ error: t2("apiError.agentPaneRequired") }, 400);
109780
+ return json10({ error: t2("apiError.agentPaneRequired") }, 400);
109478
109781
  }
109479
109782
  if (!TRIGGER_TYPES.includes(body.triggerType)) {
109480
- return json9({ error: t2("apiError.watchTriggerTypeInvalid") }, 400);
109783
+ return json10({ error: t2("apiError.watchTriggerTypeInvalid") }, 400);
109481
109784
  }
109482
109785
  const parsed = parseRuleFields(raw);
109483
109786
  if (!parsed.ok) {
109484
- return json9({ error: parsed.error }, 400);
109787
+ return json10({ error: parsed.error }, 400);
109485
109788
  }
109486
109789
  const fields = parsed.fields;
109487
109790
  const effective = {
@@ -109494,7 +109797,7 @@ async function handleCreateRule(req, deps) {
109494
109797
  };
109495
109798
  const semanticError = validateRuleSemantics(effective);
109496
109799
  if (semanticError) {
109497
- return json9({ error: semanticError }, 400);
109800
+ return json10({ error: semanticError }, 400);
109498
109801
  }
109499
109802
  const rule = createWatchRule({
109500
109803
  name: name24,
@@ -109517,50 +109820,50 @@ async function handleCreateRule(req, deps) {
109517
109820
  cooldownSeconds: fields.cooldownSeconds
109518
109821
  });
109519
109822
  await deps.service.refreshRule(rule.id);
109520
- return json9({ rule: toRuleDto(rule), state: null }, 201);
109823
+ return json10({ rule: toRuleDto(rule), state: null }, 201);
109521
109824
  }
109522
109825
  async function handleGetRule(id) {
109523
109826
  const rule = getWatchRuleById(id);
109524
109827
  if (!rule) {
109525
- return json9({ error: t2("apiError.watchRuleNotFound") }, 404);
109828
+ return json10({ error: t2("apiError.watchRuleNotFound") }, 404);
109526
109829
  }
109527
109830
  const state = getWatchRuleState(id);
109528
- return json9({ rule: toRuleDto(rule), state: state ? toStateDto(state) : null });
109831
+ return json10({ rule: toRuleDto(rule), state: state ? toStateDto(state) : null });
109529
109832
  }
109530
109833
  async function handleUpdateRule(req, id, deps) {
109531
109834
  const existing = getWatchRuleById(id);
109532
109835
  if (!existing) {
109533
- return json9({ error: t2("apiError.watchRuleNotFound") }, 404);
109836
+ return json10({ error: t2("apiError.watchRuleNotFound") }, 404);
109534
109837
  }
109535
109838
  const raw = await readJsonObjectBody3(req);
109536
109839
  if (!raw) {
109537
- return json9({ error: t2("apiError.invalidRequest") }, 400);
109840
+ return json10({ error: t2("apiError.invalidRequest") }, 400);
109538
109841
  }
109539
109842
  const body = raw;
109540
109843
  const updates = {};
109541
109844
  if (body.name !== undefined) {
109542
109845
  const name24 = typeof body.name === "string" ? body.name.trim() : "";
109543
109846
  if (!name24) {
109544
- return json9({ error: t2("apiError.watchNameRequired") }, 400);
109847
+ return json10({ error: t2("apiError.watchNameRequired") }, 400);
109545
109848
  }
109546
109849
  updates.name = name24;
109547
109850
  }
109548
109851
  if (body.paneId !== undefined) {
109549
109852
  const paneId = typeof body.paneId === "string" ? body.paneId.trim() : "";
109550
109853
  if (!paneId) {
109551
- return json9({ error: t2("apiError.agentPaneRequired") }, 400);
109854
+ return json10({ error: t2("apiError.agentPaneRequired") }, 400);
109552
109855
  }
109553
109856
  updates.paneId = paneId;
109554
109857
  }
109555
109858
  if (body.triggerType !== undefined) {
109556
109859
  if (!TRIGGER_TYPES.includes(body.triggerType)) {
109557
- return json9({ error: t2("apiError.watchTriggerTypeInvalid") }, 400);
109860
+ return json10({ error: t2("apiError.watchTriggerTypeInvalid") }, 400);
109558
109861
  }
109559
109862
  updates.triggerType = body.triggerType;
109560
109863
  }
109561
109864
  const parsed = parseRuleFields(raw);
109562
109865
  if (!parsed.ok) {
109563
- return json9({ error: parsed.error }, 400);
109866
+ return json10({ error: parsed.error }, 400);
109564
109867
  }
109565
109868
  const fields = parsed.fields;
109566
109869
  Object.assign(updates, fields);
@@ -109574,32 +109877,32 @@ async function handleUpdateRule(req, id, deps) {
109574
109877
  };
109575
109878
  const semanticError = validateRuleSemantics(effective);
109576
109879
  if (semanticError) {
109577
- return json9({ error: semanticError }, 400);
109880
+ return json10({ error: semanticError }, 400);
109578
109881
  }
109579
109882
  const rule = updateWatchRule(id, updates);
109580
109883
  if (!rule) {
109581
- return json9({ error: t2("apiError.watchRuleNotFound") }, 404);
109884
+ return json10({ error: t2("apiError.watchRuleNotFound") }, 404);
109582
109885
  }
109583
109886
  await deps.service.refreshRule(id);
109584
109887
  const state = getWatchRuleState(id);
109585
- return json9({ rule: toRuleDto(rule), state: state ? toStateDto(state) : null });
109888
+ return json10({ rule: toRuleDto(rule), state: state ? toStateDto(state) : null });
109586
109889
  }
109587
109890
  async function handleDeleteRule(id, deps) {
109588
109891
  const existing = getWatchRuleById(id);
109589
109892
  if (!existing) {
109590
- return json9({ error: t2("apiError.watchRuleNotFound") }, 404);
109893
+ return json10({ error: t2("apiError.watchRuleNotFound") }, 404);
109591
109894
  }
109592
109895
  deleteWatchRule(id);
109593
109896
  await deps.service.removeRule(id);
109594
- return json9({ success: true });
109897
+ return json10({ success: true });
109595
109898
  }
109596
109899
  async function handleGetRuleState(id, deps) {
109597
109900
  const rule = getWatchRuleById(id);
109598
109901
  if (!rule) {
109599
- return json9({ error: t2("apiError.watchRuleNotFound") }, 404);
109902
+ return json10({ error: t2("apiError.watchRuleNotFound") }, 404);
109600
109903
  }
109601
109904
  const state = getWatchRuleState(id);
109602
- return json9({
109905
+ return json10({
109603
109906
  state: state ? toStateDto(state) : null,
109604
109907
  samples: deps.service.getSamples(id)
109605
109908
  });
@@ -109623,17 +109926,17 @@ function buildAssistPrompt(description, screen) {
109623
109926
  async function handleAssistRegex(req, deps) {
109624
109927
  const raw = await readJsonObjectBody3(req);
109625
109928
  if (!raw) {
109626
- return json9({ error: t2("apiError.invalidRequest") }, 400);
109929
+ return json10({ error: t2("apiError.invalidRequest") }, 400);
109627
109930
  }
109628
109931
  const body = raw;
109629
109932
  const description = typeof body.description === "string" ? body.description.trim() : "";
109630
109933
  if (!description) {
109631
- return json9({ error: t2("apiError.watchAssistDescriptionRequired") }, 400);
109934
+ return json10({ error: t2("apiError.watchAssistDescriptionRequired") }, 400);
109632
109935
  }
109633
109936
  let providerId = null;
109634
109937
  if (body.providerId !== undefined && body.providerId !== null) {
109635
109938
  if (typeof body.providerId !== "string" || !getLlmProviderById(body.providerId)) {
109636
- return json9({ error: t2("apiError.llmProviderNotFound") }, 400);
109939
+ return json10({ error: t2("apiError.llmProviderNotFound") }, 400);
109637
109940
  }
109638
109941
  providerId = body.providerId;
109639
109942
  }
@@ -109643,7 +109946,7 @@ async function handleAssistRegex(req, deps) {
109643
109946
  const paneId = typeof body.paneId === "string" ? body.paneId.trim() : "";
109644
109947
  if (deviceId && paneId) {
109645
109948
  if (!getDeviceById(deviceId)) {
109646
- return json9({ error: t2("apiError.deviceNotFound") }, 404);
109949
+ return json10({ error: t2("apiError.deviceNotFound") }, 404);
109647
109950
  }
109648
109951
  try {
109649
109952
  screen = await deps.captureScreen(deviceId, paneId);
@@ -109664,14 +109967,14 @@ async function handleAssistRegex(req, deps) {
109664
109967
  object3 = result.object;
109665
109968
  } catch (error51) {
109666
109969
  const detail = error51 instanceof Error ? error51.message : String(error51);
109667
- return json9({ error: t2("apiError.watchAssistModelUnavailable", { detail }) }, 502);
109970
+ return json10({ error: t2("apiError.watchAssistModelUnavailable", { detail }) }, 502);
109668
109971
  }
109669
109972
  let regex;
109670
109973
  try {
109671
109974
  regex = compileWatchPattern(object3.pattern, object3.flags);
109672
109975
  } catch (error51) {
109673
109976
  const detail = error51 instanceof Error ? error51.message : String(error51);
109674
- return json9({ error: t2("apiError.watchPatternInvalid", { detail }) }, 502);
109977
+ return json10({ error: t2("apiError.watchPatternInvalid", { detail }) }, 502);
109675
109978
  }
109676
109979
  const preview = [];
109677
109980
  if (screen) {
@@ -109685,7 +109988,7 @@ async function handleAssistRegex(req, deps) {
109685
109988
  match = regex.exec(screen);
109686
109989
  }
109687
109990
  }
109688
- return json9({
109991
+ return json10({
109689
109992
  pattern: object3.pattern,
109690
109993
  flags: object3.flags,
109691
109994
  extractGroup: object3.extractGroup >= 0 ? object3.extractGroup : 0,
@@ -109693,7 +109996,7 @@ async function handleAssistRegex(req, deps) {
109693
109996
  preview
109694
109997
  });
109695
109998
  }
109696
- function json9(data, status = 200) {
109999
+ function json10(data, status = 200) {
109697
110000
  return new Response(JSON.stringify(data), {
109698
110001
  status,
109699
110002
  headers: {
@@ -109800,11 +110103,25 @@ function normalizeSiteSettingsInput(body) {
109800
110103
  }
109801
110104
  updates.language = value;
109802
110105
  }
110106
+ if (body.disabledNotificationChannels !== undefined) {
110107
+ if (!Array.isArray(body.disabledNotificationChannels) || !body.disabledNotificationChannels.every((item) => typeof item === "string")) {
110108
+ throw new Error(t2("apiError.invalidRequest"));
110109
+ }
110110
+ updates.disabledNotificationChannels = [
110111
+ ...new Set(body.disabledNotificationChannels.map((item) => item.trim()).filter(Boolean))
110112
+ ];
110113
+ }
109803
110114
  return updates;
109804
110115
  }
109805
110116
  function handleApiRequest(req, _server) {
109806
110117
  const url2 = new URL(req.url);
109807
110118
  const path = url2.pathname;
110119
+ if (path === "/api/capabilities") {
110120
+ const capabilitiesResponse = handleCapabilitiesApiRequest(req, path);
110121
+ if (capabilitiesResponse) {
110122
+ return capabilitiesResponse;
110123
+ }
110124
+ }
109808
110125
  if (path === "/api/devices" && req.method === "GET") {
109809
110126
  return handleGetDevices();
109810
110127
  }
@@ -109826,6 +110143,12 @@ function handleApiRequest(req, _server) {
109826
110143
  if (path.match(/^\/api\/devices\/[^/]+\/test-connection$/) && req.method === "POST") {
109827
110144
  return handleTestConnection(path.split("/")[3]);
109828
110145
  }
110146
+ if (path.startsWith("/api/devices/")) {
110147
+ const treeOrderResponse = handleTreeOrderApiRequest(req, path);
110148
+ if (treeOrderResponse) {
110149
+ return treeOrderResponse;
110150
+ }
110151
+ }
109829
110152
  if (path === "/api/settings/site" && req.method === "GET") {
109830
110153
  return handleGetSiteSettings();
109831
110154
  }
@@ -109947,13 +110270,13 @@ function handleApiRequest(req, _server) {
109947
110270
  return handleGetManifest(req.method);
109948
110271
  }
109949
110272
  if (path === "/healthz" && req.method === "GET") {
109950
- return json10({
110273
+ return json11({
109951
110274
  status: "ok",
109952
110275
  restarting: runtimeController.isRestarting(),
109953
110276
  env: "development"
109954
110277
  });
109955
110278
  }
109956
- return json10({ error: t2("apiError.notFound") }, 404);
110279
+ return json11({ error: t2("apiError.notFound") }, 404);
109957
110280
  }
109958
110281
  function enrichDeviceWithRuntime(device) {
109959
110282
  const status = getDeviceRuntimeStatus(device.id);
@@ -109967,22 +110290,22 @@ function enrichDeviceWithRuntime(device) {
109967
110290
  }
109968
110291
  async function handleGetDevices() {
109969
110292
  const devices2 = getAllDevices().map(enrichDeviceWithRuntime);
109970
- return json10({ devices: devices2 });
110293
+ return json11({ devices: devices2 });
109971
110294
  }
109972
110295
  async function handleGetDevice(id) {
109973
110296
  const device = getDeviceById(id);
109974
110297
  if (!device) {
109975
- return json10({ error: t2("apiError.deviceNotFound") }, 404);
110298
+ return json11({ error: t2("apiError.deviceNotFound") }, 404);
109976
110299
  }
109977
- return json10({ device: enrichDeviceWithRuntime(device) });
110300
+ return json11({ device: enrichDeviceWithRuntime(device) });
109978
110301
  }
109979
110302
  async function handleCreateDevice(req) {
109980
110303
  const body = await req.json();
109981
110304
  if (!body.name || !body.type || !body.authMode) {
109982
- return json10({ error: t2("apiError.missingFields") }, 400);
110305
+ return json11({ error: t2("apiError.missingFields") }, 400);
109983
110306
  }
109984
110307
  if (body.type === "ssh" && !body.host && !body.sshConfigRef) {
109985
- return json10({ error: t2("apiError.sshRequiresHost") }, 400);
110308
+ return json11({ error: t2("apiError.sshRequiresHost") }, 400);
109986
110309
  }
109987
110310
  const now2 = new Date().toISOString();
109988
110311
  const device = {
@@ -110004,13 +110327,14 @@ async function handleCreateDevice(req) {
110004
110327
  updatedAt: now2
110005
110328
  };
110006
110329
  createDevice(device);
110330
+ broadcastSettingsUpdate("devices");
110007
110331
  await pushSupervisor.upsert(device.id);
110008
- return json10({ device: getDeviceById(device.id) ?? device }, 201);
110332
+ return json11({ device: getDeviceById(device.id) ?? device }, 201);
110009
110333
  }
110010
110334
  async function handleUpdateDevice(req, id) {
110011
110335
  const existing = getDeviceById(id);
110012
110336
  if (!existing) {
110013
- return json10({ error: t2("apiError.deviceNotFound") }, 404);
110337
+ return json11({ error: t2("apiError.deviceNotFound") }, 404);
110014
110338
  }
110015
110339
  const body = await req.json();
110016
110340
  const updates = {};
@@ -110038,80 +110362,85 @@ async function handleUpdateDevice(req, id) {
110038
110362
  updates.privateKeyPassphraseEnc = await encrypt(body.privateKeyPassphrase);
110039
110363
  }
110040
110364
  updateDevice(id, updates);
110365
+ broadcastSettingsUpdate("devices");
110041
110366
  if (shouldReconnectPushSupervisor(existing, updates)) {
110042
110367
  await pushSupervisor.reconnect(id);
110043
110368
  } else if (updates.defaultWorkingDir !== undefined && updates.defaultWorkingDir !== existing.defaultWorkingDir) {
110044
110369
  pushSupervisor.updateDefaultWorkingDir(id, updates.defaultWorkingDir);
110045
110370
  }
110046
110371
  const device = getDeviceById(id);
110047
- return json10({ device });
110372
+ return json11({ device });
110048
110373
  }
110049
110374
  async function handleReorderDevices(req) {
110050
110375
  const body = await req.json();
110051
110376
  if (!Array.isArray(body.deviceIds) || body.deviceIds.some((id) => typeof id !== "string")) {
110052
- return json10({ error: t2("apiError.invalidRequest") }, 400);
110377
+ return json11({ error: t2("apiError.invalidRequest") }, 400);
110053
110378
  }
110054
110379
  reorderDevices(body.deviceIds);
110055
- return json10({ devices: getAllDevices().map(enrichDeviceWithRuntime) });
110380
+ broadcastSettingsUpdate("devices");
110381
+ return json11({ devices: getAllDevices().map(enrichDeviceWithRuntime) });
110056
110382
  }
110057
110383
  async function handleDeleteDevice(id) {
110058
110384
  const existing = getDeviceById(id);
110059
110385
  if (!existing) {
110060
- return json10({ error: t2("apiError.deviceNotFound") }, 404);
110386
+ return json11({ error: t2("apiError.deviceNotFound") }, 404);
110061
110387
  }
110062
110388
  deleteDevice(id);
110389
+ broadcastSettingsUpdate("devices");
110063
110390
  pushSupervisor.remove(id);
110064
- return json10({ success: true });
110391
+ return json11({ success: true });
110065
110392
  }
110066
110393
  async function handleTestConnection(id) {
110067
110394
  return handleDeviceTestConnection(id);
110068
110395
  }
110069
110396
  async function handleGetSiteSettings() {
110070
- return json10({ settings: getSiteSettings() });
110397
+ return json11({ settings: getSiteSettings() });
110071
110398
  }
110072
110399
  async function handleUpdateSiteSettings(req) {
110073
110400
  try {
110074
110401
  const body = await req.json();
110075
110402
  const updates = normalizeSiteSettingsInput(body);
110076
110403
  const settings = updateSiteSettings(updates);
110077
- return json10({ settings });
110404
+ broadcastSettingsUpdate("site");
110405
+ return json11({ settings });
110078
110406
  } catch (err) {
110079
- return json10({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
110407
+ return json11({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
110080
110408
  }
110081
110409
  }
110082
110410
  async function handleGetTerminalShortcuts() {
110083
- return json10({ settings: getTerminalShortcutSettings() });
110411
+ return json11({ settings: getTerminalShortcutSettings() });
110084
110412
  }
110085
110413
  async function handleUpdateTerminalShortcuts(req) {
110086
110414
  try {
110087
110415
  const body = await req.json();
110088
110416
  const updates = normalizeTerminalShortcutsInput(body);
110089
110417
  const settings = updateTerminalShortcutSettings(updates);
110090
- return json10({ settings });
110418
+ broadcastSettingsUpdate("terminal-shortcuts");
110419
+ return json11({ settings });
110091
110420
  } catch (err) {
110092
- return json10({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
110421
+ return json11({ error: err instanceof Error ? err.message : t2("apiError.invalidRequest") }, 400);
110093
110422
  }
110094
110423
  }
110095
110424
  async function handleRestartGateway() {
110096
110425
  setTimeout(() => {
110097
110426
  runtimeController.requestRestart();
110098
110427
  }, 50);
110099
- return json10({
110428
+ return json11({
110100
110429
  success: true,
110101
110430
  message: t2("settings.restartScheduled")
110102
110431
  });
110103
110432
  }
110104
110433
  async function handleGetTelegramBots() {
110105
110434
  const bots = getTelegramBotsWithStats();
110106
- return json10({ bots });
110435
+ return json11({ bots });
110107
110436
  }
110108
110437
  async function handleCreateTelegramBot(req) {
110109
110438
  const body = await req.json();
110110
110439
  if (!body.name?.trim()) {
110111
- return json10({ error: t2("apiError.botNameRequired") }, 400);
110440
+ return json11({ error: t2("apiError.botNameRequired") }, 400);
110112
110441
  }
110113
110442
  if (!body.token?.trim()) {
110114
- return json10({ error: t2("apiError.botTokenRequired") }, 400);
110443
+ return json11({ error: t2("apiError.botTokenRequired") }, 400);
110115
110444
  }
110116
110445
  const now2 = new Date().toISOString();
110117
110446
  createTelegramBot({
@@ -110124,27 +110453,28 @@ async function handleCreateTelegramBot(req) {
110124
110453
  createdAt: now2,
110125
110454
  updatedAt: now2
110126
110455
  });
110456
+ broadcastSettingsUpdate("telegram");
110127
110457
  await telegramService.refresh();
110128
- return json10({ success: true }, 201);
110458
+ return json11({ success: true }, 201);
110129
110459
  }
110130
110460
  async function handleUpdateTelegramBot(req, botId) {
110131
110461
  const existing = getTelegramBotById(botId);
110132
110462
  if (!existing) {
110133
- return json10({ error: t2("apiError.botNotFound") }, 404);
110463
+ return json11({ error: t2("apiError.botNotFound") }, 404);
110134
110464
  }
110135
110465
  const body = await req.json();
110136
110466
  const updates = {};
110137
110467
  if (body.name !== undefined) {
110138
110468
  const value = body.name.trim();
110139
110469
  if (!value) {
110140
- return json10({ error: t2("apiError.botNameRequired") }, 400);
110470
+ return json11({ error: t2("apiError.botNameRequired") }, 400);
110141
110471
  }
110142
110472
  updates.name = value;
110143
110473
  }
110144
110474
  if (body.token !== undefined) {
110145
110475
  const token = body.token.trim();
110146
110476
  if (!token) {
110147
- return json10({ error: t2("apiError.botTokenRequired") }, 400);
110477
+ return json11({ error: t2("apiError.botTokenRequired") }, 400);
110148
110478
  }
110149
110479
  updates.tokenEnc = await encrypt(token);
110150
110480
  }
@@ -110155,70 +110485,74 @@ async function handleUpdateTelegramBot(req, botId) {
110155
110485
  updates.allowAuthRequests = body.allowAuthRequests;
110156
110486
  }
110157
110487
  updateTelegramBot(botId, updates);
110488
+ broadcastSettingsUpdate("telegram");
110158
110489
  await telegramService.refresh();
110159
- return json10({ success: true });
110490
+ return json11({ success: true });
110160
110491
  }
110161
110492
  async function handleDeleteTelegramBot(botId) {
110162
110493
  const existing = getTelegramBotById(botId);
110163
110494
  if (!existing) {
110164
- return json10({ error: t2("apiError.botNotFound") }, 404);
110495
+ return json11({ error: t2("apiError.botNotFound") }, 404);
110165
110496
  }
110166
110497
  deleteTelegramBot(botId);
110498
+ broadcastSettingsUpdate("telegram");
110167
110499
  await telegramService.refresh();
110168
- return json10({ success: true });
110500
+ return json11({ success: true });
110169
110501
  }
110170
110502
  async function handleListTelegramChats(botId) {
110171
110503
  const existing = getTelegramBotById(botId);
110172
110504
  if (!existing) {
110173
- return json10({ error: t2("apiError.botNotFound") }, 404);
110505
+ return json11({ error: t2("apiError.botNotFound") }, 404);
110174
110506
  }
110175
110507
  const chats = listTelegramChatsByBot(botId);
110176
- return json10({ chats });
110508
+ return json11({ chats });
110177
110509
  }
110178
110510
  async function handleApproveTelegramChat(botId, chatId) {
110179
110511
  const existing = getTelegramBotById(botId);
110180
110512
  if (!existing) {
110181
- return json10({ error: t2("apiError.botNotFound") }, 404);
110513
+ return json11({ error: t2("apiError.botNotFound") }, 404);
110182
110514
  }
110183
110515
  const chat = approveTelegramChat(botId, chatId);
110184
110516
  if (!chat) {
110185
- return json10({ error: t2("apiError.chatNotFound") }, 404);
110517
+ return json11({ error: t2("apiError.chatNotFound") }, 404);
110186
110518
  }
110519
+ broadcastSettingsUpdate("telegram");
110187
110520
  const settings = getSiteSettings();
110188
110521
  await telegramService.sendTestMessage(botId, chatId, t2("telegram.approveMessageTemplate", {
110189
110522
  botName: existing.name,
110190
110523
  time: new Date().toLocaleString(toBCP47(settings.language))
110191
110524
  }));
110192
- return json10({ chat });
110525
+ return json11({ chat });
110193
110526
  }
110194
110527
  async function handleDeleteTelegramChat(botId, chatId) {
110195
110528
  const existing = getTelegramBotById(botId);
110196
110529
  if (!existing) {
110197
- return json10({ error: t2("apiError.botNotFound") }, 404);
110530
+ return json11({ error: t2("apiError.botNotFound") }, 404);
110198
110531
  }
110199
110532
  deleteTelegramChat(botId, chatId);
110200
- return json10({ success: true });
110533
+ broadcastSettingsUpdate("telegram");
110534
+ return json11({ success: true });
110201
110535
  }
110202
110536
  async function handleTestTelegramChat(botId, chatId) {
110203
110537
  const bot = getTelegramBotById(botId);
110204
110538
  if (!bot) {
110205
- return json10({ error: t2("apiError.botNotFound") }, 404);
110539
+ return json11({ error: t2("apiError.botNotFound") }, 404);
110206
110540
  }
110207
110541
  const settings = getSiteSettings();
110208
110542
  await telegramService.sendTestMessage(botId, chatId, t2("telegram.testMessageTemplate", {
110209
110543
  siteName: settings.siteName,
110210
110544
  time: new Date().toLocaleString(toBCP47(settings.language))
110211
110545
  }));
110212
- return json10({ success: true });
110546
+ return json11({ success: true });
110213
110547
  }
110214
110548
  async function handleGetWeixinAccounts() {
110215
110549
  const accounts = getWeixinAccountsWithStats();
110216
- return json10({ accounts });
110550
+ return json11({ accounts });
110217
110551
  }
110218
110552
  async function handleCreateWeixinAccount(req) {
110219
110553
  const body = await req.json();
110220
110554
  if (!body.name?.trim()) {
110221
- return json10({ error: t2("weixin.accountNameRequired") }, 400);
110555
+ return json11({ error: t2("weixin.accountNameRequired") }, 400);
110222
110556
  }
110223
110557
  const now2 = new Date().toISOString();
110224
110558
  const id = v4_default();
@@ -110235,19 +110569,20 @@ async function handleCreateWeixinAccount(req) {
110235
110569
  createdAt: now2,
110236
110570
  updatedAt: now2
110237
110571
  });
110238
- return json10({ success: true, accountId: id }, 201);
110572
+ broadcastSettingsUpdate("weixin");
110573
+ return json11({ success: true, accountId: id }, 201);
110239
110574
  }
110240
110575
  async function handleUpdateWeixinAccount(req, accountId) {
110241
110576
  const existing = getWeixinAccountById(accountId);
110242
110577
  if (!existing) {
110243
- return json10({ error: t2("weixin.accountNotFound") }, 404);
110578
+ return json11({ error: t2("weixin.accountNotFound") }, 404);
110244
110579
  }
110245
110580
  const body = await req.json();
110246
110581
  const updates = {};
110247
110582
  if (body.name !== undefined) {
110248
110583
  const value = body.name.trim();
110249
110584
  if (!value) {
110250
- return json10({ error: t2("weixin.accountNameRequired") }, 400);
110585
+ return json11({ error: t2("weixin.accountNameRequired") }, 400);
110251
110586
  }
110252
110587
  updates.name = value;
110253
110588
  }
@@ -110258,54 +110593,57 @@ async function handleUpdateWeixinAccount(req, accountId) {
110258
110593
  updates.allowAuthRequests = body.allowAuthRequests;
110259
110594
  }
110260
110595
  updateWeixinAccount(accountId, updates);
110596
+ broadcastSettingsUpdate("weixin");
110261
110597
  await weixinService.refresh();
110262
- return json10({ success: true });
110598
+ return json11({ success: true });
110263
110599
  }
110264
110600
  async function handleDeleteWeixinAccount(accountId) {
110265
110601
  const existing = getWeixinAccountById(accountId);
110266
110602
  if (!existing) {
110267
- return json10({ error: t2("weixin.accountNotFound") }, 404);
110603
+ return json11({ error: t2("weixin.accountNotFound") }, 404);
110268
110604
  }
110269
110605
  deleteWeixinAccount(accountId);
110606
+ broadcastSettingsUpdate("weixin");
110270
110607
  await weixinService.refresh();
110271
- return json10({ success: true });
110608
+ return json11({ success: true });
110272
110609
  }
110273
110610
  async function handleStartWeixinLogin(accountId) {
110274
110611
  const existing = getWeixinAccountById(accountId);
110275
110612
  if (!existing) {
110276
- return json10({ error: t2("weixin.accountNotFound") }, 404);
110613
+ return json11({ error: t2("weixin.accountNotFound") }, 404);
110277
110614
  }
110278
110615
  try {
110279
110616
  const result = await weixinService.startLogin(accountId);
110280
- return json10(result);
110617
+ return json11(result);
110281
110618
  } catch (err) {
110282
- return json10({ error: err instanceof Error ? err.message : t2("weixin.loginFailed") }, 502);
110619
+ return json11({ error: err instanceof Error ? err.message : t2("weixin.loginFailed") }, 502);
110283
110620
  }
110284
110621
  }
110285
110622
  async function handleGetWeixinLoginStatus(accountId) {
110286
110623
  const existing = getWeixinAccountById(accountId);
110287
110624
  if (!existing) {
110288
- return json10({ error: t2("weixin.accountNotFound") }, 404);
110625
+ return json11({ error: t2("weixin.accountNotFound") }, 404);
110289
110626
  }
110290
- return json10(weixinService.getLoginStatus(accountId));
110627
+ return json11(weixinService.getLoginStatus(accountId));
110291
110628
  }
110292
110629
  async function handleListWeixinUsers(accountId) {
110293
110630
  const existing = getWeixinAccountById(accountId);
110294
110631
  if (!existing) {
110295
- return json10({ error: t2("weixin.accountNotFound") }, 404);
110632
+ return json11({ error: t2("weixin.accountNotFound") }, 404);
110296
110633
  }
110297
110634
  const users = listWeixinUsersByAccount(accountId);
110298
- return json10({ users });
110635
+ return json11({ users });
110299
110636
  }
110300
110637
  async function handleApproveWeixinUser(accountId, userId) {
110301
110638
  const existing = getWeixinAccountById(accountId);
110302
110639
  if (!existing) {
110303
- return json10({ error: t2("weixin.accountNotFound") }, 404);
110640
+ return json11({ error: t2("weixin.accountNotFound") }, 404);
110304
110641
  }
110305
110642
  const user = approveWeixinUser(accountId, userId);
110306
110643
  if (!user) {
110307
- return json10({ error: t2("weixin.userNotFound") }, 404);
110644
+ return json11({ error: t2("weixin.userNotFound") }, 404);
110308
110645
  }
110646
+ broadcastSettingsUpdate("weixin");
110309
110647
  const settings = getSiteSettings();
110310
110648
  try {
110311
110649
  await weixinService.sendTestMessage(accountId, userId, t2("weixin.approveMessageTemplate", {
@@ -110315,12 +110653,12 @@ async function handleApproveWeixinUser(accountId, userId) {
110315
110653
  } catch (err) {
110316
110654
  console.error("[weixin] approve ack failed:", err);
110317
110655
  }
110318
- return json10({ user });
110656
+ return json11({ user });
110319
110657
  }
110320
110658
  async function handleTestWeixinUser(accountId, userId) {
110321
110659
  const existing = getWeixinAccountById(accountId);
110322
110660
  if (!existing) {
110323
- return json10({ error: t2("weixin.accountNotFound") }, 404);
110661
+ return json11({ error: t2("weixin.accountNotFound") }, 404);
110324
110662
  }
110325
110663
  const settings = getSiteSettings();
110326
110664
  try {
@@ -110329,14 +110667,14 @@ async function handleTestWeixinUser(accountId, userId) {
110329
110667
  time: new Date().toLocaleString(toBCP47(settings.language))
110330
110668
  }));
110331
110669
  } catch (err) {
110332
- return json10({ error: err instanceof Error ? err.message : t2("weixin.testMessageFailed") }, 400);
110670
+ return json11({ error: err instanceof Error ? err.message : t2("weixin.testMessageFailed") }, 400);
110333
110671
  }
110334
- return json10({ success: true });
110672
+ return json11({ success: true });
110335
110673
  }
110336
110674
  async function handleTestWeixinAccount(accountId) {
110337
110675
  const existing = getWeixinAccountById(accountId);
110338
110676
  if (!existing) {
110339
- return json10({ error: t2("weixin.accountNotFound") }, 404);
110677
+ return json11({ error: t2("weixin.accountNotFound") }, 404);
110340
110678
  }
110341
110679
  const settings = getSiteSettings();
110342
110680
  try {
@@ -110345,26 +110683,27 @@ async function handleTestWeixinAccount(accountId) {
110345
110683
  time: new Date().toLocaleString(toBCP47(settings.language))
110346
110684
  }));
110347
110685
  } catch (err) {
110348
- return json10({ error: err instanceof Error ? err.message : t2("weixin.testMessageFailed") }, 400);
110686
+ return json11({ error: err instanceof Error ? err.message : t2("weixin.testMessageFailed") }, 400);
110349
110687
  }
110350
- return json10({ success: true });
110688
+ return json11({ success: true });
110351
110689
  }
110352
110690
  async function handleDeleteWeixinUser(accountId, userId) {
110353
110691
  const existing = getWeixinAccountById(accountId);
110354
110692
  if (!existing) {
110355
- return json10({ error: t2("weixin.accountNotFound") }, 404);
110693
+ return json11({ error: t2("weixin.accountNotFound") }, 404);
110356
110694
  }
110357
110695
  deleteWeixinUser(accountId, userId);
110358
- return json10({ success: true });
110696
+ broadcastSettingsUpdate("weixin");
110697
+ return json11({ success: true });
110359
110698
  }
110360
110699
  async function handleGetWebhooks() {
110361
110700
  const webhooks = getAllWebhookEndpoints();
110362
- return json10({ webhooks });
110701
+ return json11({ webhooks });
110363
110702
  }
110364
110703
  async function handleCreateWebhook(req) {
110365
110704
  const body = await req.json();
110366
110705
  if (!body.url || !body.secret) {
110367
- return json10({ error: t2("apiError.urlAndSecretRequired") }, 400);
110706
+ return json11({ error: t2("apiError.urlAndSecretRequired") }, 400);
110368
110707
  }
110369
110708
  const now2 = new Date().toISOString();
110370
110709
  const endpoint = {
@@ -110377,11 +110716,13 @@ async function handleCreateWebhook(req) {
110377
110716
  updatedAt: now2
110378
110717
  };
110379
110718
  createWebhookEndpoint(endpoint);
110380
- return json10({ webhook: endpoint }, 201);
110719
+ broadcastSettingsUpdate("webhooks");
110720
+ return json11({ webhook: endpoint }, 201);
110381
110721
  }
110382
110722
  async function handleDeleteWebhook(id) {
110383
110723
  deleteWebhookEndpoint(id);
110384
- return json10({ success: true });
110724
+ broadcastSettingsUpdate("webhooks");
110725
+ return json11({ success: true });
110385
110726
  }
110386
110727
  async function handleGetManifest(method) {
110387
110728
  const settings = getSiteSettings();
@@ -110420,7 +110761,7 @@ function manifestJson(data, method) {
110420
110761
  }
110421
110762
  });
110422
110763
  }
110423
- function json10(data, status = 200, headers = {}) {
110764
+ function json11(data, status = 200, headers = {}) {
110424
110765
  return new Response(JSON.stringify(data), {
110425
110766
  status,
110426
110767
  headers: {
@@ -110496,8 +110837,7 @@ function createBorshClientState() {
110496
110837
  maxFrameBytes: exports_ws_borsh.DEFAULT_MAX_FRAME_BYTES,
110497
110838
  chunkReassembler: new exports_ws_borsh.ChunkReassembler,
110498
110839
  selectedPanes: {},
110499
- subscribedPanes: {},
110500
- pendingHistoryFetches: new Map
110840
+ subscribedPanes: {}
110501
110841
  };
110502
110842
  }
110503
110843
  function encodeTermOutput(params, seq) {
@@ -110929,7 +111269,7 @@ class SwitchBarrier {
110929
111269
  }
110930
111270
  pending.callbacks.onAckSent?.();
110931
111271
  }
110932
- sendTermHistory(ws, deviceId, paneId, historyData, alternateScreen) {
111272
+ sendTermHistory(ws, deviceId, paneId, historyData, alternateScreen, modes) {
110933
111273
  const pending = this.getPending(ws, deviceId);
110934
111274
  if (!pending)
110935
111275
  return;
@@ -110956,6 +111296,7 @@ class SwitchBarrier {
110956
111296
  selectToken: context2.selectToken,
110957
111297
  encoding: 2,
110958
111298
  alternateScreen,
111299
+ modes,
110959
111300
  data: historyData
110960
111301
  }, borshState.seqGen, borshState.maxFrameBytes);
110961
111302
  sendToClient(ws, historyMessages);
@@ -111151,6 +111492,7 @@ class WebSocketServer {
111151
111492
  currentTheme = null;
111152
111493
  themeSignalLast = new Map;
111153
111494
  lastThemeTimestamp = 0n;
111495
+ lastSettingsTimestamp = 0n;
111154
111496
  pendingTmuxTheme = null;
111155
111497
  themeApplyInFlight = false;
111156
111498
  lastBroadcastTheme = new Map;
@@ -111197,8 +111539,8 @@ class WebSocketServer {
111197
111539
  onTerminalOutput: (paneId, data) => {
111198
111540
  this.broadcastTerminalOutput(deviceId, paneId, data);
111199
111541
  },
111200
- onTerminalHistory: (paneId, data, alternateScreen) => {
111201
- this.broadcastTerminalHistory(deviceId, paneId, data, alternateScreen);
111542
+ onTerminalHistory: (paneId, data, alternateScreen, modes) => {
111543
+ this.broadcastTerminalHistory(deviceId, paneId, data, alternateScreen, modes);
111202
111544
  },
111203
111545
  onClipboardWrite: (paneId, text3) => {
111204
111546
  this.broadcastClipboardWrite(deviceId, paneId, text3);
@@ -111321,7 +111663,6 @@ class WebSocketServer {
111321
111663
  entry.clients.delete(ws);
111322
111664
  delete ws.data.borshState.selectedPanes[deviceId];
111323
111665
  delete ws.data.borshState.subscribedPanes[deviceId];
111324
- this.clearPendingHistoryFetches(ws, deviceId);
111325
111666
  if (entry.clients.size === 0) {
111326
111667
  console.log(`[ws] no more clients for device ${deviceId}, disconnecting`);
111327
111668
  this.releaseConnectionEntry(deviceId, entry);
@@ -111400,7 +111741,7 @@ class WebSocketServer {
111400
111741
  }
111401
111742
  case exports_ws_borsh.KIND_TMUX_RENAME_WINDOW: {
111402
111743
  const decoded = exports_ws_borsh.decodePayload(exports_ws_borsh.schema.TmuxRenameWindowSchema, payload);
111403
- this.handleRenameWindow(decoded.deviceId, decoded.windowId, decoded.name);
111744
+ this.renameWindow(decoded.deviceId, decoded.windowId, decoded.name);
111404
111745
  return;
111405
111746
  }
111406
111747
  case exports_ws_borsh.KIND_TMUX_SET_WINDOW_STYLE: {
@@ -111410,12 +111751,12 @@ class WebSocketServer {
111410
111751
  }
111411
111752
  case exports_ws_borsh.KIND_TMUX_REORDER_WINDOWS: {
111412
111753
  const decoded = exports_ws_borsh.decodePayload(exports_ws_borsh.schema.TmuxReorderWindowsSchema, payload);
111413
- this.handleReorderWindows(decoded.deviceId, decoded.windowIds);
111754
+ this.reorderWindows(decoded.deviceId, decoded.windowIds);
111414
111755
  return;
111415
111756
  }
111416
111757
  case exports_ws_borsh.KIND_TMUX_REORDER_PANES: {
111417
111758
  const decoded = exports_ws_borsh.decodePayload(exports_ws_borsh.schema.TmuxReorderPanesSchema, payload);
111418
- this.handleReorderPanes(decoded.deviceId, decoded.windowId, decoded.paneIds);
111759
+ this.reorderPanes(decoded.deviceId, decoded.windowId, decoded.paneIds);
111419
111760
  return;
111420
111761
  }
111421
111762
  case exports_ws_borsh.KIND_TMUX_SUBSCRIBE_PANES: {
@@ -111450,7 +111791,7 @@ class WebSocketServer {
111450
111791
  }
111451
111792
  case exports_ws_borsh.KIND_TMUX_RENAME_PANE: {
111452
111793
  const decoded = exports_ws_borsh.decodePayload(exports_ws_borsh.schema.TmuxRenamePaneSchema, payload);
111453
- this.handleRenamePane(decoded.deviceId, decoded.paneId, decoded.name);
111794
+ this.renamePane(decoded.deviceId, decoded.paneId, decoded.name);
111454
111795
  return;
111455
111796
  }
111456
111797
  case exports_ws_borsh.KIND_TMUX_MOVE_PANE: {
@@ -111522,11 +111863,11 @@ class WebSocketServer {
111522
111863
  agentWsHub.registerClient(ws);
111523
111864
  const helloS2C = {
111524
111865
  serverImpl: "tmex-gateway",
111525
- serverVersion: "0.1.0",
111866
+ serverVersion: getDisplayVersion(),
111526
111867
  selectedVersion: exports_ws_borsh.CURRENT_VERSION,
111527
111868
  maxFrameBytes: serverMaxFrameBytes,
111528
111869
  heartbeatIntervalMs: 15000,
111529
- capabilities: ["tmex-ws-borsh-v1", "tmex-agent-v1", "tmex-split-v1"]
111870
+ capabilities: [...GATEWAY_CAPABILITIES]
111530
111871
  };
111531
111872
  const payloadBytes = exports_ws_borsh.encodePayload(exports_ws_borsh.schema.HelloS2CSchema, helloS2C);
111532
111873
  this.sendEnvelope(ws, exports_ws_borsh.KIND_HELLO_S2C, payloadBytes);
@@ -111624,20 +111965,11 @@ class WebSocketServer {
111624
111965
  }
111625
111966
  delete ws.data.borshState.selectedPanes[deviceId];
111626
111967
  delete ws.data.borshState.subscribedPanes[deviceId];
111627
- this.clearPendingHistoryFetches(ws, deviceId);
111628
111968
  const disconnectedPayload = exports_ws_borsh.encodePayload(exports_ws_borsh.schema.DeviceDisconnectedSchema, {
111629
111969
  deviceId
111630
111970
  });
111631
111971
  this.sendEnvelope(ws, exports_ws_borsh.KIND_DEVICE_DISCONNECTED, disconnectedPayload);
111632
111972
  }
111633
- clearPendingHistoryFetches(ws, deviceId) {
111634
- const prefix = `${deviceId}:`;
111635
- for (const key of ws.data.borshState.pendingHistoryFetches.keys()) {
111636
- if (key.startsWith(prefix)) {
111637
- ws.data.borshState.pendingHistoryFetches.delete(key);
111638
- }
111639
- }
111640
- }
111641
111973
  handleTmuxSelect(ws, data) {
111642
111974
  const deviceId = data.deviceId;
111643
111975
  const entry = this.connections.get(deviceId);
@@ -111765,7 +112097,7 @@ class WebSocketServer {
111765
112097
  return;
111766
112098
  entry.runtime.closePane(paneId);
111767
112099
  }
111768
- handleRenamePane(deviceId, paneId, name24) {
112100
+ renamePane(deviceId, paneId, name24) {
111769
112101
  if (!isTmuxPaneId(paneId))
111770
112102
  return;
111771
112103
  const trimmed = name24.trim().slice(0, 64);
@@ -111777,6 +112109,7 @@ class WebSocketServer {
111777
112109
  } else {
111778
112110
  this.paneCustomNames.set(deviceId, new Map([[paneId, trimmed]]));
111779
112111
  }
112112
+ this.broadcastSettingsUpdate("tree-order");
111780
112113
  const entry = this.connections.get(deviceId);
111781
112114
  if (!entry?.lastSnapshot)
111782
112115
  return;
@@ -111805,7 +112138,7 @@ class WebSocketServer {
111805
112138
  return;
111806
112139
  entry.runtime.movePane(srcPaneId, dstPaneId, resolved);
111807
112140
  }
111808
- handleRenameWindow(deviceId, windowId, name24) {
112141
+ renameWindow(deviceId, windowId, name24) {
111809
112142
  const trimmed = name24.trim().slice(0, 64);
111810
112143
  const names = this.windowCustomNames.get(deviceId);
111811
112144
  if (!trimmed) {
@@ -111815,11 +112148,18 @@ class WebSocketServer {
111815
112148
  } else {
111816
112149
  this.windowCustomNames.set(deviceId, new Map([[windowId, trimmed]]));
111817
112150
  }
112151
+ this.broadcastSettingsUpdate("tree-order");
111818
112152
  const entry = this.connections.get(deviceId);
111819
112153
  if (!entry?.lastSnapshot)
111820
112154
  return;
111821
112155
  this.sendSnapshotToClients(entry, entry.lastSnapshot);
111822
112156
  }
112157
+ getCustomNames(deviceId) {
112158
+ return {
112159
+ windows: Object.fromEntries(this.windowCustomNames.get(deviceId) ?? []),
112160
+ panes: Object.fromEntries(this.paneCustomNames.get(deviceId) ?? [])
112161
+ };
112162
+ }
111823
112163
  handleSetWindowStyle(deviceId, style) {
111824
112164
  const entry = this.connections.get(deviceId);
111825
112165
  if (!entry)
@@ -111848,6 +112188,7 @@ class WebSocketServer {
111848
112188
  updateSiteSettings({ theme: themeName });
111849
112189
  this.scheduleTmuxThemeApply(themeName);
111850
112190
  this.broadcastSiteThemeUpdateS2C(themeName);
112191
+ this.broadcastSettingsUpdate("theme");
111851
112192
  }
111852
112193
  scheduleTmuxThemeApply(theme) {
111853
112194
  this.pendingTmuxTheme = theme;
@@ -111885,6 +112226,21 @@ class WebSocketServer {
111885
112226
  this.sendEnvelope(client, exports_ws_borsh.KIND_SITE_THEME_UPDATE, payloadBytes);
111886
112227
  }
111887
112228
  }
112229
+ broadcastSettingsUpdate(namespace) {
112230
+ const now2 = BigInt(Date.now());
112231
+ if (now2 <= this.lastSettingsTimestamp) {
112232
+ this.lastSettingsTimestamp += 1n;
112233
+ } else {
112234
+ this.lastSettingsTimestamp = now2;
112235
+ }
112236
+ const payloadBytes = exports_ws_borsh.encodePayload(exports_ws_borsh.schema.SettingsUpdateS2CSchema, {
112237
+ namespace,
112238
+ serverTimestamp: this.lastSettingsTimestamp
112239
+ });
112240
+ for (const client of this.connectedClients) {
112241
+ this.sendEnvelope(client, exports_ws_borsh.KIND_SETTINGS_UPDATE, payloadBytes);
112242
+ }
112243
+ }
111888
112244
  async handleSiteThemeChange(theme) {
111889
112245
  if (theme !== "dark" && theme !== "light") {
111890
112246
  return;
@@ -111930,15 +112286,17 @@ class WebSocketServer {
111930
112286
  }
111931
112287
  }
111932
112288
  }
111933
- handleReorderWindows(deviceId, windowIds) {
112289
+ reorderWindows(deviceId, windowIds) {
111934
112290
  setWindowOrder(deviceId, windowIds);
112291
+ this.broadcastSettingsUpdate("tree-order");
111935
112292
  const entry = this.connections.get(deviceId);
111936
112293
  if (!entry?.lastSnapshot)
111937
112294
  return;
111938
112295
  this.sendSnapshotToClients(entry, entry.lastSnapshot);
111939
112296
  }
111940
- handleReorderPanes(deviceId, windowId, paneIds) {
112297
+ reorderPanes(deviceId, windowId, paneIds) {
111941
112298
  setPaneOrder(deviceId, windowId, paneIds);
112299
+ this.broadcastSettingsUpdate("tree-order");
111942
112300
  const entry = this.connections.get(deviceId);
111943
112301
  if (!entry?.lastSnapshot)
111944
112302
  return;
@@ -111966,10 +112324,21 @@ class WebSocketServer {
111966
112324
  const entry = this.connections.get(deviceId);
111967
112325
  if (!entry || !isTmuxPaneId(paneId))
111968
112326
  return;
111969
- ws.data.borshState.pendingHistoryFetches.set(`${deviceId}:${paneId}`, requestToken);
111970
- entry.runtime.requestPaneHistory(paneId).catch((error51) => {
112327
+ entry.runtime.fetchPaneHistory(paneId).then((captured) => {
112328
+ if (!captured)
112329
+ return;
112330
+ const payloadBytes = exports_ws_borsh.encodePayload(exports_ws_borsh.schema.TermHistorySchema, {
112331
+ deviceId,
112332
+ paneId,
112333
+ selectToken: requestToken,
112334
+ encoding: 1,
112335
+ alternateScreen: captured.alternateScreen,
112336
+ modes: captured.modes,
112337
+ data: new TextEncoder().encode(captured.data)
112338
+ });
112339
+ this.sendChunked(ws, exports_ws_borsh.KIND_TERM_HISTORY, payloadBytes);
112340
+ }).catch((error51) => {
111971
112341
  console.warn(`[ws] fetch pane history failed on ${deviceId}/${paneId}:`, error51);
111972
- ws.data.borshState.pendingHistoryFetches.delete(`${deviceId}:${paneId}`);
111973
112342
  });
111974
112343
  }
111975
112344
  handleResizePaneById(deviceId, paneId, cols, rows) {
@@ -112226,36 +112595,20 @@ class WebSocketServer {
112226
112595
  this.sendEnvelope(client, exports_ws_borsh.KIND_CLIPBOARD_WRITE, payloadBytes);
112227
112596
  }
112228
112597
  }
112229
- broadcastTerminalHistory(deviceId, paneId, data, alternateScreen) {
112598
+ broadcastTerminalHistory(deviceId, paneId, data, alternateScreen, modes) {
112230
112599
  const entry = this.connections.get(deviceId);
112231
112600
  if (!entry)
112232
112601
  return;
112233
112602
  const historyBytes = new TextEncoder().encode(data);
112234
- const fetchKey = `${deviceId}:${paneId}`;
112235
112603
  for (const client of entry.clients) {
112236
112604
  const txPaneId = switchBarrier.getTransactionPaneId(client, deviceId);
112237
112605
  if (txPaneId !== null && txPaneId === paneId) {
112238
- switchBarrier.sendTermHistory(client, deviceId, paneId, historyBytes, alternateScreen);
112606
+ switchBarrier.sendTermHistory(client, deviceId, paneId, historyBytes, alternateScreen, modes);
112239
112607
  continue;
112240
112608
  }
112241
112609
  if (client.data.borshState.selectedPanes[deviceId] === paneId) {
112242
- switchBarrier.sendTermHistory(client, deviceId, paneId, historyBytes, alternateScreen);
112243
- continue;
112244
- }
112245
- const requestToken = client.data.borshState.pendingHistoryFetches.get(fetchKey);
112246
- if (!requestToken) {
112247
- continue;
112610
+ switchBarrier.sendTermHistory(client, deviceId, paneId, historyBytes, alternateScreen, modes);
112248
112611
  }
112249
- client.data.borshState.pendingHistoryFetches.delete(fetchKey);
112250
- const payloadBytes = exports_ws_borsh.encodePayload(exports_ws_borsh.schema.TermHistorySchema, {
112251
- deviceId,
112252
- paneId,
112253
- selectToken: requestToken,
112254
- encoding: 1,
112255
- alternateScreen,
112256
- data: historyBytes
112257
- });
112258
- this.sendChunked(client, exports_ws_borsh.KIND_TERM_HISTORY, payloadBytes);
112259
112612
  }
112260
112613
  }
112261
112614
  broadcastError(deviceId, err) {
@@ -112387,6 +112740,16 @@ async function createGatewayRuntime(options = {}) {
112387
112740
  }, (theme) => {
112388
112741
  wsServer.broadcastSiteThemeUpdateS2C(theme);
112389
112742
  });
112743
+ registerSettingsBroadcaster((namespace) => {
112744
+ wsServer.broadcastSettingsUpdate(namespace);
112745
+ });
112746
+ registerTreeOverlayBridge({
112747
+ reorderWindows: (deviceId, windowIds) => wsServer.reorderWindows(deviceId, windowIds),
112748
+ reorderPanes: (deviceId, windowId, paneIds) => wsServer.reorderPanes(deviceId, windowId, paneIds),
112749
+ renameWindow: (deviceId, windowId, name24) => wsServer.renameWindow(deviceId, windowId, name24),
112750
+ renamePane: (deviceId, paneId, name24) => wsServer.renamePane(deviceId, paneId, name24),
112751
+ getCustomNames: (deviceId) => wsServer.getCustomNames(deviceId)
112752
+ });
112390
112753
  await telegramService.refresh();
112391
112754
  await weixinService.refresh();
112392
112755
  await pushSupervisor.start();
@@ -112435,6 +112798,8 @@ async function createGatewayRuntime(options = {}) {
112435
112798
  async stop() {
112436
112799
  connectionAlertNotifier.setBroadcaster(null);
112437
112800
  registerThemeBroadcaster(null);
112801
+ registerSettingsBroadcaster(null);
112802
+ registerTreeOverlayBridge(null);
112438
112803
  wsServer.closeAll();
112439
112804
  await watchService.stop();
112440
112805
  await agentSupervisor.stop();