Skip to content

feat(onboard): add Brave Search onboarding via BRAVE_API_KEY#1464

Merged
ericksoa merged 12 commits intomainfrom
feat/brave-search-brave-api-key
Apr 4, 2026
Merged

feat(onboard): add Brave Search onboarding via BRAVE_API_KEY#1464
ericksoa merged 12 commits intomainfrom
feat/brave-search-brave-api-key

Conversation

@kjw3
Copy link
Copy Markdown
Contributor

@kjw3 kjw3 commented Apr 3, 2026

Summary

  • add NemoClaw onboarding support for Brave web search via BRAVE_API_KEY
  • validate the Brave API key during onboarding and keep retry prompts masked
  • add a Brave policy preset and bake Brave web config into immutable sandbox openclaw.json

Issues

Notes

  • this implementation enables web_search and web_fetch in OpenClaw config when BRAVE_API_KEY is set
  • policy still controls which destinations are actually reachable from the sandbox
  • the Brave API key is exposed to the OpenClaw agent because it is stored in sandbox-readable OpenClaw config

Why this shape

  • we explored using an OpenShell-hosted Brave credential first
  • OpenShell can host BRAVE_API_KEY, but current OpenClaw Brave handling does not consume the OpenShell secret-ref path end to end
  • this should be improved in the future so the Brave key does not need to be exposed in sandbox config

Verification

  • npm run build:cli
  • npm run typecheck:cli
  • npm test -- src/lib/web-search.test.ts test/onboard.test.js test/policies.test.js
  • npm test -- test/onboard.test.js

Summary by CodeRabbit

  • New Features

    • Brave Web Search integration in onboarding and sandbox creation; opt-in enables web search and optional fetch behavior.
    • Non-interactive setup can enable Brave Search via BRAVE_API_KEY environment variable.
  • Documentation

    • Added guidance and warnings about Brave API key usage and non-interactive examples.
  • Chores

    • Added a Brave policy preset and updated tests/fixtures to reflect web search configuration.
  • User Experience

    • Improved sandbox build/startup log formatting and progress output.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 3, 2026

📝 Walkthrough

Walkthrough

Adds Brave Web Search integration: Docker build argument propagation, base64 web-config encoding/decoding, onboarding prompts and session persistence for web search, sandbox Dockerfile injection, gateway config merge for tools.web when provider is brave, a new network preset, tests, and docs updates. (49 words)

Changes

Cohort / File(s) Summary
Dockerfile Build Configuration
Dockerfile
Add ARG NEMOCLAW_WEB_CONFIG_B64=e30= and ENV NEMOCLAW_WEB_CONFIG_B64=${NEMOCLAW_WEB_CONFIG_B64} so build-time web-config blob is available to container.
Web Search Module & Tests
src/lib/web-search.ts, src/lib/web-search.test.ts
New module: WebSearchConfig interface, BRAVE_API_KEY_ENV, encodeDockerJsonArg(), getBraveExposureWarningLines(), buildWebSearchDockerConfig() producing base64 payloads; unit tests added.
Onboarding Flow & Helpers
bin/lib/onboard.js
Collect/validate Brave API key, add configureWebSearch(), thread webSearchConfig into patchStagedDockerfile(...) and createSandbox(...), add streamGatewayStart() for streamed gateway logs, and include brave in policy suggestions when configured.
Session Schema & Redaction
src/lib/onboard-session.ts
Add webSearchConfig to Session/SessionUpdates, create/normalize/filter logic to persist only { fetchEnabled: true }, and extend redactSensitiveText() to redact BRAVE_API_KEY.
Sandbox Dockerfile Patching Tests
test/onboard.test.js
Update fixtures to include ARG NEMOCLAW_WEB_CONFIG_B64=e30=, add test for patched ARG when fetchEnabled: true, and expect createSandbox to receive webSearchConfig param.
Runner Output Control
bin/lib/runner.js
run()/runInteractive() skip writeRedactedResult() when opts.suppressOutput is truthy (silences redacted streaming).
Network Policy Preset
nemoclaw-blueprint/policies/presets/brave.yaml
Add brave preset: allows TLS (terminate) REST access to api.search.brave.com:443 with GET/POST, and lists allowed node binaries.
Docs & Installer Help
docs/reference/commands.md, scripts/install.sh
Document Brave Search opt-in, storage of API key in sandbox OpenClaw config, non-interactive BRAVE_API_KEY usage, and add usage/help text.
Tests: Presets & Env Handling
test/policies.test.js, test/usage-notice.test.js
Adjust preset count expectation to include brave; control NO_COLOR/TERM in usage notice test to ensure hyperlink rendering determinism.
Misc: Onboard logging & sandbox output
bin/lib/onboard.js (continued)
Replace raw console logs with width-aware printProgressLine() for phase/progress/heartbeat lines; gateway restart emits redacted diagnostic when suppressed output present.

