feat(messaging): add Google Chat channel - #7317
Conversation
Adds a manifest-first Google Chat channel: inbound webhook via the public tunnel, and outbound replies authenticated by a gateway-minted service-account token. The private key is configured through OpenShell's --secret-material-env, so it never appears in argv and never enters the sandbox. Includes the network policy preset, appPrincipal capture flow for personal/standalone accounts, DM allowlist, and tests. Co-authored-by: Carlos Villela <cvillela@nvidia.com> Signed-off-by: Hung Le <hple@nvidia.com>
|
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:
📝 WalkthroughWalkthroughThis PR adds experimental Google Chat messaging support for OpenClaw sandboxes. It adds channel configuration, webhook enrollment, bridge-provider token minting, runtime patches, lifecycle handling, documentation, CI updates, and E2E coverage. ChangesGoogle Chat Channel Integration
Estimated code review effort: 5 (Critical) | ~120 minutes Sequence Diagram(s)sequenceDiagram
participant Onboarding
participant AudienceGateHook
participant TunnelRuntime
participant Cloudflared
participant WebhookProxy
Onboarding->>AudienceGateHook: enroll googlechat
AudienceGateHook->>TunnelRuntime: start sandbox-scoped tunnel
TunnelRuntime->>Cloudflared: start public endpoint
TunnelRuntime->>WebhookProxy: start /googlechat proxy
WebhookProxy-->>TunnelRuntime: report readiness
TunnelRuntime-->>AudienceGateHook: return audience URL
AudienceGateHook-->>Onboarding: persist googlechat audience
sequenceDiagram
participant MessagingPrep
participant BridgeProvider
participant OpenShell
participant Gateway
MessagingPrep->>BridgeProvider: collect bridge token definitions
BridgeProvider-->>MessagingPrep: return pending token definitions
BridgeProvider->>OpenShell: import provider profile
BridgeProvider->>OpenShell: configure refresh material through env
OpenShell->>Gateway: mint Google Chat access token
Possibly related PRs
Suggested labels: Suggested reviewers: 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Code Coverage OverviewLanguages: TypeScript TypeScript / code-coverage/pluginThe overall coverage in commit a5ac963 in the TypeScript / code-coverage/cliThe overall coverage in commit a5ac963 in the Show a code coverage summary of the most impacted files.
Updated |
PR Review Advisor — No blocking findings reportedAdvisor assessment: No blocking advisor findings reported Model lanes
3 additional E2E selections from the second opinionAdvisory only. The primary lane did not select these E2E jobs or targets.
Second-opinion E2E selections are advisory. They do not change the primary assessment or E2E / PR Gate. E2E guidanceAdvisory only. E2E / PR Gate selects and runs jobs independently. Recommended E2E: 4 optional E2E recommendations
1 warning · 0 suggestionsWarningsWarnings do not block.
|
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (1)
src/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.ts (1)
225-255: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the shared CommonJS/ESM source-rewrite loader infrastructure into one module.
isOpenClawGooglechatFile,errorMessage,fileNameFromModuleUrl,sourceToText,createComposableJsLoader, and theinstallXPatchshape are duplicated near-verbatim between these two files (and reportedly a third,slack-channel-guard.ts, per the in-file comments). OnlypatchSource/the anchor regexes actually differ per patch.
src/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.ts#L225-L255: keep onlypatchGooglechatOutboundAuthSourcehere; delegatecreateComposableJsLoader,sourceToText,fileNameFromModuleUrl,errorMessage, and the file-matcher to a shared helper module (parameterized by channel name/path fragment).src/lib/messaging/channels/googlechat/runtime/googlechat-trusted-proxy-fetch.ts#L245-L275: same — keep onlypatchTrustedProxyFetchSourceand delegate the rest to the shared helper.This keeps the two channel-specific files focused on their actual patch logic and avoids a third drifted copy the next time a channel needs this pattern.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@src/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.ts` around lines 225 - 255, Extract the duplicated CommonJS/ESM rewrite infrastructure into a shared helper module, parameterized by channel name/path fragment and preserving the existing installXPatch shape. In src/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.ts lines 225-255, retain only patchGooglechatOutboundAuthSource and delegate createComposableJsLoader, sourceToText, fileNameFromModuleUrl, errorMessage, and isOpenClawGooglechatFile; apply the same change in src/lib/messaging/channels/googlechat/runtime/googlechat-trusted-proxy-fetch.ts lines 245-275, retaining only patchTrustedProxyFetchSource and delegating the shared helpers.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@docs/manage-sandboxes/enable-channels-during-onboarding.mdx`:
- Line 12: Remove the runtime AgentOnly import from the onboarding documentation
while preserving any build-time AgentOnly directive usage.
In `@src/lib/messaging/channels/googlechat/manifest.ts`:
- Around line 106-117: Update the explanatory comment above credentials in the
manifest to reference the actual messaging bridge provider implementation at
src/lib/onboard/messaging-bridge-provider.ts instead of the nonexistent
googlechat-bridge-provider.ts path; leave the surrounding authentication
description unchanged.
In `@src/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.ts`:
- Around line 264-281: Gate both Module.registerHooks loaders in
src/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.ts:264-281
and
src/lib/messaging/channels/googlechat/runtime/googlechat-trusted-proxy-fetch.ts:284-301
on a Node version known to support synchronous hooks, skipping them for Node
22.19.0 through 22.22.2, or raise the repository minimum to a fixed release.
Ensure affected versions never invoke nextLoad for unrelated module loads.
---
Nitpick comments:
In `@src/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.ts`:
- Around line 225-255: Extract the duplicated CommonJS/ESM rewrite
infrastructure into a shared helper module, parameterized by channel name/path
fragment and preserving the existing installXPatch shape. In
src/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.ts lines
225-255, retain only patchGooglechatOutboundAuthSource and delegate
createComposableJsLoader, sourceToText, fileNameFromModuleUrl, errorMessage, and
isOpenClawGooglechatFile; apply the same change in
src/lib/messaging/channels/googlechat/runtime/googlechat-trusted-proxy-fetch.ts
lines 245-275, retaining only patchTrustedProxyFetchSource and delegating the
shared helpers.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Enterprise
Run ID: 22d36853-0225-44c0-84fd-db82510b6b78
📒 Files selected for processing (56)
ci/reviewed-npm-lifecycle-allowlist.jsondocs/index.ymldocs/manage-sandboxes/add-channels-after-onboarding.mdxdocs/manage-sandboxes/enable-channels-during-onboarding.mdxdocs/manage-sandboxes/manage-messaging-channels.mdxdocs/manage-sandboxes/messaging-channels.mdxdocs/manage-sandboxes/set-up-google-chat.mdxsrc/lib/actions/sandbox/destroy.tssrc/lib/actions/sandbox/policy-channel-dependencies.tssrc/lib/actions/sandbox/policy-channel.tssrc/lib/messaging-channel-config.test.tssrc/lib/messaging/applier/build/messaging-build-applier.mtssrc/lib/messaging/channels/built-ins.tssrc/lib/messaging/channels/googlechat/hooks/index.tssrc/lib/messaging/channels/googlechat/hooks/tunnel-audience-gate.test.tssrc/lib/messaging/channels/googlechat/hooks/tunnel-audience-gate.tssrc/lib/messaging/channels/googlechat/hooks/tunnel-runtime.test.tssrc/lib/messaging/channels/googlechat/hooks/tunnel-runtime.tssrc/lib/messaging/channels/googlechat/manifest.tssrc/lib/messaging/channels/googlechat/policy/openclaw.yamlsrc/lib/messaging/channels/googlechat/provider-profile/openclaw.yamlsrc/lib/messaging/channels/googlechat/rendered-config-parser.tssrc/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.test.tssrc/lib/messaging/channels/googlechat/runtime/googlechat-outbound-auth.tssrc/lib/messaging/channels/googlechat/runtime/googlechat-trusted-proxy-fetch.test.tssrc/lib/messaging/channels/googlechat/runtime/googlechat-trusted-proxy-fetch.tssrc/lib/messaging/channels/googlechat/template-resolver.test.tssrc/lib/messaging/channels/googlechat/template-resolver.tssrc/lib/messaging/channels/manifests.test.tssrc/lib/messaging/channels/metadata.test.tssrc/lib/messaging/channels/rendered-config-parser.tssrc/lib/messaging/channels/template-resolver.tssrc/lib/messaging/diagnostics.test.tssrc/lib/messaging/hooks/builtins.tssrc/lib/messaging/hooks/hook-runner.test.tssrc/lib/messaging/utils.test.tssrc/lib/onboard/messaging-bridge-provider.test.tssrc/lib/onboard/messaging-bridge-provider.tssrc/lib/onboard/messaging-prep.test.tssrc/lib/onboard/messaging-prep.tssrc/lib/onboard/providers.tssrc/lib/sandbox/channels.test.tssrc/lib/state/openclaw-managed-extensions.test.tssrc/lib/tunnel/googlechat-webhook-lifecycle.test.tssrc/lib/tunnel/googlechat-webhook-lifecycle.tssrc/lib/tunnel/googlechat-webhook-proxy.test.tssrc/lib/tunnel/googlechat-webhook-proxy.tssrc/lib/tunnel/services.tstest/channels-add-bridge-lifecycle.test.tstest/channels-add-preset.test.tstest/destroy-cleanup-sandbox-services.test.tstest/e2e/live/channels-stop-start-helpers.tstest/e2e/live/channels-stop-start-progress.tstest/e2e/support/channels-stop-start-progress.test.tstest/messaging-build-applier-integrity.test.tstest/onboard-readiness.test.ts
Signed-off-by: Hung Le <hple@nvidia.com>
Signed-off-by: Hung Le <hple@nvidia.com>
|
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. |
Signed-off-by: Hung Le <hple@nvidia.com>
|
🌿 Preview your docs: https://nvidia-preview-pr-7317.docs.buildwithfern.com/nemoclaw |
…plyPreset Signed-off-by: Hung Le <hple@nvidia.com>
… import Signed-off-by: Hung Le <hple@nvidia.com>
Signed-off-by: Hung Le <hple@nvidia.com>
- Group the webhook, appPrincipal, and service-account guidance into aligned left-rail blocks, color the webhook URL, and add blank-line spacing before configuration prompts. - Cap the masked echo for a long pasted secret (the service-account JSON) with an "(and N more characters)" tail and support word-delete in the masked prompt. - Add opt-in re-prompts (maxTokenAttempts) and a Google Chat-owned token-paste hook that rejects an unparseable service-account JSON at the prompt instead of aborting onboarding later at token minting. - Make the tunnel/audience gate the single source of the webhook audience. - Suppress the harmless "profile not found" probe output when registering the bridge provider profile, and surface only an unexpected probe error. - Rename the e2e stop/start heartbeat local to avoid colliding with the merged progress fixture. Signed-off-by: Hung Le <hple@nvidia.com>
…-channel-integration-googlechat
…-channel-integration-googlechat
…ract test Signed-off-by: Hung Le <hple@nvidia.com>
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
|
Exact-head babysitter update for The earlier sandbox-scoped PID-directory, teardown ordering, stored-credential validation, proxy-header filtering, and architecture-cycle findings are fixed on this head. I am not requesting additional changes through review state, and I am not approving while the current exact-head gates are red. Remaining blockers:
No workflow rerun is useful before the deterministic failures above are addressed. I will re-enter the one-hour quiet hold on the next contributor commit and re-review that exact head. |
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
…7317) Signed-off-by: Hung Le <hple@nvidia.com>
Status updateCI is green on The one red check — @cjagwani — the exact-head deterministic gates are green on |
Signed-off-by: Apurv Kumaria <akumaria@nvidia.com>
Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
Signed-off-by: Prekshi Vyas <prekshiv@nvidia.com>
Summary
Adds Google Chat as an experimental OpenClaw messaging channel, served at
/googlechaton the gateway through a dedicated Google-Chat-only cloudflared tunnel and a loopback webhook proxy. Outbound auth is minted gateway-side (OpenShellgoogle-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.
Supersedes #6120: same change, recommitted as a single verified commit with clean history.
Documentation
https://docs.google.com/document/d/1x5NAXOvxNSPGEsDwDZrButB1Q92Vbc_Q20eAcu7ofxE/edit?tab=t.0
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).
OpenShell Requirement
The Google Chat service-account private key is configured through OpenShell's
--secret-material-env(OpenShell#2178), which keeps it off process arguments. That flag ships in OpenShell v0.0.82+; currentmainpins OpenShell 0.0.85, so the requirement is already satisfied onmain— no prerequisite PR.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 — on its own dedicated tunnel, see below).nemoclaw tunnelservice wiring.nemoclaw tunnel start) fronts a loopback-only webhook proxy that forwards onlyPOST /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.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. 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-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
gateway.reload.mode=offrendered, the non-existentserviceAccountFilesentinel, and no credentials delivered into the sandbox)8897acb3fbbffcfba59eb8ad27238e566d6292ad, feature diff fingerprint2badf49554a0bc79fbd104eb88d1f4dfba26ba6f9c52a0b848c0d191653e1e8b. The sync changes no Google Chat feature behavior.Documentation Writer Review
docs-updatednpm run checks:repositorypasses, andnpm run docspasses with 0 errors and two existing warnings.Verification
Signed-off-by:line and every commit appears asVerifiedin GitHubpre-commit,commit-msg, andpre-pushhooks passed, ornpm run check:diffpassed when hooks were skipped or unavailablenpm testfor broad runtime/test-harness changes;npm run checkfor repo-wide validation/coverage changesnpm 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