mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-09-09 04:02:35 +00:00
fix: normalize JSON-escaped Windows spill locators
This commit is contained in:
+2
-2
@@ -2,5 +2,5 @@
|
||||
# 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-09-05-session-reference-spill-reuse.md
|
||||
2026-09-05-session-reference-spill-reuse.md: 0a80a25e2808a5fa363f9e8ec5f18eea9bb8085d
|
||||
2026-09-05-session-reference-spill-reuse.zh.md: 189a6adcd86f0ea097c1c8c12d5e4ec3c2d65ffd
|
||||
2026-09-05-session-reference-spill-reuse.md: d9f9e3702075417a9b5c742831737e412e75bd00
|
||||
2026-09-05-session-reference-spill-reuse.zh.md: 346ed1a39ee65a560cf9e74e519ae45c2ce133e1
|
||||
|
||||
@@ -34,7 +34,7 @@ The model can inspect text omitted from a preview without increasing the preview
|
||||
|
||||
## Verification
|
||||
|
||||
The [unit suite](../../../../packages/context/session-reference/tests/session-reference.spec.ts) pins omission counts, full Unicode and control-character recovery, whole-message drops, three-reference isolation, missing and failed storage, source exclusions and mutation isolation, and cancellation before publication. The [Loader composition test](../../../../packages/context/session-reference/tests/loader-composition.spec.ts) exercises the real local store and paged `read` tool against the middle of a giant single-line message, with target-session storage ownership. The [keyless recorded-session scenario](../../../../snapshots/session/session-reference-spill/snapshot.yml) pins the durable model-visible reference context. Replay [normalizes known quoted spill locators](../../../../packages/test-support/session-snapshot/README.md) while preserving saved byte lengths and omission counts.
|
||||
The [unit suite](../../../../packages/context/session-reference/tests/session-reference.spec.ts) pins omission counts, full Unicode and control-character recovery, whole-message drops, three-reference isolation, missing and failed storage, source exclusions and mutation isolation, and cancellation before publication. The [Loader composition test](../../../../packages/context/session-reference/tests/loader-composition.spec.ts) exercises the real local store and paged `read` tool against the middle of a giant single-line message, with target-session storage ownership. The [keyless recorded-session scenario](../../../../snapshots/session/session-reference-spill/snapshot.yml) pins the durable model-visible reference context. Nested Windows-locator regressions cover both serialized extraction and normalization without rewriting unrelated backslashes. Replay [normalizes known quoted spill locators](../../../../packages/test-support/session-snapshot/README.md) while preserving saved byte lengths and omission counts.
|
||||
|
||||
## Related decisions
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ Status: implemented
|
||||
|
||||
## 验证
|
||||
|
||||
[单元测试](../../../../packages/context/session-reference/tests/session-reference.spec.ts)锁定省略计数、完整 Unicode 与控制字符恢复、整条消息丢弃、三个引用的隔离、无存储与保存失败、来源排除与变更隔离,以及发布前取消。[Loader 组合测试](../../../../packages/context/session-reference/tests/loader-composition.spec.ts)使用真实本地存储和分页 `read` 工具,读取巨型单行消息的中部,并检查存储归目标会话所有。[无密钥录制会话场景](../../../../snapshots/session/session-reference-spill/snapshot.yml)锁定持久的模型可见引用上下文。回放会[规范化已知的带引号 spill 定位信息](../../../../packages/test-support/session-snapshot/README.zh.md),同时保留保存字节数与省略计数。
|
||||
[单元测试](../../../../packages/context/session-reference/tests/session-reference.spec.ts)锁定省略计数、完整 Unicode 与控制字符恢复、整条消息丢弃、三个引用的隔离、无存储与保存失败、来源排除与变更隔离,以及发布前取消。[Loader 组合测试](../../../../packages/context/session-reference/tests/loader-composition.spec.ts)使用真实本地存储和分页 `read` 工具,读取巨型单行消息的中部,并检查存储归目标会话所有。[无密钥录制会话场景](../../../../snapshots/session/session-reference-spill/snapshot.yml)锁定持久的模型可见引用上下文。嵌套 Windows 定位信息回归覆盖序列化提取与规范化,且不改写无关反斜杠。回放会[规范化已知的带引号 spill 定位信息](../../../../packages/test-support/session-snapshot/README.zh.md),同时保留保存字节数与省略计数。
|
||||
|
||||
## 相关决策
|
||||
|
||||
|
||||
@@ -2,5 +2,5 @@
|
||||
# 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 packages/test-support/session-snapshot/README.md
|
||||
README.md: 8b7c7da34940b5a6616a4127565ff1bcf68a613e
|
||||
README.zh.md: 0e8f16c8af4c315d1a11385fa0bc3c57b69fa244
|
||||
README.md: 12acb8d426a2966a852f98d85843d3c246ffeb5f
|
||||
README.zh.md: 0b30cbf205f6055ef7b11b791d47adb38f6f704d
|
||||
|
||||
@@ -72,7 +72,7 @@ Each recorded-session directory carries a closed `snapshot.yml` manifest plus ca
|
||||
|
||||
`normalizeSessionSnapshot` retains the complete Session header and event payloads but omits top-level `seq`/`time` envelopes from committed fixtures after normalizing paths and scrubbing request headers; it also normalizes embedded stream clocks and historical packed-row `seq0`/`time0` envelopes. Replay synthesizes the top-level envelopes in memory, while runtime persistence continues to write complete logs. Multi-session comparison restores every selected persisted or projected fixture through the current build-static Session format catalog before identity redaction and normalization, so retained v0/v1 replay input and fresh `session.v2.jsonl` writer output compare as one v2 logical Session without rewriting or renaming historical files. Expected and harvested logs use the same strict restoration path; source filenames cannot alter format validation. Versionless protocol-adapter unit fixtures remain outside the released Session format corpus. Current v2 fixtures use one row per event; retained v0/v1 fixtures may use canonical packed rows. The [temporary repository migrator](../../../scripts/migrate-packed-session-fixtures.ts) (`pnpm run migrate:packed-session-fixtures`) rewrites older historical layouts, and its [removal proposal](../../../.agents/notes/proposed/process/2026-07-26-remove-packed-session-fixture-migrator.md) owns its deletion.
|
||||
|
||||
Known snapshot spill paths normalize to stable locator tokens, including paths quoted inside JSON omission notices. Normalization changes only the locator: saved byte lengths and omission counts remain comparison evidence.
|
||||
Known snapshot spill paths normalize to stable locator tokens, including paths quoted inside JSON omission notices with JSON-escaped Windows separators. Refresh extraction preserves the matched serialized path spelling for literal replacement. Normalization changes only the locator: saved byte lengths and omission counts remain comparison evidence.
|
||||
|
||||
### Record, replay, and refresh
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ defineAcpSnapshotSuite({
|
||||
|
||||
`normalizeSessionSnapshot` 在规范化路径并清理 request header 后,会保留完整 Session header 与事件 payload,但从已提交 fixture 中省略顶层 `seq`/`time` envelope;它还会规范化嵌入式 stream clock 与历史 packed-row 的 `seq0`/`time0` envelope。Replay 只在内存中合成顶层 envelope,而运行时持久化仍写入完整日志。多 Session 比较会先通过当前构建期静态 Session 格式目录恢复每个选定的持久化或投影 fixture,再进行身份脱敏与规范化,因此保留的 v0/v1 replay 输入与新生成的 `session.v2.jsonl` writer 输出会作为同一个 v2 logical Session 比较,且不会重写或重命名历史文件。预期日志与收集日志使用同一条严格恢复路径;来源文件名不能改变格式校验。无版本的协议适配器单元测试 fixture 不属于已发布 Session 格式语料。当前 v2 fixture 每个事件占一行;保留的 v0/v1 fixture 可以使用规范 packed row。[临时仓库迁移器](../../../scripts/migrate-packed-session-fixtures.ts)(`pnpm run migrate:packed-session-fixtures`)会改写更旧的历史布局,由其[移除提案](../../../.agents/notes/proposed/process/2026-07-26-remove-packed-session-fixture-migrator.zh.md)负责删除该迁移器。
|
||||
|
||||
已知的快照 spill 路径会规范化为稳定的定位信息 token,包括 JSON 省略通知中带引号的路径。规范化只改变定位信息:保存字节数与省略计数仍作为比较证据。
|
||||
已知的快照 spill 路径会规范化为稳定的定位信息 token,包括 JSON 省略通知中带引号、使用 JSON 转义 Windows 分隔符的路径。刷新提取会保留匹配路径的序列化写法,以便进行字面替换。规范化只改变定位信息:保存字节数与省略计数仍作为比较证据。
|
||||
|
||||
### 录制、回放与刷新
|
||||
|
||||
|
||||
@@ -46,13 +46,14 @@ const FILE_URI_PATH_PREFIX_RE = /(?:^|[^a-z0-9+.-])file:\/\/\/?$/i
|
||||
|
||||
/** A UUID v4 string, the shape `randomUUID()` produces for session ids. */
|
||||
const UUID_RE = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi
|
||||
// Separator runs also match JSON-escaped Windows paths; extraction preserves their exact serialized spelling.
|
||||
const LOCAL_SPILL_PATH_RE = new RegExp(
|
||||
String.raw`\{\{cwd\}\}[\\/]\.spill[\\/]session-[0-9a-f]{12}[\\/][0-9a-f]{12}-([A-Za-z0-9._~-]+?)`
|
||||
String.raw`\{\{cwd\}\}[\\/]+\.spill[\\/]+session-[0-9a-f]{12}[\\/]+[0-9a-f]{12}-([A-Za-z0-9._~-]+?)`
|
||||
+ String.raw`(?=\. Use read with offset/limit|[\s)"]|\\+"|$)`,
|
||||
'g',
|
||||
)
|
||||
const SNAPSHOT_SPILL_PATH_RE = new RegExp(
|
||||
String.raw`(?:[A-Za-z]:)?[\\/](?:tmp|t)[\\/](?:dsh-acp-snap-[0-9a-f]{9}|dsh-acp-snapshot-spill)[\\/]session-[0-9a-f]{12}[\\/][0-9a-f]{12}-([A-Za-z0-9._~-]+?)`
|
||||
String.raw`(?:[A-Za-z]:)?[\\/]+(?:tmp|t)[\\/]+(?:dsh-acp-snap-[0-9a-f]{9}|dsh-acp-snapshot-spill)[\\/]+session-[0-9a-f]{12}[\\/]+[0-9a-f]{12}-([A-Za-z0-9._~-]+?)`
|
||||
+ String.raw`(?=\. Use read with offset/limit|[\s)"]|\\+"|$)`,
|
||||
'g',
|
||||
)
|
||||
|
||||
@@ -910,19 +910,36 @@ describe('tokenizeSessionFixtureCwd', () => {
|
||||
})
|
||||
|
||||
describe('extractSnapshotSpillPaths', () => {
|
||||
it('recognizes locators in nested JSON omissions without scrubbing byte counts', () => {
|
||||
const locator = '/tmp/dsh-acp-snap-123456789/session-123456abcdef/abcdef123456-session-reference-1.txt'
|
||||
it.each([
|
||||
['/tmp', '/'],
|
||||
['/tmp', String.fromCharCode(92)],
|
||||
['C:/t', String.fromCharCode(92)],
|
||||
])('recognizes %s locators with %s separators in nested JSON omissions without scrubbing byte counts', (root, separator) => {
|
||||
const locator = `${root}/dsh-acp-snap-123456789/session-123456abcdef/abcdef123456-session-reference-1.txt`.replaceAll('/', separator)
|
||||
const notice = { sessionId: 'source', omittedBytes: 42, fullSnapshot: { status: 'saved', locator, bytes: 1234 } }
|
||||
const log = JSON.stringify({ type: 'user/message', data: { content: [{ type: 'text', text: JSON.stringify([notice]) }] } })
|
||||
expect(extractSnapshotSpillPaths(log)).toEqual(new Map([['session-reference-1.txt', locator]]))
|
||||
const encodedLocator = JSON.stringify(JSON.stringify(locator).slice(1, -1)).slice(1, -1)
|
||||
expect(extractSnapshotSpillPaths(log)).toEqual(new Map([['session-reference-1.txt', encodedLocator]]))
|
||||
const normalized = normalizeSessionLog(log, ctx)
|
||||
const unrelated = '/tmp/unrelated/session-123456abcdef/abcdef123456-session-reference-1.txt'
|
||||
expect(normalizeSessionLog(log.replaceAll(locator, unrelated), ctx)).toContain(unrelated)
|
||||
expect(normalizeSessionLog(log.replaceAll(encodedLocator, unrelated), ctx)).toContain(unrelated)
|
||||
const expectedNotice = { ...notice, fullSnapshot: { ...notice.fullSnapshot, locator: '{{spillLocator:session-reference-1.txt}}' } }
|
||||
expect(normalized).toBe(JSON.stringify({ type: 'user/message', data: { content: [{ type: 'text', text: JSON.stringify([expectedNotice]) }] } }) + '\n')
|
||||
expect(normalized).toContain('{{spillLocator:session-reference-1.txt}}')
|
||||
expect(normalized).toContain('omittedBytes\\":42')
|
||||
expect(normalized).toContain('bytes\\":1234')
|
||||
})
|
||||
|
||||
it.each(['canonical', 'native'] as const)('normalizes nested Windows local spill locators with %s paths', (cwdPathMode) => {
|
||||
const locator = String.raw`{{cwd}}\.spill\session-123456abcdef\abcdef123456-session-reference-1.txt`
|
||||
const notice = { locator, unrelated: String.raw`C:\work\literal\file.txt`, regex: String.raw`\d+\w` }
|
||||
const log = JSON.stringify({ type: 'user/message', data: { text: JSON.stringify(notice) } })
|
||||
const expected = { ...notice, locator: '{{spillLocator:session-reference-1.txt}}' }
|
||||
expect(normalizeSessionLog(log, ctx, { cwdPathMode })).toBe(
|
||||
JSON.stringify({ type: 'user/message', data: { text: JSON.stringify(expected) } }) + '\n',
|
||||
)
|
||||
})
|
||||
|
||||
it('maps each spill filename to its full matched path, last match wins per name', () => {
|
||||
const log = [
|
||||
'Full formatted result stored at: /tmp/dsh-acp-snapshot-spill/session-c22bc3f1d2af/8a7b6c5d4e3f-bash.txt. Use read with offset/limit, or grep this path to search within it.',
|
||||
|
||||
Reference in New Issue
Block a user