Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
75 commits
Select commit Hold shift + click to select a range
79c9cf3
feat(messaging): add Google Chat to messaging channel
hunglp6d Jun 29, 2026
ddbb8c5
feat(messaging): unblock Google Chat replies in the proxy-only sandbox
hunglp6d Jun 30, 2026
fff319c
Merge branch 'main' of github.com:NVIDIA/NemoClaw into feat/messaging…
hunglp6d Jun 30, 2026
e4b7121
feat(messaging): gateway-mint Google Chat token, key out of sandbox
hunglp6d Jul 1, 2026
4b60e40
Merge branch 'main' of github.com:NVIDIA/NemoClaw into feat/messaging…
hunglp6d Jul 1, 2026
bd4f484
feat(messaging): route Google Chat googleapis fetches via trusted env…
hunglp6d Jul 1, 2026
2f04c46
Merge branch 'main' of github.com:NVIDIA/NemoClaw into feat/messaging…
hunglp6d Jul 1, 2026
953603b
feat(messaging): apply biome and keep onboard.ts net-neutral for Goog…
hunglp6d Jul 1, 2026
04399bf
feat(messaging): keep googlechat test updates within the size budget …
hunglp6d Jul 1, 2026
980a06f
Merge branch 'main' into feat/messaging-channel-googlechat
hunglp6d Jul 1, 2026
707e2e5
Merge branch 'main' into feat/messaging-channel-googlechat
hunglp6d Jul 1, 2026
63a4352
feat(messaging): drop unused in-sandbox secretFiles delivery subsystem
hunglp6d Jul 1, 2026
f6eb4d8
feat(messaging): explain the host-local argv exposure of the Google C…
hunglp6d Jul 2, 2026
c06f9d4
feat(messaging): harden Google Chat bridge onboarding (env resolution…
hunglp6d Jul 2, 2026
16d4a70
feat(messaging): drop the Google Chat in-process mint fallback + dead…
hunglp6d Jul 2, 2026
32dee76
feat(messaging): make the Google Chat start-gate marker an obvious se…
hunglp6d Jul 2, 2026
d8f0a11
feat(messaging): explain the broad www.googleapis.com cert egress in …
hunglp6d Jul 2, 2026
05a2209
feat(messaging): keep Google Chat hook files out of the eager side-ef…
hunglp6d Jul 2, 2026
9c2e59d
feat(messaging): unit-test the Google Chat runtime preloads and bridg…
hunglp6d Jul 2, 2026
ab9077e
Merge branch 'feat/messaging-channel-googlechat' of github.com:NVIDIA…
hunglp6d Jul 2, 2026
8c6b6d6
Merge branch 'main' into feat/messaging-channel-googlechat
hunglp6d Jul 2, 2026
174c853
feat(messaging): register the Google Chat rendered-config parser
hunglp6d Jul 2, 2026
360970c
feat(messaging): satisfy the private-key + no-if guardrails in Google…
hunglp6d Jul 2, 2026
e33b925
feat(messaging): disable gateway config hot-reload for Google Chat
hunglp6d Jul 2, 2026
2005f46
Merge branch 'main' into feat/messaging-channel-googlechat
hunglp6d Jul 2, 2026
5e656cd
feat(messaging): always skip Google Chat in non-interactive mode
hunglp6d Jul 2, 2026
f301d9e
feat(messaging): auto-seed Google Chat appPrincipal discovery sentinel
hunglp6d Jul 2, 2026
f95165e
feat(messaging): clarify Google Chat appPrincipal capture note
hunglp6d Jul 2, 2026
8d4b684
Merge branch 'main' into feat/messaging-channel-googlechat
hunglp6d Jul 2, 2026
a3efa0a
Merge branch 'main' into feat/messaging-channel-googlechat
hunglp6d Jul 3, 2026
bbce878
Merge branch 'main' into feat/messaging-channel-googlechat
hunglp6d Jul 3, 2026
31179dc
Merge branch 'main' into feat/messaging-channel-googlechat
hunglp6d Jul 3, 2026
52a20cc
Merge branch 'main' into feat/messaging-channel-googlechat
cv Jul 3, 2026
75de7c3
Merge branch 'main' into feat/messaging-channel-googlechat
hunglp6d Jul 3, 2026
b3dc203
feat(messaging): co-locate Google Chat policy and provider profile
hunglp6d Jul 3, 2026
ff984f4
feat(messaging): drive bridge provider setup generically from channel…
hunglp6d Jul 6, 2026
ac8a894
feat(messaging): drop the Google Chat webhookPath config input
hunglp6d Jul 6, 2026
20ec9e5
Merge branch 'main' into feat/messaging-channel-googlechat
hunglp6d Jul 6, 2026
8660bf2
feat(messaging): ratchet test-file-size budget after main merge
hunglp6d Jul 6, 2026
1261276
feat(messaging): pin Google Chat plugin archive integrity after main …
hunglp6d Jul 6, 2026
44b6ff5
feat(messaging): lazy-load tunnel services in the Google Chat enroll …
hunglp6d Jul 6, 2026
6176bd2
feat(messaging): match Google Chat runtime patches to the extension i…
hunglp6d Jul 6, 2026
facd28a
feat(messaging): fold bridge lifecycle notes into the ascii diagram
hunglp6d Jul 6, 2026
9ab6550
feat(messaging): correct bridge-provider lifecycle on channels add/re…
hunglp6d Jul 6, 2026
73ffcc7
feat(messaging): justify the broad www.googleapis.com cert egress
hunglp6d Jul 6, 2026
10cabf0
feat(messaging): explain the intentional cloudflared presence-probe c…
hunglp6d Jul 6, 2026
f2dbcfd
feat(messaging): reword the non-interactive Google Chat skip message
hunglp6d Jul 6, 2026
859eea5
feat(messaging): note the Google Chat preload rewrite brittleness + b…
hunglp6d Jul 6, 2026
8047eeb
feat(messaging): tie DEFAULT_WEBHOOK_PATH to the manifest webhookPath…
hunglp6d Jul 6, 2026
fe26f5f
feat(messaging): add Workaround Analysis blocks to the Google Chat wo…
hunglp6d Jul 6, 2026
6fab463
Merge branch 'main' into feat/messaging-channel-googlechat
hunglp6d Jul 6, 2026
3d5fec2
Merge branch 'main' into feat/messaging-channel-googlechat
hunglp6d Jul 6, 2026
7d761f3
feat(messaging): reconcile policy-channel imports after the main merge
hunglp6d Jul 6, 2026
c5ad1c6
feat(messaging): match the reworded non-interactive Google Chat skip …
hunglp6d Jul 6, 2026
3fd5cd3
Merge branch 'main' into feat/messaging-channel-googlechat
hunglp6d Jul 6, 2026
0652056
feat(messaging): resolve budget + preset-test conflicts from main
hunglp6d Jul 7, 2026
ac471cf
feat(messaging): apply biome
hunglp6d Jul 7, 2026
9a2b127
Merge branch 'main' into feat/messaging-channel-googlechat
hunglp6d Jul 7, 2026
164c15d
Merge branch 'main' into feat/messaging-channel-googlechat
hunglp6d Jul 7, 2026
07ceedc
Merge branch 'main' into feat/messaging-channel-googlechat
hunglp6d Jul 7, 2026
ed58bb8
Merge branch 'main' into feat/messaging-channel-googlechat
hunglp6d Jul 7, 2026
377b20f
feat(messaging): bridge lifecycle on channels add
hunglp6d Jul 8, 2026
0ceb180
Merge branch 'feat/messaging-channel-googlechat' of github.com:NVIDIA…
hunglp6d Jul 8, 2026
116f123
merge: resolve conflicts with main
cv Jul 14, 2026
8f80fec
fix(messaging): keep Google Chat private key off argv
cv Jul 14, 2026
a621c3c
docs(messaging): document Google Chat setup
cv Jul 14, 2026
4d8bc4e
fix(messaging): compose Google Chat runtime preloads
cv Jul 14, 2026
76e85d3
test(messaging): cover Google Chat security lifecycle
cv Jul 14, 2026
5a54d64
test(policy): preserve merged coverage attribution
cv Jul 14, 2026
dcb4c18
test(messaging): keep lifecycle setup linear
cv Jul 14, 2026
95d83f1
fix(googlechat): restrict public webhook tunnel
cv Jul 14, 2026
4e832e1
test(googlechat): keep proxy setup linear
cv Jul 14, 2026
3949836
test(googlechat): cover stop start lifecycle
cv Jul 14, 2026
abebc59
refactor(googlechat): type runtime patches
cv Jul 14, 2026
439331d
test(e2e): keep channel lifecycle visible
cv Jul 14, 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
1 change: 1 addition & 0 deletions ci/reviewed-npm-lifecycle-allowlist.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"@openclaw/brave-plugin@2026.6.10",
"@openclaw/diagnostics-otel@2026.6.10",
"@openclaw/discord@2026.6.10",
"@openclaw/googlechat@2026.6.10",
"@openclaw/msteams@2026.6.10",
"@openclaw/slack@2026.6.10",
"@openclaw/whatsapp@2026.6.10",
Expand Down
3 changes: 3 additions & 0 deletions docs/index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,9 @@ navigation:
- page: "Set Up Slack"
path: _build/agent-variants/manage-sandboxes/set-up-slack.openclaw.generated.mdx
slug: set-up-slack
- page: "Set Up Google Chat"
path: manage-sandboxes/set-up-google-chat.mdx
slug: set-up-google-chat
- page: "Set Up WeChat"
path: _build/agent-variants/manage-sandboxes/set-up-wechat.openclaw.generated.mdx
slug: set-up-wechat
Expand Down
15 changes: 15 additions & 0 deletions docs/manage-sandboxes/add-channels-after-onboarding.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,28 @@ $$nemoclaw my-assistant channels add whatsapp
$$nemoclaw my-assistant channels add teams
```

<AgentOnly variant="openclaw">
Add experimental Google Chat through its interactive enrollment flow:

```bash
$$nemoclaw my-assistant channels add googlechat
```

Refer to [Set Up Google Chat](set-up-google-chat) before running the command.
</AgentOnly>

`channels add` accepts mixed-case input such as `Telegram`, then stores and prints the canonical lowercase name.
It collects the inputs the channel needs, registers bridge providers with the OpenShell gateway when it captures tokens, records the channel in the sandbox registry, and asks whether to rebuild immediately.

Telegram, Discord, Slack, and Microsoft Teams prompt for credentials and configuration.
WeChat runs an interactive host-side QR scan.
WhatsApp collects no token because pairing happens inside the rebuilt sandbox.

<AgentOnly variant="openclaw">
Google Chat prompts for a service-account JSON key and pauses while you configure its public webhook endpoint.
It cannot be added non-interactively.
</AgentOnly>

## Apply Policy and Rebuild

`channels add` requires the matching built-in network policy preset YAML.
Expand Down
13 changes: 13 additions & 0 deletions docs/manage-sandboxes/enable-channels-during-onboarding.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,21 @@ keywords: ["nemoclaw onboard messaging", "messaging channel picker", "channel en
content:
type: "how_to"
---
import { AgentOnly } from "../_components/AgentGuide";

Enable channels during onboarding when you are creating or recreating a sandbox.

## Use the Interactive Picker

When the wizard reaches **Messaging channels**, it lists Telegram, Discord, Slack, WeChat, WhatsApp, and Microsoft Teams when the selected agent supports them.
Press a channel number to toggle it on or off, then press **Enter** when done.

<AgentOnly variant="openclaw">
The OpenClaw picker also lists experimental Google Chat.
Google Chat enrollment is interactive because you must set and confirm the public HTTP endpoint in Google Cloud Console.
Refer to [Set Up Google Chat](set-up-google-chat) before selecting it.
</AgentOnly>

If you select no channels, pressing **Enter** skips messaging setup.
If a token-based channel token is not already in the environment or credential store, the wizard prompts for it and saves it.

Expand Down Expand Up @@ -56,6 +64,11 @@ This release does not support non-interactive WeChat configuration because the i
Run `$$nemoclaw onboard` interactively when you want to enable WeChat.
For non-interactive WhatsApp selection, set `WHATSAPP_ALLOWED_IDS` to a nonempty comma-separated sender list.

<AgentOnly variant="openclaw">
Google Chat also requires interactive enrollment.
NemoClaw skips it in non-interactive mode even when you supply its environment variables.
</AgentOnly>

## Run Onboarding

```bash
Expand Down
16 changes: 16 additions & 0 deletions docs/manage-sandboxes/manage-messaging-channels.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ Rebuild the sandbox after the update so the image reflects the current channel s
For WeChat, the cached-token shortcut applies.
Remove WeChat first when you intend to acquire a fresh account through a new QR scan.

<AgentOnly variant="openclaw">
For Google Chat, re-add the channel and paste the replacement service-account JSON.
NemoClaw updates the gateway-side refresh material, and the sandbox continues to receive only gateway-minted access tokens.
</AgentOnly>

For detailed token rotation procedures, refer to [Credential Rotation](../../security/credential-rotation).

## Remove a Channel
Expand All @@ -36,6 +41,11 @@ $$nemoclaw my-assistant channels remove teams
`channels remove wechat` clears the bot token, deletes the `<sandbox>-wechat-bridge` provider, and removes `wechat` from the enabled-channel set.
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.
</AgentOnly>

For in-sandbox QR-paired channels such as WhatsApp, `channels remove` destructively clears the session directory before rebuild so stale auth files do not reconnect the channel.

<AgentOnly variant="openclaw">
Expand Down Expand Up @@ -76,6 +86,12 @@ For WeChat, `channels stop wechat` followed by rebuild keeps the per-account sta
A later `channels start wechat` plus rebuild revives the bridge against the same iLink account without a fresh QR scan.
The bot token remains in the OpenShell provider across the stop and start cycle.

<AgentOnly variant="openclaw">
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.
</AgentOnly>

When `channels start` re-enables a channel, NemoClaw reapplies the matching built-in policy preset before rebuild.
If policy restoration fails, the command keeps the channel disabled and exits without rebuilding into a partially active state.

Expand Down
14 changes: 10 additions & 4 deletions docs/manage-sandboxes/messaging-channels.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
# SPDX-License-Identifier: Apache-2.0
title: "Choose Messaging Channels"
sidebar-title: "Choose Messaging Channels"
description: "Compare the supported Telegram, Discord, Slack, WeChat, WhatsApp, and Microsoft Teams messaging paths."
description: "Compare the supported messaging paths, including experimental Google Chat for OpenClaw."
description-agent: "Compares supported and experimental messaging channels, their credential or pairing requirements, and their NemoClaw runtime boundaries. Use when choosing a channel for OpenClaw or Hermes."
keywords: ["nemoclaw messaging channels", "telegram", "discord", "slack", "wechat", "whatsapp", "microsoft teams"]
keywords: ["nemoclaw messaging channels", "telegram", "discord", "slack", "google chat", "wechat", "whatsapp", "microsoft teams"]
content:
type: "concept"
skill:
Expand All @@ -14,6 +14,7 @@ skill:
import { AgentOnly } from "../_components/AgentGuide";

NemoClaw supports Telegram, Discord, Slack, WeChat, WhatsApp, and Microsoft Teams for OpenClaw and Hermes sandboxes.
Google Chat is available for OpenClaw sandboxes only.
OpenShell-managed processes and gateway resources carry channel traffic.

For token-based channels, NemoClaw registers credentials with OpenShell providers.
Expand All @@ -25,10 +26,10 @@ Microsoft Teams uses Bot Framework credentials plus a public HTTPS webhook that
NemoClaw writes the selected channel configuration into the sandbox image and keeps runtime delivery under OpenShell control.

<Warning title="Experimental Channels">
WeChat, WhatsApp, and Microsoft Teams are experimental.
Google Chat, WeChat, WhatsApp, and Microsoft Teams are experimental.
WeChat and WhatsApp rely on QR-based pairing flows that are more fragile than token-based bots.

Microsoft Teams requires an externally reachable webhook path that depends on your host networking setup.
Google Chat and Microsoft Teams require externally reachable webhook paths that depend on your host networking setup.
Interfaces, defaults, and supported features can change, and NVIDIA does not recommend these channels for production use.
</Warning>

Expand All @@ -43,6 +44,11 @@ Interfaces, defaults, and supported features can change, and NVIDIA does not rec
| WhatsApp | In-sandbox QR pairing | Hermes sender allowlist and non-interactive channel selection | [Set Up WhatsApp](set-up-whatsapp) |
| Microsoft Teams | `MSTEAMS_APP_ID`, `MSTEAMS_APP_PASSWORD`, `MSTEAMS_TENANT_ID` | User allowlist, webhook port, mention mode | [Set Up Microsoft Teams](set-up-microsoft-teams) |

<AgentOnly variant="openclaw">
Google Chat requires a service-account JSON key and a public HTTPS endpoint for the OpenClaw webhook.
Refer to [Set Up Google Chat](set-up-google-chat) for the interactive tunnel, audience, and account-principal flow.
</AgentOnly>

## Prepare the Host and Policy

- Use a machine where you can run `$$nemoclaw onboard`.
Expand Down
112 changes: 112 additions & 0 deletions docs/manage-sandboxes/set-up-google-chat.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
---
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
title: "Set Up Google Chat"
sidebar-title: "Set Up Google Chat"
description: "Configure the experimental Google Chat webhook, service account, access controls, and OpenShell credential boundary for OpenClaw."
description-agent: "Explains the experimental OpenClaw-only Google Chat setup, including its interactive public webhook flow, service-account key, audience, DM allowlist, personal-account principal discovery, and gateway-minted outbound token. Use before enabling Google Chat."
keywords: ["nemoclaw google chat", "google chat api bot", "google chat webhook", "google chat service account"]
content:
type: "how_to"
---
Google Chat support is experimental and available only for OpenClaw sandboxes.
It receives events at `/googlechat` on the OpenClaw dashboard port and sends replies through the Google Chat API.

<Warning title="Webhook Only">
The automatic public endpoint accepts only `POST /googlechat` and denies dashboard, health, WebSocket, and other control paths.
Continue to open the Control UI through `http://127.0.0.1:18789`; the Google Chat URL is not a dashboard URL.
</Warning>

## Prepare the Google Cloud Configuration

Prepare the Google Cloud resources before you enable the channel.

- Enable the Google Chat API and configure the Chat app that the sandbox will run.
- Create a service account and download a JSON key.
- Minify the service-account JSON to one line before you paste it at the `GOOGLECHAT_SERVICE_ACCOUNT` prompt.
- Choose an HTTP endpoint connection in **Google Chat API** > **Configuration** > **Connection settings**.

Keep the JSON key available on the trusted host during enrollment.
Do not copy it into the sandbox or its OpenClaw configuration.

## Prepare the Public Webhook

The default `app-url` audience flow starts a dedicated cloudflared service in front of a loopback-only webhook proxy.
The proxy forwards only `POST /googlechat` to OpenClaw and limits webhook request bodies to 1 MiB.
It is separate from `nemoclaw tunnel start`, which remains the explicit full-dashboard tunnel command.
Install `cloudflared` on the host before enrollment when you do not already have a public endpoint.

When the dedicated Google Chat tunnel is not running, NemoClaw starts it and prints the exact HTTPS endpoint ending in `/googlechat`.
Copy that complete URL into the Google Chat API HTTP endpoint field without adding a trailing slash, then confirm the prompt.
NemoClaw stops the dedicated tunnel and webhook proxy if you do not confirm the endpoint or if enrollment fails before confirmation.

If `GOOGLECHAT_AUDIENCE` already contains the public webhook URL, NemoClaw uses it and does not start or change cloudflared.
That operator-managed endpoint must preserve `/googlechat` and deny every other dashboard or control path before forwarding to OpenClaw.

Google Chat enrollment always requires an interactive terminal.
NemoClaw skips the channel when `NEMOCLAW_NON_INTERACTIVE=1`, even if you provide the audience and service-account environment variables.

## Configure Access

Leave `GOOGLECHAT_ALLOWED_USERS` empty to use OpenClaw's manual DM pairing flow.
To use an allowlist, set it to comma-separated Google Chat user IDs such as `users/123456789`, not email addresses.
Google Chat email addresses do not match this ID-based allowlist.

Google Workspace accounts do not need `GOOGLECHAT_APP_PRINCIPAL`.
Leave that prompt empty unless you use a personal or standalone Google account.

## Enable Google Chat

For a new sandbox, run `nemoclaw onboard` and select Google Chat in the messaging picker.
For an existing OpenClaw sandbox, run:

```bash
nemoclaw my-assistant channels add googlechat
```

Follow the prompts to confirm the public endpoint, paste the service-account JSON, configure access, and rebuild the sandbox.
NemoClaw applies the `googlechat` network policy preset and registers a sandbox-scoped `<sandbox>-googlechat-bridge` provider with OpenShell.

OpenShell uses the service-account key as gateway-side refresh material to mint short-lived tokens with the `chat.bot` scope.
NemoClaw passes the private key to the OpenShell command through an ephemeral child-process environment value, not through a command-line argument.
The service-account private key does not enter the sandbox, and the OpenShell proxy inserts the minted bearer token into allowed requests to `chat.googleapis.com`.

The channel policy permits Node.js to read the Google Chat REST API and to create, update, or delete messages under the `/v1/spaces/` tree.
It also permits Node.js GET requests to any path on `www.googleapis.com` because the Google authentication library controls the public certificate URL used to verify inbound event tokens.

## Complete Personal Account Discovery

Personal or standalone Google accounts need the add-on's numeric app principal.
This value is a Google-assigned numeric ID, not an email address.

If you do not know the value, leave `GOOGLECHAT_APP_PRINCIPAL` empty during the first enrollment, then start the rebuilt sandbox and follow these steps:

1. Watch the OpenClaw logs for the discovery message.

```bash
nemoclaw my-assistant logs --follow | grep "unexpected add-on principal"
```

2. Send one direct message to the bot.
The bot does not reply during this discovery attempt.

3. Copy the numeric value from `unexpected add-on principal: <value>`.

4. Re-add the channel with the value and accept the rebuild prompt.

```bash
GOOGLECHAT_APP_PRINCIPAL="<numeric-id>" nemoclaw my-assistant channels add googlechat
```

The re-add flow prompts for the service-account JSON again when it is not already present in the current host environment.

## Verify the Channel

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.
24 changes: 24 additions & 0 deletions src/lib/actions/sandbox/destroy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export type CleanupSandboxServicesDeps = {
unloadOllamaModels?: () => void;
runOpenshell?: RunOpenshell;
rmSync?: typeof fs.rmSync;
stopGooglechatWebhookTunnel?: (sandboxName: string) => string;
};

type ShieldsTimerNeutralizeResult = {
Expand Down Expand Up @@ -137,6 +138,13 @@ export function cleanupSandboxServices(
return runtime.runOpenshell(args, opts);
});
const rmSync = deps.rmSync ?? fs.rmSync;
const stopGooglechatWebhookTunnel =
deps.stopGooglechatWebhookTunnel ??
((name: string) => {
const lifecycle =
require("../../tunnel/googlechat-webhook-lifecycle") as typeof import("../../tunnel/googlechat-webhook-lifecycle");
return lifecycle.stopGooglechatWebhookTunnel(name);
});

if (stopHostServices) {
// `stopAll()` already runs `unloadOllamaModels()` unconditionally —
Expand All @@ -152,6 +160,14 @@ export function cleanupSandboxServices(
}
}

let googlechatServicesPidDir = `${servicesPidDir}-googlechat`;
try {
googlechatServicesPidDir = stopGooglechatWebhookTunnel(validatedSandboxName);
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
console.warn(` ${YW}⚠${R} Failed to stop Google Chat webhook tunnel: ${message}`);
}

try {
rmSync(servicesPidDir, {
recursive: true,
Expand All @@ -160,6 +176,14 @@ export function cleanupSandboxServices(
} catch {
// PID directory may not exist — ignore.
}
try {
rmSync(googlechatServicesPidDir, {
recursive: true,
force: true,
});
} catch {
// Dedicated Google Chat service directory may not exist — ignore.
}

// Delete every per-sandbox messaging and search provider created during
// onboard. Suppress stderr so "! Provider not found" noise doesn't appear
Expand Down
6 changes: 6 additions & 0 deletions src/lib/actions/sandbox/policy-channel-dependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type LegacyOnboardProvidersModule = {
};

type RebuildModule = typeof import("./rebuild");
type GooglechatWebhookLifecycleModule = typeof import("../../tunnel/googlechat-webhook-lifecycle");

/**
* Injectable, late-bound boundary around provider registration and rebuild
Expand All @@ -47,4 +48,9 @@ export const policyChannelDependencies = {
const rebuild = require("./rebuild") as RebuildModule;
return rebuild.rebuildSandbox(sandboxName, args);
},
stopGooglechatWebhookTunnel(sandboxName: string): void {
const lifecycle =
require("../../tunnel/googlechat-webhook-lifecycle") as GooglechatWebhookLifecycleModule;
lifecycle.stopGooglechatWebhookTunnel(sandboxName);
},
};
Loading
Loading