import type { CliBackendParseJsonlEvent } from "openclaw/plugin-sdk/cli-backend"; import { isRecord } from "openclaw/plugin-sdk/string-coerce-runtime"; import { findCodeRegions, type CodeRegion } from "openclaw/plugin-sdk/text-chunking"; const CLAUDE_RAW_TOOL_OUTPUT_ERROR = "Claude CLI returned malformed tool output (invalid request format): raw tool protocol appeared as assistant text. OpenClaw refused to persist or deliver it."; const RAW_INVOKE_TAG_RE = /])[^<>\r\n]*>/gu; const RAW_PARAMETER_TAG_RE = /])[^<>\r\n]*>/gu; const RAW_INVOKE_CLOSE_TAG_RE = /<\/invoke[ \t]*>/gu; const RAW_PARAMETER_LOOKAHEAD_CHARS = 2_048; const OBSERVED_TRUNCATED_LEAK_PREFIXES = new Set(["call", "count", "court"]); function readTagNameAttribute(tag: string, elementName: "invoke" | "parameter"): string { const attribute = /[ \t]+([^\s=/>]+)[ \t]*=[ \t]*(?:"([^"]*)"|'([^']*)')/gy; const tagEnd = tag.length - 1; let cursor = elementName.length + 1; while (cursor < tagEnd) { attribute.lastIndex = cursor; const match = attribute.exec(tag); if (!match) { return ""; } if (match[1] === "name") { return match[2] ?? match[3] ?? ""; } cursor = attribute.lastIndex; } return ""; } function isStandaloneLineTag(text: string, index: number): boolean { const lineStart = text.lastIndexOf("\n", index - 1) + 1; return /^[ ]{0,3}$/u.test(text.slice(lineStart, index)); } function isStandaloneOpeningTagLine(text: string, index: number, tagLength: number): boolean { if (!isStandaloneLineTag(text, index)) { return false; } const lineEnd = text.indexOf("\n", index + tagLength); const trailingText = text.slice(index + tagLength, lineEnd === -1 ? text.length : lineEnd); return /^[ \t]*\r?$/u.test(trailingText); } function isInsideCodeRegion(index: number, regions: CodeRegion[]): boolean { let low = 0; let high = regions.length - 1; while (low <= high) { const middle = Math.floor((low + high) / 2); const region = regions[middle]; if (!region) { return false; } if (index < region.start) { high = middle - 1; } else if (index >= region.end) { low = middle + 1; } else { return true; } } return false; } function hasObservedTruncatedLeakPrefix(text: string, index: number, toolName: string): boolean { const previousNonEmptyLine = text .slice(0, index) .split(/\r?\n/gu) .findLast((line) => line.trim().length > 0) ?.trim(); return ( previousNonEmptyLine === toolName || (previousNonEmptyLine !== undefined && OBSERVED_TRUNCATED_LEAK_PREFIXES.has(previousNonEmptyLine)) ); } function findNextStandaloneTag( text: string, index: number, regions: CodeRegion[], pattern: RegExp, ): number | null { const scanner = new RegExp(pattern.source, pattern.flags); scanner.lastIndex = index; for (let match = scanner.exec(text); match; match = scanner.exec(text)) { if ( isStandaloneOpeningTagLine(text, match.index, match[0].length) && !isInsideCodeRegion(match.index, regions) ) { return match.index; } } return null; } /** Detect Claude's legacy tool protocol only when it occupies standalone assistant lines. */ function hasClaudeRawToolInvocation(text: string): boolean { if (!text.includes(" { const mightContainRawToolProtocol = (line.includes("