dsh-context-compression-improved 0.1.1 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (148) hide show
  1. package/.gitattributes +1 -0
  2. package/.github/workflows/ci.yml +39 -0
  3. package/CHANGELOG.ja.md +39 -0
  4. package/CHANGELOG.ko.md +39 -0
  5. package/CHANGELOG.md +135 -0
  6. package/CHANGELOG.zh.md +39 -0
  7. package/CONTRIBUTING.md +22 -0
  8. package/README.ja.md +104 -0
  9. package/README.ko.md +103 -0
  10. package/README.md +89 -12
  11. package/README.zh.md +87 -12
  12. package/SECURITY.md +18 -0
  13. package/THIRD_PARTY_NOTICES.md +7 -31
  14. package/docs/installation.ja.md +76 -0
  15. package/docs/installation.ko.md +76 -0
  16. package/docs/installation.md +76 -0
  17. package/docs/installation.zh.md +76 -0
  18. package/docs/repair-log.md +582 -0
  19. package/eslint.config.js +30 -0
  20. package/package.json +85 -82
  21. package/packages/selector/LICENSE +21 -0
  22. package/packages/selector/README.md +26 -0
  23. package/packages/selector/README.zh.md +26 -0
  24. package/packages/selector/THIRD_PARTY_NOTICES.md +38 -0
  25. package/packages/selector/docs/history-tool-call-working-set-spec.md +112 -0
  26. package/packages/selector/docs/native-tool-result-selector-spec.md +34 -0
  27. package/packages/selector/docs/subagent-cache-reuse-spec.md +46 -0
  28. package/packages/selector/lib/style.css +308 -0
  29. package/packages/selector/package.json +115 -0
  30. package/{screenshots.json → packages/selector/screenshots.json} +6 -6
  31. package/packages/selector/src/client/CompressionProfileControls.tsx +229 -0
  32. package/packages/selector/src/client/CompressionProfileSelector.module.css +170 -0
  33. package/packages/selector/src/client/CompressionProfileSelector.tsx +79 -0
  34. package/packages/selector/src/client/CustomPolicyEditor.tsx +216 -0
  35. package/packages/selector/src/client/EstimatorControls.tsx +281 -0
  36. package/packages/selector/src/client/decode.ts +49 -0
  37. package/packages/selector/src/client/index.ts +111 -0
  38. package/packages/selector/src/client/locales.ts +198 -0
  39. package/packages/selector/src/client/preset-options.ts +70 -0
  40. package/packages/selector/src/client/settings-section.tsx +126 -0
  41. package/packages/selector/src/css-modules.d.ts +6 -0
  42. package/packages/selector/src/deepseek-v4-tokenizer.ts +210 -0
  43. package/packages/selector/src/estimator-catalog.ts +104 -0
  44. package/packages/selector/src/index.ts +327 -0
  45. package/packages/selector/src/invariant.ts +113 -0
  46. package/packages/selector/src/preset-overlay.ts +567 -0
  47. package/packages/selector/src/profiles.ts +342 -0
  48. package/packages/selector/src/pruner/content.ts +188 -0
  49. package/packages/selector/src/pruner/session.ts +94 -0
  50. package/packages/selector/src/pruner/state.ts +43 -0
  51. package/packages/selector/src/pruner/tuning.ts +23 -0
  52. package/packages/selector/src/pruner/types.ts +60 -0
  53. package/packages/selector/src/pruner.ts +2144 -0
  54. package/packages/selector/src/runtime/adaptive-cost.ts +194 -0
  55. package/packages/selector/src/runtime/audit.ts +215 -0
  56. package/packages/selector/src/runtime/config.ts +613 -0
  57. package/packages/selector/src/runtime/custom-policy.ts +278 -0
  58. package/packages/selector/src/runtime/deepseek-official-pricing.ts +298 -0
  59. package/packages/selector/src/runtime/deepseek-v4-vision-tokens.ts +254 -0
  60. package/packages/selector/src/runtime/measurement.ts +403 -0
  61. package/packages/selector/src/runtime/reducers.ts +656 -0
  62. package/packages/selector/src/runtime/retrieve.ts +457 -0
  63. package/packages/selector/src/runtime/session-events.ts +17 -0
  64. package/packages/selector/src/runtime/tail-trim.ts +166 -0
  65. package/packages/selector/src/runtime/token-count.ts +72 -0
  66. package/packages/selector/src/runtime/tokenpilot/dedup.ts +81 -0
  67. package/packages/selector/src/runtime/tokenpilot/estimator.ts +183 -0
  68. package/packages/selector/src/runtime/tokenpilot/locator.ts +128 -0
  69. package/packages/selector/src/runtime/tokenpilot/read-state.ts +77 -0
  70. package/packages/selector/src/runtime/types.ts +309 -0
  71. package/packages/selector/src/runtime/value.ts +48 -0
  72. package/packages/selector/tests/auto-compact.client.spec.tsx +226 -0
  73. package/packages/selector/tests/built/client-artifact.spec.ts +51 -0
  74. package/packages/selector/tests/cache-prefix-audit.spec.ts +123 -0
  75. package/packages/selector/tests/code-skeleton.client.spec.ts +88 -0
  76. package/packages/selector/tests/custom-contract.client.spec.ts +202 -0
  77. package/packages/selector/tests/estimator-catalog.spec.ts +70 -0
  78. package/packages/selector/tests/estimator-channel.client.spec.tsx +247 -0
  79. package/packages/selector/tests/estimator-route-registration.host.spec.ts +176 -0
  80. package/packages/selector/tests/host-preset-overlay.host.spec.ts +204 -0
  81. package/packages/selector/tests/preset-options-write.client.spec.ts +181 -0
  82. package/packages/selector/tests/preset-overlay-loader.e2e.host.spec.ts +196 -0
  83. package/packages/selector/tests/preset-overlay.host.spec.ts +243 -0
  84. package/packages/selector/tests/profiles.client.spec.tsx +434 -0
  85. package/packages/selector/tests/public/package-contract.client.spec.ts +33 -0
  86. package/packages/selector/tests/runtime/adaptive-cost.spec.ts +167 -0
  87. package/packages/selector/tests/runtime/audit.spec.ts +129 -0
  88. package/packages/selector/tests/runtime/auto-compact-config.spec.ts +523 -0
  89. package/packages/selector/tests/runtime/code-skeleton.spec.ts +141 -0
  90. package/packages/selector/tests/runtime/deepseek-official-pricing.spec.ts +186 -0
  91. package/packages/selector/tests/runtime/deepseek-v4-tokenizer.spec.ts +122 -0
  92. package/packages/selector/tests/runtime/deepseek-v4-vision-tokens.spec.ts +122 -0
  93. package/packages/selector/tests/runtime/fixtures/profile-baseline.json +273 -0
  94. package/packages/selector/tests/runtime/fixtures/tokenizer-golden.json +106 -0
  95. package/packages/selector/tests/runtime/fixtures/vision-golden.json +459 -0
  96. package/packages/selector/tests/runtime/public/public-runtime.spec.ts +2531 -0
  97. package/packages/selector/tests/runtime/session-events.spec.ts +27 -0
  98. package/packages/selector/tests/runtime/tokenizer-golden.spec.ts +53 -0
  99. package/packages/selector/tests/runtime/tokenpilot/dedup.spec.ts +52 -0
  100. package/packages/selector/tests/runtime/tokenpilot/estimator.spec.ts +56 -0
  101. package/packages/selector/tests/runtime/tokenpilot/locator.spec.ts +76 -0
  102. package/packages/selector/tests/runtime/tokenpilot/profile-baseline.spec.ts +100 -0
  103. package/packages/selector/tests/runtime/tokenpilot/read-state.spec.ts +58 -0
  104. package/packages/selector/tests/runtime/value.spec.ts +23 -0
  105. package/packages/selector/tests/standing-generation.host.spec.ts +631 -0
  106. package/packages/selector/tests/subagent-cache-reuse.host.spec.ts +250 -0
  107. package/packages/selector/tests/support/cache-prefix-audit.ts +105 -0
  108. package/packages/selector/tests/support/mock-adapter.ts +37 -0
  109. package/packages/selector/tests/support/ui-primitives.tsx +34 -0
  110. package/packages/selector/tsconfig.json +11 -0
  111. package/packages/selector/tsdown.client.config.ts +102 -0
  112. package/packages/selector/tsdown.config.ts +20 -0
  113. package/pnpm-workspace.yaml +19 -0
  114. package/scripts/capture-profile-baseline.ts +80 -0
  115. package/scripts/generate-tokenizer-fixtures.py +81 -0
  116. package/scripts/generate-vision-fixtures.py +208 -0
  117. package/scripts/packed-components-smoke.ts +713 -0
  118. package/scripts/packed-install-e2e.ts +1072 -0
  119. package/scripts/verify-release.ts +300 -0
  120. package/tests/TEST_INVENTORY.md +42 -0
  121. package/tsconfig.base.json +18 -0
  122. package/tsconfig.json +7 -0
  123. package/tsconfig.scripts.json +13 -0
  124. package/tsconfig.tests.json +15 -0
  125. package/vitest.built.config.ts +9 -0
  126. package/vitest.config.ts +43 -0
  127. /package/{assets → packages/selector/assets}/deepseek-v4/LICENSE.DeepSeek-V4-Pro.txt +0 -0
  128. /package/{assets → packages/selector/assets}/deepseek-v4/manifest.json +0 -0
  129. /package/{assets → packages/selector/assets}/deepseek-v4/tokenizer.json +0 -0
  130. /package/{assets → packages/selector/assets}/deepseek-v4/tokenizer_config.json +0 -0
  131. /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/LICENSE.DeepSeek-V4-Flash-Vision-Exp.txt +0 -0
  132. /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/manifest.json +0 -0
  133. /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/tokenizer.json +0 -0
  134. /package/{assets → packages/selector/assets}/deepseek-v4-vision-exp/tokenizer_config.json +0 -0
  135. /package/{assets → packages/selector/assets}/screenshots/context-compression-selector-profiles.jpg +0 -0
  136. /package/{assets → packages/selector/assets}/screenshots/context-compression-selector-settings.png +0 -0
  137. /package/{cordis.patch.yml → packages/selector/cordis.patch.yml} +0 -0
  138. /package/{dsh.plugin.json → packages/selector/dsh.plugin.json} +0 -0
  139. /package/{lib → packages/selector/lib}/client.d.ts +0 -0
  140. /package/{lib → packages/selector/lib}/client.js +0 -0
  141. /package/{lib → packages/selector/lib}/config.js +0 -0
  142. /package/{lib → packages/selector/lib}/index.d.ts +0 -0
  143. /package/{lib → packages/selector/lib}/index.js +0 -0
  144. /package/{lib → packages/selector/lib}/invariant.d.ts +0 -0
  145. /package/{lib → packages/selector/lib}/invariant.js +0 -0
  146. /package/{lib → packages/selector/lib}/pruner.d.ts +0 -0
  147. /package/{lib → packages/selector/lib}/pruner.js +0 -0
  148. /package/{lib → packages/selector/lib}/tail-trim.js +0 -0
