Skip to content

fix(whatsapp): use RawBody for command detection in group chats#639

Closed
mcinteerj wants to merge 1 commit intoopenclaw:mainfrom
mcinteerj:fix/whatsapp-rawbody-command-detection
Closed

fix(whatsapp): use RawBody for command detection in group chats#639
mcinteerj wants to merge 1 commit intoopenclaw:mainfrom
mcinteerj:fix/whatsapp-rawbody-command-detection

Conversation

@mcinteerj
Copy link
Copy Markdown
Contributor

Summary

WhatsApp group messages include structural context (history, sender labels) in the Body field for LLM context. This caused command detection to fail when the formatted body did not match exact command strings like /status.

Problem

In WhatsApp group chats, the gateway constructs a combinedBody with:

  • History context: [Chat messages since your last reply - for context]\n...
  • Current message with envelope: [WhatsApp ...] Sender: message
  • Sender metadata: \n[from: Name (phone)]

Commands like /status\n[from: Jake McInteer] failed exact matching against /status.

Solution

Add RawBody field to MsgContext carrying the clean message text (original Baileys message). Command detection, directive parsing, abort detection, and reset triggers now prefer RawBody over Body.

This separates concerns:

  • Body: Formatted LLM context (history, sender labels)
  • RawBody: Clean text for command matching

Changes

  • src/auto-reply/templating.ts: Add RawBody field to MsgContext
  • src/web/auto-reply.ts: Set RawBody: msg.body in WhatsApp gateway
  • src/auto-reply/reply/session.ts: Use RawBody for triggerBodyNormalized and reset triggers
  • src/auto-reply/reply.ts: Use RawBody for directive parsing
  • src/auto-reply/reply/abort.ts: Use RawBody for abort detection
  • src/auto-reply/reply/mentions.ts: Cleanup debug logging

Tests

  • session.test.ts: RawBody extraction, reset triggers, fallback to Body
  • abort.test.ts: /stop detection, bare word triggers
  • reply.raw-body.test.ts: /think, /model, /status directive parsing with structural wrappers

Related

Note

Discord has a similar combinedBody pattern that may need the same fix. See #638 for broader architectural discussion.

WhatsApp group messages include structural context (history, sender labels)
in the Body field for LLM context. This caused command detection to fail
when the formatted body did not match exact command strings like "/status".

Changes:
- Add RawBody field to MsgContext for clean message text
- WhatsApp gateway sets RawBody to msg.body (original Baileys message)
- Command detection, directive parsing, abort detection, and reset triggers
  now prefer RawBody over Body
- BodyStripped falls back to RawBody before Body

This separates concerns: Body carries formatted LLM context, RawBody carries
clean text for command matching.

Tests:
- session.test.ts: RawBody extraction, reset triggers, fallback
- abort.test.ts: /stop detection, bare word triggers
- reply.raw-body.test.ts: /think, /model, /status directive parsing

Related: openclaw#638 (RFC: Structured MsgContext and Decomposition of CombinedBody)

Note: Discord has a similar combinedBody pattern that may need the same fix.
@pasogott
Copy link
Copy Markdown
Contributor

#629

@steipete
Copy link
Copy Markdown
Contributor

Closing as already landed on main via #643 (commit e3cd431, 2026-01-10). Note: the SHAs from this PR won't appear in main because the work merged via a different PR/commit (squash/rewrite).

Copy link
Copy Markdown

@andersoncollab andersoncollab left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved as canonical BoardTableView. QA and security reviews pending.

ronan-dandelion-cult pushed a commit to karmaterminal/openclaw that referenced this pull request Apr 19, 2026
…ompaction

Per swim-35 convergent finding (Elliott journal evidence + Cael+Ronan
source trace): request_compaction() always failed because the call sites
in agent-runner-execution.ts and followup-runner.ts dropped the session's
active provider/model on the floor. resolveEmbeddedCompactionTarget then
fell through to DEFAULT_PROVIDER/DEFAULT_MODEL ('openai'/'gpt-5.4') which
no prince has auth for, producing 'Unknown model: openai/gpt-5.4' that
classified as 'reason=unknown' in the journal and an instant <1s failure.

Three coupled defects, all fixed:

