wind-agent-cli 1.37.2 → 1.37.3

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 (374) hide show
  1. package/LICENSE +718 -718
  2. package/README.md +1 -1
  3. package/dist/api-docs.html +901 -901
  4. package/dist/assets/{CodeEditor-B2Wf7Fg0.js → CodeEditor-BqmxpZ_D.js} +1 -1
  5. package/dist/assets/{FileTree-N2IkkHlv.js → FileTree-DLE7Ioai.js} +1 -1
  6. package/dist/assets/{GitPanel-DZCeuFZ2.js → GitPanel-f8RMRa8D.js} +1 -1
  7. package/dist/assets/{MarkdownPreview-C0tqYnVu.js → MarkdownPreview-93h_1tJa.js} +1 -1
  8. package/dist/assets/{PluginTabContent-Bnuf-fb8.js → PluginTabContent-DU1jjkXP.js} +1 -1
  9. package/dist/assets/{Shell-BfFnMZ-f.js → Shell-CN_2MRIM.js} +1 -1
  10. package/dist/assets/{StandaloneShell-QmyYwNAl.js → StandaloneShell-D0avS8UE.js} +1 -1
  11. package/dist/assets/{TaskMasterPanel-fMsmvN9L.js → TaskMasterPanel-Cpf1Q5Ph.js} +3 -3
  12. package/dist/assets/{abnfDiagram-N423BO3Z-DjPU7HLs.js → abnfDiagram-N423BO3Z-p3aJIm4T.js} +1 -1
  13. package/dist/assets/{arc-Pdq6Tvr3.js → arc-CTeud6Fl.js} +1 -1
  14. package/dist/assets/{architectureDiagram-T3A2C74G-D0JnT0Vh.js → architectureDiagram-T3A2C74G-CG7kRCJU.js} +1 -1
  15. package/dist/assets/{blockDiagram-VBNYF7ZC-DpaEf3mv.js → blockDiagram-VBNYF7ZC-oSjQ1SgE.js} +1 -1
  16. package/dist/assets/{c4Diagram-5PPSVZJV-MpqlPGFi.js → c4Diagram-5PPSVZJV-RkzF0DJV.js} +1 -1
  17. package/dist/assets/channel-BfQbarn9.js +1 -0
  18. package/dist/assets/{chunk-2GRJ4B5K-DoSdNo8u.js → chunk-2GRJ4B5K-B07uZ07O.js} +1 -1
  19. package/dist/assets/{chunk-2Q5K7J3B-CE7I3uMn.js → chunk-2Q5K7J3B-DNSfO7xS.js} +1 -1
  20. package/dist/assets/{chunk-5RXB4S5H-B4UQ8snD.js → chunk-5RXB4S5H-BTeYcgyb.js} +1 -1
  21. package/dist/assets/{chunk-5VM5RSS4-B55XgqHZ.js → chunk-5VM5RSS4-B7vZV0zC.js} +1 -1
  22. package/dist/assets/{chunk-6Q2QTUOP-BS1KpefW.js → chunk-6Q2QTUOP-DuS_xUGG.js} +1 -1
  23. package/dist/assets/{chunk-GF5L2VYU-DkEPedJP.js → chunk-GF5L2VYU-CKLZ3iVD.js} +1 -1
  24. package/dist/assets/{chunk-JWPE2WC7-DOtkNhMm.js → chunk-JWPE2WC7-DD2hcXvs.js} +1 -1
  25. package/dist/assets/{chunk-KBJHAD2P-BXLjviqM.js → chunk-KBJHAD2P-Cz-l38qp.js} +1 -1
  26. package/dist/assets/{chunk-RYQCIY6F-C50fjNNz.js → chunk-RYQCIY6F-DGFIU1YM.js} +1 -1
  27. package/dist/assets/{chunk-XXDRQBXY-QBYqCkb7.js → chunk-XXDRQBXY-CmfzoYFl.js} +1 -1
  28. package/dist/assets/classDiagram-JCYQIIEL-Bpfe3kq7.js +1 -0
  29. package/dist/assets/classDiagram-v2-OCEON4UE-Bpfe3kq7.js +1 -0
  30. package/dist/assets/{code-xml-VK1YfShP.js → code-xml-CLpHzEOP.js} +1 -1
  31. package/dist/assets/{cose-bilkent-JH36ORCC-DrVTo1Nh.js → cose-bilkent-JH36ORCC-DlFcLXCB.js} +1 -1
  32. package/dist/assets/{cynefin-VYW2F7L2-DnJa_OlQ.js → cynefin-VYW2F7L2-P1HecReO.js} +1 -1
  33. package/dist/assets/{cynefinDiagram-MW4NZA55-CCxBjw9E.js → cynefinDiagram-MW4NZA55-COgduz5P.js} +1 -1
  34. package/dist/assets/{dagre-VZM6K2ZE-C_GvE4lu.js → dagre-VZM6K2ZE-Bbb-siGp.js} +1 -1
  35. package/dist/assets/{diagram-7IWD3JNH-pFzs9HEu.js → diagram-7IWD3JNH-9CWGFMYR.js} +1 -1
  36. package/dist/assets/{diagram-B4RE2ZJO-CcehQC7x.js → diagram-B4RE2ZJO-TWK59T6V.js} +1 -1
  37. package/dist/assets/{diagram-LBJQPF4R-hfKcrrxe.js → diagram-LBJQPF4R-ErMC9yaa.js} +1 -1
  38. package/dist/assets/{diagram-Q27KOJAE-C1semJfK.js → diagram-Q27KOJAE-Dt_-C7hV.js} +1 -1
  39. package/dist/assets/{diagram-UB23O5K3-57hvXQXO.js → diagram-UB23O5K3-BJ1DnRAF.js} +1 -1
  40. package/dist/assets/{ebnfDiagram-BXEA7PRR-9KIAlAVw.js → ebnfDiagram-BXEA7PRR-CE1YaQ9s.js} +1 -1
  41. package/dist/assets/{erDiagram-JOGREHBK-B7OzJ767.js → erDiagram-JOGREHBK-Bqk7N9aY.js} +1 -1
  42. package/dist/assets/{flowDiagram-UKHOOZJN-CJV8fQPR.js → flowDiagram-UKHOOZJN-DEr2II5V.js} +1 -1
  43. package/dist/assets/{ganttDiagram-PKOTCBZU-CbMNWWQu.js → ganttDiagram-PKOTCBZU-CWYAUeYN.js} +1 -1
  44. package/dist/assets/{gitGraphDiagram-DS77QQ5N-I_vZ5waO.js → gitGraphDiagram-DS77QQ5N-tdEcEIM6.js} +1 -1
  45. package/dist/assets/{index-ZHwIojzi.js → index-B-sTfSZq.js} +1 -1
  46. package/dist/assets/{index-GFhXZyl2.js → index-BSe3UiFA.js} +1 -1
  47. package/dist/assets/{index-DFnVoqpx.js → index-B_FDMWar.js} +5 -5
  48. package/dist/assets/{index-DlSeAkw_.js → index-CWJtg2pR.js} +1 -1
  49. package/dist/assets/{infoDiagram-6WML65LV-Dh20Tsbq.js → infoDiagram-6WML65LV-A3IqRUnO.js} +1 -1
  50. package/dist/assets/{ishikawaDiagram-WSZJBQD7-DH2Sm3ko.js → ishikawaDiagram-WSZJBQD7-B2uL6bkK.js} +1 -1
  51. package/dist/assets/{journeyDiagram-NVQOT4AX-BNFQqlVu.js → journeyDiagram-NVQOT4AX-D9JB-hRW.js} +1 -1
  52. package/dist/assets/{kanban-definition-27J2QSJJ-B3iL0ztJ.js → kanban-definition-27J2QSJJ-DmlgiV7K.js} +1 -1
  53. package/dist/assets/{linear-Cwe9dEKA.js → linear-Ct_eoJQk.js} +1 -1
  54. package/dist/assets/{mermaid.core-BYRQSshi.js → mermaid.core-CQ698HNn.js} +5 -5
  55. package/dist/assets/{mindmap-definition-FAOFIHXS-BgfBYMzr.js → mindmap-definition-FAOFIHXS-DGtMgQhf.js} +1 -1
  56. package/dist/assets/{pegDiagram-VL7TDLO6-CNYpECr8.js → pegDiagram-VL7TDLO6-CO6vP4n9.js} +1 -1
  57. package/dist/assets/{pieDiagram-7S7Q4E2Y-DeSrxc4I.js → pieDiagram-7S7Q4E2Y-DMkt_Zfr.js} +1 -1
  58. package/dist/assets/{quadrantDiagram-CIZ2JOQS-BXVVQsa6.js → quadrantDiagram-CIZ2JOQS-DgNxRz6t.js} +1 -1
  59. package/dist/assets/{railroadDiagram-AXF67PYL-JTroIW8F.js → railroadDiagram-AXF67PYL-DKz9UqA9.js} +1 -1
  60. package/dist/assets/{requirementDiagram-LRYGKXZP-C1Z_inLz.js → requirementDiagram-LRYGKXZP-L84XyGgV.js} +1 -1
  61. package/dist/assets/{sankeyDiagram-W5VNT64P-DCJgKFUk.js → sankeyDiagram-W5VNT64P-CQqWbJ3j.js} +1 -1
  62. package/dist/assets/{sequenceDiagram-SI44F4Z6-D8Wrx5Vc.js → sequenceDiagram-SI44F4Z6--tnzS2tb.js} +1 -1
  63. package/dist/assets/{sizeCapture-X5ZJPWSS-CXqD68vD.js → sizeCapture-X5ZJPWSS-DujwEOtm.js} +1 -1
  64. package/dist/assets/{stateDiagram-OKZ733FA-CF0Ekqjo.js → stateDiagram-OKZ733FA-BK3TcZY2.js} +1 -1
  65. package/dist/assets/stateDiagram-v2-UEYNNEHI-_medyomK.js +1 -0
  66. package/dist/assets/{swimlanes-SLNWSIFB-DVIax_k4.js → swimlanes-SLNWSIFB-naX-Uv1B.js} +2 -2
  67. package/dist/assets/swimlanesDiagram-ULZ7WXOC-DstIv3TC.js +8 -0
  68. package/dist/assets/{timeline-definition-Z64GVDOM-DSqk3ApG.js → timeline-definition-Z64GVDOM-C-vP_ddU.js} +1 -1
  69. package/dist/assets/{vennDiagram-T6HMQDX7-DNbTqyG5.js → vennDiagram-T6HMQDX7-DN-I7o-7.js} +1 -1
  70. package/dist/assets/{wardleyDiagram-T6FBY63Y-BNCpsFZV.js → wardleyDiagram-T6FBY63Y-Db9dsaXv.js} +1 -1
  71. package/dist/assets/{xychartDiagram-ELKLHX3M-XsVZphHr.js → xychartDiagram-ELKLHX3M-DE6OxW_p.js} +1 -1
  72. package/dist/clear-cache.html +85 -85
  73. package/dist/convert-icons.md +52 -52
  74. package/dist/favicon.svg +8 -8
  75. package/dist/generate-icons.js +48 -48
  76. package/dist/icons/codex-white.svg +3 -3
  77. package/dist/icons/codex.svg +3 -3
  78. package/dist/icons/cursor-white.svg +11 -11
  79. package/dist/icons/icon-128x128.svg +11 -11
  80. package/dist/icons/icon-144x144.svg +11 -11
  81. package/dist/icons/icon-152x152.svg +11 -11
  82. package/dist/icons/icon-192x192.svg +11 -11
  83. package/dist/icons/icon-384x384.svg +11 -11
  84. package/dist/icons/icon-512x512.svg +11 -11
  85. package/dist/icons/icon-72x72.svg +11 -11
  86. package/dist/icons/icon-96x96.svg +11 -11
  87. package/dist/icons/icon-template.svg +11 -11
  88. package/dist/index.html +56 -56
  89. package/dist/logo.svg +17 -17
  90. package/dist/manifest.json +60 -60
  91. package/dist/sw.js +124 -124
  92. package/dist-server/server/modules/cli/cli.service.js +36 -36
  93. package/dist-server/server/modules/cli/sandbox.service.js +38 -38
  94. package/dist-server/server/modules/cli/tests/cli-environment-bootstrap.test.js +74 -74
  95. package/dist-server/server/modules/commands/commands.routes.js +24 -24
  96. package/dist-server/server/modules/database/migrations.js +190 -190
  97. package/dist-server/server/modules/database/repositories/api-keys.js +3 -3
  98. package/dist-server/server/modules/database/repositories/github-tokens.js +2 -2
  99. package/dist-server/server/modules/database/repositories/notification-channel-endpoints.js +28 -28
  100. package/dist-server/server/modules/database/repositories/notification-preferences.js +4 -4
  101. package/dist-server/server/modules/database/repositories/projects.db.js +61 -61
  102. package/dist-server/server/modules/database/repositories/provider-models.js +35 -35
  103. package/dist-server/server/modules/database/repositories/push-subscriptions.js +5 -5
  104. package/dist-server/server/modules/database/repositories/scan-state.db.js +5 -5
  105. package/dist-server/server/modules/database/repositories/sessions.db.js +92 -92
  106. package/dist-server/server/modules/database/schema.js +177 -177
  107. package/dist-server/server/modules/database/tests/provider-models.db.integration.test.js +6 -6
  108. package/dist-server/server/modules/git/git.routes.js +17 -17
  109. package/dist-server/server/modules/providers/list/opencode/opencode-models.provider.js +12 -12
  110. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.js +9 -9
  111. package/dist-server/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +17 -17
  112. package/dist-server/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.js +25 -25
  113. package/dist-server/server/modules/providers/list/opencode/opencode-sessions.provider.js +27 -27
  114. package/dist-server/server/modules/providers/services/provider-token-usage.service.js +9 -9
  115. package/dist-server/server/modules/providers/tests/mcp.test.js +3 -3
  116. package/dist-server/server/modules/providers/tests/opencode-sessions.test.js +96 -96
  117. package/dist-server/server/modules/providers/tests/provider-token-usage.service.test.js +18 -18
  118. package/dist-server/server/modules/taskmaster/taskmaster.routes.js +419 -419
  119. package/electron/cloud.js +260 -260
  120. package/electron/desktopNotifications.js +378 -378
  121. package/electron/desktopWindow.js +766 -766
  122. package/electron/launcher/index.html +14 -14
  123. package/electron/launcher/launcher.css +801 -801
  124. package/electron/launcher/launcher.js +687 -687
  125. package/electron/localServer.js +549 -549
  126. package/electron/main.js +944 -944
  127. package/electron/preload.cjs +60 -60
  128. package/electron/scripts/generate-macos-icon.js +62 -62
  129. package/electron/serverInstaller.js +277 -277
  130. package/electron/tabs.js +87 -87
  131. package/electron/viewHost.js +331 -331
  132. package/package.json +250 -247
  133. package/public/api-docs.html +901 -901
  134. package/scripts/fix-node-pty.js +67 -67
  135. package/scripts/promote-dist-server.mjs +50 -50
  136. package/scripts/release/build-server-bundle.js +176 -176
  137. package/scripts/release/prepare-desktop-app.js +152 -152
  138. package/server/index.ts +399 -399
  139. package/server/load-env.ts +47 -47
  140. package/server/modules/agent/agent.module.ts +52 -52
  141. package/server/modules/agent/agent.routes.ts +1287 -1287
  142. package/server/modules/agent/index.ts +2 -2
  143. package/server/modules/agent/tests/agent.routes.test.ts +205 -205
  144. package/server/modules/assets/assets.routes.ts +150 -150
  145. package/server/modules/assets/index.ts +3 -3
  146. package/server/modules/assets/services/image-assets.service.ts +127 -127
  147. package/server/modules/assets/tests/image-assets.service.test.ts +74 -74
  148. package/server/modules/auth/auth.middleware.ts +163 -163
  149. package/server/modules/auth/auth.module.ts +38 -38
  150. package/server/modules/auth/auth.routes.ts +57 -57
  151. package/server/modules/auth/auth.service.ts +155 -155
  152. package/server/modules/auth/index.ts +9 -9
  153. package/server/modules/auth/tests/auth.service.test.ts +95 -95
  154. package/server/modules/browser-use/browser-use-mcp.routes.ts +120 -120
  155. package/server/modules/browser-use/browser-use-mcp.ts +386 -386
  156. package/server/modules/browser-use/browser-use-runtime.ts +10 -10
  157. package/server/modules/browser-use/browser-use.routes.ts +96 -96
  158. package/server/modules/browser-use/browser-use.service.ts +836 -836
  159. package/server/modules/browser-use/index.ts +11 -11
  160. package/server/modules/browser-use/tests/browser-use.service.test.ts +10 -10
  161. package/server/modules/cli/cli.module.ts +93 -93
  162. package/server/modules/cli/cli.service.ts +248 -248
  163. package/server/modules/cli/cli.ts +19 -19
  164. package/server/modules/cli/index.ts +2 -2
  165. package/server/modules/cli/sandbox.service.ts +398 -398
  166. package/server/modules/cli/tests/cli-environment-bootstrap.test.ts +186 -186
  167. package/server/modules/cli/tests/cli.service.test.ts +86 -86
  168. package/server/modules/cli/tests/sandbox.service.test.ts +58 -58
  169. package/server/modules/commands/commands.module.ts +22 -22
  170. package/server/modules/commands/commands.routes.ts +586 -586
  171. package/server/modules/commands/index.ts +2 -2
  172. package/server/modules/commands/tests/commands.test.ts +109 -109
  173. package/server/modules/database/connection.ts +143 -143
  174. package/server/modules/database/index.ts +17 -17
  175. package/server/modules/database/init-db.ts +17 -17
  176. package/server/modules/database/migrations.ts +515 -515
  177. package/server/modules/database/repositories/api-keys.ts +119 -119
  178. package/server/modules/database/repositories/app-config.ts +53 -53
  179. package/server/modules/database/repositories/credentials.ts +106 -106
  180. package/server/modules/database/repositories/github-tokens.ts +100 -100
  181. package/server/modules/database/repositories/notification-channel-endpoints.ts +153 -153
  182. package/server/modules/database/repositories/notification-preferences.ts +117 -117
  183. package/server/modules/database/repositories/projects.db.ts +196 -196
  184. package/server/modules/database/repositories/provider-models.ts +168 -168
  185. package/server/modules/database/repositories/push-subscriptions.ts +80 -80
  186. package/server/modules/database/repositories/scan-state.db.ts +42 -42
  187. package/server/modules/database/repositories/sessions.db.ts +524 -524
  188. package/server/modules/database/repositories/users.ts +140 -140
  189. package/server/modules/database/repositories/vapid-keys.ts +57 -57
  190. package/server/modules/database/schema.ts +210 -210
  191. package/server/modules/database/tests/projects.db.integration.test.ts +72 -72
  192. package/server/modules/database/tests/provider-models.db.integration.test.ts +125 -125
  193. package/server/modules/database/tests/sessions-provider-mapping.test.ts +109 -109
  194. package/server/modules/database/tests/sessions.db.integration.test.ts +116 -116
  195. package/server/modules/file-tree/file-tree.module.ts +116 -116
  196. package/server/modules/file-tree/file-tree.routes.ts +258 -258
  197. package/server/modules/file-tree/file-tree.service.ts +675 -675
  198. package/server/modules/file-tree/index.ts +2 -2
  199. package/server/modules/file-tree/tests/file-tree.routes.test.ts +157 -157
  200. package/server/modules/file-tree/tests/file-tree.service.test.ts +371 -371
  201. package/server/modules/git/git-branch.service.ts +43 -43
  202. package/server/modules/git/git-parsing.service.ts +66 -66
  203. package/server/modules/git/git.module.ts +22 -22
  204. package/server/modules/git/git.routes.ts +1596 -1596
  205. package/server/modules/git/index.ts +2 -2
  206. package/server/modules/git/tests/git-branch.service.test.ts +60 -60
  207. package/server/modules/git/tests/git-init.routes.test.ts +132 -132
  208. package/server/modules/git/tests/git.test.ts +106 -106
  209. package/server/modules/notifications/index.ts +24 -24
  210. package/server/modules/notifications/notifications.routes.ts +127 -127
  211. package/server/modules/notifications/services/desktop-notification-clients.service.ts +124 -124
  212. package/server/modules/notifications/services/notification-orchestrator.service.js +310 -310
  213. package/server/modules/notifications/tests/notification-orchestrator.integration.test.ts +55 -55
  214. package/server/modules/notifications/vapid-keys.service.ts +38 -38
  215. package/server/modules/notifications/websocket/desktop-notifications-websocket.service.ts +109 -109
  216. package/server/modules/plugins/index.ts +9 -9
  217. package/server/modules/plugins/plugin-process.service.ts +217 -217
  218. package/server/modules/plugins/plugin-registry.service.ts +459 -459
  219. package/server/modules/plugins/plugins.module.ts +33 -33
  220. package/server/modules/plugins/plugins.routes.ts +66 -66
  221. package/server/modules/plugins/plugins.service.ts +147 -147
  222. package/server/modules/plugins/tests/plugins.service.test.ts +32 -32
  223. package/server/modules/projects/index.ts +11 -11
  224. package/server/modules/projects/projects.routes.ts +273 -273
  225. package/server/modules/projects/services/project-clone.service.ts +323 -323
  226. package/server/modules/projects/services/project-delete.service.ts +90 -90
  227. package/server/modules/projects/services/project-management.service.ts +144 -144
  228. package/server/modules/projects/services/project-star.service.ts +78 -78
  229. package/server/modules/projects/services/projects-has-taskmaster.service.ts +248 -248
  230. package/server/modules/projects/services/projects-with-sessions-fetch.service.ts +314 -314
  231. package/server/modules/projects/tests/project-clone.service.test.ts +183 -183
  232. package/server/modules/projects/tests/project-management.service.test.ts +117 -117
  233. package/server/modules/projects/tests/project-star.service.test.ts +123 -123
  234. package/server/modules/projects/tests/projects-has-taskmaster.service.test.ts +105 -105
  235. package/server/modules/providers/README.md +380 -380
  236. package/server/modules/providers/index.ts +10 -10
  237. package/server/modules/providers/list/claude/claude-auth.provider.ts +160 -160
  238. package/server/modules/providers/list/claude/claude-mcp.provider.ts +135 -135
  239. package/server/modules/providers/list/claude/claude-models.provider.ts +294 -294
  240. package/server/modules/providers/list/claude/claude-runtime.provider.js +1068 -1068
  241. package/server/modules/providers/list/claude/claude-session-synchronizer.provider.ts +203 -203
  242. package/server/modules/providers/list/claude/claude-sessions.provider.ts +684 -684
  243. package/server/modules/providers/list/claude/claude-skills.provider.ts +265 -265
  244. package/server/modules/providers/list/claude/claude.provider.ts +30 -30
  245. package/server/modules/providers/list/codex/codex-auth.provider.ts +100 -100
  246. package/server/modules/providers/list/codex/codex-mcp.provider.ts +135 -135
  247. package/server/modules/providers/list/codex/codex-models.provider.ts +123 -123
  248. package/server/modules/providers/list/codex/codex-runtime.provider.js +533 -533
  249. package/server/modules/providers/list/codex/codex-session-synchronizer.provider.ts +203 -203
  250. package/server/modules/providers/list/codex/codex-sessions.provider.ts +938 -938
  251. package/server/modules/providers/list/codex/codex-skills.provider.ts +73 -73
  252. package/server/modules/providers/list/codex/codex.provider.ts +28 -28
  253. package/server/modules/providers/list/cursor/cursor-auth.provider.ts +143 -143
  254. package/server/modules/providers/list/cursor/cursor-mcp.provider.ts +108 -108
  255. package/server/modules/providers/list/cursor/cursor-models.provider.ts +119 -119
  256. package/server/modules/providers/list/cursor/cursor-runtime.provider.js +385 -385
  257. package/server/modules/providers/list/cursor/cursor-session-synchronizer.provider.ts +159 -159
  258. package/server/modules/providers/list/cursor/cursor-sessions.provider.ts +654 -654
  259. package/server/modules/providers/list/cursor/cursor-skills.provider.ts +39 -39
  260. package/server/modules/providers/list/cursor/cursor.provider.ts +30 -30
  261. package/server/modules/providers/list/opencode/opencode-auth.provider.ts +110 -110
  262. package/server/modules/providers/list/opencode/opencode-mcp.provider.ts +228 -228
  263. package/server/modules/providers/list/opencode/opencode-models.provider.ts +203 -203
  264. package/server/modules/providers/list/opencode/opencode-runtime.provider.js +434 -434
  265. package/server/modules/providers/list/opencode/opencode-runtime.provider.test.js +261 -261
  266. package/server/modules/providers/list/opencode/opencode-session-synchronizer.provider.ts +179 -179
  267. package/server/modules/providers/list/opencode/opencode-sessions.provider.ts +500 -500
  268. package/server/modules/providers/list/opencode/opencode-skills.provider.ts +78 -78
  269. package/server/modules/providers/list/opencode/opencode.provider.ts +30 -30
  270. package/server/modules/providers/provider.registry.ts +36 -36
  271. package/server/modules/providers/provider.routes.ts +877 -877
  272. package/server/modules/providers/services/mcp.service.ts +109 -109
  273. package/server/modules/providers/services/provider-auth.service.ts +26 -26
  274. package/server/modules/providers/services/provider-capabilities.service.ts +97 -97
  275. package/server/modules/providers/services/provider-models.service.ts +401 -401
  276. package/server/modules/providers/services/provider-runtime.service.ts +108 -108
  277. package/server/modules/providers/services/provider-token-usage.service.ts +357 -357
  278. package/server/modules/providers/services/session-conversations-search.service.ts +1321 -1321
  279. package/server/modules/providers/services/session-synchronizer.service.ts +74 -74
  280. package/server/modules/providers/services/sessions-watcher.service.ts +247 -247
  281. package/server/modules/providers/services/sessions.service.ts +471 -471
  282. package/server/modules/providers/services/skills.service.ts +39 -39
  283. package/server/modules/providers/shared/base/abstract.provider.ts +32 -32
  284. package/server/modules/providers/shared/mcp/mcp.provider.ts +151 -151
  285. package/server/modules/providers/shared/skills/skills.provider.ts +287 -287
  286. package/server/modules/providers/tests/claude-auth.test.ts +150 -150
  287. package/server/modules/providers/tests/claude-sessions.test.ts +61 -61
  288. package/server/modules/providers/tests/codex-sessions.test.ts +231 -231
  289. package/server/modules/providers/tests/mcp.test.ts +349 -349
  290. package/server/modules/providers/tests/opencode-models.test.ts +41 -41
  291. package/server/modules/providers/tests/opencode-sessions.test.ts +522 -522
  292. package/server/modules/providers/tests/provider-attachment-history.test.ts +247 -247
  293. package/server/modules/providers/tests/provider-models.service.test.ts +353 -353
  294. package/server/modules/providers/tests/provider-runtime.service.test.ts +138 -138
  295. package/server/modules/providers/tests/provider-token-usage.service.test.ts +186 -186
  296. package/server/modules/providers/tests/provider.routes.test.ts +244 -244
  297. package/server/modules/providers/tests/sessions-details.test.ts +73 -73
  298. package/server/modules/providers/tests/sessions.service.test.ts +127 -127
  299. package/server/modules/providers/tests/skills.test.ts +692 -692
  300. package/server/modules/settings/index.ts +2 -2
  301. package/server/modules/settings/settings.module.ts +50 -50
  302. package/server/modules/settings/settings.routes.ts +51 -51
  303. package/server/modules/settings/settings.service.ts +186 -186
  304. package/server/modules/settings/tests/settings.service.test.ts +54 -54
  305. package/server/modules/system/index.ts +2 -2
  306. package/server/modules/system/system.module.ts +62 -62
  307. package/server/modules/system/system.routes.ts +21 -21
  308. package/server/modules/system/system.service.ts +76 -76
  309. package/server/modules/system/tests/system.service.test.ts +118 -118
  310. package/server/modules/taskmaster/index.ts +2 -2
  311. package/server/modules/taskmaster/taskmaster.module.ts +24 -24
  312. package/server/modules/taskmaster/taskmaster.routes.ts +1482 -1482
  313. package/server/modules/taskmaster/taskmaster.service.ts +132 -132
  314. package/server/modules/taskmaster/tests/taskmaster.routes.test.ts +144 -144
  315. package/server/modules/taskmaster/tests/taskmaster.service.test.ts +97 -97
  316. package/server/modules/user/index.ts +2 -2
  317. package/server/modules/user/tests/user.service.test.ts +61 -61
  318. package/server/modules/user/user.module.ts +58 -58
  319. package/server/modules/user/user.routes.ts +50 -50
  320. package/server/modules/user/user.service.ts +85 -85
  321. package/server/modules/voice/index.ts +2 -2
  322. package/server/modules/voice/tests/voice.service.test.ts +98 -98
  323. package/server/modules/voice/voice.module.ts +47 -47
  324. package/server/modules/voice/voice.routes.ts +113 -113
  325. package/server/modules/voice/voice.service.ts +192 -192
  326. package/server/modules/websocket/README.md +273 -273
  327. package/server/modules/websocket/index.ts +3 -3
  328. package/server/modules/websocket/services/chat-run-registry.service.ts +343 -343
  329. package/server/modules/websocket/services/chat-session-writer.service.ts +145 -145
  330. package/server/modules/websocket/services/chat-websocket.service.ts +425 -425
  331. package/server/modules/websocket/services/plugin-websocket-proxy.service.ts +65 -65
  332. package/server/modules/websocket/services/shell-websocket.service.ts +502 -502
  333. package/server/modules/websocket/services/websocket-auth.service.ts +59 -59
  334. package/server/modules/websocket/services/websocket-server.service.ts +124 -124
  335. package/server/modules/websocket/services/websocket-state.service.ts +16 -16
  336. package/server/modules/websocket/services/websocket-writer.service.ts +38 -38
  337. package/server/modules/websocket/tests/chat-attachment-filter.test.ts +59 -59
  338. package/server/modules/websocket/tests/chat-run-registry.test.ts +282 -282
  339. package/server/modules/websocket/tests/shell-websocket.service.test.ts +100 -100
  340. package/server/modules/websocket/tests/websocket-heartbeat.service.test.ts +78 -78
  341. package/server/modules/worktrees/index.ts +2 -2
  342. package/server/modules/worktrees/services/worktree-create-and-open.service.ts +53 -53
  343. package/server/modules/worktrees/services/worktree-create.service.ts +104 -104
  344. package/server/modules/worktrees/services/worktree-git.service.ts +207 -207
  345. package/server/modules/worktrees/services/worktree-list.service.ts +143 -143
  346. package/server/modules/worktrees/services/worktree-merge.service.ts +157 -157
  347. package/server/modules/worktrees/services/worktree-open.service.ts +85 -85
  348. package/server/modules/worktrees/services/worktree-remove.service.ts +88 -88
  349. package/server/modules/worktrees/tests/worktree-create-and-open.service.test.ts +68 -68
  350. package/server/modules/worktrees/tests/worktree-create.service.test.ts +142 -142
  351. package/server/modules/worktrees/tests/worktree-git.service.test.ts +84 -84
  352. package/server/modules/worktrees/tests/worktree-list.service.test.ts +45 -45
  353. package/server/modules/worktrees/tests/worktree-merge.service.test.ts +213 -213
  354. package/server/modules/worktrees/tests/worktree-open.service.test.ts +122 -122
  355. package/server/modules/worktrees/tests/worktree-remove.service.test.ts +227 -227
  356. package/server/modules/worktrees/tests/worktrees.routes.test.ts +199 -199
  357. package/server/modules/worktrees/worktrees.module.ts +113 -113
  358. package/server/modules/worktrees/worktrees.routes.ts +116 -116
  359. package/server/shared/claude-cli-path.ts +139 -139
  360. package/server/shared/frontmatter.ts +18 -18
  361. package/server/shared/image-attachments.ts +435 -435
  362. package/server/shared/interfaces.ts +179 -179
  363. package/server/shared/tests/claude-cli-path.test.ts +61 -61
  364. package/server/shared/tests/image-attachments.test.ts +353 -353
  365. package/server/shared/tests/slice-tail-page.test.ts +42 -42
  366. package/server/shared/types.ts +1226 -1226
  367. package/server/shared/utils.ts +1161 -1161
  368. package/server/tsconfig.json +40 -40
  369. package/shared/networkHosts.js +22 -22
  370. package/dist/assets/channel-Bm0ZLE4z.js +0 -1
  371. package/dist/assets/classDiagram-JCYQIIEL-DqEOcNxw.js +0 -1
  372. package/dist/assets/classDiagram-v2-OCEON4UE-DqEOcNxw.js +0 -1
  373. package/dist/assets/stateDiagram-v2-UEYNNEHI-DARLpEFJ.js +0 -1
  374. package/dist/assets/swimlanesDiagram-ULZ7WXOC-CrgDgaY8.js +0 -8
