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>
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>
<!-- markdownlint-disable MD041 --> ## Summary Adds the canonical dated `v0.0.101` changelog entry that was missing when the release tag was cut. This post-release recovery records the shipped behavior on current `main` without changing or replacing the existing tag. ## Changes - Add `docs/changelog/2026-08-03.mdx` with the exact `## v0.0.101` heading, release summary, detailed behavior changes, support boundaries, and links to durable documentation. - [#7317](#7317) -> `docs/changelog/2026-08-03.mdx`: Records experimental OpenClaw Google Chat support and its restricted credential and webhook boundary. - [#7715](#7715) -> `docs/changelog/2026-08-03.mdx`: Records strict onboarding recovery state and authoritative resume identity. - [#7749](#7749) -> `docs/changelog/2026-08-03.mdx`: Records the provider-neutral policy seam and unchanged runtime support boundary. - [#7817](#7817) -> `docs/changelog/2026-08-03.mdx`: Records preserved Hermes home-channel assignments across rebuilds. - [#7820](#7820) -> `docs/changelog/2026-08-03.mdx`: Records the SSH-session status field correction. - [#7847](#7847) -> `docs/changelog/2026-08-03.mdx`: Records fail-closed credential filtering for migration and rebuild backups. - [#7870](#7870) -> `docs/changelog/2026-08-03.mdx`: Records sandbox-qualified in-sandbox host command hints. - [#7875](#7875) -> `docs/changelog/2026-08-03.mdx`: Records Microsoft Teams stop and start E2E coverage. - [#7885](#7885) -> `docs/changelog/2026-08-03.mdx`: Records Hermes managed gateway detection in status. - [#7889](#7889) -> `docs/changelog/2026-08-03.mdx`: Records policy-authenticated HTTPS Pin Runtime route revocation. - [#7891](#7891) -> `docs/changelog/2026-08-03.mdx`: Records default fallback for negative timeout and polling overrides. - [#7993](#7993) -> `docs/changelog/2026-08-03.mdx`: Records correct sibling detection during uninstall. - [#7995](#7995) -> `docs/changelog/2026-08-03.mdx`: Records absent configuration-hash handling before shields lock. - [#8001](#8001) -> `docs/changelog/2026-08-03.mdx`: Records the dormant atomic managed workload replacement foundation. - [#8029](#8029) -> `docs/changelog/2026-08-03.mdx`: Records repository terminology review in PR Review Advisor. - [#8031](#8031) -> `docs/changelog/2026-08-03.mdx`: Records provider-neutral managed snapshot authority. - [#8032](#8032) -> `docs/changelog/2026-08-03.mdx`: Records immutable managed clone handoff contracts. - [#8034](#8034) -> `docs/changelog/2026-08-03.mdx`: Records the dormant provider-owned clone transaction surface. - [#8035](#8035) -> `docs/changelog/2026-08-03.mdx`: Records the dormant Hermes managed clone broker boundary. - [#8036](#8036) -> `docs/changelog/2026-08-03.mdx`: Records the dormant transactional managed bootstrap boundary. - [#8037](#8037) -> `docs/changelog/2026-08-03.mdx`: Records dormant Docker bootstrap primitives and the unchanged provider support boundary. - [#8070](#8070) -> `docs/changelog/2026-08-03.mdx`: Records consolidated sandbox resource-limit E2E coverage. - [#8071](#8071) -> `docs/changelog/2026-08-03.mdx`: Records escaped and bounded CLI validation diagnostics. - [#8081](#8081) -> `docs/changelog/2026-08-03.mdx`: Records bounded linear snapshot Base64 validation. - [#8085](#8085) -> `docs/changelog/2026-08-03.mdx`: Records commit-bound workflow approval for eligible same-repository maintainers. - [#8088](#8088) -> `docs/changelog/2026-08-03.mdx`: Records Hermes managed-policy E2E selection. - [#8090](#8090) -> `docs/changelog/2026-08-03.mdx`: Records pinned CI search-tool provisioning. - [#8106](#8106) -> `docs/changelog/2026-08-03.mdx`: Records fallback from failed managed OpenShell gateway startup. - [#8107](#8107) -> `docs/changelog/2026-08-03.mdx`: Records Hermes adapter lifecycle E2E selection. - [#8128](#8128) -> `docs/changelog/2026-08-03.mdx`: Records the dormant transactional Docker bootstrap adapter and rollback authority. - [#8140](#8140) -> `docs/changelog/2026-08-03.mdx`: Records Slack conflict scope across independent OpenShell gateways. - [#8147](#8147) -> `docs/changelog/2026-08-03.mdx`: Records completion of durable v0.0.100 documentation audit follow-ups. ## Type of Change - [ ] Code change (feature, bug fix, or refactor) - [ ] Code change with doc updates - [x] Doc only (prose changes, no code sample modifications) - [ ] Doc only (includes code sample changes) ## Quality Gates - [ ] Tests added or updated for changed behavior - [ ] Existing tests cover changed behavior — justification: - [x] Tests not applicable — justification: This documentation-only recovery does not change executable behavior. - [x] Docs updated for user-facing behavior changes - [ ] Docs not applicable — justification: - [ ] Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging) - [ ] Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: - [ ] 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` - Evidence: Independently reviewed `docs/changelog/2026-08-03.mdx` at commit `0bebe1f568e3dc85cf410aac1dfb8f8830070b85`. Its blob is `82887920f9720eafd75db6b2271c35f7477edb9b`. The entry follows the writing guide, controlled terminology, changelog structure, MDX SPDX format, literal CLI-name rule, and root-absolute route requirements. It accurately records the `v0.0.100...v0.0.101` release range, Announcement #8162, accepted scope boundaries, and shipped security behavior. There are no code samples. Focused changelog tests and the documentation build pass for this commit. - Agent: Codex Desktop independent documentation writer <!-- docs-review-head-sha: 0bebe1f --> <!-- docs-review-agents-blob-sha: 3dd7c24 --> ## Security Review - Result: `PASS` - Reviewed commit: `0bebe1f568e3dc85cf410aac1dfb8f8830070b85` - Base commit: `643a4ab8b5f583d8555192a37927268b26022c51` - Findings: None. - Secrets and credentials: `PASS`. No credential values or secret files are present. - Input validation and data sanitization: `PASS`. No executable input path changes. - Authentication and authorization: `PASS`. No identity or permission logic changes. - Dependencies and third-party libraries: `PASS`. No dependency changes. - Error handling and logging: `PASS`. No runtime path changes; diagnostic-security claims are precise. - Cryptography and data protection: `PASS`. No implementation changes. - Configuration and security controls: `PASS`. No configuration, container, port, or HTTP changes. - Security testing: `PASS`. No coverage is removed; the entry records shipped test and security behavior. - System security: `PASS`. No runtime control changes; dormant and non-activation boundaries are explicit. - Agent: Codex Desktop independent security reviewer ## Verification - [ ] PR description includes a `Signed-off-by:` line and every commit appears as `Verified` in GitHub — verification is pending after commit `0bebe1f568e3dc85cf410aac1dfb8f8830070b85` is pushed. - [ ] Normal `pre-commit`, `commit-msg`, and `pre-push` hooks passed, or `npm run validate:pr` passed after refreshing `origin/main` when hooks were skipped or unavailable — commit hooks passed; pre-push is pending. - [x] Targeted behavior tests pass for the current change set, or tests are marked not applicable above — tests are not applicable to this documentation-only recovery. - [x] Applicable broad gate passed — not applicable to this documentation-only recovery. - [x] Quality Gates section completed with required justifications or waivers - [x] No secrets, API keys, credentials, or private keys are added by this diff. - [ ] `npm run docs` builds without warnings (doc changes only) — GitHub documentation checks are pending. - [x] Doc pages follow the [style guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md) (doc changes only) — independent documentation review passed. - [x] New doc pages include SPDX header and frontmatter (new pages only) — the native changelog entry uses the required parser-safe MDX SPDX comment and intentionally has no frontmatter. GitHub CI is authoritative. Focused changelog tests and `npm run docs` passed after the merge refresh. --- Signed-off-by: Apurv Kumaria <akumaria@nvidia.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added experimental Google Chat support. * Improved runtime and session status visibility. * Added onboarding recovery and persistence safeguards. * Added snapshot validation and dormant managed-workload support. * **Bug Fixes** * Improved backup sanitization, route handling, and gateway reliability. * **Documentation** * Added the v0.0.101 changelog and related updates. * **Tests** * Expanded end-to-end coverage and strengthened trusted CI validation. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Signed-off-by: Apurv Kumaria <akumaria@nvidia.com> Signed-off-by: Carlos Villela <cvillela@nvidia.com> Co-authored-by: Carlos Villela <cvillela@nvidia.com> Co-authored-by: Senthil Ravichandran <senthilr@nvidia.com>
…EST pull (#9393) <!-- markdownlint-disable MD041 --> ## Summary Google Chat was the only messaging channel restricted to OpenClaw. This change enables it for Hermes without placing the service-account key inside the sandbox: Hermes pulls Chat events from the configured Pub/Sub subscription over the REST API and replies through the OpenShell L7 proxy with a gateway-minted bearer, so the sandbox only ever holds a credential placeholder. Before, `channels add googlechat` was refused on a Hermes sandbox; after, the channel enrolls, receives DMs, and replies. ## Related Issue Completes the Hermes half of #5492 for Google Chat. That issue models support per agent and states a channel needs to work for each runtime whose upstream actually supports it; Hermes `v2026.7.20` ships `plugins/platforms/google_chat/adapter.py`, so Hermes is inside its accepted scope. #7317 delivered the OpenClaw half and the issue was closed with that half done. It is reopened, and its channel table now records this PR against the Google Chat row. Not a closing reference, since the issue tracks the whole channel catalogue. Release target: next patch release. ## Changes - Add `channels/googlechat/policy/hermes.yaml`: Pub/Sub REST pull for inbound and Chat REST for the reply, restricted to the two Pub/Sub operations the adapter issues (`:pull`, `:acknowledge`) and to the Chat `spaces` tree for writes. - Add `channels/googlechat/provider-profile/hermes.yaml`: one gateway-minted token covering `chat.bot` and `pubsub`, with the service-account private key designated as gateway-side secret material. - Extend `channels/googlechat/manifest.ts`: allow Hermes, add the Hermes-only project and subscription inputs, render the Hermes env and platform fragment, and declare the `google-*` packages the managed image needs. - Move `google_chat` from the managed-image neutral list to the Hermes supported list, and let the bridge-provider collector select a profile by agent. - Add `channels/googlechat/runtime/hermes-adapter.py`: a channel-owned runtime asset that subclasses the bundled Google Chat adapter for a Pub/Sub REST pull loop, placeholder credentials, and an aiohttp reply transport, and attaches it through `platform_registry.get()` plus `dataclasses.replace()`. - Correct the DM allowlist prompt, which told every operator to use `users/NNN` ids and claimed emails are ignored. That holds for OpenClaw and is inverted for Hermes, and the prompt did not say that filling the allowlist switches the DM policy from pairing to allowlist, so a wrong-form entry drops the sender with no reply and no pairing code. **Why the module load is conditional.** Hermes loads only `__init__.py` as the plugin entry, so the gate in `agents/hermes/plugin/__init__.py` loads the sibling channel asset by path when the rendered `GOOGLE_CHAT_SUBSCRIPTION_NAME` is present. Nothing else reads the module, and the registry entry is replaced through `platform_registry.get()` and `dataclasses.replace()` rather than a global `register` wrapper. A failed load logs and returns without aborting plugin registration. The gate itself has no automated test, for the reason recorded under Quality Gates. ## Type of Change - [x] Code change (feature, bug fix, or refactor) - [ ] 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 - [ ] Existing tests cover changed behavior — justification: - [ ] Tests not applicable — justification: - [x] Sensitive paths changed (security, policy, credentials, preflight, onboarding, inference, runner, sandbox, or messaging) - [ ] Sensitive-path review completed or maintainer-approved waiver recorded — reviewer/approval link/justification: - [ ] Non-success, skipped, or missing CI check accepted by maintainer — check name, approval link, and follow-up issue: **Coverage gap.** The override itself is covered: `channels/googlechat/runtime/hermes-adapter.test.ts` drives the real pull loop under `python3`, and `channels/googlechat/policy.test.ts` pins the egress preset. What has no automated coverage is the load gate in `agents/hermes/plugin/__init__.py`, because this repository runs no CI lane for Python tests under `agents/hermes/plugin/`, so a unit test placed there would gate nothing. The risk it would guard against is a runtime property, that the gate stops firing and Hermes silently keeps the stock gRPC and service-account adapter, which the REST-only policy then blocks. The nearest real coverage is the `hermes-e2e` lane. Live validation is recorded under Verification. ## DGX Station Hardware Evidence - [ ] Tested on DGX Station - Tested commit: - Station profile/scenario: - Result: - Supporting evidence: ## 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 validate:pr` passed after refreshing `origin/main` when hooks were skipped or unavailable - [x] Targeted behavior tests pass for the current change set, or tests are marked not applicable above — command/result or justification: `npx vitest run src/lib/messaging/channels --project cli --coverage=false` passes 410 tests across 36 files; `npx vitest run src/lib/messaging/applier/setup-applier.test.ts --project cli --coverage=false` passes 24 tests. Both were run after merging `origin/main` into the branch. `src/lib/messaging/channels/googlechat/tunnel/lifecycle.test.ts` needs the compiled plugin, so run `npm --prefix nemoclaw run build` first. - [ ] Applicable broad gate passed — `npm test` for broad runtime/test-harness changes; `npm run check` for repo-wide validation/coverage changes — command/result: - [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) - [ ] Doc pages follow the [style guide](https://github.com/NVIDIA/NemoClaw/blob/main/docs/CONTRIBUTING.md) (doc changes only) - [ ] New doc pages include SPDX header and frontmatter (new pages only) **Live validation.** On a managed Hermes sandbox built from this branch, the plugin replaces the bundled `google_chat` entry, `connect()` reports the keyless REST pull transport, a Chat DM reaches the agent, and the bot replies in the space. The conditional load was exercised against the baked module inside that sandbox in both directions: with the channel configured the module loads and the entry is replaced; without it the module is not loaded and the bundled entry stands. --- Signed-off-by: Hung Le <hple@nvidia.com> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Added Google Chat support for Hermes alongside OpenClaw. - Added configuration for Google Cloud projects, Pub/Sub subscriptions, service accounts, and message allowlists. - Added secure message receiving and acknowledgement through Google Chat and Pub/Sub. - Added agent-specific setup and provider configuration for Google Chat integrations. - **Bug Fixes** - Improved resilience during temporary connection and acknowledgement failures, including message retries. - Added preflight checks to block setup when required messaging credentials are missing. - **Tests** - Added coverage for setup, permissions, delivery, retries, and configuration resolution. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: San Dang <sdang@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