@@ -0,0 +1,27 @@
1
+ import { describe, expect, it, vi } from 'vitest'
2
+ import type { Session, SessionEvent } from '@deepseek-ai/dsh-session'
3
+ import { sessionEvents } from '../../src/runtime/session-events.ts'
4
+
5
+ const EVENTS = Object.freeze([
6
+ Object.freeze({ seq: 0, time: 1, type: 'turn/start', data: { turn: 1 } }),
7
+ ]) as unknown as readonly SessionEvent[]
8
+
9
+ describe('sessionEvents', () => {
10
+ it('retains the rc.2 events accessor fallback', () => {
11
+ const session = { events: EVENTS } as Session
12
+ expect(sessionEvents(session)).toBe(EVENTS)
13
+ })
14
+
15
+ it('prefers the Alpha 5 public snapshotEvents API and preserves its receiver', () => {
16
+ const modern = {
17
+ marker: EVENTS,
18
+ events: undefined,
19
+ snapshotEvents(this: { marker: readonly SessionEvent[] }) {
20
+ return this.marker
21
+ },
22
+ }
23
+ const snapshot = vi.spyOn(modern, 'snapshotEvents')
24
+ expect(sessionEvents(modern as unknown as Session)).toBe(EVENTS)
25
+ expect(snapshot).toHaveBeenCalledOnce()
26
+ })
27
+ })
@@ -0,0 +1,53 @@
1
+ /** Node loader equivalence against Hugging Face Python tokenizer golden counts. */
2
+
3
+ import { readFileSync } from 'node:fs'
4
+ import { fileURLToPath } from 'node:url'
5
+ import { describe, expect, it } from 'vitest'
6
+ import { deepSeekV4TokenizerForModel } from '../../src/deepseek-v4-tokenizer.ts'
7
+
8
+ interface GoldenTokenizerCase {
9
+ readonly label: string
10
+ readonly text: string
11
+ readonly counts: Readonly<Record<string, number>>
12
+ }
13
+
14
+ interface GoldenFixture {
15
+ readonly generator: string
16
+ readonly revision: string
17
+ readonly tokenizers: Readonly<Record<string, string>>
18
+ readonly cases: readonly GoldenTokenizerCase[]
19
+ }
20
+
21
+ const fixture = JSON.parse(readFileSync(
22
+ fileURLToPath(new URL('fixtures/tokenizer-golden.json', import.meta.url)),
23
+ 'utf8',
24
+ )) as GoldenFixture
25
+
26
+ const MODEL_BY_TOKENIZER: Readonly<Record<string, string>> = {
27
+ 'deepseek-ai/DeepSeek-V4-Pro': 'deepseek-v4-pro',
28
+ 'deepseek-ai/DeepSeek-V4-Flash-Vision-Exp': 'deepseek-v4-flash-vision-exp',
29
+ }
30
+
31
+ describe('bundled tokenizers match Hugging Face Python golden counts', () => {
32
+ it('carries cases for both pinned artifacts with distinct vocabularies', () => {
33
+ expect(Object.keys(fixture.tokenizers).sort()).toEqual(Object.keys(MODEL_BY_TOKENIZER).sort())
34
+ // At least one case must prove the vocabularies differ (vision-only special token).
35
+ const imageCase = fixture.cases.find(entry => entry.text.includes('deepseek_image'))
36
+ expect(imageCase).toBeDefined()
37
+ expect(imageCase?.counts['deepseek-ai/DeepSeek-V4-Pro'])
38
+ .not.toBe(imageCase?.counts['deepseek-ai/DeepSeek-V4-Flash-Vision-Exp'])
39
+ })
40
+
41
+ it.each(fixture.cases)('counts "$label" identically in every tokenizer', (entry) => {
42
+ for (const [repository, expected] of Object.entries(entry.counts)) {
43
+ const modelId = MODEL_BY_TOKENIZER[repository]
44
+ if (modelId === undefined) throw new Error(`golden fixture cites unknown tokenizer ${repository}`)
45
+ const count = deepSeekV4TokenizerForModel(modelId)?.countText(entry.text)
46
+ expect(count, `${repository} must resolve for ${modelId}`).toMatchObject({
47
+ kind: 'exact-tokenizer',
48
+ tokenizerId: repository,
49
+ })
50
+ expect(count?.tokens).toBe(expected)
51
+ }
52
+ })
53
+ })
@@ -0,0 +1,52 @@
1
+ /**
2
+ * TokenPilot-inspired A1 unit coverage: dedup hashing, canonicalization,
3
+ * table eviction, and pointer placeholder shape.
4
+ */
5
+ import { describe, expect, it } from 'vitest'
6
+ import {
7
+ DedupeTable,
8
+ canonicalizeForDedupe,
9
+ dedupeHash,
10
+ dedupePlaceholder,
11
+ flattenPlainText,
12
+ } from '../../../src/runtime/tokenpilot/dedup.ts'
13
+
14
+ describe('tokenpilot dedup helpers', () => {
15
+ it('canonicalizes trim-eol without touching inner content', () => {
16
+ expect(canonicalizeForDedupe('a \n b \n\n', 'trim-eol')).toBe('a\n b')
17
+ expect(canonicalizeForDedupe('a\nb', 'exact')).toBe('a\nb')
18
+ expect(dedupeHash('a \n', 'trim-eol')).toBe(dedupeHash('a', 'trim-eol'))
19
+ expect(dedupeHash('a \n', 'exact')).not.toBe(dedupeHash('a', 'exact'))
20
+ })
21
+
22
+ it('flattens all-text content and rejects rich blocks', () => {
23
+ expect(flattenPlainText([{ type: 'text', text: 'a' }, { type: 'text', text: 'b' }])).toBe('ab')
24
+ expect(flattenPlainText([{ type: 'text', text: 'a' }, { type: 'image', source: {} } as never])).toBeUndefined()
25
+ })
26
+
27
+ it('records first occurrences, keeps them, and evicts oldest beyond the cap', () => {
28
+ const table = new DedupeTable(2)
29
+ table.record('h1', { seq: 10, sourceRef: 'session://s/event/10', toolName: 'bash', originalChars: 100 })
30
+ expect(table.get('h1')?.seq).toBe(10)
31
+ // Re-recording the same hash never replaces the first occurrence.
32
+ table.record('h1', { seq: 99, sourceRef: 'session://s/event/99', toolName: 'bash', originalChars: 5 })
33
+ expect(table.get('h1')?.seq).toBe(10)
34
+ table.record('h2', { seq: 20, sourceRef: 'session://s/event/20', toolName: 'bash', originalChars: 1 })
35
+ table.record('h3', { seq: 30, sourceRef: 'session://s/event/30', toolName: 'bash', originalChars: 1 })
36
+ // Insertion-order eviction drops h1, the oldest.
37
+ expect(table.get('h1')).toBeUndefined()
38
+ expect(table.get('h2')?.seq).toBe(20)
39
+ expect(table.get('h3')?.seq).toBe(30)
40
+ })
41
+
42
+ it('points the placeholder at the first occurrence with a retrieval hint', () => {
43
+ const text = dedupePlaceholder(
44
+ { seq: 10, sourceRef: 'session://s1/event/10', toolName: 'git_status', originalChars: 4096 },
45
+ 4096,
46
+ )
47
+ expect(text).toContain('identical to the earlier git_status result')
48
+ expect(text).toContain('session://s1/event/10')
49
+ expect(text).toContain('original_chars=4096')
50
+ expect(text).toContain('context_compression_retrieve')
51
+ })
52
+ })
@@ -0,0 +1,56 @@
1
+ /**
2
+ * TokenPilot-inspired E1 unit coverage: pure estimator helpers. The channels
3
+ * themselves are integration-level (host/direct transports) and are covered
4
+ * by the fail-open contract — every malformed answer resolves to no verdicts.
5
+ */
6
+ import { describe, expect, it } from 'vitest'
7
+ import {
8
+ backoffCooldownMs,
9
+ buildEstimatorUserPrompt,
10
+ isCoolingDown,
11
+ parseEstimatorAnswer,
12
+ } from '../../../src/runtime/tokenpilot/estimator.ts'
13
+
14
+ describe('tokenpilot estimator helpers', () => {
15
+ it('parses well-formed verdict arrays, tolerating surrounding prose', () => {
16
+ expect(parseEstimatorAnswer('[{"seq":12,"expired":true},{"seq":13,"expired":false}]')).toEqual([
17
+ { seq: 12, expired: true },
18
+ { seq: 13, expired: false },
19
+ ])
20
+ expect(parseEstimatorAnswer('Verdicts: [{"seq":7,"expired":true}] done')).toEqual([
21
+ { seq: 7, expired: true },
22
+ ])
23
+ })
24
+
25
+ it('yields no verdicts for malformed answers', () => {
26
+ expect(parseEstimatorAnswer('')).toEqual([])
27
+ expect(parseEstimatorAnswer('no json here')).toEqual([])
28
+ expect(parseEstimatorAnswer('[{"seq":"12","expired":true}]')).toEqual([])
29
+ expect(parseEstimatorAnswer('[{"seq":12}]')).toEqual([])
30
+ expect(parseEstimatorAnswer('not json [')).toEqual([])
31
+ })
32
+
33
+ it('builds a path-only user prompt without file content', () => {
34
+ const prompt = buildEstimatorUserPrompt([
35
+ { seq: 4, path: 'src/a.ts', turn: 2 },
36
+ { seq: 9, path: 'docs/b.md', turn: 3 },
37
+ ])
38
+ expect(prompt).toContain('"seq":4')
39
+ expect(prompt).toContain('"path":"src/a.ts"')
40
+ expect(prompt).not.toMatch(/content/i)
41
+ })
42
+
43
+ it('backs off exponentially with a five-minute cap', () => {
44
+ expect(backoffCooldownMs(0)).toBe(1_000)
45
+ expect(backoffCooldownMs(1)).toBe(1_000)
46
+ expect(backoffCooldownMs(2)).toBe(2_000)
47
+ expect(backoffCooldownMs(3)).toBe(4_000)
48
+ expect(backoffCooldownMs(20)).toBe(300_000)
49
+ })
50
+
51
+ it('treats missing failure state as never cooling down', () => {
52
+ expect(isCoolingDown(undefined, Date.now())).toBe(false)
53
+ expect(isCoolingDown({ failures: 1, cooldownUntil: Date.now() - 1 }, Date.now())).toBe(false)
54
+ expect(isCoolingDown({ failures: 1, cooldownUntil: Date.now() + 60_000 }, Date.now())).toBe(true)
55
+ })
56
+ })
@@ -0,0 +1,76 @@
1
+ /**
2
+ * TokenPilot-inspired A2 unit coverage: compaction trace resolution, spill and
3
+ * touched-file extraction, and the Exact Sources block shape.
4
+ */
5
+ import { describe, expect, it } from 'vitest'
6
+ import {
7
+ buildLocatorBlock,
8
+ extractSpillPaths,
9
+ extractTouchedPath,
10
+ findCompactionTrace,
11
+ } from '../../../src/runtime/tokenpilot/locator.ts'
12
+ import type { SessionEvent } from '@deepseek-ai/dsh-session'
13
+
14
+ /** Minimal event literal; the locator only reads seq/type/data fields. */
15
+ function event(seq: number, data: Record<string, unknown>): SessionEvent {
16
+ return { seq, type: 'tool/call', data } as unknown as SessionEvent
17
+ }
18
+
19
+ const SHADOWED = { start: 3, end: 9 }
20
+
21
+ const EVENTS: SessionEvent[] = [
22
+ event(0, {}),
23
+ event(1, {}),
24
+ { seq: 2, type: 'compaction/start', data: { compactionId: 'c1' } } as unknown as SessionEvent,
25
+ event(3, { name: 'read', arguments: '{"path":"src/a.ts"}' }),
26
+ { seq: 4, type: 'tool/result', data: { message: { content: [{ type: 'text', text: 'omitted 100 bytes. Full formatted result stored at: /tmp/spill/a.txt. Use read to restore.' }] } } } as unknown as SessionEvent,
27
+ event(5, { name: 'grep', arguments: '{"path":"src/a.ts"}' }),
28
+ event(6, { name: 'edit', arguments: '{"file_path":"src/a.ts"}' }),
29
+ { seq: 7, type: 'user/message', data: { content: [{ type: 'text', text: 'stored at: /tmp/spill/b.txt' }] } } as unknown as SessionEvent,
30
+ event(8, { name: 'bash', arguments: '{"command":"ls"}' }),
31
+ { seq: 9, type: 'compaction/summary', data: { compactionId: 'c1', shadowedRange: SHADOWED, shadowedTokenCount: 100, provider: 'deepseek', model: 'v4' } } as unknown as SessionEvent,
32
+ { seq: 10, type: 'compaction/end', data: { compactionId: 'c1' } } as unknown as SessionEvent,
33
+ ]
34
+
35
+ describe('tokenpilot summary locator helpers', () => {
36
+ it('resolves the latest compaction/summary trace for one compaction id', () => {
37
+ const trace = findCompactionTrace(EVENTS, 'c1')
38
+ expect(trace?.summarySeq).toBe(9)
39
+ expect(trace?.summaryShadowedRange).toEqual(SHADOWED)
40
+ expect(findCompactionTrace(EVENTS, 'unknown')).toBeUndefined()
41
+ })
42
+
43
+ it('extracts spill paths, stripping trailing punctuation and deduplicating', () => {
44
+ expect(extractSpillPaths('stored at: /tmp/a.txt. Use read')).toEqual(['/tmp/a.txt'])
45
+ expect(extractSpillPaths('stored at: /tmp/a.txt, stored at: /tmp/a.txt')).toEqual(['/tmp/a.txt', '/tmp/a.txt'])
46
+ expect(extractSpillPaths('nothing here')).toEqual([])
47
+ })
48
+
49
+ it('extracts touched paths only from read/grep-style calls', () => {
50
+ expect(extractTouchedPath('read', '{"path":"src/a.ts"}')).toBe('src/a.ts')
51
+ expect(extractTouchedPath('str_replace_editor', '{"file_path":"src/b.ts"}')).toBe('src/b.ts')
52
+ expect(extractTouchedPath('bash', '{"path":"src/a.ts"}')).toBeUndefined()
53
+ expect(extractTouchedPath('read', 'not json')).toBeUndefined()
54
+ })
55
+
56
+ it('builds an Exact Sources block over the shadowed range', () => {
57
+ const block = buildLocatorBlock(EVENTS, SHADOWED)
58
+ expect(block).not.toBeNull()
59
+ expect(block?.text).toContain('## Exact Sources (locators)')
60
+ expect(block?.text).toContain('- seq range: 3-9')
61
+ expect(block?.text).toContain('- spill file: /tmp/spill/a.txt')
62
+ expect(block?.text).toContain('- file touched: src/a.ts')
63
+ expect(block?.text).toContain('context_compression_retrieve')
64
+ expect(block?.spillFiles).toBeGreaterThanOrEqual(1)
65
+ expect(block?.touchedFiles).toBeGreaterThanOrEqual(1)
66
+ })
67
+
68
+ it('returns null when the range locates nothing concrete', () => {
69
+ const empty: SessionEvent[] = [
70
+ { seq: 0, type: 'compaction/summary', data: { compactionId: 'c1', shadowedRange: { start: 1, end: 2 }, shadowedTokenCount: 1, provider: 'p', model: 'm' } } as unknown as SessionEvent,
71
+ event(1, { name: 'bash', arguments: '{"command":"ls"}' }),
72
+ event(2, {}),
73
+ ]
74
+ expect(buildLocatorBlock(empty, { start: 1, end: 2 })).toBeNull()
75
+ })
76
+ })
@@ -0,0 +1,100 @@
1
+ /**
2
+ * TokenPilot-inspired preset: policy derivation plus the backward-compat
3
+ * golden. The fixture freezes the resolvePolicy output of every pre-existing
4
+ * profile captured BEFORE this preset existed; any byte difference is a
5
+ * compatibility regression, however intentional it feels.
6
+ */
7
+ import { readFileSync } from 'node:fs'
8
+ import { join } from 'node:path'
9
+ import { describe, expect, it } from 'vitest'
10
+ import {
11
+ parseContextCompressionSettings,
12
+ resolveConfig,
13
+ resolvePolicy,
14
+ } from '../../../src/pruner.ts'
15
+
16
+ const baseline = JSON.parse(readFileSync(
17
+ join(import.meta.dirname, '..', 'fixtures', 'profile-baseline.json'),
18
+ 'utf8',
19
+ )) as {
20
+ profiles: Record<string, Record<string, unknown>>
21
+ }
22
+
23
+ const OLD_PROFILES = ['off', 'native', 'balanced', 'cache-strict', 'savings', 'adaptive', 'custom'] as const
24
+ const OPTION_CASES = [
25
+ { label: 'defaults', options: {} },
26
+ { label: 'linkage', options: { contextWindowTokens: 128_000, autoCompactThresholdPercent: 80 } },
27
+ ] as const
28
+
29
+ describe('tokenpilot-inspired preset', () => {
30
+ it('keeps every pre-existing profile byte-identical to the pre-preset baseline', () => {
31
+ const config = resolveConfig({})
32
+ for (const profile of OLD_PROFILES) {
33
+ for (const optionCase of OPTION_CASES) {
34
+ const key = optionCase.label
35
+ expect(
36
+ resolvePolicy(config, profile, undefined, optionCase.options),
37
+ `${profile}/${key}`,
38
+ ).toStrictEqual(baseline.profiles[profile]?.[key])
39
+ }
40
+ }
41
+ })
42
+
43
+ it('derives from the balanced baseline and injects the capability matrix', () => {
44
+ const config = resolveConfig({})
45
+ const policy = resolvePolicy(config, 'tokenpilot-inspired')
46
+ expect(policy.profile).toBe('tokenpilot-inspired')
47
+ expect(policy.freshEnabled).toBe(true)
48
+ expect(policy.aggregateEnabled).toBe(true)
49
+ expect(policy.historyMode).toBe('routine')
50
+ expect(policy.freshTriggerTokens).toBe(8_192)
51
+ expect(policy.freshTargetTokens).toBe(3_072)
52
+ expect(policy.aggregateTriggerTokens).toBe(32_768)
53
+ expect(policy.aggregateTargetTokens).toBe(12_288)
54
+ expect(policy.historyTriggerTokens).toBe(500_000)
55
+ expect(policy.historyMinReclaimTokens).toBe(96_000)
56
+ expect(policy.presetOptions).toStrictEqual({
57
+ noNetSavingsGuard: true,
58
+ skipReductionRecovery: true,
59
+ dedupeToolResults: true,
60
+ summaryLocator: true,
61
+ prefixStabilizer: true,
62
+ readState: true,
63
+ estimator: { mode: '' },
64
+ })
65
+ // Other profiles never carry the capability matrix.
66
+ expect(resolvePolicy(config, 'balanced').presetOptions).toBeUndefined()
67
+ })
68
+
69
+ it('links Auto Compact watermarks like balanced does', () => {
70
+ const config = resolveConfig({})
71
+ const policy = resolvePolicy(config, 'tokenpilot-inspired', undefined, {
72
+ contextWindowTokens: 128_000,
73
+ autoCompactThresholdPercent: 80,
74
+ })
75
+ expect(policy.autoCompactTokens).toBe(102_400)
76
+ expect(policy.microDeadlineTokens).toBe(89_600)
77
+ expect(policy.historyTriggerTokens).toBe(64_000)
78
+ expect(policy.historyMinReclaimTokens).toBe(12_288)
79
+ expect(policy.historyKeepRecentTokens).toBe(8_192)
80
+ })
81
+
82
+ it('accepts persisted presetOptions overrides and rejects unknown keys', () => {
83
+ const base = {
84
+ profile: 'balanced',
85
+ custom: { version: 3, unit: 'tokens', fresh: { enabled: true, trigger: 2, target: 1 }, aggregate: { enabled: false, trigger: 2, target: 1 }, history: { enabled: false, trigger: 2, keepRecentToolCalls: 1, keepRecentTokens: 1, minReclaim: 1 }, prefixPolicy: 'preserve', tailTrim: { enabled: false, trigger: 2 } },
86
+ }
87
+ const doc = {
88
+ ...base,
89
+ presetOptions: { dedupeToolResults: false, estimatorMode: 'host' },
90
+ }
91
+ expect((parseContextCompressionSettings(structuredClone(doc)) as { presetOptions?: unknown }).presetOptions).toStrictEqual({
92
+ dedupeToolResults: false,
93
+ estimatorMode: 'host',
94
+ })
95
+ expect(() => parseContextCompressionSettings({
96
+ ...base,
97
+ presetOptions: { nope: true },
98
+ })).toThrow()
99
+ })
100
+ })
@@ -0,0 +1,58 @@
1
+ /**
2
+ * TokenPilot-inspired R2/R3 unit coverage: superseded-read classification and
3
+ * clustered omission summaries.
4
+ */
5
+ import { describe, expect, it } from 'vitest'
6
+ import {
7
+ clusterOmittedLines,
8
+ isSupersededRead,
9
+ toolCallPath,
10
+ } from '../../../src/runtime/tokenpilot/read-state.ts'
11
+ import type { SessionEvent } from '@deepseek-ai/dsh-session'
12
+
13
+ function callEvent(seq: number, name: string, args: Record<string, unknown>): SessionEvent {
14
+ return { seq, type: 'tool/call', data: { name, arguments: JSON.stringify(args) } } as unknown as SessionEvent
15
+ }
16
+
17
+ function resultEvent(seq: number): SessionEvent {
18
+ return { seq, type: 'tool/result', data: { message: { content: [{ type: 'text', text: 'x' }] } } } as unknown as SessionEvent
19
+ }
20
+
21
+ describe('tokenpilot read-state helpers', () => {
22
+ it('parses paths from path and file_path argument keys', () => {
23
+ expect(toolCallPath('{"path":"a.ts"}')).toBe('a.ts')
24
+ expect(toolCallPath('{"file_path":"b.ts"}')).toBe('b.ts')
25
+ expect(toolCallPath('not json')).toBeUndefined()
26
+ expect(toolCallPath('{"path":42}')).toBeUndefined()
27
+ })
28
+
29
+ it('marks a read superseded only by a later write-style call on the same path', () => {
30
+ const events: SessionEvent[] = [
31
+ resultEvent(1),
32
+ callEvent(2, 'read', { path: 'src/a.ts' }),
33
+ callEvent(3, 'bash', { command: 'echo write src/a.ts' }),
34
+ callEvent(4, 'edit', { path: 'src/other.ts' }),
35
+ ]
36
+ expect(isSupersededRead(events, 2, 'src/a.ts')).toBe(false)
37
+ const events2: SessionEvent[] = [...events, callEvent(5, 'file_edit', { path: 'src/a.ts' })]
38
+ expect(isSupersededRead(events2, 2, 'src/a.ts')).toBe(true)
39
+ expect(isSupersededRead(events, 2, undefined)).toBe(false)
40
+ // Mutations before the read never supersede it.
41
+ const events3: SessionEvent[] = [callEvent(0, 'edit', { path: 'src/a.ts' }), resultEvent(1), callEvent(2, 'read', { path: 'src/a.ts' })]
42
+ expect(isSupersededRead(events3, 2, 'src/a.ts')).toBe(false)
43
+ })
44
+
45
+ it('clusters omitted lines into an error/warn/info census', () => {
46
+ const text = [
47
+ 'ok line',
48
+ 'Error: bad thing',
49
+ 'warning: careful',
50
+ 'another ok',
51
+ 'FATAL: worse',
52
+ 'warn: also',
53
+ ].join('\n')
54
+ expect(clusterOmittedLines(text, 6)).toBe('6 lines omitted (2 error, 2 warn, 2 info)')
55
+ expect(clusterOmittedLines('fine', 1)).toBe('1 lines omitted (1 info)')
56
+ expect(clusterOmittedLines('fine', 0)).toBeUndefined()
57
+ })
58
+ })
@@ -0,0 +1,23 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { assertNever, deepFreeze } from '../../src/runtime/value.ts'
3
+
4
+ describe('version-neutral runtime values', () => {
5
+ it('freezes nested and cyclic data without freezing a live AbortSignal', () => {
6
+ const controller = new AbortController()
7
+ const value: { nested: { value: number }, self?: unknown, signal: AbortSignal } = {
8
+ nested: { value: 1 },
9
+ signal: controller.signal,
10
+ }
11
+ value.self = value
12
+
13
+ expect(deepFreeze(value)).toBe(value)
14
+ expect(Object.isFrozen(value)).toBe(true)
15
+ expect(Object.isFrozen(value.nested)).toBe(true)
16
+ expect(Object.isFrozen(controller.signal)).toBe(false)
17
+ })
18
+
19
+ it('reports an escaped closed-union member with its switch context', () => {
20
+ expect(() => assertNever({ kind: 'unexpected' } as never, 'value test'))
21
+ .toThrow('unreachable variant in value test: {"kind":"unexpected"}')
22
+ })
23
+ })