@agenthippo-ai/agenthippo 0.2.3-dataplane → 1.110.11

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 (139) hide show
  1. package/out/agentpack/index.js +3 -1
  2. package/out/agentpack/paths.js +25 -2
  3. package/out/analytics/dashboard/localFleetAdapter.js +82 -2
  4. package/out/analytics/dashboard/provider.js +13 -2
  5. package/out/analytics/dashboard/webview/panel.js +3 -2
  6. package/out/analytics/identity.js +0 -0
  7. package/out/analytics/service.js +29 -25
  8. package/out/analytics/sessionEvents.js +100 -24
  9. package/out/auth/authBaseUrl.js +59 -0
  10. package/out/auth/bootstrapApply.js +9 -10
  11. package/out/auth/brokerClient.js +48 -14
  12. package/out/auth/provider.js +8 -21
  13. package/out/auth/sessionStore.js +3 -40
  14. package/out/auth/signInFlow.js +2 -1
  15. package/out/auth-broker/config.js +50 -5
  16. package/out/auth-broker/server.js +126 -26
  17. package/out/auth-broker/store.js +25 -24
  18. package/out/authority/warrant.js +206 -0
  19. package/out/branding.js +27 -1
  20. package/out/cli/commands/conformance.js +249 -0
  21. package/out/cli/commands/data-plane.js +151 -0
  22. package/out/cli/commands/deploy.js +1 -0
  23. package/out/cli/commands/gateway.js +4 -0
  24. package/out/cli/commands/pack.js +182 -3
  25. package/out/cli/commands/sandbox.js +351 -0
  26. package/out/cli/commands/serve.js +1145 -130
  27. package/out/cli/commands/store.js +12 -4
  28. package/out/cli/credential-context.js +17 -0
  29. package/out/cli/data-plane/gateway.js +28 -5
  30. package/out/cli/data-plane/index.js +18 -2
  31. package/out/cli/data-plane/registry.js +9 -0
  32. package/out/cli/data-plane/remote.js +106 -0
  33. package/out/cli/data-plane/sidecar.js +225 -0
  34. package/out/cli/deploy/layers/identity.js +88 -4
  35. package/out/cli/deploy-init.js +89 -24
  36. package/out/cli/deps.js +106 -2
  37. package/out/cli/idp-init.js +35 -0
  38. package/out/cli/index.js +171 -17
  39. package/out/cli/pack-install-ref.js +15 -0
  40. package/out/cli/pack-loader.js +8 -1
  41. package/out/cli/pack-runtime.js +61 -0
  42. package/out/cli/runtime.js +35 -1
  43. package/out/cli/serve-hardening.js +260 -14
  44. package/out/cli/utils.js +12 -1
  45. package/out/engines/base.js +32 -3
  46. package/out/engines/claude.js +33 -15
  47. package/out/engines/codex.js +111 -27
  48. package/out/engines/custom.js +15 -1
  49. package/out/evidence/sessionShip.js +643 -0
  50. package/out/extension.js +11 -2
  51. package/out/gateway/bridge.js +40 -2
  52. package/out/gateway/client.js +34 -1
  53. package/out/gateway/installSource.js +68 -0
  54. package/out/gateway/lifecycle.js +185 -51
  55. package/out/gateway/outboundPaths.js +4 -1
  56. package/out/gateway/secretsPreflight.js +90 -0
  57. package/out/gateway/service.js +7 -2
  58. package/out/headless/index.js +25 -2
  59. package/out/live-e2e/mcp-demo/server.mjs +32 -0
  60. package/out/mcp.js +30 -0
  61. package/out/pack/caps.js +271 -0
  62. package/out/pack/governor.js +326 -0
  63. package/out/pack/lease-schema.js +273 -0
  64. package/out/pack/lease.js +682 -0
  65. package/out/pack/reconcile.js +217 -0
  66. package/out/pack/signature.js +839 -0
  67. package/out/remote/handleRemoteChat.js +44 -3
  68. package/out/remote/remoteTrust.js +277 -0
  69. package/out/remote/serveClient.js +52 -5
  70. package/out/runner/host.js +141 -0
  71. package/out/runner/protocol.js +128 -0
  72. package/out/runner/remoteEngine.js +166 -0
  73. package/out/runtime/interfaces/edit-tracker.js +2 -0
  74. package/out/runtime/vscode/vscode-edit-tracker.js +2 -0
  75. package/out/sandbox/containerDriver.js +572 -0
  76. package/out/sandbox/processDriver.js +318 -0
  77. package/out/sandbox/sandboxRunner.js +303 -0
  78. package/out/sandbox/support.js +301 -0
  79. package/out/sandbox/types.js +55 -0
  80. package/out/scripts/agentide-litellm.sh +67 -5
  81. package/out/scripts/agentide_litellm_hooks_v1.py +266 -21
  82. package/out/scripts/analytics/agentide-litellm-otel.config.yaml +98 -132
  83. package/out/scripts/analytics/setup-analytics.ps1 +45 -14
  84. package/out/scripts/analytics/setup-analytics.sh +106 -15
  85. package/out/scripts/analytics/start-analytics.sh +86 -14
  86. package/out/scripts/gateway/setup-gateway.sh +72 -32
  87. package/out/shared/models.js +11 -110
  88. package/out/spotlight/install.js +7 -1
  89. package/out/store/index.js +41 -10
  90. package/out/sync.js +3 -1
  91. package/out/test/analytics.test.js +162 -23
  92. package/out/test/auth-baseurl.test.js +132 -0
  93. package/out/test/auth-bootstrapApply.test.js +27 -6
  94. package/out/test/auth-broker-hardening.test.js +227 -0
  95. package/out/test/auth-broker.test.js +59 -9
  96. package/out/test/branding.test.js +13 -0
  97. package/out/test/caps-governor.test.js +460 -0
  98. package/out/test/codex-new-file-survival.test.js +119 -0
  99. package/out/test/configuration-scopes.test.js +131 -0
  100. package/out/test/data-plane-engine-wiring.test.js +32 -0
  101. package/out/test/data-plane.test.js +380 -0
  102. package/out/test/deploy-init.test.js +47 -1
  103. package/out/test/gateway-client.test.js +28 -0
  104. package/out/test/gateway-install-source.test.js +111 -0
  105. package/out/test/gateway-lifecycle.test.js +1 -1
  106. package/out/test/gateway-outbound.test.js +7 -0
  107. package/out/test/gateway-secrets-preflight.test.js +127 -0
  108. package/out/test/gateway-session-routing.test.js +18 -0
  109. package/out/test/governor-hardening.test.js +222 -0
  110. package/out/test/headless-sync.test.js +22 -0
  111. package/out/test/lease-signature.test.js +357 -0
  112. package/out/test/lease-trust-root.test.js +273 -0
  113. package/out/test/lease-validation.test.js +224 -0
  114. package/out/test/lease.test.js +114 -0
  115. package/out/test/localFleetAdapter.test.js +91 -0
  116. package/out/test/mcp.test.js +27 -1
  117. package/out/test/pack-admission.test.js +216 -0
  118. package/out/test/pack-digest-parity.test.js +140 -0
  119. package/out/test/pack-reconcile.test.js +183 -0
  120. package/out/test/pack-signature-artifact.test.js +103 -0
  121. package/out/test/pack-signature.test.js +176 -0
  122. package/out/test/pack-trust-root.test.js +202 -0
  123. package/out/test/remote-trust.test.js +242 -0
  124. package/out/test/runner-protocol.test.js +249 -0
  125. package/out/test/sandbox-driver.test.js +553 -0
  126. package/out/test/sandbox-hardening.test.js +406 -0
  127. package/out/test/sandbox-runner.test.js +343 -0
  128. package/out/test/sandbox-scope.test.js +449 -0
  129. package/out/test/serve-cron-acceptance.test.js +370 -0
  130. package/out/test/serve-gateway-turn.test.js +122 -0
  131. package/out/test/serve-hardening.test.js +227 -15
  132. package/out/test/serveClient.test.js +6 -5
  133. package/out/test/sessionShip.test.js +618 -0
  134. package/out/test/spotlightPublish.test.js +28 -0
  135. package/out/test/store-install-safe.test.js +69 -0
  136. package/out/test/tls-bypass.test.js +90 -0
  137. package/out/test/warrant.test.js +138 -0
  138. package/out/utils.js +46 -9
  139. package/package.json +1 -1