Sequence Diagram

sequenceDiagram
    actor User
    participant CLI as Onboarding CLI
    participant Session as Session Manager
    participant WebSearch as WebSearch Module
    participant DockerPatcher as Dockerfile Patcher
    participant Sandbox as Sandbox Manager
    participant Gateway as Gateway Runtime

    User->>CLI: run onboarding (interactive or env BRAVE_API_KEY)
    CLI->>Session: createSession(overrides including webSearch)
    Session->>Session: validate/normalize webSearchConfig
    Session-->>CLI: webSearchConfig or null

    CLI->>WebSearch: buildWebSearchDockerConfig(webSearchConfig, BRAVE_API_KEY)
    WebSearch->>WebSearch: serialize JSON, Base64-encode
    WebSearch-->>CLI: NEMOCLAW_WEB_CONFIG_B64

    CLI->>DockerPatcher: patchStagedDockerfile(..., NEMOCLAW_WEB_CONFIG_B64)
    DockerPatcher->>DockerPatcher: inject ARG/ENV into Dockerfile
    DockerPatcher-->>CLI: patched Dockerfile

    CLI->>Sandbox: createSandbox(..., webSearchConfig)
    Sandbox->>Sandbox: validate BRAVE_API_KEY presence if enabled
    Sandbox->>Gateway: start container image

    Gateway->>Gateway: decode NEMOCLAW_WEB_CONFIG_B64
    Gateway->>Gateway: merge tools.web when provider == "brave"
    Gateway-->>User: sandbox ready (with Brave web search configured)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐰 I hopped a patch into the tree,

Brave keys packed safe in base64 glee,
Sessions remember, Dockerfiles sing,
Gateway wakes and policies spring,
A bunny's cheer for search set free! 🥕🔍

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly and clearly describes the main feature addition: Brave Search onboarding support using a BRAVE_API_KEY environment variable, which is the core change across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/brave-search-brave-api-key

Comment @coderabbitai help to get the list of available commands and usage tips.

@kjw3 kjw3 changed the title add Brave Search onboarding via BRAVE_API_KEY feat(onboard): add Brave Search onboarding via BRAVE_API_KEY Apr 4, 2026
@kjw3 kjw3 marked this pull request as ready for review April 4, 2026 00:48
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/lib/onboard-session.ts (1)

79-89: ⚠️ Potential issue | 🟡 Minor

webSearchConfig is still dropped by the safe-update path.

SessionUpdates now advertises webSearchConfig, but filterSafeUpdates() never copies it into the persisted session. Any caller that uses markStepComplete() or completeSession() with this field will silently lose it, while direct mutators behave differently. Please either wire it through there or remove it from SessionUpdates until the helper supports it.

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

In `@src/lib/onboard-session.ts` around lines 79 - 89, SessionUpdates declares
webSearchConfig but filterSafeUpdates does not copy it, causing callers like
markStepComplete and completeSession to silently drop webSearchConfig; update
filterSafeUpdates to include webSearchConfig in the set of allowed/whitelisted
fields copied into the persisted session (mirror how policyPresets/metadata are
handled) so any caller using markStepComplete/completeSession preserves
webSearchConfig, or alternatively remove webSearchConfig from SessionUpdates if
you prefer not to support it via filterSafeUpdates yet.
🧹 Nitpick comments (2)
docs/reference/commands.md (1)

76-76: Prefer present tense in the Brave credential-path sentence.

Consider rephrasing “explored” into present-tense wording to match docs voice rules.

As per coding guidelines: “Present tense.”

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

In `@docs/reference/commands.md` at line 76, The sentence "NemoClaw explored an
OpenShell-hosted credential path first, but the current OpenClaw Brave runtime
does not consume that path end to end yet." uses past tense; change "explored"
to present tense (for example, "explores") so it reads "NemoClaw explores an
OpenShell-hosted credential path first, but the current OpenClaw Brave runtime
does not consume that path end to end yet." to match the docs' present-tense
voice.
src/lib/web-search.test.ts (1)

