Skip to content

feat(cron): support Google Chat platform for scheduled messages - #1374

Merged
thepagent merged 3 commits into
mainfrom
feat/googlechat-cron-dispatch
Jul 13, 2026
Merged

feat(cron): support Google Chat platform for scheduled messages#1374
thepagent merged 3 commits into
mainfrom
feat/googlechat-cron-dispatch

Conversation

@shaun-agent

@shaun-agent shaun-agent commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

What problem does this solve?

OpenAB's Google Chat adapter already supports inbound messages and outbound replies through UnifiedGatewayAdapter, but native cron rejects platform = "googlechat" and does not register the adapter with the scheduler.

This affects both the latest published beta and current main.

Related to #866. This implements only the Google Chat scheduled-message portion and does not close the broader DM and gateway-target proposal.

This is the core transport prerequisite for cron-driven workflows such as Google Sheets reporting. Sheet reads/writes, meeting retrieval, deduplication, alert evaluation, private space IDs, schedules, and credentials remain agent/deployment configuration and are not added to OpenAB core.

Discord Discussion URL: N/A - maintainer-authored follow-up to #866 and merged PR #1315.

At a Glance — before vs after

BEFORE                                     AFTER
======                                     =====
[[cron.jobs]]                              [[cron.jobs]]
platform = "googlechat"                    platform = "googlechat"
        |                                          |
        v                                          v
validate_cronjobs()                        validate_cronjobs()
        |                                          |
        X  rejected at startup:                    v  (accepted: googlechat in
           "unknown platform"                      |   VALID_PLATFORMS + platform
                                                   |   enabled config-first)
   bot refuses to start                            v
                                             cron scheduler
                                                   |
                                                   v
                                           UnifiedGatewayAdapter
                                             ("googlechat" cron key)
                                                   |
                                                   v
                                           Google Chat adapter
                                             spaces.messages.create
                                                   |
                                                   v
                                           configured Chat space

                                           No thread_id       -> top level (no
                                                                 synthetic thread;
                                                                 create_topic is
                                                                 not implemented)
                                           Explicit thread_id -> that existing
                                                                 thread

Config sample

# config.toml — enable the platform (config-first; GOOGLE_CHAT_ENABLED env
# remains the fallback)
[googlechat]
enabled = true
sa_key_file = "/etc/openab/gchat-sa.json"   # or sa_key_json / access_token
allowed_users = ["users/123456789"]          # inbound trust — does NOT
                                             # restrict scheduled outbound

[[cron.jobs]]
schedule    = "0 9 * * 1-5"                  # weekdays 09:00
channel     = "spaces/AAAA1234567"           # Chat space resource name
message     = "summarize the new support escalations"
platform    = "googlechat"
sender_name = "SupportDigest"
timezone    = "Asia/Taipei"
# thread_id = "spaces/AAAA1234567/threads/XYZ"  # optional: post into an
#                                               # existing thread instead

Prior Art & Industry Research

OpenClaw:

OpenClaw scheduled tasks separates scheduling from delivery and stores an explicit platform and destination using delivery.channel and delivery.to. It also treats scheduled delivery as an operator automation path rather than an inbound user event.

Hermes Agent:

Hermes scheduled tasks automatically delivers an agent's final response to a configured platform target. Its continuation behavior is platform-specific: thread-capable targets may create threads, while flat surfaces remain top-level.

Other references:

Proposed Solution

  1. Add googlechat to the scheduler's valid platforms.
  2. Treat Google Chat as configured when the feature is compiled and the platform is enabled — resolved config-first via GoogleChatConfig::resolve().enabled ([googlechat] enabled = trueGOOGLE_CHAT_ENABLED env → default), matching the first-class-platform-config ADR.
  3. Register the existing unified adapter under the googlechat cron key.
  4. Keep jobs without thread_id at the top level of the configured space.
  5. Preserve explicit Google Chat thread resource names when supplied.
  6. Add validation and thread-selection unit tests.
  7. Update the cron documentation and Google Chat capability schema.

Thread behavior

