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

推荐订阅源

博客园 - 司徒正美
大猫的无限游戏
大猫的无限游戏
腾讯CDC
J
Java Code Geeks
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
V
Visual Studio Blog
人人都是产品经理
人人都是产品经理
博客园 - Franky
博客园 - 聂微东
阮一峰的网络日志
阮一峰的网络日志
美团技术团队
云风的 BLOG
云风的 BLOG
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
U
Unit 42
雷峰网
雷峰网
B
Blog RSS Feed
博客园_首页
量子位
F
Fortinet All Blogs
罗磊的独立博客
H
Hackread – Cybersecurity News, Data Breaches, AI and More
酷 壳 – CoolShell
酷 壳 – CoolShell
C
Check Point Blog

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
refactor(channels): share progress draft primitives · ope...
obviyus · 2026-06-03 · via Recent Commits to openclaw:main
11

import { formatReasoningMessage } from "../agents/embedded-agent-utils.js";

2+

import { findCodeRegions, isInsideCode } from "../shared/text/code-regions.js";

23

import { stripInlineDirectiveTagsForDelivery } from "../utils/directive-tags.js";

4+

import { removeChannelProgressDraftLine } from "./progress-draft-lines.js";

35

import {

46

createChannelProgressDraftGate,

57

type ChannelProgressDraftLine,

@@ -89,10 +91,8 @@ export function createChannelProgressDraftCompositor(params: {

8991

});

90929193

const clearLine = async (lineId: string) => {

92-

const nextLines = lines.filter(

93-

(line) => typeof line !== "object" || line.id?.trim() !== lineId,

94-

);

95-

if (nextLines.length === lines.length) {

94+

const nextLines = removeChannelProgressDraftLine(lines, lineId);

95+

if (nextLines === lines) {

9696

return;

9797

}

9898

lines = nextLines;

@@ -280,7 +280,11 @@ export function createChannelProgressDraftCompositor(params: {

280280

}

281281282282

function normalizeReasoningProgressLine(text: string): string {

283-

return stripReasoningProgressTags(text)

283+

const reasoningText = readReasoningProgressTextOutsideCode(text);

284+

if (reasoningText === undefined) {

285+

return "";

286+

}

287+

return stripReasoningProgressTagsOutsideCode(reasoningText)

284288

.replace(

285289

/^\s*(?:>\s*)?(?:Reasoning:\s*(?:\r?\n|\r)\s*|Thinking\.{0,3}\s*(?:\r?\n|\r)\s*(?:\r?\n|\r)\s*)/i,

286290

"",

@@ -289,10 +293,75 @@ function normalizeReasoningProgressLine(text: string): string {

289293

.trim();

290294

}

291295292-

function stripReasoningProgressTags(text: string): string {

293-

return text.replace(

294-

/<\s*\/?\s*(?:(?:antml:)?(?:think(?:ing)?|thought)|antthinking)\b[^<>]*>/giu,

295-

"",

296+

const REASONING_PROGRESS_TAG_RE =

297+

/<\s*(\/?)\s*(?:(?:antml:)?(?:think(?:ing)?|thought)|antthinking)\b[^<>]*>/giu;

298+

const REASONING_PROGRESS_TAG_NAMES = [

299+

"think",

300+

"thinking",

301+

"thought",

302+

"antthinking",

303+

"antml:think",

304+

"antml:thinking",

305+

"antml:thought",

306+

] as const;

307+

const REASONING_PROGRESS_TAG_PREFIXES = REASONING_PROGRESS_TAG_NAMES.flatMap((name) => [

308+

`<${name}`,

309+

`</${name}`,

310+

]);

311+312+

function readReasoningProgressTextOutsideCode(text: string): string | undefined {

313+

if (isPartialReasoningProgressTagPrefix(text)) {

314+

return undefined;

315+

}

316+

const codeRegions = findCodeRegions(text);

317+

let hasTags = false;

318+

let inReasoning = false;

319+

let cursor = 0;

320+

const chunks: string[] = [];

321+

for (const match of text.matchAll(REASONING_PROGRESS_TAG_RE)) {

322+

const offset = match.index ?? 0;

323+

if (isInsideCode(offset, codeRegions)) {

324+

continue;

325+

}

326+

hasTags = true;

327+

if (match[1]) {

328+

if (inReasoning) {

329+

chunks.push(text.slice(cursor, offset));

330+

}

331+

inReasoning = false;

332+

cursor = offset + match[0].length;

333+

continue;

334+

}

335+

if (inReasoning) {

336+

chunks.push(text.slice(cursor, offset));

337+

}

338+

inReasoning = true;

339+

cursor = offset + match[0].length;

340+

}

341+

if (!hasTags) {

342+

return text;

343+

}

344+

if (inReasoning) {

345+

chunks.push(text.slice(cursor));

346+

}

347+

return chunks.join("").trim();

348+

}

349+350+

function isPartialReasoningProgressTagPrefix(text: string): boolean {

351+

const normalized = text.trimStart().toLowerCase();

352+

return (

353+

normalized.startsWith("<") &&

354+

!normalized.includes(">") &&

355+

REASONING_PROGRESS_TAG_PREFIXES.some(

356+

(prefix) => prefix.startsWith(normalized) || normalized.startsWith(prefix),

357+

)

358+

);

359+

}

360+361+

function stripReasoningProgressTagsOutsideCode(text: string): string {

362+

const codeRegions = findCodeRegions(text);

363+

return text.replace(REASONING_PROGRESS_TAG_RE, (match, _closing: string, offset: number) =>

364+

isInsideCode(offset, codeRegions) ? match : "",

296365

);

297366

}

298367

@@ -367,13 +436,18 @@ function mergeReasoningProgressText(

367436

}

368437

const normalizedCurrent = normalizeReasoningProgressInput(current);

369438

const normalizedIncoming = normalizeReasoningProgressInput(incoming);

370-

if (!normalizedIncoming || normalizedIncoming === normalizedCurrent) {

439+

if (!normalizedIncoming) {

440+

return shouldAppendEmptyReasoningProgressDelta(current, incoming)

441+

? `${current}${incoming}`

442+

: current;

443+

}

444+

if (normalizedIncoming === normalizedCurrent) {

371445

return current;

372446

}

373447

if (

374448

options?.snapshot === true ||

375449

isReasoningSnapshotText(incoming) ||

376-

normalizedIncoming.startsWith(normalizedCurrent)

450+

(normalizedCurrent && normalizedIncoming.startsWith(normalizedCurrent))

377451

) {

378452

return incoming;

379453

}

@@ -385,3 +459,21 @@ function isReasoningSnapshotText(text: string): boolean {

385459

text,

386460

);

387461

}

462+463+

function shouldAppendEmptyReasoningProgressDelta(current: string, incoming: string): boolean {

464+

return (

465+

isPartialReasoningProgressTagPrefix(current) ||

466+

isPartialReasoningProgressTagPrefix(incoming) ||

467+

hasReasoningProgressTagOutsideCode(incoming)

468+

);

469+

}

470+471+

function hasReasoningProgressTagOutsideCode(text: string): boolean {

472+

const codeRegions = findCodeRegions(text);

473+

for (const match of text.matchAll(REASONING_PROGRESS_TAG_RE)) {

474+

if (!isInsideCode(match.index ?? 0, codeRegions)) {

475+

return true;

476+

}

477+

}

478+

return false;

479+

}