1. Defect 2 (root cause): pass run.provider + run.model into
   compactEmbeddedPiSession at both call sites.

2. Defect 3 (caller lies to tool): both sites previously discarded the
   compactEmbeddedPiSession return value and unconditionally returned
   { ok: true, compacted: true } even on real failure. This caused
   incrementVolitionalCompactionCount to fire on phantom-successful
   compactions, lying to /status telemetry. Now honor the real result.

3. Defect 1 (silent failure): add log.warn on the resolve-with-failure
   branch in request-compaction-tool.ts so volitional failures are
   visible. Add 'unknown model' classifier branch in compact-reasons.ts
   so the journal stops saying 'reason=unknown' for this exact case.

Tests: 15/15 green on request-compaction-tool + compact-reasons.
tsc --noEmit clean across whole project.

Closes karmaterminal/openclaw-bootstrap#639
ronan-dandelion-cult pushed a commit to karmaterminal/openclaw that referenced this pull request Apr 19, 2026
…del + skip-reason log level

Three follow-up fixes from PR #191 review (copilot pass relayed by figs-as-ronan):

1. followup-runner.ts:283 — read inner-scope provider/model from the
   run: async (provider, model, ...) fallback dispatcher (line 207),
   not the outer run.provider/run.model (persisted primary). Otherwise
   a fallback-selected model still routes compaction through a primary
   that may be in cooldown — half-fix.

2. agent-runner-execution.ts:1020 — same shadowing fix; closure opens
   at line 805. Same failure mode.

3. request-compaction-tool.ts:169 — downgrade resolved-failure WARN
   to info-level when the reason matches a legitimate skip case
   (nothing to compact / below threshold / already compacted / no
   real conversation messages). Mirrors isCompactionSkipReason from
   commands-compact.ts:39 with a local copy to avoid cross-package
   coupling (agents/tools → auto-reply/reply). Real failure cases
   still WARN.

Findings 1 & 2 are correctness; finding 3 is observability polish.
ronan-dandelion-cult pushed a commit to karmaterminal/openclaw that referenced this pull request Apr 19, 2026
…alls

Scribe self-review pass (Cael's claude_session_start /pr-review-toolkit
on 37aae9c) caught the next failure mode after the original openclaw#639 bug
and the Copilot fold:

compactEmbeddedPiSession accepts authProfileId and uses it via
resolveEmbeddedCompactionTarget to pick credentials. Both call sites
(agent-runner-execution.ts:~1027, followup-runner.ts:~285) were not
threading it — so a session on a non-default auth profile (per-user
API key) would route compaction with the right provider/model but the
wrong credentials.

Conditional pattern mirrors agent-runner-execution.ts:841-844: thread
the persisted authProfileId only when the inner-scope provider matches
the persisted primary. On fallback to a different provider, the
persisted profile is wrong (keyed to primary), so leave undefined and
let resolveEmbeddedCompactionTarget pick the default profile for that
provider.

This is symmetric to findings 1+2 from the Copilot fold: same fallback-
shadowing class, one parameter deeper. Pre-existing bug, but the lane
is open and the fix is +2 conditional + 1 field per call site.
karmafeast added a commit to karmaterminal/openclaw that referenced this pull request Apr 19, 2026
…ompaction-provider

