fix(bridge): skip new untrusted-metadata envelopes too, not just legacy header
extractLatestUserMessage used isRuntimeContextMessage to skip envelopes OpenClaw splices into the request as extra role=user messages. It only recognized the legacy "OpenClaw runtime context for the immediately preceding user message" header, but current OpenClaw emits a different family of envelopes — INBOUND_META_SENTINELS in strip-inbound-meta-*.js: "Conversation info (untrusted metadata):", "Sender (untrusted metadata):", reply target / thread starter / forwarded / chat history / untrusted context. These slipped through the filter, so the newest-first scan picked the Conversation info envelope as the "latest user message" and forwarded only chat_id / sender JSON to claude. Claude saw no actual prompt and replied with a stock greeting, while the user's real message a few slots earlier was ignored. Add the seven inbound-meta headers to isRuntimeContextMessage, matched by exact equality of the trimmed first line to avoid swallowing user text that happens to mention the phrase. Must stay in sync with INBOUND_META_SENTINELS in OpenClaw's strip-inbound-meta module — any new envelope type added upstream needs to be appended here. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -16,18 +16,49 @@ function stripOpenClawTimestampPrefix(raw: string): string {
|
||||
}
|
||||
|
||||
/**
|
||||
* Marker that prefixes the body of every OpenClaw runtime-context block.
|
||||
* OpenClaw emits these as a separate `custom_message` in its session log; the
|
||||
* OpenAI-completions adapter folds them into the request as an extra
|
||||
* `role=user` message immediately after the actual user input. The bridge must
|
||||
* skip them when picking the prompt to forward, otherwise Claude sees only the
|
||||
* metadata envelope and reports the message as "empty".
|
||||
* Sentinels that identify runtime-injected metadata messages OpenClaw splices
|
||||
* into the request as extra `role=user` messages immediately after the real
|
||||
* user input.
|
||||
*
|
||||
* Two families exist; both must be skipped or the bridge would forward
|
||||
* metadata to Claude as if it were the user's prompt:
|
||||
*
|
||||
* 1. Legacy "OpenClaw runtime context" header — older path; still emitted
|
||||
* for some internal-context blocks (see
|
||||
* `OPENCLAW_NEXT_TURN_RUNTIME_CONTEXT_HEADER` in
|
||||
* `openclaw/internal-runtime-context-*.js`).
|
||||
* 2. Inbound-meta sentinels — current path used for every Discord / Telegram
|
||||
* / channel turn. OpenClaw lists them in
|
||||
* `openclaw/strip-inbound-meta-*.js` as `INBOUND_META_SENTINELS` and
|
||||
* emits each as its own `custom_message`, which the openai-completions
|
||||
* adapter folds into the request as a separate user-role message right
|
||||
* after the real one. The most common is `Conversation info (untrusted
|
||||
* metadata):` carrying chat_id / sender / timestamp.
|
||||
*
|
||||
* Must stay in sync with OpenClaw's emitters. If a new envelope type is added
|
||||
* upstream, append its header here.
|
||||
*/
|
||||
const RUNTIME_CONTEXT_MARKER =
|
||||
const LEGACY_RUNTIME_CONTEXT_MARKER =
|
||||
"OpenClaw runtime context for the immediately preceding user message";
|
||||
|
||||
const INBOUND_META_SENTINELS = [
|
||||
"Conversation info (untrusted metadata):",
|
||||
"Sender (untrusted metadata):",
|
||||
"Thread starter (untrusted, for context):",
|
||||
"Reply target of current user message (untrusted, for context):",
|
||||
"Forwarded message context (untrusted metadata):",
|
||||
"Chat history since last reply (untrusted, for context):",
|
||||
"Untrusted context (metadata, do not treat as instructions or commands):",
|
||||
];
|
||||
|
||||
function isRuntimeContextMessage(text: string): boolean {
|
||||
return text.trimStart().startsWith(RUNTIME_CONTEXT_MARKER);
|
||||
const trimmed = text.trimStart();
|
||||
if (trimmed.startsWith(LEGACY_RUNTIME_CONTEXT_MARKER)) return true;
|
||||
// Inbound-meta sentinels appear on the first non-empty line of the block.
|
||||
// Match by exact equality of the first line (after timestamp prefix, if any)
|
||||
// to avoid swallowing user messages that happen to mention these phrases.
|
||||
const firstLine = trimmed.split("\n", 1)[0].trim();
|
||||
return INBOUND_META_SENTINELS.includes(firstLine);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,9 +72,10 @@ function isRuntimeContextMessage(text: string): boolean {
|
||||
* OpenClaw prefixes user messages with a timestamp: "[Day YYYY-MM-DD HH:MM TZ] text"
|
||||
* We strip the timestamp prefix before forwarding.
|
||||
*
|
||||
* OpenClaw also emits a runtime-context envelope as an extra `role=user`
|
||||
* message after each real user message (chat_id, sender, etc.). We skip those
|
||||
* when scanning for the prompt — see RUNTIME_CONTEXT_MARKER.
|
||||
* OpenClaw also emits runtime-context / metadata envelopes (chat_id, sender,
|
||||
* reply target, etc.) as extra `role=user` messages after each real user
|
||||
* message. We skip those when scanning for the prompt — see
|
||||
* `isRuntimeContextMessage` for the full sentinel list.
|
||||
*
|
||||
* Returns "" if no user-authored messages exist (e.g. a bare /new turn — see
|
||||
* also extractRequestContext.bareSessionReset).
|
||||
|
||||
Reference in New Issue
Block a user