Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions scripts/checks/vitest-project-overlap.mts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ const INSTALLER_INTEGRATION_TESTS = new Set([
"test/install-clone-ref.test.ts",
"test/install-express-prompt.test.ts",
"test/install-express-wsl-ollama.test.ts",
"test/install-forward-restore-diagnostics.test.ts",
"test/install-hermes-forward-restore.test.ts",
"test/install-managed-cli-reuse.test.ts",
"test/install-openshell-version-pin.test.ts",
Expand Down
86 changes: 82 additions & 4 deletions scripts/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ resolve_hermes_api_port() {
}

restore_onboard_forward_after_post_checks() {
local sandbox_name agent_name agent_display port openshell_bin openshell_dir attempt selected_state_dir state_dir pid_file watcher_script watcher_pid
local sandbox_name agent_name agent_display port openshell_bin openshell_dir attempt selected_state_dir state_dir pid_file watcher_script watcher_pid start_diagnostic diagnostic_file
sandbox_name="$(resolve_default_sandbox_name)"
agent_name="$(resolve_onboarded_agent)"
agent_display="$(agent_display_name "$agent_name")"
Expand Down Expand Up @@ -557,6 +557,37 @@ restore_onboard_forward_after_post_checks() {
rm -f "$pid_file"
fi

redact_forward_start_diagnostic() {
local redactor
redactor="$(resolve_repo_root)/dist/lib/security/redact.js"
if command_exists node && [[ -f "$redactor" ]] \
&& node -e '
const fs = require("fs");
const { redactFull } = require(process.argv[1]);
process.stdout.write(redactFull(fs.readFileSync(0, "utf8")));
' "$redactor" 2>/dev/null; then
return 0
fi
printf "<REDACTED>"
}

sanitize_forward_start_diagnostic() {
if command_exists node && node -e '
const fs = require("fs");
const encoded = fs.readFileSync(0).toString("latin1")
.replace(/\x1B\][\s\S]*?(?:\x07|\x1B\\|$)/g, "")
.replace(/\x9D[\s\S]*?(?:\x07|\x1B\\|\x9C|$)/g, "")
.replace(/(?:\x1B\[|\x9B)[0-?]*[ -/]*[@-~]/g, "")
.replace(/\x1B[@-_]/g, "");
const diagnostic = Buffer.from(encoded, "latin1").toString("utf8")
.replace(/[\u0000-\u001F\u007F-\u009F]/g, " ");
process.stdout.write(diagnostic);
' 2>/dev/null; then
return 0
fi
printf "<REDACTED>"
}

stop_agent_forward_if_owned() {
local forward_list owner status
"$openshell_bin" forward stop "$port" "$sandbox_name" >/dev/null 2>&1 && return 0
Expand All @@ -578,12 +609,36 @@ restore_onboard_forward_after_post_checks() {
fi
}

start_diagnostic=""
diagnostic_file="$(mktemp "${TMPDIR:-/tmp}/nemoclaw-forward-start-XXXXXX" 2>/dev/null)" \
|| diagnostic_file=""

for attempt in 1 2 3; do
stop_agent_forward_if_owned
if [ "$attempt" -gt 1 ]; then
sleep 2
fi
"$openshell_bin" forward start --background "$port" "$sandbox_name" >/dev/null 2>&1 || true
if [[ -n "$diagnostic_file" ]]; then
"$openshell_bin" forward start --background "$port" "$sandbox_name" \
>"$diagnostic_file" 2>&1 || true
start_diagnostic="$(sanitize_forward_start_diagnostic <"$diagnostic_file" | awk '
{
gsub(/[[:space:]]+/, " ")
sub(/^ /, "")
sub(/ $/, "")
if ($0 != "") joined = (joined == "" ? $0 : joined "; " $0)
}
END { printf "%s", joined }
' 2>/dev/null || true)"
start_diagnostic="$(
printf "%s" "$start_diagnostic" | redact_forward_start_diagnostic
)"
if [[ "${#start_diagnostic}" -gt 300 ]]; then
start_diagnostic="${start_diagnostic:0:300} [truncated]"
fi
else
"$openshell_bin" forward start --background "$port" "$sandbox_name" >/dev/null 2>&1 || true
fi
watcher_pid=""
if [[ "${NEMOCLAW_SKIP_FORWARD_WATCHER:-}" != "1" ]] && command_exists node; then
watcher_script="${pid_file}.js"
Expand All @@ -598,10 +653,27 @@ function healthy() {
stdio: "ignore",
}).status === 0;
}
function listedStatus() {
const listed = spawnSync(openshellBin, ["forward", "list"], { encoding: "utf-8" });
if (listed.status !== 0 || typeof listed.stdout !== "string") return null;
for (const line of listed.stdout.split("\n")) {
const columns = line.replace(/\x1B\[[0-?]*[ -/]*[@-~]/g, "").trim().split(/\s+/);
if (columns[0] === sandboxName && columns[2] === port) {
return (columns[4] || "").toLowerCase();
}
}
return "";
}
function tick() {
if (healthy()) return;
run(["forward", "stop", port, sandboxName]);
run(["forward", "start", "--background", port, sandboxName]);
const status = listedStatus();
if (status === null) return;
if (status === "dead") {
run(["forward", "stop", port, sandboxName]);
run(["forward", "start", "--background", port, sandboxName]);
return;
}
if (status === "") run(["forward", "start", "--background", port, sandboxName]);
}
tick();
setInterval(tick, 10_000);
Expand All @@ -622,19 +694,25 @@ NODE
sleep 4
if command_exists curl \
&& curl -sf --max-time 3 "http://127.0.0.1:${port}/health" >/dev/null 2>&1; then
[[ -n "$diagnostic_file" ]] && rm -f "$diagnostic_file"
return 0
fi
watcher_pid="$(cat "$pid_file" 2>/dev/null || true)"
if ! command_exists curl && [[ -n "$watcher_pid" ]] && kill -0 "$watcher_pid" >/dev/null 2>&1; then
[[ -n "$diagnostic_file" ]] && rm -f "$diagnostic_file"
return 0
fi
if [[ -n "$watcher_pid" ]]; then
kill "$watcher_pid" >/dev/null 2>&1 || true
fi
rm -f "$pid_file"
done
[[ -n "$diagnostic_file" ]] && rm -f "$diagnostic_file"

warn "Could not restore ${agent_display} host forward on port ${port}."
if [[ -n "$start_diagnostic" ]]; then
warn "OpenShell reported: ${start_diagnostic}"
fi
warn "Run: openshell forward start --background ${port} ${sandbox_name}"
return 1
}
Expand Down
Loading
Loading