Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
dda17f2
chore: unify credential passing pattern across all integrations
ericksoa Mar 22, 2026
67cc12b
feat: host-side bridge framework with Discord support
ericksoa Mar 22, 2026
80afba3
Merge branch 'feat/host-side-bridge-framework' into chore/unify-crede…
ericksoa Mar 22, 2026
04537f1
fix: add host-side Slack bridge using bridge-core
ericksoa Mar 22, 2026
ea3fb46
refactor: yaml-driven bridge architecture with adapter pattern
ericksoa Mar 22, 2026
bff2a60
fix: address CodeRabbit findings on bridge architecture
ericksoa Mar 22, 2026
b875742
fix: log metadata only in bridge message flow, never raw content
ericksoa Mar 22, 2026
223b650
fix: replace hand-rolled yaml parser with js-yaml
ericksoa Mar 22, 2026
c019a7c
refactor: move bridge configs into blueprint.yaml components
ericksoa Mar 22, 2026
2690fab
fix: add backwards-compatible telegram-bridge.js wrapper
ericksoa Mar 22, 2026
09bc811
fix: auto-start messaging bridges after onboard when tokens detected
ericksoa Mar 22, 2026
c729c14
test: add regression tests for bridge architecture and migration path
ericksoa Mar 22, 2026
44a0fe0
fix: catch execFileSync failure in bridge-core sandbox relay
ericksoa Mar 22, 2026
8143bfd
fix: address CodeRabbit round 4 findings
ericksoa Mar 22, 2026
1fa10d3
fix: reject tgApi promise on Telegram ok:false responses
ericksoa Mar 22, 2026
b970dcf
merge: resolve conflict with vitest migration on main
ericksoa Mar 22, 2026
29a4696
fix: preserve /start and /reset bot commands in telegram adapter
ericksoa Mar 22, 2026
7c97109
Merge upstream/main into chore/unify-credential-passing
cv Mar 23, 2026
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
14 changes: 12 additions & 2 deletions bin/lib/onboard.js
Original file line number Diff line number Diff line change
Expand Up @@ -443,9 +443,15 @@ async function createSandbox(gpu) {

console.log(` Creating sandbox '${sandboxName}' (this takes a few minutes on first run)...`);
const chatUiUrl = process.env.CHAT_UI_URL || 'http://127.0.0.1:18789';
// Pass user-provided secrets into the sandbox as environment variables.
// All tokens follow the same pattern: getCredential() checks env first,
// then ~/.nemoclaw/credentials.json. OpenClaw auto-enables channels when
// it detects the corresponding env var (e.g., DISCORD_BOT_TOKEN).
// See docs/reference/architecture.md "Credential Handling" for the full inventory.
const envArgs = [`CHAT_UI_URL=${shellQuote(chatUiUrl)}`];
if (process.env.NVIDIA_API_KEY) {
envArgs.push(`NVIDIA_API_KEY=${shellQuote(process.env.NVIDIA_API_KEY)}`);
const apiKey = getCredential("NVIDIA_API_KEY") || process.env.NVIDIA_API_KEY;
if (apiKey) {
envArgs.push(`NVIDIA_API_KEY=${shellQuote(apiKey)}`);
}
Comment on lines +564 to 567
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Finish the NVIDIA_API_KEY migration beyond envArgs.

These lines resolve the key only for sandbox env injection. Line 701 and Line 728 still read process.env.NVIDIA_API_KEY, so a key stored only in ~/.nemoclaw/credentials.json still breaks the non-interactive cloud path.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@bin/lib/onboard.js` around lines 452 - 455, The code only adds the resolved
NVIDIA API key to envArgs but leaves process.env.NVIDIA_API_KEY untouched, so
later reads still fail for keys stored in credentials.json; modify the
onboarding flow so that when getCredential("NVIDIA_API_KEY") returns a value you
also set process.env.NVIDIA_API_KEY (or otherwise replace later direct
process.env reads with the resolved value) so the non-interactive cloud path
uses the credential. Ensure this change is applied where envArgs is built and
will cover subsequent usages that currently access process.env.NVIDIA_API_KEY
(refer to getCredential, envArgs and the later reads of
process.env.NVIDIA_API_KEY).

const discordToken = getCredential("DISCORD_BOT_TOKEN") || process.env.DISCORD_BOT_TOKEN;
if (discordToken) {
Expand All @@ -455,6 +461,10 @@ async function createSandbox(gpu) {
if (slackToken) {
envArgs.push(`SLACK_BOT_TOKEN=${shellQuote(slackToken)}`);
}
const tgToken = getCredential("TELEGRAM_BOT_TOKEN") || process.env.TELEGRAM_BOT_TOKEN;
if (tgToken) {
envArgs.push(`TELEGRAM_BOT_TOKEN=${shellQuote(tgToken)}`);
}

// Run without piping through awk — the pipe masked non-zero exit codes
// from openshell because bash returns the status of the last pipeline
Expand Down
28 changes: 28 additions & 0 deletions docs/reference/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,31 @@ Agent (sandbox) ──▶ OpenShell gateway ──▶ NVIDIA Endpoint (build
```

Refer to [Inference Profiles](../reference/inference-profiles.md) for provider configuration details.

## Credential Handling

All user-provided secrets follow the same pattern:

1. Stored on the host in `~/.nemoclaw/credentials.json` (mode 0600) or as environment variables.
2. Retrieved via `getCredential(key)` which checks env vars first, then the credentials file.
3. Passed into the sandbox as environment variables at creation time.
4. Never written to `openclaw.json` (immutable at runtime).

### Credential Inventory

| Credential | Passed to sandbox | Used inside sandbox | Used on host | Notes |
|---|---|---|---|---|
| `NVIDIA_API_KEY` | Yes (env var) | Yes — startup script writes `auth-profiles.json` | Yes — deploy, Telegram bridge | OpenClaw requires a specific JSON file format; `nemoclaw-start.sh` handles the translation |
| `DISCORD_BOT_TOKEN` | Yes (env var) | Yes — OpenClaw reads env var directly, auto-enables Discord channel | No | |
| `SLACK_BOT_TOKEN` | Yes (env var) | Yes — OpenClaw reads env var directly, auto-enables Slack channel | No | |
| `TELEGRAM_BOT_TOKEN` | Yes (env var) | Available but unused — bridge runs on host | Yes — Telegram bridge (host-side) | Bridge uses SSH to relay messages into sandbox |
| `GITHUB_TOKEN` | Deploy path only | No | Yes — private repo access | Not needed inside sandbox |
| Gateway auth token | No — baked at build time | Yes — `openclaw.json` (root:root 444) | N/A | Per-build unique, immutable security boundary |

### Why Telegram runs on the host

The Telegram bridge (`scripts/telegram-bridge.js`) runs on the host and communicates with the sandbox via OpenShell SSH. This is intentional:

- It avoids giving the sandbox direct Telegram API access beyond what the network policy allows.
- It allows the bridge to manage multiple sandboxes from one host process.
- The `TELEGRAM_BOT_TOKEN` is still passed into the sandbox for future OpenClaw channel plugin support.
16 changes: 16 additions & 0 deletions nemoclaw-blueprint/bridges/messaging/discord.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

bridge:
name: discord
type: messaging
adapter: discord
description: "Discord bot bridge"

credentials:
token_env: DISCORD_BOT_TOKEN
allowed_env: ALLOWED_CHANNEL_IDS

messaging:
session_prefix: dc
max_chunk_size: 1900
18 changes: 18 additions & 0 deletions nemoclaw-blueprint/bridges/messaging/slack.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

bridge:
name: slack
type: messaging
adapter: slack
description: "Slack bot bridge (Socket Mode)"

credentials:
token_env: SLACK_BOT_TOKEN
extra_env:
- SLACK_APP_TOKEN
allowed_env: ALLOWED_CHANNEL_IDS

messaging:
session_prefix: sl
max_chunk_size: 3000
16 changes: 16 additions & 0 deletions nemoclaw-blueprint/bridges/messaging/telegram.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

bridge:
name: telegram
type: messaging
adapter: telegram
description: "Telegram Bot API bridge"

credentials:
token_env: TELEGRAM_BOT_TOKEN
allowed_env: ALLOWED_CHAT_IDS

messaging:
session_prefix: tg
max_chunk_size: 4000
Loading
Loading