@@ -282,6 +282,37 @@ function Resolve-LiteLLMHooks {
282
282
  return $null
283
283
  }
284
284
 
285
+ # Ensure the proxy has an auth key without ever asking the user for one.
286
+ # Mirrors ensure_litellm_master_key in setup-analytics.sh — the launcher refuses to start
287
+ # unauthenticated, so a first run with no key would leave LiteLLM dead with the error in a log.
288
+ function Ensure-LiteLLMMasterKey {
289
+ $envFile = Join-Path $AgentHomeDir ".env"
290
+
291
+ if ((Test-Path $envFile) -and (Select-String -Path $envFile -Pattern '^\s*LITELLM_MASTER_KEY=' -Quiet)) {
292
+ Log-Success "LiteLLM master key already configured"
293
+ return
294
+ }
295
+ if ($env:LITELLM_MASTER_KEY) {
296
+ Log-Success "Using LITELLM_MASTER_KEY from the environment"
297
+ return
298
+ }
299
+
300
+ $bytes = New-Object byte[] 24
301
+ [System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
302
+ $key = "sk-" + (($bytes | ForEach-Object { $_.ToString("x2") }) -join "")
303
+
304
+ New-Item -ItemType Directory -Path $AgentHomeDir -Force | Out-Null
305
+ $lines = @(
306
+ "# Generated by setup-analytics - authenticates the local LiteLLM proxy.",
307
+ "# Clients read the same value as AGENTHIPPO_LITELLM_API_KEY.",
308
+ "LITELLM_MASTER_KEY=$key",
309
+ "AGENTHIPPO_LITELLM_API_KEY=$key"
310
+ )
311
+ Add-Content -Path $envFile -Value $lines
312
+ $env:LITELLM_MASTER_KEY = $key
313
+ Log-Success "Generated a LiteLLM master key (stored in $envFile)"
314
+ }
315
+
285
316
  function Setup-Config {
286
317
  Log-Step "Setting up configuration..."
287
318
 
@@ -289,6 +320,9 @@ function Setup-Config {
289
320
  New-Item -ItemType Directory -Path (Join-Path $AnalyticsDir "config") -Force | Out-Null
290
321
  New-Item -ItemType Directory -Path (Join-Path $AnalyticsDir "traces") -Force | Out-Null
291
322
 
323
+ # Before any early return below - the launcher exits non-zero without a key.
324
+ Ensure-LiteLLMMasterKey
325
+
292
326
  # Copy OTEL config
293
327
  $configDest = Join-Path $AnalyticsDir "config\otel-collector.yaml"
294
328
  $configSrc = Join-Path $ScriptDir "otel-collector.yaml"
@@ -325,24 +359,21 @@ function Setup-Config {
325
359
 
326
360
  if (-not (Test-Path $litellmDest)) {
327
361
  Copy-Item $litellmSrc $litellmDest
328
- Log-Success "Created LiteLLM OTEL config from template"
362
+ # IDE/local lane: Spotlight needs prompt content, so message logging defaults ON here.
363
+ # The flip happens at copy time, not in the template, because Dockerfile.litellm bakes
364
+ # the same template into the gateway image, which must stay metadata-only.
365
+ $cfg = Get-Content $litellmDest -Raw
366
+ $cfg = $cfg -replace '(?m)^ turn_off_message_logging: true$', ' turn_off_message_logging: false'
367
+ $cfg = $cfg -replace '(?m)^ message_logging: false', ' message_logging: true'
368
+ Set-Content -Path $litellmDest -Value $cfg -NoNewline
369
+ Log-Success "Created LiteLLM OTEL config from template (local Spotlight: message logging ON)"
329
370
  } else {
330
371
  Log-Success "LiteLLM OTEL config already present (user-owned)"
331
372
  }
332
373
 
333
- # Keep canonical template in sync with user edits when writable.
334
- if ((Test-Path $litellmSrc) -and (Test-Path $litellmDest)) {
335
- $sourceHash = (Get-Sha256Hash $litellmSrc)
336
- $destHash = (Get-Sha256Hash $litellmDest)
337
- if ($sourceHash -ne $destHash) {
338
- try {
339
- Copy-Item $litellmDest $litellmSrc -Force
340
- Log-Success "Synced LiteLLM OTEL config back to source template"
341
- } catch {
342
- Log-Info "Using user-owned LiteLLM config (source template is read-only)"
343
- }
344
- }
345
- }
374
+ # Deliberately ONE-WAY (template -> user). Copying the user's config back over the shipped
375
+ # template wrote their edits into the installed extension / a git-tracked file, and model
376
+ # entries can carry a literal upstream api_key - a credential leak into source control.
346
377
 
347
378
  $hooksDest = Join-Path $AgentHomeDir "agentide_litellm_hooks_v1.py"
348
379
  $hooksSrc = Resolve-LiteLLMHooks
@@ -361,6 +361,53 @@ run_install() {
361
361
  fi
362
362
  }
363
363
 
364
+ # Ensure the proxy has an auth key without ever asking the user for one.
365
+ #
366
+ # The shipped config sets general_settings.master_key from ${LITELLM_MASTER_KEY} and the launcher
367
+ # refuses to start unauthenticated, so a first run with no key leaves LiteLLM dead with the real
368
+ # error buried in a log. Generate one, persist it to the env file the launcher already sources,
369
+ # and hand the same value to clients as AGENTHIPPO_LITELLM_API_KEY.
370
+ ensure_litellm_master_key() {
371
+ local env_file="${AGENT_HOME_DIR}/.env"
372
+
373
+ if [[ -f "$env_file" ]] && grep -q '^[[:space:]]*LITELLM_MASTER_KEY=' "$env_file"; then
374
+ log_success "LiteLLM master key already configured"
375
+ return 0
376
+ fi
377
+ # Respect a key exported by the caller rather than minting a second one.
378
+ if [[ -n "${LITELLM_MASTER_KEY:-}" ]]; then
379
+ log_success "Using LITELLM_MASTER_KEY from the environment"
380
+ return 0
381
+ fi
382
+
383
+ local key=""
384
+ if command -v openssl >/dev/null 2>&1; then
385
+ key="sk-$(openssl rand -hex 24)"
386
+ elif command -v python3 >/dev/null 2>&1; then
387
+ key="sk-$(python3 -c 'import secrets; print(secrets.token_hex(24))')"
388
+ else
389
+ log_warn "Neither openssl nor python3 found; cannot generate a LiteLLM master key"
390
+ log_info "Set one manually: echo 'LITELLM_MASTER_KEY=sk-...' >> ${env_file}"
391
+ return 1
392
+ fi
393
+
394
+ mkdir -p "${AGENT_HOME_DIR}"
395
+ touch "$env_file"
396
+ chmod 600 "$env_file" 2>/dev/null || true
397
+ # Keep a trailing newline so appends never join onto a previous line.
398
+ if [[ -s "$env_file" ]] && [[ -n "$(tail -c 1 "$env_file" 2>/dev/null)" ]]; then
399
+ echo "" >> "$env_file"
400
+ fi
401
+ {
402
+ echo "# Generated by setup-analytics — authenticates the local LiteLLM proxy."
403
+ echo "# Clients read the same value as AGENTHIPPO_LITELLM_API_KEY."
404
+ echo "LITELLM_MASTER_KEY=${key}"
405
+ echo "AGENTHIPPO_LITELLM_API_KEY=${key}"
406
+ } >> "$env_file"
407
+ export LITELLM_MASTER_KEY="$key"
408
+ log_success "Generated a LiteLLM master key (stored in ${env_file}, mode 600)"
409
+ }
410
+
364
411
  # Setup configuration
365
412
  setup_config() {
366
413
  log_step "Setting up configuration..."
@@ -370,6 +417,9 @@ setup_config() {
370
417
  mkdir -p "${ANALYTICS_DIR}/config"
371
418
  mkdir -p "${ANALYTICS_DIR}/traces"
372
419
 
420
+ # Before any early return below — the launcher exits non-zero without a key.
421
+ ensure_litellm_master_key || true
422
+
373
423
  # Copy OTEL Collector config if not present
374
424
  if [[ ! -f "${ANALYTICS_DIR}/config/otel-collector.yaml" ]]; then
375
425
  if [[ -f "${SCRIPT_DIR}/otel-collector.yaml" ]]; then
@@ -406,19 +456,21 @@ setup_config() {
406
456
 
407
457
  if [[ ! -f "$litellm_config" ]]; then
408
458
  cp "$source_config" "$litellm_config"
409
- log_success "Created LiteLLM OTEL config from template"
459
+ # IDE/local lane: Spotlight needs prompt content, so message logging defaults ON here.
460
+ # The flip happens at copy time, not in the template, because Dockerfile.litellm bakes
461
+ # the same template into the gateway image, which must stay metadata-only.
462
+ sed -i.bak \
463
+ -e 's/^ turn_off_message_logging: true$/ turn_off_message_logging: false/' \
464
+ -e 's/^ message_logging: false/ message_logging: true/' \
465
+ "$litellm_config" && rm -f "${litellm_config}.bak"
466
+ log_success "Created LiteLLM OTEL config from template (local Spotlight: message logging ON)"
410
467
  else
411
468
  log_success "LiteLLM OTEL config already present (user-owned)"
412
469
  fi
413
470
 
414
- # Keep canonical template in sync with user edits when writable.
415
- if [[ -f "$source_config" ]] && ! cmp -s "$litellm_config" "$source_config"; then
416
- if cp "$litellm_config" "$source_config" 2>/dev/null; then
417
- log_success "Synced LiteLLM OTEL config back to source template"
418
- else
419
- log_info "Using user-owned LiteLLM config (source template is read-only)"
420
- fi
421
- fi
471
+ # Deliberately ONE-WAY (template -> user). Copying the user's config back over the shipped
472
+ # template wrote their edits into the installed extension / a git-tracked file, and model
473
+ # entries can carry a literal upstream `api_key` — a credential leak into source control.
422
474
  # Install LiteLLM hook for tool order sanitizer
423
475
  local hooks_target="${AGENT_HOME_DIR}/agentide_litellm_hooks_v1.py"
424
476
  local hooks_source=""
@@ -501,9 +553,34 @@ check_litellm() {
501
553
  fi
502
554
  }
503
555
 
504
- # Get LiteLLM PIDs
556
+ # Get LiteLLM PIDs.
557
+ #
558
+ # Prefer the PID the launcher recorded: `pgrep -f litellm` matches ANY command line containing
559
+ # the word, so on a shared or CI host it happily returns a colleague's proxy, an editor with the
560
+ # string in its argv, or a log tail — and stop_litellm SIGKILLs whatever it returns. The broad
561
+ # match stays only as a fallback when no PID file exists, and is scoped to a real proxy invocation.
505
562
  get_litellm_pids() {
506
- pgrep -f "litellm" 2>/dev/null || true
563
+ local pid_file="${AGENT_HOME_DIR}/litellm.pid"
564
+ if [[ -f "$pid_file" ]]; then
565
+ local recorded
566
+ recorded="$(head -n 1 "$pid_file" | tr -d '\r' | tr -d '[:space:]')"
567
+ if [[ "$recorded" =~ ^[0-9]+$ ]] && kill -0 "$recorded" 2>/dev/null; then
568
+ echo "$recorded"
569
+ return 0
570
+ fi
571
+ fi
572
+ # Fallback for an orphaned proxy with no PID file. Two filters, because "litellm --config"
573
+ # also appears inside the lifecycle wrapper's own script text: require the config invocation,
574
+ # then drop shell wrappers (and this script) so we never SIGKILL the thing that started us.
575
+ local pid cmd
576
+ for pid in $(pgrep -f "litellm.*--config" 2>/dev/null || true); do
577
+ [[ "$pid" == "$$" ]] && continue
578
+ cmd="$(ps -p "$pid" -o command= 2>/dev/null || true)"
579
+ case "$cmd" in
580
+ */sh\ -c*|*/bash\ -c*|*/zsh\ -c*|sh\ -c*|bash\ -c*|zsh\ -c*) continue ;;
581
+ esac
582
+ echo "$pid"
583
+ done
507
584
  }
508
585
 
509
586
  # Stop LiteLLM gracefully
@@ -558,15 +635,22 @@ start_litellm_with_otel() {
558
635
  log_info "Starting LiteLLM with OTEL config..."
559
636
  log_info "Config: $otel_config"
560
637
  # Start in background
561
- LITELLM_CONFIG_PATH="$otel_config" nohup "$litellm_script" > /tmp/litellm-otel.log 2>&1 &
638
+ # IDE/local Spotlight defaults content capture ON (operator can still export =0 to disable);
639
+ # hardened serve deployments do not launch through this script and stay metadata-only.
640
+ LITELLM_CONFIG_PATH="$otel_config" \
641
+ AGENTHIPPO_EVIDENCE_CONTENT_CAPTURE="${AGENTHIPPO_EVIDENCE_CONTENT_CAPTURE:-1}" \
642
+ nohup "$litellm_script" > /tmp/litellm-otel.log 2>&1 &
562
643
  local litellm_pid=$!
563
644
 
564
645
  # Write PID file with OTEL flag + base URL for fast status checks
565
646
  local pid_file="${AGENT_HOME_DIR}/litellm.pid"
566
647
  local litellm_host="${LITELLM_HOST:-127.0.0.1}"
567
- local tls_enabled=true
568
- case "${LITELLM_ENABLE_TLS:-true}" in
569
- 0|false|no|off) tls_enabled=false ;;
648
+ # Must match agentide-litellm.sh, which defaults TLS OFF (port 4000). Defaulting to true here
649
+ # recorded https://…:4443 in the PID file while the proxy actually bound http://…:4000, and
650
+ # the extension then persisted that unreachable URL as the global litellm baseUrl.
651
+ local tls_enabled=false
652
+ case "${LITELLM_ENABLE_TLS:-false}" in
653
+ 1|true|yes|on) tls_enabled=true ;;
570
654
  esac
571
655
  local litellm_port="${LITELLM_PORT:-}"
572
656
  if [[ -z "$litellm_port" ]]; then
@@ -593,6 +677,13 @@ start_litellm_with_otel() {
593
677
  return 0
594
678
  else
595
679
  log_error "Failed to start LiteLLM. Check /tmp/litellm-otel.log"
680
+ # Surface the actual reason — the IDE shows this output, and "check a
681
+ # log file" is a dead end for non-technical users.
682
+ if [[ -f /tmp/litellm-otel.log ]]; then
683
+ echo ""
684
+ log_info "Last 10 lines of log:"
685
+ tail -10 /tmp/litellm-otel.log 2>/dev/null || true
686
+ fi
596
687
  return 1
597
688
  fi
598
689
  }
@@ -61,6 +61,25 @@ is_running() {
61
61
  return 1
62
62
  }
63
63
 
64
+ # OTLP receiver ports, read from the collector config the daemon will run with,
65
+ # so the preflight can never drift from what the collector actually binds.
66
+ # Falls back to the OTLP defaults if the config is missing or unparseable.
67
+ get_collector_ports() {
68
+ local ports=""
69
+ if [[ -f "${CONFIG_FILE}" ]]; then
70
+ ports=$(awk '
71
+ /^receivers:/ { in_receivers = 1; next }
72
+ /^[^[:space:]#]/ { in_receivers = 0 }
73
+ in_receivers && /endpoint:/ && match($0, /:[0-9]+[[:space:]]*$/) {
74
+ port = substr($0, RSTART + 1, RLENGTH - 1)
75
+ gsub(/[[:space:]]/, "", port)
76
+ print port
77
+ }
78
+ ' "${CONFIG_FILE}" 2>/dev/null | sort -un | tr '\n' ' ')
79
+ fi
80
+ echo "${ports:-4317 4318}"
81
+ }
82
+
64
83
  # Check if port is in use (returns 0 if in use)
65
84
  port_in_use() {
66
85
  local port=$1
@@ -100,12 +119,13 @@ find_orphan_otelcol() {
100
119
  # Kill orphan collector if user confirms
101
120
  handle_orphan_collector() {
102
121
  local orphan_pid=$1
122
+ local port="${2:-4317}"
103
123
  local orphan_cmdline=$(ps -p "$orphan_pid" -o command= 2>/dev/null || echo "unknown")
104
124
 
105
125
  log_warn "Found orphan OTEL Collector (PID: ${orphan_pid})"
106
126
  log_warn "Command: ${orphan_cmdline}"
107
127
 
108
- if [[ "${FORCE:-}" == "1" ]] || [[ "${1:-}" == "--force" ]]; then
128
+ if [[ "${FORCE:-}" == "1" ]]; then
109
129
  log_info "Killing orphan process..."
110
130
  kill "${orphan_pid}" 2>/dev/null || true
111
131
  sleep 1
@@ -115,7 +135,7 @@ handle_orphan_collector() {
115
135
  return 0
116
136
  fi
117
137
 
118
- log_error "Port 4317 is in use by an orphan collector."
138
+ log_error "Port ${port} is in use by an orphan collector."
119
139
  log_error "This may be a leftover from a previous installation."
120
140
  echo ""
121
141
  echo "To fix, run one of:"
@@ -125,6 +145,37 @@ handle_orphan_collector() {
125
145
  return 1
126
146
  }
127
147
 
148
+ # Explain, in plain language, who owns a port we need and how to free it.
149
+ # Docker publishes container ports through its own proxy process, so name the
150
+ # container (the thing the user can act on) rather than "com.docker.backend".
151
+ report_port_conflict() {
152
+ local port=$1
153
+ local owner="another program"
154
+ local fix="Free the port (check with: lsof -i :${port}), then run Setup Spotlight again."
155
+
156
+ if command -v lsof &>/dev/null; then
157
+ local line=$(lsof -nP -iTCP:"${port}" -sTCP:LISTEN 2>/dev/null | awk 'NR==2 {print $1, $2}')
158
+ if [[ -n "$line" ]]; then
159
+ local cmd="${line% *}"
160
+ local pid="${line##* }"
161
+ owner="'${cmd}' (PID ${pid})"
162
+ fix="Stop that program (or run: kill ${pid}), then run Setup Spotlight again."
163
+ if [[ "$cmd" == com.docke* || "$cmd" == *docker* ]] && command -v docker &>/dev/null; then
164
+ local container=$(docker ps --filter "publish=${port}" --format '{{.Names}}' 2>/dev/null | head -1)
165
+ if [[ -n "$container" ]]; then
166
+ owner="Docker container '${container}'"
167
+ fix="Stop it with: docker stop ${container} then run Setup Spotlight again."
168
+ fi
169
+ fi
170
+ fi
171
+ fi
172
+
173
+ log_error "Cannot start Spotlight: port ${port} is already in use by ${owner}."
174
+ echo ""
175
+ echo " To fix: ${fix}"
176
+ echo ""
177
+ }
178
+
128
179
  # Start in foreground
129
180
  start_foreground() {
130
181
  local otelcol=$(find_otelcol)
@@ -149,12 +200,18 @@ start_daemon() {
149
200
 
150
201
  local otelcol=$(find_otelcol)
151
202
 
152
- # Check for port conflicts before starting
153
- if port_in_use 4317; then
203
+ # Check for port conflicts before starting. The collector needs every
204
+ # receiver port in its config — a conflict on any of them kills it at bind
205
+ # time with the real error buried in the log, so fail here with a clear message.
206
+ local port
207
+ for port in $(get_collector_ports); do
208
+ if ! port_in_use "$port"; then
209
+ continue
210
+ fi
154
211
  local orphan_pid=$(find_orphan_otelcol || true)
155
212
  if [[ -n "$orphan_pid" ]]; then
156
213
  if [[ "${FORCE:-}" == "1" ]]; then
157
- log_warn "Port 4317 in use by orphan process (PID: ${orphan_pid}). Killing..."
214
+ log_warn "Port ${port} in use by orphan process (PID: ${orphan_pid}). Killing..."
158
215
  kill "${orphan_pid}" 2>/dev/null || true
159
216
  sleep 1
160
217
  # Force kill if still running
@@ -162,16 +219,21 @@ start_daemon() {
162
219
  kill -9 "${orphan_pid}" 2>/dev/null || true
163
220
  sleep 1
164
221
  fi
222
+ # The orphan may not have been the port owner (e.g. a container
223
+ # holds it) — re-check before declaring the port free.
224
+ if port_in_use "$port"; then
225
+ report_port_conflict "$port"
226
+ exit 1
227
+ fi
165
228
  else
166
- handle_orphan_collector "$orphan_pid"
229
+ handle_orphan_collector "$orphan_pid" "$port"
167
230
  exit 1
168
231
  fi
169
232
  else
170
- log_error "Port 4317 is in use by another process."
171
- log_error "Check with: lsof -i :4317"
233
+ report_port_conflict "$port"
172
234
  exit 1
173
235
  fi
174
- fi
236
+ done
175
237
 
176
238
  # Ensure directories exist
177
239
  mkdir -p "${ANALYTICS_DIR}/traces"
@@ -260,12 +322,22 @@ show_status() {
260
322
  echo " kill ${orphan_pid} && $0 --daemon"
261
323
  echo " Or auto-fix with:"
262
324
  echo " FORCE=1 $0 --daemon"
263
- elif port_in_use 4317; then
264
- echo -e "OTEL Collector: ${RED}Port Conflict${NC}"
265
- echo -e " ${RED}⚠ Port 4317 in use by another process${NC}"
266
- echo " Check with: lsof -i :4317"
267
325
  else
268
- echo -e "OTEL Collector: ${RED}Stopped${NC}"
326
+ local conflict_port=""
327
+ local port
328
+ for port in $(get_collector_ports); do
329
+ if port_in_use "$port"; then
330
+ conflict_port=$port
331
+ break
332
+ fi
333
+ done
334
+ if [[ -n "$conflict_port" ]]; then
335
+ echo -e "OTEL Collector: ${RED}Port Conflict${NC}"
336
+ echo -e " ${RED}⚠ Port ${conflict_port} in use by another process${NC}"
337
+ echo " Check with: lsof -i :${conflict_port}"
338
+ else
339
+ echo -e "OTEL Collector: ${RED}Stopped${NC}"
340
+ fi
269
341
  fi
270
342
  fi
271
343
  echo ""
@@ -162,6 +162,32 @@ MANAGED_GATEWAY_WRAPPER_DIR="${HOME}/.local/bin"
162
162
  AGENT_ANYWHERE_PATH_MARKER_BEGIN="# >>> Agent Anywhere PATH >>>"
163
163
  AGENT_ANYWHERE_PATH_MARKER_END="# <<< Agent Anywhere PATH <<<"
164
164
  MANAGED_GATEWAY_WRAPPER_MARKER="# Managed by Agent Anywhere"
165
+ HIPPOCLAW_NPM_PACKAGE="@agenthippo-ai/hippoclaw"
166
+
167
+ # Global npm trees: scoped HippoClaw first, legacy openclaw fallback.
168
+ global_gateway_package_dir_candidates() {
169
+ local npm_root="${1:-}"
170
+ local lib_base="${2:-}"
171
+ if [[ -n "$npm_root" ]]; then
172
+ printf '%s\n' "$npm_root/@agenthippo-ai/hippoclaw" "$npm_root/openclaw"
173
+ fi
174
+ if [[ -n "$lib_base" ]]; then
175
+ printf '%s\n' "$lib_base/@agenthippo-ai/hippoclaw" "$lib_base/openclaw"
176
+ fi
177
+ }
178
+
179
+ remove_global_gateway_package_trees() {
180
+ local npm_root prefix pkg_dir
181
+ npm_root="$(npm root -g 2>/dev/null || true)"
182
+ prefix="$(npm prefix -g 2>/dev/null || true)"
183
+ while IFS= read -r pkg_dir; do
184
+ [[ -n "$pkg_dir" && -e "$pkg_dir" ]] || continue
185
+ rm -rf "$pkg_dir"
186
+ done < <(
187
+ global_gateway_package_dir_candidates "$npm_root" "${prefix:+$prefix/lib/node_modules}"
188
+ global_gateway_package_dir_candidates "" "$NPM_GLOBAL_PREFIX/lib/node_modules"
189
+ )
190
+ }
165
191
 
166
192
  add_path_entry() {
167
193
  local entry="$1"
@@ -514,15 +540,15 @@ resolve_managed_gateway_install() {
514
540
  if [[ -n "$current_node" ]]; then
515
541
  npm_root="$(npm root -g 2>/dev/null || true)"
516
542
  prefix="$(npm prefix -g 2>/dev/null || true)"
517
- package_root="${npm_root}/openclaw"
518
- if [[ -n "$npm_root" && -n "$prefix" && -d "$package_root" ]]; then
543
+ while IFS= read -r package_root; do
544
+ [[ -n "$npm_root" && -n "$prefix" && -d "$package_root" ]] || continue
519
545
  resolve_managed_gateway_install_from_root "$current_node" "$package_root" "${prefix}/bin" && return 0
520
- fi
546
+ done < <(global_gateway_package_dir_candidates "$npm_root" "${prefix:+$prefix/lib/node_modules}")
521
547
 
522
- package_root="${NPM_GLOBAL_PREFIX}/lib/node_modules/openclaw"
523
- if [[ -d "$package_root" ]]; then
548
+ while IFS= read -r package_root; do
549
+ [[ -d "$package_root" ]] || continue
524
550
  resolve_managed_gateway_install_from_root "$current_node" "$package_root" "${NPM_GLOBAL_PREFIX}/bin" && return 0
525
- fi
551
+ done < <(global_gateway_package_dir_candidates "" "$NPM_GLOBAL_PREFIX/lib/node_modules")
526
552
  fi
527
553
 
528
554
  local candidate_node candidate_dir install_dir
@@ -530,10 +556,10 @@ resolve_managed_gateway_install() {
530
556
  while IFS= read -r candidate_node; do
531
557
  candidate_dir="${candidate_node%/node}"
532
558
  install_dir="$(dirname "$candidate_dir")"
533
- package_root="${install_dir}/lib/node_modules/openclaw"
534
- if [[ -d "$package_root" ]]; then
559
+ while IFS= read -r package_root; do
560
+ [[ -d "$package_root" ]] || continue
535
561
  resolve_managed_gateway_install_from_root "$candidate_node" "$package_root" "$candidate_dir" && return 0
536
- fi
562
+ done < <(global_gateway_package_dir_candidates "" "$install_dir/lib/node_modules")
537
563
  done < <(find "$NODE_BOOTSTRAP_ROOT" -maxdepth 4 -type f -path '*/bin/node' 2>/dev/null | sort -r)
538
564
  return 1
539
565
  }
@@ -845,7 +871,7 @@ install_openclaw_from_remote_git_spec() {
845
871
  }
846
872
 
847
873
  local tgz
848
- tgz="$(ls -t "$work/src"/openclaw-*.tgz 2>/dev/null | head -1)"
874
+ tgz="$(ls -t "$work/src"/agenthippo-ai-hippoclaw-*.tgz "$work/src"/openclaw-*.tgz 2>/dev/null | head -1)"
849
875
  if [[ -z "$tgz" || ! -f "$tgz" ]]; then
850
876
  log_error "npm pack did not produce openclaw-*.tgz under clone"
851
877
  rm -rf "$work"
@@ -1047,11 +1073,15 @@ check_installed() {
1047
1073
  groot="$(resolve_managed_gateway_package_root_from_manifest || true)"
1048
1074
  if [[ -z "$groot" || ! -d "$groot" ]]; then
1049
1075
  npm_root="$(npm root -g 2>/dev/null || true)"
1050
- groot=""
1051
- [[ -n "$npm_root" ]] && groot="$npm_root/openclaw"
1052
- if [[ -z "$groot" || ! -d "$groot" ]]; then
1053
- groot="$NPM_GLOBAL_PREFIX/lib/node_modules/openclaw"
1054
- fi
1076
+ prefix="$(npm prefix -g 2>/dev/null || true)"
1077
+ while IFS= read -r candidate; do
1078
+ [[ -d "$candidate" ]] || continue
1079
+ groot="$candidate"
1080
+ break
1081
+ done < <(
1082
+ global_gateway_package_dir_candidates "$npm_root" "${prefix:+$prefix/lib/node_modules}"
1083
+ global_gateway_package_dir_candidates "" "$NPM_GLOBAL_PREFIX/lib/node_modules"
1084
+ )
1055
1085
  fi
1056
1086
  if [[ -n "$groot" && -d "$groot" ]]; then
1057
1087
  if [[ -f "$groot/dist/entry.mjs" || -f "$groot/dist/entry.js" ]]; then
@@ -1119,7 +1149,11 @@ check_uninstalled() {
1119
1149
  local groot=""
1120
1150
  groot="$(resolve_managed_gateway_package_root_from_manifest || true)"
1121
1151
  if [[ -z "$groot" || ! -d "$groot" ]]; then
1122
- groot="$NPM_GLOBAL_PREFIX/lib/node_modules/openclaw"
1152
+ while IFS= read -r candidate; do
1153
+ [[ -d "$candidate" ]] || continue
1154
+ groot="$candidate"
1155
+ break
1156
+ done < <(global_gateway_package_dir_candidates "" "$NPM_GLOBAL_PREFIX/lib/node_modules")
1123
1157
  fi
1124
1158
  if [[ -n "$groot" && -d "$groot" ]]; then
1125
1159
  log_warn "Managed npm openclaw package still present ($groot)"
@@ -1169,9 +1203,9 @@ install_gateway() {
1169
1203
 
1170
1204
  local _npm_root
1171
1205
  _npm_root="$(npm root -g 2>/dev/null || true)"
1172
- if [[ -n "$_npm_root" && -e "$_npm_root/openclaw" ]]; then
1173
- log_info "Removing existing global openclaw tree (avoids partial install / tar errors)..."
1174
- rm -rf "$_npm_root/openclaw"
1206
+ if [[ -n "$_npm_root" ]]; then
1207
+ log_info "Removing existing global gateway package trees (if present)..."
1208
+ remove_global_gateway_package_trees
1175
1209
  fi
1176
1210
 
1177
1211
  # Local directory: npm links to that path (stable). Remote github:/git+: use clone+pack+tgz
@@ -1270,8 +1304,13 @@ remove_managed_openclaw_artifacts() {
1270
1304
  local dir install_dir pkg_root
1271
1305
 
1272
1306
  pkg_root="$NPM_GLOBAL_PREFIX/lib/node_modules/openclaw"
1273
- if [[ -d "$pkg_root" ]]; then
1274
- log_info "Removing managed openclaw package from $pkg_root"
1307
+ if [[ -d "$NPM_GLOBAL_PREFIX/lib/node_modules/@agenthippo-ai/hippoclaw" ]]; then
1308
+ pkg_root="$NPM_GLOBAL_PREFIX/lib/node_modules/@agenthippo-ai/hippoclaw"
1309
+ elif [[ ! -d "$pkg_root" ]]; then
1310
+ pkg_root=""
1311
+ fi
1312
+ if [[ -n "$pkg_root" && -d "$pkg_root" ]]; then
1313
+ log_info "Removing managed gateway package from $pkg_root"
1275
1314
  rm -rf "$pkg_root"
1276
1315
  fi
1277
1316
  rm -f "$NPM_GLOBAL_PREFIX/bin/openclaw" "$NPM_GLOBAL_PREFIX/bin/hippoclaw" 2>/dev/null || true
@@ -1279,11 +1318,11 @@ remove_managed_openclaw_artifacts() {
1279
1318
  [[ -d "$NODE_BOOTSTRAP_ROOT" ]] || return 0
1280
1319
  while IFS= read -r dir; do
1281
1320
  install_dir="$(dirname "$dir")"
1282
- pkg_root="$install_dir/lib/node_modules/openclaw"
1283
- if [[ -d "$pkg_root" ]]; then
1284
- log_info "Removing managed openclaw package from $pkg_root"
1285
- rm -rf "$pkg_root"
1286
- fi
1321
+ while IFS= read -r candidate; do
1322
+ [[ -d "$candidate" ]] || continue
1323
+ log_info "Removing managed gateway package from $candidate"
1324
+ rm -rf "$candidate"
1325
+ done < <(global_gateway_package_dir_candidates "" "$install_dir/lib/node_modules")
1287
1326
  rm -f "$dir/openclaw" "$dir/hippoclaw" 2>/dev/null || true
1288
1327
  done < <(
1289
1328
  find "$NODE_BOOTSTRAP_ROOT" -maxdepth 4 -type f -path '*/bin/node' -print 2>/dev/null \
@@ -1306,12 +1345,13 @@ remove_resolved_cli_openclaw_install() {
1306
1345
  node_bin_dir="$(cd "$(dirname "$cli_bin")" 2>/dev/null && pwd)" || break
1307
1346
  # npm global layout: <prefix>/bin/<cli> and <prefix>/lib/node_modules/openclaw.
1308
1347
  prefix="$(dirname "$node_bin_dir")"
1309
- pkg_root="$prefix/lib/node_modules/openclaw"
1310
- if [[ -d "$pkg_root" ]]; then
1311
- log_info "Removing openclaw package tree resolved via CLI on PATH: $pkg_root"
1348
+ while IFS= read -r pkg_root; do
1349
+ [[ -d "$pkg_root" ]] || continue
1350
+ log_info "Removing gateway package tree resolved via CLI on PATH: $pkg_root"
1312
1351
  rm -rf "$pkg_root" 2>/dev/null || true
1313
1352
  removed_any=true
1314
- fi
1353
+ break
1354
+ done < <(global_gateway_package_dir_candidates "" "$prefix/lib/node_modules")
1315
1355
  rm -f "$node_bin_dir/openclaw" "$node_bin_dir/hippoclaw" 2>/dev/null || true
1316
1356
  # Bail if the same path still resolves (could not delete, e.g. permissions) to avoid looping.
1317
1357
  hash -r 2>/dev/null || true
@@ -1393,13 +1433,13 @@ uninstall_gateway() {
1393
1433
  log_error "npm not found"
1394
1434
  exit 1
1395
1435
  fi
1396
- npm uninstall -g openclaw 2>/dev/null || true
1436
+ npm uninstall -g @agenthippo-ai/hippoclaw openclaw 2>/dev/null || true
1397
1437
  remove_managed_gateway_wrapper_state
1398
1438
  cleanup_shell_path_setup
1399
1439
  # Also remove the install wherever the CLI actually resolves (nvm/other Node global trees),
1400
1440
  # not just the active node + managed prefix that `npm uninstall -g` above covers.
1401
1441
  remove_resolved_cli_openclaw_install
1402
- log_success "Ran: npm uninstall -g openclaw (global tree removed if present)"
1442
+ log_success "Ran: npm uninstall -g @agenthippo-ai/hippoclaw openclaw (global tree removed if present)"
1403
1443
  log_info "~/.openclaw was not removed (config, sessions, and credentials stay on disk)."
1404
1444
  local oc_after=""
1405
1445
  hash -r 2>/dev/null || true