Google Chat currently ignores OpenAB's create_topic gateway command. Calling the generic unified create_thread implementation would therefore create no remote thread but return a synthetic local thread ID, which could then be used or persisted as though it were a real Google Chat resource.

This change avoids that invalid state. Automatic thread creation remains unchanged for Discord, Slack, and Telegram.

Trust behavior

Cron dispatch intentionally does not pass through the inbound Google Chat trust gate because no external user event initiates the run. The authority comes from trusted baseline cron configuration or an enabled writable usercron file.

Consequently, [googlechat].allowed_users controls inbound messages but does not restrict scheduled outbound delivery. Anyone authorized to modify cron configuration can select a space where the Chat app already has permission to post. This PR does not introduce a separate outbound target allowlist.

Why this approach?

It follows the narrow registration pattern already merged for Telegram in #1315 and reuses Google Chat's existing outbound adapter instead of adding a second delivery implementation.

The Google Chat-specific thread decision is necessary because the platform adapter does not implement create_topic. Remaining top-level is safer than manufacturing and persisting a nonexistent thread ID.

Known limitations:

  • Cron cannot automatically create a new Google Chat thread.
  • Posting to an existing thread requires an explicit full thread resource name.
  • Delivery acknowledgement and retry behavior remain those of the existing unified adapter.
  • Outbound destination authorization remains an operational configuration responsibility.
  • Google Sheets and meeting workflow logic remain outside OpenAB core.

Alternatives Considered

  • External scheduler or webhook: Functional, but duplicates native scheduling and adapter routing.
  • Implement all gateway and DM targets from feat(cron): support DM and gateway platform targets for scheduled messages #866: Broader than the Google Chat requirement and harder to review independently.
  • Implement Google Chat thread creation: Requires separate adapter/API semantics and is unnecessary for basic scheduled delivery.
  • Put Google Sheets or meeting-alert logic in cron core: Rejected because those are workflow-specific agent tools and policies, not transport behavior.

Validation

  • cargo build --features googlechat
  • cargo check --workspace
  • cargo test -p openab-core cron::tests - 74 passed
  • cargo test --workspace - 15 OpenAB, 667 core, and 254 gateway tests passed
  • cargo test --workspace --features unified - 15 OpenAB, 667 core, and 254 gateway tests passed
  • Platform schema tests - 4 unit and 8 conformance tests passed
  • git diff --check
  • Required GitHub CI - workspace check, default/unified Clippy, and workspace tests passed
  • Default and unified Clippy pass when allowing the existing baseline lint:
    • cargo clippy --workspace -- -D warnings -A clippy::collapsible_else_if
    • cargo clippy --workspace --features unified -- -D warnings -A clippy::collapsible_else_if

Local Rust 1.92 additionally reports a pre-existing clippy::collapsible_else_if finding at crates/openab-core/src/pre_seed.rs:471; the repository's required GitHub Clippy jobs pass unchanged.