@@ -1,378 +1,378 @@
1
- import fs from 'node:fs/promises';
2
- import path from 'node:path';
3
- import { Notification } from 'electron';
4
- import WebSocket from 'ws';
5
-
6
- const RECONNECT_MIN_MS = 1000;
7
- const RECONNECT_MAX_MS = 30000;
8
- const TARGET_REGISTER_TIMEOUT_MS = 8000;
9
-
10
- function toNotificationsWsUrl(httpUrl) {
11
- try {
12
- const parsed = new URL(httpUrl);
13
- parsed.protocol = parsed.protocol === 'http:' ? 'ws:' : 'wss:';
14
- parsed.pathname = '/desktop-notifications';
15
- parsed.search = '';
16
- parsed.hash = '';
17
- return parsed.toString();
18
- } catch {
19
- return null;
20
- }
21
- }
22
-
23
- function readJsonMessage(raw) {
24
- try {
25
- return JSON.parse(String(raw));
26
- } catch {
27
- return null;
28
- }
29
- }
30
-
31
- async function requestJson(url, { method = 'POST', body = null, headers = {} } = {}) {
32
- const controller = new AbortController();
33
- const timeout = setTimeout(() => controller.abort(), TARGET_REGISTER_TIMEOUT_MS);
34
- try {
35
- const response = await fetch(url, {
36
- method,
37
- signal: controller.signal,
38
- headers: {
39
- 'Content-Type': 'application/json',
40
- ...headers,
41
- },
42
- ...(body == null ? {} : { body: JSON.stringify(body) }),
43
- });
44
- const payload = await response.json().catch(() => ({}));
45
- if (!response.ok) {
46
- throw new Error(payload.error || `Request failed with status ${response.status}`);
47
- }
48
- return payload;
49
- } finally {
50
- clearTimeout(timeout);
51
- }
52
- }
53
-
54
- export class DesktopNotificationsController {
55
- constructor({
56
- settingsPath,
57
- appVersion,
58
- appName,
59
- getDeviceId,
60
- getAccountEmail,
61
- getRunningEnvironmentUrls,
62
- getApiKey,
63
- getAuthToken,
64
- getIconPath,
65
- openNotificationTarget,
66
- onChange,
67
- }) {
68
- this.settingsPath = settingsPath;
69
- this.appVersion = appVersion;
70
- this.appName = appName;
71
- this.getDeviceId = getDeviceId;
72
- this.getAccountEmail = getAccountEmail;
73
- this.getRunningEnvironmentUrls = getRunningEnvironmentUrls;
74
- this.getApiKey = getApiKey;
75
- this.getAuthToken = getAuthToken;
76
- this.getIconPath = getIconPath;
77
- this.openNotificationTarget = openNotificationTarget;
78
- this.onChange = onChange;
79
- this.settings = { enabled: false };
80
- this.connections = new Map();
81
- this.lastEvent = null;
82
- this.lastError = null;
83
- }
84
-
85
- getState() {
86
- const connectedTargets = [];
87
- for (const [url, connection] of this.connections.entries()) {
88
- if (connection.ws?.readyState === WebSocket.OPEN) {
89
- connectedTargets.push(url);
90
- }
91
- }
92
-
93
- return {
94
- enabled: this.settings.enabled,
95
- supported: Notification.isSupported(),
96
- targetCount: this.connections.size,
97
- connectedCount: connectedTargets.length,
98
- connectedTargets,
99
- lastEvent: this.lastEvent,
100
- lastError: this.lastError,
101
- };
102
- }
103
-
104
- async loadSettings() {
105
- try {
106
- const raw = await fs.readFile(this.settingsPath, 'utf8');
107
- const stored = JSON.parse(raw);
108
- this.settings = { enabled: Boolean(stored.enabled) };
109
- } catch {
110
- this.settings = { enabled: false };
111
- }
112
- return this.settings;
113
- }
114
-
115
- async saveSettings(next) {
116
- const enabled = Boolean(next?.enabled);
117
- if (!enabled && this.settings.enabled) {
118
- await this.disableCurrentTargets();
119
- }
120
- this.settings = { enabled };
121
- await fs.mkdir(path.dirname(this.settingsPath), { recursive: true });
122
- await fs.writeFile(this.settingsPath, JSON.stringify(this.settings, null, 2), 'utf8');
123
- await this.sync();
124
- this.onChange?.();
125
- return this.settings;
126
- }
127
-
128
- async sync() {
129
- if (!this.settings.enabled) {
130
- this.stop();
131
- this.lastEvent = 'disabled';
132
- this.onChange?.();
133
- return;
134
- }
135
-
136
- if (!Notification.isSupported()) {
137
- this.stop();
138
- this.lastEvent = 'unsupported';
139
- this.lastError = 'Native notifications are not supported on this system.';
140
- this.onChange?.();
141
- return;
142
- }
143
-
144
- const deviceId = this.getDeviceId?.();
145
- if (!deviceId) {
146
- this.stop();
147
- this.lastEvent = 'missing-device';
148
- this.lastError = 'Connect a CloudCLI account before enabling desktop notifications.';
149
- this.onChange?.();
150
- return;
151
- }
152
-
153
- const targets = (this.getRunningEnvironmentUrls?.() || [])
154
- .map((httpUrl) => ({
155
- httpUrl,
156
- wsUrl: toNotificationsWsUrl(httpUrl),
157
- }))
158
- .filter((target) => target.wsUrl);
159
-
160
- const nextWsUrls = new Set(targets.map((target) => target.wsUrl));
161
- for (const [wsUrl, connection] of this.connections.entries()) {
162
- if (!nextWsUrls.has(wsUrl)) {
163
- this.closeConnection(connection);
164
- this.connections.delete(wsUrl);
165
- }
166
- }
167
-
168
- for (const target of targets) {
169
- if (!this.connections.has(target.wsUrl)) {
170
- void this.connect(target).catch((error) => {
171
- this.lastEvent = 'connect-error';
172
- this.lastError = error instanceof Error ? error.message : String(error);
173
- this.onChange?.();
174
- });
175
- }
176
- }
177
-
178
- this.lastEvent = targets.length ? 'sync' : 'no-targets';
179
- this.onChange?.();
180
- }
181
-
182
- async connect(target, attempt = 0) {
183
- const existing = this.connections.get(target.wsUrl);
184
- if (existing?.ws && [WebSocket.CONNECTING, WebSocket.OPEN].includes(existing.ws.readyState)) {
185
- return;
186
- }
187
-
188
- const connection = {
189
- ...target,
190
- ws: null,
191
- reconnectTimer: null,
192
- closed: false,
193
- attempt,
194
- };
195
- this.connections.set(target.wsUrl, connection);
196
-
197
- const headers = await this.getTargetAuthHeaders(target.httpUrl);
198
- if (connection.closed || this.connections.get(target.wsUrl) !== connection) {
199
- return;
200
- }
201
-
202
- const ws = new WebSocket(target.wsUrl, { headers: Object.keys(headers).length ? headers : undefined });
203
- connection.ws = ws;
204
-
205
- ws.on('open', async () => {
206
- try {
207
- await this.registerTarget(target.httpUrl);
208
- ws.send(JSON.stringify({
209
- type: 'register',
210
- deviceId: this.getDeviceId?.(),
211
- label: this.getAccountEmail?.() || this.appName,
212
- platform: process.platform,
213
- appVersion: this.appVersion,
214
- }));
215
- connection.attempt = 0;
216
- this.lastEvent = 'connected';
217
- this.lastError = null;
218
- this.onChange?.();
219
- } catch (error) {
220
- this.lastEvent = 'register-error';
221
- this.lastError = error instanceof Error ? error.message : String(error);
222
- this.onChange?.();
223
- try { ws.close(); } catch {}
224
- }
225
- });
226
-
227
- ws.on('message', (raw) => this.handleMessage(target, ws, raw));
228
- ws.on('close', () => this.scheduleReconnect(target.wsUrl));
229
- ws.on('error', (error) => {
230
- this.lastEvent = 'socket-error';
231
- this.lastError = error instanceof Error ? error.message : String(error);
232
- this.onChange?.();
233
- });
234
- }
235
-
236
- async registerTarget(httpUrl) {
237
- const url = new URL('/api/notifications/endpoints/current', httpUrl).toString();
238
- await requestJson(url, {
239
- method: 'POST',
240
- headers: await this.getTargetAuthHeaders(httpUrl),
241
- body: {
242
- channel: 'desktop',
243
- endpointId: this.getDeviceId?.(),
244
- label: this.getAccountEmail?.() || this.appName,
245
- metadata: {
246
- platform: process.platform,
247
- appVersion: this.appVersion,
248
- },
249
- enabled: true,
250
- },
251
- });
252
- }
253
-
254
- async disableCurrentTargets() {
255
- const deviceId = this.getDeviceId?.();
256
- if (!deviceId) return;
257
-
258
- const targets = new Set([
259
- ...[...this.connections.values()].map((connection) => connection.httpUrl).filter(Boolean),
260
- ...(this.getRunningEnvironmentUrls?.() || []),
261
- ]);
262
-
263
- const results = await Promise.allSettled([...targets].map(async (httpUrl) => {
264
- const url = new URL(`/api/notifications/endpoints/desktop/${encodeURIComponent(deviceId)}`, httpUrl).toString();
265
- await requestJson(url, {
266
- method: 'PATCH',
267
- headers: await this.getTargetAuthHeaders(httpUrl),
268
- body: { enabled: false },
269
- });
270
- }));
271
-
272
- const rejected = results.find((result) => result.status === 'rejected');
273
- if (rejected) {
274
- this.lastEvent = 'disable-endpoint-error';
275
- this.lastError = rejected.reason instanceof Error ? rejected.reason.message : String(rejected.reason);
276
- }
277
- }
278
-
279
- async getTargetAuthHeaders(httpUrl) {
280
- const headers = {};
281
- const apiKey = this.getApiKey?.();
282
- if (apiKey) {
283
- headers['X-API-Key'] = apiKey;
284
- }
285
-
286
- const authToken = await Promise.resolve(this.getAuthToken?.(httpUrl)).catch(() => null);
287
- if (authToken) {
288
- headers.Authorization = `Bearer ${authToken}`;
289
- }
290
- return headers;
291
- }
292
-
293
- handleMessage(target, ws, raw) {
294
- const message = readJsonMessage(raw);
295
- if (!message || message.type !== 'notification' || !message.payload) {
296
- return;
297
- }
298
-
299
- const shown = this.showNativeNotification(target, message.payload);
300
- if (shown && ws.readyState === WebSocket.OPEN) {
301
- ws.send(JSON.stringify({
302
- type: 'notification_ack',
303
- id: message.id || message.payload?.data?.tag || null,
304
- action: 'shown',
305
- }));
306
- }
307
- }
308
-
309
- showNativeNotification(target, payload) {
310
- if (!Notification.isSupported()) return false;
311
-
312
- const notification = new Notification({
313
- title: payload.title || this.appName,
314
- body: payload.body || '',
315
- icon: this.getIconPath?.(),
316
- silent: false,
317
- });
318
-
319
- notification.on('click', () => {
320
- void this.openNotificationTarget?.({
321
- environmentUrl: target.httpUrl,
322
- sessionId: payload.data?.sessionId || null,
323
- provider: payload.data?.provider || null,
324
- }).catch((error) => {
325
- this.lastEvent = 'click-error';
326
- this.lastError = error instanceof Error ? error.message : String(error);
327
- this.onChange?.();
328
- });
329
- });
330
-
331
- notification.show();
332
- this.lastEvent = 'notification-shown';
333
- this.lastError = null;
334
- this.onChange?.();
335
- return true;
336
- }
337
-
338
- scheduleReconnect(wsUrl) {
339
- const connection = this.connections.get(wsUrl);
340
- if (!connection || connection.closed || !this.settings.enabled) {
341
- return;
342
- }
343
-
344
- const attempt = connection.attempt + 1;
345
- connection.attempt = attempt;
346
- const delay = Math.min(RECONNECT_MAX_MS, RECONNECT_MIN_MS * (2 ** Math.min(attempt, 5)));
347
- connection.reconnectTimer = setTimeout(() => {
348
- if (!this.connections.has(wsUrl) || !this.settings.enabled) return;
349
- void this.connect({
350
- httpUrl: connection.httpUrl,
351
- wsUrl: connection.wsUrl,
352
- }, attempt).catch((error) => {
353
- this.lastEvent = 'connect-error';
354
- this.lastError = error instanceof Error ? error.message : String(error);
355
- this.onChange?.();
356
- });
357
- }, delay);
358
- this.lastEvent = 'reconnecting';
359
- this.onChange?.();
360
- }
361
-
362
- closeConnection(connection) {
363
- connection.closed = true;
364
- if (connection.reconnectTimer) {
365
- clearTimeout(connection.reconnectTimer);
366
- connection.reconnectTimer = null;
367
- }
368
- try { connection.ws?.close(); } catch {}
369
- }
370
-
371
- stop() {
372
- for (const connection of this.connections.values()) {
373
- this.closeConnection(connection);
374
- }
375
- this.connections.clear();
376
- this.onChange?.();
377
- }
378
- }
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { Notification } from 'electron';
4
+ import WebSocket from 'ws';
5
+
6
+ const RECONNECT_MIN_MS = 1000;
7
+ const RECONNECT_MAX_MS = 30000;
8
+ const TARGET_REGISTER_TIMEOUT_MS = 8000;
9
+
10
+ function toNotificationsWsUrl(httpUrl) {
11
+ try {
12
+ const parsed = new URL(httpUrl);
13
+ parsed.protocol = parsed.protocol === 'http:' ? 'ws:' : 'wss:';
14
+ parsed.pathname = '/desktop-notifications';
15
+ parsed.search = '';
16
+ parsed.hash = '';
17
+ return parsed.toString();
18
+ } catch {
19
+ return null;
20
+ }
21
+ }
22
+
23
+ function readJsonMessage(raw) {
24
+ try {
25
+ return JSON.parse(String(raw));
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ async function requestJson(url, { method = 'POST', body = null, headers = {} } = {}) {
32
+ const controller = new AbortController();
33
+ const timeout = setTimeout(() => controller.abort(), TARGET_REGISTER_TIMEOUT_MS);
34
+ try {
35
+ const response = await fetch(url, {
36
+ method,
37
+ signal: controller.signal,
38
+ headers: {
39
+ 'Content-Type': 'application/json',
40
+ ...headers,
41
+ },
42
+ ...(body == null ? {} : { body: JSON.stringify(body) }),
43
+ });
44
+ const payload = await response.json().catch(() => ({}));
45
+ if (!response.ok) {
46
+ throw new Error(payload.error || `Request failed with status ${response.status}`);
47
+ }
48
+ return payload;
49
+ } finally {
50
+ clearTimeout(timeout);
51
+ }
52
+ }
53
+
54
+ export class DesktopNotificationsController {
55
+ constructor({
56
+ settingsPath,
57
+ appVersion,
58
+ appName,
59
+ getDeviceId,
60
+ getAccountEmail,
61
+ getRunningEnvironmentUrls,
62
+ getApiKey,
63
+ getAuthToken,
64
+ getIconPath,
65
+ openNotificationTarget,
66
+ onChange,
67
+ }) {
68
+ this.settingsPath = settingsPath;
69
+ this.appVersion = appVersion;
70
+ this.appName = appName;
71
+ this.getDeviceId = getDeviceId;
72
+ this.getAccountEmail = getAccountEmail;
73
+ this.getRunningEnvironmentUrls = getRunningEnvironmentUrls;
74
+ this.getApiKey = getApiKey;
75
+ this.getAuthToken = getAuthToken;
76
+ this.getIconPath = getIconPath;
77
+ this.openNotificationTarget = openNotificationTarget;
78
+ this.onChange = onChange;
79
+ this.settings = { enabled: false };
80
+ this.connections = new Map();
81
+ this.lastEvent = null;
82
+ this.lastError = null;
83
+ }
84
+
85
+ getState() {
86
+ const connectedTargets = [];
87
+ for (const [url, connection] of this.connections.entries()) {
88
+ if (connection.ws?.readyState === WebSocket.OPEN) {
89
+ connectedTargets.push(url);
90
+ }
91
+ }
92
+
93
+ return {
94
+ enabled: this.settings.enabled,
95
+ supported: Notification.isSupported(),
96
+ targetCount: this.connections.size,
97
+ connectedCount: connectedTargets.length,
98
+ connectedTargets,
99
+ lastEvent: this.lastEvent,
100
+ lastError: this.lastError,
101
+ };
102
+ }
103
+
104
+ async loadSettings() {
105
+ try {
106
+ const raw = await fs.readFile(this.settingsPath, 'utf8');
107
+ const stored = JSON.parse(raw);
108
+ this.settings = { enabled: Boolean(stored.enabled) };
109
+ } catch {
110
+ this.settings = { enabled: false };
111
+ }
112
+ return this.settings;
113
+ }
114
+
115
+ async saveSettings(next) {
116
+ const enabled = Boolean(next?.enabled);
117
+ if (!enabled && this.settings.enabled) {
118
+ await this.disableCurrentTargets();
119
+ }
120
+ this.settings = { enabled };
121
+ await fs.mkdir(path.dirname(this.settingsPath), { recursive: true });
122
+ await fs.writeFile(this.settingsPath, JSON.stringify(this.settings, null, 2), 'utf8');
123
+ await this.sync();
124
+ this.onChange?.();
125
+ return this.settings;
126
+ }
127
+
128
+ async sync() {
129
+ if (!this.settings.enabled) {
130
+ this.stop();
131
+ this.lastEvent = 'disabled';
132
+ this.onChange?.();
133
+ return;
134
+ }
135
+
136
+ if (!Notification.isSupported()) {
137
+ this.stop();
138
+ this.lastEvent = 'unsupported';
139
+ this.lastError = 'Native notifications are not supported on this system.';
140
+ this.onChange?.();
141
+ return;
142
+ }
143
+
144
+ const deviceId = this.getDeviceId?.();
145
+ if (!deviceId) {
146
+ this.stop();
147
+ this.lastEvent = 'missing-device';
148
+ this.lastError = 'Connect a CloudCLI account before enabling desktop notifications.';
149
+ this.onChange?.();
150
+ return;
151
+ }
152
+
153
+ const targets = (this.getRunningEnvironmentUrls?.() || [])
154
+ .map((httpUrl) => ({
155
+ httpUrl,
156
+ wsUrl: toNotificationsWsUrl(httpUrl),
157
+ }))
158
+ .filter((target) => target.wsUrl);
159
+
160
+ const nextWsUrls = new Set(targets.map((target) => target.wsUrl));
161
+ for (const [wsUrl, connection] of this.connections.entries()) {
162
+ if (!nextWsUrls.has(wsUrl)) {
163
+ this.closeConnection(connection);
164
+ this.connections.delete(wsUrl);
165
+ }
166
+ }
167
+
168
+ for (const target of targets) {
169
+ if (!this.connections.has(target.wsUrl)) {
170
+ void this.connect(target).catch((error) => {
171
+ this.lastEvent = 'connect-error';
172
+ this.lastError = error instanceof Error ? error.message : String(error);
173
+ this.onChange?.();
174
+ });
175
+ }
176
+ }
177
+
178
+ this.lastEvent = targets.length ? 'sync' : 'no-targets';
179
+ this.onChange?.();
180
+ }
181
+
182
+ async connect(target, attempt = 0) {
183
+ const existing = this.connections.get(target.wsUrl);
184
+ if (existing?.ws && [WebSocket.CONNECTING, WebSocket.OPEN].includes(existing.ws.readyState)) {
185
+ return;
186
+ }
187
+
188
+ const connection = {
189
+ ...target,
190
+ ws: null,
191
+ reconnectTimer: null,
192
+ closed: false,
193
+ attempt,
194
+ };
195
+ this.connections.set(target.wsUrl, connection);
196
+
197
+ const headers = await this.getTargetAuthHeaders(target.httpUrl);
198
+ if (connection.closed || this.connections.get(target.wsUrl) !== connection) {
199
+ return;
200
+ }
201
+
202
+ const ws = new WebSocket(target.wsUrl, { headers: Object.keys(headers).length ? headers : undefined });
203
+ connection.ws = ws;
204
+
205
+ ws.on('open', async () => {
206
+ try {
207
+ await this.registerTarget(target.httpUrl);
208
+ ws.send(JSON.stringify({
209
+ type: 'register',
210
+ deviceId: this.getDeviceId?.(),
211
+ label: this.getAccountEmail?.() || this.appName,
212
+ platform: process.platform,
213
+ appVersion: this.appVersion,
214
+ }));
215
+ connection.attempt = 0;
216
+ this.lastEvent = 'connected';
217
+ this.lastError = null;
218
+ this.onChange?.();
219
+ } catch (error) {
220
+ this.lastEvent = 'register-error';
221
+ this.lastError = error instanceof Error ? error.message : String(error);
222
+ this.onChange?.();
223
+ try { ws.close(); } catch {}
224
+ }
225
+ });
226
+
227
+ ws.on('message', (raw) => this.handleMessage(target, ws, raw));
228
+ ws.on('close', () => this.scheduleReconnect(target.wsUrl));
229
+ ws.on('error', (error) => {
230
+ this.lastEvent = 'socket-error';
231
+ this.lastError = error instanceof Error ? error.message : String(error);
232
+ this.onChange?.();
233
+ });
234
+ }
235
+
236
+ async registerTarget(httpUrl) {
237
+ const url = new URL('/api/notifications/endpoints/current', httpUrl).toString();
238
+ await requestJson(url, {
239
+ method: 'POST',
240
+ headers: await this.getTargetAuthHeaders(httpUrl),
241
+ body: {
242
+ channel: 'desktop',
243
+ endpointId: this.getDeviceId?.(),
244
+ label: this.getAccountEmail?.() || this.appName,
245
+ metadata: {
246
+ platform: process.platform,
247
+ appVersion: this.appVersion,
248
+ },
249
+ enabled: true,
250
+ },
251
+ });
252
+ }
253
+
254
+ async disableCurrentTargets() {
255
+ const deviceId = this.getDeviceId?.();
256
+ if (!deviceId) return;
257
+
258
+ const targets = new Set([
259
+ ...[...this.connections.values()].map((connection) => connection.httpUrl).filter(Boolean),
260
+ ...(this.getRunningEnvironmentUrls?.() || []),
261
+ ]);
262
+
263
+ const results = await Promise.allSettled([...targets].map(async (httpUrl) => {
264
+ const url = new URL(`/api/notifications/endpoints/desktop/${encodeURIComponent(deviceId)}`, httpUrl).toString();
265
+ await requestJson(url, {
266
+ method: 'PATCH',
267
+ headers: await this.getTargetAuthHeaders(httpUrl),
268
+ body: { enabled: false },
269
+ });
270
+ }));
271
+
272
+ const rejected = results.find((result) => result.status === 'rejected');
273
+ if (rejected) {
274
+ this.lastEvent = 'disable-endpoint-error';
275
+ this.lastError = rejected.reason instanceof Error ? rejected.reason.message : String(rejected.reason);
276
+ }
277
+ }
278
+
279
+ async getTargetAuthHeaders(httpUrl) {
280
+ const headers = {};
281
+ const apiKey = this.getApiKey?.();
282
+ if (apiKey) {
283
+ headers['X-API-Key'] = apiKey;
284
+ }
285
+
286
+ const authToken = await Promise.resolve(this.getAuthToken?.(httpUrl)).catch(() => null);
287
+ if (authToken) {
288
+ headers.Authorization = `Bearer ${authToken}`;
289
+ }
290
+ return headers;
291
+ }
292
+
293
+ handleMessage(target, ws, raw) {
294
+ const message = readJsonMessage(raw);
295
+ if (!message || message.type !== 'notification' || !message.payload) {
296
+ return;
297
+ }
298
+
299
+ const shown = this.showNativeNotification(target, message.payload);
300
+ if (shown && ws.readyState === WebSocket.OPEN) {
301
+ ws.send(JSON.stringify({
302
+ type: 'notification_ack',
303
+ id: message.id || message.payload?.data?.tag || null,
304
+ action: 'shown',
305
+ }));
306
+ }
307
+ }
308
+
309
+ showNativeNotification(target, payload) {
310
+ if (!Notification.isSupported()) return false;
311
+
312
+ const notification = new Notification({
313
+ title: payload.title || this.appName,
314
+ body: payload.body || '',
315
+ icon: this.getIconPath?.(),
316
+ silent: false,
317
+ });
318
+
319
+ notification.on('click', () => {
320
+ void this.openNotificationTarget?.({
321
+ environmentUrl: target.httpUrl,
322
+ sessionId: payload.data?.sessionId || null,
323
+ provider: payload.data?.provider || null,
324
+ }).catch((error) => {
325
+ this.lastEvent = 'click-error';
326
+ this.lastError = error instanceof Error ? error.message : String(error);
327
+ this.onChange?.();
328
+ });
329
+ });
330
+
331
+ notification.show();
332
+ this.lastEvent = 'notification-shown';
333
+ this.lastError = null;
334
+ this.onChange?.();
335
+ return true;
336
+ }
337
+
338
+ scheduleReconnect(wsUrl) {
339
+ const connection = this.connections.get(wsUrl);
340
+ if (!connection || connection.closed || !this.settings.enabled) {
341
+ return;
342
+ }
343
+
344
+ const attempt = connection.attempt + 1;
345
+ connection.attempt = attempt;
346
+ const delay = Math.min(RECONNECT_MAX_MS, RECONNECT_MIN_MS * (2 ** Math.min(attempt, 5)));
347
+ connection.reconnectTimer = setTimeout(() => {
348
+ if (!this.connections.has(wsUrl) || !this.settings.enabled) return;
349
+ void this.connect({
350
+ httpUrl: connection.httpUrl,
351
+ wsUrl: connection.wsUrl,
352
+ }, attempt).catch((error) => {
353
+ this.lastEvent = 'connect-error';
354
+ this.lastError = error instanceof Error ? error.message : String(error);
355
+ this.onChange?.();
356
+ });
357
+ }, delay);
358
+ this.lastEvent = 'reconnecting';
359
+ this.onChange?.();
360
+ }
361
+
362
+ closeConnection(connection) {
363
+ connection.closed = true;
364
+ if (connection.reconnectTimer) {
365
+ clearTimeout(connection.reconnectTimer);
366
+ connection.reconnectTimer = null;
367
+ }
368
+ try { connection.ws?.close(); } catch {}
369
+ }
370
+
371
+ stop() {
372
+ for (const connection of this.connections.values()) {
373
+ this.closeConnection(connection);
374
+ }
375
+ this.connections.clear();
376
+ this.onChange?.();
377
+ }
378
+ }