12-25: Add a regression test for { fetchEnabled: false }.

This closes the branch where config exists but web search should still stay disabled.

Suggested test case
 describe("web-search helpers", () => {
   it("emits empty docker config when web search is disabled", () => {
     expect(Buffer.from(buildWebSearchDockerConfig(null, null), "base64").toString("utf8")).toBe(
       "{}",
     );
   });
+
+  it("emits empty docker config when fetchEnabled is false", () => {
+    expect(
+      Buffer.from(
+        buildWebSearchDockerConfig({ fetchEnabled: false }, "brv-x"),
+        "base64",
+      ).toString("utf8"),
+    ).toBe("{}");
+  });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/web-search.test.ts` around lines 12 - 25, Add a test to ensure
buildWebSearchDockerConfig treats an explicit { fetchEnabled: false } as
disabled: call buildWebSearchDockerConfig({ fetchEnabled: false }, null) (or
empty api key), base64-decode the result and assert the UTF-8 string equals "{}"
(same expectation as the existing "web search is disabled" test) to cover the
branch where a config object exists but web search remains off; use the test
name like "emits empty docker config when fetchEnabled is false" and reference
the buildWebSearchDockerConfig function in the new spec.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@bin/lib/onboard.js`:
- Around line 4030-4038: When webSearchConfig exists, do not reuse it blindly:
rehydrate and revalidate the Brave credential by calling
ensureValidatedBraveSearchCredential() (or equivalent) before treating
webSearchConfig as reusable; if validation fails or returns a refreshed key,
update webSearchConfig accordingly and call
onboardSession.updateSession((c)=>{c.webSearchConfig = webSearchConfig; return
c}); otherwise fall through to configureWebSearch(webSearchConfig) to obtain a
new config; ensure createSandbox() never receives a stale BRAVE_API_KEY by
performing this check in the branch that currently logs "[resume] Reusing Brave
Search configuration."
- Around line 2347-2351: runOpenshell is being called with suppressOutput: true
which hides all stdout/stderr on failure; change the call to capture its return
value (from runOpenshell(["gateway","start","--name", GATEWAY_NAME], {
ignoreError: true, env: getGatewayStartEnv(), suppressOutput: true })) then
inspect the returned exit code and on non-zero produce a trimmed, redacted
summary of stdout/stderr to the logger (e.g., processLogger.warn/error) so
successes remain quiet but failures emit a concise diagnostic; keep using
runOpenshell, getGatewayStartEnv and GATEWAY_NAME to locate the call and ensure
any output is sanitized/trimmed before logging.

In `@Dockerfile`:
- Around line 125-138: The code is writing the raw Brave API key into the
sandboxed config via config.update(...) (setting tools.web.search.apiKey from
web_config.get('apiKey')), which exposes the secret; instead remove embedding
the raw key into tools.web.search.apiKey and pass a host-side secret reference
or proxy token (e.g., set tools.web.search.secretRef or
tools.web.search.proxyEndpoint) so the sandbox only receives a reference; update
the logic around config.update and the web_config.get('apiKey') usage to
populate a non-secret identifier (secretRef/proxy URL) and ensure any code that
uses tools.web.search.apiKey is changed to resolve the secret from the host-side
secret store or call the proxied endpoint.
- Line 57: Replace the ARG-only declaration for NEMOCLAW_WEB_CONFIG_B64 with an
ENV declaration and stop performing inline shell expansion in RUN lines that use
it: keep ARG NEMOCLAW_WEB_CONFIG_B64 (if you need build-time default) and add
ENV NEMOCLAW_WEB_CONFIG_B64=${NEMOCLAW_WEB_CONFIG_B64}, then update the RUN
commands (the commands around the current RUN usage at lines ~85-95) to
reference the environment variable directly (e.g. use $NEMOCLAW_WEB_CONFIG_B64
or ${NEMOCLAW_WEB_CONFIG_B64} without embedding it into a quoted assignment like
NEMOCLAW_WEB_CONFIG_B64="${...}") so the value isn’t subject to shell command
evaluation during build.

In `@src/lib/web-search.ts`:
- Around line 25-31: The payload currently hardcodes fetchEnabled: true which
ignores the incoming config; update the payload construction in
src/lib/web-search.ts to set fetchEnabled based on config.fetchEnabled (e.g.,
use Boolean(config.fetchEnabled) or default to false when undefined) so the web
search respects the caller's config; keep provider: "brave" and apiKey:
braveApiKey || "" as-is and only change the fetchEnabled value in the payload
object.

In `@test/onboard.test.js`:
- Around line 243-265: Save the prior value of process.env.BRAVE_API_KEY at the
start of the test and restore it in the finally block instead of unconditionally
deleting it; specifically, before setting process.env.BRAVE_API_KEY =
"brv-test-key" capture const priorBraveKey = process.env.BRAVE_API_KEY, and in
the finally block set process.env.BRAVE_API_KEY = priorBraveKey (or delete it
only if priorBraveKey is undefined/null) so the test's use of
patchStagedDockerfile and assertions do not leak or clobber existing environment
state.

---

Outside diff comments:
In `@src/lib/onboard-session.ts`:
- Around line 79-89: SessionUpdates declares webSearchConfig but
filterSafeUpdates does not copy it, causing callers like markStepComplete and
completeSession to silently drop webSearchConfig; update filterSafeUpdates to
include webSearchConfig in the set of allowed/whitelisted fields copied into the
persisted session (mirror how policyPresets/metadata are handled) so any caller
using markStepComplete/completeSession preserves webSearchConfig, or
alternatively remove webSearchConfig from SessionUpdates if you prefer not to
support it via filterSafeUpdates yet.

---

Nitpick comments:
In `@docs/reference/commands.md`:
- Line 76: The sentence "NemoClaw explored an OpenShell-hosted credential path
first, but the current OpenClaw Brave runtime does not consume that path end to
end yet." uses past tense; change "explored" to present tense (for example,
"explores") so it reads "NemoClaw explores an OpenShell-hosted credential path
first, but the current OpenClaw Brave runtime does not consume that path end to
end yet." to match the docs' present-tense voice.

In `@src/lib/web-search.test.ts`:
- Around line 12-25: Add a test to ensure buildWebSearchDockerConfig treats an
explicit { fetchEnabled: false } as disabled: call buildWebSearchDockerConfig({
fetchEnabled: false }, null) (or empty api key), base64-decode the result and
assert the UTF-8 string equals "{}" (same expectation as the existing "web
search is disabled" test) to cover the branch where a config object exists but
web search remains off; use the test name like "emits empty docker config when
fetchEnabled is false" and reference the buildWebSearchDockerConfig function in
the new spec.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3f6923ec-c7e7-4ef0-8516-7e27d90cf6ec

📥 Commits

Reviewing files that changed from the base of the PR and between c3ee651 and 2c122ef.

📒 Files selected for processing (11)
  • Dockerfile
  • bin/lib/onboard.js
  • bin/lib/runner.js
  • docs/reference/commands.md
  • nemoclaw-blueprint/policies/presets/brave.yaml
  • scripts/install.sh
  • src/lib/onboard-session.ts
  • src/lib/web-search.test.ts
  • src/lib/web-search.ts
  • test/onboard.test.js
  • test/policies.test.js

Comment thread bin/lib/onboard.js Outdated
Comment thread bin/lib/onboard.js
Comment thread Dockerfile
Comment thread Dockerfile
Comment thread src/lib/web-search.ts Outdated
Comment on lines +25 to +31
if (!config) return encodeDockerJsonArg({});

const payload = {
provider: "brave",
fetchEnabled: true,
apiKey: braveApiKey || "",
};
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 | 🟡 Minor

Respect config.fetchEnabled instead of forcing true.

Current logic enables web config whenever config is non-null, even if fetchEnabled is false.

Suggested fix
 export function buildWebSearchDockerConfig(
   config: WebSearchConfig | null,
   braveApiKey: string | null,
 ): string {
-  if (!config) return encodeDockerJsonArg({});
+  if (!config || !config.fetchEnabled) return encodeDockerJsonArg({});
 
   const payload = {
     provider: "brave",
-    fetchEnabled: true,
-    apiKey: braveApiKey || "",
+    fetchEnabled: config.fetchEnabled,
+    apiKey: braveApiKey ?? "",
   };
   return encodeDockerJsonArg(payload);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (!config) return encodeDockerJsonArg({});
const payload = {
provider: "brave",
fetchEnabled: true,
apiKey: braveApiKey || "",
};
if (!config || !config.fetchEnabled) return encodeDockerJsonArg({});
const payload = {
provider: "brave",
fetchEnabled: config.fetchEnabled,
apiKey: braveApiKey ?? "",
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/lib/web-search.ts` around lines 25 - 31, The payload currently hardcodes
fetchEnabled: true which ignores the incoming config; update the payload
construction in src/lib/web-search.ts to set fetchEnabled based on
config.fetchEnabled (e.g., use Boolean(config.fetchEnabled) or default to false
when undefined) so the web search respects the caller's config; keep provider:
"brave" and apiKey: braveApiKey || "" as-is and only change the fetchEnabled
value in the payload object.

Comment thread test/onboard.test.js
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/lib/onboard-session.ts (1)

58-58: Include webSearchConfig in debug summaries for onboarding diagnostics.

Now that this field is persisted, omitting it from summarizeForDebug() makes troubleshooting harder.

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

In `@src/lib/onboard-session.ts` at line 58, summarizeForDebug() currently omits
the persisted webSearchConfig field; update the method in the OnboardSession (or
whichever class defines summarizeForDebug) to include webSearchConfig in its
returned debug summary (or string), handling null/undefined safely (e.g.,
include "none" or JSON.stringify/webSearchConfig as appropriate) and reference
the existing property name webSearchConfig so the debug output reflects its
current value.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/lib/onboard-session.ts`:
- Line 87: The session update path has a mismatch:
SessionUpdates.webSearchConfig can carry { fetchEnabled: false } to disable web
search but filterSafeUpdates() currently ignores that shape and leaves an
enabled session unchanged; update filterSafeUpdates (and any related validation
in onboard-session.ts) to recognize webSearchConfig presence even when
fetchEnabled is false and apply that update to the session (i.e., overwrite or
set webSearchConfig.fetchEnabled = false on the existing session). Specifically,
adjust logic that checks for webSearchConfig to treat a non-null object with
fetchEnabled:false as an explicit update (not as absent), handling the same for
the other occurrence referenced around lines 416-420 so toggling off works as
intended.

---

Nitpick comments:
In `@src/lib/onboard-session.ts`:
- Line 58: summarizeForDebug() currently omits the persisted webSearchConfig
field; update the method in the OnboardSession (or whichever class defines
summarizeForDebug) to include webSearchConfig in its returned debug summary (or
string), handling null/undefined safely (e.g., include "none" or
JSON.stringify/webSearchConfig as appropriate) and reference the existing
property name webSearchConfig so the debug output reflects its current value.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 06e55a13-849e-40d2-9319-c35e6ea7684f

📥 Commits

Reviewing files that changed from the base of the PR and between 2c122ef and ef20850.

📒 Files selected for processing (8)
  • Dockerfile
  • bin/lib/onboard.js
  • docs/reference/commands.md
  • src/lib/onboard-session.ts
  • src/lib/web-search.test.ts
  • src/lib/web-search.ts
  • test/onboard.test.js
  • test/usage-notice.test.js
✅ Files skipped from review due to trivial changes (2)
  • src/lib/web-search.test.ts
  • docs/reference/commands.md
🚧 Files skipped from review as they are similar to previous changes (3)
  • Dockerfile
  • src/lib/web-search.ts
  • bin/lib/onboard.js

credentialEnv?: string;
preferredInferenceApi?: string;
nimContainer?: string;
webSearchConfig?: WebSearchConfig | null;
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 | 🟡 Minor

webSearchConfig boolean contract is inconsistent in update flow.

SessionUpdates.webSearchConfig allows fetchEnabled: false, but filterSafeUpdates() ignores that shape instead of disabling config. If a session is already enabled, passing { fetchEnabled: false } leaves it enabled.

💡 Proposed fix
 export function filterSafeUpdates(updates: SessionUpdates): Partial<Session> {
   const safe: Partial<Session> = {};
   if (!isObject(updates)) return safe;
@@
-  if (isObject(updates.webSearchConfig) && updates.webSearchConfig.fetchEnabled === true) {
-    safe.webSearchConfig = { fetchEnabled: true };
-  } else if (updates.webSearchConfig === null) {
-    safe.webSearchConfig = null;
-  }
+  if (isObject(updates.webSearchConfig)) {
+    if (updates.webSearchConfig.fetchEnabled === true) {
+      safe.webSearchConfig = { fetchEnabled: true };
+    } else if (updates.webSearchConfig.fetchEnabled === false) {
+      safe.webSearchConfig = null;
+    }
+  } else if (updates.webSearchConfig === null) {
+    safe.webSearchConfig = null;
+  }

Also applies to: 416-420

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

In `@src/lib/onboard-session.ts` at line 87, The session update path has a
mismatch: SessionUpdates.webSearchConfig can carry { fetchEnabled: false } to
disable web search but filterSafeUpdates() currently ignores that shape and
leaves an enabled session unchanged; update filterSafeUpdates (and any related
validation in onboard-session.ts) to recognize webSearchConfig presence even
when fetchEnabled is false and apply that update to the session (i.e., overwrite
or set webSearchConfig.fetchEnabled = false on the existing session).
Specifically, adjust logic that checks for webSearchConfig to treat a non-null
object with fetchEnabled:false as an explicit update (not as absent), handling
the same for the other occurrence referenced around lines 416-420 so toggling
off works as intended.

@ericksoa ericksoa merged commit 3f4d6fe into main Apr 4, 2026
8 checks passed
@kjw3 kjw3 deleted the feat/brave-search-brave-api-key branch April 4, 2026 01:50
@wscurran wscurran added Getting Started Use this label to identify setup, installation, or onboarding issues. security Something isn't secure priority: high Important issue that should be resolved in the next release NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI). enhancement: feature Use this label to identify requests for new capabilities in NemoClaw. Integration: Brave labels Apr 4, 2026
gemini2026 pushed a commit to gemini2026/NemoClaw that referenced this pull request Apr 14, 2026
…1464)

## Summary
- add NemoClaw onboarding support for Brave web search via
`BRAVE_API_KEY`
- validate the Brave API key during onboarding and keep retry prompts
masked
- add a Brave policy preset and bake Brave web config into immutable
sandbox `openclaw.json`

## Issues
- addresses NVIDIA#773 by adding a supported NemoClaw path to enable
`web_search` without relying on `openclaw configure --section web`
- addresses NVIDIA#988 by moving Brave web-search setup into onboarding
instead of mutable in-sandbox provider switching
- addresses NVIDIA#518 for the Brave path by baking the Brave web config into
the sandbox image instead of relying on post-start config inheritance
- partially addresses NVIDIA#719 for web-search onboarding by providing a
first-class setup path despite immutable `openclaw.json`
- informed by NVIDIA#396, NVIDIA#1063, and NVIDIA#1252, but does not fully solve the
upstream trusted env-proxy DNS/runtime issues for all search providers

## Notes
- this implementation enables `web_search` and `web_fetch` in OpenClaw
config when `BRAVE_API_KEY` is set
- policy still controls which destinations are actually reachable from
the sandbox
- the Brave API key is exposed to the OpenClaw agent because it is
stored in sandbox-readable OpenClaw config

## Why this shape
- we explored using an OpenShell-hosted Brave credential first
- OpenShell can host `BRAVE_API_KEY`, but current OpenClaw Brave
handling does not consume the OpenShell secret-ref path end to end
- this should be improved in the future so the Brave key does not need
to be exposed in sandbox config

## Verification
- `npm run build:cli`
- `npm run typecheck:cli`
- `npm test -- src/lib/web-search.test.ts test/onboard.test.js
test/policies.test.js`
- `npm test -- test/onboard.test.js`

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Brave Web Search integration in onboarding and sandbox creation;
opt-in enables web search and optional fetch behavior.
* Non-interactive setup can enable Brave Search via BRAVE_API_KEY
environment variable.

* **Documentation**
* Added guidance and warnings about Brave API key usage and
non-interactive examples.

* **Chores**
* Added a Brave policy preset and updated tests/fixtures to reflect web
search configuration.

* **User Experience**
  * Improved sandbox build/startup log formatting and progress output.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Co-authored-by: Aaron Erickson 🦞 <aerickson@nvidia.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement: feature Use this label to identify requests for new capabilities in NemoClaw. Getting Started Use this label to identify setup, installation, or onboarding issues. Integration: Brave NemoClaw CLI Use this label to identify issues with the NemoClaw command-line interface (CLI). priority: high Important issue that should be resolved in the next release security Something isn't secure

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants