UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

193 lines (192 loc) 9.67 kB
//#region src/agents/internal-runtime-context.ts /** * Internal runtime-context delimiter and stripping helpers. * Protects runtime-generated prompt blocks from user text and removes old * context formats before replaying or comparing messages. */ /** Opening delimiter for protected OpenClaw runtime context blocks. */ const INTERNAL_RUNTIME_CONTEXT_BEGIN = "<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>"; /** Closing delimiter for protected OpenClaw runtime context blocks. */ const INTERNAL_RUNTIME_CONTEXT_END = "<<<END_OPENCLAW_INTERNAL_CONTEXT>>>"; const ESCAPED_INTERNAL_RUNTIME_CONTEXT_BEGIN = "[[OPENCLAW_INTERNAL_CONTEXT_BEGIN]]"; const ESCAPED_INTERNAL_RUNTIME_CONTEXT_END = "[[OPENCLAW_INTERNAL_CONTEXT_END]]"; /** Notice inserted into runtime-generated context blocks. */ const OPENCLAW_RUNTIME_CONTEXT_NOTICE = "This context is runtime-generated, not user-authored. Keep internal details private."; /** Header for context attached to the immediately preceding user message. */ const OPENCLAW_NEXT_TURN_RUNTIME_CONTEXT_HEADER = "OpenClaw runtime context for the immediately preceding user message."; /** Header for runtime events passed as prompt context. */ const OPENCLAW_RUNTIME_EVENT_HEADER = "OpenClaw runtime event."; /** Custom message type used for structured runtime-context messages. */ const OPENCLAW_RUNTIME_CONTEXT_CUSTOM_TYPE = "openclaw.runtime-context"; const LEGACY_INTERNAL_CONTEXT_HEADER = [ "OpenClaw runtime context (internal):", OPENCLAW_RUNTIME_CONTEXT_NOTICE, "" ].join("\n") + "\n"; const LEGACY_INTERNAL_EVENT_MARKER = "[Internal task completion event]"; const LEGACY_INTERNAL_EVENT_SEPARATOR = "\n\n---\n\n"; const LEGACY_UNTRUSTED_RESULT_BEGIN = "<<<BEGIN_UNTRUSTED_CHILD_RESULT>>>"; const LEGACY_UNTRUSTED_RESULT_END = "<<<END_UNTRUSTED_CHILD_RESULT>>>"; /** Escape protected context delimiters before embedding untrusted text. */ function escapeInternalRuntimeContextDelimiters(value) { return value.replaceAll(INTERNAL_RUNTIME_CONTEXT_BEGIN, ESCAPED_INTERNAL_RUNTIME_CONTEXT_BEGIN).replaceAll(INTERNAL_RUNTIME_CONTEXT_END, ESCAPED_INTERNAL_RUNTIME_CONTEXT_END); } function escapeRegExp(value) { return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); } function findDelimitedTokenIndex(text, token, from) { const tokenRe = new RegExp(`(?:^|\\r?\\n)${escapeRegExp(token)}(?=\\r?\\n|$)`, "g"); tokenRe.lastIndex = Math.max(0, from); const match = tokenRe.exec(text); if (!match) return -1; const prefixLength = match[0].length - token.length; return match.index + prefixLength; } function extractDelimitedBlocks(text, begin, end) { let next = text; const blocks = []; for (;;) { const start = findDelimitedTokenIndex(next, begin, 0); if (start === -1) return { text: next, blocks }; let cursor = start + begin.length; let depth = 1; let finish = -1; while (depth > 0) { const nextBegin = findDelimitedTokenIndex(next, begin, cursor); const nextEnd = findDelimitedTokenIndex(next, end, cursor); if (nextEnd === -1) break; if (nextBegin !== -1 && nextBegin < nextEnd) { depth += 1; cursor = nextBegin + begin.length; continue; } depth -= 1; finish = nextEnd; cursor = nextEnd + end.length; } const before = next.slice(0, start).trimEnd(); if (finish === -1 || depth !== 0) return { text: before, blocks }; const blockEnd = finish + end.length; blocks.push(next.slice(start, blockEnd).trim()); const after = next.slice(blockEnd).trimStart(); next = before && after ? `${before}\n\n${after}` : `${before}${after}`; } } function stripDelimitedBlock(text, begin, end) { return extractDelimitedBlocks(text, begin, end).text; } function findLegacyInternalEventEnd(text, start) { if (!text.startsWith(LEGACY_INTERNAL_EVENT_MARKER, start)) return null; const resultBegin = text.indexOf(LEGACY_UNTRUSTED_RESULT_BEGIN, start + 32); if (resultBegin === -1) return null; const resultEnd = text.indexOf(LEGACY_UNTRUSTED_RESULT_END, resultBegin + 34); if (resultEnd === -1) return null; const actionIndex = text.indexOf("\n\nAction:\n", resultEnd + 32); if (actionIndex === -1) return null; const afterAction = actionIndex + 10; const nextEvent = text.indexOf(`${LEGACY_INTERNAL_EVENT_SEPARATOR}${LEGACY_INTERNAL_EVENT_MARKER}`, afterAction); if (nextEvent !== -1) return nextEvent; const nextParagraph = text.indexOf("\n\n", afterAction); return nextParagraph === -1 ? text.length : nextParagraph; } function stripLegacyInternalRuntimeContext(text) { let next = text; let searchFrom = 0; for (;;) { const headerStart = next.indexOf(LEGACY_INTERNAL_CONTEXT_HEADER, searchFrom); if (headerStart === -1) return next; const eventStart = headerStart + LEGACY_INTERNAL_CONTEXT_HEADER.length; if (!next.startsWith(LEGACY_INTERNAL_EVENT_MARKER, eventStart)) { searchFrom = eventStart; continue; } let blockEnd = findLegacyInternalEventEnd(next, eventStart); if (blockEnd == null) { const nextParagraph = next.indexOf("\n\n", eventStart + 32); blockEnd = nextParagraph === -1 ? next.length : nextParagraph; } else while (next.startsWith(`${LEGACY_INTERNAL_EVENT_SEPARATOR}${LEGACY_INTERNAL_EVENT_MARKER}`, blockEnd)) { const nextEventStart = blockEnd + 7; const nextEventEnd = findLegacyInternalEventEnd(next, nextEventStart); if (nextEventEnd == null) break; blockEnd = nextEventEnd; } const before = next.slice(0, headerStart).trimEnd(); const after = next.slice(blockEnd).trimStart(); next = before && after ? `${before}\n\n${after}` : `${before}${after}`; searchFrom = Math.max(0, before.length - 1); } } function isRuntimeContextPromptHeader(line) { return line === "OpenClaw runtime context for the immediately preceding user message." || line === "OpenClaw runtime event."; } function stripRuntimeContextPromptPreface(text) { const lines = text.split(/\r?\n/); let changed = false; const output = []; for (let index = 0; index < lines.length; index += 1) { const line = lines[index] ?? ""; const nextLine = lines[index + 1] ?? ""; if (isRuntimeContextPromptHeader(line.trim()) && nextLine.trim() === "This context is runtime-generated, not user-authored. Keep internal details private.") { changed = true; index += 1; while (index + 1 < lines.length && (lines[index + 1] ?? "").trim() === "") index += 1; continue; } output.push(line); } return changed ? output.join("\n").replace(/\n{3,}/g, "\n\n").trim() : text; } /** Remove protected and legacy runtime-context blocks from text. */ function stripInternalRuntimeContext(text) { if (!text) return text; return stripRuntimeContextPromptPreface(stripLegacyInternalRuntimeContext(stripDelimitedBlock(text, INTERNAL_RUNTIME_CONTEXT_BEGIN, INTERNAL_RUNTIME_CONTEXT_END))); } /** Extract protected runtime-context blocks while returning remaining visible text. */ function extractInternalRuntimeContext(text) { const extracted = extractDelimitedBlocks(text, INTERNAL_RUNTIME_CONTEXT_BEGIN, INTERNAL_RUNTIME_CONTEXT_END); return { text: extracted.text, ...extracted.blocks.length > 0 ? { runtimeContext: extracted.blocks.join("\n\n") } : {} }; } /** Return true when text contains current or legacy runtime-context markers. */ function hasInternalRuntimeContext(text) { if (!text) return false; return findDelimitedTokenIndex(text, "<<<BEGIN_OPENCLAW_INTERNAL_CONTEXT>>>", 0) !== -1 || text.includes(LEGACY_INTERNAL_CONTEXT_HEADER) || text.includes(`OpenClaw runtime context for the immediately preceding user message.\nThis context is runtime-generated, not user-authored. Keep internal details private.`) || text.includes(`OpenClaw runtime event.\nThis context is runtime-generated, not user-authored. Keep internal details private.`); } function isOpenClawRuntimeContextCustomMessage(message) { if (!message || typeof message !== "object") return false; const candidate = message; return candidate.role === "custom" && candidate.customType === "openclaw.runtime-context"; } /** Remove all structured runtime-context custom messages. */ function stripRuntimeContextCustomMessages(messages) { if (!messages.some(isOpenClawRuntimeContextCustomMessage)) return messages; return messages.filter((message) => !isOpenClawRuntimeContextCustomMessage(message)); } function isUserMessage(message) { return Boolean(message && typeof message === "object" && message.role === "user"); } /** Keeps only current-turn runtime context positioned immediately before the active user. */ function stripHistoricalRuntimeContextCustomMessages(messages) { if (!messages.some(isOpenClawRuntimeContextCustomMessage)) return messages; const lastUserIndex = messages.findLastIndex(isUserMessage); if (lastUserIndex === -1) return messages.filter((message) => !isOpenClawRuntimeContextCustomMessage(message)); const currentRuntimeContextIndexes = /* @__PURE__ */ new Set(); for (let index = lastUserIndex - 1; index >= 0; index -= 1) { if (!isOpenClawRuntimeContextCustomMessage(messages[index])) break; currentRuntimeContextIndexes.add(index); } return messages.filter((message, index) => { if (!isOpenClawRuntimeContextCustomMessage(message)) return true; return currentRuntimeContextIndexes.has(index); }); } //#endregion export { OPENCLAW_RUNTIME_CONTEXT_NOTICE as a, extractInternalRuntimeContext as c, stripInternalRuntimeContext as d, stripRuntimeContextCustomMessages as f, OPENCLAW_RUNTIME_CONTEXT_CUSTOM_TYPE as i, hasInternalRuntimeContext as l, INTERNAL_RUNTIME_CONTEXT_END as n, OPENCLAW_RUNTIME_EVENT_HEADER as o, OPENCLAW_NEXT_TURN_RUNTIME_CONTEXT_HEADER as r, escapeInternalRuntimeContextDelimiters as s, INTERNAL_RUNTIME_CONTEXT_BEGIN as t, stripHistoricalRuntimeContextCustomMessages as u };