fix(web): avoid history pagination stack overflow

This commit is contained in:
kingwl
2026-08-04 11:03:45 +08:00
parent 21d2433d97
commit 5201b84863
5 changed files with 106 additions and 2 deletions

View File

@@ -0,0 +1,6 @@
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write .agents/notes/implemented/bug-fix/2026-08-04-large-history-pagination-call-stack.md
2026-08-04-large-history-pagination-call-stack.md: 28c22121123a227c507c506683ae727d238d98bd
2026-08-04-large-history-pagination-call-stack.zh.md: 57dde9bdc0a4aa52e1af024eb606bf9258430fa7

View File

@@ -0,0 +1,27 @@
# Agent Note: Large history provenance is scanned without argument expansion
Status: implemented
English | [中文](2026-08-04-large-history-pagination-call-stack.zh.md)
## Problem
A finalized assistant message can reference hundreds of thousands of streamed chunks through `sourceEventSeqs`. History pagination found the message group's first event with `Math.min(event.seq, ...sourceEventSeqs)`, so a valid session could exceed the JavaScript engine's function-argument limit and make `session.history` fail with HTTP 500.
## Decision
Pagination scans `sourceEventSeqs` and updates the earliest sequence number one element at a time. The algorithm remains linear in the provenance size and preserves the existing page boundary: a page starts before all recorded sources of its oldest included message.
A regression test rejects multi-argument minimum calls and verifies that every provenance event remains on the page with its finalized message. This exercises the failure mechanism without making the default test suite allocate a production-sized chunk stream.
## Alternatives considered
- **Raise the JavaScript stack or argument limit** — rejected: the limit is engine- and deployment-dependent, and array expansion still makes valid history depend on an unrelated runtime ceiling.
- **Truncate `sourceEventSeqs` during pagination** — rejected: this could cut a page inside a message and violate replay grouping.
- **Cap streamed chunk count at the provider boundary** — rejected: providers may legitimately emit long streams, and pagination must handle every valid session representation.
## Consequences
- Large provenance arrays no longer make history pagination throw solely because of their length.
- Pagination semantics and wire responses are unchanged.
- This does not bound the byte size of a history page or the browser cost of replaying it; those performance concerns remain separate from the server-side call-stack failure.

View File

@@ -0,0 +1,27 @@
# Agent Note: 大规模历史记录的溯源信息通过扫描处理,不做参数展开
Status: implemented
[English](2026-08-04-large-history-pagination-call-stack.md) | 中文
## 问题
一条已定稿的 assistant 消息可以通过 `sourceEventSeqs` 引用数十万个流式分片。历史记录分页使用 `Math.min(event.seq, ...sourceEventSeqs)` 查找消息组的首个事件,因此,有效会话可能超出 JavaScript 引擎的函数参数数量上限,导致 `session.history` 以 HTTP 500 失败。
## 决策
分页逻辑逐项扫描 `sourceEventSeqs`,每次使用一个元素更新最早的序号。该算法的复杂度相对溯源信息规模仍为线性,并保留现有的页面边界:页面起点位于其所含最早消息的所有已记录来源之前。
回归测试会拒绝以多个参数调用取最小值的做法,并验证每个溯源事件都会与其已定稿消息保留在同一页中。这既覆盖了故障机制,也避免默认测试套件分配生产规模的分片流。
## 考虑过的替代方案
- **提高 JavaScript 栈或参数上限**:不予采纳,因为该上限取决于引擎和部署环境,而且数组展开仍会让有效历史记录受制于无关的运行时上限。
- **在分页时截断 `sourceEventSeqs`**:不予采纳,因为这可能会从消息中间切分页面,破坏回放分组。
- **在提供方边界限制流式分片数量**:不予采纳,因为提供方可能会合理地产生长流,而分页必须处理每一种有效的会话表示。
## 后果
- 大型溯源数组不再仅因长度而使历史记录分页抛出异常。
- 分页语义与协议响应保持不变。
- 本决策不限制历史记录页面的字节大小,也不限制浏览器回放该页面的开销;这两项性能问题仍与服务端调用栈故障分开处理。