@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.
- package/out/agentpack/index.js +3 -1
- package/out/agentpack/paths.js +25 -2
- package/out/analytics/dashboard/localFleetAdapter.js +82 -2
- package/out/analytics/dashboard/provider.js +13 -2
- package/out/analytics/dashboard/webview/panel.js +3 -2
- package/out/analytics/identity.js +0 -0
- package/out/analytics/service.js +29 -25
- package/out/analytics/sessionEvents.js +100 -24
- package/out/auth/authBaseUrl.js +59 -0
- package/out/auth/bootstrapApply.js +9 -10
- package/out/auth/brokerClient.js +48 -14
- package/out/auth/provider.js +8 -21
- package/out/auth/sessionStore.js +3 -40
- package/out/auth/signInFlow.js +2 -1
- package/out/auth-broker/config.js +50 -5
- package/out/auth-broker/server.js +126 -26
- package/out/auth-broker/store.js +25 -24
- package/out/authority/warrant.js +206 -0
- package/out/branding.js +27 -1
- package/out/cli/commands/conformance.js +249 -0
- package/out/cli/commands/data-plane.js +151 -0
- package/out/cli/commands/deploy.js +1 -0
- package/out/cli/commands/gateway.js +4 -0
- package/out/cli/commands/pack.js +182 -3
- package/out/cli/commands/sandbox.js +351 -0
- package/out/cli/commands/serve.js +1145 -130
- package/out/cli/commands/store.js +12 -4
- package/out/cli/credential-context.js +17 -0
- package/out/cli/data-plane/gateway.js +28 -5
- package/out/cli/data-plane/index.js +18 -2
- package/out/cli/data-plane/registry.js +9 -0
- package/out/cli/data-plane/remote.js +106 -0
- package/out/cli/data-plane/sidecar.js +225 -0
- package/out/cli/deploy/layers/identity.js +88 -4
- package/out/cli/deploy-init.js +89 -24
- package/out/cli/deps.js +106 -2
- package/out/cli/idp-init.js +35 -0
- package/out/cli/index.js +171 -17
- package/out/cli/pack-install-ref.js +15 -0
- package/out/cli/pack-loader.js +8 -1
- package/out/cli/pack-runtime.js +61 -0
- package/out/cli/runtime.js +35 -1
- package/out/cli/serve-hardening.js +260 -14
- package/out/cli/utils.js +12 -1
- package/out/engines/base.js +32 -3
- package/out/engines/claude.js +33 -15
- package/out/engines/codex.js +111 -27
- package/out/engines/custom.js +15 -1
- package/out/evidence/sessionShip.js +643 -0
- package/out/extension.js +11 -2
- package/out/gateway/bridge.js +40 -2
- package/out/gateway/client.js +34 -1
- package/out/gateway/installSource.js +68 -0
- package/out/gateway/lifecycle.js +185 -51
- package/out/gateway/outboundPaths.js +4 -1
- package/out/gateway/secretsPreflight.js +90 -0
- package/out/gateway/service.js +7 -2
- package/out/headless/index.js +25 -2
- package/out/live-e2e/mcp-demo/server.mjs +32 -0
- package/out/mcp.js +30 -0
- package/out/pack/caps.js +271 -0
- package/out/pack/governor.js +326 -0
- package/out/pack/lease-schema.js +273 -0
- package/out/pack/lease.js +682 -0
- package/out/pack/reconcile.js +217 -0
- package/out/pack/signature.js +839 -0
- package/out/remote/handleRemoteChat.js +44 -3
- package/out/remote/remoteTrust.js +277 -0
- package/out/remote/serveClient.js +52 -5
- package/out/runner/host.js +141 -0
- package/out/runner/protocol.js +128 -0
- package/out/runner/remoteEngine.js +166 -0
- package/out/runtime/interfaces/edit-tracker.js +2 -0
- package/out/runtime/vscode/vscode-edit-tracker.js +2 -0
- package/out/sandbox/containerDriver.js +572 -0
- package/out/sandbox/processDriver.js +318 -0
- package/out/sandbox/sandboxRunner.js +303 -0
- package/out/sandbox/support.js +301 -0
- package/out/sandbox/types.js +55 -0
- package/out/scripts/agentide-litellm.sh +67 -5
- package/out/scripts/agentide_litellm_hooks_v1.py +266 -21
- package/out/scripts/analytics/agentide-litellm-otel.config.yaml +98 -132
- package/out/scripts/analytics/setup-analytics.ps1 +45 -14
- package/out/scripts/analytics/setup-analytics.sh +106 -15
- package/out/scripts/analytics/start-analytics.sh +86 -14
- package/out/scripts/gateway/setup-gateway.sh +72 -32
- package/out/shared/models.js +11 -110
- package/out/spotlight/install.js +7 -1
- package/out/store/index.js +41 -10
- package/out/sync.js +3 -1
- package/out/test/analytics.test.js +162 -23
- package/out/test/auth-baseurl.test.js +132 -0
- package/out/test/auth-bootstrapApply.test.js +27 -6
- package/out/test/auth-broker-hardening.test.js +227 -0
- package/out/test/auth-broker.test.js +59 -9
- package/out/test/branding.test.js +13 -0
- package/out/test/caps-governor.test.js +460 -0
- package/out/test/codex-new-file-survival.test.js +119 -0
- package/out/test/configuration-scopes.test.js +131 -0
- package/out/test/data-plane-engine-wiring.test.js +32 -0
- package/out/test/data-plane.test.js +380 -0
- package/out/test/deploy-init.test.js +47 -1
- package/out/test/gateway-client.test.js +28 -0
- package/out/test/gateway-install-source.test.js +111 -0
- package/out/test/gateway-lifecycle.test.js +1 -1
- package/out/test/gateway-outbound.test.js +7 -0
- package/out/test/gateway-secrets-preflight.test.js +127 -0
- package/out/test/gateway-session-routing.test.js +18 -0
- package/out/test/governor-hardening.test.js +222 -0
- package/out/test/headless-sync.test.js +22 -0
- package/out/test/lease-signature.test.js +357 -0
- package/out/test/lease-trust-root.test.js +273 -0
- package/out/test/lease-validation.test.js +224 -0
- package/out/test/lease.test.js +114 -0
- package/out/test/localFleetAdapter.test.js +91 -0
- package/out/test/mcp.test.js +27 -1
- package/out/test/pack-admission.test.js +216 -0
- package/out/test/pack-digest-parity.test.js +140 -0
- package/out/test/pack-reconcile.test.js +183 -0
- package/out/test/pack-signature-artifact.test.js +103 -0
- package/out/test/pack-signature.test.js +176 -0
- package/out/test/pack-trust-root.test.js +202 -0
- package/out/test/remote-trust.test.js +242 -0
- package/out/test/runner-protocol.test.js +249 -0
- package/out/test/sandbox-driver.test.js +553 -0
- package/out/test/sandbox-hardening.test.js +406 -0
- package/out/test/sandbox-runner.test.js +343 -0
- package/out/test/sandbox-scope.test.js +449 -0
- package/out/test/serve-cron-acceptance.test.js +370 -0
- package/out/test/serve-gateway-turn.test.js +122 -0
- package/out/test/serve-hardening.test.js +227 -15
- package/out/test/serveClient.test.js +6 -5
- package/out/test/sessionShip.test.js +618 -0
- package/out/test/spotlightPublish.test.js +28 -0
- package/out/test/store-install-safe.test.js +69 -0
- package/out/test/tls-bypass.test.js +90 -0
- package/out/test/warrant.test.js +138 -0
- package/out/utils.js +46 -9
- 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
|
-
|
|
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
|
-
#
|
|
334
|
-
|
|
335
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
415
|
-
|
|
416
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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" ]]
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
171
|
-
log_error "Check with: lsof -i :4317"
|
|
233
|
+
report_port_conflict "$port"
|
|
172
234
|
exit 1
|
|
173
235
|
fi
|
|
174
|
-
|
|
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
|
-
|
|
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
|
|
518
|
-
|
|
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
|
-
|
|
546
|
+
done < <(global_gateway_package_dir_candidates "$npm_root" "${prefix:+$prefix/lib/node_modules}")
|
|
521
547
|
|
|
522
|
-
package_root
|
|
523
|
-
|
|
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
|
-
|
|
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
|
|
534
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
groot="$
|
|
1054
|
-
|
|
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
|
-
|
|
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"
|
|
1173
|
-
log_info "Removing existing global
|
|
1174
|
-
|
|
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 "$
|
|
1274
|
-
|
|
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
|
-
|
|
1283
|
-
|
|
1284
|
-
log_info "Removing managed
|
|
1285
|
-
rm -rf "$
|
|
1286
|
-
|
|
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
|
|
1310
|
-
|
|
1311
|
-
log_info "Removing
|
|
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
|
-
|
|
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
|