fix(bridge): skip OpenClaw runtime-context envelope when picking prompt

OpenClaw emits its runtime-context block as a separate custom_message; the
openai-completions adapter folds that into the request as an extra role=user
message after the real user input. extractLatestUserMessage was taking the
last user message unconditionally, so Claude received only the metadata
envelope and replied "your message came through empty".

Walk user messages backward, skip ones starting with the runtime-context
marker, and return the most recent real user message instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
zhi
2026-04-29 08:19:59 +00:00
parent 4e015c677b
commit 91acce9b32

View File

@@ -16,7 +16,22 @@ function stripOpenClawTimestampPrefix(raw: string): string {
}
/**
* Extract the latest user message from the OpenClaw request.
* 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".
*/
const RUNTIME_CONTEXT_MARKER =
"OpenClaw runtime context for the immediately preceding user message";
function isRuntimeContextMessage(text: string): boolean {
return text.trimStart().startsWith(RUNTIME_CONTEXT_MARKER);
}
/**
* Extract the latest user-authored message from the OpenClaw request.
*
* OpenClaw accumulates all user messages and sends the full array every turn,
* but assistant messages may be missing if the previous response wasn't streamed
@@ -26,15 +41,22 @@ function stripOpenClawTimestampPrefix(raw: string): string {
* OpenClaw prefixes user messages with a timestamp: "[Day YYYY-MM-DD HH:MM TZ] text"
* We strip the timestamp prefix before forwarding.
*
* Returns "" if no user messages exist or the latest user message is empty
* (e.g. a bare /new turn — see also extractRequestContext.bareSessionReset).
* 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.
*
* Returns "" if no user-authored messages exist (e.g. a bare /new turn — see
* also extractRequestContext.bareSessionReset).
*/
export function extractLatestUserMessage(req: BridgeInboundRequest): string {
const userMessages = req.messages.filter((m) => m.role === "user");
if (userMessages.length === 0) return "";
const raw = messageText(userMessages[userMessages.length - 1]);
return stripOpenClawTimestampPrefix(raw);
for (let i = userMessages.length - 1; i >= 0; i -= 1) {
const raw = messageText(userMessages[i]);
if (!raw) continue;
if (isRuntimeContextMessage(raw)) continue;
return stripOpenClawTimestampPrefix(raw);
}
return "";
}
export type RequestContext = {