feat(plugin): request network access through policy local#3746
feat(plugin): request network access through policy local#3746cheese-head wants to merge 4 commits into
Conversation
📝 WalkthroughWalkthroughThis PR implements NemoClaw's integration with OpenShell to enable agents to request external service access. It adds 10 provider profile configurations, an access client for OpenShell communication, three agent tools for resource access management, onboarding logic to import profiles, policy service wiring to synthesize presets from profiles, and comprehensive testing with e2e validation. ChangesNemoClaw OpenShell Integration
Sequence DiagramsequenceDiagram
participant Agent as NemoClaw Agent
participant Policy as NemoClaw Plugin<br/>(Tools)
participant AccessClient as Access Client<br/>(access-client.ts)
participant OpenShell as OpenShell<br/>policy.local
participant Sandbox as Agent Sandbox<br/>(HTTP)
Agent->>Policy: execute list_resource_access_presets
Policy->>AccessClient: listAccessPresets()
AccessClient->>OpenShell: GET /openshell/presets (via HTTP)
OpenShell->>AccessClient: built-in + provider presets
AccessClient->>Policy: AccessPresetsResponse
Policy->>Agent: tool result with preset list
Agent->>Policy: execute request_resource_access<br/>(resource, mode)
Policy->>AccessClient: createAccessRequest(preset, rules)
AccessClient->>Sandbox: POST /policy/propose (JSON body)
Sandbox->>OpenShell: forward proposal
OpenShell->>Sandbox: accepted_chunk_id
Sandbox->>AccessClient: response with chunk id
AccessClient->>Policy: pending_approval status
Policy->>AccessClient: waitAccessRequest(id)
loop poll every N seconds
AccessClient->>OpenShell: GET /proposal/{id}
end
OpenShell->>AccessClient: approved + policy_reloaded
AccessClient->>Policy: applied status
Policy->>Agent: tool result with applied status
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts
Warning There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure. 🔧 ESLint
ESLint skipped: no ESLint configuration detected in root package.json. To enable, add Comment |
|
✨ Thanks for submitting this detailed PR to add OpenClaw plugin support for requesting OpenShell policy.local network access from inside the sandbox. This change aims to improve the security and usability of the sandbox environment by giving agents a structured way to request network access. |
There was a problem hiding this comment.
Actionable comments posted: 8
🧹 Nitpick comments (3)
src/lib/onboard.ts (1)
9751-9751: Run the onboarding E2E matrix for this flow change before merge.This hook executes in the core onboarding path, so run the recommended targeted jobs (
cloud-e2e,sandbox-operations-e2e,rebuild-openclaw-e2e,channels-stop-start-e2e,messaging-compatible-endpoint-e2e,hermes-discord-e2e,hermes-slack-e2e,openshell-gateway-upgrade-e2e).As per coding guidelines,
src/lib/onboard.ts“contains core onboarding logic” and includes the listed “E2E test recommendation” suite.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/onboard.ts` at line 9751, The onboarding change adds a core hook call ensureProviderProfilesAvailable() in src/lib/onboard.ts so you must run the recommended targeted E2E matrix before merging: execute the cloud-e2e, sandbox-operations-e2e, rebuild-openclaw-e2e, channels-stop-start-e2e, messaging-compatible-endpoint-e2e, hermes-discord-e2e, hermes-slack-e2e and openshell-gateway-upgrade-e2e pipelines against this branch, confirm all pass (or capture/fix any failures), and attach the passing job artifacts to the PR; do not merge the change to ensureProviderProfilesAvailable() until those E2E jobs are green.docs/reference/nemoclaw-openshell-integration.md (2)
68-68: ⚡ Quick winUse one sentence per source line in prose sections.
Line 68 and Line 72 each contain multiple sentences on a single line.
As per coding guidelines "One sentence per line in source (makes diffs readable)."
Also applies to: 72-72
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/reference/nemoclaw-openshell-integration.md` at line 68, The prose line containing "Each agent adapter exposes the same tool names and response shape through the harness-native mechanism. OpenClaw uses its plugin API. Hermes uses its Python plugin API. Additional harnesses can implement the same contract without changing the OpenShell policy proposal flow." should be split so each sentence is on its own source line (one sentence per line); similarly split the sentence group at the other occurrence starting with "OpenClaw" so that every sentence occupies its own line to satisfy the "one sentence per line" guideline.
1-73: ⚡ Quick winAdd the required intro placement and a
Next Stepssection.The page starts directly with a diagram instead of the required 1–2 sentence introduction, and it does not end with a
Next Stepssection linking related pages.As per coding guidelines "Page starts with a one- or two-sentence introduction." and "A 'Next Steps' section at the bottom links to related pages."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@docs/reference/nemoclaw-openshell-integration.md` around lines 1 - 73, Add a 1–2 sentence intro above the mermaid diagram (locate the "# NemoClaw OpenShell Integration" header or the "```mermaid" block) that briefly summarizes what this page covers, and append a "Next Steps" section at the bottom (after the "Provider Profiles" paragraph) with links to related docs such as the agent tools and onboarding pages (eg. list_resource_access_presets, request_resource_access, check_resource_access, and the NemoClaw onboarding/import guidance) so readers can navigate to implementation and usage details.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/reference/nemoclaw-openshell-integration.md`:
- Around line 1-2: Add a YAML frontmatter block at the top of the document
containing the required fields (title, description, keywords, topics, tags,
content type, difficulty, audience, status) and then insert the SPDX license
header immediately after the closing frontmatter delimiter; ensure the SPDX
header uses the project's standard SPDX string and that all frontmatter keys
match the docs schema exactly.
In `@nemoclaw/src/access-client.ts`:
- Around line 305-310: cleanProviderEndpoint currently uses
Number(endpoint.port) <= 0 which lets non-numeric ports like "abc" slip through
(NaN comparison is false); update the validation in cleanProviderEndpoint to
parse and validate the port explicitly (e.g., const p = Number(endpoint.port)),
then check Number.isFinite(p) && Number.isInteger(p) && p > 0 (optionally also p
<= 65535) and return null if that fails, otherwise set output.port = p; keep
references to ProviderProfileEndpoint and NetworkEndpoint and ensure invalid
ports are rejected and not serialized.
- Line 578: The proxy target construction is hard-coding policy.local:80, which
ignores the configured policyLocalUrl host and port when HTTP_PROXY is enabled.
Update the target logic in access-client.ts to derive the proxy destination from
the existing policyLocalUrl configuration (reusing the same parsed host/port
used in the non-proxy path) instead of a fixed hostname and port, and keep the
pathname/requestPath concatenation behavior unchanged.
- Around line 396-400: The preset merge in the listProviderProfilePresets()
handling is preserving the built-in preset object on name collisions, which
keeps the old rule and drops other provider_profile fields. Update the collision
path in the byName.set logic so a provider profile with the same preset name
replaces the preset’s provider_profile data from the imported preset instead of
only copying provider_profile; ensure the rule selection also follows the
provider-profile rule when a collision occurs.
In `@nemoclaw/src/index.ts`:
- Around line 384-403: The normalizeRequestedResource function currently
canonicalizes only GitHub hosts and returns other hostnames verbatim, causing
request_resource_access to send preset ids that list_resource_access_presets
never returns; update normalizeRequestedResource to either (A) validate inputs
against a closed set of supported provider host mappings (e.g., map pypi.org ->
pypi, registry.npmjs.org -> npm, api.telegram.org -> telegram, github.com ->
github) and return a canonical preset id for those hosts, or (B) throw an error
for any host not in that supported mapping so callers like
request_resource_access reject non-preset inputs up front; implement the chosen
approach inside normalizeRequestedResource (and update any callers if switching
to throwing) so preset ids always match what list_resource_access_presets
produces.
In `@src/lib/onboard.ts`:
- Around line 1795-1805: Move the wrapper/reporting logic out of
ensureProviderProfilesAvailable in onboard.ts into the provider-profiles module:
create a new exported helper in the provider-profiles module that calls
providerProfileOnboard.ensureNemoClawProviderProfiles(runOpenshell, { log: note
}) and performs the status branches (logging the "unsupported" message or the
"already-present" skipped list) and then calls
policies.clearProviderProfileCache(); then update
ensureProviderProfilesAvailable to simply call and return that new exported
helper (or inline a single short delegating call) so onboard.ts loses the extra
lines while keeping behavior identical; ensure imports/exports are updated to
reference the new helper and preserve the same runtime logging by reusing the
same runOpenshell, note, and policies symbols.
In `@src/lib/onboard/provider-profiles.ts`:
- Around line 116-123: The code currently treats any non-zero result from the
`provider list-profiles` invocation (the `list` variable and its `status` check)
as "unsupported"; change this so you only return {status: "unsupported", ...}
when the command output actually matches the unsupported-command patterns
(inspect `list.stdout` / `list.stderr` for those specific messages), and for any
other non-zero exit throw an Error that includes `list.stdout` and `list.stderr`
so callers can see the runtime failure details; update the branch that handles
`list.status !== 0` to perform the pattern match and either return the
unsupported response or throw with stderr/stdout accordingly.
In `@test/e2e/test-nemoclaw-policy-local-plugin.sh`:
- Around line 25-28: Before enabling agent_policy_proposals_enabled, read and
save the current global value by invoking "${OPENSHELL_BIN} settings get
--global --key agent_policy_proposals_enabled" (or equivalent) into a variable,
then set the flag as you do now; in cleanup(), restore the saved value by
calling "${OPENSHELL_BIN} settings set --global --key
agent_policy_proposals_enabled --value <saved_value>" (or delete/unset it if the
saved value indicates it was absent), ensuring the saved variable is in a scope
accessible to cleanup() so the original global setting is always restored even
on early exit.
---
Nitpick comments:
In `@docs/reference/nemoclaw-openshell-integration.md`:
- Line 68: The prose line containing "Each agent adapter exposes the same tool
names and response shape through the harness-native mechanism. OpenClaw uses its
plugin API. Hermes uses its Python plugin API. Additional harnesses can
implement the same contract without changing the OpenShell policy proposal
flow." should be split so each sentence is on its own source line (one sentence
per line); similarly split the sentence group at the other occurrence starting
with "OpenClaw" so that every sentence occupies its own line to satisfy the "one
sentence per line" guideline.
- Around line 1-73: Add a 1–2 sentence intro above the mermaid diagram (locate
the "# NemoClaw OpenShell Integration" header or the "```mermaid" block) that
briefly summarizes what this page covers, and append a "Next Steps" section at
the bottom (after the "Provider Profiles" paragraph) with links to related docs
such as the agent tools and onboarding pages (eg. list_resource_access_presets,
request_resource_access, check_resource_access, and the NemoClaw
onboarding/import guidance) so readers can navigate to implementation and usage
details.
In `@src/lib/onboard.ts`:
- Line 9751: The onboarding change adds a core hook call
ensureProviderProfilesAvailable() in src/lib/onboard.ts so you must run the
recommended targeted E2E matrix before merging: execute the cloud-e2e,
sandbox-operations-e2e, rebuild-openclaw-e2e, channels-stop-start-e2e,
messaging-compatible-endpoint-e2e, hermes-discord-e2e, hermes-slack-e2e and
openshell-gateway-upgrade-e2e pipelines against this branch, confirm all pass
(or capture/fix any failures), and attach the passing job artifacts to the PR;
do not merge the change to ensureProviderProfilesAvailable() until those E2E
jobs are green.
🪄 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: Enterprise
Run ID: 28e5da0d-2364-4730-889a-72217ff1c00e
📒 Files selected for processing (22)
docs/reference/nemoclaw-openshell-integration.mdnemoclaw-blueprint/provider-profiles/brave.yamlnemoclaw-blueprint/provider-profiles/brew.yamlnemoclaw-blueprint/provider-profiles/discord.yamlnemoclaw-blueprint/provider-profiles/huggingface.yamlnemoclaw-blueprint/provider-profiles/jira.yamlnemoclaw-blueprint/provider-profiles/local-inference.yamlnemoclaw-blueprint/provider-profiles/npm.yamlnemoclaw-blueprint/provider-profiles/pypi.yamlnemoclaw-blueprint/provider-profiles/slack.yamlnemoclaw-blueprint/provider-profiles/telegram.yamlnemoclaw/src/access-client.test.tsnemoclaw/src/access-client.tsnemoclaw/src/index.tsnemoclaw/src/register.test.tssrc/lib/onboard.tssrc/lib/onboard/provider-profiles.tssrc/lib/policy/index.tstest/e2e/nemoclaw-policy-local-runner.mjstest/e2e/test-nemoclaw-policy-local-plugin.shtest/policies.test.tstest/provider-profile-onboard.test.ts
| # NemoClaw OpenShell Integration | ||
|
|
There was a problem hiding this comment.
Add required docs frontmatter and SPDX placement.
This new docs page is missing required frontmatter fields, and the SPDX header is not present after frontmatter as required for docs pages.
As per coding guidelines "SPDX license header is present after frontmatter." and "Frontmatter includes title, description, keywords, topics, tags, content type, difficulty, audience, and status fields."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@docs/reference/nemoclaw-openshell-integration.md` around lines 1 - 2, Add a
YAML frontmatter block at the top of the document containing the required fields
(title, description, keywords, topics, tags, content type, difficulty, audience,
status) and then insert the SPDX license header immediately after the closing
frontmatter delimiter; ensure the SPDX header uses the project's standard SPDX
string and that all frontmatter keys match the docs schema exactly.
| function cleanProviderEndpoint(endpoint: ProviderProfileEndpoint): NetworkEndpoint | null { | ||
| if (typeof endpoint.host !== "string" || Number(endpoint.port) <= 0) return null; | ||
| const output: NetworkEndpoint = { | ||
| host: endpoint.host, | ||
| port: Number(endpoint.port), | ||
| }; |
There was a problem hiding this comment.
Reject malformed provider-profile ports here.
Number(endpoint.port) <= 0 lets values like "abc" through because NaN <= 0 is false. That leaves an unusable endpoint in the preset list and serializes port as null when a proposal is submitted.
Suggested fix
function cleanProviderEndpoint(endpoint: ProviderProfileEndpoint): NetworkEndpoint | null {
- if (typeof endpoint.host !== "string" || Number(endpoint.port) <= 0) return null;
+ const port = typeof endpoint.port === "number" ? endpoint.port : Number(endpoint.port);
+ if (
+ typeof endpoint.host !== "string" ||
+ endpoint.host.trim() === "" ||
+ !Number.isInteger(port) ||
+ port < 1 ||
+ port > 65535
+ ) {
+ return null;
+ }
const output: NetworkEndpoint = {
host: endpoint.host,
- port: Number(endpoint.port),
+ port,
};📝 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.
| function cleanProviderEndpoint(endpoint: ProviderProfileEndpoint): NetworkEndpoint | null { | |
| if (typeof endpoint.host !== "string" || Number(endpoint.port) <= 0) return null; | |
| const output: NetworkEndpoint = { | |
| host: endpoint.host, | |
| port: Number(endpoint.port), | |
| }; | |
| function cleanProviderEndpoint(endpoint: ProviderProfileEndpoint): NetworkEndpoint | null { | |
| const port = typeof endpoint.port === "number" ? endpoint.port : Number(endpoint.port); | |
| if ( | |
| typeof endpoint.host !== "string" || | |
| endpoint.host.trim() === "" || | |
| !Number.isInteger(port) || | |
| port < 1 || | |
| port > 65535 | |
| ) { | |
| return null; | |
| } | |
| const output: NetworkEndpoint = { | |
| host: endpoint.host, | |
| port, | |
| }; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@nemoclaw/src/access-client.ts` around lines 305 - 310, cleanProviderEndpoint
currently uses Number(endpoint.port) <= 0 which lets non-numeric ports like
"abc" slip through (NaN comparison is false); update the validation in
cleanProviderEndpoint to parse and validate the port explicitly (e.g., const p =
Number(endpoint.port)), then check Number.isFinite(p) && Number.isInteger(p) &&
p > 0 (optionally also p <= 65535) and return null if that fails, otherwise set
output.port = p; keep references to ProviderProfileEndpoint and NetworkEndpoint
and ensure invalid ports are rejected and not serialized.
| for (const preset of listProviderProfilePresets()) { | ||
| const existing = byName.get(preset.name); | ||
| byName.set( | ||
| preset.name, | ||
| existing ? { ...existing, provider_profile: preset.provider_profile } : preset, |
There was a problem hiding this comment.
Use the provider-profile rule on preset name collisions.
When a provider profile reuses a built-in preset name, this merge keeps the baked-in rule and only copies provider_profile. That drops the imported profile’s endpoints, binaries, and extra endpoint fields, so proposals for common presets can drift from the OpenShell definition.
Suggested fix
for (const preset of listProviderProfilePresets()) {
const existing = byName.get(preset.name);
byName.set(
preset.name,
- existing ? { ...existing, provider_profile: preset.provider_profile } : preset,
+ existing
+ ? {
+ ...preset,
+ description: preset.description || existing.description,
+ }
+ : preset,
);
}📝 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.
| for (const preset of listProviderProfilePresets()) { | |
| const existing = byName.get(preset.name); | |
| byName.set( | |
| preset.name, | |
| existing ? { ...existing, provider_profile: preset.provider_profile } : preset, | |
| for (const preset of listProviderProfilePresets()) { | |
| const existing = byName.get(preset.name); | |
| byName.set( | |
| preset.name, | |
| existing | |
| ? { | |
| ...preset, | |
| description: preset.description || existing.description, | |
| } | |
| : preset, | |
| ); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@nemoclaw/src/access-client.ts` around lines 396 - 400, The preset merge in
the listProviderProfilePresets() handling is preserving the built-in preset
object on name collisions, which keeps the old rule and drops other
provider_profile fields. Update the collision path in the byName.set logic so a
provider profile with the same preset name replaces the preset’s
provider_profile data from the imported preset instead of only copying
provider_profile; ensure the rule selection also follows the provider-profile
rule when a collision occurs.
| return Promise.reject(new Error("HTTP proxy is not configured.")); | ||
| } | ||
|
|
||
| const target = `http://policy.local:80${base.pathname.replace(/\/$/, "")}${requestPath}`; |
There was a problem hiding this comment.
Honor the configured policyLocalUrl host and port in proxy mode.
The proxy request target is hard-coded to policy.local:80, so a custom policyLocalUrl port works without a proxy but breaks as soon as HTTP_PROXY is present.
Suggested fix
- const target = `http://policy.local:80${base.pathname.replace(/\/$/, "")}${requestPath}`;
+ const target = `${base.protocol}//${base.host}${base.pathname.replace(/\/$/, "")}${requestPath}`;📝 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.
| const target = `http://policy.local:80${base.pathname.replace(/\/$/, "")}${requestPath}`; | |
| const target = `${base.protocol}//${base.host}${base.pathname.replace(/\/$/, "")}${requestPath}`; |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@nemoclaw/src/access-client.ts` at line 578, The proxy target construction is
hard-coding policy.local:80, which ignores the configured policyLocalUrl host
and port when HTTP_PROXY is enabled. Update the target logic in access-client.ts
to derive the proxy destination from the existing policyLocalUrl configuration
(reusing the same parsed host/port used in the non-proxy path) instead of a
fixed hostname and port, and keep the pathname/requestPath concatenation
behavior unchanged.
| function normalizeRequestedResource(resource: string): string { | ||
| const normalized = resource.trim().toLowerCase(); | ||
| const normalizedHost = (() => { | ||
| if (!normalized.includes("://")) { | ||
| return normalized; | ||
| } | ||
| try { | ||
| return new URL(normalized).hostname.toLowerCase(); | ||
| } catch { | ||
| return normalized; | ||
| } | ||
| })(); | ||
| if ( | ||
| normalizedHost === "github" || | ||
| normalizedHost === "github.com" || | ||
| normalizedHost === "api.github.com" | ||
| ) { | ||
| return "github"; | ||
| } | ||
| return normalizedHost; |
There was a problem hiding this comment.
Don't silently accept non-GitHub hostnames as preset ids.
Only GitHub inputs are canonicalized here. Other common provider-backed hosts like pypi.org, registry.npmjs.org, or api.telegram.org fall through as raw hostnames, so request_resource_access can submit preset ids that list_resource_access_presets never returns. Either reject non-preset inputs up front or normalize all supported provider-backed hosts before building the request.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@nemoclaw/src/index.ts` around lines 384 - 403, The normalizeRequestedResource
function currently canonicalizes only GitHub hosts and returns other hostnames
verbatim, causing request_resource_access to send preset ids that
list_resource_access_presets never returns; update normalizeRequestedResource to
either (A) validate inputs against a closed set of supported provider host
mappings (e.g., map pypi.org -> pypi, registry.npmjs.org -> npm,
api.telegram.org -> telegram, github.com -> github) and return a canonical
preset id for those hosts, or (B) throw an error for any host not in that
supported mapping so callers like request_resource_access reject non-preset
inputs up front; implement the chosen approach inside normalizeRequestedResource
(and update any callers if switching to throwing) so preset ids always match
what list_resource_access_presets produces.
| function ensureProviderProfilesAvailable(): void { | ||
| const result = providerProfileOnboard.ensureNemoClawProviderProfiles(runOpenshell, { | ||
| log: note, | ||
| }); | ||
| if (result.status === "unsupported") { | ||
| note(` ${result.message}`); | ||
| } else if (result.status === "already-present" && result.skipped.length > 0) { | ||
| note(` NemoClaw provider profiles already registered: ${result.skipped.join(", ")}`); | ||
| } | ||
| policies.clearProviderProfileCache(); | ||
| } |
There was a problem hiding this comment.
Onboard entrypoint budget regression is currently blocking CI.
This helper adds net lines in src/lib/onboard.ts, and the onboard-entrypoint-budget check is failing (+16 lines). Please move this wrapper/reporting logic into src/lib/onboard/provider-profiles.ts (or otherwise reduce net lines in onboard.ts) so the budget gate passes.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/onboard.ts` around lines 1795 - 1805, Move the wrapper/reporting
logic out of ensureProviderProfilesAvailable in onboard.ts into the
provider-profiles module: create a new exported helper in the provider-profiles
module that calls
providerProfileOnboard.ensureNemoClawProviderProfiles(runOpenshell, { log: note
}) and performs the status branches (logging the "unsupported" message or the
"already-present" skipped list) and then calls
policies.clearProviderProfileCache(); then update
ensureProviderProfilesAvailable to simply call and return that new exported
helper (or inline a single short delegating call) so onboard.ts loses the extra
lines while keeping behavior identical; ensure imports/exports are updated to
reference the new helper and preserve the same runtime logging by reusing the
same runOpenshell, note, and policies symbols.
| if (list.status !== 0) { | ||
| return { | ||
| status: "unsupported", | ||
| imported: [], | ||
| skipped: [], | ||
| message: "OpenShell provider profiles are not available; using local preset fallbacks.", | ||
| }; | ||
| } |
There was a problem hiding this comment.
Don’t classify all list-profiles failures as unsupported.
A non-zero exit from provider list-profiles is currently always mapped to unsupported, which can hide transient/runtime failures and silently force fallback behavior. Only return unsupported when the output matches unsupported-command patterns; otherwise throw with stderr/stdout details.
Proposed fix
const list = runOpenshell(["provider", "list-profiles", "-o", "json"], {
ignoreError: true,
stdio: ["ignore", "pipe", "pipe"],
suppressOutput: true,
timeout: 10_000,
});
if (list.status !== 0) {
- return {
- status: "unsupported",
- imported: [],
- skipped: [],
- message: "OpenShell provider profiles are not available; using local preset fallbacks.",
- };
+ if (isUnsupportedProviderProfileCommand(list)) {
+ return {
+ status: "unsupported",
+ imported: [],
+ skipped: [],
+ message: "OpenShell provider profiles are not available; using local preset fallbacks.",
+ };
+ }
+ const details =
+ outputText(list.stderr) || outputText(list.stdout) || "provider list-profiles failed";
+ throw new Error(`NemoClaw provider profile listing failed: ${details.trim()}`);
}🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/lib/onboard/provider-profiles.ts` around lines 116 - 123, The code
currently treats any non-zero result from the `provider list-profiles`
invocation (the `list` variable and its `status` check) as "unsupported"; change
this so you only return {status: "unsupported", ...} when the command output
actually matches the unsupported-command patterns (inspect `list.stdout` /
`list.stderr` for those specific messages), and for any other non-zero exit
throw an Error that includes `list.stdout` and `list.stderr` so callers can see
the runtime failure details; update the branch that handles `list.status !== 0`
to perform the pattern match and either return the unsupported response or throw
with stderr/stdout accordingly.
| "${OPENSHELL_BIN}" settings set --global \ | ||
| --key agent_policy_proposals_enabled \ | ||
| --value true \ | ||
| --yes |
There was a problem hiding this comment.
Restore the previous global policy setting in cleanup().
This helper permanently mutates the caller's OpenShell config. If the script exits after enabling agent_policy_proposals_enabled, later local runs inherit that state unexpectedly. Please capture the existing value before settings set --global and restore it from cleanup().
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@test/e2e/test-nemoclaw-policy-local-plugin.sh` around lines 25 - 28, Before
enabling agent_policy_proposals_enabled, read and save the current global value
by invoking "${OPENSHELL_BIN} settings get --global --key
agent_policy_proposals_enabled" (or equivalent) into a variable, then set the
flag as you do now; in cleanup(), restore the saved value by calling
"${OPENSHELL_BIN} settings set --global --key agent_policy_proposals_enabled
--value <saved_value>" (or delete/unset it if the saved value indicates it was
absent), ensuring the saved variable is in a scope accessible to cleanup() so
the original global setting is always restored even on early exit.
cv
left a comment
There was a problem hiding this comment.
Reviewed with the PR review advisor rubric (static patch review only; not evaluating CI/mergeability). Requesting changes before merge:
- This PR includes the provider-profile layer from #3745, so the same blockers apply here:
src/lib/onboard/provider-profiles.ts:116should not classify everyprovider list-profilesnon-zero exit as unsupported, and provider endpoint ports need finite integer1..65535validation. nemoclaw/src/access-client.ts:306has the same port-validation issue in the in-sandbox OpenClaw client.Number(endpoint.port) <= 0letsNaNthrough and then serializes it at line 309 into policy.local proposals. Please validate finite integer ports, including an upper bound.nemoclaw/src/access-client.ts:578hardcodes proxy absolute-form requests tohttp://policy.local:80.... That ignoresOPENSHELL_POLICY_LOCAL_URLhost/port when proxy transport is used. Please derive the absolute target from the parsedpolicyLocalUrlwhile preserving the configured path prefix.nemoclaw/src/index.ts:384only normalizes GitHub URLs to preset ids. A resource likehttps://pypi.org/project/xbecomespypi.org, butlist_presetsreturnspypi. Please either require exact preset ids or map all supported provider hosts to their canonical preset ids before submitting proposals.nemoclaw/src/access-client.ts:398preserves fallback rules on provider-profile preset collisions. If provider profiles are the source of truth when available, the provider profile's rule should replace the fallback rule.
Please add tests for invalid provider-profile ports, custom OPENSHELL_POLICY_LOCAL_URL with HTTP proxy, and non-GitHub URL normalization/rejection.
Summary
Adds OpenClaw plugin support for requesting OpenShell policy.local network access from inside the sandbox. This gives agents a structured way to list provider-backed presets, submit least-privilege access proposals, and poll proposal status.
Changes
nemoclaw/src/access-client.tsfor policy.local proposal creation and status polling.nemoclaw/src/index.ts.Type of Change
Verification
npx prek run --all-filespassesnpm testpassesmake docsbuilds without warnings (doc changes only)Signed-off-by: Patrick Riel priel@nvidia.com
Summary by CodeRabbit
Release Notes
New Features
Documentation