From 87de389cd2524349bfff29055c6b77b5b638e976 Mon Sep 17 00:00:00 2001 From: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> Date: Tue, 10 Nov 2026 04:53:29 +0800 Subject: [PATCH] fix(chat): retain ordered list starting numbers Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com> --- .../smoke/bound-conversation-http-fixture.py | 15 ++++++++++++++- .../smoke/conversation-reply-browser-smoke.mjs | 9 +++++++++ .../personal-workspace/markdown-blocks.ts | 11 ++++++----- .../src/features/personal-workspace/markdown.tsx | 2 +- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/apps/presentation/dashboard/smoke/bound-conversation-http-fixture.py b/apps/presentation/dashboard/smoke/bound-conversation-http-fixture.py index eef47e7924..e5959dc117 100644 --- a/apps/presentation/dashboard/smoke/bound-conversation-http-fixture.py +++ b/apps/presentation/dashboard/smoke/bound-conversation-http-fixture.py @@ -102,7 +102,20 @@ def start(self, server): from loopx.extensions.lark.conversation_identity import identity_ref from loopx.chat_store import _atomic_write_json event = self.reply_event() - parent = "原要求:保留已有结果和失败记录。Original requirement: retain results and failed attempts." + parent = "\n\n".join([ + "原要求:保留已有结果和失败记录。Original requirement: retain results and failed attempts.", + "1. Inspect the evidence", + "Keep the original records available.", + "2. Compare the sources", + "Read the relevant sections before deciding.", + "3. Report the result", + "An independent list can start later:", + "7. A retained step\n42. The following step", + "A zero-based list:", + "0. Initial state", + "- Preserve uncertainty.\n- Preserve failed attempts.", + "```text\n9. This is literal code, not another list.\n```", + ]) body = "请比较这次修改,并保留原话题中的限制。Compare this change against the original constraints." request = identity_ref(self.observe()["provider_ref"], event["message_id"]) record = {"event": event, "profile": self.transport_ref, "request_ref": request} diff --git a/apps/presentation/dashboard/smoke/conversation-reply-browser-smoke.mjs b/apps/presentation/dashboard/smoke/conversation-reply-browser-smoke.mjs index b3d04b9ea6..55171d455a 100644 --- a/apps/presentation/dashboard/smoke/conversation-reply-browser-smoke.mjs +++ b/apps/presentation/dashboard/smoke/conversation-reply-browser-smoke.mjs @@ -72,6 +72,8 @@ try { await context.locator("summary").scrollIntoViewIfNeeded(); assert.ok(await page.locator("body").evaluate(node => node.scrollWidth <= innerWidth + 1)); await page.screenshot({path: resolve(output, "expanded-narrow.png"), animations: "disabled"}); + await context.locator("blockquote ol").nth(1).scrollIntoViewIfNeeded(); + await page.screenshot({path: resolve(output, "expanded-narrow-steps.png"), animations: "disabled"}); await context.locator("summary").click(); await page.screenshot({path: resolve(output, "default-narrow.png"), animations: "disabled"}); await page.evaluate(() => localStorage.setItem("loopx-pw-locale", "en")); @@ -79,6 +81,13 @@ try { await page.getByRole("navigation", {name: "Manager view"}).getByRole("button", {name: /^(Chat|Conversation)$/}).click(); await page.getByText("Reply and thread context", {exact: true}).waitFor(); await page.screenshot({path: resolve(output, "english-narrow.png"), animations: "disabled"}); + const quote = context.locator("blockquote"); + assert.deepEqual(await quote.locator("ol").evaluateAll(lists => lists.map(list => list.start)), + [1, 2, 3, 7, 0], "Paragraph-separated steps and independent lists retain their starting numbers"); + assert.equal(await quote.locator("ol").nth(3).locator("li").count(), 2, + "Later source markers in one consecutive list do not split its sequence"); + assert.equal(await quote.locator("ul li").count(), 2, "Unordered lists stay unordered"); + assert.equal(await quote.locator("pre code").textContent(), "9. This is literal code, not another list."); assert.deepEqual(errors, []); assert.ok(!writes.some(path => /\/turns(?:\/|$)/u.test(path)), "Browsing cannot submit a Turn"); child.stdin.write("inspect\n"); diff --git a/apps/presentation/dashboard/src/features/personal-workspace/markdown-blocks.ts b/apps/presentation/dashboard/src/features/personal-workspace/markdown-blocks.ts index 20e9ebeeda..fcb48835af 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/markdown-blocks.ts +++ b/apps/presentation/dashboard/src/features/personal-workspace/markdown-blocks.ts @@ -3,13 +3,13 @@ export type MarkdownBlock = {startLine: number; endLine: number} & ( | {type: "code"; text: string} | {type: "table"; headers: string[]; rows: string[][]} | {type: "heading"; level: number; text: string} - | {type: "list"; ordered: boolean; items: string[]} + | {type: "list"; ordered: boolean; start?: number; items: string[]} | {type: "paragraph"; lines: string[]} ); type BlockContent = MarkdownBlock extends infer T ? T extends MarkdownBlock ? Omit : never : never; const UNORDERED = /^\s*[-*•]\s+(.*)$/; -const ORDERED = /^\s*\d{1,9}[.、)]\s+(.*)$/; +const ORDERED = /^\s*(\d{1,9})[.、)]\s+(.*)$/; function tableCells(line: string) { return line.trim().replace(/^\|/, "").replace(/(? cell.trim().replace(/\\\|/g, "|")); @@ -68,14 +68,15 @@ export function parseMarkdownBlocks(text: string): MarkdownBlock[] { } if (UNORDERED.test(line) || ORDERED.test(line)) { flushParagraph(); - const start = i, ordered = ORDERED.test(line), items: string[] = []; + const start = i, firstOrdered = line.match(ORDERED), ordered = firstOrdered !== null, items: string[] = []; const pattern = ordered ? ORDERED : UNORDERED; while (i < lines.length) { const item = lines[i].match(pattern); if (!item) break; - items.push(item[1]); i += 1; + items.push(item[ordered ? 2 : 1]); i += 1; } - push({type: "list", ordered, items}, start, i); + const number = Number(firstOrdered?.[1] ?? 1); + push({type: "list", ordered, items, ...(number !== 1 ? {start: number} : {})}, start, i); continue; } if (line.trim() === "") { diff --git a/apps/presentation/dashboard/src/features/personal-workspace/markdown.tsx b/apps/presentation/dashboard/src/features/personal-workspace/markdown.tsx index 5374cd08c3..db76efd36d 100644 --- a/apps/presentation/dashboard/src/features/personal-workspace/markdown.tsx +++ b/apps/presentation/dashboard/src/features/personal-workspace/markdown.tsx @@ -146,7 +146,7 @@ export function MarkdownText({text, headingPrefix, headingLevel = 2}: { if (block.type === "list") { const items = block.items.map((item, itemIndex) =>
  • {renderInline(item, `${key}-${itemIndex}`)}
  • ); return block.ordered - ?
      {items}
    + ?
      {items}
    : ; } return (