@agenthippo-ai/agenthippo 1.110.9 → 1.110.19

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 (533) hide show
  1. package/out/agentpack/types.js +2 -5
  2. package/out/analytics/dashboard/artifactManager.js +2 -439
  3. package/out/analytics/dashboard/fleetDisplayUtils.js +2 -162
  4. package/out/analytics/dashboard/fleetOrchestrator.js +3 -142
  5. package/out/analytics/dashboard/fleetRegistry.js +2 -79
  6. package/out/analytics/dashboard/fleetRowDefaults.js +2 -87
  7. package/out/analytics/dashboard/fleetScriptAdapter.js +2 -146
  8. package/out/analytics/dashboard/fleetTypes.js +2 -29
  9. package/out/analytics/dashboard/index.js +2 -17
  10. package/out/analytics/dashboard/indexingService.js +2 -422
  11. package/out/analytics/dashboard/localFleetAdapter.js +2 -222
  12. package/out/analytics/dashboard/performanceMonitor.js +5 -76
  13. package/out/analytics/dashboard/processManager.js +2 -209
  14. package/out/analytics/dashboard/provider.js +38 -3817
  15. package/out/analytics/dashboard/queryRunner.js +62 -750
  16. package/out/analytics/dashboard/scheduledFleetStorage.js +2 -241
  17. package/out/analytics/dashboard/sessionJsonlFleet.js +3 -204
  18. package/out/analytics/dashboard/teamViewDiscovery.js +2 -106
  19. package/out/analytics/dashboard/webview/panel.js +34 -2318
  20. package/out/analytics/dashboard/webview/runtime-bridge.js +2 -82
  21. package/out/analytics/lifecycle.js +2 -43
  22. package/out/analytics/service.js +8 -1277
  23. package/out/auth/authBaseUrl.js +2 -59
  24. package/out/auth/bootstrapApply.js +2 -71
  25. package/out/auth/brokerClient.js +2 -160
  26. package/out/auth/callbackUri.js +2 -59
  27. package/out/auth/githubStoreGate.js +2 -70
  28. package/out/auth/loopbackAuthServer.js +4 -159
  29. package/out/auth/onboardingPrompt.js +2 -112
  30. package/out/auth/pkce.js +2 -9
  31. package/out/auth/provider.js +2 -226
  32. package/out/auth/sessionStore.js +2 -156
  33. package/out/auth/signInFlow.js +2 -183
  34. package/out/cli/index.js +619 -551
  35. package/out/cli/run-custom-engine-runner.js +3 -318
  36. package/out/cli/vscode-shim.js +2 -86
  37. package/out/engines/factory.js +2 -45
  38. package/out/engines/types.js +2 -17
  39. package/out/examples/run-headless-repl.js +6 -286
  40. package/out/examples/run-headless.js +2 -187
  41. package/out/extension.js +32 -4473
  42. package/out/gateway/gatewayRouteIndex.js +6 -371
  43. package/out/gateway/gatewaySessionStore.js +3 -243
  44. package/out/gateway/index.js +2 -31
  45. package/out/gateway/resetMappings.js +2 -52
  46. package/out/gateway/service.js +5 -1249
  47. package/out/mcp/publish.js +2 -143
  48. package/out/remote/handleRemoteChat.js +2 -169
  49. package/out/remote/remotePackStatusBar.js +3 -103
  50. package/out/remote/remoteTrust.js +6 -277
  51. package/out/remote/serveClient.js +4 -277
  52. package/out/remoteSkills.js +3 -191
  53. package/out/runtime/index.js +2 -32
  54. package/out/runtime/vscode/index.js +2 -20
  55. package/out/runtime/vscode/vscode-config-provider.js +2 -77
  56. package/out/runtime/vscode/vscode-edit-tracker.js +2 -122
  57. package/out/runtime/vscode/vscode-logger.js +2 -30
  58. package/out/runtime/vscode/vscode-permission-handler.js +2 -72
  59. package/out/scripts/agentide-litellm.sh +0 -1
  60. package/out/scripts/agentide_litellm_hooks_v1.py +170 -418
  61. package/out/scripts/analytics/agent-fleet-widget.js +18 -257
  62. package/out/scripts/analytics/agentide-litellm-otel.config.yaml +4 -4
  63. package/out/scripts/analytics/duckdbLocator.js +2 -28
  64. package/out/scripts/analytics/generate-default-charts.py +43 -323
  65. package/out/scripts/analytics/hotspot-analysis-widget.js +4 -430
  66. package/out/scripts/analytics/index-traces.py +78 -1036
  67. package/out/scripts/analytics/session-comparison-widget.js +26 -472
  68. package/out/scripts/analytics/session-timeline-widget.js +25 -846
  69. package/out/scripts/analytics/setup-analytics.ps1 +8 -1
  70. package/out/scripts/analytics/setup-analytics.sh +13 -2
  71. package/out/scripts/analytics/token-cost-flamegraph-widget.js +4 -461
  72. package/out/scripts/analytics/token-cost-trend-widget.js +4 -534
  73. package/out/scripts/analytics/workflows-collector.cjs +4 -314
  74. package/out/scripts/analytics/workflows-daemon.mjs +9 -185
  75. package/out/scripts/analytics/workflows-widget.cjs +15 -159
  76. package/out/scripts/analytics/wow-model-widget.js +10 -120
  77. package/out/scripts/vendor-tools.mjs +2 -233
  78. package/out/skills/publish.js +3 -178
  79. package/out/store/github/setup.js +7 -212
  80. package/out/store/oramaIndex.js +2 -884
  81. package/out/sync.js +26 -1829
  82. package/out/terminalLogger.js +5 -358
  83. package/out/utils/pathSafety.js +2 -60
  84. package/out/utils/pythonRunner.js +4 -258
  85. package/out/utils/toolPath.js +2 -83
  86. package/out/workspaceFolders.js +2 -59
  87. package/package.json +1 -1
  88. package/out/agentpack/github.js +0 -547
  89. package/out/agentpack/index.js +0 -67
  90. package/out/agentpack/install.js +0 -297
  91. package/out/agentpack/manager.js +0 -441
  92. package/out/agentpack/manifest.js +0 -135
  93. package/out/agentpack/paths.js +0 -498
  94. package/out/agentpack/publish.js +0 -248
  95. package/out/agentpack/stores.js +0 -554
  96. package/out/analytics/dashboard/fleetStatusUtils.js +0 -30
  97. package/out/analytics/dashboard/fleetTitleUtils.js +0 -68
  98. package/out/analytics/dashboard/refreshPolicy.js +0 -16
  99. package/out/analytics/dashboard/scheduledFleetFingerprint.js +0 -22
  100. package/out/analytics/dashboard/scheduledFleetUtils.js +0 -22
  101. package/out/analytics/dashboard/sessionEventFleetStats.js +0 -118
  102. package/out/analytics/dashboard/viewsConfigMerge.js +0 -221
  103. package/out/analytics/dashboard/webview/codicon.ttf +0 -0
  104. package/out/analytics/identity.js +0 -0
  105. package/out/analytics/sessionEvents.js +0 -323
  106. package/out/auth/callbackUriParse.js +0 -59
  107. package/out/auth/types.js +0 -12
  108. package/out/auth-broker/config.js +0 -112
  109. package/out/auth-broker/jwt.js +0 -105
  110. package/out/auth-broker/pkce.js +0 -59
  111. package/out/auth-broker/server.js +0 -677
  112. package/out/auth-broker/store.js +0 -275
  113. package/out/authority/warrant.js +0 -206
  114. package/out/base-prompts/mcp-servers/.mcp.json +0 -13
  115. package/out/base-prompts/mcp-servers/langfuse-analytics/README.md +0 -142
  116. package/out/base-prompts/mcp-servers/langfuse-analytics/mcp.json +0 -13
  117. package/out/base-prompts/mcp-servers/langfuse-analytics/package-lock.json +0 -1192
  118. package/out/base-prompts/mcp-servers/langfuse-analytics/package.json +0 -23
  119. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/run-with-env.sh +0 -12
  120. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/sdk-test.js +0 -102
  121. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/simple-test.js +0 -137
  122. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-client.js +0 -118
  123. package/out/base-prompts/mcp-servers/langfuse-analytics/scripts/test-server.js +0 -209
  124. package/out/base-prompts/mcp-servers/langfuse-analytics/src/client.ts +0 -431
  125. package/out/base-prompts/mcp-servers/langfuse-analytics/src/index.ts +0 -270
  126. package/out/base-prompts/mcp-servers/langfuse-analytics/tsconfig.json +0 -18
  127. package/out/base-prompts/rules/AGENTS.md +0 -204
  128. package/out/base-prompts/skills/agent-browser/SKILL.md +0 -102
  129. package/out/base-prompts/skills/agent-builder/SKILL.md +0 -73
  130. package/out/base-prompts/skills/agent-builder/references/agent-pack.md +0 -63
  131. package/out/base-prompts/skills/agent-builder/references/crewai.md +0 -66
  132. package/out/base-prompts/skills/agent-builder/references/langchain.md +0 -60
  133. package/out/base-prompts/skills/agent-builder/references/raw-sdk.md +0 -73
  134. package/out/base-prompts/skills/agent-engine-creator/SKILL.md +0 -586
  135. package/out/base-prompts/skills/agent-engine-installer/SKILL.md +0 -106
  136. package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.mjs +0 -259
  137. package/out/base-prompts/skills/agent-engine-installer/scripts/install-engine.ps1 +0 -189
  138. package/out/base-prompts/skills/agenthippo-cli/SKILL.md +0 -69
  139. package/out/base-prompts/skills/agenthippo-cli/scripts/install.sh +0 -63
  140. package/out/base-prompts/skills/agentpack-creator/SKILL.md +0 -375
  141. package/out/base-prompts/skills/agentpack-creator/references/agent-templates.md +0 -261
  142. package/out/base-prompts/skills/agentpack-creator/references/agents-md-guide.md +0 -195
  143. package/out/base-prompts/skills/agentpack-creator/scripts/init_pack.py +0 -391
  144. package/out/base-prompts/skills/agentpack-creator/scripts/init_pack_test.py +0 -294
  145. package/out/base-prompts/skills/agentpack-testbench/SKILL.md +0 -135
  146. package/out/base-prompts/skills/algorithmic-art/LICENSE.txt +0 -202
  147. package/out/base-prompts/skills/algorithmic-art/SKILL.md +0 -405
  148. package/out/base-prompts/skills/algorithmic-art/templates/generator_template.js +0 -223
  149. package/out/base-prompts/skills/algorithmic-art/templates/viewer.html +0 -599
  150. package/out/base-prompts/skills/debug-agent/SKILL.md +0 -83
  151. package/out/base-prompts/skills/desktop-automation/SKILL.md +0 -166
  152. package/out/base-prompts/skills/desktop-automation/references/agenthippo-ui-recipes.md +0 -441
  153. package/out/base-prompts/skills/desktop-automation/scripts/windows-desktop-uia.psm1 +0 -280
  154. package/out/base-prompts/skills/docx/LICENSE.txt +0 -30
  155. package/out/base-prompts/skills/docx/SKILL.md +0 -197
  156. package/out/base-prompts/skills/docx/docx-js.md +0 -350
  157. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
  158. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
  159. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
  160. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
  161. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
  162. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
  163. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
  164. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
  165. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
  166. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
  167. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
  168. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
  169. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
  170. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
  171. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
  172. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
  173. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
  174. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
  175. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
  176. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
  177. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
  178. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
  179. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
  180. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
  181. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
  182. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
  183. package/out/base-prompts/skills/docx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
  184. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
  185. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
  186. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
  187. package/out/base-prompts/skills/docx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
  188. package/out/base-prompts/skills/docx/ooxml/schemas/mce/mc.xsd +0 -75
  189. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2010.xsd +0 -560
  190. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2012.xsd +0 -67
  191. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-2018.xsd +0 -14
  192. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cex-2018.xsd +0 -20
  193. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-cid-2016.xsd +0 -13
  194. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
  195. package/out/base-prompts/skills/docx/ooxml/schemas/microsoft/wml-symex-2015.xsd +0 -8
  196. package/out/base-prompts/skills/docx/ooxml/scripts/pack.py +0 -159
  197. package/out/base-prompts/skills/docx/ooxml/scripts/unpack.py +0 -29
  198. package/out/base-prompts/skills/docx/ooxml/scripts/validate.py +0 -69
  199. package/out/base-prompts/skills/docx/ooxml/scripts/validation/__init__.py +0 -15
  200. package/out/base-prompts/skills/docx/ooxml/scripts/validation/base.py +0 -951
  201. package/out/base-prompts/skills/docx/ooxml/scripts/validation/docx.py +0 -274
  202. package/out/base-prompts/skills/docx/ooxml/scripts/validation/pptx.py +0 -315
  203. package/out/base-prompts/skills/docx/ooxml/scripts/validation/redlining.py +0 -279
  204. package/out/base-prompts/skills/docx/ooxml.md +0 -610
  205. package/out/base-prompts/skills/docx/scripts/__init__.py +0 -1
  206. package/out/base-prompts/skills/docx/scripts/document.py +0 -1276
  207. package/out/base-prompts/skills/docx/scripts/templates/comments.xml +0 -3
  208. package/out/base-prompts/skills/docx/scripts/templates/commentsExtended.xml +0 -3
  209. package/out/base-prompts/skills/docx/scripts/templates/commentsExtensible.xml +0 -3
  210. package/out/base-prompts/skills/docx/scripts/templates/commentsIds.xml +0 -3
  211. package/out/base-prompts/skills/docx/scripts/templates/people.xml +0 -3
  212. package/out/base-prompts/skills/docx/scripts/utilities.py +0 -374
  213. package/out/base-prompts/skills/gateway-outbound/SKILL.md +0 -136
  214. package/out/base-prompts/skills/mcp-builder/LICENSE.txt +0 -202
  215. package/out/base-prompts/skills/mcp-builder/SKILL.md +0 -328
  216. package/out/base-prompts/skills/mcp-builder/reference/evaluation.md +0 -602
  217. package/out/base-prompts/skills/mcp-builder/reference/mcp_best_practices.md +0 -249
  218. package/out/base-prompts/skills/mcp-builder/reference/node_mcp_server.md +0 -970
  219. package/out/base-prompts/skills/mcp-builder/reference/python_mcp_server.md +0 -719
  220. package/out/base-prompts/skills/mcp-builder/scripts/connections.py +0 -151
  221. package/out/base-prompts/skills/mcp-builder/scripts/evaluation.py +0 -373
  222. package/out/base-prompts/skills/mcp-builder/scripts/example_evaluation.xml +0 -22
  223. package/out/base-prompts/skills/mcp-builder/scripts/requirements.txt +0 -2
  224. package/out/base-prompts/skills/openclaw/SKILL.md +0 -406
  225. package/out/base-prompts/skills/openclaw/scripts/run-smoke.sh +0 -61
  226. package/out/base-prompts/skills/pdf/LICENSE.txt +0 -30
  227. package/out/base-prompts/skills/pdf/SKILL.md +0 -294
  228. package/out/base-prompts/skills/pdf/forms.md +0 -205
  229. package/out/base-prompts/skills/pdf/reference.md +0 -612
  230. package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes.py +0 -70
  231. package/out/base-prompts/skills/pdf/scripts/check_bounding_boxes_test.py +0 -226
  232. package/out/base-prompts/skills/pdf/scripts/check_fillable_fields.py +0 -12
  233. package/out/base-prompts/skills/pdf/scripts/convert_pdf_to_images.py +0 -35
  234. package/out/base-prompts/skills/pdf/scripts/create_validation_image.py +0 -41
  235. package/out/base-prompts/skills/pdf/scripts/extract_form_field_info.py +0 -152
  236. package/out/base-prompts/skills/pdf/scripts/fill_fillable_fields.py +0 -114
  237. package/out/base-prompts/skills/pdf/scripts/fill_pdf_form_with_annotations.py +0 -108
  238. package/out/base-prompts/skills/pptx/LICENSE.txt +0 -30
  239. package/out/base-prompts/skills/pptx/SKILL.md +0 -484
  240. package/out/base-prompts/skills/pptx/html2pptx.md +0 -625
  241. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chart.xsd +0 -1499
  242. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-chartDrawing.xsd +0 -146
  243. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-diagram.xsd +0 -1085
  244. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-lockedCanvas.xsd +0 -11
  245. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-main.xsd +0 -3081
  246. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-picture.xsd +0 -23
  247. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-spreadsheetDrawing.xsd +0 -185
  248. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/dml-wordprocessingDrawing.xsd +0 -287
  249. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/pml.xsd +0 -1676
  250. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-additionalCharacteristics.xsd +0 -28
  251. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-bibliography.xsd +0 -144
  252. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-commonSimpleTypes.xsd +0 -174
  253. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlDataProperties.xsd +0 -25
  254. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-customXmlSchemaProperties.xsd +0 -18
  255. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesCustom.xsd +0 -59
  256. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesExtended.xsd +0 -56
  257. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-documentPropertiesVariantTypes.xsd +0 -195
  258. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-math.xsd +0 -582
  259. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/shared-relationshipReference.xsd +0 -25
  260. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/sml.xsd +0 -4439
  261. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-main.xsd +0 -570
  262. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-officeDrawing.xsd +0 -509
  263. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-presentationDrawing.xsd +0 -12
  264. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-spreadsheetDrawing.xsd +0 -108
  265. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/vml-wordprocessingDrawing.xsd +0 -96
  266. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/wml.xsd +0 -3646
  267. package/out/base-prompts/skills/pptx/ooxml/schemas/ISO-IEC29500-4_2016/xml.xsd +0 -116
  268. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-contentTypes.xsd +0 -42
  269. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-coreProperties.xsd +0 -50
  270. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-digSig.xsd +0 -49
  271. package/out/base-prompts/skills/pptx/ooxml/schemas/ecma/fouth-edition/opc-relationships.xsd +0 -33
  272. package/out/base-prompts/skills/pptx/ooxml/schemas/mce/mc.xsd +0 -75
  273. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2010.xsd +0 -560
  274. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2012.xsd +0 -67
  275. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-2018.xsd +0 -14
  276. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cex-2018.xsd +0 -20
  277. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-cid-2016.xsd +0 -13
  278. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-sdtdatahash-2020.xsd +0 -4
  279. package/out/base-prompts/skills/pptx/ooxml/schemas/microsoft/wml-symex-2015.xsd +0 -8
  280. package/out/base-prompts/skills/pptx/ooxml/scripts/pack.py +0 -159
  281. package/out/base-prompts/skills/pptx/ooxml/scripts/unpack.py +0 -29
  282. package/out/base-prompts/skills/pptx/ooxml/scripts/validate.py +0 -69
  283. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/__init__.py +0 -15
  284. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/base.py +0 -951
  285. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/docx.py +0 -274
  286. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/pptx.py +0 -315
  287. package/out/base-prompts/skills/pptx/ooxml/scripts/validation/redlining.py +0 -279
  288. package/out/base-prompts/skills/pptx/ooxml.md +0 -427
  289. package/out/base-prompts/skills/pptx/scripts/html2pptx.js +0 -984
  290. package/out/base-prompts/skills/pptx/scripts/inventory.py +0 -1020
  291. package/out/base-prompts/skills/pptx/scripts/rearrange.py +0 -231
  292. package/out/base-prompts/skills/pptx/scripts/replace.py +0 -385
  293. package/out/base-prompts/skills/pptx/scripts/thumbnail.py +0 -450
  294. package/out/base-prompts/skills/prompt-lab/SKILL.md +0 -149
  295. package/out/base-prompts/skills/prompt-lab/scripts/query-analytics.sh +0 -2551
  296. package/out/base-prompts/skills/skill-creator/LICENSE.txt +0 -202
  297. package/out/base-prompts/skills/skill-creator/SKILL.md +0 -371
  298. package/out/base-prompts/skills/skill-creator/references/output-patterns.md +0 -82
  299. package/out/base-prompts/skills/skill-creator/references/workflows.md +0 -28
  300. package/out/base-prompts/skills/skill-creator/scripts/init_skill.py +0 -303
  301. package/out/base-prompts/skills/skill-creator/scripts/package_skill.py +0 -110
  302. package/out/base-prompts/skills/skill-creator/scripts/quick_validate.py +0 -95
  303. package/out/base-prompts/skills/skill-installer/SKILL.md +0 -251
  304. package/out/base-prompts/skills/skill-installer/references/store-api.md +0 -219
  305. package/out/base-prompts/skills/spotlight-dashboard/SKILL.md +0 -83
  306. package/out/base-prompts/skills/spotlight-insights/SKILL.md +0 -101
  307. package/out/base-prompts/skills/spotlight-optimize/SKILL.md +0 -93
  308. package/out/base-prompts/skills/spotlight-optimize/scripts/query-analytics.sh +0 -2551
  309. package/out/base-prompts/skills/spotlight-query/SKILL.md +0 -314
  310. package/out/base-prompts/skills/spotlight-query/references/otel-schema.md +0 -163
  311. package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.sh +0 -2551
  312. package/out/base-prompts/skills/spotlight-query/scripts/query-analytics.test.sh +0 -744
  313. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/sessions/sess-test-1.jsonl +0 -14
  314. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/.duckdb-index.json +0 -7
  315. package/out/base-prompts/skills/spotlight-query/scripts/test-fixtures/analytics/traces/sample.jsonl +0 -5
  316. package/out/base-prompts/skills/spotlight-report/SKILL.md +0 -86
  317. package/out/base-prompts/skills/spotlight-visualize/SKILL.md +0 -92
  318. package/out/base-prompts/skills/spotlight-visualize/scripts/query-analytics.sh +0 -2551
  319. package/out/base-prompts/skills/xlsx/LICENSE.txt +0 -30
  320. package/out/base-prompts/skills/xlsx/SKILL.md +0 -289
  321. package/out/base-prompts/skills/xlsx/recalc.py +0 -178
  322. package/out/branding.js +0 -49
  323. package/out/cli/commands/anywhere.js +0 -66
  324. package/out/cli/commands/ask.js +0 -246
  325. package/out/cli/commands/auth-broker.js +0 -24
  326. package/out/cli/commands/auth.js +0 -219
  327. package/out/cli/commands/chat.js +0 -357
  328. package/out/cli/commands/conformance.js +0 -249
  329. package/out/cli/commands/data-plane.js +0 -67
  330. package/out/cli/commands/deploy.js +0 -263
  331. package/out/cli/commands/gateway.js +0 -218
  332. package/out/cli/commands/idp.js +0 -85
  333. package/out/cli/commands/pack.js +0 -399
  334. package/out/cli/commands/probe-custom-engine.js +0 -82
  335. package/out/cli/commands/sandbox.js +0 -332
  336. package/out/cli/commands/serve.js +0 -1758
  337. package/out/cli/commands/store.js +0 -927
  338. package/out/cli/credential-context.js +0 -91
  339. package/out/cli/data-plane/broker.js +0 -78
  340. package/out/cli/data-plane/connector-host.js +0 -121
  341. package/out/cli/data-plane/egress.js +0 -189
  342. package/out/cli/data-plane/gateway.js +0 -218
  343. package/out/cli/data-plane/index.js +0 -152
  344. package/out/cli/data-plane/registry.js +0 -89
  345. package/out/cli/data-plane/remote.js +0 -106
  346. package/out/cli/data-plane/sidecar.js +0 -161
  347. package/out/cli/deploy/blueprints/registry.js +0 -84
  348. package/out/cli/deploy/layers/identity.js +0 -172
  349. package/out/cli/deploy-init.js +0 -3023
  350. package/out/cli/deps.js +0 -342
  351. package/out/cli/idp-init.js +0 -338
  352. package/out/cli/pack-install-ref.js +0 -15
  353. package/out/cli/pack-loader.js +0 -418
  354. package/out/cli/pack-runtime.js +0 -61
  355. package/out/cli/runtime.js +0 -450
  356. package/out/cli/serve-hardening.js +0 -607
  357. package/out/cli/state-sync.js +0 -424
  358. package/out/cli/utils.js +0 -231
  359. package/out/engines/base.js +0 -188
  360. package/out/engines/claude.js +0 -1254
  361. package/out/engines/codex.js +0 -862
  362. package/out/engines/custom.js +0 -674
  363. package/out/engines/customEngineIpc.js +0 -95
  364. package/out/engines/customEngineRunnerEmbed.js +0 -107
  365. package/out/engines/customRegistry.js +0 -274
  366. package/out/engines/embeddedCustomEngineRunner.generated.js +0 -9
  367. package/out/engines/engineApiKey.js +0 -138
  368. package/out/engines/install.js +0 -372
  369. package/out/engines/persistentEngineWorker.js +0 -320
  370. package/out/engines/publish.js +0 -341
  371. package/out/evidence/sessionShip.js +0 -610
  372. package/out/gateway/bridge.js +0 -843
  373. package/out/gateway/cli-agent-id.js +0 -36
  374. package/out/gateway/client.js +0 -336
  375. package/out/gateway/client.test.js +0 -395
  376. package/out/gateway/gateway-scope-errors.js +0 -14
  377. package/out/gateway/gatewayBackup.js +0 -76
  378. package/out/gateway/installSource.js +0 -68
  379. package/out/gateway/lifecycle.js +0 -3190
  380. package/out/gateway/openclaw-channel-routing.js +0 -317
  381. package/out/gateway/outboundHandles.js +0 -373
  382. package/out/gateway/outboundPaths.js +0 -75
  383. package/out/gateway/outboundSend.js +0 -254
  384. package/out/gateway/protocol.js +0 -48
  385. package/out/gateway/secretsPreflight.js +0 -90
  386. package/out/gateway/sessionRouting.js +0 -77
  387. package/out/headless/index.js +0 -539
  388. package/out/headless/sync.js +0 -473
  389. package/out/live-e2e/mcp-demo/server.mjs +0 -32
  390. package/out/live-probe/connector.mjs +0 -8
  391. package/out/live-probe/env-report.mjs +0 -8
  392. package/out/mcp.js +0 -654
  393. package/out/pack/caps.js +0 -271
  394. package/out/pack/governor.js +0 -309
  395. package/out/pack/lease-schema.js +0 -270
  396. package/out/pack/lease.js +0 -640
  397. package/out/pack/reconcile.js +0 -217
  398. package/out/pack/signature.js +0 -839
  399. package/out/permissions.js +0 -108
  400. package/out/permissions.mapping.yaml +0 -68
  401. package/out/registry/index.js +0 -332
  402. package/out/runner/host.js +0 -131
  403. package/out/runner/protocol.js +0 -108
  404. package/out/runner/remoteEngine.js +0 -130
  405. package/out/runtime/interfaces/config-provider.js +0 -32
  406. package/out/runtime/interfaces/edit-tracker.js +0 -24
  407. package/out/runtime/interfaces/index.js +0 -29
  408. package/out/runtime/interfaces/logger.js +0 -40
  409. package/out/runtime/interfaces/permission-handler.js +0 -66
  410. package/out/runtime/interfaces/session-store.js +0 -342
  411. package/out/runtime/vscode/vscode-session-store.js +0 -22
  412. package/out/sandbox/containerDriver.js +0 -570
  413. package/out/sandbox/processDriver.js +0 -315
  414. package/out/sandbox/sandboxRunner.js +0 -191
  415. package/out/sandbox/support.js +0 -301
  416. package/out/sandbox/types.js +0 -55
  417. package/out/scripts/analytics/fleet-local-adapter.js +0 -246
  418. package/out/scripts/analytics/live-fleet-4-collector.cjs +0 -315
  419. package/out/scripts/analytics/live-fleet-4-daemon.mjs +0 -134
  420. package/out/scripts/analytics/live-fleet-4-widget.cjs +0 -433
  421. package/out/scripts/gateway/Dockerfile.serve +0 -45
  422. package/out/scripts/gateway/docker-serve.sh +0 -248
  423. package/out/scripts/gateway/setup-gateway-server.ps1 +0 -953
  424. package/out/scripts/gateway/setup-gateway-server.sh +0 -935
  425. package/out/session.js +0 -272
  426. package/out/shared/agentEngines.js +0 -170
  427. package/out/shared/index.js +0 -109
  428. package/out/shared/models.js +0 -136
  429. package/out/shared/providers.js +0 -205
  430. package/out/spotlight/install.js +0 -345
  431. package/out/spotlight/publish.js +0 -544
  432. package/out/spotlight/yaml.js +0 -31
  433. package/out/store/index.js +0 -786
  434. package/out/store/shared.js +0 -241
  435. package/out/test/agentEnvFile.test.js +0 -97
  436. package/out/test/agentYaml.test.js +0 -373
  437. package/out/test/agentpack.test.js +0 -639
  438. package/out/test/analytics-lifecycle.test.js +0 -174
  439. package/out/test/analytics.test.js +0 -756
  440. package/out/test/auth-baseurl.test.js +0 -132
  441. package/out/test/auth-bootstrapApply.test.js +0 -123
  442. package/out/test/auth-broker-hardening.test.js +0 -227
  443. package/out/test/auth-broker.test.js +0 -541
  444. package/out/test/auth-callback.test.js +0 -62
  445. package/out/test/blueprint-registry.test.js +0 -105
  446. package/out/test/branding.test.js +0 -242
  447. package/out/test/caps-governor.test.js +0 -455
  448. package/out/test/cli-agent-id.test.js +0 -55
  449. package/out/test/codex-new-file-survival.test.js +0 -119
  450. package/out/test/configuration-scopes.test.js +0 -131
  451. package/out/test/credential-context.test.js +0 -160
  452. package/out/test/custom-env-layering.test.js +0 -151
  453. package/out/test/customEngineInstall.test.js +0 -100
  454. package/out/test/customRegistry.test.js +0 -122
  455. package/out/test/data-plane-engine-wiring.test.js +0 -250
  456. package/out/test/data-plane.test.js +0 -311
  457. package/out/test/deploy-init.test.js +0 -854
  458. package/out/test/deps-auto-install.test.js +0 -81
  459. package/out/test/egress-broker.test.js +0 -172
  460. package/out/test/engine-integration.test.js +0 -297
  461. package/out/test/enginePaths.test.js +0 -195
  462. package/out/test/enginePublish.test.js +0 -82
  463. package/out/test/fleetDisplayUtils.test.js +0 -100
  464. package/out/test/gateway-client.test.js +0 -409
  465. package/out/test/gateway-install-source.test.js +0 -111
  466. package/out/test/gateway-lifecycle.test.js +0 -608
  467. package/out/test/gateway-outbound.test.js +0 -538
  468. package/out/test/gateway-reset-mappings.test.js +0 -221
  469. package/out/test/gateway-route-index.test.js +0 -235
  470. package/out/test/gateway-secrets-preflight.test.js +0 -127
  471. package/out/test/gateway-session-routing.test.js +0 -65
  472. package/out/test/gateway-session-store.test.js +0 -216
  473. package/out/test/github.test.js +0 -232
  474. package/out/test/governor-hardening.test.js +0 -217
  475. package/out/test/headless-sync.test.js +0 -301
  476. package/out/test/helpers/manifest-fixtures.js +0 -27
  477. package/out/test/idp-init.test.js +0 -151
  478. package/out/test/lease-signature.test.js +0 -357
  479. package/out/test/lease-trust-root.test.js +0 -273
  480. package/out/test/lease-validation.test.js +0 -169
  481. package/out/test/lease.test.js +0 -114
  482. package/out/test/mcp-obo.test.js +0 -107
  483. package/out/test/mcp.test.js +0 -389
  484. package/out/test/model-routing.test.js +0 -133
  485. package/out/test/openclaw-channel-routing.test.js +0 -191
  486. package/out/test/pack-admission.test.js +0 -216
  487. package/out/test/pack-digest-parity.test.js +0 -140
  488. package/out/test/pack-reconcile.test.js +0 -183
  489. package/out/test/pack-signature-artifact.test.js +0 -103
  490. package/out/test/pack-signature.test.js +0 -176
  491. package/out/test/pack-trust-root.test.js +0 -202
  492. package/out/test/pathSafety.test.js +0 -63
  493. package/out/test/permissions.test.js +0 -175
  494. package/out/test/persistent-engine-worker.test.js +0 -220
  495. package/out/test/refreshPolicy.test.js +0 -60
  496. package/out/test/registry.test.js +0 -351
  497. package/out/test/remote-trust.test.js +0 -242
  498. package/out/test/runner-protocol.test.js +0 -249
  499. package/out/test/runtime.test.js +0 -349
  500. package/out/test/sandbox-driver.test.js +0 -553
  501. package/out/test/sandbox-hardening.test.js +0 -395
  502. package/out/test/sandbox-runner.test.js +0 -343
  503. package/out/test/scheduledFleetStorage.test.js +0 -72
  504. package/out/test/serve-gateway-turn.test.js +0 -267
  505. package/out/test/serve-hardening.test.js +0 -418
  506. package/out/test/serveClient.test.js +0 -209
  507. package/out/test/sessionJsonlFleet.test.js +0 -78
  508. package/out/test/sessionShip.test.js +0 -560
  509. package/out/test/shared.test.js +0 -173
  510. package/out/test/skills-mcp.test.js +0 -299
  511. package/out/test/spotlightPublish.test.js +0 -327
  512. package/out/test/spotlightYaml.test.js +0 -58
  513. package/out/test/state-sync-peer.test.js +0 -156
  514. package/out/test/store-install-safe.test.js +0 -69
  515. package/out/test/store.test.js +0 -331
  516. package/out/test/stores.test.js +0 -504
  517. package/out/test/sync-switch.test.js +0 -251
  518. package/out/test/teamViewDiscovery.test.js +0 -90
  519. package/out/test/tls-bypass.test.js +0 -90
  520. package/out/test/toolPath.test.js +0 -120
  521. package/out/test/utils.test.js +0 -308
  522. package/out/test/viewsConfigMerge.test.js +0 -180
  523. package/out/test/warrant.test.js +0 -138
  524. package/out/test/windows-command-line.test.js +0 -75
  525. package/out/utils/agentEnvFile.js +0 -131
  526. package/out/utils/binary.js +0 -175
  527. package/out/utils/index.js +0 -37
  528. package/out/utils/network.js +0 -74
  529. package/out/utils/paths.js +0 -116
  530. package/out/utils/pid.js +0 -180
  531. package/out/utils/resolveNodeBinary.js +0 -168
  532. package/out/utils/windowsCommandLine.js +0 -29
  533. package/out/utils.js +0 -968
