fix(bridge): recover inline-prefixed metadata in user message body #4

Merged
hzhang merged 1 commits from fix/inline-metadata-strip into main 2026-05-31 19:52:40 +00:00
Contributor

Summary

  • Contractor bridge's extractLatestUserMessage skipped any user message whose first line was an INBOUND_META_SENTINEL (e.g. "Conversation info (untrusted metadata):").
  • That's correct for OpenClaw's canonical convention where metadata is a SEPARATE follow-up user message (Discord/Telegram path).
  • Fabric.OpenclawPlugin dispatch passes metadata + real content as ONE merged user-message body, so the actual prompt sitting AFTER the metadata blocks was being dropped.
  • End-user symptom: contractor agents on Fabric channels silently fail to reply with HTTP 400 "no user message found". Surfaced recruiting developer1 (Claude bridge) on prod-t2 2026-05-31.

Fix

  • New helper stripPrefixedMetadataBlocks(raw) splits the body on blank-line boundaries, walks past leading sentinel-prefixed blocks, returns whatever non-metadata block follows.
  • When isRuntimeContextMessage(raw) matches, try stripPrefixedMetadataBlocks; if it yields content, use that. If the body really is metadata-only, fall through to the previous skip semantics.

Test plan

  • Local sanity: a body of Conversation info (untrusted metadata):\n\``json\n{...}\n```\n\nSender (...):\n```json\n{...}\n```\n\n欢迎来到招聘面试...→ returns欢迎来到招聘面试...(verified in code, blocks split by/\n\n+/`).
  • Deploy + re-run developer1 / developer2 recruitment sub-discussion on prod-t2: expect contractor sessions to actually reply (no longer 400).
## Summary - Contractor bridge's `extractLatestUserMessage` skipped any user message whose first line was an INBOUND_META_SENTINEL (e.g. "Conversation info (untrusted metadata):"). - That's correct for OpenClaw's canonical convention where metadata is a SEPARATE follow-up user message (Discord/Telegram path). - Fabric.OpenclawPlugin dispatch passes metadata + real content as ONE merged user-message body, so the actual prompt sitting AFTER the metadata blocks was being dropped. - End-user symptom: contractor agents on Fabric channels silently fail to reply with HTTP 400 "no user message found". Surfaced recruiting developer1 (Claude bridge) on prod-t2 2026-05-31. ## Fix - New helper `stripPrefixedMetadataBlocks(raw)` splits the body on blank-line boundaries, walks past leading sentinel-prefixed blocks, returns whatever non-metadata block follows. - When `isRuntimeContextMessage(raw)` matches, try `stripPrefixedMetadataBlocks`; if it yields content, use that. If the body really is metadata-only, fall through to the previous skip semantics. ## Test plan - [x] Local sanity: a body of `Conversation info (untrusted metadata):\n\`\`\`json\n{...}\n\`\`\`\n\nSender (...):\n\`\`\`json\n{...}\n\`\`\`\n\n欢迎来到招聘面试...` → returns `欢迎来到招聘面试...` (verified in code, blocks split by `/\n\n+/`). - [ ] Deploy + re-run developer1 / developer2 recruitment sub-discussion on prod-t2: expect contractor sessions to actually reply (no longer 400).
hzhang added 1 commit 2026-05-31 19:52:38 +00:00
OpenClaw's canonical convention is to emit metadata envelopes (chat_id,
sender, reply target, …) as SEPARATE user-role messages folded into the
openai-completions request right after the real one — `extractLatestUserMessage`
skips those whole.

Fabric.OpenclawPlugin's dispatch does not split: it passes metadata
blocks and the real user content as ONE merged user-message body,
separated by blank lines. With the prior filter that meant the entire
turn was dropped with "no user message found" (HTTP 400) because the
first line matched a sentinel — the actual prompt sitting after the
metadata blocks never reached the bridge.

When the whole-body check fails for a single-message body, walk past
leading sentinel-prefixed blocks (sentinel header + optional ```json
code fence + blank-line separator) and use whatever non-metadata block
follows. Falls back to the previous "skip entirely" semantics when the
body is metadata-only.

End-user symptom that surfaced this: every contractor agent (Claude /
Gemini) subscribed to a Fabric channel silently failed to reply to
sub-discussion messages during recruitment — fabric dispatch said
"completed" in 1.6s but trajectory had `assistantTexts: []`,
`terminalError: non_deliverable_terminal_turn`,
`errorMessage: "400 \"no user message found\""`. Surfaced
recruiting developer1 on prod-t2 2026-05-31.
hzhang merged commit 689e3da0ba into main 2026-05-31 19:52:40 +00:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: nav/ContractorAgent#4