diff --git a/docs/get-started/quickstart.mdx b/docs/get-started/quickstart.mdx
index f182d956172..6c3ae76d7a9 100644
--- a/docs/get-started/quickstart.mdx
+++ b/docs/get-started/quickstart.mdx
@@ -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.
diff --git a/docs/inference/custom-endpoint-security.mdx b/docs/inference/custom-endpoint-security.mdx
index 8fdc60e3733..203eba7a3bf 100644
--- a/docs/inference/custom-endpoint-security.mdx
+++ b/docs/inference/custom-endpoint-security.mdx
@@ -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.
+
+
+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.
+
+
+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.
diff --git a/docs/manage-sandboxes/manage-messaging-channels.mdx b/docs/manage-sandboxes/manage-messaging-channels.mdx
index 6b90a7cf348..4c3098c9c2a 100644
--- a/docs/manage-sandboxes/manage-messaging-channels.mdx
+++ b/docs/manage-sandboxes/manage-messaging-channels.mdx
@@ -43,6 +43,7 @@ The next rebuild omits WeChat configuration and per-account state files.
`channels remove googlechat` detaches and deletes the `-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 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.
@@ -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.
When `channels start` re-enables a channel, NemoClaw reapplies the matching built-in policy preset before rebuild.
@@ -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.
-`$$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.
-`$$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.
diff --git a/docs/manage-sandboxes/set-up-google-chat.mdx b/docs/manage-sandboxes/set-up-google-chat.mdx
index cc973237cc2..b66a0587f95 100644
--- a/docs/manage-sandboxes/set-up-google-chat.mdx
+++ b/docs/manage-sandboxes/set-up-google-chat.mdx
@@ -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 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.
diff --git a/docs/network-policy/integration-policy-examples.mdx b/docs/network-policy/integration-policy-examples.mdx
index 499c2d3afe0..ad95f441de5 100644
--- a/docs/network-policy/integration-policy-examples.mdx
+++ b/docs/network-policy/integration-policy-examples.mdx
@@ -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 |
@@ -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
@@ -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.
diff --git a/docs/reference/commands.mdx b/docs/reference/commands.mdx
index 91ba7a6d790..598684ba8f2 100644
--- a/docs/reference/commands.mdx
+++ b/docs/reference/commands.mdx
@@ -2149,9 +2149,13 @@ $$nemoclaw my-assistant hosts-remove searxng.local
### `$$nemoclaw 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.
```bash
$$nemoclaw my-assistant channels list
@@ -2160,9 +2164,10 @@ $$nemoclaw my-assistant channels list
### `$$nemoclaw channels add `
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`.
@@ -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.
@@ -2212,7 +2220,7 @@ If you omit the required `` 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:
@@ -2237,7 +2245,9 @@ Host-side removal is the supported path because agent channel config is baked in
### `$$nemoclaw channels stop `
-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.
@@ -2254,6 +2264,8 @@ Use `channels stop` instead of `channels remove` when you want to pause a bridge
### `$$nemoclaw channels start `
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.
diff --git a/src/lib/actions/sandbox/policy-channel-agent-gate.test.ts b/src/lib/actions/sandbox/policy-channel-agent-gate.test.ts
index 5c36118284f..3b9f0a9e17a 100644
--- a/src/lib/actions/sandbox/policy-channel-agent-gate.test.ts
+++ b/src/lib/actions/sandbox/policy-channel-agent-gate.test.ts
@@ -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 {
@@ -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();
+ });
+});
diff --git a/src/lib/actions/sandbox/policy-channel-remove-flow.test.ts b/src/lib/actions/sandbox/policy-channel-remove-flow.test.ts
index c6931fb0f05..ed84d8bd5c7 100644
--- a/src/lib/actions/sandbox/policy-channel-remove-flow.test.ts
+++ b/src/lib/actions/sandbox/policy-channel-remove-flow.test.ts
@@ -42,17 +42,17 @@ describe("policy channel remove/enable flows", () => {
expect(exitSpy).not.toHaveBeenCalled();
});
- it("supports stop dry runs for configured channels", async () => {
- vi.spyOn(registry, "getSandbox").mockReturnValue({ name: "alpha" });
- vi.spyOn(registry, "getConfiguredMessagingChannelsFromEntry").mockReturnValue(["telegram"]);
+ it("supports stop dry runs for configured Hermes channels", async () => {
+ vi.spyOn(registry, "getSandbox").mockReturnValue({ name: "alpha", agent: "hermes" });
+ vi.spyOn(registry, "getConfiguredMessagingChannelsFromEntry").mockReturnValue(["teams"]);
vi.spyOn(registry, "getDisabledChannels").mockReturnValue([]);
await expect(
- stopSandboxChannel("alpha", { channel: "telegram", dryRun: true }),
+ stopSandboxChannel("alpha", { channel: "teams", dryRun: true }),
).resolves.toBeUndefined();
expect(logSpy.mock.calls.flat().join("\n")).toContain(
- "--dry-run: would stop channel 'telegram' for 'alpha'.",
+ "--dry-run: would stop channel 'teams' for 'alpha'.",
);
expect(exitSpy).not.toHaveBeenCalled();
});
diff --git a/src/lib/actions/sandbox/policy-channel.ts b/src/lib/actions/sandbox/policy-channel.ts
index ebad45040b4..1edb4737f6d 100644
--- a/src/lib/actions/sandbox/policy-channel.ts
+++ b/src/lib/actions/sandbox/policy-channel.ts
@@ -1731,10 +1731,10 @@ async function sandboxChannelsSetEnabled(
process.exit(1);
}
- const channel = getChannelDef(channelArg);
- if (!channel) {
+ const manifest = resolveChannelManifest(channelArg);
+ if (!manifest) {
console.error(` Unknown channel '${channelArg}'.`);
- console.error(` Valid channels: ${knownChannelNames().join(", ")}`);
+ console.error(` Valid channels: ${knownManifestChannelNames().join(", ")}`);
process.exit(1);
}
@@ -1744,30 +1744,45 @@ async function sandboxChannelsSetEnabled(
process.exit(1);
}
- const normalized = channelArg.trim().toLowerCase();
+ const canonical = manifest.id;
+ const agent = resolveAgentForSandbox(sandboxName);
+ const availableChannels = availableManifestChannelsForAgent(agent);
+ if (!availableChannels.some((candidate) => candidate.id === canonical)) {
+ console.error(
+ ` Channel '${canonical}' does not support agent '${agent.name}' for sandbox '${sandboxName}'.`,
+ );
+ console.error(
+ ` Channel-supported agents: ${formatSupportedMessagingAgentIds(manifest.supportedAgents)}.`,
+ );
+ console.error(
+ ` Channels supported by agent '${agent.name}': ${formatAvailableChannelsForAgent(agent)}.`,
+ );
+ process.exit(1);
+ }
+
const configuredChannels = registry.getConfiguredMessagingChannelsFromEntry(registryEntry);
- if (!configuredChannels.includes(normalized)) {
- console.error(` Channel '${normalized}' is not configured for '${sandboxName}'.`);
+ if (!configuredChannels.includes(canonical)) {
+ console.error(` Channel '${canonical}' is not configured for '${sandboxName}'.`);
process.exit(1);
}
- const alreadyDisabled = registry.getDisabledChannels(sandboxName).includes(normalized);
+ const alreadyDisabled = registry.getDisabledChannels(sandboxName).includes(canonical);
if (alreadyDisabled === disabled) {
console.log(
- ` Channel '${normalized}' is already ${disabled ? "disabled" : "enabled"} for '${sandboxName}'. Nothing to do.`,
+ ` Channel '${canonical}' is already ${disabled ? "disabled" : "enabled"} for '${sandboxName}'. Nothing to do.`,
);
return;
}
const disclosedPresetState = disabled
? undefined
- : loadValidateAndDiscloseChannelPreset(sandboxName, normalized, "start");
+ : loadValidateAndDiscloseChannelPreset(sandboxName, canonical, "start");
if (dryRun) {
- console.log(` --dry-run: would ${verb} channel '${normalized}' for '${sandboxName}'.`);
+ console.log(` --dry-run: would ${verb} channel '${canonical}' for '${sandboxName}'.`);
return;
}
- const plan = await persistManifestChannelDisabledPlan(sandboxName, normalized, disabled);
+ const plan = await persistManifestChannelDisabledPlan(sandboxName, canonical, disabled);
if (!plan) {
console.error(` Could not persist messaging plan for '${sandboxName}'.`);
process.exit(1);
@@ -1779,22 +1794,22 @@ async function sandboxChannelsSetEnabled(
// runtime configuration cannot later be rebuilt without the required egress.
if (
!disabled &&
- !applyChannelPresetIfAvailable(sandboxName, normalized, "start", {
+ !applyChannelPresetIfAvailable(sandboxName, canonical, "start", {
disclosedPresetState,
})
) {
- const rolledBack = await persistManifestChannelDisabledPlan(sandboxName, normalized, true);
+ const rolledBack = await persistManifestChannelDisabledPlan(sandboxName, canonical, true);
if (!rolledBack) {
console.error(
- ` ${YW}⚠${R} Could not restore '${normalized}' to disabled state after its policy preset failed to apply.`,
+ ` ${YW}⚠${R} Could not restore '${canonical}' to disabled state after its policy preset failed to apply.`,
);
- console.error(` Re-run: ${CLI_NAME} ${sandboxName} channels stop ${normalized}`);
+ console.error(` Re-run: ${CLI_NAME} ${sandboxName} channels stop ${canonical}`);
}
process.exit(1);
}
const state = disabled ? "disabled" : "enabled";
- console.log(` ${G}✓${R} Marked ${normalized} ${state} for '${sandboxName}'.`);
- const rebuilt = await promptAndRebuild(sandboxName, `${verb} '${normalized}'`);
+ console.log(` ${G}✓${R} Marked ${canonical} ${state} for '${sandboxName}'.`);
+ const rebuilt = await promptAndRebuild(sandboxName, `${verb} '${canonical}'`);
if (rebuilt && !disabled) {
ensureMessagingHostForwardAfterRebuild(sandboxName, plan);
}