Maintainer update (4174a0f): merged current main (post #1381#1385/#1387) and fixed the enablement check to be config-first — the original read only the GOOGLE_CHAT_ENABLED env var, so a [googlechat] enabled = true config-only deployment would have its cron jobs rejected at startup. Re-verified on the updated branch: all-features clippy clean, cron tests 74 pass, bin tests 15 pass, schema conformance green.

Manual integration testing used an every-minute usercron job against a private test Google Chat space. The equivalent scheduled delivery path and agent response were observed through the unified adapter. Private target IDs, credentials, meeting content, and Google Sheets data are intentionally excluded from this PR.

The deployed test build also contained deployment-specific workflow extensions; those are not part of this change.

@shaun-agent
shaun-agent requested a review from thepagent as a code owner July 12, 2026 15:18
@chaodu-agent

This comment has been minimized.

@chaodu-agent

This comment has been minimized.

@chaodu-agent

This comment was marked as outdated.

@chaodu-agent

This comment has been minimized.

The platform-configured check read only the GOOGLE_CHAT_ENABLED env
var, so a deployment enabling the platform via `[googlechat] enabled =
true` (first-class section, #1383) would have its cron jobs rejected
at startup by validate_cronjobs. Use GoogleChatConfig::resolve()
(config → env → default) — same semantics the adapter itself uses.
Align the cronjob.md platform table and the schema note wording.

Verified: all-features clippy clean, cron tests 74 pass, bin tests 15
pass, schema conformance green. (Single-feature --features googlechat
clippy failure is pre-existing on main, unrelated.)
@chaodu-agent

Copy link
Copy Markdown
Collaborator

Note

LGTM ✅ — Clean, narrow Google Chat cron dispatch wired through the established unified adapter pattern.

What This PR Does

Google Chat was already supported for inbound messages via the unified gateway adapter, but VALID_PLATFORMS in cron.rs rejected it at startup. This PR registers Google Chat as a valid cron platform, routes scheduled messages through the existing adapter, and correctly avoids thread creation (which Google Chat does not implement).

How It Works

  1. Validation: googlechat added to VALID_PLATFORMS; platform-enabled detection uses #[cfg(feature = "googlechat")] + GoogleChatConfig::resolve().enabled.
  2. Thread logic: New should_create_cron_thread() returns false for Google Chat (avoids synthetic thread IDs from the no-op create_topic). Existing platforms retain their thread-creation behavior.
  3. Sender context: cron_sender_thread_id() replaces the old always-Some(channel_id) fallback with a more precise approach — returns None when there is no real thread, which is correct for top-level Google Chat delivery.
  4. Adapter registration: Mirrors the Telegram pattern from feat(cron): support telegram platform for cronjob #1315 — inserts shared_unified_adapter under the "googlechat" key in cron_adapters.

Findings

# Severity Finding Location
1 🟢 Follows established #1315 pattern exactly src/main.rs
2 🟢 Thread logic correctly avoids manufacturing fake thread IDs crates/openab-core/src/cron.rs
3 🟢 Good test coverage for all new code paths (6 unit tests) crates/openab-core/src/cron.rs
4 🟢 Schema doc updated to implemented with source references docs/platforms/schema/googlechat.toml
5 🟢 Docs include clear Google Chat example and thread behavior explanation docs/cronjob.md
What's Good (🟢)
  • Precisely scoped: only adds the minimum needed for Google Chat cron dispatch without expanding scope to thread creation or outbound allowlists.
  • The should_create_cron_thread / cron_sender_thread_id refactor is a clean abstraction that makes the platform-specific thread behavior explicit and testable.
  • No behavioral regression for existing platforms — create_thread for Discord/Slack/Telegram always returns a ChannelRef with thread_id or parent_id populated, so cron_sender_thread_id produces equivalent results.
  • The .clone().unwrap_or_default().resolve() pattern in main.rs correctly handles the Option<GoogleChatConfig> — defaulting to disabled when the section is absent.
  • Trust behavior is well-documented: cron bypasses inbound trust gates, which is the correct design since authority comes from the cron config itself.
  • Comprehensive validation section with CI evidence.
Baseline Check
  • PR opened: 2026-07-13
  • Main already has: Google Chat adapter (inbound + outbound via unified gateway), VALID_PLATFORMS covering discord/slack/telegram, Telegram cron registration pattern (feat(cron): support telegram platform for cronjob #1315), GoogleChatConfig with resolve().
  • Net-new value: Wires Google Chat into cron validation + adapter registration + thread-aware dispatch, with correct top-level-only behavior.

5️⃣ Three Reasons We Might Not Need This PR

  1. External schedulers work — A cron job or GitHub Action calling the Google Chat API directly achieves the same outcome without touching OpenAB core. Counter: This would bypass adapter routing, message formatting, and agent dispatch — duplicating infrastructure already present.

  2. No thread creation limits usefulness — Without auto-thread-creation, all scheduled messages pile up at the top level of a space. Counter: This is a platform limitation, not a PR flaw; explicit thread_id is supported for those who need threads, and top-level is the expected default for digest-style messages.

  3. Narrow user base — Google Chat adoption in OpenAB deployments is still emerging. Counter: The implementation cost is minimal (~50 lines of logic), follows an established pattern, and unblocks real use cases without adding maintenance burden.

@thepagent
thepagent merged commit 1a249d0 into main Jul 13, 2026
40 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants