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
Binary file not shown.
314 changes: 295 additions & 19 deletions brev/welcome-ui/server.py

Large diffs are not rendered by default.

17 changes: 16 additions & 1 deletion sandboxes/nemoclaw/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,17 @@ USER root
COPY nemoclaw-start.sh /usr/local/bin/nemoclaw-start
RUN chmod +x /usr/local/bin/nemoclaw-start

# Install the policy reverse proxy (sits in front of the OpenClaw gateway,
# intercepts /api/policy to read/write the sandbox policy file) and its
# runtime dependencies for gRPC gateway sync.
COPY policy-proxy.js /usr/local/lib/policy-proxy.js
COPY proto/ /usr/local/lib/nemoclaw-proto/
RUN npm install -g @grpc/grpc-js @grpc/proto-loader js-yaml

# Allow the sandbox user to read the default policy (the startup script
# copies it to a writable location; this chown covers non-Landlock envs)
RUN chown -R sandbox:sandbox /etc/navigator

# Stage the NeMoClaw DevX extension source
COPY nemoclaw-ui-extension/extension/ /opt/nemoclaw-devx/

Expand All @@ -29,8 +40,11 @@ COPY nemoclaw-ui-extension/extension/ /opt/nemoclaw-devx/
# add <script>/<link> tags to index.html.
# API key placeholders (__NVIDIA_*_API_KEY__) stay as literal strings in the
# bundle; they are substituted at container startup from environment variables.
# js-yaml is installed locally so esbuild can bundle it for browser-side YAML
# parsing in the policy page; the node_modules are removed after bundling.
RUN set -e; \
npm install -g esbuild; \
cd /opt/nemoclaw-devx && npm install --production; \
UI_DIR="$(npm root -g)/openclaw/dist/control-ui"; \
esbuild /opt/nemoclaw-devx/index.ts \
--bundle \
Expand All @@ -39,6 +53,7 @@ RUN set -e; \
HASH=$(md5sum "$UI_DIR/assets/nemoclaw-devx.js" | cut -c1-8); \
sed -i "s|</head>|<link rel=\"stylesheet\" href=\"./assets/nemoclaw-devx.css?v=${HASH}\">\n</head>|" "$UI_DIR/index.html"; \
sed -i "s|</head>|<script type=\"module\" src=\"./assets/nemoclaw-devx.js?v=${HASH}\"></script>\n</head>|" "$UI_DIR/index.html"; \
npm uninstall -g esbuild
npm uninstall -g esbuild; \
rm -rf /opt/nemoclaw-devx/node_modules

ENTRYPOINT ["/bin/bash"]
43 changes: 32 additions & 11 deletions sandboxes/nemoclaw/nemoclaw-start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,14 @@ set -euo pipefail
# that is blocked, we skip gracefully — users can still enter keys via
# the API Keys page in the OpenClaw UI.
# --------------------------------------------------------------------------
if [ -z "${CHAT_UI_URL:-}" ]; then
echo "Error: CHAT_UI_URL environment variable is required." >&2
echo "Set it to the URL where the chat UI will be accessed, e.g.:" >&2
echo " Local: CHAT_UI_URL=http://127.0.0.1:18789" >&2
echo " Brev: CHAT_UI_URL=https://187890-<brev-id>.brevlab.com" >&2
exit 1
fi

BUNDLE="$(npm root -g)/openclaw/dist/control-ui/assets/nemoclaw-devx.js"

if [ -f "$BUNDLE" ]; then
Expand Down Expand Up @@ -74,26 +82,21 @@ openclaw onboard \
--custom-api-key "not-used" \
--secret-input-mode plaintext \
--custom-compatibility openai \
--gateway-port 18789 \
--gateway-port 18788 \
--gateway-bind loopback

export NVIDIA_API_KEY=" "

GATEWAY_PORT=18789

if [ -z "${CHAT_UI_URL:-}" ]; then
echo "Error: CHAT_UI_URL environment variable is required." >&2
echo "Set it to the URL where the chat UI will be accessed, e.g.:" >&2
echo " Local: CHAT_UI_URL=http://127.0.0.1:18789" >&2
echo " Brev: CHAT_UI_URL=https://187890-<brev-id>.brevlab.com" >&2
exit 1
fi
INTERNAL_GATEWAY_PORT=18788
PUBLIC_PORT=18789