fix(openclaw#639): thread session provider/model through volitional compaction
ronan-dandelion-cult pushed a commit to karmaterminal/openclaw that referenced this pull request Apr 19, 2026
…le + status restoration

Updates RFC docs/design/continue-work-signal-v2.md to reflect the totality of changes since 107ca2b (the prior RFC edit) plus the two ship-gate PRs about to land:

- §4.3: document session provider/model threading through volitional compaction (openclaw#191 / bootstrap#639). Three coupled defects: root cause, caller-honesty (phantom-counter), visibility (`unknown_model` classifier + `isLegitSkipReason` helper + `log.warn` on resolve-with-fallback + scope-aware `authProfileId`).
- §6.1: add `[context-pressure:noop]` log anchor with reason taxonomy (window-zero / below-threshold / band-dedup); document the bootstrap#580 investigation cycle (`:reach`/`:skip` instrumentation, root cause = sentinel collision on band 0, fix = -1 sentinel).
- §6.3: clarify Discord/agent path through src/auto-reply/status.ts was reconnected at openclaw#187 + tested at #188 (the line had been silently dropped in an earlier refactor); note `volitional: N` is honest only after #191.
- §6.4: replace 'instrumentation is not currently in place' note with status of distinguishing-instrumentation work (openclaw#164/171/172/173).
- Appendix C.1: add 'Closed failure modes' table — phantom-counter, hedge-timer ref leak, band-0 dedup, precondition-skip blindness, Copilot summarization headers, dist-bundle satellite chunks, subagent-announce runtime path mismatch.
- Appendix D.2: add evidence-location rows for the new file paths (volitional threading sites; armHedgeTimer; status renderer; request-compaction-tool tests; context-pressure noop sites; agent-runner runtime promotion; subagent-announce co-location; F-NOISE scheduler test).
- Header: bump test count (~180 across 13 files, was '172 across 8') to reflect additions in #165, #170, #188, #193.

Skip-list (no RFC mention): #174 sessions/config raw-key sweep (internal hygiene); #173 Copilot log-enabled nits (micro-hygiene); 86134af removal of investigation breadcrumbs (cycle is folded into §6.1 narrative).

Refs:
  openclaw#191 head fc3f415 (in-flight, MERGEABLE/UNSTABLE, APPROVED)
  openclaw#193 head 14483a6   (in-flight, MERGEABLE/UNSTABLE, APPROVED x2)
  openclaw#187, #188 (merged d787890)
  openclaw#160, #162, #164, #165, #169, #170, #171, #172, #173, #174

🍆 in 🩲: this is a docs PR; if either #191 or #193 changes shape pre-merge the affected paragraph here will need a one-line touch-up.

Co-Authored-By: dandelion cult - ronan 🌊 <karmafeast@gmail.com>
ronan-dandelion-cult pushed a commit to karmaterminal/openclaw that referenced this pull request Apr 20, 2026
)

The empty catch in agent-runner.ts post-compaction lifecycle silently
swallowed spawnSubagentDirect rejections — staged TaskFlow records were
released by consumeStagedPostCompactionDelegates, then any throw left
zero trace in logs, journals, or operator events. Same bug class as
the openclaw#639 ship-gate we just fought.

Extract the per-delegate spawn loop into
continuation/post-compaction-dispatch.ts so the failure path is
testable without agent-runner integration fixtures. On failure, emit:

  - log.warn '[continuation:post-compaction-spawn-failed]' with
    error message, sessionKey, and task slice (mirrors
    delegate-dispatch.ts:223-232)
  - enqueueSystemEvent so the operator surface sees dropped work

Failures are isolated per-delegate; one rejection does not abort the
remaining staged delegates.

Test coverage: 5 cases including success, named-anchor + system-event
emission on rejection, per-delegate isolation, non-Error throwables,
and empty-list short-circuit.

Closes #203
cael-dandelion-cult pushed a commit to karmaterminal/openclaw that referenced this pull request Apr 20, 2026
)

The empty catch block in agent-runner.ts was silently swallowing spawn
failures for post-compaction delegates. Since consumeStagedPostCompactionDelegates
had already marked the TaskFlow as released, these failures resulted in
lost work with zero trace.

This fix extracts the post-compaction dispatch loop into a dedicated
helper function (dispatchPostCompactionDelegates) with proper error
handling that mirrors delegate-dispatch.ts:223-232:

- Logs warn with anchor [continuation:post-compaction-spawn-failed]
- Enqueues a system event so failures are observable

Bug-class precedent: openclaw#639 (same silent-catch pattern in delegate-dispatch).

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
cael-dandelion-cult pushed a commit to karmaterminal/openclaw that referenced this pull request Apr 20, 2026
…th (#209)

Adds behavioral test coverage for the openclaw#639 fix arc. These tests guard
against regression when the next provider/model rename or fallback
refactor happens.

Coverage:
- Counter truthfulness: getVolitionalCompactionCount increments ONLY on
  {ok:true, compacted:true}; legit-skip reasons and failures do not
  increment.
- Log level correctness: legit-skip → log.info with [resolved-skip]
  anchor; real failures → log.warn with [resolved-failure] anchor.
- Call-site threading: verifies triggerCompaction closure passes the
  session's actual provider/model/authProfileId to compactEmbeddedPiSession
  (not DEFAULT_PROVIDER/DEFAULT_MODEL).
- authProfileId fallback-drop: when compaction provider ≠ persisted
  primary, authProfileId is dropped (set to undefined).

Refs: openclaw#639

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
ronan-dandelion-cult pushed a commit to karmaterminal/openclaw that referenced this pull request Apr 20, 2026
…/error paths (#205)

- Route request-compaction-tool's resolved-failure warn log through
  classifyCompactionReason so journal lines carry the structured code
  (e.g. unknown_model, timeout, provider_error_4xx) alongside the raw
  reason string. Before: operators had to grep the freeform 'reason=...'
  field; after: they can pivot on a closed-taxonomy 'code=...' field.
- Mirror the same classification on the background-error branch so
  transport-level rejects (AbortError / module-not-found / network)
  surface a structured code instead of only the raw throw message.
- Add request-compaction-tool.classifier-emission.test.ts (4 cases):
  resolve-with-unknown_model warn includes code=unknown_model, generic
  'Compaction cancelled' warn emits some code= field (pinning emission
  contract for future classifier extensions), promise-rejection error
  path includes code=<classified>, legit-skip takes info path (not warn).
- Pre-existing isLegitSkipReason routes 'nothing to compact' /
  'below threshold' / 'already compacted' / 'no real conversation
  messages' to the info path unchanged; the classifier only runs on
  real failures. This preserves the openclaw#639 legit-skip-quiet posture
  while closing the #205 structured-logging gap.
- triggerCompaction catch sites in followup-runner.ts and
  agent-runner-execution.ts intentionally unchanged: their return flows
  through the warn sibling which now classifies, satisfying the #205
  acceptance criterion without extra touch on the catch itself.

pnpm tsgo clean. 14/14 request-compaction-tool + compact-reasons tests green.

Closes #205.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
silas-dandelion-cult added a commit to karmaterminal/openclaw that referenced this pull request Apr 20, 2026
…upstream

22 comment-edits across 13 files. All bare `#N` references that point
to karmaterminal/openclaw or karmaterminal/openclaw-bootstrap fork
issues now use the prefixed `karmaterminal/openclaw#N` (or
`karmaterminal/openclaw-bootstrap#N`) form so they cannot be silently
auto-linked to unrelated upstream openclaw/openclaw issues.

Citations changed:
- bug openclaw#639 -> karmaterminal/openclaw#639 (8 sites: compact-reasons.{ts,test.ts}, followup-runner.ts ×3, agent-runner-execution.ts ×3, request-compaction-tool.ts ×2)
- (#580) / See #580 -> (#580) / See #580 (4 sites: context-pressure.{ts,test.ts})
- (#531) -> (#531) (2 sites: scheduler.test.ts)
- issue #475 -> #475 (1 site: session-cost-usage.ts)
- bootstrap#475 -> karmaterminal/openclaw-bootstrap#475 (1 site: discoverAllSessions.test.ts; the line above already used the prefixed form, this was the laggard)
- Bug #473 fix / issue #473 -> #473 (3 sites: subagent-announce.continuation.runtime.{ts,test.ts})
- review gap on #188 -> review gap on #188 (1 site: status.test.ts; also dropped a named-prince prefix in the same test label, which separately satisfies part of #200)

Left alone (out of scope per issue):
- Pre-existing upstream openclaw/openclaw citations (openclaw#26905, openclaw#36142, openclaw#60552, openclaw#28491, openclaw#41981, openclaw#18264, openclaw#59428, openclaw#31311, openclaw#14869, openclaw#17971, openclaw#29683, openclaw#58409, openclaw#35481, openclaw#30115, openclaw#14396, openclaw#19537, openclaw#1205, openclaw#1435, openclaw#26881, openclaw#32228, openclaw#35074, openclaw#26881)  -- these were present at v2026.4.14 and resolve correctly.
- delegate-dispatch.ts:62 (`openclaw#189.`) -- already in the canonical prefixed form per the issue's exception note.

Refs #199
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants