惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

罗磊的独立博客
Recent Announcements
Recent Announcements
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
有赞技术团队
有赞技术团队
J
Java Code Geeks
T
The Blog of Author Tim Ferriss
MyScale Blog
MyScale Blog
人人都是产品经理
人人都是产品经理
aimingoo的专栏
aimingoo的专栏
U
Unit 42
The GitHub Blog
The GitHub Blog
云风的 BLOG
云风的 BLOG
T
Tailwind CSS Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
酷 壳 – CoolShell
酷 壳 – CoolShell
博客园 - 三生石上(FineUI控件)
Apple Machine Learning Research
Apple Machine Learning Research
小众软件
小众软件
Hugging Face - Blog
Hugging Face - Blog
博客园 - 司徒正美
腾讯CDC
I
InfoQ
GbyAI
GbyAI
博客园_首页

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(codex): sanitize elicitation approval text · openclaw...
Lucenx9 · 2026-04-25 · via Recent Commits to openclaw:main

@@ -30,7 +30,28 @@ const MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY = "connector_name";

3030

const MCP_TOOL_APPROVAL_TOOL_TITLE_KEY = "tool_title";

3131

const MCP_TOOL_APPROVAL_TOOL_DESCRIPTION_KEY = "tool_description";

3232

const MCP_TOOL_APPROVAL_TOOL_PARAMS_DISPLAY_KEY = "tool_params_display";

33+

const MAX_DISPLAY_PARAM_ENTRIES = 8;

3334

const MAX_DISPLAY_PARAM_VALUE_LENGTH = 120;

35+

const MAX_DISPLAY_VALUE_ARRAY_ITEMS = 8;

36+

const MAX_DISPLAY_VALUE_OBJECT_KEYS = 8;

37+

const MAX_DISPLAY_VALUE_DEPTH = 3;

38+

const DISPLAY_TEXT_SCAN_MAX_LENGTH = 4096;

39+

const ANSI_OSC_SEQUENCE_RE = new RegExp(

40+

String.raw`(?:\u001b]|\u009d)[^\u001b\u009c\u0007]*(?:\u0007|\u001b\\|\u009c)`,

41+

"g",

42+

);

43+

const ANSI_CONTROL_SEQUENCE_RE = new RegExp(

44+

String.raw`(?:\u001b\[[0-?]*[ -/]*[@-~]|\u009b[0-?]*[ -/]*[@-~]|\u001b[@-Z\\-_])`,

45+

"g",

46+

);

47+

const CONTROL_CHARACTER_RE = new RegExp(String.raw`[\u0000-\u001f\u007f-\u009f]+`, "g");

48+

const INVISIBLE_FORMATTING_CONTROL_RE = new RegExp(

49+

String.raw`[\u00ad\u034f\u061c\u200b-\u200f\u202a-\u202e\u2060-\u206f\ufeff\ufe00-\ufe0f\u{e0100}-\u{e01ef}]`,

50+

"gu",

51+

);

52+

const DANGLING_TERMINAL_SEQUENCE_SUFFIX_RE = new RegExp(

53+

String.raw`(?:\u001b\][^\u001b\u009c\u0007]*|\u009d[^\u001b\u009c\u0007]*|\u001b\[[0-?]*[ -/]*|\u009b[0-?]*[ -/]*|\u001b)$`,

54+

);

34553556

