fix(googlechat): scope agent sessions per thread in threaded spaces#1
Open
augusteo wants to merge 463 commits into
Open
fix(googlechat): scope agent sessions per thread in threaded spaces#1augusteo wants to merge 463 commits into
augusteo wants to merge 463 commits into
Conversation
Summary: - The PR threads middleware tool-error metadata into reply payloads, teaches cron outcome and diagnostics code to keep marked recovered warnings non-fatal, and adds focused regression coverage plus a changelog entry. - Reproducibility: yes. Source inspection shows current main lacks a non-terminal recovered-warning path in cr ... fication, and the linked source PR includes a terminal runtime probe for the affected cron payload outcome. Automerge notes: - PR branch already contained follow-up commit before automerge: fix(cron): keep recovered tool warnings diagnostic Validation: - ClawSweeper review passed for head 8b8a36e. - Required merge gates passed before the squash merge. Prepared head SHA: 8b8a36e Review: openclaw#84308 (comment) Co-authored-by: abnershang <[email protected]> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman Co-authored-by: takhoffman <[email protected]>
…turns rediscovering constraints (openclaw#84368) Summary: - The PR updates the code-mode exec tool description, adds regression coverage for the model-visible constraints, and records the fix in the changelog. - Reproducibility: yes. at source level: current main's exec schema omits constraints that the current code-mo ... also includes a live before/after recitation path showing the model receives the changed tool description. Automerge notes: - PR branch already contained follow-up commit before automerge: test(code-mode): cover exec tool guidance - PR branch already contained follow-up commit before automerge: fix(code-mode): sharpen exec tool description so models stop wasting … Validation: - ClawSweeper review passed for head 8ff8507. - Required merge gates passed before the squash merge. Prepared head SHA: 8ff8507 Review: openclaw#84368 (comment) Co-authored-by: Kaspre <[email protected]> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman Co-authored-by: takhoffman <[email protected]>
* fix(doctor): preserve unknown web search records * fix(doctor): filter dangerous web search keys * fix(config): preserve extensible web search settings * fix(config): keep legacy web search validation strict * fix(config): reject blocked web search keys
Summary: - The branch gives Codex `image_generate` dynamic-tool calls a 120s default watchdog in main and side-thread paths and updates docs, tests, and changelog. - Reproducibility: yes. Source inspection on current main shows unconfigured Codex `image_generate` calls fall ... -tool default, and the linked source PR includes live Gateway before/after output for the timeout behavior. Automerge notes: - PR branch already contained follow-up commit before automerge: fix(clawsweeper): address review for automerge-openclaw-openclaw-8425… - PR branch already contained follow-up commit before automerge: Fix Codex image generation tool timeout Validation: - ClawSweeper review passed for head 10c7f87. - Required merge gates passed before the squash merge. Prepared head SHA: 10c7f87 Review: openclaw#84369 (comment) Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: moritzmmayerhofer <[email protected]> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman Co-authored-by: takhoffman <[email protected]>
* fix(cli): request node approval scopes * docs(changelog): note node approval scope fix
Marks skipped and supplemental Microsoft Teams system events as non-owner/untrusted while preserving active primary message dispatch behavior. Verified before merge: - PR was open, not draft, mergeable, and clean against main - Matched head: 4f79f46 - GitHub checks passed, including Real behavior proof, auto-response, build artifacts, type/lint checks, channel/runtime critical quality checks, and security-fast - ClawSweeper marked proof sufficient with no concrete contributor-facing blocker remaining Co-authored-by: GuoJiaming <[email protected]>
Summary: - The branch caps async diagnostic drains at 100 events per turn, adds pending/full-drain diagnostic helpers, ... rminal diagnostics to inspect pending events, and adds regression coverage plus changelog/baseline updates. - Reproducibility: yes. from source inspection. Current main drains the entire async diagnostic queue in one s ... ck, and the PR body supplies a focused 250-event after-fix probe showing 100/200/250 delivery across turns. Automerge notes: - PR branch already contained follow-up commit before automerge: fix: yield diagnostic event drains Validation: - ClawSweeper review passed for head 9561093. - Required merge gates passed before the squash merge. Prepared head SHA: 9561093 Review: openclaw#82937 (comment) Co-authored-by: Galin Iliev <[email protected]> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Summary: - This PR replaces pair-only heartbeat filtering with span-based filtering before embedded-runner prompt assem ... ession coverage and a changelog entry, and updates the LINE command type to use the SDK command definition. - Reproducibility: yes. from source and report evidence: current main only removes immediate heartbeat prompt/ ... body supplies same-session terminal proof and a commenter supplied a matching Discord gateway observation. Automerge notes: - PR branch already contained follow-up commit before automerge: fix: filter heartbeat transcript artifacts - PR branch already contained follow-up commit before automerge: fix: clean up heartbeat filter lint - PR branch already contained follow-up commit before automerge: fix: keep line entry on channel SDK - PR branch already contained follow-up commit before automerge: fix: filter heartbeat response text transcript shapes - PR branch already contained follow-up commit before automerge: Filter heartbeat response-tool transcript artifacts Validation: - ClawSweeper review passed for head e019c74. - Required merge gates passed before the squash merge. Prepared head SHA: e019c74 Review: openclaw#83477 (comment) Co-authored-by: fuller-stack-dev <[email protected]> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman Co-authored-by: takhoffman <[email protected]>
…4281) * Include delivery error in subagent announce give-up logs * test(agents): type announce delivery error response
…#84180) Summary: - The PR routes model-list row construction through provider-owned resolved-model normalization for configured ... rows, adds Anthropic regression coverage, updates focused test mocks/fixtures, and adds a changelog entry. - Reproducibility: yes. at source level: current main renders configured/default list rows without calling the ... ty is restored. The PR body also supplies terminal output showing the fixed configured row as `text+image`. Automerge notes: - PR branch already contained follow-up commit before automerge: test(models): update forward compat agent-scope mock - PR branch already contained follow-up commit before automerge: test(models): isolate provider catalog row tests - PR branch already contained follow-up commit before automerge: test(models): complete provider catalog fixtures - PR branch already contained follow-up commit before automerge: Merge remote-tracking branch 'upstream/main' into fix/anthropic-confi… - PR branch already contained follow-up commit before automerge: test(workflows): match alpha concurrency rules Validation: - ClawSweeper review passed for head 7a1caa7. - Required merge gates passed before the squash merge. Prepared head SHA: 7a1caa7 Review: openclaw#84180 (comment) Co-authored-by: Andy Ye <[email protected]> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman Co-authored-by: takhoffman <[email protected]>
* fix: dedupe openai strict schema downgrade logs * test: align openai transport helper export * test: cover openai downgrade log behavior * docs: note openai downgrade diagnostic dedupe --------- Co-authored-by: Galin Iliev <[email protected]>
…openclaw#84330) (openclaw#84420) Summary: - The PR adds a Warning block to `docs/channels/imessage.md` explaining that iMessage `cliPath` wrappers and SSH proxies must stream long-lived JSON-RPC stdin/stdout incrementally. - Reproducibility: not applicable. for this docs-only PR. Source inspection verifies the runtime uses long-lived line-framed stdio, and current main lacks the operator warning being added. Automerge notes: - PR branch already contained follow-up commit before automerge: docs(imessage): warn that cliPath wrappers must stream JSON-RPC stdio… Validation: - ClawSweeper review passed for head a371ee9. - Required merge gates passed before the squash merge. Prepared head SHA: a371ee9 Review: openclaw#84420 (comment) Co-authored-by: HCL <[email protected]> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman Co-authored-by: takhoffman <[email protected]>
* fix(agents): recover stale Codex encrypted reasoning replay * docs(changelog): note Codex encrypted replay recovery * fix(agents): bind Codex reasoning replay provenance * fix(agents): pin codex reasoning replay provenance
* fix(cron): isolate main-session cron wake lanes * test(cron): expect dedicated main cron lanes * fix(cron): route global main cron wakes * docs(changelog): note cron main-session lane fix --------- Co-authored-by: Galin Iliev <[email protected]> Co-authored-by: Galin Iliev <[email protected]>
* fix: prompt codex to send visible channel replies * chore: add codex reply changelog entry * test: refresh codex prompt snapshots
Summary:
- The PR changes cron store loading to normalize legacy top-level array `jobs.json` files into the versioned store shape and adds store, service, doctor, gateway tests plus a changelog entry.
- Reproducibility: yes. Current `main` clearly maps a top-level parsed array to `{}` before reading `.jobs`, and the PR body supplies before/after runtime output for the load/add/save path.
Automerge notes:
- PR branch already contained follow-up commit before automerge: [AI-assisted] fix(cron): preserve legacy array stores
Validation:
- ClawSweeper review passed for head 446014b.
- Required merge gates passed before the squash merge.
Prepared head SHA: 446014b
Review: openclaw#84433 (comment)
Co-authored-by: JARVIS-Glasses <[email protected]>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: takhoffman
Co-authored-by: takhoffman <[email protected]>
Summary: - The branch suppresses regular verbose failed-tool raw output after final replies across shared dispatch, Codex, Telegram, and Discord paths, keeps raw detail under `/verbose full`, and updates tests, docs, and changelog. - Reproducibility: yes. The current-main source path and supplied before screenshot show failed text-only tool ... ping after a final reply; I did not rerun a live Telegram or Discord reproduction in this read-only review. Automerge notes: - PR branch already contained follow-up commit before automerge: fix message-tool delivery gating - PR branch already contained follow-up commit before automerge: fix(channels): keep verbose tool failures compact - PR branch already contained follow-up commit before automerge: fix(channels): suppress in-flight final progress - PR branch already contained follow-up commit before automerge: fix(replies): suppress failed tool dumps in message-only mode - PR branch already contained follow-up commit before automerge: fix(replies): avoid duplicate exec failure warnings - PR branch already contained follow-up commit before automerge: Revert "fix(replies): avoid duplicate exec failure warnings" Validation: - ClawSweeper review passed for head d15ae69. - Required merge gates passed before the squash merge. Prepared head SHA: d15ae69 Review: openclaw#84354 (comment) Co-authored-by: VACInc <[email protected]> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman Co-authored-by: takhoffman <[email protected]>
Summary: - The replacement branch adds an owned transcript write context around Pi prompt-time delivery mirror appends and a message-tool-only terminal hook, with focused tests and a changelog entry. - Reproducibility: yes. the source PR includes before/after redacted live Discord logs for a message-tool-only ... ession-lock and transcript append code. I did not rerun the live Discord scenario in this read-only review. Automerge notes: - PR branch already contained follow-up commit before automerge: fix(pi): keep message-tool delivery in session lock Validation: - ClawSweeper review passed for head f166781. - Required merge gates passed before the squash merge. Prepared head SHA: f166781 Review: openclaw#84437 (comment) Co-authored-by: Andrew Meyer <[email protected]> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman Co-authored-by: takhoffman <[email protected]>
…4397)" (openclaw#84442) This reverts commit 47eb4ca.
…token (openclaw#85028) OAuth credentials that loaded without their sidecar material (no access, no refresh) would still enter the refresh path inside the per-profile lock, where the adapter call is bounded by OAUTH_REFRESH_CALL_TIMEOUT_MS (120s). That made the eventual "No API key found for provider" surface to the user only after a long stall, even though the resolver had no usable material to attempt with. Short-circuit doRefreshOAuthTokenWithLock to return null when there is no refresh token to use, after the in-lock main-store adoption and external bootstrap-credential checks have already had a chance to recover. Thanks @RomneyDa.
…e providers only (openclaw#84762) Summary: - The branch scopes config-time Google Gemini preview model normalization to Google providers or nested `google/` proxy suffixes, adds model-picker regression coverage, and adds a changelog entry. - Reproducibility: yes. by source inspection. Current main sends every provider suffix through the Google prev ... i-3-flash` deterministically becomes `litellm/gemini-3-flash-preview`; I did not run a live cron preflight. Automerge notes: - PR branch already contained follow-up commit before automerge: fix(config): scope Google preview model normalization to Google provi… - PR branch already contained follow-up commit before automerge: fix openclaw#84745: scope Google preview model normalization to Google provid… - PR branch already contained follow-up commit before automerge: fix openclaw#84745: preserve proxy Google model normalization Validation: - ClawSweeper review passed for head c59163c. - Required merge gates passed before the squash merge. Prepared head SHA: c59163c Review: openclaw#84762 (comment) Co-authored-by: zhang-guiping <[email protected]> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: 张贵萍0668001030 <[email protected]> Co-authored-by: Claude Opus 4.7 <[email protected]> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
…tern (openclaw#85066) knip's deadcode-unused-files check ignores fixtures matching **/*.fixture.ts (dot before "fixture"). The codex lifecycle fixtures landed in bbf3eec as auth-profile-fixture.ts and codex-plugin-fixture.ts (hyphen), so knip flagged them as unexpected unused files and CI's check-dependencies job has been failing on main since then. Rename to auth-profile.fixture.ts and codex-plugin.fixture.ts and update the lifecycle test, the fixture cross-import, and the six qa/scenarios markdown files that reference them by path and qaImport specifier.
Eliminates the per-call auth-filter loop that every /models invocation (Discord/Telegram pickers, CLI, status commands) was paying — 30 unique providers × ~600 ms each of plugin-runtime / external-CLI / auth-profile discovery, done fresh on every call (~20 s per call). warmCurrentProviderAuthState builds a provider->boolean map once at gateway startup against a single AuthProfileStore scoped to every candidate provider, and hasAuthForModelProvider consults the prepared map first and short-circuits. The map is invalidated on config reload alongside resetModelCatalogCache so the next read after a relevant config change rewarms. Per /models: ~20,569 ms → ~5 ms (~4,100×). One-time startup warm cost: ~49 s (cold catalog + auth sweep), logged via gateway log.info on completion.
Asserts hasAuthForModelProvider returns the warmed answer for providers in the prepared map and skips the compute path, and that clearCurrentProviderAuthState restores fall-through to compute.
…n reload Two fixes flagged by ClawSweeper. P1 — hasAuthForModelProvider now only short-circuits via the prepared map when the caller's scope matches the warmer's (broad discovery, no agentDir/env/store override). Read-only gateway model lists pass runtimeAuthDiscovery: false, which the visibility helper maps to discoverExternalCliAuth: false and allowPluginSyntheticAuth: false; the prepared broad answer was previously masking that narrower intent. Now those callers fall through to compute the narrow answer. P2 — server-reload-handlers now also schedules a rewarm right after clearing the prepared state on model-config reload, so long-lived gateways don't regress to per-call discovery between reload and the next restart. Test: extends model-provider-auth.test.ts with a scope-narrowing case asserting the prepared answer is bypassed when the caller passes discoverExternalCliAuth: false / allowPluginSyntheticAuth: false.
…oad trigger ClawSweeper P1 + P2 findings on the prior review-fix commit. - [P1] hasAuthForModelProvider now also checks workspaceDir against the warm's snapshot value. The warmer uses resolveDefaultAgentWorkspaceDir, but per-agent picker calls (buildModelsProviderData → resolveVisibleModelCatalog → createProviderAuthChecker) thread an agent-specific workspaceDir, and provider env/synthetic-auth resolution depends on it. Without this check the picker for agent B would silently reuse agent A's warmed answer. - [P1] warmCurrentProviderAuthState now claims a generation counter at the start of its work and only publishes the new state if the generation hasn't been bumped (by a concurrent clear or another warm). Closes the race where a slow startup warm could overwrite a newer reload-driven rewarm with stale data. - [P2] Reload handler now also clears and rewarms the prepared map when plugins.* config paths change. Provider auth can come from plugin env vars and plugin synthetic-auth wiring, so plugin hot reloads must invalidate the auth state too — not just model config paths. Test: new case asserting a non-default workspaceDir caller bypasses the prepared map and falls through to compute.
…efer plugin-reload rewarm P1 (auth-profile logout): invalidateModelAuthStatusCache now also clears the prepared provider-auth map, and the models.auth.logout handler fires a rewarm against the current config. Without this, removing a provider's auth profiles left the warmed 'true' answer in the map until restart, so /models and pickers kept advertising the removed provider. P1 (plugin-reload ordering): the previous version fired the rewarm inline with the clear, before plan.reloadPlugins() ran. The warmer reads plugin metadata and synthetic-auth hooks, so it published the pre-reload runtime's answers. Moved the rewarm to fire after the plugin-reload block completes, so it reads the new plugin runtime. The early clear still happens upfront so callers don't keep seeing the pre-reload answer during the reload window.
Merged via squash. Prepared head SHA: 3aa770f Co-authored-by: litang9 <[email protected]> Co-authored-by: altaywtf <[email protected]> Reviewed-by: @altaywtf
…me loaders (openclaw#85074) The auto-migration introduced in openclaw#83312 only fires when a credential is loaded via a path that reads its sidecar tokens. The OAuth refresh manager's internal loader does (so direct CLI inference works and self-heals on first refresh). The embedded runner's secrets-runtime loaders did not: - loadAuthProfileStoreForSecretsRuntime - loadAuthProfileStoreWithoutExternalProfiles - ensureAuthProfileStoreWithoutExternalProfiles All three opted out of sidecar resolution. So for an upgraded user with a legacy oauthRef-backed openai-codex profile, the credential loaded with no access/refresh material, evaluateStoredCredentialEligibility marked it ineligible, resolveAuthProfileOrder filtered it out, and resolveApiKeyForProvider threw "No API key found for provider 'openai-codex'" before the OAuth manager (and its migration path) was ever consulted. CLI worked, Telegram/cron/embedded turns broke — only doctor-or-bust would fix it. Flip the three embedded loaders to default resolveLegacyOAuthSidecars to true (matching loadStoredOAuthRefreshStore). The existing openclaw#83312 refresh-and-rewrite then fires on the first embedded turn for these users and persists tokens inline, removing the legacy sidecar from disk on the next doctor pass. Cherry-picked and squashed from PR openclaw#84752 (commits 85f36e8 and 4624e34). Comments noting local-fork bookkeeping stripped per repo policy. Co-authored-by: Will <[email protected]>
…openclaw#83405) Merged via squash. Prepared head SHA: b2c2e9a Co-authored-by: medns <[email protected]> Co-authored-by: odysseus0 <[email protected]> Reviewed-by: @odysseus0
… helper (openclaw#84427) Merged via squash. Prepared head SHA: 257aab2 Co-authored-by: medns <[email protected]> Co-authored-by: odysseus0 <[email protected]> Reviewed-by: @odysseus0
…openclaw#81403) Merged via squash. Prepared head SHA: 874fa90 Co-authored-by: medns <[email protected]> Co-authored-by: odysseus0 <[email protected]> Reviewed-by: @odysseus0
Merged via squash. Prepared head SHA: eb49667 Co-authored-by: medns <[email protected]> Co-authored-by: odysseus0 <[email protected]> Reviewed-by: @odysseus0
Merged via squash. Prepared head SHA: b005134 Co-authored-by: martingarramon <[email protected]> Co-authored-by: altaywtf <[email protected]> Reviewed-by: @altaywtf
Two threads in the same Google Chat space previously shared one agent
session and one bot-loop counter because conversationId/route.sessionKey
were keyed off the space id. This mirrored Slack/Discord/Teams/Mattermost
behavior into Google Chat.
After this change, in spaces with spaceThreadingState=THREADED_MESSAGES
(or spaceType=SPACE when threading state is absent), the resolved
route.sessionKey is suffixed with ':thread:<name>' via the shared
resolveThreadSessionKeys helper. DMs, group chats, and unthreaded
spaces remain space-scoped. The envelope builder is rebound to the
thread-scoped key so previousTimestamp reads the right session. A
parentSessionKey carrying the space-level key is propagated so the
first turn in a fresh thread session inherits prior space context
rather than orphaning active conversations.
Bot-loop conversationId becomes a native compound id (${spaceId}:${thread})
to match Slack's debounce-key shape — not the full agent session key,
which would couple guard semantics to agent routing config.
Adds 12 unit tests (monitor.thread-session-key.test.ts) and 3 e2e
integration tests (monitor.thread-session-key.e2e.test.ts) covering
THREADED isolation, DM/GROUP_CHAT/UNTHREADED collapse, legacy 'DM'
type, conversation.threadId propagation, parent inheritance, bot-loop
shape, and non-MESSAGE event short-circuit.
Adversarial review (superpowers + codex) surfaced five real issues against
the initial implementation. Fixes:
- Bot-loop conversationId is now the bare space id (matches Slack's
prepared.message.channel shape). Previous ${spaceId}:${thread} compound
was finer-grained than Slack without justification.
- isGroup now reads modern space.spaceType first (DIRECT_MESSAGE -> direct),
falling back to legacy space.type. Modern DM payloads without the
deprecated 'type' field are no longer misrouted as group:.
- Inbound session scoping and outbound reply targeting both read from
resolveGoogleChatThreadName(event) — same source — eliminating a split-
brain risk where the agent could record the turn under thread A but post
the reply into thread B.
- spaceSupportsThreading no longer treats spaceType=SPACE alone as positive
evidence; GROUPED_MESSAGES spaces are also SPACE. Requires explicit
THREADED_MESSAGES state or legacy type=ROOM.
- Removed unused GoogleChatMessage.threadReply field — declared but never
read in production.
Tests:
- Added unit tests for modern DM/GROUP_CHAT routing, bare-SPACE fallback
behavior, and legacy ROOM threading.
- Updated bot-loop test to assert space-only conversationId.
- Restructured e2e harness with unique account ids so the real bot-loop
SDK guard (process-scoped, no plugin-visible clear helper) cannot leak
state across tests. Added two e2e cases that actually exercise the real
recordChannelBotPairLoopAndCheckSuppression: tight bot-to-bot ping-pong
suppression within one space, and cross-space scope isolation.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Two threads in the same Google Chat space previously shared one agent session and one bot-loop counter because
conversationIdandroute.sessionKeywere keyed off the space id. This change brings Google Chat into line with Slack/Discord/Teams/Mattermost: in spaces withspaceThreadingState: THREADED_MESSAGES(or legacytype: ROOM), the resolvedroute.sessionKeyis suffixed with:thread:<name>via the sharedresolveThreadSessionKeyshelper. DMs, group chats, and unthreaded spaces keep the existing space-scoped behavior.Key implementation details:
spaceSupportsThreading()requires an explicit positive threading signal —spaceType: SPACEalone is not sufficient becauseGROUPED_MESSAGESspaces also report asSPACE.isGroupGoogleChatSpace()reads modernspace.spaceTypefirst (so aDIRECT_MESSAGEpayload without the deprecatedtypefield is no longer misrouted asgroup:).createInboundEnvelopeBuilderwith the thread-scoped key sopreviousTimestampreads the right session's last-updated time.parentSessionKey: route.sessionKeyis propagated so the first turn in a fresh thread session inherits the space-level transcript (consumed downstream atsrc/auto-reply/reply/session.ts:733) — prevents orphaning active conversations on deploy.conversationIdis the bare space id (matches Slack'sprepared.message.channelshape).resolveGoogleChatThreadName(event)— same source — eliminating a split-brain risk.sendGoogleChatMessage({ thread })is unchanged in behavior.Two adversarial reviews (superpowers code-review + OpenAI Codex challenge) were run against the initial commit; the follow-up commit addresses every actionable Important finding.
Verification
Behavior addressed: Google Chat agent sessions and bot-loop protection were scoped per-space; replies in different threads of the same space polluted each other's transcripts.
Real environment tested: No — Crabbox/live Google Chat testing not run. Verified via unit + integration test suites only.
Exact steps or command run after this patch:
node scripts/run-vitest.mjs extensions/googlechat→ 141 unit tests passing across 17 files.node scripts/run-vitest.mjs run --config test/vitest/vitest.e2e.config.ts extensions/googlechat/src/monitor.thread-session-key.e2e.test.ts→ 5 e2e tests passing.node scripts/run-oxlint.mjs extensions/googlechat/src/monitor.ts extensions/googlechat/src/monitor.thread-session-key.test.ts extensions/googlechat/src/monitor.thread-session-key.e2e.test.ts extensions/googlechat/src/types.ts extensions/googlechat/runtime-api.ts→ clean.node scripts/run-tsgo.mjs -p tsconfig.extensions.json --incremental --tsBuildInfoFile .artifacts/tsgo-cache/extensions.tsbuildinfo→ exit 0.Evidence after fix:
uses distinct session keys for two threads in a THREADED_MESSAGES spacewas the original RED test — it fails on the parent commit (both threads produce identicalagent:main:googlechat:default:group:spaces/AAA) and passes on this branch (T1 →…:thread:spaces/AAA/threads/T1, T2 →…:thread:spaces/AAA/threads/T2).exercises the real bot-loop SDK: suppresses a tight bot-to-bot ping-pong within one spaceexercises the un-mockedrecordChannelBotPairLoopAndCheckSuppressionand proves space-scoped suppression works end-to-end.real bot-loop SDK does NOT cross-suppress across different spacesproves the same guard does NOT bleed acrossspaceIdvalues (regression lock on bot-loop scope isolation).routes a modern DIRECT_MESSAGE payload as 'direct' peer kind (not group)proves the modern-DM routing fix.Observed result after fix: 141 unit tests + 5 e2e tests = 146 passing. Lint clean. Typecheck clean.
What was not tested: No live Google Chat webhook traffic. No Crabbox / Docker / cross-OS proof. Transcript-fork behavior at
src/auto-reply/reply/session.ts:733(the consumer ofparentSessionKey) is unit-tested insession.tsupstream; this change only verifies the field is propagated correctly, not that the fork-and-merge dance executes end-to-end against a real session store.Refs
resolveThreadSessionKeyshelper used by Slack, Discord, MS Teams, Mattermost, and Telegram (src/routing/session-key.ts:289-307).extensions/slack/src/monitor/message-handler/prepare.ts:265-274,1254-1271.extensions/slack/src/monitor/message-handler/dispatch.ts:145— bareprepared.message.channel.Summary by cubic
Scopes Google Chat agent sessions per thread in threaded spaces to stop transcript mixing between threads and align with other channels. DMs, group chats, and unthreaded spaces remain space-scoped, and replies still go to the right thread.
route.sessionKeywith:thread:<name>inTHREADED_MESSAGESspaces (or legacytype: ROOM) viaresolveThreadSessionKeys.resolveGoogleChatThreadName(event).createInboundEnvelopeBuilderso timestamps read the thread-scoped session; addsparentSessionKeyso new threads inherit space context.conversationIdto the bare space id to scope suppression per space (not per thread).space.spaceType(DIRECT_MESSAGE→ direct), falling back to legacytypeonly when needed.Written for commit 62fd3e6. Summary will update on new commits. Review in cubic