Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
39cd4f7
docs: complete v0.0.101 post-tag audit follow-ups
miyoungc Aug 4, 2026
a76ecf9
Merge branch 'main' into codex/post-tag-docs-audit-20260804-114049
apurvvkumaria Aug 4, 2026
6fb65bd
merge: integrate current main
cv Aug 5, 2026
693da58
fix(messaging): gate channel lifecycle by agent (#8296)
senthilr-nv Aug 5, 2026
305b5f0
Merge branch 'main' into codex/post-tag-docs-audit-20260804-114049
senthilr-nv Aug 5, 2026
e0c511f
docs: correct channel and route recovery descriptions
cv Aug 5, 2026
68c980c
Merge branch 'main' into codex/post-tag-docs-audit-20260804-114049
senthilr-nv Aug 5, 2026
09b3672
merge: integrate contributor update
cv Aug 5, 2026
c9395b9
merge: integrate current main
cv Aug 5, 2026
fc6f6d7
docs: align channel lifecycle availability
cv Aug 5, 2026
92884da
merge: integrate PR 8255 update
cv Aug 5, 2026
215b957
merge: integrate current main
cv Aug 5, 2026
4531517
docs: add snapshot warning to route cleanup
cv Aug 5, 2026
d5a38cb
docs: clarify retained state after destroy
cv Aug 5, 2026
e2b79ac
docs(security): fix snapshot route
cv Aug 5, 2026
8ff4a9d
merge(main): refresh PR #8255
cv Aug 5, 2026
d9f40e9
merge(main): refresh PR #8255
cv Aug 5, 2026
88de00f
merge(main): refresh PR #8255
cv Aug 5, 2026
1e39bce
docs(security): clarify adapter cleanup
cv Aug 5, 2026
d3d4c05
merge(main): refresh PR #8255
cv Aug 5, 2026
e968715
docs(security): fix cleanup command variant
cv Aug 5, 2026
7b8fc75
Merge branch 'main' into codex/post-tag-docs-audit-20260804-114049
prekshivyas Aug 5, 2026
d6b5c3b
merge(pr): integrate concurrent #8255 refresh
cv Aug 5, 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
6 changes: 3 additions & 3 deletions docs/get-started/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -327,12 +327,12 @@ Use these details when your first-run path needs more control.
Changing or disabling web search requires re-running onboarding with the new selection and accepting sandbox recreation, or passing `--recreate-sandbox`.
NemoClaw backs up supported workspace state before recreation and restores it into the replacement sandbox.

The wizard also offers Telegram, Discord, Slack, WeChat, and WhatsApp.
The onboarding flow also offers Telegram, Discord, Slack, WeChat, WhatsApp, Microsoft Teams, and Google Chat.
Press a channel number to toggle it, then press Enter to continue.
Leave every channel unselected to skip messaging setup.
When you select a channel, NemoClaw validates the token format before it bakes the channel configuration into the sandbox.
When a channel accepts pasted credentials, NemoClaw validates the credential input before it builds the sandbox image.
For example, Slack bot tokens must start with `xoxb-`.
WeChat and WhatsApp are experimental.
WeChat, WhatsApp, Microsoft Teams, and Google Chat are experimental.
Refer to [Choose Messaging Channels](../manage-sandboxes/messaging-channels/choose-messaging-channels) before enabling them.

After the sandbox image builds and OpenClaw starts, NemoClaw asks which network policy tier to apply.
Expand Down
44 changes: 39 additions & 5 deletions docs/inference/custom-endpoint-security.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -72,11 +72,45 @@ Authenticated control health and route-registration requests remain loopback-onl
NemoClaw refuses to expose the adapter when it cannot discover a valid bridge subnet.
Adapter reuse also requires an authenticated health proof for the same source-subnet policy, so a running process with a stale or different policy is replaced.
After an adapter restart, routes other than the one that triggered recovery return a recovery-needed response until their original `inference set --endpoint-url` command is rerun.
Switching away from a route or destroying its last sandbox reference revokes it; a scoped uninstall that leaves sibling gateways in place preserves the shared adapter and its remaining routes.
Revocation has to authenticate the running adapter first, and the control-plane proof binds the source-subnet policy that adapter was started with, so NemoClaw records that policy in host recovery state when it starts or reuses the adapter and reads it back at revocation time.
It never re-derives the policy from the current host: a bridge that has since been recreated or renumbered would produce a value the running adapter never used.
An adapter started before NemoClaw recorded that policy cannot be authenticated for revocation, so the superseded route stays registered and `inference set` reports that its upstream credentials are still resident.
Rerun the `inference set --endpoint-url` command that registered the route: that records the policy for the running adapter, and the next switch revokes cleanly.

### Handle a Route Revocation Warning

Before revocation, NemoClaw checks whether another sandbox references the route.
If another sandbox references it, NemoClaw keeps the route.
If no other sandbox references the route, NemoClaw attempts revocation after it applies the new inference route or completes sandbox deletion.
If the reference check or revocation fails, NemoClaw does not restore the previous inference route or the deleted sandbox.
NemoClaw prints a warning because the upstream credential can remain in adapter memory.

Revocation must authenticate the running adapter.
The control-plane proof binds the source-subnet policy that the adapter used at startup.
NemoClaw records that policy in host recovery state when it starts or reuses the adapter, then reads it during revocation.
It does not derive the policy again from the current host because a recreated or renumbered bridge would produce a different value.

If an adapter started before NemoClaw recorded the policy, NemoClaw cannot authenticate the adapter for revocation.
NemoClaw preserves the route recovery state and reports the failed revocation.

If the sandbox still exists, recover the route as follows:

1. Rerun the `inference set --endpoint-url` command that registered the affected route.
2. Switch away from that route again.

The first command records the source-subnet policy for the running adapter.
NemoClaw retries revocation during the second command.
When no other sandbox references the route, a completed switch without a revocation warning confirms that the adapter removed it.

<Warning title="Create Snapshots Before Cleanup">
NemoClaw attempts to wipe manifest-defined agent state before it deletes a sandbox.
OpenShell can retain the per-name persistent volume after sandbox deletion.
If the wipe cannot complete, onboarding with the same name can make old files available again.
Do not rely on a retained volume as a backup.
Before you destroy a remaining sandbox, create a snapshot of any state that you must keep.
Refer to [Create and Restore Snapshots](../../manage-sandboxes/state-and-backups/create-and-restore-snapshots) for instructions.
</Warning>

If the affected sandbox was destroyed or revocation continues to fail, destroy all remaining sandboxes before adapter cleanup.
A scoped uninstall that leaves sibling gateways in place preserves the shared adapter and its remaining routes.
After you destroy all sandboxes, run `$$nemoclaw uninstall --all-gateway-ports` to remove every gateway-port environment and stop the shared adapter.
Do not treat its in-memory credentials as purged until the command succeeds, the adapter PID has exited, and no listener remains on `NEMOCLAW_HTTPS_PIN_RUNTIME_ADAPTER_PORT` (default `11438`).

This support is specific to `inference set` on an already-onboarded sandbox.
Hermes Provider setup, host-side `config set`, and a direct blueprint run still reject DNS-backed HTTPS URLs as described above.
Expand Down
7 changes: 5 additions & 2 deletions docs/manage-sandboxes/manage-messaging-channels.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ The next rebuild omits WeChat configuration and per-account state files.
<AgentOnly variant="openclaw">
`channels remove googlechat` detaches and deletes the `<sandbox>-googlechat-bridge` provider before the rebuild removes Google Chat configuration and the matching policy preset.
It also stops the dedicated cloudflared process and webhook proxy for that sandbox.
`$$nemoclaw <name> destroy` also stops both host-side processes.
If endpoint teardown fails, the command exits nonzero without changing the channel credentials, bridge provider, policy preset, or durable messaging plan so you can fix the host service and retry removal.
</AgentOnly>

Expand Down Expand Up @@ -90,6 +91,8 @@ The bot token remains in the OpenShell provider across the stop and start cycle.
Google Chat stop and start cycles also preserve the bridge provider and its gateway-side refresh material.
They preserve the dedicated public webhook endpoint so the Google Cloud configuration can keep the same URL.
The next rebuild reuses that provider without requiring the service-account JSON again.
`$$nemoclaw tunnel stop` does not stop the dedicated Google Chat endpoint.
It controls the separate full-dashboard tunnel.
</AgentOnly>

When `channels start` re-enables a channel, NemoClaw reapplies the matching built-in policy preset before rebuild.
Expand Down Expand Up @@ -129,14 +132,14 @@ Resolve any conflict, then rerun the operation.
Use `channels stop` for an individual bridge or stop the sandbox when you need to stop the complete supervised runtime.

<AgentOnly variant="openclaw">
`$$nemoclaw tunnel stop` stops host auxiliary services and asks NemoClaw to stop the OpenClaw gateway inside the selected sandbox.
`$$nemoclaw tunnel stop` stops the dashboard tunnel services that `$$nemoclaw tunnel start` created and asks NemoClaw to stop the OpenClaw gateway inside the selected sandbox.
The deprecated full `$$nemoclaw stop` command also attempts to release an unshared OpenShell gateway port whose ownership NemoClaw can verify.

Stopping the in-sandbox gateway stops all channel delivery for that sandbox until you restart the sandbox or gateway.
</AgentOnly>

<AgentOnly variant="hermes">
`$$nemoclaw tunnel stop` stops host auxiliary services without stopping the supervisor-owned Hermes gateway, agent-owned host forwards, or managed OpenShell gateway port.
`$$nemoclaw tunnel stop` stops the dashboard tunnel services that `$$nemoclaw tunnel start` created without stopping the supervisor-owned Hermes gateway, agent-owned host forwards, or managed OpenShell gateway port.
The deprecated full `$$nemoclaw stop` command attempts to stop host forwards and safely release an unshared OpenShell gateway port while the Hermes gateway remains under sandbox supervision.
</AgentOnly>

Expand Down
7 changes: 2 additions & 5 deletions docs/manage-sandboxes/set-up-google-chat.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,5 @@ The re-add flow prompts for the service-account JSON again when it is not alread
After the rebuild, send a direct message from an allowed or paired account and confirm that OpenClaw replies.
If the webhook returns an error, verify that the public endpoint still ends in `/googlechat`, the dedicated tunnel and webhook proxy are running, and the Google Chat API configuration contains the exact same URL.

Use the host-side `channels stop`, `channels start`, and `channels remove` commands to manage the integration.
`channels stop googlechat` leaves the dedicated endpoint running so `channels start googlechat` can keep the same Google Cloud endpoint URL.
`channels remove googlechat` and `nemoclaw <name> destroy` stop the dedicated cloudflared process and webhook proxy.
`nemoclaw tunnel stop` does not stop the dedicated Google Chat endpoint; it controls the separate full-dashboard tunnel.
Refer to [Manage Messaging Channels](manage-messaging-channels) for lifecycle behavior.
Refer to [Manage Messaging Channels](manage-messaging-channels) to stop, start, or remove Google Chat after setup.
That page explains how each command affects the bridge provider, service-account refresh material, and dedicated public endpoint.
7 changes: 5 additions & 2 deletions docs/network-policy/integration-policy-examples.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,11 @@ Messaging channel presets are scoped to the sandbox's active agent; if an agent
| Discord messaging | `discord` | OpenClaw and Hermes |
| GitHub and GitHub API | `github` | OpenClaw and Hermes |
| Gmail IMAP and SMTP | `gmail` | OpenClaw and Hermes |
| Google Chat messaging (experimental) | `googlechat` | OpenClaw |
| Hugging Face Hub and Inference API | `huggingface` | OpenClaw and Hermes |
| Jira and Atlassian Cloud | `jira` | OpenClaw and Hermes |
| Local Ollama or vLLM through the host gateway | `local-inference` | OpenClaw and Hermes |
| Microsoft Teams messaging (experimental) | `teams` | OpenClaw and Hermes |
| OpenClaw model-pricing reference fetch | `openclaw-pricing` | OpenClaw |
| npm and Yarn packages | `npm` | OpenClaw and Hermes |
| Microsoft 365, Outlook, and Graph API | `outlook` | OpenClaw and Hermes |
Expand Down Expand Up @@ -138,7 +140,8 @@ If delivery fails, open the TUI and send a test message to the bot:
openshell term
```

The matching preset for each supported messaging channel is the channel name (`telegram`, `discord`, `slack`, `wechat`, or `whatsapp`).
The matching preset uses the channel name: `telegram`, `discord`, `slack`, `wechat`, `whatsapp`, or `teams`.
OpenClaw also uses the `googlechat` preset.

## Slack or Discord Messaging

Expand Down Expand Up @@ -421,5 +424,5 @@ Use [Apply Policy Presets](configure-policies/apply-policy-presets) to merge mai
- [Approve or Deny Agent Network Requests](approve-network-requests) for the interactive OpenShell TUI flow.
- [Customize the Sandbox Network Policy](customize-network-policy) to choose the correct policy workflow.
- [Set Up Gmail With an App Password](set-up-gmail-with-an-app-password) for the full Gmail IMAP and SMTP workflow.
- [Choose Messaging Channels](../manage-sandboxes/messaging-channels/choose-messaging-channels) for Telegram, Discord, Slack, WeChat, WhatsApp, and Microsoft Teams configuration.
- [Choose Messaging Channels](../manage-sandboxes/messaging-channels/choose-messaging-channels) for Telegram, Discord, Slack, WeChat, WhatsApp, Microsoft Teams, and Google Chat configuration.
- [Commands](../reference/commands) for the full `policy get`, `policy add`, `policy list`, `policy remove`, and `channels` command reference.
26 changes: 19 additions & 7 deletions docs/reference/commands.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2149,9 +2149,13 @@ $$nemoclaw my-assistant hosts-remove searxng.local

### `$$nemoclaw <name> channels list`

List the messaging channels NemoClaw knows about (`telegram`, `discord`, `slack`, `wechat`, `whatsapp`) with a short description.
The command is a static reference; it does not consult credentials or the running sandbox.
WeChat and WhatsApp are experimental.
List the messaging channels supported by the sandbox's agent runtime with a short description.
Telegram, Discord, Slack, WeChat, WhatsApp, and Microsoft Teams are available for OpenClaw and Hermes.
WeChat, WhatsApp, and Microsoft Teams are experimental.
OpenClaw also supports experimental Google Chat.

The command reads the sandbox registry to select agent-compatible channel manifests.
It does not inspect messaging credentials or the live sandbox runtime.
Comment thread
coderabbitai[bot] marked this conversation as resolved.

```bash
$$nemoclaw my-assistant channels list
Expand All @@ -2160,9 +2164,10 @@ $$nemoclaw my-assistant channels list
### `$$nemoclaw <name> channels add <channel>`

Register a messaging channel with the sandbox and rebuild so the image picks up the new channel.
Channels fall into three login modes:
Channel enrollment uses these credential and pairing flows:

- **Token paste** (`telegram`, `discord`, `slack`): the command prompts for any missing token and registers it with the OpenShell gateway.
- **Token paste** (`telegram`, `discord`, `slack`, `teams`): the command prompts for required channel inputs.
It registers secret credentials with the OpenShell gateway and saves non-secret configuration for rebuilds.
- **Host-side QR** (`wechat`, experimental): the command renders an iLink QR code on the host and you scan it from WeChat on your phone.
On confirm, NemoClaw captures the bot token, registers it with the OpenShell gateway, and stores non-secret per-account metadata (`WECHAT_ACCOUNT_ID`, `WECHAT_BASE_URL`, `WECHAT_USER_ID`) for the in-sandbox bridge.
NemoClaw automatically adds the scanning operator's WeChat user ID to `WECHAT_ALLOWED_IDS`.
Expand All @@ -2172,6 +2177,9 @@ Channels fall into three login modes:
NemoClaw advertises WhatsApp for OpenClaw and Hermes sandboxes; after rebuild, run `openclaw channels login --channel whatsapp` for OpenClaw or `hermes whatsapp` for Hermes.
This intentionally leaves QR-created mutable session state in the sandbox until you unpair it or clear the durable agent state.

The OpenClaw-only Google Chat channel uses an experimental token-paste flow for the service-account JSON.
It also requires interactive public-webhook confirmation and prompts for optional app-principal configuration during enrollment.

After registering the channel, NemoClaw asks whether to rebuild immediately.
Running `add` for an already-configured channel overwrites the stored credentials where applicable.
The operation is idempotent.
Expand Down Expand Up @@ -2212,7 +2220,7 @@ If you omit the required `<channel>` argument, the CLI prints the `channels add
Clear the stored credentials for a messaging channel and rebuild the sandbox so the image drops the channel.
Running `remove` for a channel that was never configured is a no-op against the credentials file and still triggers the rebuild prompt.
When the bridge provider is attached to a live sandbox, NemoClaw detaches it before deleting the provider from the OpenShell gateway.
If the matching built-in policy preset is applied, such as `telegram`, `discord`, `slack`, `wechat`, or `whatsapp`, NemoClaw also removes that preset so the upstream API is no longer allow-listed after the channel is gone.
If the matching built-in policy preset is applied, NemoClaw also removes that preset so the upstream API is no longer allow-listed after the channel is gone.
NemoClaw also strips the channel from `session.policyPresets` so a subsequent `onboard --resume` does not re-apply the preset on the next rebuild.

For QR-paired channels (today: WhatsApp), NemoClaw destructively clears the in-sandbox session directory before the rebuild so the `state_dirs` backup does not restore the auth blob and let the channel reconnect:
Expand All @@ -2237,7 +2245,9 @@ Host-side removal is the supported path because agent channel config is baked in

### `$$nemoclaw <name> channels stop <channel>`

Pause a single messaging bridge (`telegram`, `discord`, `slack`, `wechat`, or `whatsapp`) without clearing its credentials.
Pause one configured messaging channel without clearing its credentials.
The command verifies that the sandbox's agent runtime supports the channel before reading configured or disabled channel state.
It then requires the channel to be configured for the sandbox.
The channel is marked disabled in the per-sandbox registry, and the sandbox is rebuilt so the onboard step skips registering the bridge with the gateway.
The provider stays registered with the OpenShell gateway, so a later `channels start` brings the bridge back without re-entering tokens.

Expand All @@ -2254,6 +2264,8 @@ Use `channels stop` instead of `channels remove` when you want to pause a bridge
### `$$nemoclaw <name> channels start <channel>`

Re-enable a channel previously paused with `channels stop`. The channel is removed from the disabled list, the sandbox is rebuilt, and the bridge registers with the gateway again using the stored credentials.
The command verifies that the sandbox's agent runtime supports the channel before reading configured or disabled channel state.
It then requires the channel to be configured for the sandbox.
Before the rebuild, NemoClaw reapplies the matching built-in network policy preset so the restored bridge has egress to its upstream API.
Before updating the disabled list or applying the policy, NemoClaw prints the exact effective egress scope when the preset would open or replace access, or reports that no new egress would be opened when the preset is already effective.
If policy restoration fails, NemoClaw rolls the channel back to disabled and exits without rebuilding into a partially active state.
Expand Down
41 changes: 40 additions & 1 deletion src/lib/actions/sandbox/policy-channel-agent-gate.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import * as defs from "../../agent/defs";
import * as store from "../../credentials/store";
import * as policy from "../../policy";
import * as registry from "../../state/registry";
import { addSandboxChannel } from "./policy-channel";
import { addSandboxChannel, startSandboxChannel, stopSandboxChannel } from "./policy-channel";
import { policyChannelDependencies } from "./policy-channel-dependencies";

function agentFixture(name: string): defs.AgentDefinition {
Expand Down Expand Up @@ -150,3 +150,42 @@ describe("addSandboxChannel agent gate", () => {
void logSpy;
});
});

describe("channel lifecycle agent gate", () => {
it.each([
["start", ["googlechat"], () => startSandboxChannel("da-test", { channel: "googlechat" })],
["stop", [], () => stopSandboxChannel("da-test", { channel: "googlechat" })],
])("rejects a stale channel during %s before reading channel state or mutating the sandbox", async (_verb, disabledChannels, run) => {
getSandboxMock.mockReturnValue({ name: "da-test", agent: "hermes" });
vi.spyOn(defs, "loadAgent").mockReturnValue(agentFixture("hermes"));
const configuredChannelsMock = vi
.spyOn(registry, "getConfiguredMessagingChannelsFromEntry")
.mockReturnValue(["googlechat"]);
const disabledChannelsMock = vi
.spyOn(registry, "getDisabledChannels")
.mockReturnValue(disabledChannels);

let caught: unknown;
try {
await run();
} catch (err) {
caught = err;
}

expect(exitCodeFromError(caught)).toBe(1);
const errorText = (errSpy.mock.calls as unknown[][])
.map((call) => call.map(String).join(" "))
.join("\n");
expect(errorText).toMatch(/Channel 'googlechat' does not support agent 'hermes'/);
expect(errorText).toMatch(/Channel-supported agents: openclaw/);
expect(errorText).toMatch(/Channels supported by agent 'hermes':/);

expect(configuredChannelsMock).not.toHaveBeenCalled();
expect(disabledChannelsMock).not.toHaveBeenCalled();
expect(loadPresetForSandboxMock).not.toHaveBeenCalled();
expect(applyPresetMock).not.toHaveBeenCalled();
expect(updateSandboxMock).not.toHaveBeenCalled();
expect(rebuildMock).not.toHaveBeenCalled();
expect(runOpenshellMock).not.toHaveBeenCalled();
});
});
Loading
Loading