export async function handleCodexAppServerElicitationRequest(params: {

3657

requestParams: JsonValue | undefined;

@@ -97,14 +118,15 @@ function readBridgeableApprovalElicitation(

97118

return undefined;

98119

}

99120100-

const title = readString(requestParams, "message") ?? "Codex MCP tool approval";

121+

const title =

122+

sanitizeDisplayText(readString(requestParams, "message") ?? "") || "Codex MCP tool approval";

101123

return {

102124

title,

103125

description: buildApprovalDescription({

104126

title,

105127

meta: requestParams._meta,

106128

requestedSchema,

107-

serverName: readString(requestParams, "serverName"),

129+

serverName: sanitizeOptionalDisplayText(readString(requestParams, "serverName")),

108130

}),

109131

requestedSchema,

110132

meta: requestParams._meta,

@@ -117,13 +139,20 @@ function buildApprovalDescription(params: {

117139

requestedSchema: JsonObject;

118140

serverName: string | undefined;

119141

}): string {

142+

const connectorName = sanitizeOptionalDisplayText(

143+

readString(params.meta, MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY),

144+

);

145+

const toolTitle = sanitizeOptionalDisplayText(

146+

readString(params.meta, MCP_TOOL_APPROVAL_TOOL_TITLE_KEY),

147+

);

148+

const toolDescription = sanitizeOptionalDisplayText(

149+

readString(params.meta, MCP_TOOL_APPROVAL_TOOL_DESCRIPTION_KEY),

150+

);

120151

const summaryLines = [

121-

readString(params.meta, MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY) &&

122-

`App: ${readString(params.meta, MCP_TOOL_APPROVAL_CONNECTOR_NAME_KEY)}`,

123-

readString(params.meta, MCP_TOOL_APPROVAL_TOOL_TITLE_KEY) &&

124-

`Tool: ${readString(params.meta, MCP_TOOL_APPROVAL_TOOL_TITLE_KEY)}`,

152+

connectorName && `App: ${connectorName}`,

153+

toolTitle && `Tool: ${toolTitle}`,

125154

params.serverName && `MCP server: ${params.serverName}`,

126-

readString(params.meta, MCP_TOOL_APPROVAL_TOOL_DESCRIPTION_KEY),

155+

toolDescription,

127156

].filter((line): line is string => Boolean(line));

128157

const paramLines = readDisplayParamLines(params.meta);

129158

const propertyLines = readPropertyDescriptionLines(params.requestedSchema);

@@ -145,8 +174,11 @@ function readPropertyDescriptionLines(requestedSchema: JsonObject): string[] {

145174

if (!schema) {

146175

return undefined;

147176

}

148-

const propTitle = readString(schema, "title") ?? name;

149-

const description = readString(schema, "description");

177+

const propTitle =

178+

sanitizeDisplayText(readString(schema, "title") ?? "") ||

179+

sanitizeDisplayText(name) ||

180+

"field";

181+

const description = sanitizeOptionalDisplayText(readString(schema, "description"));

150182

return description ? `- ${propTitle}: ${description}` : `- ${propTitle}`;

151183

})

152184

.filter((line): line is string => Boolean(line));

@@ -157,26 +189,105 @@ function readDisplayParamLines(meta: JsonObject): string[] {

157189

if (!Array.isArray(displayParams)) {

158190

return [];

159191

}

160-

return displayParams

192+

const lines = displayParams

193+

.slice(0, MAX_DISPLAY_PARAM_ENTRIES)

161194

.map((entry) => {

162195

const param = isJsonObject(entry) ? entry : undefined;

163196

if (!param) {

164197

return undefined;

165198

}

166-

const name = readString(param, "display_name") ?? readString(param, "name");

199+

const name =

200+

sanitizeOptionalDisplayText(readString(param, "display_name")) ??

201+

sanitizeOptionalDisplayText(readString(param, "name"));

167202

if (!name) {

168203

return undefined;

169204

}

170205

return `- ${name}: ${formatDisplayParamValue(param.value)}`;

171206

})

172207

.filter((line): line is string => Boolean(line));

208+

const remaining = displayParams.length - MAX_DISPLAY_PARAM_ENTRIES;

209+

return remaining > 0 ? [...lines, `- Additional parameters: ${remaining} more`] : lines;

173210

}

174211175212

function formatDisplayParamValue(value: JsonValue | undefined): string {

176-

const formatted = typeof value === "string" ? value : JSON.stringify(value ?? null);

177-

return formatted.length <= MAX_DISPLAY_PARAM_VALUE_LENGTH

178-

? formatted

179-

: `${formatted.slice(0, MAX_DISPLAY_PARAM_VALUE_LENGTH - 3)}...`;

213+

const formatted = typeof value === "string" ? value : formatDisplayJsonValue(value ?? null);

214+

return truncateDisplayText(sanitizeDisplayText(formatted), MAX_DISPLAY_PARAM_VALUE_LENGTH);

215+

}

216+217+

function formatDisplayJsonValue(value: JsonValue, depth = MAX_DISPLAY_VALUE_DEPTH): string {

218+

if (value === null) {

219+

return "null";

220+

}

221+

if (typeof value === "string") {

222+

return JSON.stringify(truncateDisplayText(sanitizeDisplayText(value), 80));

223+

}

224+

if (typeof value === "number" || typeof value === "boolean") {

225+

return String(value);

226+

}

227+

if (Array.isArray(value)) {

228+

if (depth <= 0) {

229+

return "[truncated]";

230+

}

231+

const parts: string[] = [];

232+

const limit = Math.min(value.length, MAX_DISPLAY_VALUE_ARRAY_ITEMS);

233+

for (let i = 0; i < limit; i += 1) {

234+

parts.push(formatDisplayJsonValue(value[i] ?? null, depth - 1));

235+

}

236+

if (value.length > MAX_DISPLAY_VALUE_ARRAY_ITEMS) {

237+

parts.push("...");

238+

}

239+

return `[${parts.join(",")}]`;

240+

}

241+

if (typeof value === "object") {

242+

if (depth <= 0) {

243+

return "{truncated}";

244+

}

245+

const parts: string[] = [];

246+

let count = 0;

247+

let truncated = false;

248+

for (const key in value) {

249+

if (!Object.prototype.hasOwnProperty.call(value, key)) {

250+

continue;

251+

}

252+

if (count >= MAX_DISPLAY_VALUE_OBJECT_KEYS) {

253+

truncated = true;

254+

break;

255+

}

256+

const safeKey = truncateDisplayText(sanitizeDisplayText(key), 80);

257+

parts.push(

258+

`${JSON.stringify(safeKey)}:${formatDisplayJsonValue(value[key] ?? null, depth - 1)}`,

259+

);

260+

count += 1;

261+

}

262+

if (truncated) {

263+

parts.push("...");

264+

}

265+

return `{${parts.join(",")}}`;

266+

}

267+

return "null";

268+

}

269+270+

function sanitizeOptionalDisplayText(value: string | undefined): string | undefined {

271+

const sanitized = value === undefined ? "" : sanitizeDisplayText(value);

272+

return sanitized || undefined;

273+

}

274+275+

function sanitizeDisplayText(value: string): string {

276+

const scanned = value.slice(0, DISPLAY_TEXT_SCAN_MAX_LENGTH);

277+

const clipped = value.length > DISPLAY_TEXT_SCAN_MAX_LENGTH;

278+

const sanitized = scanned

279+

.replace(ANSI_OSC_SEQUENCE_RE, "")

280+

.replace(ANSI_CONTROL_SEQUENCE_RE, "")

281+

.replace(DANGLING_TERMINAL_SEQUENCE_SUFFIX_RE, "")

282+

.replace(INVISIBLE_FORMATTING_CONTROL_RE, " ")

283+

.replace(CONTROL_CHARACTER_RE, " ")

284+

.replace(/\s+/g, " ")

285+

.trim();

286+

return clipped ? `${sanitized}...` : sanitized;

287+

}

288+289+

function truncateDisplayText(value: string, maxLength: number): string {

290+

return value.length <= maxLength ? value : `${value.slice(0, Math.max(0, maxLength - 3))}...`;

180291

}

181292182293

async function requestPluginApprovalOutcome(params: {