# allowedOrigins must reference the PUBLIC port (18789) since that is the
# origin the browser sends. The proxy on 18789 forwards to 18788 internally.
python3 -c "
import json, os
from urllib.parse import urlparse
cfg = json.load(open(os.environ['HOME'] + '/.openclaw/openclaw.json'))
local = 'http://127.0.0.1:${GATEWAY_PORT}'
local = 'http://127.0.0.1:${PUBLIC_PORT}'
parsed = urlparse(os.environ['CHAT_UI_URL'])
chat_origin = f'{parsed.scheme}://{parsed.netloc}'
origins = [local]
Expand All @@ -108,6 +111,24 @@ json.dump(cfg, open(os.environ['HOME'] + '/.openclaw/openclaw.json', 'w'), inden

nohup openclaw gateway > /tmp/gateway.log 2>&1 &

# Copy the default policy to a writable location so that policy-proxy can
# update it at runtime. /etc is read-only under Landlock, but /sandbox is
# read-write, so we use /sandbox/.openclaw/ which is already owned by the
# sandbox user.
_POLICY_SRC="/etc/navigator/policy.yaml"
_POLICY_DST="/sandbox/.openclaw/policy.yaml"
if [ ! -f "$_POLICY_DST" ] && [ -f "$_POLICY_SRC" ]; then
cp "$_POLICY_SRC" "$_POLICY_DST" 2>/dev/null || true
fi
_POLICY_PATH="${_POLICY_DST}"
[ -f "$_POLICY_PATH" ] || _POLICY_PATH="$_POLICY_SRC"

# Start the policy reverse proxy on the public-facing port. It forwards all
# traffic to the OpenClaw gateway on the internal port and intercepts
# /api/policy requests to read/write the sandbox policy file.
NODE_PATH=$(npm root -g) POLICY_PATH=${_POLICY_PATH} UPSTREAM_PORT=${INTERNAL_GATEWAY_PORT} LISTEN_PORT=${PUBLIC_PORT} \
nohup node /usr/local/lib/policy-proxy.js >> /tmp/gateway.log 2>&1 &

# Auto-approve pending device pairing requests so the browser is paired
# before the user notices the "pairing required" prompt in the Control UI.
(
Expand Down
24 changes: 24 additions & 0 deletions sandboxes/nemoclaw/nemoclaw-ui-extension/extension/icons.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,30 @@ export const ICON_EYE = `<svg viewBox="0 0 24 24"><path d="M2 12s3-7 10-7 10 7 1

export const ICON_EYE_OFF = `<svg viewBox="0 0 24 24"><path d="M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19m-6.72-1.07a3 3 0 1 1-4.24-4.24"/><line x1="1" y1="1" x2="23" y2="23"/></svg>`;

export const ICON_LOCK = `<svg viewBox="0 0 24 24"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>`;

export const ICON_PLUS = `<svg viewBox="0 0 24 24"><path d="M12 5v14"/><path d="M5 12h14"/></svg>`;

export const ICON_TRASH = `<svg viewBox="0 0 24 24"><path d="M3 6h18"/><path d="M19 6v14c0 1-1 2-2 2H7c-1 0-2-1-2-2V6"/><path d="M8 6V4c0-1 1-2 2-2h4c1 0 2 1 2 2v2"/><line x1="10" x2="10" y1="11" y2="17"/><line x1="14" x2="14" y1="11" y2="17"/></svg>`;

export const ICON_EDIT = `<svg viewBox="0 0 24 24"><path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/></svg>`;

export const ICON_INFO = `<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M12 16v-4"/><path d="M12 8h.01"/></svg>`;

export const ICON_GLOBE = `<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg>`;

export const ICON_TERMINAL = `<svg viewBox="0 0 24 24"><polyline points="4 17 10 11 4 5"/><line x1="12" x2="20" y1="19" y2="19"/></svg>`;

export const ICON_FOLDER = `<svg viewBox="0 0 24 24"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>`;

export const ICON_USER = `<svg viewBox="0 0 24 24"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`;

export const ICON_CHEVRON_RIGHT = `<svg viewBox="0 0 24 24"><polyline points="9 18 15 12 9 6"/></svg>`;

export const ICON_SEARCH = `<svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><path d="m21 21-4.3-4.3"/></svg>`;

export const ICON_WARNING = `<svg viewBox="0 0 24 24"><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><path d="M12 9v4"/><path d="M12 17h.01"/></svg>`;

export const TARGET_ICONS: Record<string, string> = {
"dgx-spark": ICON_CHIP,
"dgx-station": ICON_SERVER,
Expand Down
11 changes: 6 additions & 5 deletions sandboxes/nemoclaw/nemoclaw-ui-extension/extension/nav-group.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import { ICON_SHIELD, ICON_ROUTE, ICON_KEY } from "./icons.ts";
import { renderApiKeysPage, areAllKeysConfigured, updateStatusDots } from "./api-keys-page.ts";
import { renderPolicyPage } from "./policy-page.ts";

// ---------------------------------------------------------------------------
// Page definitions
Expand All @@ -26,12 +27,12 @@ interface NemoClawPage {
const NEMOCLAW_PAGES: NemoClawPage[] = [
{
id: "nemoclaw-policy",
label: "Policy",
label: "Sandbox Policy",
icon: ICON_SHIELD,
title: "Policy",
subtitle: "Manage deployment policies and guardrails",
emptyMessage:
"Policy configuration is coming soon. You'll be able to define safety policies, rate limits, and access controls for your NeMoClaw deployments here.",
title: "Sandbox Policy",
subtitle: "View and manage sandbox security guardrails",
emptyMessage: "",
customRender: renderPolicyPage,
},
{
id: "nemoclaw-inference-routes",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"private": true,
"dependencies": {
"js-yaml": "^4.1.0"
}
}
Loading