Skip to content

fix(openclaw-plugin): dedupe afterTurn replay with raw tail#1903

Open
huangxun375-stack wants to merge 1 commit intovolcengine:mainfrom
huangxun375-stack:afterturn-raw-tail
Open

fix(openclaw-plugin): dedupe afterTurn replay with raw tail#1903
huangxun375-stack wants to merge 1 commit intovolcengine:mainfrom
huangxun375-stack:afterturn-raw-tail

Conversation

@huangxun375-stack
Copy link
Copy Markdown
Contributor

Description

Related Issue

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactoring (no functional changes)
  • Performance improvement
  • Test update

Changes Made

Testing

  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have tested this on the following platforms:
    • Linux
    • macOS
    • Windows

Checklist

  • My code follows the project's coding style
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

Screenshots (if applicable)

Additional Notes

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🏅 Score: 92
🧪 PR contains tests
🔒 No security concerns identified
✅ No TODO sections
🔀 No multiple PR themes
⚡ No major issues detected

@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 8, 2026

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
General
Optimize overlap check to linear time

Replace the quadratic nested loop in countPersistedPrefixOverlap with a linear-time
algorithm using the KMP prefix function. This avoids O(n²) worst-case time when
checking large overlaps (up to 10,000 messages), which could cause performance
issues.

examples/openclaw-plugin/context-engine.ts [272-300]

 function countPersistedPrefixOverlap(
   storedTail: OVMessage[],
   incoming: CapturedOpenVikingMessage[],
 ): number {
   const maxOverlap = Math.min(storedTail.length, incoming.length);
   if (maxOverlap <= 0) {
     return 0;
   }
 
   const storedSignatures = storedTail.map((message) =>
     capturedMessageSignature({ role: message.role, parts: message.parts }),
   );
   const incomingSignatures = incoming.map((message) => message.signature);
 
-  for (let overlap = maxOverlap; overlap > 0; overlap -= 1) {
-    let matches = true;
-    for (let i = 0; i < overlap; i += 1) {
-      if (storedSignatures[storedSignatures.length - overlap + i] !== incomingSignatures[i]) {
-        matches = false;
-        break;
-      }
+  // Use KMP prefix function for linear-time longest prefix-suffix match
+  const combined = [...incomingSignatures, "", ...storedSignatures];
+  const prefix = new Array(combined.length).fill(0);
+
+  for (let i = 1; i < combined.length; i++) {
+    let j = prefix[i - 1];
+    while (j > 0 && combined[i] !== combined[j]) {
+      j = prefix[j - 1];
     }
-    if (matches) {
-      return overlap;
+    if (combined[i] === combined[j]) {
+      j++;
     }
+    prefix[i] = j;
   }
 
-  return 0;
+  return Math.min(prefix[prefix.length - 1], maxOverlap);
 }
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential performance issue with the O(n²) overlap check for up to 10,000 messages and proposes a linear-time KMP-based solution. The improved code is logically sound for finding the maximum prefix-suffix overlap, making this a valuable performance optimization.

Medium

@huangxun375-stack huangxun375-stack force-pushed the afterturn-raw-tail branch 9 times, most recently from 8212abd to fd4f5af Compare May 9, 2026 02:26
@Mijamind719
Copy link
Copy Markdown
Collaborator

P2完整 replay 仍会被再次写入
忽略

这里在 stored tail 与 incoming 完全相同、没有新 tail 时返回了原 incoming,而不是空数组;下方 oversized tail-match 分支也一样。这样 OpenClaw finalizer 如果只是重放 loop hook 已经完整捕获过的同一批消息,第二次 afterTurn 仍会把整批消息再次 addSessionMessage,PR 标题要修的 replay duplicate 仍存在。PR 自带的 keeps an entire identical finalizer transcript 测试也证明了这个行为。建议要么对完整连续匹配返回 [],要么引入 turn/runtime id 来区分真实重复输入与 finalizer replay。

/Users/quemingjian/Source/OpenViking/examples/openclaw-plugin/context-engine.ts:393-399

我主要发现这一处会影响 PR 目标的问题:当前实现只能处理“已有前缀 + 新消息”的 finalizer replay,但处理不了“finalizer 完整重放同一批消息且没有新消息”的情况,所以仍会产生重复 transcript。

验证情况:git diff --check origin/main...origin/pr/1903 通过;在临时 worktree 中 npm test -- --run tests/ut/dedup-after-turn-batch.test.ts tests/ut/context-engine-afterTurn.test.ts tests/ut/client.test.ts 通过,3 个文件 86 个测试。Python 目标测试启动后卡在构建 Rust 依赖,没有进入 pytest 输出,我中断并清理了临时 worktree。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

2 participants