@@ -1,3190 +0,0 @@
1
- "use strict";
2
- /*---------------------------------------------------------------------------------------------
3
- * Copyright (c) AgentHippo.ai. All rights reserved.
4
- *--------------------------------------------------------------------------------------------*/
5
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- var desc = Object.getOwnPropertyDescriptor(m, k);
8
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9
- desc = { enumerable: true, get: function() { return m[k]; } };
10
- }
11
- Object.defineProperty(o, k2, desc);
12
- }) : (function(o, m, k, k2) {
13
- if (k2 === undefined) k2 = k;
14
- o[k2] = m[k];
15
- }));
16
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
17
- Object.defineProperty(o, "default", { enumerable: true, value: v });
18
- }) : function(o, v) {
19
- o["default"] = v;
20
- });
21
- var __importStar = (this && this.__importStar) || (function () {
22
- var ownKeys = function(o) {
23
- ownKeys = Object.getOwnPropertyNames || function (o) {
24
- var ar = [];
25
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
26
- return ar;
27
- };
28
- return ownKeys(o);
29
- };
30
- return function (mod) {
31
- if (mod && mod.__esModule) return mod;
32
- var result = {};
33
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
34
- __setModuleDefault(result, mod);
35
- return result;
36
- };
37
- })();
38
- Object.defineProperty(exports, "__esModule", { value: true });
39
- exports.GatewayLifecycleManager = exports.validateAnywhereInstallSource = exports.ANYWHERE_SETUP_SCRIPT_ENV = exports.ANYWHERE_INSTALL_SOURCE_ENV = exports.ANYWHERE_CONFIG_TEMPLATE_ENV = exports.AGENT_ANYWHERE_LABEL = void 0;
40
- exports.detectGateway = detectGateway;
41
- /* eslint-disable local/code-no-unexternalized-strings */
42
- /**
43
- * Agent Anywhere -- Gateway Lifecycle Manager
44
- *
45
- * Detects, installs, starts, stops, and monitors the OpenClaw Gateway process.
46
- * All actions are opt-in -- nothing auto-installs or auto-starts without explicit user action.
47
- *
48
- * Branding: the user-facing feature is called "Agent Anywhere".
49
- * Internals still use "gateway" for code references.
50
- */
51
- const vscode = __importStar(require("vscode"));
52
- const child_process_1 = require("child_process");
53
- const crypto_1 = require("crypto");
54
- const util_1 = require("util");
55
- const fs = __importStar(require("fs"));
56
- const path = __importStar(require("path"));
57
- const os = __importStar(require("os"));
58
- const index_1 = require("../utils/index");
59
- const agentEnvFile_js_1 = require("../utils/agentEnvFile.js");
60
- const secretsPreflight_js_1 = require("./secretsPreflight.js");
61
- const windowsCommandLine_js_1 = require("../utils/windowsCommandLine.js");
62
- const gateway_scope_errors_js_1 = require("./gateway-scope-errors.js");
63
- const openclaw_channel_routing_js_1 = require("./openclaw-channel-routing.js");
64
- const branding_js_1 = require("../branding.js");
65
- const execAsync = (0, util_1.promisify)(child_process_1.exec);
66
- const execFileAsync = (0, util_1.promisify)(child_process_1.execFile);
67
- const IS_WINDOWS = process.platform === "win32";
68
- const IS_MACOS = process.platform === "darwin";
69
- function addPathEntries(acc, value) {
70
- if (!value) {
71
- return;
72
- }
73
- for (const entry of value.split(path.delimiter)) {
74
- const trimmed = entry.trim();
75
- if (trimmed.length > 0) {
76
- acc.add(trimmed);
77
- }
78
- }
79
- }
80
- /**
81
- * Build a resilient PATH for setup terminals launched from signed macOS apps (Finder launch),
82
- * where process.env.PATH can be minimal and miss Homebrew/nvm/pnpm locations.
83
- */
84
- function buildAnywhereSetupPath() {
85
- const entries = new Set();
86
- const home = os.homedir();
87
- // Always keep core system paths first.
88
- addPathEntries(entries, "/usr/bin:/bin:/usr/sbin:/sbin");
89
- addPathEntries(entries, process.env.PATH);
90
- const commonDirs = [
91
- path.join(home, ".local", "bin"),
92
- path.join(home, "Library", "pnpm"),
93
- path.join(home, ".local", "share", "pnpm"),
94
- path.join(home, ".npm-global", "bin"),
95
- path.join(home, ".volta", "bin"),
96
- path.join(home, ".fnm"),
97
- path.join(home, ".asdf", "shims"),
98
- "/opt/homebrew/bin",
99
- "/usr/local/bin",
100
- ];
101
- for (const dir of commonDirs) {
102
- if (fs.existsSync(dir)) {
103
- entries.add(dir);
104
- }
105
- }
106
- // Include available nvm Node bin dirs (newest first) if present.
107
- const nvmDir = process.env.NVM_DIR || path.join(home, ".nvm");
108
- const nvmVersionsDir = path.join(nvmDir, "versions", "node");
109
- if (fs.existsSync(nvmVersionsDir)) {
110
- try {
111
- const versions = fs
112
- .readdirSync(nvmVersionsDir, { withFileTypes: true })
113
- .filter((d) => d.isDirectory())
114
- .map((d) => d.name)
115
- .sort()
116
- .reverse()
117
- .slice(0, 8);
118
- for (const version of versions) {
119
- const binDir = path.join(nvmVersionsDir, version, "bin");
120
- if (fs.existsSync(binDir)) {
121
- entries.add(binDir);
122
- }
123
- }
124
- }
125
- catch {
126
- // Ignore nvm directory read errors; fallback PATH still works.
127
- }
128
- }
129
- return Array.from(entries).join(path.delimiter);
130
- }
131
- function getAnywhereSetupShellPath() {
132
- if (IS_MACOS && fs.existsSync("/bin/zsh")) {
133
- return "/bin/zsh";
134
- }
135
- return "/bin/bash";
136
- }
137
- function getAnywhereSetupShellArgs(command) {
138
- // macOS: use login+interactive zsh so ~/.zprofile and ~/.zshrc can expose node/npm/pnpm.
139
- if (IS_MACOS) {
140
- return ["-lic", command];
141
- }
142
- // Linux: login bash is enough for common system/profile PATH setups.
143
- return ["-lc", command];
144
- }
145
- /**
146
- * Env for gateway setup terminals: npm's lifecycle runner spawns bare `sh` unless script-shell
147
- * is set; @npmcli/run-script can rebuild PATH without /bin when the parent PATH is empty (IDE hosts).
148
- */
149
- function anywhereSetupTerminalEnv() {
150
- if (IS_WINDOWS) {
151
- return undefined;
152
- }
153
- return {
154
- ...process.env,
155
- PATH: buildAnywhereSetupPath(),
156
- npm_config_script_shell: "/bin/sh",
157
- };
158
- }
159
- // ============================================================================
160
- // Constants
161
- // ============================================================================
162
- /** User-facing feature name */
163
- exports.AGENT_ANYWHERE_LABEL = "Agent Anywhere";
164
- const AGENT_ANYWHERE_SETUP_TERMINAL = `${exports.AGENT_ANYWHERE_LABEL} Setup`;
165
- const AGENT_ANYWHERE_UNINSTALL_TERMINAL = `${exports.AGENT_ANYWHERE_LABEL} Uninstall`;
166
- /** Default gateway port */
167
- const DEFAULT_PORT = 18789;
168
- /** Persistent gateway stdout/stderr log (see doStart). */
169
- const DEFAULT_GATEWAY_LOG_PATH = path.join(os.homedir(), ".openclaw", "gateway.log");
170
- const OPEN_GATEWAY_LOG_ACTION = "Open Gateway Log";
171
- function stripAnsi(text) {
172
- return text.replace(/\x1b\[[0-9;]*m/g, "");
173
- }
174
- /** Last actionable error line from gateway.log for startup failure toasts. */
175
- function readLastGatewayLogHint(logPath) {
176
- try {
177
- if (!fs.existsSync(logPath)) {
178
- return undefined;
179
- }
180
- const content = fs.readFileSync(logPath, "utf8");
181
- const lines = content.split(/\r?\n/).filter((line) => line.trim());
182
- for (let i = lines.length - 1; i >= Math.max(0, lines.length - 50); i--) {
183
- const plain = stripAnsi(lines[i]).trim();
184
- if (!plain || /Doctor warnings|force: no listeners/i.test(plain)) {
185
- continue;
186
- }
187
- const gatewayFailed = plain.match(/Gateway failed to start:\s*(.+)/i);
188
- if (gatewayFailed?.[1]) {
189
- return gatewayFailed[1].trim().slice(0, 160);
190
- }
191
- if (/SecretRefResolutionError|is missing or empty|required secrets are unavailable/i.test(plain)) {
192
- return plain.replace(/^\d{4}-\d{2}-\d{2}T[\d:.+-]+\s*/, "").slice(0, 160);
193
- }
194
- if (/errorCode=|errorMessage=/i.test(plain)) {
195
- return plain.slice(0, 160);
196
- }
197
- }
198
- }
199
- catch {
200
- // ignore read errors
201
- }
202
- return undefined;
203
- }
204
- async function showGatewayStartFailedToast(logPath) {
205
- const hint = readLastGatewayLogHint(logPath);
206
- const message = hint
207
- ? `${exports.AGENT_ANYWHERE_LABEL}: Gateway failed to start. ${hint}`
208
- : `${exports.AGENT_ANYWHERE_LABEL}: Gateway failed to start.`;
209
- const choice = await vscode.window.showErrorMessage(message, OPEN_GATEWAY_LOG_ACTION);
210
- if (choice === OPEN_GATEWAY_LOG_ACTION) {
211
- try {
212
- const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(logPath));
213
- await vscode.window.showTextDocument(doc, { preview: false });
214
- }
215
- catch (err) {
216
- const detail = err instanceof Error ? err.message : String(err);
217
- void vscode.window.showErrorMessage(`${exports.AGENT_ANYWHERE_LABEL}: Could not open gateway log (${detail}). Path: ${logPath}`);
218
- }
219
- }
220
- }
221
- /** Health check interval in ms */
222
- const HEALTH_CHECK_INTERVAL_MS = 30_000;
223
- /** Gateway startup timeout in ms (Windows cold starts can take longer). */
224
- const STARTUP_TIMEOUT_MS = IS_WINDOWS ? 75_000 : 20_000;
225
- /**
226
- * Only remove a gateway lock file when it is at least this old. Avoids a TOCTOU race where
227
- * concurrent starts see "lock exists, port not listening yet" and delete another instance's fresh lock.
228
- */
229
- const STALE_GATEWAY_LOCK_MIN_AGE_MS = 30_000;
230
- /** PID file for fast status checks (~1-2ms vs 100ms+ for shell commands) */
231
- const GATEWAY_PID_FILE = index_1.PID_FILES.gateway();
232
- const FORK_CLI_NAME = "hippoclaw";
233
- const GATEWAY_NPM_PACKAGE_DIRS = ["@agenthippo-ai/hippoclaw", "openclaw"];
234
- const MANAGED_GATEWAY_STATE_DIR = path.join(os.homedir(), ".agenthippo", "gateway");
235
- const MANAGED_GATEWAY_MANIFEST_PATH = path.join(MANAGED_GATEWAY_STATE_DIR, "install.json");
236
- const GATEWAY_INSTALL_STATUS_FILE = path.join(MANAGED_GATEWAY_STATE_DIR, "install-status.json");
237
- const MANAGED_GATEWAY_WRAPPER_DIR = IS_WINDOWS
238
- ? path.join(process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local"), "AgentHippo", "bin")
239
- : path.join(os.homedir(), ".local", "bin");
240
- const MANAGED_GATEWAY_WRAPPER_PATH = path.join(MANAGED_GATEWAY_WRAPPER_DIR, IS_WINDOWS ? "hippoclaw.cmd" : "hippoclaw");
241
- const MANAGED_OPENCLAW_WRAPPER_PATH = path.join(MANAGED_GATEWAY_WRAPPER_DIR, IS_WINDOWS ? "openclaw.cmd" : "openclaw");
242
- const MANAGED_GATEWAY_WRAPPER_MARKER = IS_WINDOWS
243
- ? "REM Managed by Agent Anywhere"
244
- : "# Managed by Agent Anywhere";
245
- const installSource_js_1 = require("./installSource.js");
246
- Object.defineProperty(exports, "ANYWHERE_CONFIG_TEMPLATE_ENV", { enumerable: true, get: function () { return installSource_js_1.ANYWHERE_CONFIG_TEMPLATE_ENV; } });
247
- Object.defineProperty(exports, "ANYWHERE_INSTALL_SOURCE_ENV", { enumerable: true, get: function () { return installSource_js_1.ANYWHERE_INSTALL_SOURCE_ENV; } });
248
- Object.defineProperty(exports, "ANYWHERE_SETUP_SCRIPT_ENV", { enumerable: true, get: function () { return installSource_js_1.ANYWHERE_SETUP_SCRIPT_ENV; } });
249
- Object.defineProperty(exports, "validateAnywhereInstallSource", { enumerable: true, get: function () { return installSource_js_1.validateAnywhereInstallSource; } });
250
- function installSourceNeedsBuild(source) {
251
- if (!source) {
252
- return false;
253
- }
254
- return (source.startsWith("\\") ||
255
- source.startsWith("/") ||
256
- source.startsWith(".") ||
257
- /^[A-Za-z]:/.test(source) ||
258
- source.startsWith("github:") ||
259
- source.startsWith("git+http:") ||
260
- source.startsWith("git+https:") ||
261
- /^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+(?:#.+)?$/.test(source));
262
- }
263
- const GATEWAY_INSTALL_PROGRESS_STEPS = [
264
- ["npmInstallComplete", "npm install complete"],
265
- ["wrapperExists", "wrapper exists"],
266
- ["configExists", "config exists"],
267
- ["distExists", "dist exists"],
268
- ["webUiExists", "web UI exists"],
269
- ["finalCheckPassed", "final check passed"],
270
- ];
271
- // ============================================================================
272
- // Detection
273
- // ============================================================================
274
- /**
275
- * Detect whether OpenClaw is installed and whether the gateway is running.
276
- * This is a fast, non-destructive check.
277
- *
278
- * Detection logic:
279
- * - If the managed fork CLI (`hippoclaw`) resolves and is usable, it's installed.
280
- * - If the port is open, listener ownership is verified before it is treated as running.
281
- * - We also check common install locations in case PATH is incomplete
282
- * (VS Code's shell PATH often differs from the user's terminal).
283
- */
284
- function normalizePathForCompare(value) {
285
- return value.replace(/\\/g, "/").replace(/\/+$/, "").toLowerCase();
286
- }
287
- function pathStartsWithPath(child, parent) {
288
- const childNorm = normalizePathForCompare(child);
289
- const parentNorm = normalizePathForCompare(parent);
290
- return childNorm === parentNorm || childNorm.startsWith(`${parentNorm}/`);
291
- }
292
- function readManagedGatewayInstallRecord() {
293
- try {
294
- if (!fs.existsSync(MANAGED_GATEWAY_MANIFEST_PATH)) {
295
- return undefined;
296
- }
297
- // Strip UTF-8 BOM — PowerShell writes JSON with BOM by default
298
- const raw = fs.readFileSync(MANAGED_GATEWAY_MANIFEST_PATH, "utf8").replace(/^/, "");
299
- return JSON.parse(raw);
300
- }
301
- catch {
302
- return undefined;
303
- }
304
- }
305
- function writeManagedGatewayInstallRecord(record) {
306
- fs.mkdirSync(MANAGED_GATEWAY_STATE_DIR, { recursive: true });
307
- fs.writeFileSync(MANAGED_GATEWAY_MANIFEST_PATH, JSON.stringify(record, null, 2), "utf8");
308
- }
309
- function getManagedGatewayPackageRoot(record) {
310
- const candidate = record?.packageRoot;
311
- if (!candidate || !fs.existsSync(candidate)) {
312
- return undefined;
313
- }
314
- try {
315
- return fs.realpathSync(candidate);
316
- }
317
- catch {
318
- return candidate;
319
- }
320
- }
321
- function resolveManagedGatewayCliTarget(binaryPath) {
322
- if (!binaryPath) {
323
- return undefined;
324
- }
325
- const record = readManagedGatewayInstallRecord();
326
- if (!record?.entryScriptPath && !record?.cliPath) {
327
- return binaryPath;
328
- }
329
- if ((record.wrapperPath || record.openclawWrapperPath) &&
330
- [
331
- record.wrapperPath,
332
- record.openclawWrapperPath,
333
- ].some((wrapperPath) => wrapperPath &&
334
- normalizePathForCompare(binaryPath) === normalizePathForCompare(wrapperPath)) &&
335
- ((record.entryScriptPath && fs.existsSync(record.entryScriptPath)) ||
336
- (record.cliPath && fs.existsSync(record.cliPath)))) {
337
- return record.entryScriptPath && fs.existsSync(record.entryScriptPath)
338
- ? record.entryScriptPath
339
- : record.cliPath;
340
- }
341
- return binaryPath;
342
- }
343
- function resolveManagedGatewayLauncherPath(binaryPath) {
344
- if (!binaryPath) {
345
- return undefined;
346
- }
347
- const record = readManagedGatewayInstallRecord();
348
- if (!record?.cliPath) {
349
- return binaryPath;
350
- }
351
- if ([
352
- record.wrapperPath,
353
- record.openclawWrapperPath,
354
- record.entryScriptPath,
355
- ].some((candidate) => candidate &&
356
- normalizePathForCompare(binaryPath) === normalizePathForCompare(candidate)) &&
357
- fs.existsSync(record.cliPath)) {
358
- return record.cliPath;
359
- }
360
- return binaryPath;
361
- }
362
- function getManagedGatewayBinary(record) {
363
- for (const candidate of [record?.wrapperPath, record?.openclawWrapperPath, record?.cliPath]) {
364
- if (candidate && fs.existsSync(candidate)) {
365
- return candidate;
366
- }
367
- }
368
- return undefined;
369
- }
370
- function getManagedGatewayNodePath(record) {
371
- const candidate = record?.nodePath;
372
- if (!candidate || !fs.existsSync(candidate)) {
373
- return undefined;
374
- }
375
- return candidate;
376
- }
377
- function getManagedGatewayEntryScriptPath(record) {
378
- const candidate = record?.entryScriptPath;
379
- if (!candidate || !fs.existsSync(candidate)) {
380
- return undefined;
381
- }
382
- return candidate;
383
- }
384
- async function resolveManagedGatewayRuntimeSpec(binaryPath, record) {
385
- const installRecord = record ?? readManagedGatewayInstallRecord();
386
- const cachedBinary = binaryPath ?? getManagedGatewayBinary(installRecord) ?? (await getCachedBinaryPath());
387
- const launcherPath = resolveManagedGatewayLauncherPath(cachedBinary) ?? cachedBinary;
388
- if (!launcherPath || !fs.existsSync(launcherPath)) {
389
- return undefined;
390
- }
391
- const packageRoot = getManagedGatewayPackageRoot(installRecord) ?? resolvePackageRootFromBinary(launcherPath);
392
- if (!packageRoot) {
393
- return undefined;
394
- }
395
- const nodePath = getManagedGatewayNodePath(installRecord) ?? resolveManagedGatewayNodePath(launcherPath);
396
- const entryScriptPath = getManagedGatewayEntryScriptPath(installRecord) ??
397
- resolveManagedGatewayEntryScriptFromPackageRoot(packageRoot);
398
- if (!nodePath || !entryScriptPath) {
399
- return undefined;
400
- }
401
- return {
402
- launcherPath,
403
- packageRoot,
404
- nodePath,
405
- entryScriptPath,
406
- };
407
- }
408
- function isAgentAnywhereManagedWrapper(wrapperPath) {
409
- try {
410
- if (!fs.existsSync(wrapperPath)) {
411
- return false;
412
- }
413
- const content = fs.readFileSync(wrapperPath, "utf8");
414
- return content.includes(MANAGED_GATEWAY_WRAPPER_MARKER);
415
- }
416
- catch {
417
- return false;
418
- }
419
- }
420
- function writeManagedGatewayWrapper(nodePath, entryScriptPath, wrapperPath) {
421
- fs.mkdirSync(path.dirname(wrapperPath), { recursive: true });
422
- const content = IS_WINDOWS
423
- ? `@echo off\r\n${MANAGED_GATEWAY_WRAPPER_MARKER}\r\n"${nodePath}" "${entryScriptPath}" %*\r\n`
424
- : `#!/bin/sh\n${MANAGED_GATEWAY_WRAPPER_MARKER}\nexec "${nodePath}" "${entryScriptPath}" "$@"\n`;
425
- fs.writeFileSync(wrapperPath, content, IS_WINDOWS ? "ascii" : "utf8");
426
- if (!IS_WINDOWS) {
427
- fs.chmodSync(wrapperPath, 0o755);
428
- }
429
- return wrapperPath;
430
- }
431
- function resolveManagedGatewayEntryScriptFromPackageRoot(packageRoot) {
432
- if (!packageRoot) {
433
- return undefined;
434
- }
435
- try {
436
- const pkgJsonPath = path.join(packageRoot, "package.json");
437
- if (!fs.existsSync(pkgJsonPath)) {
438
- return undefined;
439
- }
440
- const pkg = JSON.parse(fs.readFileSync(pkgJsonPath, "utf8"));
441
- let bins = pkg.bin;
442
- if (typeof bins === "string") {
443
- bins = { [pkg.name || "openclaw"]: bins };
444
- }
445
- const orderedNames = [...new Set(["hippoclaw", "openclaw", ...Object.keys(bins || {})])];
446
- for (const name of orderedNames) {
447
- const relativeEntry = bins && typeof bins[name] === "string" ? bins[name] : undefined;
448
- if (!relativeEntry) {
449
- continue;
450
- }
451
- const candidate = path.resolve(packageRoot, relativeEntry);
452
- if (fs.existsSync(candidate)) {
453
- return candidate;
454
- }
455
- }
456
- }
457
- catch {
458
- // ignore
459
- }
460
- return undefined;
461
- }
462
- function resolveManagedGatewayNodePath(binaryPath) {
463
- const launcherPath = resolveManagedGatewayLauncherPath(binaryPath) ?? binaryPath;
464
- if (launcherPath) {
465
- const candidate = path.join(path.dirname(launcherPath), IS_WINDOWS ? "node.exe" : "node");
466
- if (fs.existsSync(candidate)) {
467
- return candidate;
468
- }
469
- }
470
- for (const candidate of (0, index_1.getCommonBinaryPaths)("node")) {
471
- if (fs.existsSync(candidate)) {
472
- return candidate;
473
- }
474
- }
475
- return undefined;
476
- }
477
- function removeManagedGatewayWrapperState() {
478
- for (const wrapperPath of [MANAGED_GATEWAY_WRAPPER_PATH, MANAGED_OPENCLAW_WRAPPER_PATH]) {
479
- if (isAgentAnywhereManagedWrapper(wrapperPath)) {
480
- try {
481
- fs.rmSync(wrapperPath, { force: true });
482
- }
483
- catch {
484
- // ignore cleanup errors
485
- }
486
- }
487
- }
488
- try {
489
- fs.rmSync(MANAGED_GATEWAY_MANIFEST_PATH, { force: true });
490
- }
491
- catch {
492
- // ignore cleanup errors
493
- }
494
- }
495
- function addManagedOpenclawRoot(roots, candidate) {
496
- if (!fs.existsSync(candidate)) {
497
- return;
498
- }
499
- try {
500
- roots.add(fs.realpathSync(candidate));
501
- }
502
- catch {
503
- roots.add(candidate);
504
- }
505
- }
506
- function addManagedGatewayPackageRoots(roots, nodeModulesDir) {
507
- for (const pkgDir of GATEWAY_NPM_PACKAGE_DIRS) {
508
- addManagedOpenclawRoot(roots, path.join(nodeModulesDir, pkgDir));
509
- }
510
- }
511
- /** Scan nvm version dirs when `npm root -g` is unavailable in the extension host PATH. */
512
- function collectNvmOpenclawPackageRoots() {
513
- const home = os.homedir();
514
- const nvmDir = process.env.NVM_DIR || path.join(home, ".nvm");
515
- const versionsDir = path.join(nvmDir, "versions", "node");
516
- if (!fs.existsSync(versionsDir)) {
517
- return [];
518
- }
519
- const candidates = [];
520
- try {
521
- for (const entry of fs.readdirSync(versionsDir, { withFileTypes: true })) {
522
- if (!entry.isDirectory()) {
523
- continue;
524
- }
525
- candidates.push(...GATEWAY_NPM_PACKAGE_DIRS.map((pkgDir) => path.join(versionsDir, entry.name, "lib", "node_modules", pkgDir)));
526
- }
527
- }
528
- catch {
529
- // ignore nvm directory read errors
530
- }
531
- return candidates;
532
- }
533
- async function getManagedOpenclawRoots() {
534
- const roots = new Set();
535
- const manifestRecord = readManagedGatewayInstallRecord();
536
- const manifestRoot = getManagedGatewayPackageRoot(manifestRecord);
537
- if (manifestRoot && fs.existsSync(manifestRoot)) {
538
- roots.add(manifestRoot);
539
- }
540
- const bootstrapNodeRoot = path.join(os.homedir(), ".agenthippo", "tools", "node");
541
- if (fs.existsSync(bootstrapNodeRoot)) {
542
- try {
543
- const versions = fs.readdirSync(bootstrapNodeRoot, { withFileTypes: true });
544
- for (const entry of versions) {
545
- if (!entry.isDirectory()) {
546
- continue;
547
- }
548
- // Windows npm uses node_modules/ directly (no lib/ prefix); Unix uses lib/node_modules/
549
- for (const pkgDir of GATEWAY_NPM_PACKAGE_DIRS) {
550
- const candidate = path.join(bootstrapNodeRoot, entry.name, ...(IS_WINDOWS ? ["node_modules"] : ["lib", "node_modules"]), pkgDir);
551
- if (!fs.existsSync(candidate)) {
552
- continue;
553
- }
554
- try {
555
- roots.add(fs.realpathSync(candidate));
556
- }
557
- catch {
558
- roots.add(candidate);
559
- }
560
- }
561
- }
562
- }
563
- catch {
564
- // ignore bootstrap node scan failures
565
- }
566
- }
567
- // Windows npm uses node_modules/ directly (no lib/ prefix); Unix uses lib/node_modules/
568
- addManagedGatewayPackageRoots(roots, path.join(os.homedir(), ".npm-global", ...(IS_WINDOWS ? ["node_modules"] : ["lib", "node_modules"])));
569
- for (const nvmManaged of collectNvmOpenclawPackageRoots()) {
570
- addManagedOpenclawRoot(roots, nvmManaged);
571
- }
572
- try {
573
- const { stdout } = await execAsync("npm root -g", { timeout: 3_000 });
574
- const npmRoot = stdout.trim();
575
- if (npmRoot) {
576
- addManagedGatewayPackageRoots(roots, npmRoot);
577
- }
578
- }
579
- catch {
580
- // ignore npm root failures
581
- }
582
- return Array.from(roots);
583
- }
584
- function resolvePackageRootFromBinary(binaryPath) {
585
- try {
586
- const targetBinary = resolveManagedGatewayCliTarget(binaryPath) ?? binaryPath;
587
- const realBinary = fs.realpathSync(targetBinary);
588
- let pkgRoot = path.dirname(realBinary);
589
- for (let i = 0; i < 8; i++) {
590
- if (fs.existsSync(path.join(pkgRoot, "package.json"))) {
591
- return pkgRoot;
592
- }
593
- const parent = path.dirname(pkgRoot);
594
- if (parent === pkgRoot) {
595
- break;
596
- }
597
- pkgRoot = parent;
598
- }
599
- }
600
- catch {
601
- // ignore
602
- }
603
- return undefined;
604
- }
605
- async function isManagedForkBinaryPath(binaryPath, managedRoots) {
606
- const roots = managedRoots ?? (await getManagedOpenclawRoots());
607
- if (roots.length === 0) {
608
- return false;
609
- }
610
- const pkgRoot = resolvePackageRootFromBinary(binaryPath);
611
- if (!pkgRoot) {
612
- return false;
613
- }
614
- return roots.some((root) => pathStartsWithPath(pkgRoot, root));
615
- }
616
- async function getListeningCommandLinesForPort(port) {
617
- if (!Number.isFinite(port) || port < 1 || port > 65535) {
618
- return [];
619
- }
620
- if (IS_WINDOWS) {
621
- try {
622
- const command = `$listenerPids=Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue | ` +
623
- `Select-Object -ExpandProperty OwningProcess -Unique; ` +
624
- `foreach($listenerProcId in $listenerPids){$proc=Get-CimInstance Win32_Process -Filter \\\"ProcessId=$listenerProcId\\\" -ErrorAction SilentlyContinue; if($proc -and $proc.CommandLine){$proc.CommandLine}}`;
625
- const { stdout } = await execAsync(`powershell.exe -NoProfile -Command "${command}"`, {
626
- timeout: 10_000,
627
- });
628
- return stdout
629
- .split(/\r?\n/)
630
- .map((line) => line.trim())
631
- .filter((line) => line.length > 0);
632
- }
633
- catch {
634
- return [];
635
- }
636
- }
637
- try {
638
- const { stdout } = await execAsync(`lsof -nP -iTCP:${port} -sTCP:LISTEN -t 2>/dev/null || true`, { timeout: 5_000 });
639
- const pids = [
640
- ...new Set(stdout
641
- .trim()
642
- .split(/\n/)
643
- .map((s) => s.trim())
644
- .filter((s) => /^\d+$/.test(s))),
645
- ];
646
- const commands = [];
647
- for (const pid of pids) {
648
- try {
649
- const { stdout: cmdOut } = await execAsync(`ps -p ${pid} -o command=`, {
650
- timeout: 3_000,
651
- });
652
- const cmd = cmdOut.trim();
653
- if (cmd) {
654
- commands.push(cmd);
655
- }
656
- }
657
- catch {
658
- // ignore per-pid failures
659
- }
660
- }
661
- return commands;
662
- }
663
- catch {
664
- return [];
665
- }
666
- }
667
- async function isTrustedGatewayListener(port, binaryPath) {
668
- const managedRoots = await getManagedOpenclawRoots();
669
- if (managedRoots.length === 0) {
670
- return false;
671
- }
672
- let binaryIsManaged = false;
673
- let binaryReal = "";
674
- if (binaryPath) {
675
- binaryIsManaged = await isManagedForkBinaryPath(binaryPath, managedRoots);
676
- if (binaryIsManaged) {
677
- try {
678
- binaryReal = fs.realpathSync(binaryPath);
679
- }
680
- catch {
681
- binaryReal = binaryPath;
682
- }
683
- }
684
- }
685
- const commandLines = await getListeningCommandLinesForPort(port);
686
- if (commandLines.length === 0) {
687
- return false;
688
- }
689
- // The gateway sets process.title to "openclaw-gateway", so ps output won't
690
- // contain file paths. Add the managed node binary path to both the command
691
- // list (via lsof) and a matching needle, so we can trust processes started
692
- // from our bootstrapped node tree even when the process title is opaque.
693
- const manifestRecord = readManagedGatewayInstallRecord();
694
- const manifestNodePath = getManagedGatewayNodePath(manifestRecord);
695
- const augmentedCommands = [...commandLines];
696
- if (manifestNodePath) {
697
- augmentedCommands.push(manifestNodePath);
698
- }
699
- const rootNeedles = managedRoots.map((root) => normalizePathForCompare(root));
700
- // Add the managed node binary dir as a needle so it can match against the
701
- // augmented command list entry for the node executable.
702
- if (manifestNodePath) {
703
- rootNeedles.push(normalizePathForCompare(path.dirname(manifestNodePath)));
704
- }
705
- const binaryNeedle = binaryReal ? normalizePathForCompare(binaryReal) : "";
706
- for (const cmd of augmentedCommands) {
707
- const normalized = normalizePathForCompare(cmd);
708
- if (normalized.includes(FORK_CLI_NAME)) {
709
- if (binaryIsManaged) {
710
- return true;
711
- }
712
- }
713
- if (binaryNeedle && normalized.includes(binaryNeedle)) {
714
- return true;
715
- }
716
- if (rootNeedles.some((needle) => normalized.includes(needle))) {
717
- return true;
718
- }
719
- }
720
- return false;
721
- }
722
- async function detectGateway(port = DEFAULT_PORT) {
723
- // Parallelize port probe and binary lookup for ~50% faster detection
724
- const [portOpen, managedRoots] = await Promise.all([
725
- (0, index_1.probePort)(port),
726
- getManagedOpenclawRoots(),
727
- ]);
728
- const binaryPath = await getCachedBinaryPath(managedRoots);
729
- let version;
730
- if (binaryPath) {
731
- try {
732
- const { stdout } = await execAsync(`"${binaryPath}" --version`, {
733
- timeout: 3_000, // Reduced from 5s - local command should be fast
734
- });
735
- version = stdout.trim().split("\n")[0];
736
- }
737
- catch {
738
- // Binary exists but --version failed
739
- }
740
- }
741
- // Treat installed as "managed fork CLI is available and usable".
742
- const installed = !!binaryPath && !!version;
743
- const running = portOpen
744
- ? await isTrustedGatewayListener(port, binaryPath)
745
- : false;
746
- return {
747
- installed,
748
- binaryPath,
749
- version,
750
- running,
751
- port,
752
- };
753
- }
754
- /**
755
- * Ask the OpenClaw CLI to stop a supervised or local gateway (launchd/systemd aware).
756
- */
757
- async function tryOpenclawGatewayStopCli(binaryPath, log) {
758
- try {
759
- if (IS_WINDOWS) {
760
- const runtimeSpec = await resolveManagedGatewayRuntimeSpec(binaryPath);
761
- if (runtimeSpec) {
762
- log("Running gateway stop via managed node runtime...");
763
- await execFileAsync(runtimeSpec.nodePath, [runtimeSpec.entryScriptPath, "gateway", "stop"], { timeout: 20_000, windowsHide: true });
764
- return;
765
- }
766
- }
767
- log(`Running ${path.basename(binaryPath)} gateway stop (CLI)...`);
768
- await execAsync(`"${binaryPath}" gateway stop`, { timeout: 20_000, windowsHide: true });
769
- }
770
- catch {
771
- // Non-fatal — fall back to process signals / port-based kill
772
- }
773
- }
774
- function readGatewayInstallStatusSnapshot() {
775
- try {
776
- if (!fs.existsSync(GATEWAY_INSTALL_STATUS_FILE)) {
777
- return undefined;
778
- }
779
- const raw = fs.readFileSync(GATEWAY_INSTALL_STATUS_FILE, "utf8").replace(/^\uFEFF/, "");
780
- return JSON.parse(raw);
781
- }
782
- catch {
783
- return undefined;
784
- }
785
- }
786
- function isGatewayInstallComplete(status) {
787
- return status?.phase === "complete" && status.steps?.finalCheckPassed === true;
788
- }
789
- function getGatewayInstallProgressSummary(status) {
790
- if (!status) {
791
- return {
792
- message: "Waiting for setup status...",
793
- percent: 0,
794
- };
795
- }
796
- if (status.phase === "failed") {
797
- return {
798
- message: status.lastError?.trim() || status.message?.trim() || "Install failed.",
799
- percent: 100,
800
- };
801
- }
802
- const completedLabels = GATEWAY_INSTALL_PROGRESS_STEPS
803
- .filter(([key]) => status.steps?.[key])
804
- .map(([, label]) => label);
805
- const completedCount = completedLabels.length;
806
- const phaseMessage = status.message?.trim() || "Installing Agent Anywhere...";
807
- const progressMessage = completedLabels.length > 0
808
- ? `${phaseMessage} (done: ${completedLabels.join(", ")})`
809
- : phaseMessage;
810
- return {
811
- message: progressMessage,
812
- percent: Math.min(95, Math.floor((completedCount / GATEWAY_INSTALL_PROGRESS_STEPS.length) * 100)),
813
- };
814
- }
815
- function escapePowerShellSingleQuotedString(value) {
816
- return value.replace(/'/g, "''");
817
- }
818
- async function getListeningProcessIdsForPort(port) {
819
- if (!Number.isFinite(port) || port < 1 || port > 65535) {
820
- return [];
821
- }
822
- if (IS_WINDOWS) {
823
- try {
824
- const { stdout } = await execAsync(`powershell.exe -NoProfile -Command "Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue | ForEach-Object { $_.OwningProcess } | Sort-Object -Unique"`, { timeout: 10_000, windowsHide: true });
825
- return [
826
- ...new Set(stdout
827
- .trim()
828
- .split(/\r?\n/)
829
- .map((s) => parseInt(s.trim(), 10))
830
- .filter((pid) => Number.isInteger(pid) && pid > 0)),
831
- ];
832
- }
833
- catch {
834
- return [];
835
- }
836
- }
837
- try {
838
- const { stdout } = await execAsync(`lsof -nP -iTCP:${port} -sTCP:LISTEN -t 2>/dev/null || true`, { timeout: 5_000 });
839
- return [
840
- ...new Set(stdout
841
- .trim()
842
- .split(/\n/)
843
- .map((s) => parseInt(s.trim(), 10))
844
- .filter((pid) => Number.isInteger(pid) && pid > 0)),
845
- ];
846
- }
847
- catch {
848
- return [];
849
- }
850
- }
851
- async function waitForGatewayShutdown(port, observedPids, timeoutMs = 8_000) {
852
- const trackedPids = [...new Set(observedPids.filter((pid) => Number.isInteger(pid) && pid > 0))];
853
- const deadline = Date.now() + timeoutMs;
854
- let stableClosedSince = 0;
855
- while (Date.now() < deadline) {
856
- const portOpen = await (0, index_1.probePort)(port);
857
- const stillRunning = trackedPids.filter((pid) => (0, index_1.isProcessRunning)(pid));
858
- if (!portOpen && stillRunning.length === 0) {
859
- if (stableClosedSince === 0) {
860
- stableClosedSince = Date.now();
861
- }
862
- if (Date.now() - stableClosedSince >= 750) {
863
- return true;
864
- }
865
- }
866
- else {
867
- stableClosedSince = 0;
868
- }
869
- await new Promise((resolve) => setTimeout(resolve, 250));
870
- }
871
- return !(await (0, index_1.probePort)(port)) && trackedPids.every((pid) => !(0, index_1.isProcessRunning)(pid));
872
- }
873
- /**
874
- * Signal the gateway process group. We spawn the gateway launcher detached, so it is a process
875
- * group leader and its listening child shares the group; signalling the group avoids orphaning the
876
- * child that actually holds the port. Falls back to signalling just the launcher when the group
877
- * signal is unavailable. Unix only -- Windows uses `taskkill /T` for tree termination.
878
- */
879
- function killGatewayProcessGroupOrSelf(child, pid, signal) {
880
- if (pid && pid > 0) {
881
- try {
882
- process.kill(-pid, signal);
883
- return;
884
- }
885
- catch {
886
- // Group signal unavailable (not a group leader / already gone) -- fall back below.
887
- }
888
- }
889
- try {
890
- child.kill(signal);
891
- }
892
- catch {
893
- // Process already exited.
894
- }
895
- }
896
- /**
897
- * Stop whatever is listening on the gateway TCP port (covers node …/cli.mjs
898
- * when argv does not contain the substring "openclaw").
899
- */
900
- async function killTcpListenersOnPort(port) {
901
- if (!Number.isFinite(port) || port < 1 || port > 65535) {
902
- return;
903
- }
904
- if (IS_WINDOWS) {
905
- try {
906
- const { stdout } = await execAsync(`powershell.exe -NoProfile -Command "Get-NetTCPConnection -LocalPort ${port} -State Listen -ErrorAction SilentlyContinue | ForEach-Object { $_.OwningProcess } | Sort-Object -Unique"`, { timeout: 10_000 });
907
- const pids = stdout
908
- .trim()
909
- .split(/\r?\n/)
910
- .map((s) => s.trim())
911
- .filter((s) => /^\d+$/.test(s));
912
- for (const pid of pids) {
913
- await execAsync(`taskkill /F /T /PID ${pid}`, { timeout: 5_000 }).catch(() => { });
914
- }
915
- }
916
- catch {
917
- // ignore
918
- }
919
- return;
920
- }
921
- try {
922
- const { stdout } = await execAsync(`lsof -nP -iTCP:${port} -sTCP:LISTEN -t 2>/dev/null || true`, { timeout: 5_000 });
923
- const pids = [
924
- ...new Set(stdout
925
- .trim()
926
- .split(/\n/)
927
- .map((s) => s.trim())
928
- .filter((s) => /^\d+$/.test(s))),
929
- ];
930
- for (const pid of pids) {
931
- await execAsync(`kill ${pid}`, { timeout: 3_000 }).catch(() => { });
932
- }
933
- await new Promise((r) => setTimeout(r, 800));
934
- const { stdout: stdout2 } = await execAsync(`lsof -nP -iTCP:${port} -sTCP:LISTEN -t 2>/dev/null || true`, { timeout: 5_000 });
935
- const pids2 = [
936
- ...new Set(stdout2
937
- .trim()
938
- .split(/\n/)
939
- .map((s) => s.trim())
940
- .filter((s) => /^\d+$/.test(s))),
941
- ];
942
- for (const pid of pids2) {
943
- await execAsync(`kill -9 ${pid}`, { timeout: 3_000 }).catch(() => { });
944
- }
945
- }
946
- catch {
947
- // lsof missing or failed — ignore
948
- }
949
- }
950
- /**
951
- * Stop a gateway we did not spawn in-process (detached child, terminal, setup script, etc.).
952
- * @returns true if the gateway port is no longer accepting connections
953
- */
954
- async function stopExternalGatewayListener(port, binaryPath, log) {
955
- const observedPids = await getListeningProcessIdsForPort(port);
956
- if (binaryPath) {
957
- await tryOpenclawGatewayStopCli(binaryPath, log);
958
- if (await waitForGatewayShutdown(port, observedPids)) {
959
- return true;
960
- }
961
- }
962
- log(`Stopping listener(s) on port ${port}...`);
963
- await killTcpListenersOnPort(port);
964
- const allObservedPids = [...observedPids];
965
- if (port !== DEFAULT_PORT && (await (0, index_1.probePort)(DEFAULT_PORT))) {
966
- allObservedPids.push(...(await getListeningProcessIdsForPort(DEFAULT_PORT)));
967
- log(`Stopping listener(s) on port ${DEFAULT_PORT} (default gateway port)...`);
968
- await killTcpListenersOnPort(DEFAULT_PORT);
969
- }
970
- return waitForGatewayShutdown(port, allObservedPids);
971
- }
972
- // Note: findBinary() and probePort() are now imported from ../utils
973
- // ============================================================================
974
- // Fast Detection (PID file based, ~1-2ms)
975
- // Uses shared utilities from ../utils/pid.ts
976
- // ============================================================================
977
- /** Wrapper for gateway-specific PID file writes */
978
- async function writeGatewayPidFile(pid, port, binaryPath) {
979
- await (0, index_1.writePidFile)(GATEWAY_PID_FILE, pid, port, binaryPath);
980
- }
981
- /** Wrapper for gateway-specific PID file removal */
982
- async function removeGatewayPidFile() {
983
- await (0, index_1.removePidFile)(GATEWAY_PID_FILE);
984
- }
985
- /**
986
- * Fast gateway detection using PID file.
987
- * Cost: ~1-2ms when PID file exists and process is running.
988
- * Falls back to full detection if PID file is missing/stale.
989
- */
990
- async function detectGatewayFast(port) {
991
- const pidData = await (0, index_1.checkPidFileWithCleanup)(GATEWAY_PID_FILE);
992
- if (!pidData) {
993
- return null; // No PID file or stale
994
- }
995
- // Parse gateway-specific metadata: [port, binaryPath]
996
- const portFromFile = pidData.metadata?.[0] ? parseInt(pidData.metadata[0], 10) : undefined;
997
- const binaryPath = pidData.metadata?.[1] || undefined;
998
- const effectivePort = portFromFile ?? port;
999
- // PID can be reused by another process; require the gateway port to be listening.
1000
- if (!(await (0, index_1.probePort)(effectivePort))) {
1001
- return null;
1002
- }
1003
- // PID can be stale/reused. Trust fast-path only when listener still looks like our fork.
1004
- if (!(await isTrustedGatewayListener(effectivePort, binaryPath))) {
1005
- return null;
1006
- }
1007
- return {
1008
- installed: true,
1009
- binaryPath,
1010
- running: true,
1011
- port: effectivePort,
1012
- };
1013
- }
1014
- /**
1015
- * Get cached binary path, or find it fresh if cache expired.
1016
- * Uses shared binary finder utility with caching.
1017
- */
1018
- async function getCachedBinaryPath(managedRoots) {
1019
- const roots = managedRoots ?? (await getManagedOpenclawRoots());
1020
- return getCachedBinaryPathFromRoots(roots);
1021
- }
1022
- async function getCachedBinaryPathFromRoots(managedRoots) {
1023
- const manifestRecord = readManagedGatewayInstallRecord();
1024
- const manifestRoot = getManagedGatewayPackageRoot(manifestRecord);
1025
- const manifestBinary = getManagedGatewayBinary(manifestRecord);
1026
- if (manifestRoot && manifestBinary && fs.existsSync(manifestBinary)) {
1027
- const manifestMatchesManaged = managedRoots.length === 0 ||
1028
- managedRoots.some((root) => pathStartsWithPath(manifestRoot, root));
1029
- if (manifestMatchesManaged) {
1030
- return manifestBinary;
1031
- }
1032
- }
1033
- const forkCli = await (0, index_1.findBinaryCached)(FORK_CLI_NAME, (0, index_1.getCommonBinaryPaths)(FORK_CLI_NAME));
1034
- if (forkCli && (await isManagedForkBinaryPath(forkCli, managedRoots))) {
1035
- return forkCli;
1036
- }
1037
- const forkCliFile = IS_WINDOWS ? `${FORK_CLI_NAME}.cmd` : FORK_CLI_NAME;
1038
- for (const managedRoot of managedRoots) {
1039
- // Unix: {prefix}/lib/node_modules/openclaw → 3 levels up → {prefix}/bin/cmd
1040
- // Windows: {prefix}/node_modules/openclaw → 2 levels up → {prefix}/cmd.cmd (no bin/)
1041
- const prefixDir = IS_WINDOWS
1042
- ? path.dirname(path.dirname(managedRoot))
1043
- : path.dirname(path.dirname(path.dirname(managedRoot)));
1044
- const candidate = IS_WINDOWS
1045
- ? path.join(prefixDir, forkCliFile)
1046
- : path.join(prefixDir, "bin", forkCliFile);
1047
- if (!fs.existsSync(candidate)) {
1048
- continue;
1049
- }
1050
- if (await isManagedForkBinaryPath(candidate, managedRoots)) {
1051
- return candidate;
1052
- }
1053
- }
1054
- return undefined;
1055
- }
1056
- class GatewayLifecycleManager {
1057
- outputChannel;
1058
- process = null;
1059
- healthCheckTimer = null;
1060
- _status = "not-installed";
1061
- _bridgeConnected = false;
1062
- _lastDetection = null;
1063
- _bridgeRef = null;
1064
- /** True while `install()` is running setup in a terminal (poll loop). Prevents toggle/detect from resetting UI to "Install" again. */
1065
- _gatewayInstallInProgress = false;
1066
- _installProgressMessage;
1067
- _installSetupTerminal = null;
1068
- _gatewayUninstallInProgress = false;
1069
- _uninstallSetupTerminal = null;
1070
- _startInFlight = null;
1071
- _lastStartInProgressNoticeAt = 0;
1072
- /**
1073
- * Cached answer to "does the gateway have at least one configured messaging channel?" — drives
1074
- * the connected menu's primary button (Meet at... when channels exist, else Web UI). Undefined
1075
- * until first probed via {@link refreshHasConfiguredChannels}.
1076
- */
1077
- _hasConfiguredChannelsCache;
1078
- disposables = [];
1079
- statusBarItem;
1080
- _onStatusChange = new vscode.EventEmitter();
1081
- onStatusChange = this._onStatusChange.event;
1082
- /**
1083
- * Set the bridge reference (GatewayService) so lifecycle can trigger connect/disconnect.
1084
- */
1085
- setBridgeRef(ref) {
1086
- this._bridgeRef = ref;
1087
- }
1088
- constructor(outputChannel, extensionContext) {
1089
- this.outputChannel = outputChannel;
1090
- void extensionContext; // Reserved for future use (e.g., storage, secrets)
1091
- // Status bar at priority 100 (replaces the old GatewayService icon, before Spotlight at 99)
1092
- this.statusBarItem = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right, 100);
1093
- this.statusBarItem.command = "agentide.anywhere.toggle";
1094
- this.statusBarItem.name = exports.AGENT_ANYWHERE_LABEL;
1095
- // Initialize context key for connection state (used by mode picker button)
1096
- void vscode.commands.executeCommand("setContext", "agentide.anywhere.connected", this._bridgeConnected);
1097
- this.disposables.push(vscode.window.onDidCloseTerminal((terminal) => {
1098
- if (this._installSetupTerminal === terminal) {
1099
- this._installSetupTerminal = null;
1100
- if (this._gatewayInstallInProgress) {
1101
- void this.reconcileInstallProgressState();
1102
- }
1103
- }
1104
- if (this._uninstallSetupTerminal === terminal) {
1105
- this._uninstallSetupTerminal = null;
1106
- if (this._gatewayUninstallInProgress) {
1107
- void this.reconcileUninstallProgressState();
1108
- }
1109
- }
1110
- }));
1111
- this.registerCommands();
1112
- this.registerConfigWatcher();
1113
- // Lazy init: only probe if feature is enabled
1114
- void this.initializeLazy();
1115
- }
1116
- setInstallProgressFlag(inProgress, reason) {
1117
- if (this._gatewayInstallInProgress === inProgress) {
1118
- return;
1119
- }
1120
- this._gatewayInstallInProgress = inProgress;
1121
- this.log(`Install flag -> ${inProgress ? "true" : `false (${reason})`}`);
1122
- }
1123
- get status() {
1124
- return this._status;
1125
- }
1126
- get lastDetection() {
1127
- return this._lastDetection;
1128
- }
1129
- // ============================================================================
1130
- // Initialization
1131
- // ============================================================================
1132
- async initializeLazy() {
1133
- const hidden = this.getConfig("hidden", false);
1134
- if (hidden) {
1135
- this.statusBarItem.hide();
1136
- return; // Enterprise kill switch: hide everything
1137
- }
1138
- const enabled = this.getConfig("enabled", false);
1139
- if (!enabled) {
1140
- // Not enabled -- show a subtle status bar hint, no probing
1141
- this.updateStatusBar("not-installed");
1142
- this.statusBarItem.show();
1143
- return;
1144
- }
1145
- // Feature enabled -- detect current state (fast path first)
1146
- const port = this.getGatewayPort();
1147
- const fast = await detectGatewayFast(port);
1148
- if (fast) {
1149
- this._lastDetection = fast;
1150
- this.setStatus("running");
1151
- }
1152
- else {
1153
- await this.detect();
1154
- }
1155
- // Auto-start if configured and installed
1156
- const autoStart = this.getConfig("autoStart", false);
1157
- if (autoStart && this._lastDetection?.installed && !this._lastDetection?.running) {
1158
- this.log(`Auto-starting gateway (${(0, branding_js_1.toSettingsDisplayKey)("agentide.anywhere.autoStart")} = true)`);
1159
- await this.start();
1160
- }
1161
- }
1162
- // ============================================================================
1163
- // Commands
1164
- // ============================================================================
1165
- registerCommands() {
1166
- this.disposables.push(vscode.commands.registerCommand("agentide.anywhere.toggle", () => this.toggle()), vscode.commands.registerCommand("agentide.anywhere.install", () => this.install()), vscode.commands.registerCommand("agentide.anywhere.uninstall", () => this.uninstall()), vscode.commands.registerCommand("agentide.anywhere.start", () => this.start()), vscode.commands.registerCommand("agentide.anywhere.stop", () => this.stop()), vscode.commands.registerCommand("agentide.anywhere.status", () => this.showStatus()), vscode.commands.registerCommand("agentide.anywhere.openConfig", () => this.openConfig()), vscode.commands.registerCommand("agentide.anywhere.connect", () => this.connectBridge()), vscode.commands.registerCommand("agentide.anywhere.disconnect", () => this.disconnectBridge()), vscode.commands.registerCommand("agentide.anywhere.openWebUI", () => this.openWebUI()), vscode.commands.registerCommand("agentide.anywhere.detect", () => this.detect()),
1167
- // Exposed for mode picker button: "Meet Agent Anywhere"
1168
- vscode.commands.registerCommand("agentide.anywhere.meetAgent", () => this.showMeetAgentPicker()));
1169
- }
1170
- setBridgeConnected(value) {
1171
- this._bridgeConnected = value;
1172
- void vscode.commands.executeCommand("setContext", "agentide.anywhere.connected", value);
1173
- }
1174
- registerConfigWatcher() {
1175
- this.disposables.push(vscode.workspace.onDidChangeConfiguration((e) => {
1176
- if (e.affectsConfiguration("agentide.anywhere")) {
1177
- void this.initializeLazy();
1178
- }
1179
- }));
1180
- }
1181
- // ============================================================================
1182
- // Toggle (status bar click)
1183
- // ============================================================================
1184
- async toggle() {
1185
- const hidden = this.getConfig("hidden", false);
1186
- if (hidden) {
1187
- return;
1188
- }
1189
- if (this._gatewayInstallInProgress) {
1190
- await this.showGatewayInstallInProgressPrompt();
1191
- return;
1192
- }
1193
- if (this._gatewayUninstallInProgress) {
1194
- await this.showGatewayUninstallInProgressPrompt();
1195
- return;
1196
- }
1197
- if (this._startInFlight) {
1198
- this.setStatus("starting");
1199
- const now = Date.now();
1200
- if (now - this._lastStartInProgressNoticeAt > 4000) {
1201
- this._lastStartInProgressNoticeAt = now;
1202
- void vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}: Starting gateway... please wait.`);
1203
- }
1204
- return;
1205
- }
1206
- // Instant path: route to a menu using cached state only.
1207
- // Do not block click on detect/start/connect; those happen only after user action.
1208
- this.setBridgeConnected(this._bridgeRef?.isConnected ?? false);
1209
- switch (this._status) {
1210
- case "error": {
1211
- const likelyInstalled = this._lastDetection?.installed === true;
1212
- if (likelyInstalled) {
1213
- await this.showDisconnectedQuickMenu();
1214
- }
1215
- else {
1216
- await this.showNotInstalledQuickMenu();
1217
- }
1218
- break;
1219
- }
1220
- case "starting":
1221
- case "stopping":
1222
- void vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}: Operation in progress...`);
1223
- break;
1224
- case "running":
1225
- if (this._bridgeConnected) {
1226
- await this.showConnectedQuickMenu();
1227
- }
1228
- else {
1229
- await this.showDisconnectedQuickMenu();
1230
- }
1231
- break;
1232
- case "installed":
1233
- await this.showDisconnectedQuickMenu();
1234
- break;
1235
- case "not-installed":
1236
- default: {
1237
- // If cache is stale but we previously saw an installed state, prefer the
1238
- // disconnected menu and let background refresh correct the UI.
1239
- const likelyInstalled = this._lastDetection?.installed === true;
1240
- if (likelyInstalled) {
1241
- await this.showDisconnectedQuickMenu();
1242
- break;
1243
- }
1244
- await this.showNotInstalledQuickMenu();
1245
- break;
1246
- }
1247
- }
1248
- }
1249
- queueBackgroundDetect(reason) {
1250
- if (this._gatewayInstallInProgress ||
1251
- this._gatewayUninstallInProgress ||
1252
- this._startInFlight ||
1253
- this._status === "starting" ||
1254
- this._status === "stopping") {
1255
- return;
1256
- }
1257
- setTimeout(() => {
1258
- if (this._gatewayInstallInProgress ||
1259
- this._gatewayUninstallInProgress ||
1260
- this._startInFlight ||
1261
- this._status === "starting" ||
1262
- this._status === "stopping") {
1263
- return;
1264
- }
1265
- void this.detect().catch((err) => {
1266
- this.log(`Background detect failed after ${reason}: ${err instanceof Error ? err.message : String(err)}`);
1267
- });
1268
- }, 100);
1269
- }
1270
- /**
1271
- * Level 1 menu for running + connected state.
1272
- * Connected: Web UI | Disconnect | More | Ask Agent Hippo
1273
- */
1274
- async showConnectedQuickMenu() {
1275
- // Primary action depends on whether messaging channels are configured: surface "Meet at..."
1276
- // (bind/reclaim a channel for this IDE agent) when they are, otherwise keep "Web UI" since
1277
- // there is nothing to meet at yet. Cached probe keeps the menu instant; first open awaits once.
1278
- const hasChannels = this._hasConfiguredChannelsCache ?? (await this.refreshHasConfiguredChannels());
1279
- const primaryAction = hasChannels ? "Meet at..." : "Web UI";
1280
- const choicePromise = vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL} is running (bridge: connected).`, primaryAction, "Disconnect", "Ask Agent Hippo", "More");
1281
- this.queueBackgroundDetect("opening connected menu");
1282
- // Keep the cache warm for the next open and the More menu's Web UI entry.
1283
- void this.refreshHasConfiguredChannels();
1284
- const choice = await choicePromise;
1285
- switch (choice) {
1286
- case "Meet at...":
1287
- await this.showMeetAgentPicker();
1288
- break;
1289
- case "Web UI":
1290
- await this.openWebUI();
1291
- break;
1292
- case "Disconnect":
1293
- await this.disconnectBridge();
1294
- break;
1295
- case "More":
1296
- await this.showMaintenanceQuickMenu();
1297
- break;
1298
- case "Ask Agent Hippo":
1299
- await this.openAskAgentHippoChat();
1300
- break;
1301
- }
1302
- }
1303
- /**
1304
- * Level 1 menu for installed but disconnected (or not running) state.
1305
- * Disconnected: Connect | Reinstall | More | Ask Agent Hippo
1306
- */
1307
- async showDisconnectedQuickMenu() {
1308
- const choicePromise = vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}: Disconnected.`, "Connect", "Reinstall", "Ask Agent Hippo", "More");
1309
- this.queueBackgroundDetect("opening disconnected menu");
1310
- const choice = await choicePromise;
1311
- switch (choice) {
1312
- case "Connect":
1313
- await this.start();
1314
- if (this._status === "running") {
1315
- await this.connectBridge();
1316
- this.setBridgeConnected(this._bridgeRef?.isConnected ?? false);
1317
- if (this._bridgeConnected) {
1318
- await this.showConnectedQuickMenu();
1319
- }
1320
- }
1321
- break;
1322
- case "Reinstall":
1323
- await this.install();
1324
- break;
1325
- case "More":
1326
- await this.showMaintenanceQuickMenu();
1327
- break;
1328
- case "Ask Agent Hippo":
1329
- await this.openAskAgentHippoChat();
1330
- break;
1331
- }
1332
- }
1333
- async showGatewayInstallInProgressPrompt() {
1334
- const choice = await vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}: Install in progress. Follow the "${exports.AGENT_ANYWHERE_LABEL} Setup" terminal; when it finishes, status updates automatically.`, "Show Setup Terminal", "OK");
1335
- if (choice === "Show Setup Terminal") {
1336
- if (!this.revealInstallSetupTerminal()) {
1337
- await this.showMissingInstallSetupTerminalPrompt();
1338
- }
1339
- }
1340
- }
1341
- async showMissingInstallSetupTerminalPrompt() {
1342
- const result = await this.reconcileInstallProgressState();
1343
- if (result?.installed) {
1344
- void vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}: Install completed. Status has been refreshed.`);
1345
- }
1346
- else {
1347
- void vscode.window.showWarningMessage(`${exports.AGENT_ANYWHERE_LABEL}: Setup terminal is no longer available. Install did not complete; run Install again to relaunch setup.`);
1348
- }
1349
- }
1350
- async showGatewayUninstallInProgressPrompt() {
1351
- const choice = await vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}: Uninstall in progress. Follow the "${AGENT_ANYWHERE_UNINSTALL_TERMINAL}" terminal; when it finishes, status updates automatically.`, "Show Uninstall Terminal", "OK");
1352
- if (choice === "Show Uninstall Terminal") {
1353
- if (!this.revealUninstallSetupTerminal()) {
1354
- await this.showMissingUninstallSetupTerminalPrompt();
1355
- }
1356
- }
1357
- }
1358
- async showMissingUninstallSetupTerminalPrompt() {
1359
- const result = await this.reconcileUninstallProgressState();
1360
- if (result && !result.installed && !result.running) {
1361
- void vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}: Uninstall completed. Status has been refreshed.`);
1362
- }
1363
- else {
1364
- void vscode.window.showWarningMessage(`${exports.AGENT_ANYWHERE_LABEL}: Uninstall terminal is no longer available. Uninstall may still be running or did not complete; run Status to confirm.`);
1365
- }
1366
- }
1367
- revealInstallSetupTerminal() {
1368
- const terminal = this._installSetupTerminal ??
1369
- vscode.window.terminals.find((t) => t.name === AGENT_ANYWHERE_SETUP_TERMINAL ||
1370
- t.name.startsWith(`${AGENT_ANYWHERE_SETUP_TERMINAL} `)) ??
1371
- null;
1372
- if (!terminal) {
1373
- return false;
1374
- }
1375
- this._installSetupTerminal = terminal;
1376
- terminal.show();
1377
- return true;
1378
- }
1379
- revealUninstallSetupTerminal() {
1380
- const terminal = this._uninstallSetupTerminal ??
1381
- vscode.window.terminals.find((t) => t.name === AGENT_ANYWHERE_UNINSTALL_TERMINAL ||
1382
- t.name.startsWith(`${AGENT_ANYWHERE_UNINSTALL_TERMINAL} `)) ??
1383
- null;
1384
- if (!terminal) {
1385
- return false;
1386
- }
1387
- this._uninstallSetupTerminal = terminal;
1388
- terminal.show();
1389
- return true;
1390
- }
1391
- async reconcileInstallProgressState() {
1392
- if (!this._gatewayInstallInProgress) {
1393
- return this._lastDetection;
1394
- }
1395
- if (this.revealInstallSetupTerminal()) {
1396
- return this._lastDetection;
1397
- }
1398
- // Setup terminal closed — allow npm/manifest to settle before treating as failure.
1399
- await this.sleep(2_000);
1400
- if (this.revealInstallSetupTerminal()) {
1401
- return this._lastDetection;
1402
- }
1403
- (0, index_1.clearBinaryCache)(FORK_CLI_NAME);
1404
- (0, index_1.clearBinaryCache)("openclaw");
1405
- const result = await this.detectGatewayState(this.getGatewayPort());
1406
- this._lastDetection = result;
1407
- if (result.running) {
1408
- this.finishInstallUiState("running", "terminal reconciliation");
1409
- }
1410
- else if (result.installed) {
1411
- this.finishInstallUiState("installed", "terminal reconciliation");
1412
- }
1413
- else {
1414
- this.log("Setup terminal closed before gateway install was detected");
1415
- this.setInstallProgressFlag(false, "terminal reconciliation");
1416
- this._installProgressMessage = undefined;
1417
- this.setStatus("not-installed");
1418
- }
1419
- return result;
1420
- }
1421
- finishInstallUiState(status, reason) {
1422
- this.setInstallProgressFlag(false, reason);
1423
- this._installProgressMessage = undefined;
1424
- this.setStatus(status);
1425
- }
1426
- readInstallStatusSnapshot() {
1427
- return readGatewayInstallStatusSnapshot();
1428
- }
1429
- getInstallProgressSummary(status) {
1430
- return getGatewayInstallProgressSummary(status);
1431
- }
1432
- async detectGatewayState(port) {
1433
- return detectGateway(port);
1434
- }
1435
- async detectGatewayFastState(port) {
1436
- return detectGatewayFast(port);
1437
- }
1438
- async probeGatewayPort(port) {
1439
- return (0, index_1.probePort)(port);
1440
- }
1441
- isStartedGatewayProcessAlive(pid) {
1442
- return pid !== undefined && (0, index_1.isProcessRunning)(pid);
1443
- }
1444
- getStartupTimeoutMs() {
1445
- return STARTUP_TIMEOUT_MS;
1446
- }
1447
- getStartupCheckIntervalMs() {
1448
- return 500;
1449
- }
1450
- getStartupLaunchGraceMs() {
1451
- return IS_WINDOWS ? 15_000 : 0;
1452
- }
1453
- getUninstallInitialDelayMs() {
1454
- return IS_WINDOWS ? 2_000 : 3_000;
1455
- }
1456
- getUninstallVerificationTimeoutMs() {
1457
- return IS_WINDOWS ? 15_000 : 90_000;
1458
- }
1459
- getUninstallCheckIntervalMs() {
1460
- return IS_WINDOWS ? 1_000 : 4_000;
1461
- }
1462
- async sleep(ms) {
1463
- await new Promise((resolve) => setTimeout(resolve, ms));
1464
- }
1465
- /**
1466
- * Whether the gateway launch should take the Windows (hidden-process) path. Defaults to the
1467
- * platform constant; exposed as a method so tests can force the Windows branch off-Windows.
1468
- */
1469
- isWindowsLaunch() {
1470
- return IS_WINDOWS;
1471
- }
1472
- async stopExistingGatewayBeforeStart(binaryPath) {
1473
- await tryOpenclawGatewayStopCli(binaryPath, (message) => this.log(message));
1474
- }
1475
- getGatewayLockPathForConfig() {
1476
- const configHash = (0, crypto_1.createHash)("sha256").update(this.configPath).digest("hex").slice(0, 8);
1477
- return path.join(os.tmpdir(), "openclaw", `gateway.${configHash}.lock`);
1478
- }
1479
- async removeStaleGatewayLockBeforeStart(port) {
1480
- if (!IS_WINDOWS) {
1481
- return;
1482
- }
1483
- const lockPath = this.getGatewayLockPathForConfig();
1484
- if (!fs.existsSync(lockPath)) {
1485
- return;
1486
- }
1487
- if (await this.probeGatewayPort(port)) {
1488
- this.log(`Gateway lock present but port ${port} is open; leaving lock in place.`);
1489
- return;
1490
- }
1491
- let lockAgeMs = Number.POSITIVE_INFINITY;
1492
- try {
1493
- const st = await fs.promises.stat(lockPath);
1494
- lockAgeMs = Date.now() - st.mtimeMs;
1495
- }
1496
- catch (error) {
1497
- this.log(`Could not stat gateway lock before start: ${error instanceof Error ? error.message : String(error)}`);
1498
- return;
1499
- }
1500
- if (lockAgeMs < STALE_GATEWAY_LOCK_MIN_AGE_MS) {
1501
- this.log(`Gateway lock is only ${Math.round(lockAgeMs / 1000)}s old; not removing (possible concurrent start still binding).`);
1502
- return;
1503
- }
1504
- try {
1505
- fs.rmSync(lockPath, { force: true });
1506
- this.log(`Removed stale gateway lock before start (${Math.round(lockAgeMs / 1000)}s old): ${lockPath}`);
1507
- }
1508
- catch (error) {
1509
- this.log(`Could not remove stale gateway lock before start: ${error instanceof Error ? error.message : String(error)}`);
1510
- }
1511
- }
1512
- async writeGatewayPidRecord(pid, port, binaryPath) {
1513
- await writeGatewayPidFile(pid, port, binaryPath);
1514
- }
1515
- async reconcileUninstallProgressState() {
1516
- if (!this._gatewayUninstallInProgress) {
1517
- return this._lastDetection;
1518
- }
1519
- if (this.revealUninstallSetupTerminal()) {
1520
- return this._lastDetection;
1521
- }
1522
- const result = await detectGateway(this.getGatewayPort());
1523
- this._lastDetection = result;
1524
- this._gatewayUninstallInProgress = false;
1525
- if (result.running) {
1526
- this.setStatus("running");
1527
- }
1528
- else if (result.installed) {
1529
- this.setStatus("installed");
1530
- }
1531
- else {
1532
- this.setStatus("not-installed");
1533
- }
1534
- return result;
1535
- }
1536
- /**
1537
- * Level 1 menu for not-installed state.
1538
- * Not installed: Install | Ask Agent Hippo
1539
- */
1540
- async showNotInstalledQuickMenu() {
1541
- const choicePromise = vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}: Reach your AgentHippo IDE or CLI agents from WhatsApp, Telegram, Slack, Discord, and more - right from your phone or any messaging app. This will also install OpenClaw fork on your computer.`, "Install", "Ask Agent Hippo");
1542
- this.queueBackgroundDetect("opening not-installed menu");
1543
- const choice = await choicePromise;
1544
- switch (choice) {
1545
- case "Install":
1546
- await this.install();
1547
- break;
1548
- case "Ask Agent Hippo":
1549
- await this.openAskAgentHippoChat();
1550
- break;
1551
- }
1552
- }
1553
- /**
1554
- * Level 2 maintenance menu.
1555
- * Config | Reset auth | Restart (only if OpenClaw appears installed) | Uninstall
1556
- */
1557
- async showMaintenanceQuickMenu() {
1558
- // Keep "More" instant: build actions from cached in-memory state and
1559
- // refresh detection in the background.
1560
- const likelyInstalled = this._status === "running" ||
1561
- this._status === "installed" ||
1562
- this._lastDetection?.installed === true;
1563
- // Web UI lives here once channels are configured (the connected menu's front button becomes
1564
- // "Meet at..." in that case); keep it reachable but out of the way.
1565
- const showWebUi = this._status === "running" && this._hasConfiguredChannelsCache === true;
1566
- const actions = [
1567
- ...(showWebUi ? ["Web UI"] : []),
1568
- "Config",
1569
- "Reset auth",
1570
- "Reset mappings",
1571
- ...(likelyInstalled ? ["Restart"] : []),
1572
- "Uninstall",
1573
- ];
1574
- const choicePromise = vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}: More options`, ...actions);
1575
- this.queueBackgroundDetect("opening maintenance menu");
1576
- const choice = await choicePromise;
1577
- switch (choice) {
1578
- case "Web UI":
1579
- await this.openWebUI();
1580
- break;
1581
- case "Config":
1582
- await this.openConfig();
1583
- break;
1584
- case "Reset auth":
1585
- await this.resetAuth();
1586
- break;
1587
- case "Reset mappings":
1588
- await vscode.commands.executeCommand("agentide.gateway.resetMappings");
1589
- break;
1590
- case "Restart":
1591
- await this.restartGateway();
1592
- break;
1593
- case "Uninstall":
1594
- await this.uninstall();
1595
- break;
1596
- }
1597
- }
1598
- /**
1599
- * Best-effort `channels.status` probe used to choose the connected menu's primary button. Caches
1600
- * and returns whether any messaging channel is configured. On an unreachable gateway it returns
1601
- * the last-known value (or false), so the menu falls back to "Web UI" rather than hanging.
1602
- */
1603
- async refreshHasConfiguredChannels() {
1604
- const ref = this._bridgeRef;
1605
- if (!ref?.request) {
1606
- return this._hasConfiguredChannelsCache ?? false;
1607
- }
1608
- try {
1609
- const { rows } = await (0, openclaw_channel_routing_js_1.fetchMeetAgentChannelContext)(ref.request.bind(ref));
1610
- this._hasConfiguredChannelsCache = rows.length > 0;
1611
- }
1612
- catch (err) {
1613
- this.log(`channels.status probe failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
1614
- this._hasConfiguredChannelsCache ??= false;
1615
- }
1616
- return this._hasConfiguredChannelsCache;
1617
- }
1618
- async openAskAgentHippoChat() {
1619
- await vscode.commands.executeCommand("workbench.action.chat.newChat", {
1620
- agentMode: true,
1621
- });
1622
- const initialQuery = "use openclaw skill as needed.";
1623
- await vscode.commands.executeCommand("workbench.action.chat.open", {
1624
- mode: "agent",
1625
- query: initialQuery,
1626
- // Prefill without auto-sending; user can review/edit before sending.
1627
- isPartialQuery: true,
1628
- });
1629
- }
1630
- // ============================================================================
1631
- // Meet Agent at... (channel binding picker)
1632
- // ============================================================================
1633
- /**
1634
- * Show a QuickPick to choose which channels this agent should receive messages from.
1635
- * Queries the gateway for available channels and current bindings, then writes
1636
- * the selected bindings back via config.patch (route bindings).
1637
- */
1638
- async showMeetAgentPicker(agentId) {
1639
- if (!this._bridgeRef?.request) {
1640
- vscode.window.showWarningMessage("Not connected to gateway.");
1641
- return;
1642
- }
1643
- const request = this._bridgeRef.request.bind(this._bridgeRef);
1644
- let didScopeRetry = false;
1645
- const requestWithScopeRetry = async (method, params) => {
1646
- try {
1647
- return await request(method, params);
1648
- }
1649
- catch (err) {
1650
- const message = err instanceof Error ? err.message : String(err);
1651
- if (!didScopeRetry && (0, gateway_scope_errors_js_1.isGatewayScopeAuthError)(message)) {
1652
- didScopeRetry = true;
1653
- this.log(`Gateway denied RPC (${message}); reconnecting with fresh handshake...`);
1654
- await this.disconnectBridge();
1655
- await this.connectBridge();
1656
- // Rebind after reconnect just in case the ref changed.
1657
- if (!this._bridgeRef?.request) {
1658
- throw err;
1659
- }
1660
- return await this._bridgeRef.request(method, params);
1661
- }
1662
- throw err;
1663
- }
1664
- };
1665
- try {
1666
- // Fetch available channels from the gateway (OpenClaw: channels.status).
1667
- this.log("Meet Agent: fetching channels.status...");
1668
- const { rows: channels, statusPayload } = await (0, openclaw_channel_routing_js_1.fetchMeetAgentChannelContext)(requestWithScopeRetry);
1669
- this.log(`Meet Agent: got ${channels?.length ?? 0} channel(s): ${channels?.map((c) => c.id).join(", ") ?? "none"}`);
1670
- if (!channels || channels.length === 0) {
1671
- const choice = await vscode.window.showInformationMessage("No messaging channels (Telegram, WhatsApp, etc.) are configured. You can chat with your agent in the Gateway Web UI, or set up channels in OpenClaw config.", "Open Web UI", "Open Config");
1672
- if (choice === "Open Web UI") {
1673
- await this.openWebUI();
1674
- }
1675
- else if (choice === "Open Config") {
1676
- await this.openConfig();
1677
- }
1678
- return;
1679
- }
1680
- // Fetch current bindings for this agent to pre-select.
1681
- // Use the first registered agent if no specific agentId provided.
1682
- const resolvedAgentId = agentId ?? await this.getFirstRegisteredAgentId();
1683
- if (!resolvedAgentId) {
1684
- vscode.window.showWarningMessage("No agent registered with the gateway.");
1685
- return;
1686
- }
1687
- // Get current bindings from the agents.register response or agents.list.
1688
- const agentsList = await requestWithScopeRetry("agents.list");
1689
- const externalAgent = agentsList.external?.find((a) => a.id.toLowerCase() === resolvedAgentId.toLowerCase());
1690
- const currentlyBound = new Set(externalAgent?.boundChannels?.map((c) => c.toLowerCase()) ?? []);
1691
- const items = channels.map((ch) => ({
1692
- channelId: ch.id,
1693
- label: ch.label,
1694
- description: ch.connected ? "connected" : "offline",
1695
- detail: ch.accounts.length > 1 ? `Accounts: ${ch.accounts.join(", ")}` : undefined,
1696
- picked: currentlyBound.has(ch.id.toLowerCase()),
1697
- }));
1698
- const picked = await vscode.window.showQuickPick(items, {
1699
- canPickMany: true,
1700
- title: `Meet ${resolvedAgentId} at...`,
1701
- placeHolder: "Select channels where people can reach this agent",
1702
- });
1703
- if (!picked) {
1704
- return; // User cancelled.
1705
- }
1706
- const selectedChannels = picked.map((item) => item.channelId);
1707
- // Write route bindings via config.patch (replaces removed bindings.set).
1708
- await (0, openclaw_channel_routing_js_1.applyAgentChannelBindingsViaConfigPatch)(requestWithScopeRetry, {
1709
- agentId: resolvedAgentId,
1710
- selectedChannelIds: selectedChannels,
1711
- statusPayload,
1712
- });
1713
- if (selectedChannels.length === 0) {
1714
- vscode.window.showInformationMessage(`${resolvedAgentId}: all channel bindings removed.`);
1715
- }
1716
- else {
1717
- const names = picked.map((item) => item.label).join(", ");
1718
- vscode.window.showInformationMessage(`${resolvedAgentId} can now be reached at: ${names}`);
1719
- }
1720
- }
1721
- catch (err) {
1722
- this.log(`Meet Agent picker failed: ${err}`);
1723
- vscode.window.showErrorMessage(`Failed to configure channels: ${err instanceof Error ? err.message : String(err)}`);
1724
- }
1725
- }
1726
- async getFirstRegisteredAgentId() {
1727
- if (!this._bridgeRef?.request) {
1728
- return undefined;
1729
- }
1730
- try {
1731
- const result = await this._bridgeRef.request("agents.list");
1732
- return result.external?.[0]?.id;
1733
- }
1734
- catch {
1735
- return undefined;
1736
- }
1737
- }
1738
- // ============================================================================
1739
- // Detect
1740
- // ============================================================================
1741
- async detect() {
1742
- const port = this.getGatewayPort();
1743
- const baseResult = await detectGateway(port);
1744
- let result = baseResult;
1745
- // Do not clobber in-progress lifecycle UI while setup/teardown runs in terminals.
1746
- if (this._gatewayInstallInProgress || this._gatewayUninstallInProgress) {
1747
- this._lastDetection = result;
1748
- return result;
1749
- }
1750
- // Do not downgrade "starting" back to "installed" while start() is in-flight.
1751
- if (this._startInFlight && this._status === "starting") {
1752
- this._lastDetection = result;
1753
- return result;
1754
- }
1755
- if (!result.running && result.installed) {
1756
- const managedInstallPresent = await this.hasGlobalNpmOpenclawTree();
1757
- if (!managedInstallPresent) {
1758
- result = { ...result, installed: false };
1759
- }
1760
- }
1761
- this._lastDetection = result;
1762
- if (result.running) {
1763
- this.setStatus("running");
1764
- }
1765
- else if (result.installed) {
1766
- this.setStatus("installed");
1767
- }
1768
- else {
1769
- this.setStatus("not-installed");
1770
- }
1771
- return result;
1772
- }
1773
- // ============================================================================
1774
- // Install
1775
- // ============================================================================
1776
- async install() {
1777
- const installSource = this.getConfig("installSource", "");
1778
- const port = this.getGatewayPort();
1779
- if (!installSource) {
1780
- vscode.window.showErrorMessage(`${exports.AGENT_ANYWHERE_LABEL}: Install source not configured. Set "${(0, branding_js_1.toSettingsDisplayKey)("agentide.anywhere.installSource")}" in settings.`);
1781
- return false;
1782
- }
1783
- // Security: this value names a package that is installed globally and whose postinstall
1784
- // script runs as the user. It is validated against an ALLOWLIST of specifier shapes (see
1785
- // validateAnywhereInstallSource) and then passed to the setup terminal through an
1786
- // environment variable rather than being interpolated into a command string.
1787
- const sourceCheck = (0, installSource_js_1.validateAnywhereInstallSource)(installSource);
1788
- if (!sourceCheck.ok) {
1789
- vscode.window.showErrorMessage(`${exports.AGENT_ANYWHERE_LABEL}: Refusing to install from "${installSource}" — ${sourceCheck.reason}. ` +
1790
- `Fix "${(0, branding_js_1.toSettingsDisplayKey)("agentide.anywhere.installSource")}" in your user settings.`);
1791
- this.log(`Rejected installSource (${sourceCheck.reason}): ${installSource}`);
1792
- return false;
1793
- }
1794
- if (this._gatewayInstallInProgress) {
1795
- const dupChoice = await vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}: Install is already in progress. Watch the "${exports.AGENT_ANYWHERE_LABEL} Setup" terminal.`, "Show Setup Terminal", "OK");
1796
- if (dupChoice === "Show Setup Terminal") {
1797
- if (!this.revealInstallSetupTerminal()) {
1798
- await this.showMissingInstallSetupTerminalPrompt();
1799
- }
1800
- }
1801
- return false;
1802
- }
1803
- this.setInstallProgressFlag(true, "install command started");
1804
- try {
1805
- this.log(`Installing gateway from: ${installSource}`);
1806
- (0, index_1.clearBinaryCache)(FORK_CLI_NAME);
1807
- (0, index_1.clearBinaryCache)("openclaw");
1808
- // Prevent the status bar from flipping to "running" while install is in
1809
- // progress. During the install window, the port may transiently open,
1810
- // and `detect()` would otherwise turn the icon green early.
1811
- this.setStatus("starting");
1812
- this.stopHealthChecks();
1813
- // Stop a running gateway before setup so npm/git can replace the global
1814
- // CLI without file locks on the binary (especially on Windows).
1815
- const snapshot = await this.detectGatewayState(port);
1816
- this._lastDetection = snapshot;
1817
- if (snapshot.running) {
1818
- this.log("Stopping gateway before install...");
1819
- await this.disconnectBridge();
1820
- await this.stop();
1821
- }
1822
- // Find the setup script (platform-specific: .ps1 on Windows, .sh elsewhere)
1823
- const scriptExt = IS_WINDOWS ? "setup-gateway.ps1" : "setup-gateway.sh";
1824
- const scriptCandidates = [
1825
- path.join(__dirname, "..", "..", "scripts", "gateway", scriptExt),
1826
- path.join(__dirname, "..", "scripts", "gateway", scriptExt),
1827
- ];
1828
- const scriptPath = scriptCandidates.find((p) => fs.existsSync(p));
1829
- // Find the config template
1830
- const templateCandidates = [
1831
- path.join(__dirname, "..", "..", "base-prompts", "gateway", "openclaw-starter.json5"),
1832
- path.join(__dirname, "..", "base-prompts", "gateway", "openclaw-starter.json5"),
1833
- ];
1834
- const templatePath = templateCandidates.find((p) => fs.existsSync(p));
1835
- let terminalOptions;
1836
- // NOTE ON WHY THIS IS NOT execFile/spawn WITH AN ARGV ARRAY.
1837
- // The setup runs in a VISIBLE terminal (the user watches npm/git progress and answers
1838
- // prompts), and it must run under a LOGIN shell: node/npm/pnpm on macOS commonly live on
1839
- // a PATH built by ~/.zprofile, and `getAnywhereSetupShellArgs` uses `-lic`/`-lc` for
1840
- // exactly that reason. A bare argv exec would break every nvm/volta install.
1841
- // So the shell stays — but no untrusted value is ever spliced into the command text.
1842
- // `installSource` (and the two bundled paths) travel in the terminal's ENVIRONMENT and
1843
- // are referenced as `"$VAR"` / `$env:VAR`, which the shell expands AFTER parsing: the
1844
- // value becomes exactly one argument and none of its characters can become syntax.
1845
- const setupEnv = {
1846
- ...anywhereSetupTerminalEnv(),
1847
- [installSource_js_1.ANYWHERE_INSTALL_SOURCE_ENV]: installSource,
1848
- ...(scriptPath ? { [installSource_js_1.ANYWHERE_SETUP_SCRIPT_ENV]: scriptPath } : {}),
1849
- ...(templatePath ? { [installSource_js_1.ANYWHERE_CONFIG_TEMPLATE_ENV]: templatePath } : {}),
1850
- };
1851
- if (scriptPath && IS_WINDOWS) {
1852
- // Windows: run PowerShell script
1853
- const args = [`-Source $env:${installSource_js_1.ANYWHERE_INSTALL_SOURCE_ENV}`];
1854
- if (templatePath) {
1855
- args.push(`-ConfigTemplate $env:${installSource_js_1.ANYWHERE_CONFIG_TEMPLATE_ENV}`);
1856
- }
1857
- const psCmd = `& $env:${installSource_js_1.ANYWHERE_SETUP_SCRIPT_ENV} ${args.join(" ")}; Write-Host ""; Write-Host "You can close this window."`;
1858
- terminalOptions = {
1859
- name: AGENT_ANYWHERE_SETUP_TERMINAL,
1860
- shellPath: "powershell.exe",
1861
- shellArgs: ["-NoProfile", "-NoExit", "-ExecutionPolicy", "Bypass", "-Command", psCmd],
1862
- env: {
1863
- [installSource_js_1.ANYWHERE_INSTALL_SOURCE_ENV]: installSource,
1864
- [installSource_js_1.ANYWHERE_SETUP_SCRIPT_ENV]: scriptPath,
1865
- ...(templatePath ? { [installSource_js_1.ANYWHERE_CONFIG_TEMPLATE_ENV]: templatePath } : {}),
1866
- },
1867
- };
1868
- }
1869
- else if (scriptPath) {
1870
- // macOS/Linux: run bash script
1871
- const args = [`--source "$${installSource_js_1.ANYWHERE_INSTALL_SOURCE_ENV}"`];
1872
- if (templatePath) {
1873
- args.push(`--config-template "$${installSource_js_1.ANYWHERE_CONFIG_TEMPLATE_ENV}"`);
1874
- }
1875
- const setupCmd = `bash "$${installSource_js_1.ANYWHERE_SETUP_SCRIPT_ENV}" ${args.join(" ")}`;
1876
- const holdOpenCmd = `${setupCmd}; echo ""; echo "Press Enter to close..."; read`;
1877
- terminalOptions = {
1878
- name: AGENT_ANYWHERE_SETUP_TERMINAL,
1879
- shellPath: getAnywhereSetupShellPath(),
1880
- shellArgs: getAnywhereSetupShellArgs(holdOpenCmd),
1881
- env: setupEnv,
1882
- };
1883
- }
1884
- else {
1885
- // Fallback: raw npm install (script not bundled)
1886
- this.log("Setup script not found, falling back to npm install");
1887
- const fallbackCmd = `npm install -g "$${installSource_js_1.ANYWHERE_INSTALL_SOURCE_ENV}"`;
1888
- const holdOpenCmd = `${fallbackCmd}; echo ""; echo "Press Enter to close..."; read`;
1889
- terminalOptions = IS_WINDOWS
1890
- // cmd.exe is the one shell here that expands `%VAR%` DURING parsing rather than
1891
- // after it, so the env indirection alone is not a boundary on Windows — the
1892
- // allowlist above is what makes this safe (it admits no `&`, `|`, `^`, quote or
1893
- // whitespace outside a path shape). Kept as cmd.exe because this fallback only
1894
- // fires when the bundled setup script is missing, and PowerShell is not
1895
- // guaranteed present on every Windows host we support.
1896
- ? {
1897
- name: AGENT_ANYWHERE_SETUP_TERMINAL,
1898
- shellPath: "cmd.exe",
1899
- shellArgs: ["/c", `npm install -g "%${installSource_js_1.ANYWHERE_INSTALL_SOURCE_ENV}%" && echo. && echo Setup complete. && pause`],
1900
- env: { [installSource_js_1.ANYWHERE_INSTALL_SOURCE_ENV]: installSource },
1901
- }
1902
- : {
1903
- name: AGENT_ANYWHERE_SETUP_TERMINAL,
1904
- shellPath: getAnywhereSetupShellPath(),
1905
- shellArgs: getAnywhereSetupShellArgs(holdOpenCmd),
1906
- env: setupEnv,
1907
- };
1908
- }
1909
- // Run in a visible terminal so user can see progress
1910
- try {
1911
- fs.rmSync(GATEWAY_INSTALL_STATUS_FILE, { force: true });
1912
- }
1913
- catch {
1914
- // ignore stale install status cleanup failures
1915
- }
1916
- this._installProgressMessage = "Launching setup...";
1917
- this.updateStatusBar("starting");
1918
- const terminal = vscode.window.createTerminal(terminalOptions);
1919
- this._installSetupTerminal = terminal;
1920
- terminal.show();
1921
- const installTimeoutMs = installSourceNeedsBuild(installSource)
1922
- ? 10 * 60_000
1923
- : 120_000;
1924
- this.log(`Waiting up to ${Math.round(installTimeoutMs / 60_000)} minute(s) for gateway install completion...`);
1925
- const useInstallStatusMarker = IS_WINDOWS && !!scriptPath;
1926
- const installSucceeded = await vscode.window.withProgress({
1927
- location: vscode.ProgressLocation.Window,
1928
- title: `${exports.AGENT_ANYWHERE_LABEL}: Installing gateway...`,
1929
- cancellable: false,
1930
- }, async (progress) => {
1931
- const deadline = Date.now() + installTimeoutMs;
1932
- let lastPercent = 0;
1933
- while (Date.now() < deadline) {
1934
- if (!this._gatewayInstallInProgress) {
1935
- this.log("Gateway install wait aborted — setup terminal closed before completion was detected");
1936
- return false;
1937
- }
1938
- const installStatus = this.readInstallStatusSnapshot();
1939
- const summary = this.getInstallProgressSummary(installStatus);
1940
- this._installProgressMessage = summary.message;
1941
- this.updateStatusBar("starting");
1942
- const increment = Math.max(0, summary.percent - lastPercent);
1943
- progress.report({ message: summary.message, increment });
1944
- lastPercent = Math.max(lastPercent, summary.percent);
1945
- if (installStatus?.phase === "failed") {
1946
- this.log(`Gateway install failed: ${installStatus.lastError || installStatus.message || "unknown error"}`);
1947
- return false;
1948
- }
1949
- (0, index_1.clearBinaryCache)(FORK_CLI_NAME);
1950
- (0, index_1.clearBinaryCache)("openclaw");
1951
- const result = await this.detectGatewayState(port);
1952
- this._lastDetection = result;
1953
- const installReady = useInstallStatusMarker
1954
- ? isGatewayInstallComplete(installStatus) && result.installed
1955
- : result.installed;
1956
- if (installReady) {
1957
- this.log("Gateway installed successfully");
1958
- await this.ensureManagedGatewayWrapperState(result.binaryPath);
1959
- // Build the web Control UI if it's missing.
1960
- // The setup script should do this, but if it failed or was
1961
- // skipped (e.g. missing pnpm), build it now so the UI works
1962
- // out of the box for new users.
1963
- await this.ensureWebUIBuilt(result.binaryPath);
1964
- // Write starter config if none exists
1965
- this.writeStarterConfig();
1966
- // Auto-enable the feature now that it's installed
1967
- const anywhereConfig = vscode.workspace.getConfiguration("agentide.anywhere");
1968
- await anywhereConfig.update("enabled", true, vscode.ConfigurationTarget.Global);
1969
- if (result.running) {
1970
- this.finishInstallUiState("running", "success");
1971
- progress.report({
1972
- message: `Install complete. Gateway is running on port ${port}.`,
1973
- increment: Math.max(0, 100 - lastPercent),
1974
- });
1975
- }
1976
- else {
1977
- this.finishInstallUiState("installed", "success");
1978
- progress.report({
1979
- message: "Install complete. Gateway is installed. Click Agent Anywhere to start.",
1980
- increment: Math.max(0, 100 - lastPercent),
1981
- });
1982
- }
1983
- // Install no longer auto-starts the gateway; starting is an explicit user action.
1984
- void vscode.window.showInformationMessage(result.running
1985
- ? `${exports.AGENT_ANYWHERE_LABEL}: Installed and running. Open the config to add your channels or use the status bar to connect.`
1986
- : `${exports.AGENT_ANYWHERE_LABEL}: Installed. Open the config to add your channels, then click Agent Anywhere to start.`, "Open Config").then(async (choice) => {
1987
- if (choice === "Open Config") {
1988
- await this.openConfig();
1989
- }
1990
- });
1991
- return true;
1992
- }
1993
- await new Promise((resolve) => setTimeout(resolve, 1_000));
1994
- }
1995
- return false;
1996
- });
1997
- if (!installSucceeded) {
1998
- this.log("Gateway install timed out or failed");
1999
- this.finishInstallUiState("error", "failure");
2000
- return false;
2001
- }
2002
- return true;
2003
- }
2004
- catch (e) {
2005
- this.log(`Install failed: ${e instanceof Error ? e.message : String(e)}`);
2006
- this.finishInstallUiState("error", "failure");
2007
- return false;
2008
- }
2009
- finally {
2010
- if (this._gatewayInstallInProgress) {
2011
- this.setInstallProgressFlag(false, "finally");
2012
- this._installProgressMessage = undefined;
2013
- }
2014
- this.updateStatusBar(this._status);
2015
- }
2016
- }
2017
- /**
2018
- * Remove the globally installed `openclaw` npm package (runs setup script --uninstall / -Uninstall).
2019
- * The script runs `openclaw uninstall --service --yes --non-interactive` when the CLI exists,
2020
- * then `npm uninstall -g openclaw`, then frees loopback listener(s) if still bound (see OpenClaw docs: /cli/uninstall).
2021
- * Does not delete ~/.openclaw (config, sessions, credentials) unless the user runs OpenClaw with --state.
2022
- */
2023
- async uninstall() {
2024
- if (this._gatewayUninstallInProgress) {
2025
- await this.showGatewayUninstallInProgressPrompt();
2026
- return;
2027
- }
2028
- await this.stop();
2029
- const scriptExt = IS_WINDOWS ? "setup-gateway.ps1" : "setup-gateway.sh";
2030
- const scriptCandidates = [
2031
- path.join(__dirname, "..", "scripts", "gateway", scriptExt),
2032
- path.join(__dirname, "..", "..", "scripts", "gateway", scriptExt),
2033
- ];
2034
- const scriptPath = scriptCandidates.find((p) => fs.existsSync(p));
2035
- let terminalOptions;
2036
- if (scriptPath && IS_WINDOWS) {
2037
- const psCmd = `& "${scriptPath}" -Uninstall; Write-Host ""; Write-Host "You can close this window."`;
2038
- terminalOptions = {
2039
- name: AGENT_ANYWHERE_UNINSTALL_TERMINAL,
2040
- shellPath: "powershell.exe",
2041
- shellArgs: ["-NoProfile", "-NoExit", "-ExecutionPolicy", "Bypass", "-Command", psCmd],
2042
- };
2043
- }
2044
- else if (scriptPath) {
2045
- const setupCmd = `bash "${scriptPath}" --uninstall`;
2046
- const holdOpenCmd = `${setupCmd}; echo ""; echo "Press Enter to close..."; read`;
2047
- terminalOptions = {
2048
- name: AGENT_ANYWHERE_UNINSTALL_TERMINAL,
2049
- shellPath: getAnywhereSetupShellPath(),
2050
- shellArgs: getAnywhereSetupShellArgs(holdOpenCmd),
2051
- env: anywhereSetupTerminalEnv(),
2052
- };
2053
- }
2054
- else {
2055
- const fallbackCmd = "npm uninstall -g openclaw";
2056
- const holdOpenCmd = `${fallbackCmd}; echo ""; echo "Press Enter to close..."; read`;
2057
- terminalOptions = IS_WINDOWS
2058
- ? {
2059
- name: AGENT_ANYWHERE_UNINSTALL_TERMINAL,
2060
- shellPath: "cmd.exe",
2061
- shellArgs: ["/c", `${fallbackCmd} && echo. && pause`],
2062
- }
2063
- : {
2064
- name: AGENT_ANYWHERE_UNINSTALL_TERMINAL,
2065
- shellPath: getAnywhereSetupShellPath(),
2066
- shellArgs: getAnywhereSetupShellArgs(holdOpenCmd),
2067
- env: anywhereSetupTerminalEnv(),
2068
- };
2069
- }
2070
- this._gatewayUninstallInProgress = true;
2071
- this.setStatus("stopping");
2072
- try {
2073
- const terminal = vscode.window.createTerminal(terminalOptions);
2074
- this._uninstallSetupTerminal = terminal;
2075
- terminal.show();
2076
- this.log("Gateway uninstall started in terminal");
2077
- (0, index_1.clearBinaryCache)(FORK_CLI_NAME);
2078
- (0, index_1.clearBinaryCache)("openclaw");
2079
- await this.sleep(this.getUninstallInitialDelayMs());
2080
- const deadline = Date.now() + this.getUninstallVerificationTimeoutMs();
2081
- let lastResult;
2082
- let managedInstallPresent = true;
2083
- while (Date.now() < deadline) {
2084
- (0, index_1.clearBinaryCache)(FORK_CLI_NAME);
2085
- (0, index_1.clearBinaryCache)("openclaw");
2086
- const result = await this.detectGatewayState(this.getGatewayPort());
2087
- lastResult = result;
2088
- this._lastDetection = result;
2089
- managedInstallPresent = await this.hasGlobalNpmOpenclawTree();
2090
- // If the npm-managed OpenClaw package is gone and no local listener is alive,
2091
- // consider Agent Anywhere uninstall complete even if another `openclaw`
2092
- // binary remains on PATH (for example, a self-updated/external install).
2093
- if (!managedInstallPresent && !result.running) {
2094
- removeManagedGatewayWrapperState();
2095
- this.setStatus("not-installed");
2096
- if (result.installed) {
2097
- this.log("Managed OpenClaw npm package removed; another OpenClaw binary remains on PATH (likely external/self-updated).");
2098
- }
2099
- else {
2100
- this.log("Global OpenClaw package removed (or no longer detected).");
2101
- }
2102
- return;
2103
- }
2104
- if (!result.installed) {
2105
- removeManagedGatewayWrapperState();
2106
- this.setStatus("not-installed");
2107
- this.log("Global OpenClaw package removed (or no longer detected).");
2108
- return;
2109
- }
2110
- await this.sleep(this.getUninstallCheckIntervalMs());
2111
- }
2112
- if (!managedInstallPresent && lastResult?.running) {
2113
- void vscode.window.showWarningMessage(`${exports.AGENT_ANYWHERE_LABEL}: Managed package was removed, but the gateway listener still appears to be running. Finish the terminal command or run Status.`);
2114
- }
2115
- else {
2116
- void vscode.window.showWarningMessage(`${exports.AGENT_ANYWHERE_LABEL}: Uninstall is still verifying managed package removal. Finish the terminal command or run Status.`);
2117
- }
2118
- }
2119
- finally {
2120
- this._gatewayUninstallInProgress = false;
2121
- this.updateStatusBar(this._status);
2122
- }
2123
- }
2124
- // ============================================================================
2125
- // Config
2126
- // ============================================================================
2127
- /** Path to the OpenClaw config file */
2128
- get configPath() {
2129
- return path.join(os.homedir(), ".openclaw", "openclaw.json");
2130
- }
2131
- /**
2132
- * Write a starter openclaw.json if none exists.
2133
- * Reads the template from base-prompts/gateway/openclaw-starter.json5
2134
- * (copied to out/ during compile). JSON5 format -- comments supported by OpenClaw.
2135
- */
2136
- writeStarterConfig() {
2137
- const configDir = path.dirname(this.configPath);
2138
- if (fs.existsSync(this.configPath)) {
2139
- this.log("Config already exists, skipping starter config");
2140
- return;
2141
- }
2142
- // Find the starter template (check compiled output first, then source for dev)
2143
- const templateCandidates = [
2144
- path.join(__dirname, "..", "base-prompts", "gateway", "openclaw-starter.json5"),
2145
- path.join(__dirname, "..", "..", "base-prompts", "gateway", "openclaw-starter.json5"),
2146
- ];
2147
- const templatePath = templateCandidates.find((p) => fs.existsSync(p));
2148
- if (!templatePath) {
2149
- this.log("Starter config template not found, skipping");
2150
- return;
2151
- }
2152
- fs.mkdirSync(configDir, { recursive: true, mode: 0o700 });
2153
- // Read template and replace placeholder token with a random one
2154
- let content = fs.readFileSync(templatePath, "utf-8");
2155
- const randomToken = `ah-${(0, crypto_1.randomBytes)(24).toString("hex")}`;
2156
- content = content.replace("__GENERATED_TOKEN__", randomToken);
2157
- fs.writeFileSync(this.configPath, content, { encoding: "utf-8", mode: IS_WINDOWS ? undefined : 0o600 });
2158
- this.log(`Wrote starter config to ${this.configPath}`);
2159
- }
2160
- /**
2161
- * Open the OpenClaw config file in the editor.
2162
- */
2163
- async openConfig() {
2164
- if (!fs.existsSync(this.configPath)) {
2165
- this.writeStarterConfig();
2166
- }
2167
- const doc = await vscode.workspace.openTextDocument(this.configPath);
2168
- await vscode.window.showTextDocument(doc);
2169
- }
2170
- /**
2171
- * Check that every enabled channel's env-sourced secrets resolve before spawning the
2172
- * gateway. On missing secrets, offer to disable the affected channels (config is backed
2173
- * up first) or to open ~/.agent-hippo/.env with the missing keys stubbed in.
2174
- *
2175
- * Configs that are not strict JSON (OpenClaw accepts JSON5 with comments) are left to
2176
- * the gateway itself: rewriting them would destroy comments, and the fail-fast exit
2177
- * detection in doStart still surfaces the error promptly.
2178
- */
2179
- async preflightChannelSecrets(gatewayEnv) {
2180
- let content;
2181
- try {
2182
- content = fs.readFileSync(this.configPath, "utf-8");
2183
- }
2184
- catch {
2185
- return "continue"; // No config yet — nothing to resolve.
2186
- }
2187
- const config = (0, secretsPreflight_js_1.parseStrictJsonConfig)(content);
2188
- if (config === undefined) {
2189
- return "continue";
2190
- }
2191
- const missing = (0, secretsPreflight_js_1.collectMissingChannelEnvSecrets)(config, gatewayEnv);
2192
- if (missing.length === 0) {
2193
- return "continue";
2194
- }
2195
- const envFilePath = (0, agentEnvFile_js_1.getAgentHomeEnvFilePath)();
2196
- const channelNames = missing.map((m) => m.channel).join(", ");
2197
- const varNames = [...new Set(missing.flatMap((m) => m.envVars))];
2198
- this.log(`Channel secrets preflight: ${channelNames} missing ${varNames.join(", ")}`);
2199
- const disableAndStart = `Disable ${channelNames} & Start`;
2200
- const addSecrets = "Add Secrets";
2201
- const choice = await vscode.window.showErrorMessage(`${exports.AGENT_ANYWHERE_LABEL}: The ${channelNames} channel needs ${varNames.join(", ")}, which ${varNames.length === 1 ? "is" : "are"} not set in ${envFilePath} or the IDE environment. Add the secret${varNames.length === 1 ? "" : "s"} or disable the channel to start the gateway.`, disableAndStart, addSecrets);
2202
- if (choice === disableAndStart) {
2203
- const updated = (0, secretsPreflight_js_1.disableChannelsInConfigText)(content, missing.map((m) => m.channel));
2204
- if (!updated) {
2205
- void vscode.window.showErrorMessage(`${exports.AGENT_ANYWHERE_LABEL}: Could not update ${this.configPath}. Set "enabled": false on the ${channelNames} channel manually, then start again.`);
2206
- return "abort";
2207
- }
2208
- const backupPath = `${this.configPath}.bak-missing-secrets`;
2209
- try {
2210
- fs.copyFileSync(this.configPath, backupPath);
2211
- fs.writeFileSync(this.configPath, updated, { encoding: "utf-8", mode: IS_WINDOWS ? undefined : 0o600 });
2212
- }
2213
- catch (err) {
2214
- const detail = err instanceof Error ? err.message : String(err);
2215
- void vscode.window.showErrorMessage(`${exports.AGENT_ANYWHERE_LABEL}: Could not update ${this.configPath} (${detail}).`);
2216
- return "abort";
2217
- }
2218
- this.log(`Disabled channels missing secrets: ${channelNames} (backup: ${backupPath})`);
2219
- void vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}: Disabled the ${channelNames} channel (backup: ${path.basename(backupPath)}). To re-enable: add ${varNames.join(", ")} to ${envFilePath}, set "enabled": true in the config, then restart the gateway.`);
2220
- return "continue";
2221
- }
2222
- if (choice === addSecrets) {
2223
- await this.openEnvFileWithSecretStubs(envFilePath, varNames);
2224
- }
2225
- return "abort";
2226
- }
2227
- /** Append stub lines for missing secret keys to ~/.agent-hippo/.env and open it. */
2228
- async openEnvFileWithSecretStubs(envFilePath, keys) {
2229
- try {
2230
- let content = "";
2231
- try {
2232
- content = fs.readFileSync(envFilePath, "utf-8");
2233
- }
2234
- catch {
2235
- // New file.
2236
- }
2237
- const toAdd = keys.filter((key) => !new RegExp(`^\\s*#?\\s*${key}=`, "m").test(content));
2238
- if (toAdd.length) {
2239
- const separator = content.length && !content.endsWith("\n") ? "\n" : "";
2240
- const block = [
2241
- `# Added by ${exports.AGENT_ANYWHERE_LABEL} — fill in, then start the gateway again.`,
2242
- ...toAdd.map((key) => `${key}=`),
2243
- ].join("\n");
2244
- fs.writeFileSync(envFilePath, `${content}${separator}${block}\n`, {
2245
- encoding: "utf-8",
2246
- mode: IS_WINDOWS ? undefined : 0o600,
2247
- });
2248
- }
2249
- const doc = await vscode.workspace.openTextDocument(vscode.Uri.file(envFilePath));
2250
- await vscode.window.showTextDocument(doc, { preview: false });
2251
- }
2252
- catch (err) {
2253
- const detail = err instanceof Error ? err.message : String(err);
2254
- void vscode.window.showErrorMessage(`${exports.AGENT_ANYWHERE_LABEL}: Could not open ${envFilePath} (${detail}).`);
2255
- }
2256
- }
2257
- async startGatewayViaHiddenWindowsProcess(spawnExe, spawnArgs, logPath, errorLogPath, gatewayEnv) {
2258
- const argumentLine = (0, windowsCommandLine_js_1.buildWindowsArgumentLine)(spawnArgs);
2259
- const argumentClause = argumentLine.length > 0
2260
- ? ` -ArgumentList '${escapePowerShellSingleQuotedString(argumentLine)}'`
2261
- : "";
2262
- const psCommand = [
2263
- `$proc = Start-Process -FilePath '${escapePowerShellSingleQuotedString(spawnExe)}' -WindowStyle Hidden${argumentClause} -RedirectStandardOutput '${escapePowerShellSingleQuotedString(logPath)}' -RedirectStandardError '${escapePowerShellSingleQuotedString(errorLogPath)}' -PassThru`,
2264
- "Write-Output $proc.Id",
2265
- ].join("; ");
2266
- return new Promise((resolve) => {
2267
- let stdout = "";
2268
- let stderr = "";
2269
- const launcher = (0, child_process_1.spawn)("powershell.exe", ["-NoProfile", "-ExecutionPolicy", "Bypass", "-Command", psCommand], {
2270
- windowsHide: true,
2271
- env: gatewayEnv,
2272
- });
2273
- launcher.stdout.on("data", (data) => {
2274
- stdout += data.toString();
2275
- });
2276
- launcher.stderr.on("data", (data) => {
2277
- stderr += data.toString();
2278
- });
2279
- launcher.once("error", (err) => {
2280
- this.log(`Gateway hidden Windows launcher failed to spawn: ${err}`);
2281
- resolve(undefined);
2282
- });
2283
- launcher.once("exit", (code) => {
2284
- if (code !== 0) {
2285
- const detail = stderr.trim() || stdout.trim();
2286
- this.log(`Gateway hidden Windows launcher exited with code ${code}${detail ? `: ${detail}` : ""}`);
2287
- resolve(undefined);
2288
- return;
2289
- }
2290
- const startedPid = parseInt(stdout.trim(), 10);
2291
- if (!Number.isNaN(startedPid)) {
2292
- this.log(`Gateway hidden Windows process started (PID: ${startedPid})`);
2293
- resolve(startedPid);
2294
- return;
2295
- }
2296
- this.log("Gateway hidden Windows launcher did not return a PID");
2297
- resolve(undefined);
2298
- });
2299
- });
2300
- }
2301
- // ============================================================================
2302
- // Start
2303
- // ============================================================================
2304
- async start() {
2305
- if (this._gatewayUninstallInProgress) {
2306
- await this.showGatewayUninstallInProgressPrompt();
2307
- return;
2308
- }
2309
- if (this._startInFlight) {
2310
- this.setStatus("starting");
2311
- await this._startInFlight;
2312
- return;
2313
- }
2314
- this.setStatus("starting");
2315
- const startPromise = this.doStart();
2316
- this._startInFlight = startPromise;
2317
- try {
2318
- await startPromise;
2319
- }
2320
- finally {
2321
- if (this._startInFlight === startPromise) {
2322
- this._startInFlight = null;
2323
- }
2324
- }
2325
- }
2326
- async doStart() {
2327
- // Fast check first: if PID file says running, skip slow detection
2328
- const port = this.getGatewayPort();
2329
- const fast = await this.detectGatewayFastState(port);
2330
- if (fast?.running) {
2331
- this.log("Gateway already running (fast check)");
2332
- this._lastDetection = fast;
2333
- this.setStatus("running");
2334
- return;
2335
- }
2336
- // Full detection needed (finds binary path, checks install status)
2337
- const detection = await this.detect();
2338
- const portOccupied = await this.probeGatewayPort(port);
2339
- if (!detection.installed) {
2340
- if (portOccupied) {
2341
- const approved = await this.promptNonManagedPortTakeover(port, "start");
2342
- if (!approved || (await this.probeGatewayPort(port))) {
2343
- this.setStatus("error");
2344
- return;
2345
- }
2346
- }
2347
- const doInstall = await vscode.window.showWarningMessage(`${exports.AGENT_ANYWHERE_LABEL}: Gateway is not installed.`, "Install Now");
2348
- if (doInstall === "Install Now") {
2349
- await this.install();
2350
- }
2351
- else {
2352
- // Dismiss leaves start(); we must not keep "starting" (blocks background detect / toggle).
2353
- this.setStatus("not-installed");
2354
- }
2355
- return;
2356
- }
2357
- if (detection.running) {
2358
- this.log("Gateway already running");
2359
- this.setStatus("running");
2360
- return;
2361
- }
2362
- if (portOccupied) {
2363
- const approved = await this.promptNonManagedPortTakeover(port, "start");
2364
- if (!approved || (await this.probeGatewayPort(port))) {
2365
- this.setStatus("error");
2366
- return;
2367
- }
2368
- }
2369
- if (!detection.binaryPath) {
2370
- this.setStatus("error");
2371
- vscode.window.showErrorMessage(`${exports.AGENT_ANYWHERE_LABEL}: Gateway binary not found. Try reinstalling.`);
2372
- return;
2373
- }
2374
- const binaryPath = detection.binaryPath;
2375
- // Final attach guard: detection.running was evaluated earlier, but detection and the
2376
- // stale-lock cleanup below take seconds (CLI `--version`, `gateway stop`), during which a
2377
- // healthy gateway -- ours from a prior start, or one started via the CLI -- can bind the
2378
- // port. Re-check now and attach instead of force-respawning, which would kill it.
2379
- if ((await this.probeGatewayPort(port)) && (await isTrustedGatewayListener(port, binaryPath))) {
2380
- this.log("Healthy gateway already listening; attaching instead of starting a new one");
2381
- this._lastDetection = { ...detection, running: true };
2382
- this.setStatus("running");
2383
- return;
2384
- }
2385
- // Best-effort stale-lock cleanup: OpenClaw can report "gateway already running"
2386
- // even when no trusted listener is present, for example after a crashed prior
2387
- // instance. Ask the CLI to stop first so a fresh local start can succeed.
2388
- await this.stopExistingGatewayBeforeStart(binaryPath);
2389
- await this.removeStaleGatewayLockBeforeStart(port);
2390
- // Only force-free the port when something still holds it right before launch (a non-managed
2391
- // listener, or a straggler from a crashed prior instance). When the port is free -- the
2392
- // normal case -- omit `--force` so we never kill a gateway that bound during startup.
2393
- // stop() now reliably reaps our own listener, so `--force` is no longer needed for restarts.
2394
- const gatewayArgs = ["gateway", "run", "--bind", "loopback", "--port", String(port)];
2395
- if (await this.probeGatewayPort(port)) {
2396
- gatewayArgs.push("--force");
2397
- }
2398
- let spawnExe = binaryPath;
2399
- let spawnArgs = gatewayArgs;
2400
- let spawnShell = false;
2401
- const isWindowsLaunch = this.isWindowsLaunch();
2402
- if (isWindowsLaunch) {
2403
- // Windows lifecycle launch must avoid shell-backed .cmd/.ps1 shims. Those wrappers can
2404
- // create a visible console window and make detached startup behave like a terminal-owned
2405
- // session. Repair the managed runtime metadata if needed, then launch node.exe directly.
2406
- const windowsLaunchRuntime = await this.resolveWindowsManagedGatewayLaunchRuntime(binaryPath);
2407
- if (!windowsLaunchRuntime) {
2408
- this.setStatus("installed");
2409
- const choice = await vscode.window.showWarningMessage(`${exports.AGENT_ANYWHERE_LABEL}: Windows background start requires managed gateway runtime metadata. Reinstall Agent Anywhere to refresh the launch configuration.`, "Reinstall");
2410
- if (choice === "Reinstall") {
2411
- await this.install();
2412
- }
2413
- return;
2414
- }
2415
- spawnExe = windowsLaunchRuntime.nodePath;
2416
- spawnArgs = [windowsLaunchRuntime.entryScriptPath, ...gatewayArgs];
2417
- }
2418
- // Gateway log file -- survives IDE restarts so users can debug issues
2419
- const logDir = path.dirname(DEFAULT_GATEWAY_LOG_PATH);
2420
- fs.mkdirSync(logDir, { recursive: true });
2421
- const logPath = DEFAULT_GATEWAY_LOG_PATH;
2422
- const errorLogPath = path.join(logDir, "gateway.error.log");
2423
- this.log(`Starting gateway: ${spawnExe} ${spawnArgs.join(" ")}`);
2424
- this.log(`Gateway log: ${logPath}`);
2425
- // Re-reads ~/.agent-hippo/.env and overlays managed secret keys so token rotations
2426
- // (e.g. switching Slack apps) take effect here instead of reusing the stale process.env
2427
- // snapshot captured at extension activation.
2428
- const gatewayEnv = await (0, agentEnvFile_js_1.readGatewayChildProcessEnv)();
2429
- // The IDE spawns the gateway directly (detached), with no launchd/systemd supervisor. On a
2430
- // config-triggered restart the gateway would otherwise take the "supervised" path -- exit(0)
2431
- // expecting a supervisor to relaunch it -- and never come back. Force the in-process restart
2432
- // path so SIGUSR1 restarts re-listen in the same process and survive (no supervisor needed).
2433
- gatewayEnv.OPENCLAW_NO_RESPAWN = "1";
2434
- for (const key of agentEnvFile_js_1.GATEWAY_SECRET_ENV_KEYS) {
2435
- if (gatewayEnv[key]?.trim()) {
2436
- this.log(`Gateway env: ${key} present for child process`);
2437
- }
2438
- }
2439
- // Channel secrets preflight: an enabled channel referencing an unset env var makes the
2440
- // gateway crash at startup (SecretRefResolutionError) with the reason buried in the log.
2441
- // Catch it here and offer a fix instead of spawning a process that cannot survive.
2442
- if ((await this.preflightChannelSecrets(gatewayEnv)) === "abort") {
2443
- this.setStatus("installed");
2444
- return;
2445
- }
2446
- let startedPid;
2447
- if (isWindowsLaunch) {
2448
- startedPid = await this.startGatewayViaHiddenWindowsProcess(spawnExe, spawnArgs, logPath, errorLogPath, gatewayEnv);
2449
- this.process = null;
2450
- if (!startedPid) {
2451
- this.setStatus("error");
2452
- await showGatewayStartFailedToast(logPath);
2453
- return;
2454
- }
2455
- }
2456
- else {
2457
- const logFd = fs.openSync(logPath, "a");
2458
- this.process = (0, child_process_1.spawn)(spawnExe, spawnArgs, {
2459
- stdio: ["ignore", logFd, logFd],
2460
- detached: true, // Persist across IDE restarts
2461
- env: gatewayEnv,
2462
- shell: spawnShell,
2463
- windowsHide: true, // Prevent cmd.exe window appearing on Windows
2464
- });
2465
- // Close the log fd in this process -- the child owns it now
2466
- fs.closeSync(logFd);
2467
- // Detach: let the gateway keep running after the IDE exits.
2468
- // The IDE only manages the process lifecycle via explicit Start/Stop commands.
2469
- this.process.unref();
2470
- this.process.on("exit", (code) => {
2471
- this.log(`Gateway process exited with code ${code}`);
2472
- this.process = null;
2473
- // Clean up PID file
2474
- void removeGatewayPidFile();
2475
- // While start()/doStart readiness runs, that loop owns terminal status (Unix: clean exit
2476
- // before the port opens must surface as error, not "installed", vs this handler).
2477
- if (this._startInFlight) {
2478
- return;
2479
- }
2480
- this.setStatus(code !== 0 && code !== null ? "error" : "installed");
2481
- });
2482
- }
2483
- // Wait for gateway readiness in the window progress area. VS Code progress
2484
- // notifications only support a Cancel button, not an X-only close affordance.
2485
- await vscode.window.withProgress({
2486
- location: vscode.ProgressLocation.Window,
2487
- title: `${exports.AGENT_ANYWHERE_LABEL}: Starting gateway...`,
2488
- }, async (progress) => {
2489
- try {
2490
- const startTime = Date.now();
2491
- const startupTimeoutMs = this.getStartupTimeoutMs();
2492
- const launchGraceMs = this.getStartupLaunchGraceMs();
2493
- const checkInterval = this.getStartupCheckIntervalMs();
2494
- let elapsed = 0;
2495
- let lastPercent = 0;
2496
- let sawLaunchedProcessAlive = false;
2497
- let loggedLaunchedProcessExit = false;
2498
- const trackingPid = this.process?.pid ?? startedPid;
2499
- while (true) {
2500
- if (await this.probeGatewayPort(port)) {
2501
- this.setStatus("running");
2502
- this.log(`Gateway started and ready (${elapsed}ms)`);
2503
- // Write PID file for fast subsequent status checks
2504
- const pidForTracking = this.process?.pid ?? startedPid;
2505
- if (pidForTracking) {
2506
- await this.writeGatewayPidRecord(pidForTracking, port, binaryPath);
2507
- this.log(`PID file written: ${GATEWAY_PID_FILE}`);
2508
- }
2509
- this.startHealthChecks(port);
2510
- progress.report({ message: "Ready!", increment: 100 });
2511
- return;
2512
- }
2513
- const processAlive = this.isStartedGatewayProcessAlive(trackingPid);
2514
- if (trackingPid && processAlive) {
2515
- sawLaunchedProcessAlive = true;
2516
- }
2517
- elapsed = Date.now() - startTime;
2518
- const withinPrimaryTimeout = elapsed < startupTimeoutMs;
2519
- const withinLaunchGrace = trackingPid !== undefined &&
2520
- processAlive &&
2521
- sawLaunchedProcessAlive &&
2522
- elapsed < startupTimeoutMs + launchGraceMs;
2523
- // A gateway that crashes at boot (e.g. unresolvable secrets) can exit before
2524
- // the first aliveness sample, so don't require sawLaunchedProcessAlive: on
2525
- // Unix the exit handler nulling this.process is just as definitive. Without
2526
- // this, an instant crash silently spins until the startup timeout.
2527
- const launchedProcessGone = !processAlive && (sawLaunchedProcessAlive || this.process === null);
2528
- if (trackingPid && launchedProcessGone && !isWindowsLaunch) {
2529
- this.setStatus("error");
2530
- this.log(`Gateway process exited before opening port ${port}`);
2531
- await showGatewayStartFailedToast(logPath);
2532
- return;
2533
- }
2534
- if (trackingPid && sawLaunchedProcessAlive && !processAlive && isWindowsLaunch && !loggedLaunchedProcessExit) {
2535
- loggedLaunchedProcessExit = true;
2536
- this.log(`Gateway launch process exited before opening port ${port}; continuing to wait for port readiness`);
2537
- }
2538
- if (!withinPrimaryTimeout && !withinLaunchGrace) {
2539
- break;
2540
- }
2541
- const denominator = Math.max(startupTimeoutMs + (withinLaunchGrace ? launchGraceMs : 0), 1);
2542
- const pct = Math.min(90, Math.floor((elapsed / denominator) * 100));
2543
- const increment = Math.max(0, pct - lastPercent);
2544
- lastPercent = pct;
2545
- const progressMessage = trackingPid !== undefined
2546
- ? processAlive
2547
- ? `Gateway launched (PID ${trackingPid}), waiting for port ${port}...`
2548
- : `Waiting for gateway process to initialize...`
2549
- : `Waiting for port ${port}...`;
2550
- progress.report({ message: progressMessage, increment });
2551
- await this.sleep(checkInterval);
2552
- }
2553
- // Timeout
2554
- this.setStatus("error");
2555
- this.log(`Gateway failed to start within timeout (pid=${trackingPid ?? "n/a"}, alive=${trackingPid !== undefined ? this.isStartedGatewayProcessAlive(trackingPid) : false})`);
2556
- await showGatewayStartFailedToast(logPath);
2557
- }
2558
- catch {
2559
- this.setStatus("error");
2560
- this.log("Gateway failed to start");
2561
- await showGatewayStartFailedToast(logPath);
2562
- }
2563
- });
2564
- }
2565
- // ============================================================================
2566
- // Stop
2567
- // ============================================================================
2568
- async stop() {
2569
- this.stopHealthChecks();
2570
- // Clear bridge UI state immediately so we don't show green "Connected" while
2571
- // the gateway is being torn down (or if stop fails but the socket was stale).
2572
- await this.disconnectBridge();
2573
- if (this.process) {
2574
- // We started it -- graceful shutdown
2575
- this.setStatus("stopping");
2576
- this.log("Stopping gateway...");
2577
- const port = this.getGatewayPort();
2578
- const pid = this.process.pid;
2579
- // The gateway we spawn is a launcher whose CHILD process holds the listening socket.
2580
- // Capture the actual listener(s) now so we can confirm/reap them after the launcher exits.
2581
- const listenerPids = await getListeningProcessIdsForPort(port);
2582
- const observedPids = [
2583
- ...new Set([pid, ...listenerPids].filter((p) => typeof p === "number" && p > 0)),
2584
- ];
2585
- if (IS_WINDOWS && pid) {
2586
- // On Windows, SIGTERM maps to TerminateProcess which force-kills.
2587
- // Use taskkill /T to also kill child processes (node spawns).
2588
- await execAsync(`taskkill /F /T /PID ${pid}`, { timeout: 5_000 }).catch(() => { });
2589
- }
2590
- else {
2591
- // Signal the whole process group (we spawn detached, so the launcher is the group
2592
- // leader); its listening child shares the group and would otherwise be orphaned.
2593
- killGatewayProcessGroupOrSelf(this.process, pid, "SIGTERM");
2594
- }
2595
- const exited = await new Promise((resolve) => {
2596
- const timeout = setTimeout(() => resolve(false), 5_000);
2597
- this.process?.once("exit", () => {
2598
- clearTimeout(timeout);
2599
- resolve(true);
2600
- });
2601
- });
2602
- if (!exited && this.process) {
2603
- this.log("Forcefully killing gateway...");
2604
- if (IS_WINDOWS && pid) {
2605
- await execAsync(`taskkill /F /T /PID ${pid}`, { timeout: 5_000 }).catch(() => { });
2606
- }
2607
- else {
2608
- killGatewayProcessGroupOrSelf(this.process, pid, "SIGKILL");
2609
- }
2610
- }
2611
- this.process = null;
2612
- await removeGatewayPidFile();
2613
- // The launcher exiting does not guarantee its listening child died and released the port.
2614
- // Confirm the port is actually free; if not, reap the straggler by port (same mechanism as
2615
- // the external-stop path) so we never report "stopped" while the port is still held.
2616
- let freed = await waitForGatewayShutdown(port, observedPids);
2617
- if (!freed) {
2618
- this.log("Gateway port still held after process exit; reaping listener(s)...");
2619
- await killTcpListenersOnPort(port);
2620
- freed = await waitForGatewayShutdown(port, [
2621
- ...observedPids,
2622
- ...(await getListeningProcessIdsForPort(port)),
2623
- ]);
2624
- }
2625
- if (freed) {
2626
- this.setStatus("installed");
2627
- this.log("Gateway stopped");
2628
- }
2629
- else {
2630
- this.setStatus("running");
2631
- this.log("Could not free gateway port after stop");
2632
- vscode.window.showWarningMessage(`${exports.AGENT_ANYWHERE_LABEL}: Could not fully stop the gateway. Try stopping it manually.`);
2633
- }
2634
- return;
2635
- }
2636
- // We didn't start it -- check if something else is running
2637
- const detection = await this.detect();
2638
- if (!detection.running) {
2639
- this.log("Gateway is not running");
2640
- return;
2641
- }
2642
- // Kill external gateway process (CLI stop → pkill/CIM → port-based kill)
2643
- this.setStatus("stopping");
2644
- this.log("Stopping externally started gateway...");
2645
- const stopped = await stopExternalGatewayListener(detection.port, detection.binaryPath, (m) => this.log(m));
2646
- await removeGatewayPidFile();
2647
- if (stopped) {
2648
- this.setStatus("installed");
2649
- this.log("Gateway stopped");
2650
- }
2651
- else {
2652
- this.setStatus("running");
2653
- this.log("Could not stop gateway process");
2654
- vscode.window.showWarningMessage(`${exports.AGENT_ANYWHERE_LABEL}: Could not stop the gateway. Try stopping it manually.`);
2655
- }
2656
- }
2657
- async restartGateway() {
2658
- const wasConnected = this._bridgeRef?.isConnected ?? false;
2659
- await this.disconnectBridge();
2660
- let shouldStop = this._status === "running";
2661
- if (!shouldStop) {
2662
- // Lightweight runtime check to avoid full detect() latency.
2663
- shouldStop = await (0, index_1.probePort)(this.getGatewayPort(), "127.0.0.1", 250);
2664
- }
2665
- if (shouldStop) {
2666
- await this.stop();
2667
- }
2668
- else {
2669
- this.log("Restart requested while gateway is stopped; skipping stop.");
2670
- }
2671
- await this.start();
2672
- if (wasConnected && this._status === "running") {
2673
- await this.connectBridge();
2674
- }
2675
- }
2676
- // ============================================================================
2677
- // Status
2678
- // ============================================================================
2679
- async showStatus() {
2680
- this.setBridgeConnected(this._bridgeRef?.isConnected ?? false);
2681
- const port = this.getGatewayPort();
2682
- const detection = this._lastDetection ?? await this.detect();
2683
- // Build status summary
2684
- // allow-any-unicode-next-line
2685
- const gwStatus = detection.running ? "🟢 Running" : detection.installed ? "⚫ Stopped" : "⬜ Not installed";
2686
- // allow-any-unicode-next-line
2687
- const bridgeStatus = this._bridgeConnected ? "🟢 Connected" : "⚫ Disconnected";
2688
- const version = detection.version ? ` (${detection.version})` : "";
2689
- const message = [
2690
- `Gateway: ${gwStatus}${version}`,
2691
- `Bridge: ${bridgeStatus}`,
2692
- `Port: ${port}`,
2693
- ].join("\n");
2694
- if (!detection.installed) {
2695
- const choice = await vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}\n${message}`, "Set Up");
2696
- if (choice === "Set Up") {
2697
- await this.install();
2698
- }
2699
- return;
2700
- }
2701
- if (!detection.running) {
2702
- const choice = await vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}\n${message}`, "Connect", "Config", "Uninstall");
2703
- switch (choice) {
2704
- case "Connect":
2705
- await this.start();
2706
- if (this._status === "running") {
2707
- await this.connectBridge();
2708
- }
2709
- break;
2710
- case "Config":
2711
- await this.openConfig();
2712
- break;
2713
- case "Uninstall":
2714
- await this.uninstall();
2715
- break;
2716
- }
2717
- return;
2718
- }
2719
- // Running -- show status details with maintenance actions
2720
- const choice = await vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}\n${message}`, "Web UI", "Config", "Restart", "Reset auth", "Uninstall");
2721
- switch (choice) {
2722
- case "Web UI":
2723
- await this.openWebUI();
2724
- break;
2725
- case "Config":
2726
- await this.openConfig();
2727
- break;
2728
- case "Restart":
2729
- await this.restartGateway();
2730
- break;
2731
- case "Reset auth":
2732
- await this.resetAuth();
2733
- break;
2734
- case "Uninstall":
2735
- await this.uninstall();
2736
- break;
2737
- }
2738
- }
2739
- async resetAuth() {
2740
- try {
2741
- if (!fs.existsSync(this.configPath)) {
2742
- this.writeStarterConfig();
2743
- }
2744
- const raw = fs.readFileSync(this.configPath, "utf-8");
2745
- const stripped = raw.replace(/^\s*\/\/.*$/gm, "").replace(/,\s*([}\]])/g, "$1");
2746
- const parsed = JSON.parse(stripped);
2747
- if (!parsed.gateway || typeof parsed.gateway !== "object") {
2748
- parsed.gateway = {};
2749
- }
2750
- if (!parsed.gateway.auth || typeof parsed.gateway.auth !== "object") {
2751
- parsed.gateway.auth = {};
2752
- }
2753
- const newToken = `ah-${(0, crypto_1.randomBytes)(24).toString("hex")}`;
2754
- parsed.gateway.auth.token = newToken;
2755
- fs.writeFileSync(this.configPath, `${JSON.stringify(parsed, null, 2)}\n`, {
2756
- encoding: "utf-8",
2757
- mode: IS_WINDOWS ? undefined : 0o600,
2758
- });
2759
- // Application-scoped setting: user scope is the only writable target.
2760
- const gwConfig = vscode.workspace.getConfiguration("agentide.gateway");
2761
- await gwConfig.update("token", newToken, vscode.ConfigurationTarget.Global);
2762
- await this.restartGateway();
2763
- void vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}: Auth reset; gateway restarted. If you use the Web UI, paste the new token in Control UI settings.`);
2764
- }
2765
- catch (err) {
2766
- this.log(`Failed to reset auth: ${err instanceof Error ? err.message : String(err)}`);
2767
- vscode.window.showErrorMessage(`${exports.AGENT_ANYWHERE_LABEL}: Failed to reset auth. Open Config to update it manually.`);
2768
- }
2769
- }
2770
- // ============================================================================
2771
- // Bridge Connect/Disconnect
2772
- // ============================================================================
2773
- /**
2774
- * Connect the bridge to the running gateway.
2775
- * Ensures the gateway.url setting is set, then delegates to GatewayService.connect().
2776
- */
2777
- async connectBridge() {
2778
- if (!this._bridgeRef) {
2779
- this.log("Bridge ref not set -- cannot connect");
2780
- return;
2781
- }
2782
- if (this._gatewayUninstallInProgress) {
2783
- await this.showGatewayUninstallInProgressPrompt();
2784
- return;
2785
- }
2786
- if (this._bridgeRef.isConnected) {
2787
- this.log("Bridge already connected");
2788
- return;
2789
- }
2790
- // Fast check: if we already know the gateway is running, skip full detection.
2791
- // Full detect() shells out to `openclaw --version` which adds 1-3s latency.
2792
- const port = this.getGatewayPort();
2793
- if (await (0, index_1.probePort)(port)) {
2794
- const trustedListener = await isTrustedGatewayListener(port, this._lastDetection?.binaryPath);
2795
- if (!trustedListener) {
2796
- const approved = await this.promptNonManagedPortTakeover(port, "connect");
2797
- if (!approved || (await (0, index_1.probePort)(port))) {
2798
- if (this._status === "running") {
2799
- this.setStatus("installed");
2800
- }
2801
- return;
2802
- }
2803
- }
2804
- }
2805
- let isRunning = this._status === "running";
2806
- if (!isRunning) {
2807
- // Try fast PID-file check first (~1-2ms)
2808
- const fast = await detectGatewayFast(port);
2809
- if (fast?.running) {
2810
- isRunning = true;
2811
- this._lastDetection = fast;
2812
- this.setStatus("running");
2813
- }
2814
- }
2815
- if (!isRunning) {
2816
- // Slow path: full detection only when necessary
2817
- const detection = await this.detect();
2818
- if (!detection.running) {
2819
- if (!detection.installed) {
2820
- const choice = await vscode.window.showInformationMessage(`${exports.AGENT_ANYWHERE_LABEL}: Reach your AgentHippo IDE or CLI agents from WhatsApp, Telegram, Slack, Discord, and more - right from your phone or any messaging app. This will also install OpenClaw fork on your computer.`, "Set Up Agent Anywhere");
2821
- if (choice === "Set Up Agent Anywhere") {
2822
- await this.install();
2823
- }
2824
- return;
2825
- }
2826
- await this.start();
2827
- // Re-detect after start
2828
- const afterStart = await this.detect();
2829
- if (!afterStart.running) {
2830
- this.log("Gateway failed to start, cannot connect");
2831
- await showGatewayStartFailedToast(DEFAULT_GATEWAY_LOG_PATH);
2832
- return;
2833
- }
2834
- }
2835
- }
2836
- // Ensure gateway.url is set
2837
- const gwConfig = vscode.workspace.getConfiguration("agentide.gateway");
2838
- let wsUrl = gwConfig.get("url", "").trim();
2839
- if (!wsUrl) {
2840
- wsUrl = `ws://127.0.0.1:${port}`;
2841
- await gwConfig.update("url", wsUrl, vscode.ConfigurationTarget.Global);
2842
- }
2843
- // For local gateways, apply gateway.auth.token from ~/.openclaw/openclaw.json.
2844
- // The setting is application-scoped (a credential must never land in a committable
2845
- // .vscode/settings.json), and VS Code rejects any non-user write for that scope.
2846
- // Remote URLs: do not override — token must come from settings / env for that host.
2847
- const localGateway = /127\.0\.0\.1|localhost|\[::1\]/i.test(wsUrl);
2848
- const fileToken = this.readGatewayToken();
2849
- if (fileToken && localGateway) {
2850
- await gwConfig.update("token", fileToken, vscode.ConfigurationTarget.Global);
2851
- this.log("Gateway token applied from openclaw.json (user scope)");
2852
- }
2853
- await this._bridgeRef.connect();
2854
- this.setBridgeConnected(this._bridgeRef.isConnected);
2855
- // Ensure the visual state matches the actual gateway port state.
2856
- // This prevents a race where the bridge connects but `_status` is still "installed".
2857
- const alive = await (0, index_1.probePort)(port);
2858
- if (alive) {
2859
- if (this._status !== "running") {
2860
- this.setStatus("running");
2861
- }
2862
- }
2863
- else {
2864
- if (this._status !== "installed") {
2865
- this.setStatus("installed");
2866
- }
2867
- }
2868
- this.updateStatusBar(this._status);
2869
- this.log("Bridge connected");
2870
- }
2871
- /**
2872
- * Disconnect the bridge from the gateway.
2873
- */
2874
- async disconnectBridge() {
2875
- if (!this._bridgeRef) {
2876
- return;
2877
- }
2878
- await this._bridgeRef.disconnect();
2879
- this.setBridgeConnected(false);
2880
- this.updateStatusBar(this._status);
2881
- this.log("Bridge disconnected");
2882
- }
2883
- // ============================================================================
2884
- // Web UI
2885
- // ============================================================================
2886
- /**
2887
- * Open the OpenClaw Gateway web UI in the default browser.
2888
- * The web UI is served by the gateway on the same port (default: 18789).
2889
- * Use it to configure channels, monitor sessions, and manage agents.
2890
- */
2891
- async openWebUI() {
2892
- const port = this.getGatewayPort();
2893
- // Quick port probe (~10ms) instead of full detect (~1-3s)
2894
- const running = this._status === "running" || await (0, index_1.probePort)(port);
2895
- if (!running) {
2896
- const choice = await vscode.window.showWarningMessage(`${exports.AGENT_ANYWHERE_LABEL}: Gateway is not running. Start it first to use the web UI.`, "Start Now");
2897
- if (choice === "Start Now") {
2898
- await this.start();
2899
- }
2900
- return;
2901
- }
2902
- // Pass token in the URL fragment so the Control UI can import it (applySettingsFromUrl).
2903
- // Query ?token= is intentionally stripped by OpenClaw without applying (referrer/logs).
2904
- const tokenFromSettings = (vscode.workspace.getConfiguration("agentide.gateway").get("token") ?? "").trim();
2905
- const token = this.readGatewayToken() ?? (tokenFromSettings || undefined);
2906
- const hash = token ? `#token=${encodeURIComponent(token)}` : "";
2907
- const url = `http://127.0.0.1:${port}/${hash}`;
2908
- await vscode.env.openExternal(vscode.Uri.parse(url));
2909
- }
2910
- // ============================================================================
2911
- // Health Checks
2912
- // ============================================================================
2913
- startHealthChecks(port) {
2914
- this.stopHealthChecks();
2915
- this.healthCheckTimer = setInterval(async () => {
2916
- const alive = await (0, index_1.probePort)(port);
2917
- if (!alive) {
2918
- this.log("Health check failed -- gateway is no longer running");
2919
- this.stopHealthChecks();
2920
- this.process = null;
2921
- this.setStatus("installed");
2922
- }
2923
- }, HEALTH_CHECK_INTERVAL_MS);
2924
- }
2925
- stopHealthChecks() {
2926
- if (this.healthCheckTimer) {
2927
- clearInterval(this.healthCheckTimer);
2928
- this.healthCheckTimer = null;
2929
- }
2930
- }
2931
- // ============================================================================
2932
- // Helpers
2933
- // ============================================================================
2934
- /**
2935
- * Build the web Control UI if it's missing.
2936
- * Resolves the gateway package root from the binary path and runs `pnpm ui:build`.
2937
- * Best-effort: logs failures but doesn't block the install flow.
2938
- */
2939
- async ensureWebUIBuilt(binaryPath) {
2940
- if (!binaryPath) {
2941
- return;
2942
- }
2943
- try {
2944
- const pkgRoot = resolvePackageRootFromBinary(binaryPath);
2945
- if (!pkgRoot) {
2946
- return;
2947
- }
2948
- const uiIndex = path.join(pkgRoot, "dist", "control-ui", "index.html");
2949
- if (fs.existsSync(uiIndex)) {
2950
- return; // Already built
2951
- }
2952
- this.log("Web UI not built - building now...");
2953
- // On Windows, npm/pnpm install as .cmd wrappers that need a shell to execute.
2954
- const uiBuildCmd = IS_WINDOWS ? "pnpm.cmd ui:build" : "pnpm ui:build";
2955
- await execAsync(uiBuildCmd, {
2956
- cwd: pkgRoot,
2957
- timeout: 60_000,
2958
- env: { ...process.env },
2959
- ...(IS_WINDOWS ? { shell: "cmd.exe" } : {}),
2960
- });
2961
- this.log("Web UI built successfully");
2962
- }
2963
- catch (err) {
2964
- this.log(`Web UI build failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
2965
- }
2966
- }
2967
- async promptNonManagedPortTakeover(port, intent) {
2968
- const action = intent === "connect" ? "connect Agent Anywhere" : "start Agent Anywhere";
2969
- const choice = await vscode.window.showWarningMessage(`${exports.AGENT_ANYWHERE_LABEL}: Port ${port} is in use by a non-managed process. Stop that process and ${action}?`, { modal: true }, "Yes", "No");
2970
- if (choice !== "Yes") {
2971
- this.log(`User declined non-managed port takeover on ${port}`);
2972
- return false;
2973
- }
2974
- this.log(`User approved non-managed port takeover on ${port}`);
2975
- const priorPids = new Set(await getListeningProcessIdsForPort(port));
2976
- const stopped = await stopExternalGatewayListener(port, undefined, (m) => this.log(m));
2977
- if (!stopped) {
2978
- // A supervised daemon (launchd/systemd/scheduled task) restarts itself when killed, so a
2979
- // fresh PID re-takes the port. Detect that and tell the user the truth — killing the
2980
- // process can't win against the supervisor's auto-restart; the service must be uninstalled.
2981
- const currentPids = await getListeningProcessIdsForPort(port);
2982
- const supervisedRespawn = priorPids.size > 0 && currentPids.some((pid) => !priorPids.has(pid));
2983
- if (supervisedRespawn) {
2984
- vscode.window.showWarningMessage(`${exports.AGENT_ANYWHERE_LABEL}: Port ${port} is held by a self-restarting gateway service. Run \`openclaw daemon uninstall\` to remove it, then try again.`);
2985
- }
2986
- else {
2987
- vscode.window.showErrorMessage(`${exports.AGENT_ANYWHERE_LABEL}: Could not free port ${port}. Stop that process manually and try again.`);
2988
- }
2989
- return false;
2990
- }
2991
- return true;
2992
- }
2993
- async ensureManagedGatewayWrapperState(binaryPath, createOpenclawAlias = false) {
2994
- try {
2995
- const record = readManagedGatewayInstallRecord();
2996
- const runtimeSpec = await resolveManagedGatewayRuntimeSpec(binaryPath, record);
2997
- if (!runtimeSpec) {
2998
- return undefined;
2999
- }
3000
- const { launcherPath, packageRoot, nodePath, entryScriptPath } = runtimeSpec;
3001
- const wrapperPath = writeManagedGatewayWrapper(nodePath, entryScriptPath, MANAGED_GATEWAY_WRAPPER_PATH);
3002
- let openclawWrapperPath;
3003
- if (createOpenclawAlias ||
3004
- isAgentAnywhereManagedWrapper(MANAGED_OPENCLAW_WRAPPER_PATH)) {
3005
- openclawWrapperPath = writeManagedGatewayWrapper(nodePath, entryScriptPath, MANAGED_OPENCLAW_WRAPPER_PATH);
3006
- }
3007
- writeManagedGatewayInstallRecord({
3008
- ...record,
3009
- nodePath,
3010
- cliPath: launcherPath,
3011
- entryScriptPath,
3012
- packageRoot,
3013
- wrapperPath,
3014
- wrapperDir: MANAGED_GATEWAY_WRAPPER_DIR,
3015
- openclawWrapperPath,
3016
- updatedAt: new Date().toISOString(),
3017
- });
3018
- return wrapperPath;
3019
- }
3020
- catch (err) {
3021
- this.log(`Managed gateway wrapper setup failed: ${err instanceof Error ? err.message : String(err)}`);
3022
- return undefined;
3023
- }
3024
- }
3025
- async resolveWindowsManagedGatewayLaunchRuntime(binaryPath) {
3026
- if (!IS_WINDOWS) {
3027
- return undefined;
3028
- }
3029
- // Best effort: backfill manifest + wrapper state from an existing managed package install.
3030
- await this.ensureManagedGatewayWrapperState(binaryPath);
3031
- const runtimeSpec = await resolveManagedGatewayRuntimeSpec(binaryPath);
3032
- if (!runtimeSpec) {
3033
- return undefined;
3034
- }
3035
- return {
3036
- nodePath: runtimeSpec.nodePath,
3037
- entryScriptPath: runtimeSpec.entryScriptPath,
3038
- };
3039
- }
3040
- /**
3041
- * Read the gateway auth token from ~/.openclaw/openclaw.json.
3042
- * Parses JSON5 loosely (strip comments, then JSON.parse) to avoid a dep.
3043
- */
3044
- readGatewayToken() {
3045
- try {
3046
- if (!fs.existsSync(this.configPath)) {
3047
- return undefined;
3048
- }
3049
- const raw = fs.readFileSync(this.configPath, "utf-8");
3050
- // Strip single-line comments (// ...) for basic JSON5 compat
3051
- const stripped = raw.replace(/^\s*\/\/.*$/gm, "").replace(/,\s*([}\]])/g, "$1");
3052
- const parsed = JSON.parse(stripped);
3053
- const token = parsed?.gateway?.auth?.token;
3054
- return typeof token === "string" && token.trim() ? token.trim() : undefined;
3055
- }
3056
- catch (err) {
3057
- this.log(`Could not read gateway token: ${err}`);
3058
- return undefined;
3059
- }
3060
- }
3061
- setStatus(status) {
3062
- this._status = status;
3063
- this.updateStatusBar(status);
3064
- this._onStatusChange.fire(status);
3065
- }
3066
- updateStatusBar(status) {
3067
- const hidden = this.getConfig("hidden", false);
3068
- if (hidden) {
3069
- this.statusBarItem.hide();
3070
- return;
3071
- }
3072
- switch (status) {
3073
- case "not-installed":
3074
- this.statusBarItem.text = `$(cloud-download) ${exports.AGENT_ANYWHERE_LABEL}`;
3075
- this.statusBarItem.tooltip = `${exports.AGENT_ANYWHERE_LABEL}: Reach your agents from WhatsApp, Telegram, Slack (click to set up)`;
3076
- this.statusBarItem.color = undefined;
3077
- this.statusBarItem.backgroundColor = undefined;
3078
- break;
3079
- case "installed":
3080
- this.statusBarItem.text = `$(debug-disconnect) ${exports.AGENT_ANYWHERE_LABEL}`;
3081
- this.statusBarItem.tooltip = `${exports.AGENT_ANYWHERE_LABEL}: Installed, not running (click to start)`;
3082
- this.statusBarItem.color = undefined;
3083
- this.statusBarItem.backgroundColor = undefined;
3084
- break;
3085
- case "starting":
3086
- this.statusBarItem.text = `$(sync~spin) ${exports.AGENT_ANYWHERE_LABEL}`;
3087
- this.statusBarItem.tooltip = this._gatewayInstallInProgress
3088
- ? `${exports.AGENT_ANYWHERE_LABEL}: ${this._installProgressMessage ?? "Installing... (see Setup terminal)"}`
3089
- : `${exports.AGENT_ANYWHERE_LABEL}: Starting...`;
3090
- this.statusBarItem.color = undefined;
3091
- this.statusBarItem.backgroundColor = undefined;
3092
- break;
3093
- case "running":
3094
- if (this._bridgeConnected) {
3095
- this.statusBarItem.text = `$(broadcast) ${exports.AGENT_ANYWHERE_LABEL}`;
3096
- this.statusBarItem.tooltip = `${exports.AGENT_ANYWHERE_LABEL}: Connected (click for options)`;
3097
- this.statusBarItem.color = new vscode.ThemeColor("testing.iconPassed");
3098
- }
3099
- else {
3100
- this.statusBarItem.text = `$(debug-disconnect) ${exports.AGENT_ANYWHERE_LABEL}`;
3101
- this.statusBarItem.tooltip = `${exports.AGENT_ANYWHERE_LABEL}: Disconnected (click to connect)`;
3102
- this.statusBarItem.color = undefined;
3103
- }
3104
- this.statusBarItem.backgroundColor = undefined;
3105
- break;
3106
- case "stopping":
3107
- this.statusBarItem.text = `$(sync~spin) ${exports.AGENT_ANYWHERE_LABEL}`;
3108
- this.statusBarItem.tooltip = this._gatewayUninstallInProgress
3109
- ? `${exports.AGENT_ANYWHERE_LABEL}: Uninstalling... (see Uninstall terminal)`
3110
- : `${exports.AGENT_ANYWHERE_LABEL}: Stopping...`;
3111
- this.statusBarItem.color = undefined;
3112
- this.statusBarItem.backgroundColor = undefined;
3113
- break;
3114
- case "error":
3115
- this.statusBarItem.text = `$(warning) ${exports.AGENT_ANYWHERE_LABEL}`;
3116
- this.statusBarItem.tooltip = `${exports.AGENT_ANYWHERE_LABEL}: Error (click for details)`;
3117
- this.statusBarItem.color = new vscode.ThemeColor("errorForeground");
3118
- this.statusBarItem.backgroundColor = undefined;
3119
- break;
3120
- }
3121
- this.statusBarItem.show();
3122
- }
3123
- getConfig(key, defaultValue) {
3124
- return vscode.workspace
3125
- .getConfiguration("agentide.anywhere")
3126
- .get(key, defaultValue);
3127
- }
3128
- /**
3129
- * True when the global npm tree still has `openclaw` installed.
3130
- * This is used by uninstall flow to distinguish managed npm installs
3131
- * from unrelated/external binaries still present on PATH.
3132
- */
3133
- async hasGlobalNpmOpenclawTree() {
3134
- const managedRoots = await getManagedOpenclawRoots();
3135
- return managedRoots.length > 0;
3136
- }
3137
- readGatewayPortFromOpenclawJson() {
3138
- try {
3139
- if (!fs.existsSync(this.configPath)) {
3140
- return undefined;
3141
- }
3142
- const raw = fs.readFileSync(this.configPath, "utf-8");
3143
- const stripped = raw.replace(/^\s*\/\/.*$/gm, "").replace(/,\s*([}\]])/g, "$1");
3144
- const parsed = JSON.parse(stripped);
3145
- const p = parsed?.gateway?.port;
3146
- if (typeof p === "number" && Number.isFinite(p)) {
3147
- const n = Math.floor(p);
3148
- if (n >= 1 && n <= 65535) {
3149
- return n;
3150
- }
3151
- }
3152
- if (typeof p === "string" && /^\d+$/.test(p)) {
3153
- const n = parseInt(p, 10);
3154
- if (n >= 1 && n <= 65535) {
3155
- return n;
3156
- }
3157
- }
3158
- return undefined;
3159
- }
3160
- catch {
3161
- return undefined;
3162
- }
3163
- }
3164
- /**
3165
- * Port used for local gateway probe/start/stop/Web UI. When openclaw.json sets gateway.port,
3166
- * that wins so the IDE matches the running OpenClaw process (settings default alone can miss).
3167
- */
3168
- getGatewayPort() {
3169
- const fromFile = this.readGatewayPortFromOpenclawJson();
3170
- if (fromFile !== undefined) {
3171
- return fromFile;
3172
- }
3173
- return this.getConfig("port", DEFAULT_PORT);
3174
- }
3175
- log(message) {
3176
- this.outputChannel.appendLine(`[${exports.AGENT_ANYWHERE_LABEL}] ${message}`);
3177
- }
3178
- dispose() {
3179
- this.stopHealthChecks();
3180
- // Don't kill the gateway on IDE close -- it's detached and persists.
3181
- // Users stop it explicitly via the Stop command.
3182
- this.process = null;
3183
- this.statusBarItem.dispose();
3184
- this._onStatusChange.dispose();
3185
- for (const d of this.disposables) {
3186
- d.dispose();
3187
- }
3188
- }
3189
- }
3190
- exports.GatewayLifecycleManager = GatewayLifecycleManager;