feat(messaging): add Google Chat channel for OpenClaw - #6120
Conversation
|
Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually. Contributors can view more details about this message here. |
|
Important Review skippedDraft detected. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Enterprise Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds an experimental Google Chat channel for OpenClaw with manifest-driven enrollment, service-account token handling, public webhook audience setup, runtime safeguards, template resolution, documentation, and comprehensive tests. ChangesGoogle Chat channel manifest
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Operator
participant Enrollment
participant Tunnel
participant GoogleCloud
participant OpenClaw
Operator->>Enrollment: Add googlechat interactively
Enrollment->>Tunnel: Start or reuse public tunnel
Tunnel-->>Enrollment: Return webhook URL
Enrollment->>GoogleCloud: Confirm endpoint configuration
Operator->>Enrollment: Paste service-account JSON
Enrollment->>OpenClaw: Render channel and runtime configuration
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
PR Review Advisor (Nemotron Ultra) — Changes requestedMerge posture: Do not merge yet Action checklist
Findings index
🚨 Required before mergeAddress these before merging unless a maintainer explicitly overrides the advisor with rationale.
|
PR Review Advisor — InformationalAdvisor assessment: Informational / medium confidence Model lanes
Nemotron output stays in workflow artifacts and does not change the assessment above. E2E guidanceAdvisory only. E2E / PR Gate selects and runs jobs independently. Recommended E2E: 4 optional E2E recommendations
1 warning · 0 suggestionsWarningsWarnings do not block.
|
…le Chat bridge wiring
|
🌿 Preview your docs: https://nvidia-preview-pr-6120.docs.buildwithfern.com/nemoclaw |
|
Current-head update (
Merge prerequisite: #6726 must land first because current The pushed commits are signed and GitHub-verified. Please re-review the current head once CI completes. @coderabbitai review |
|
The PR description image shows a Google Chat conversation screenshot with a bot named "NemoClaw-GGChat". The user sends "Hello" and the bot replies "Hello Hung! How can I help you today?" (marked as edited). The user then sends "Reply exactly: PONG" and the bot responds "PONG" (marked as "App" and "Edited"). This appears to be a live end-to-end verification that the Google Chat channel is functioning correctly — inbound messages are received and outbound replies are delivered through the webhook.
Kicking off a fresh review of the current head now. ✅ Action performedReview finished.
|
|
The PR description image shows a mobile chat interface titled "NemoClaw-GGChat" with a bot avatar. It displays a conversation: the user sent "Hello" 35 minutes ago, and "NemoClaw-GGChat" replied "Hello Hung! How can I help you today?" (marked as edited). Then the user sent "Reply exactly: PONG" just now, and "NemoClaw-GGChat" (labeled "App") replied "PONG" (also marked as edited). This appears to be a live end-to-end demonstration of the Google Chat integration working — the bot is successfully receiving and responding to messages through the Google Chat channel, confirming the inbound/outbound webhook flow is functional.
One thing worth flagging explicitly for anyone tracking merge status: this PR is not yet mergeable in practice even with conflicts resolved, since The image showing the live Hello/PONG exchange through "NemoClaw-GGChat" is a nice concrete confirmation that the inbound/outbound webhook flow works end-to-end. I'll kick off a fresh review of the current head now. ✅ Action performedReview finished.
|
|
Security review for final head Overall verdict: WARNING (merge prerequisite), with no code-level security blocker found. This PR is ready for maintainer review after #6726 lands OpenShell 0.0.82; it should remain draft until that prerequisite is mergeable and merged.
Final verification: the signed commit is GitHub Verified and includes DCO. The main CI rollup has 42 passing and 5 skipped checks with no failures; all 11 authorized exact-diff live E2E jobs passed, including the 16-minute Hermes channel stop/start target: https://github.com/NVIDIA/NemoClaw/actions/runs/29335937019. Focused lifecycle tests, The canonical review-advisor ledger reports 0 blockers, 0 warnings, and 0 suggestions, with both Terra and Nemotron lanes completed successfully: #6120 (comment). No advisor follow-up is required. |
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
| import { | ||
| chmodSync, | ||
| closeSync, | ||
| constants, | ||
| existsSync, | ||
| fchmodSync, | ||
| mkdirSync, | ||
| openSync, | ||
| readFileSync, | ||
| rmSync, | ||
| writeFileSync, | ||
| } from "node:fs"; |
| import { | ||
| chmodSync, | ||
| closeSync, | ||
| constants, | ||
| existsSync, | ||
| fchmodSync, | ||
| mkdirSync, | ||
| openSync, | ||
| readFileSync, | ||
| rmSync, | ||
| writeFileSync, | ||
| } from "node:fs"; |
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Signed-off-by: Carlos Villela <cvillela@nvidia.com>
Both requested blockers are addressed on final Verified head 439331d: Google Chat provider lifecycle now covers add/remove/rollback, and service-account key transport uses --secret-material-env instead of argv. The canonical advisor rerun reports 0 blockers, 0 warnings, and 0 suggestions; exact-diff E2E is green.
<!-- markdownlint-disable MD041 --> ## Summary Adds Google Chat as an experimental OpenClaw messaging channel, served at `/googlechat` on the gateway through a dedicated Google-Chat-only cloudflared tunnel and a loopback webhook proxy. Outbound auth is minted gateway-side (OpenShell `google-service-account-jwt` provider) and injected by the L7 proxy, so the service-account private key never enters the sandbox. Inbound cert verification and outbound replies both route by hostname through the trusted L7 proxy (the SSRF guard's trusted-proxy modes), so the channel works in the DNS-less proxy-only sandbox — no local DNS resolve, no sentinel IP. **Scope: OpenClaw only.** Hermes uses a different inbound model (Cloud Pub/Sub pull, no webhook) and a different credential path, so Hermes Google Chat is a planned follow-up rather than part of this PR. Supersedes #6120: same change, recommitted as a single verified commit with clean history. ## Architecture Google Chat is the first bridge channel: inbound webhooks are verified inside the sandbox, but the outbound bot token is minted on the host and the service-account private key never enters the sandbox. Two JWTs run in opposite directions — Google signs one that the plugin **verifies** (inbound), and the gateway signs one to **mint** the outbound token (outbound). ```text INBOUND — Google → bot (Google signs a JWT; the plugin VERIFIES it) [GOOGLE] chat.googleapis.com — signs a JWT, POSTs the webhook │ ▼ [HOST] cloudflared (dedicated tunnel) │ ▼ [HOST] loopback webhook proxy — admits only POST /googlechat, 404s all else │ (the public URL is a webhook pinhole, not the dashboard) ▼ [HOST] OpenClaw gateway :18789 — routes /googlechat into the sandbox │ ▼ [SANDBOX] @openclaw/googlechat plugin │ VERIFY the JWT — fetch Google's certs (GET www.googleapis.com, out through the │ L7 proxy via trusted-proxy-fetch) → check signature + audience + appPrincipal ▼ [SANDBOX] agent handles the message OUTBOUND — bot → Google (the gateway GENERATES the token; the L7 proxy swaps it in — key never in the sandbox) [SANDBOX] @openclaw/googlechat plugin — builds the reply, stamps «Authorization: Bearer <placeholder>» │ outbound-auth put the placeholder there → the sandbox holds no key, no real token; │ trusted-proxy-fetch routes the send out through the L7 proxy (no local DNS) ▼ [HOST] L7 egress proxy │ ① swap «placeholder» → the real Bearer minted by the gateway: │ GENERATE — sign an SA-JWT with private_key → Google OAuth → access token (cached ~1h) │ ② policy-check — chat.googleapis.com, POST/PATCH/DELETE within /v1/spaces ▼ [GOOGLE] chat.googleapis.com — user sees the reply Note — trusted-proxy-fetch is ROUTING (every plugin→Google call goes via the L7 proxy, no local DNS), so it appears in both flows: the inbound cert fetch and the outbound send. The token swap, the outbound-auth placeholder, and the JWT verify/generate are each one-directional. ``` ## OpenShell Requirement The Google Chat service-account private key is configured through OpenShell's `--secret-material-env` ([OpenShell#2178](NVIDIA/OpenShell#2178)), which keeps it off process arguments. That flag ships in OpenShell v0.0.82+; current `main` pins OpenShell 0.0.85, so the requirement is already satisfied on `main` — no prerequisite PR. ## Related Issue Parts of #5492 ## Result <img width="582" height="800" alt="image" src="https://github.com/user-attachments/assets/02153f1f-ed55-4e62-9163-17271cacd895" /> ## Changes **Channel** - New `googlechat` manifest (inputs, enrollment notes, render) + built-in registration; served on the shared gateway dashboard port at `/googlechat` (no host forward — on its own dedicated tunnel, see below). - Enroll hooks: tunnel/audience gate (derives the public webhook URL from the cloudflared tunnel), token-paste for the SA JSON (validates the paste is parseable JSON and re-prompts before skip), config prompts (audience, appPrincipal, DM allowlist); channel template resolver + `nemoclaw tunnel` service wiring. - **Dedicated webhook tunnel.** A Google-Chat-only cloudflared tunnel (separate from `nemoclaw tunnel start`) fronts a loopback-only webhook proxy that forwards **only** `POST /googlechat` (1 MiB body cap) and returns 404 for every other method or path, so the public URL is a narrow webhook pinhole that never exposes the dashboard or control UI. NemoClaw tears down both the proxy and its cloudflared on skip, remove, and destroy. - Always skips in non-interactive mode (mirrors WeChat host-QR): enrollment needs manual, out-of-band Google Cloud Console + appPrincipal steps that no environment variable can satisfy, so the tunnel/audience gate skips rather than enroll a half-configured channel that silently 404s on inbound webhooks. - appPrincipal discovery sentinel: a blank appPrincipal renders an all-zeros placeholder so the first DM logs `unexpected add-on principal: <N>` (the real value to copy) instead of a numberless `missing add-on principal binding`; inert for Google Workspace accounts. Enrollment notes are rewritten into an always-printed capture guide with the exact `logs --follow` command and the persist-and-rebuild steps. **Gateway reliability** - Renders `gateway.reload.mode=off`. OpenClaw rewrites its own config ~60s after boot (auto-enabling default provider plugins); with reactive hot-reload on, that self-write rebuilds the gateway's HTTP route table and drops the Google Chat inbound webhook route, so `/googlechat` starts returning 404 and the bot goes silent ~60s after every start. The sandbox's `openclaw.json` is sealed at build time (0600 + integrity hash), so disabling reactive reload is safe; NemoClaw still restarts the gateway explicitly on rebuild / `gateway restart`. **Inbound + outbound routing (proxy-only sandbox)** - `googlechat-trusted-proxy-fetch` boot preload: rewrites the plugin's three googleapis fetch sites (inbound cert verify + all outbound sends/edits) to the SSRF guard's `trusted_env_proxy`/`trusted_explicit_proxy` modes, so they skip the local `getaddrinfo` and route by hostname through the L7 proxy — fixes `EAI_AGAIN` in the DNS-less netns, no sentinel IP. Interim; clean fix is upstream in OpenClaw (like web_fetch #50650). - `googlechat` network-policy preset scoped to the Chat spaces tree, including `PATCH` for streaming message edits. **Outbound auth (key out of sandbox)** - `google-chat-bridge` OpenShell provider profile + `googlechat-bridge-provider` onboard wiring: import the profile pre-create, run `provider refresh configure --strategy google-service-account-jwt` post-create from the pasted SA JSON — the gateway mints/rotates the token and the L7 proxy injects `Authorization: Bearer` on chat.googleapis.com. The private key is supplied through `--secret-material-env`, so it never reaches argv; the profile authorizes only the Node runtime for the injected bearer. - `googlechat-outbound-auth` boot preload: load-time rewrite of the plugin's single token producer to emit the OpenShell credential placeholder (revision-less alias, so it resolves to the latest re-minted token and survives rotation) instead of signing in-process. - Drops in-sandbox SA-key file delivery for outbound; `serviceAccountFile` is kept only as a channel-start-gate marker (never delivered or read); keeps the `BEGIN PRIVATE KEY` secret scan. **Documentation** - Adds OpenClaw-only setup, onboarding, lifecycle, and security guidance. The docs build completed with zero errors; two pre-existing warnings remain (redirect authentication and light-accent contrast). ## Type of Change - [ ] Code change (feature, bug fix, or refactor) - [x] Code change with doc updates - [ ] Doc only (prose changes, no code sample modifications) - [ ] Doc only (includes code sample changes) ## Quality Gates - [x] Tests added or updated for changed behavior — unit tests for the two boot preloads (anchor rewrite, idempotency, drift-throw, runtime short-circuit), the bridge provider (env resolution, fail-closed refresh, Node-only credential boundary), the tunnel/audience gate (unconditional non-interactive skip, audience does not bypass), the template resolver (appPrincipal discovery sentinel when blank), and a manifest runtime-security-contract test (both boot preloads present and non-optional, `gateway.reload.mode=off` rendered, the non-existent `serviceAccountFile` sentinel, and no credentials delivered into the sandbox) - [ ] Existing tests cover changed behavior — justification: - [ ] Tests not applicable — justification: - [x] Docs updated for user-facing behavior changes - [ ] Docs not applicable — justification: - [x] Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging) - [x] Sensitive-path review completed or maintainer-approved waiver recorded — [nine-category review on the predecessor #6120](#6120 (comment)); exact current-main delta review PASS at `8897acb3fbbffcfba59eb8ad27238e566d6292ad`, feature diff fingerprint `2badf49554a0bc79fbd104eb88d1f4dfba26ba6f9c52a0b848c0d191653e1e8b`. The sync changes no Google Chat feature behavior. - [ ] Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue: ## Documentation Writer Review - [x] Documentation writer subagent reviewed the completed changes - Result: `docs-updated` - Exact-head revalidation: `8897acb3fb`; current-main synced. The exact-head Google Chat suite passed 69/69 with repository checks and normal pre-push CLI/type/version gates; the unchanged feature diff previously passed 106 Google Chat CLI tests + 6 E2E-support tests, the standalone loader contract, and `npm run validate:pr`. Feature diff fingerprint: `2badf49554a0bc79fbd104eb88d1f4dfba26ba6f9c52a0b848c0d191653e1e8b`; nine-category security delta review PASS because the follow-up uses Vitest-tracked environment stubs only and the standalone E2E bridge retains the same sandbox-name and process-local capability boundaries. The prior exact-head docs build reported 0 errors and two pre-existing warnings; the later current-main sync only imported the already-reviewed release changelog and requires no Google Chat docs change. - Evidence: Reviewed the latest Google Chat doc changes — set-up-google-chat.mdx (agent-variant scope + non-interactive-skip wording), enable-channels-during-onboarding.mdx (non-interactive-skip wording + app-principal term consistency), and the NEMOCLAW_SKIP_GOOGLECHAT_TUNNEL row in docs/reference/commands.mdx. Accurate and style-compliant; `npm run docs:prepare` exits 0, markdownlint clean. No blocking findings. - Agent: Claude Code <!-- docs-review-head-sha: 8897acb --> <!-- docs-review-agents-blob-sha: c052d60 --> ## Verification - [x] PR description includes a `Signed-off-by:` line and every commit appears as `Verified` in GitHub - [x] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed, or `npm run check:diff` passed when hooks were skipped or unavailable - [x] Targeted behavior tests pass for the current change set, or tests are marked not applicable above - [ ] Applicable broad gate passed — `npm test` for broad runtime/test-harness changes; `npm run check` for repo-wide validation/coverage changes - [x] Quality Gates section completed with required justifications or waivers - [x] No secrets, API keys, or credentials committed - [ ] `npm run docs` builds without warnings (doc changes only) - [x] Doc pages follow the [style guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md) (doc changes only) - [x] New doc pages include SPDX header and frontmatter (new pages only) --- Signed-off-by: Hung Le <hple@nvidia.com> Signed-off-by: Carlos Villela <cvillela@nvidia.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added experimental Google Chat messaging for OpenClaw sandboxes. * Added interactive setup with service-account credentials, public HTTPS webhooks, audience controls, and user allowlists. * Added lifecycle controls for the dedicated webhook endpoint. * **Bug Fixes** * Improved cleanup reliability when webhook shutdown fails, preserving channel state for retry. * Improved fail-closed behavior for incomplete or invalid configuration. * **Documentation** * Added Google Chat setup and onboarding guidance. * **Security** * Added credential validation, masked secret entry, protected token handling, and restricted webhook routing. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Hung Le <hple@nvidia.com> Signed-off-by: Apurv Kumaria <akumaria@nvidia.com> Co-authored-by: Carlos Villela <cvillela@nvidia.com> Co-authored-by: Apurv Kumaria <akumaria@nvidia.com> Co-authored-by: San Dang <sdang@nvidia.com> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Senthil Ravichandran <senthilr@nvidia.com>
Summary
Adds Google Chat as a first-class OpenClaw messaging channel, served on the existing gateway webhook via the cloudflared tunnel. Outbound auth is minted gateway-side (OpenShell
google-service-account-jwtprovider) and injected by the L7 proxy, so the service-account private key never enters the sandbox. Inbound cert verification and outbound replies both route by hostname through the trusted L7 proxy (the SSRF guard's trusted-proxy modes), so the channel works in the DNS-less proxy-only sandbox — no local DNS resolve, no sentinel IP.Scope: OpenClaw only. Hermes uses a different inbound model (Cloud Pub/Sub pull, no webhook) and a different credential path, so Hermes Google Chat is a planned follow-up rather than part of this PR.
Merge Prerequisite
PR #6726 must land first. It upgrades NemoClaw to OpenShell 0.0.82, whose provider refresh secret-material environment support keeps the Google Chat service-account private key off process arguments. Current main pins OpenShell 0.0.72 and cannot safely configure the bridge.
Related Issue
Parts of #5492
Result
Changes
Channel
googlechatmanifest (inputs, enrollment notes, render) + built-in registration; served on the shared gateway dashboard port at/googlechat(no host forward — reuses the tunnel).nemoclaw tunnelservice wiring.unexpected add-on principal: <N>(the real value to copy) instead of a numberlessmissing add-on principal binding; inert for Google Workspace accounts. Enrollment notes are rewritten into an always-printed capture guide with the exactlogs --followcommand and the persist-and-rebuild steps.Gateway reliability
gateway.reload.mode=off. OpenClaw rewrites its own config ~60s after boot (auto-enabling default provider plugins); with reactive hot-reload on, that self-write rebuilds the gateway's HTTP route table and drops the Google Chat inbound webhook route, so/googlechatstarts returning 404 and the bot goes silent ~60s after every start. The sandbox'sopenclaw.jsonis sealed at build time (0600 + integrity hash), so disabling reactive reload is safe; NemoClaw still restarts the gateway explicitly on rebuild /gateway restart.Inbound + outbound routing (proxy-only sandbox)
googlechat-trusted-proxy-fetchboot preload: rewrites the plugin's three googleapis fetch sites (inbound cert verify + all outbound sends/edits) to the SSRF guard'strusted_env_proxy/trusted_explicit_proxymodes, so they skip the localgetaddrinfoand route by hostname through the L7 proxy — fixesEAI_AGAINin the DNS-less netns, no sentinel IP. Interim; clean fix is upstream in OpenClaw (like web_fetch #50650).googlechatnetwork-policy preset scoped to the Chat spaces tree, includingPATCHfor streaming message edits.Outbound auth (key out of sandbox)
google-chat-bridgeOpenShell provider profile +googlechat-bridge-provideronboard wiring: import the profile pre-create, runprovider refresh configure --strategy google-service-account-jwtpost-create from the pasted SA JSON — the gateway mints/rotates the token and the L7 proxy injectsAuthorization: Beareron chat.googleapis.com.googlechat-outbound-authboot preload: load-time rewrite of the plugin's single token producer to emit the OpenShell credential placeholder (revision-less alias, so it resolves to the latest re-minted token and survives rotation) instead of signing in-process.serviceAccountFileis kept only as a channel-start-gate marker (never delivered or read); keeps theBEGIN PRIVATE KEYsecret scan.Documentation
Type of Change
Quality Gates
Verification
Verifiedin GitHubnpx prek run --from-ref main --to-ref HEADpassesnpm testpasses (broad runtime changes only)npm run docsbuilds without warnings (doc changes only)Signed-off-by: Hung Le hple@nvidia.com
Signed-off-by: Carlos Villela cvillela@nvidia.com
Summary by CodeRabbit
Summary by CodeRabbit