openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
1,087 lines (1,086 loc) • 43.2 kB
JavaScript
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { i as asOptionalRecord } from "./record-coerce-DHZ4bFlT.js";
import { s as asFiniteNumber } from "./number-coercion-CJQ8TR--.js";
import { c as parseAgentSessionKey } from "./session-key-utils-Bx3apsJ3.js";
import { f as isOpenClawDeliveryMirrorAssistantMessage } from "./transcript-Cw-EfeYD.js";
import { a as parseAssistantTextSignature, s as resolveAssistantMessagePhase } from "./chat-message-content-CnGjfPsN.js";
import "./internal-runtime-context-BH_40W4f.js";
import { v as stripEnvelopeFromMessages } from "./session-utils.fs-D_8-X4jl.js";
import { l as stripInterSessionPromptPrefixForDisplay, s as normalizeInputProvenance, t as INTER_SESSION_PROMPT_PREFIX_BASE } from "./input-provenance-CYQvfQZP.js";
import { i as stripInlineDirectiveTagsForDisplay } from "./directive-tags-Dg-nkHHo.js";
import { n as HEARTBEAT_PROMPT } from "./heartbeat-CicGJ6Bb.js";
import { n as isHeartbeatOkResponse, r as isHeartbeatUserMessage } from "./heartbeat-filter-D4dPmFvN.js";
import { n as isSuppressedControlReplyText } from "./control-reply-text-F2bAzr1v.js";
import { createHash } from "node:crypto";
//#region src/chat/canvas-render.ts
function tryParseJsonRecord(value) {
if (typeof value !== "string") return;
try {
return asOptionalRecord(JSON.parse(value));
} catch {
return;
}
}
function getRecordStringField(record, key) {
const value = record?.[key];
return typeof value === "string" && value.trim() ? value : void 0;
}
function getRecordNumberField(record, key) {
const value = record?.[key];
return asFiniteNumber(value);
}
function getNestedRecord(record, key) {
const value = record?.[key];
return asOptionalRecord(value);
}
function normalizeSurface(value) {
return value === "assistant_message" ? value : void 0;
}
function normalizePreferredHeight(value) {
return typeof value === "number" && Number.isFinite(value) && value >= 160 ? Math.min(Math.trunc(value), 1200) : void 0;
}
function coerceCanvasPreview(record) {
if (!record) return;
if (getRecordStringField(record, "kind")?.trim().toLowerCase() !== "canvas") return;
const presentation = getNestedRecord(record, "presentation");
const view = getNestedRecord(record, "view");
const source = getNestedRecord(record, "source");
const requestedSurface = getRecordStringField(presentation, "target") ?? getRecordStringField(record, "target");
const surface = requestedSurface ? normalizeSurface(requestedSurface) : "assistant_message";
if (!surface) return;
const title = getRecordStringField(presentation, "title") ?? getRecordStringField(view, "title");
const preferredHeight = normalizePreferredHeight(getRecordNumberField(presentation, "preferred_height") ?? getRecordNumberField(presentation, "preferredHeight") ?? getRecordNumberField(view, "preferred_height") ?? getRecordNumberField(view, "preferredHeight"));
const className = getRecordStringField(presentation, "class_name") ?? getRecordStringField(presentation, "className");
const style = getRecordStringField(presentation, "style");
const viewUrl = getRecordStringField(view, "url") ?? getRecordStringField(view, "entryUrl");
const viewId = getRecordStringField(view, "id") ?? getRecordStringField(view, "docId");
if (viewUrl) return {
kind: "canvas",
surface,
render: "url",
url: viewUrl,
...viewId ? { viewId } : {},
...title ? { title } : {},
...preferredHeight ? { preferredHeight } : {},
...className ? { className } : {},
...style ? { style } : {}
};
if (getRecordStringField(source, "type")?.trim().toLowerCase() === "url") {
const url = getRecordStringField(source, "url");
if (!url) return;
return {
kind: "canvas",
surface,
render: "url",
url,
...title ? { title } : {},
...preferredHeight ? { preferredHeight } : {},
...className ? { className } : {},
...style ? { style } : {}
};
}
}
/** Extracts a canvas preview from a JSON-shaped tool or assistant payload. */
function extractCanvasFromText(outputText, _toolName) {
return coerceCanvasPreview(tryParseJsonRecord(outputText));
}
//#endregion
//#region src/gateway/chat-display-projection.ts
const DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS = 8e3;
/** Resolve the text cap used when projecting chat history for display. */
function resolveEffectiveChatHistoryMaxChars(_cfg, maxChars) {
if (typeof maxChars === "number") return maxChars;
return DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS;
}
function truncateChatHistoryText(text, maxChars = DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS) {
if (text.length <= maxChars) return {
text,
truncated: false
};
return {
text: `${text.slice(0, maxChars)}\n...(truncated)...`,
truncated: true
};
}
/** Return true for known tool-call/tool-result block type spellings in transcripts. */
function isToolHistoryBlockType(type) {
if (typeof type !== "string") return false;
const normalized = type.trim().toLowerCase();
return normalized === "toolcall" || normalized === "tool_call" || normalized === "tooluse" || normalized === "tool_use" || normalized === "toolresult" || normalized === "tool_result";
}
function extractChatHistoryBlockText(message) {
if (!message || typeof message !== "object") return;
const entry = message;
if (typeof entry.content === "string") return entry.content;
if (typeof entry.text === "string") return entry.text;
if (!Array.isArray(entry.content)) return;
const textParts = entry.content.map((block) => {
if (!block || typeof block !== "object") return;
const typed = block;
return typeof typed.text === "string" ? typed.text : void 0;
}).filter((value) => typeof value === "string");
return textParts.length > 0 ? textParts.join("\n") : void 0;
}
function appendCanvasBlockToAssistantHistoryMessage(params) {
const preview = params.preview;
if (!preview || !params.message || typeof params.message !== "object") return params.message;
const entry = params.message;
const baseContent = Array.isArray(entry.content) ? [...entry.content] : typeof entry.content === "string" ? [{
type: "text",
text: entry.content
}] : typeof entry.text === "string" ? [{
type: "text",
text: entry.text
}] : [];
if (!baseContent.some((block) => {
if (!block || typeof block !== "object") return false;
const typed = block;
return typed.type === "canvas" && typed.preview && typeof typed.preview === "object" && (typed.preview.viewId && typed.preview.viewId === preview.viewId || typed.preview.url && typed.preview.url === preview.url);
})) baseContent.push({
type: "canvas",
preview,
rawText: params.rawText
});
return {
...entry,
content: baseContent
};
}
function messageContainsToolHistoryContent(message) {
if (!message || typeof message !== "object") return false;
const entry = message;
if (typeof entry.toolCallId === "string" || typeof entry.tool_call_id === "string" || typeof entry.toolName === "string" || typeof entry.tool_name === "string") return true;
if (!Array.isArray(entry.content)) return false;
return entry.content.some((block) => {
if (!block || typeof block !== "object") return false;
return isToolHistoryBlockType(block.type);
});
}
function augmentChatHistoryWithCanvasBlocks(messages) {
if (messages.length === 0) return messages;
const next = [...messages];
let changed = false;
let lastAssistantIndex = -1;
let lastRenderableAssistantIndex = -1;
const pending = [];
for (let index = 0; index < next.length; index++) {
const message = next[index];
if (!message || typeof message !== "object") continue;
const entry = message;
if ((typeof entry.role === "string" ? entry.role.toLowerCase() : "") === "assistant") {
lastAssistantIndex = index;
if (!messageContainsToolHistoryContent(entry)) {
lastRenderableAssistantIndex = index;
if (pending.length > 0) {
let target = next[index];
for (const item of pending) target = appendCanvasBlockToAssistantHistoryMessage({
message: target,
preview: item.preview,
rawText: item.rawText
});
next[index] = target;
pending.length = 0;
changed = true;
}
}
continue;
}
if (!messageContainsToolHistoryContent(entry)) continue;
const toolName = typeof entry.toolName === "string" ? entry.toolName : typeof entry.tool_name === "string" ? entry.tool_name : void 0;
const text = extractChatHistoryBlockText(entry);
const preview = extractCanvasFromText(text, toolName);
if (!preview) continue;
pending.push({
preview,
rawText: text ?? null
});
}
if (pending.length > 0) {
const targetIndex = lastRenderableAssistantIndex >= 0 ? lastRenderableAssistantIndex : lastAssistantIndex;
if (targetIndex >= 0) {
let target = next[targetIndex];
for (const item of pending) target = appendCanvasBlockToAssistantHistoryMessage({
message: target,
preview: item.preview,
rawText: item.rawText
});
next[targetIndex] = target;
changed = true;
}
}
return changed ? next : messages;
}
function sanitizeChatHistoryContentBlock(block, opts) {
if (!block || typeof block !== "object") return {
block,
changed: false
};
const entry = { ...block };
let changed = false;
const preserveExactToolPayload = opts?.preserveExactToolPayload === true || isToolHistoryBlockType(entry.type);
const maxChars = opts?.maxChars ?? 8e3;
if (typeof entry.text === "string") {
const stripped = stripInlineDirectiveTagsForDisplay(entry.text);
if (preserveExactToolPayload) {
entry.text = stripped.text;
changed ||= stripped.changed;
} else {
const res = truncateChatHistoryText(stripped.text, maxChars);
entry.text = res.text;
changed ||= stripped.changed || res.truncated;
}
}
if (typeof entry.content === "string") {
const stripped = stripInlineDirectiveTagsForDisplay(entry.content);
if (preserveExactToolPayload) {
entry.content = stripped.text;
changed ||= stripped.changed;
} else {
const res = truncateChatHistoryText(stripped.text, maxChars);
entry.content = res.text;
changed ||= stripped.changed || res.truncated;
}
}
if (typeof entry.partialJson === "string" && !preserveExactToolPayload) {
const res = truncateChatHistoryText(entry.partialJson, maxChars);
entry.partialJson = res.text;
changed ||= res.truncated;
}
if (typeof entry.arguments === "string" && !preserveExactToolPayload) {
const res = truncateChatHistoryText(entry.arguments, maxChars);
entry.arguments = res.text;
changed ||= res.truncated;
}
if (typeof entry.thinking === "string") {
const res = truncateChatHistoryText(entry.thinking, maxChars);
entry.thinking = res.text;
changed ||= res.truncated;
}
if ("thinkingSignature" in entry) {
delete entry.thinkingSignature;
changed = true;
}
if ("openclawReasoningReplay" in entry) {
delete entry.openclawReasoningReplay;
changed = true;
}
const type = typeof entry.type === "string" ? entry.type : "";
if (type === "image" && typeof entry.data === "string") {
const bytes = Buffer.byteLength(entry.data, "utf8");
delete entry.data;
entry.omitted = true;
entry.bytes = bytes;
changed = true;
}
if (type === "audio" && entry.source && typeof entry.source === "object") {
const source = { ...entry.source };
if (source.type === "base64" && typeof source.data === "string") {
const bytes = Buffer.byteLength(source.data, "utf8");
delete source.data;
source.omitted = true;
source.bytes = bytes;
entry.source = source;
changed = true;
}
}
return {
block: changed ? entry : block,
changed
};
}
function sanitizeAssistantPhasedContentBlocks(content) {
if (!content.some((block) => {
if (!block || typeof block !== "object") return false;
const entry = block;
return entry.type === "text" && Boolean(parseAssistantTextSignature(entry.textSignature)?.phase);
})) return {
content,
changed: false
};
const filtered = content.filter((block) => {
if (!block || typeof block !== "object") return true;
const entry = block;
if (entry.type !== "text") return true;
return parseAssistantTextSignature(entry.textSignature)?.phase === "final_answer";
});
return {
content: filtered,
changed: filtered.length !== content.length
};
}
function projectAssistantTextFromMixedToolContent(content, maxChars) {
if (!content.some((block) => {
if (!block || typeof block !== "object") return false;
return isToolHistoryBlockType(block.type);
})) return null;
const textBlocks = [];
for (const block of content) {
if (!block || typeof block !== "object") continue;
const entry = block;
if (entry.type !== "text" || typeof entry.text !== "string" || !entry.text.trim()) continue;
const truncated = truncateChatHistoryText(stripInlineDirectiveTagsForDisplay(entry.text).text, maxChars);
if (truncated.text.trim()) textBlocks.push({
type: "text",
text: truncated.text
});
}
return textBlocks.length > 0 ? {
content: textBlocks,
changed: true
} : null;
}
function toFiniteNumber(x) {
return asFiniteNumber(x);
}
function sanitizeCost(raw) {
if (!raw || typeof raw !== "object") return;
const total = toFiniteNumber(raw.total);
return total !== void 0 ? { total } : void 0;
}
function sanitizeUsage(raw) {
if (!raw || typeof raw !== "object") return;
const u = raw;
const out = {};
for (const k of [
"input",
"output",
"total",
"totalTokens",
"inputTokens",
"outputTokens",
"promptTokens",
"completionTokens",
"cacheRead",
"cacheWrite",
"cache_read_input_tokens",
"cache_creation_input_tokens",
"input_tokens",
"output_tokens",
"prompt_tokens",
"completion_tokens",
"total_tokens"
]) {
const n = toFiniteNumber(u[k]);
if (n !== void 0) out[k] = n;
}
if ("cost" in u && u.cost != null && typeof u.cost === "object") {
const sanitizedCost = sanitizeCost(u.cost);
if (sanitizedCost) out.cost = sanitizedCost;
}
return Object.keys(out).length > 0 ? out : void 0;
}
function sanitizeChatHistoryMessage(message, maxChars = DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS) {
if (!message || typeof message !== "object") return {
message,
changed: false
};
const entry = { ...message };
let changed = false;
const role = typeof entry.role === "string" ? entry.role.toLowerCase() : "";
const preserveExactToolPayload = role === "toolresult" || role === "tool_result" || role === "tool" || role === "function" || typeof entry.toolName === "string" || typeof entry.tool_name === "string" || typeof entry.toolCallId === "string" || typeof entry.tool_call_id === "string";
if ("details" in entry) {
delete entry.details;
changed = true;
}
if (entry.role !== "assistant") {
if ("usage" in entry) {
delete entry.usage;
changed = true;
}
if ("cost" in entry) {
delete entry.cost;
changed = true;
}
} else {
if ("usage" in entry) {
const sanitized = sanitizeUsage(entry.usage);
if (sanitized) entry.usage = sanitized;
else delete entry.usage;
changed = true;
}
if ("cost" in entry) {
const sanitized = sanitizeCost(entry.cost);
if (sanitized) entry.cost = sanitized;
else delete entry.cost;
changed = true;
}
}
if (typeof entry.content === "string") {
const stripped = stripInlineDirectiveTagsForDisplay(entry.content);
if (preserveExactToolPayload) {
entry.content = stripped.text;
changed ||= stripped.changed;
} else {
const res = truncateChatHistoryText(stripped.text, maxChars);
entry.content = res.text;
changed ||= stripped.changed || res.truncated;
}
} else if (Array.isArray(entry.content)) {
const updated = entry.content.map((block) => sanitizeChatHistoryContentBlock(block, {
preserveExactToolPayload,
maxChars
}));
if (updated.some((item) => item.changed)) {
entry.content = updated.map((item) => item.block);
changed = true;
}
if (entry.role === "assistant" && Array.isArray(entry.content)) {
const mixedToolText = projectAssistantTextFromMixedToolContent(entry.content, maxChars);
if (mixedToolText) {
entry.content = mixedToolText.content;
if (entry.phase === "commentary") delete entry.phase;
changed = true;
} else {
const sanitizedPhases = sanitizeAssistantPhasedContentBlocks(entry.content);
if (sanitizedPhases.changed) {
entry.content = sanitizedPhases.content;
changed = true;
}
}
}
}
if (typeof entry.text === "string") {
const stripped = stripInlineDirectiveTagsForDisplay(entry.text);
if (preserveExactToolPayload) {
entry.text = stripped.text;
changed ||= stripped.changed;
} else {
const res = truncateChatHistoryText(stripped.text, maxChars);
entry.text = res.text;
changed ||= stripped.changed || res.truncated;
}
}
return {
message: changed ? entry : message,
changed
};
}
function extractAssistantTextForSilentCheck(message) {
if (!message || typeof message !== "object") return;
const entry = message;
if (entry.role !== "assistant") return;
if (typeof entry.text === "string") return entry.text;
if (typeof entry.content === "string") return entry.content;
if (!Array.isArray(entry.content) || entry.content.length === 0) return;
const texts = [];
for (const block of entry.content) {
if (!block || typeof block !== "object") return;
const typed = block;
if (typed.type !== "text" || typeof typed.text !== "string") return;
texts.push(typed.text);
}
return texts.length > 0 ? texts.join("\n") : void 0;
}
function hasAssistantNonTextContent(message) {
if (!message || typeof message !== "object") return false;
const content = message.content;
if (!Array.isArray(content)) return false;
return content.some((block) => block && typeof block === "object" && block.type !== "text");
}
function hasAssistantMixedToolVisibleText(message) {
if (!message || typeof message !== "object") return false;
const content = message.content;
if (!Array.isArray(content)) return false;
let hasToolHistoryBlock = false;
let hasText = false;
for (const block of content) {
if (!block || typeof block !== "object") continue;
const entry = block;
if (isToolHistoryBlockType(entry.type)) hasToolHistoryBlock = true;
if (entry.type === "text" && typeof entry.text === "string" && entry.text.trim()) hasText = true;
}
return hasToolHistoryBlock && hasText;
}
function normalizeToolHistoryType(value) {
const normalized = normalizeOptionalString(value)?.toLowerCase();
return normalized ? normalized.replace(/_/g, "") : void 0;
}
function parseJsonRecord(value) {
try {
return asOptionalRecord(JSON.parse(value));
} catch {
return;
}
}
function readMaybeJsonRecord(value) {
if (typeof value === "string") return parseJsonRecord(value);
return asOptionalRecord(value);
}
function readToolBlockName(block) {
const direct = normalizeOptionalString(block.name) ?? normalizeOptionalString(block.toolName) ?? normalizeOptionalString(block.tool_name) ?? normalizeOptionalString(block.tool);
if (direct) return direct;
const fn = asOptionalRecord(block.function);
return fn ? normalizeOptionalString(fn.name) : void 0;
}
function readToolBlockCallId(block) {
return normalizeOptionalString(block.id) ?? normalizeOptionalString(block.toolCallId) ?? normalizeOptionalString(block.tool_call_id) ?? normalizeOptionalString(block.callId) ?? normalizeOptionalString(block.call_id);
}
function readToolBlockArguments(block) {
for (const key of [
"arguments",
"input",
"args",
"params"
]) {
const args = readMaybeJsonRecord(block[key]);
if (args) return args;
}
const fn = asOptionalRecord(block.function);
if (fn) {
const args = readMaybeJsonRecord(fn.arguments);
if (args) return args;
}
return {};
}
function hasNonEmptyValue(value) {
if (typeof value === "string") return value.trim().length > 0;
if (Array.isArray(value)) return value.some(hasNonEmptyValue);
if (!value || typeof value !== "object") return value != null;
return Object.values(value).some(hasNonEmptyValue);
}
function hasExplicitMessageToolRoute(args) {
return [
"target",
"targets",
"to",
"recipient",
"recipients",
"chatId",
"chat_id",
"channelId",
"channel_id",
"conversationId",
"conversation_id",
"threadId",
"thread_id",
"roomId",
"room_id",
"groupId",
"group_id"
].some((field) => hasNonEmptyValue(args[field]));
}
function readMessageToolVisibleText(args) {
for (const field of [
"message",
"text",
"content",
"body",
"caption"
]) {
const value = args[field];
if (typeof value === "string" && value.trim()) return stripInlineDirectiveTagsForDisplay(value).text;
}
}
function isDryRunMessageToolRecord(record) {
if (record.dryRun === true || record.dry_run === true) return true;
return (normalizeOptionalString(record.deliveryStatus) ?? normalizeOptionalString(record.delivery_status) ?? normalizeOptionalString(record.status))?.toLowerCase() === "dry_run";
}
function extractMessageToolVisibleReplies(message) {
if (message.role !== "assistant" || !Array.isArray(message.content)) return [];
const replies = [];
for (const block of message.content) {
const record = asOptionalRecord(block);
if (!record) continue;
const type = normalizeToolHistoryType(record.type);
if (type !== "toolcall" && type !== "tooluse") continue;
if (readToolBlockName(record)?.toLowerCase() !== "message") continue;
const args = readToolBlockArguments(record);
if (normalizeOptionalString(args.action)?.toLowerCase() !== "send") continue;
if (isDryRunMessageToolRecord(args)) continue;
if (hasExplicitMessageToolRoute(args)) continue;
const text = readMessageToolVisibleText(args);
if (!text?.trim()) continue;
const toolCallId = readToolBlockCallId(record);
replies.push({
...toolCallId ? { toolCallId } : {},
text
});
}
return replies;
}
function isAssistantSilentControlReplyOnly(message) {
const text = extractAssistantTextForSilentCheck(message);
return text !== void 0 && isSuppressedControlReplyText(text) && !hasAssistantNonTextContent(message);
}
function isRenderableAssistantDisplayMessage(message) {
if (message.role !== "assistant") return false;
const text = extractAssistantTextForSilentCheck(message);
return text !== void 0 && !isSuppressedControlReplyText(text);
}
function readMessageToolResultName(message) {
return normalizeOptionalString(message.toolName) ?? normalizeOptionalString(message.tool_name) ?? normalizeOptionalString(message.name) ?? normalizeOptionalString(message.tool);
}
function readMessageToolResultCallId(message) {
return normalizeOptionalString(message.toolCallId) ?? normalizeOptionalString(message.tool_call_id) ?? normalizeOptionalString(message.callId) ?? normalizeOptionalString(message.call_id) ?? normalizeOptionalString(message.id);
}
function readToolResultOkValue(value) {
if (typeof value === "boolean") return value;
const record = readMaybeJsonRecord(value);
if (record && typeof record.ok === "boolean") return record.ok;
if (Array.isArray(value)) for (const block of value) {
const blockOk = readToolResultOkValue(block);
if (blockOk !== void 0) return blockOk;
const recordBlock = asOptionalRecord(block);
if (typeof recordBlock?.text === "string") {
const textOk = readToolResultOkValue(recordBlock.text);
if (textOk !== void 0) return textOk;
}
if (typeof recordBlock?.content === "string") {
const contentOk = readToolResultOkValue(recordBlock.content);
if (contentOk !== void 0) return contentOk;
}
}
}
function hasDryRunToolResultValue(value) {
const record = readMaybeJsonRecord(value);
if (record && isDryRunMessageToolRecord(record)) return true;
if (!Array.isArray(value)) return false;
return value.some((block) => {
if (hasDryRunToolResultValue(block)) return true;
const recordBlock = asOptionalRecord(block);
if (typeof recordBlock?.text === "string" && hasDryRunToolResultValue(recordBlock.text)) return true;
return typeof recordBlock?.content === "string" && hasDryRunToolResultValue(recordBlock.content);
});
}
function isSuccessfulMessageToolResult(message, pending) {
const role = typeof message.role === "string" ? message.role.toLowerCase().replace(/_/g, "") : "";
const toolName = readMessageToolResultName(message)?.toLowerCase();
if (role !== "toolresult" && role !== "tool" && role !== "function" && toolName !== "message") return false;
if (toolName && toolName !== "message") return false;
const resultCallId = readMessageToolResultCallId(message);
if (pending.toolCallId) return resultCallId === pending.toolCallId && isSuccessfulMessageToolResultPayload(message);
return isSuccessfulMessageToolResultPayload(message);
}
function isSuccessfulMessageToolResultPayload(message) {
if (message.isError === true || message.error != null && message.error !== false) return false;
if (hasDryRunToolResultValue(message.result) || hasDryRunToolResultValue(message.output) || hasDryRunToolResultValue(message.content) || hasDryRunToolResultValue(message.text)) return false;
return (readToolResultOkValue(message.result) ?? readToolResultOkValue(message.output) ?? readToolResultOkValue(message.content) ?? readToolResultOkValue(message.text)) !== false;
}
function buildMessageToolVisibleReplyMirror(pending) {
const mirror = {
role: "assistant",
content: [{
type: "text",
text: pending.text
}],
openclawMessageToolMirror: {
toolName: "message",
...pending.toolCallId ? { toolCallId: pending.toolCallId } : {}
}
};
for (const field of [
"timestamp",
"createdAt",
"agentId"
]) if (pending.anchor[field] !== void 0) mirror[field] = pending.anchor[field];
const transcriptMeta = asOptionalRecord((pending.completionAnchor ?? pending.anchor)["__openclaw"]);
if (transcriptMeta) mirror["__openclaw"] = { ...transcriptMeta };
return mirror;
}
function readMessageToolDeliveryMirrorText(message) {
if (!isOpenClawDeliveryMirrorAssistantMessage(message)) return;
return displayTextForDuplicateCheck(message);
}
function mirrorMessageToolVisibleReplies(messages) {
if (messages.length === 0) return messages;
if (!messages.some((message) => asOptionalRecord(message))) return messages;
let changed = false;
const next = [];
const pending = [];
const clearPending = () => {
if (pending.length > 0) pending.length = 0;
};
const flushSucceededMirrors = () => {
for (const item of pending) {
if (!item.succeeded) continue;
next.push(buildMessageToolVisibleReplyMirror(item));
changed = true;
}
clearPending();
};
const flushSelectedMirrors = (items) => {
if (items.length === 0) return;
const selected = new Set(items);
const remaining = [];
for (const item of pending) {
if (selected.has(item) && item.succeeded) {
next.push(buildMessageToolVisibleReplyMirror(item));
changed = true;
continue;
}
remaining.push(item);
}
pending.length = 0;
pending.push(...remaining);
};
for (const message of messages) {
const record = asOptionalRecord(message);
if (!record) {
next.push(message);
continue;
}
if (record.role === "user" && isSessionsSendInterSessionUserMessage(record) || isProjectedSessionsSendForwardedMessage(record)) {
next.push(message);
continue;
}
if (record.role === "user") {
clearPending();
next.push(message);
continue;
}
const flushAfterCurrentMessage = [];
const deliveryMirrorText = readMessageToolDeliveryMirrorText(record);
const matchingDeliveryMirrorPending = deliveryMirrorText ? pending.filter((item) => item.text.trim() === deliveryMirrorText) : [];
const duplicateDeliveryMirror = matchingDeliveryMirrorPending.some((item) => item.succeeded);
const visibleReplies = extractMessageToolVisibleReplies(record);
if (visibleReplies.length > 0) for (const reply of visibleReplies) pending.push({
...reply,
anchor: record,
succeeded: false
});
else if (matchingDeliveryMirrorPending.length === 0 && isRenderableAssistantDisplayMessage(record)) clearPending();
if (pending.length > 0) {
for (const item of pending) if (!item.succeeded && isSuccessfulMessageToolResult(record, item)) {
item.succeeded = true;
item.completionAnchor = item.deliveryMirrorAnchor ?? record;
if (item.deliveryMirrorAnchor) {
if (typeof item.deliveryMirrorIndex === "number") next[item.deliveryMirrorIndex] = {
...item.deliveryMirrorAnchor,
display: false
};
flushAfterCurrentMessage.push(item);
}
}
if (isAssistantSilentControlReplyOnly(record)) flushSucceededMirrors();
}
if (duplicateDeliveryMirror) {
for (const item of matchingDeliveryMirrorPending) item.completionAnchor = record;
flushSelectedMirrors(matchingDeliveryMirrorPending);
changed = true;
continue;
}
for (const item of matchingDeliveryMirrorPending) {
item.deliveryMirrorAnchor = record;
item.deliveryMirrorIndex = next.length;
}
next.push(message);
flushSelectedMirrors(flushAfterCurrentMessage);
}
return changed ? next : messages;
}
function shouldDropAssistantHistoryMessage(message) {
if (!message || typeof message !== "object") return false;
const entry = message;
if (entry.role !== "assistant") return false;
if (isProjectedSessionsSendForwardedMessage(entry)) return false;
if (resolveAssistantMessagePhase(message) === "commentary") return !hasAssistantMixedToolVisibleText(message);
const text = extractAssistantTextForSilentCheck(message);
if (text === void 0 || !isSuppressedControlReplyText(text)) return false;
return !hasAssistantNonTextContent(message);
}
function sanitizeChatHistoryMessages(messages, maxChars = DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS) {
if (messages.length === 0) return messages;
let changed = false;
const next = [];
for (const message of messages) {
if (shouldDropAssistantHistoryMessage(message)) {
changed = true;
continue;
}
const res = sanitizeChatHistoryMessage(message, maxChars);
changed ||= res.changed;
if (shouldDropAssistantHistoryMessage(res.message)) {
changed = true;
continue;
}
next.push(res.message);
}
return changed ? next : messages;
}
function asRoleContentMessage(message) {
const role = typeof message.role === "string" ? message.role.toLowerCase() : "";
if (!role) return null;
return {
role,
...message.content !== void 0 ? { content: message.content } : message.text !== void 0 ? { content: message.text } : {}
};
}
function isEmptyTextOnlyContent(content) {
if (typeof content === "string") return content.trim().length === 0;
if (!Array.isArray(content)) return false;
if (content.length === 0) return true;
let sawText = false;
for (const block of content) {
if (!block || typeof block !== "object") return false;
const entry = block;
if (entry.type !== "text") return false;
sawText = true;
if (typeof entry.text !== "string" || entry.text.trim().length > 0) return false;
}
return sawText;
}
function hasTranscriptMediaPaths(message) {
return (Array.isArray(message.MediaPaths) ? message.MediaPaths : typeof message.MediaPath === "string" ? [message.MediaPath] : []).some((value) => typeof value === "string" && value.trim());
}
function extractProjectedText(content) {
if (typeof content === "string") return content;
if (!Array.isArray(content)) return "";
const parts = [];
for (const block of content) {
if (!block || typeof block !== "object") continue;
const text = block.text;
if (typeof text === "string") parts.push(text);
}
return parts.join("\n");
}
function digestTtsSupplementText(text) {
return createHash("sha256").update(text.trim()).digest("hex");
}
function readTtsSupplementMarker(message) {
const marker = message.openclawTtsSupplement;
if (!marker || typeof marker !== "object" || Array.isArray(marker)) return;
const entry = marker;
const textSha256 = typeof entry.textSha256 === "string" && entry.textSha256.trim() ? entry.textSha256.trim() : void 0;
const spokenText = typeof entry.spokenText === "string" && entry.spokenText.trim() ? entry.spokenText.trim() : void 0;
return textSha256 || spokenText ? {
textSha256,
spokenText
} : void 0;
}
function isAssistantTtsSupplementMessage(message) {
if (asRoleContentMessage(message)?.role !== "assistant") return false;
if (!readTtsSupplementMarker(message)) return false;
const content = message.content;
if (!Array.isArray(content)) return false;
let hasSupplementBlock = false;
for (const block of content) {
if (!block || typeof block !== "object") continue;
if (block.type !== "text") {
hasSupplementBlock = true;
continue;
}
const text = typeof block.text === "string" ? block.text.trim() : "";
if (text && text !== "Audio reply") return false;
}
return hasSupplementBlock;
}
function ttsSupplementMatchesAssistant(marker, message) {
if (asRoleContentMessage(message)?.role !== "assistant") return false;
if (isProjectedSessionsSendForwardedMessage(message)) return false;
if (readTtsSupplementMarker(message)) return false;
const text = extractProjectedText(message.content ?? message.text).trim();
if (!text) return false;
if (marker.textSha256 && digestTtsSupplementText(text) === marker.textSha256) return true;
return Boolean(marker.spokenText && text === marker.spokenText);
}
function mergeTtsSupplementContent(target, supplement) {
const supplementBlocks = Array.isArray(supplement.content) ? supplement.content.filter((block) => Boolean(block) && typeof block === "object" && block.type !== "text") : [];
if (supplementBlocks.length === 0) return target;
const targetContent = target.content;
if (Array.isArray(targetContent)) return {
...target,
content: [...targetContent, ...supplementBlocks]
};
const targetText = extractProjectedText(targetContent ?? target.text).trim();
return {
...target,
content: [...targetText ? [{
type: "text",
text: targetText
}] : [], ...supplementBlocks]
};
}
function mergeTtsSupplementMessages(messages) {
if (!messages.some(isAssistantTtsSupplementMessage)) return messages;
const merged = [];
let changed = false;
for (const message of messages) {
const marker = readTtsSupplementMarker(message);
if (marker && isAssistantTtsSupplementMessage(message)) {
let targetIndex = -1;
for (let i = merged.length - 1; i >= 0; i--) if (ttsSupplementMatchesAssistant(marker, merged[i])) {
targetIndex = i;
break;
}
if (targetIndex >= 0) {
merged[targetIndex] = mergeTtsSupplementContent(merged[targetIndex], message);
changed = true;
continue;
}
}
merged.push(message);
}
return changed ? merged : messages;
}
function isSubagentAnnounceInterSessionUserMessage(message) {
const provenance = normalizeInputProvenance(message.provenance);
if (provenance?.kind === "inter_session" && provenance.sourceTool === "subagent_announce") return true;
const text = extractProjectedText(message.content ?? message.text);
return text.includes("[Inter-session message]") && text.includes("sourceTool=subagent_announce");
}
function readChatHistoryRecordTimestampMs(message) {
const value = asOptionalRecord(asOptionalRecord(message)?.["__openclaw"])?.recordTimestampMs;
if (typeof value === "number" && Number.isFinite(value)) return value;
const timestamp = asOptionalRecord(message)?.timestamp;
return typeof timestamp === "number" && Number.isFinite(timestamp) ? timestamp : void 0;
}
function isSubagentAnnounceInterSessionUserChatHistoryMessage(message) {
const record = asOptionalRecord(message);
if (!record || record.role !== "user") return false;
const provenance = normalizeInputProvenance(record.provenance);
if (provenance?.kind === "inter_session" && provenance.sourceTool === "subagent_announce") return true;
const text = extractChatHistoryBlockText(record);
return typeof text === "string" && text.includes("[Inter-session message]") && text.includes("sourceTool=subagent_announce");
}
function isChatHistoryAssistantMessage(message) {
return asOptionalRecord(message)?.role === "assistant";
}
function dropPreSessionStartAnnouncePairs(messages, sessionStartedAt) {
if (sessionStartedAt === void 0 || messages.length === 0) return messages;
let changed = false;
const kept = [];
for (let i = 0; i < messages.length; i++) {
const current = messages[i];
if (isSubagentAnnounceInterSessionUserChatHistoryMessage(current)) {
const ts = readChatHistoryRecordTimestampMs(current);
if (typeof ts === "number" && ts < sessionStartedAt) {
const next = messages[i + 1];
const nextTs = readChatHistoryRecordTimestampMs(next);
if (isChatHistoryAssistantMessage(next) && typeof nextTs === "number" && nextTs < sessionStartedAt) i++;
changed = true;
continue;
}
}
kept.push(current);
}
return changed ? kept : messages;
}
function isSessionsSendInterSessionUserMessage(message) {
if (message.role !== "user") return false;
const provenance = normalizeInputProvenance(message.provenance);
return provenance?.kind === "inter_session" && provenance.sourceTool === "sessions_send";
}
function isProjectedSessionsSendForwardedMessage(message) {
if (message.role !== "assistant") return false;
const provenance = normalizeInputProvenance(message.provenance);
return provenance?.kind === "inter_session" && provenance.sourceTool === "sessions_send";
}
function isDisplayHiddenProjectedMessage(message) {
if (message.display === false) return true;
return message.role === "custom" && message.customType === "openclaw.runtime-context";
}
function shouldHideProjectedHistoryMessage(message) {
if (isDisplayHiddenProjectedMessage(message)) return true;
if (isProjectedSessionsSendForwardedMessage(message)) return false;
const roleContent = asRoleContentMessage(message);
if (!roleContent) return false;
if (roleContent.role === "user" && isSubagentAnnounceInterSessionUserMessage(message)) return true;
if (roleContent.role === "user" && isEmptyTextOnlyContent(message.content ?? message.text) && !hasTranscriptMediaPaths(message)) return true;
if (roleContent.role === "assistant" && isEmptyTextOnlyContent(message.content ?? message.text)) return false;
if (isHeartbeatUserMessage(roleContent, HEARTBEAT_PROMPT)) return true;
return isHeartbeatOkResponse(roleContent);
}
function openclawAssistantModel(message) {
return message.role === "assistant" && message.provider === "openclaw" && typeof message.model === "string" ? message.model : void 0;
}
function displayTextForDuplicateCheck(message) {
const text = extractProjectedText(message.content ?? message.text).trim();
return text ? text : void 0;
}
function isDuplicateAcpGatewayInjectedMessage(current, previousVisible) {
if (!previousVisible) return false;
if (openclawAssistantModel(previousVisible) !== "acp-runtime" || openclawAssistantModel(current) !== "gateway-injected") return false;
if (hasAssistantNonTextContent(previousVisible) || hasAssistantNonTextContent(current)) return false;
const previousText = displayTextForDuplicateCheck(previousVisible);
const currentText = displayTextForDuplicateCheck(current);
return Boolean(previousText && currentText && previousText === currentText);
}
function toProjectedMessages(messages) {
return messages.filter((message) => Boolean(message) && typeof message === "object" && !Array.isArray(message));
}
function filterVisibleProjectedHistoryMessages(messages) {
if (messages.length === 0) return messages;
let changed = false;
const visible = [];
for (let i = 0; i < messages.length; i++) {
const current = messages[i];
if (!current) continue;
const currentRoleContent = asRoleContentMessage(current);
const next = messages[i + 1];
const nextRoleContent = next ? asRoleContentMessage(next) : null;
if (currentRoleContent && nextRoleContent && isHeartbeatUserMessage(currentRoleContent, HEARTBEAT_PROMPT) && isHeartbeatOkResponse(nextRoleContent) && !isProjectedSessionsSendForwardedMessage(next)) {
changed = true;
i++;
continue;
}
if (shouldHideProjectedHistoryMessage(current)) {
changed = true;
continue;
}
if (isDuplicateAcpGatewayInjectedMessage(current, visible.at(-1))) {
changed = true;
continue;
}
visible.push(current);
}
return changed ? visible : messages;
}
function stripInterSessionPromptPrefixFromContent(content) {
if (typeof content === "string") return stripInterSessionPromptPrefixForDisplay(content);
if (!Array.isArray(content)) return content;
return content.map((block) => {
if (!block || typeof block !== "object" || Array.isArray(block)) return block;
const record = block;
if (typeof record.text !== "string") return block;
const stripped = stripInterSessionPromptPrefixForDisplay(record.text);
return stripped === record.text ? block : {
...record,
text: stripped
};
});
}
function extractPromptPrefixField(text, field) {
const prefixIndex = text.indexOf(INTER_SESSION_PROMPT_PREFIX_BASE);
if (prefixIndex === -1) return;
const lineEnd = text.indexOf("\n", prefixIndex);
const header = lineEnd === -1 ? text.slice(prefixIndex) : text.slice(prefixIndex, lineEnd);
const escapedField = field.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
return normalizeOptionalString(new RegExp(`(?:^|\\s)${escapedField}=([^\\s]+)`).exec(header)?.[1]);
}
function resolveSessionsSendForwardedSenderLabel(message) {
const provenance = normalizeInputProvenance(message.provenance);
const text = extractProjectedText(message.content ?? message.text);
const agentId = parseAgentSessionKey(provenance?.sourceSessionKey ?? extractPromptPrefixField(text, "sourceSession"))?.agentId;
return agentId ? `Forwarded from ${agentId}` : "Forwarded agent message";
}
function projectSessionsSendInterSessionMessages(messages) {
let changed = false;
const projected = messages.map((message) => {
if (!isSessionsSendInterSessionUserMessage(message)) return message;
changed = true;
const next = {
...message,
role: "assistant",
senderLabel: resolveSessionsSendForwardedSenderLabel(message)
};
if ("content" in next) next.content = stripInterSessionPromptPrefixFromContent(next.content);
if (typeof next.text === "string") next.text = stripInterSessionPromptPrefixForDisplay(next.text);
return next;
});
return changed ? projected : messages;
}
function projectChatDisplayMessages(messages, options) {
return sanitizeChatHistoryMessages(mergeTtsSupplementMessages(filterVisibleProjectedHistoryMessages(projectSessionsSendInterSessionMessages(toProjectedMessages(sanitizeChatHistoryMessages(mirrorMessageToolVisibleReplies(options?.stripEnvelope === false ? messages : stripEnvelopeFromMessages(messages)), Number.MAX_SAFE_INTEGER))))), options?.maxChars ?? 8e3);
}
function limitChatDisplayMessages(messages, maxMessages) {
if (typeof maxMessages !== "number" || !Number.isFinite(maxMessages) || maxMessages <= 0 || messages.length <= maxMessages) return messages;
return messages.slice(-Math.floor(maxMessages));
}
function projectRecentChatDisplayMessages(messages, options) {
return limitChatDisplayMessages(projectChatDisplayMessages(messages, options), options?.maxMessages);
}
function projectChatDisplayMessage(message, options) {
return projectChatDisplayMessages([message], options)[0];
}
//#endregion
export { projectChatDisplayMessages as a, sanitizeChatHistoryMessages as c, projectChatDisplayMessage as i, augmentChatHistoryWithCanvasBlocks as n, projectRecentChatDisplayMessages as o, dropPreSessionStartAnnouncePairs as r, resolveEffectiveChatHistoryMaxChars as s, DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS as t };