






















@@ -1,12 +1,16 @@
11import type { CurrentTurnPromptContext } from "../../agents/pi-embedded-runner/run/params.js";
2+import type { InboundTurnKind } from "../../channels/turn/kind.js";
23import { annotateInterSessionPromptText } from "../../sessions/input-provenance.js";
4+import { normalizeOptionalString } from "../../shared/string-coerce.js";
35import { HEARTBEAT_TRANSCRIPT_PROMPT } from "../heartbeat.js";
46import { buildInboundMediaNote } from "../media-note.js";
57import type { MsgContext, TemplateContext } from "../templating.js";
68import { appendUntrustedContext } from "./untrusted-context.js";
79810const REPLY_MEDIA_HINT =
911"To send an image back, prefer the message tool (media/path/filePath). If you must inline, use MEDIA:https://example.com/image.jpg (spaces ok, quote if needed) or a safe relative path like MEDIA:./image.jpg. Absolute and ~ paths only work when they stay inside your allowed file-read boundary; host file:// URLs are blocked. Keep caption in the text body.";
12+const ROOM_EVENT_PROMPT = "[OpenClaw room event]";
13+const ROOM_EVENT_VISIBLE_REPLY_CONTRACT = "message_tool_only";
10141115export function buildReplyPromptBodies(params: {
1216ctx: MsgContext;
@@ -45,9 +49,11 @@ export function buildReplyPromptBodies(params: {
4549 ? [mediaNote, mediaReplyHint, prefixedBody].filter(Boolean).join("\n").trim()
4650 : prefixedBody;
4751const transcriptBody = params.transcriptBody ?? params.effectiveBaseBody;
48-const transcriptCommandBodyRaw = mediaNote
49- ? [mediaNote, transcriptBody].filter(Boolean).join("\n").trim()
50- : transcriptBody;
52+const transcriptCommandBodyRaw = transcriptBody
53+ ? mediaNote
54+ ? [mediaNote, transcriptBody].filter(Boolean).join("\n").trim()
55+ : transcriptBody
56+ : "";
5157return {
5258 mediaNote,
5359 mediaReplyHint,
@@ -95,12 +101,43 @@ type ReplyPromptEnvelopeBaseParams = {
95101startupContextPrelude?: string | null;
96102softResetTail?: string;
97103isHeartbeat?: boolean;
104+turnKind?: InboundTurnKind;
98105};
99106107+function formatRoomEventLine(ctx: TemplateContext, body: string): string {
108+const messageId =
109+normalizeOptionalString(ctx.MessageSid) ?? normalizeOptionalString(ctx.MessageSidFull);
110+const sender =
111+normalizeOptionalString(ctx.SenderName) ??
112+normalizeOptionalString(ctx.SenderUsername) ??
113+normalizeOptionalString(ctx.SenderId);
114+const prefix = [messageId ? `#${messageId}` : undefined, sender].filter(Boolean).join(" ");
115+return prefix ? `${prefix}: ${body}` : body;
116+}
117+118+function buildRoomEventContext(params: ReplyPromptEnvelopeBaseParams): string {
119+const roomEventBody = params.hasUserBody
120+ ? params.baseBody.trim()
121+ : "[User sent media without caption]";
122+return [
123+"[OpenClaw turn]",
124+"kind: room_event",
125+`visible_reply_contract: ${ROOM_EVENT_VISIBLE_REPLY_CONTRACT}`,
126+params.inboundUserContext.trim() ? `Room context:\n${params.inboundUserContext.trim()}` : "",
127+`Current event:\n${formatRoomEventLine(params.sessionCtx, roomEventBody)}`,
128+"Treat this as observed room activity. Decide whether to act.",
129+]
130+.filter(Boolean)
131+.join("\n\n");
132+}
133+100134export function buildReplyPromptEnvelopeBase(
101135params: ReplyPromptEnvelopeBaseParams,
102136): ReplyPromptEnvelopeBase {
103137const softResetTail = params.softResetTail?.trim() ?? "";
138+const isRoomEvent = params.turnKind === "room_event";
139+const roomEventContext = buildRoomEventContext(params);
140+const currentTurnContextText = isRoomEvent ? roomEventContext : params.inboundUserContext.trim();
104141const resetModelBody = params.isBareSessionReset
105142 ? [
106143params.inboundUserContext,
@@ -113,20 +150,24 @@ export function buildReplyPromptEnvelopeBase(
113150.filter(Boolean)
114151.join("\n\n")
115152 : params.baseBody;
116-const effectiveBaseBody = params.hasUserBody
117- ? resetModelBody
118- : "[User sent media without caption]";
153+const effectiveBaseBody = isRoomEvent
154+ ? ROOM_EVENT_PROMPT
155+ : params.hasUserBody
156+ ? resetModelBody
157+ : "[User sent media without caption]";
119158const transcriptBody = params.isHeartbeat
120159 ? HEARTBEAT_TRANSCRIPT_PROMPT
121160 : params.isBareSessionReset
122161 ? softResetTail || `[OpenClaw session ${params.startupAction}]`
123- : params.hasUserBody
124- ? params.baseBody
125- : "[User sent media without caption]";
162+ : isRoomEvent
163+ ? ""
164+ : params.hasUserBody
165+ ? params.baseBody
166+ : "[User sent media without caption]";
126167const currentTurnContext: CurrentTurnPromptContext | undefined =
127-!params.isBareSessionReset && params.inboundUserContext.trim()
168+!params.isBareSessionReset && currentTurnContextText
128169 ? {
129-text: params.inboundUserContext,
170+text: currentTurnContextText,
130171promptJoiner: params.inboundUserContextPromptJoiner,
131172}
132173 : undefined;
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。