dsh-compact-saviour 0.2.6

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 (100) hide show
  1. package/ACCEPTANCE.md +16 -0
  2. package/LICENSE +21 -0
  3. package/README.md +110 -0
  4. package/THIRD_PARTY_NOTICES.md +50 -0
  5. package/cordis.patch.yml +3 -0
  6. package/cordis.yml +2 -0
  7. package/docs/development.md +28 -0
  8. package/dshx.yml +16 -0
  9. package/lib/client.js +5933 -0
  10. package/lib/client.js.map +1 -0
  11. package/lib/dsh-compact-saviour.js +1206 -0
  12. package/lib/types/client/context-elements.d.ts +6 -0
  13. package/lib/types/client/context-elements.d.ts.map +1 -0
  14. package/lib/types/client/context-elements.js +41 -0
  15. package/lib/types/client/context-elements.js.map +1 -0
  16. package/lib/types/client/index.d.ts +10 -0
  17. package/lib/types/client/index.d.ts.map +1 -0
  18. package/lib/types/client/index.js +217 -0
  19. package/lib/types/client/index.js.map +1 -0
  20. package/lib/types/client/meter-bridge.d.ts +12 -0
  21. package/lib/types/client/meter-bridge.d.ts.map +1 -0
  22. package/lib/types/client/meter-bridge.js +89 -0
  23. package/lib/types/client/meter-bridge.js.map +1 -0
  24. package/lib/types/client/ring-control.d.ts +32 -0
  25. package/lib/types/client/ring-control.d.ts.map +1 -0
  26. package/lib/types/client/ring-control.js +191 -0
  27. package/lib/types/client/ring-control.js.map +1 -0
  28. package/lib/types/client/satellite.d.ts +32 -0
  29. package/lib/types/client/satellite.d.ts.map +1 -0
  30. package/lib/types/client/satellite.js +163 -0
  31. package/lib/types/client/satellite.js.map +1 -0
  32. package/lib/types/client/waiting-copy.d.ts +10 -0
  33. package/lib/types/client/waiting-copy.d.ts.map +1 -0
  34. package/lib/types/client/waiting-copy.js +31 -0
  35. package/lib/types/client/waiting-copy.js.map +1 -0
  36. package/lib/types/compress.d.ts +31 -0
  37. package/lib/types/compress.d.ts.map +1 -0
  38. package/lib/types/compress.js +265 -0
  39. package/lib/types/compress.js.map +1 -0
  40. package/lib/types/controller.d.ts +31 -0
  41. package/lib/types/controller.d.ts.map +1 -0
  42. package/lib/types/controller.js +323 -0
  43. package/lib/types/controller.js.map +1 -0
  44. package/lib/types/dsh-compact-saviour.d.ts +10 -0
  45. package/lib/types/dsh-compact-saviour.d.ts.map +1 -0
  46. package/lib/types/dsh-compact-saviour.js +104 -0
  47. package/lib/types/dsh-compact-saviour.js.map +1 -0
  48. package/lib/types/message-source.d.ts +9 -0
  49. package/lib/types/message-source.d.ts.map +1 -0
  50. package/lib/types/message-source.js +2 -0
  51. package/lib/types/message-source.js.map +1 -0
  52. package/lib/types/pressure.d.ts +17 -0
  53. package/lib/types/pressure.d.ts.map +1 -0
  54. package/lib/types/pressure.js +71 -0
  55. package/lib/types/pressure.js.map +1 -0
  56. package/lib/types/shared.d.ts +42 -0
  57. package/lib/types/shared.d.ts.map +1 -0
  58. package/lib/types/shared.js +2 -0
  59. package/lib/types/shared.js.map +1 -0
  60. package/lib/types/summary-types.d.ts +20 -0
  61. package/lib/types/summary-types.d.ts.map +1 -0
  62. package/lib/types/summary-types.js +7 -0
  63. package/lib/types/summary-types.js.map +1 -0
  64. package/lib/types/vendor/region.d.ts +65 -0
  65. package/lib/types/vendor/region.d.ts.map +1 -0
  66. package/lib/types/vendor/region.js +412 -0
  67. package/lib/types/vendor/region.js.map +1 -0
  68. package/package.json +101 -0
  69. package/scripts/link-harness-dependencies.mjs +41 -0
  70. package/scripts/normalize-artifacts.mjs +7 -0
  71. package/src/client/assets.d.ts +1 -0
  72. package/src/client/context-elements.ts +39 -0
  73. package/src/client/index.tsx +214 -0
  74. package/src/client/meter-bridge.ts +65 -0
  75. package/src/client/ring-control.ts +134 -0
  76. package/src/client/satellite.ts +121 -0
  77. package/src/client/style.css +54 -0
  78. package/src/client/vendor/LICENSE +21 -0
  79. package/src/client/vendor/open-bot-motion.cjs +1673 -0
  80. package/src/client/vendor/open-bot-motion.d.cts +9 -0
  81. package/src/client/waiting-copy.tsx +29 -0
  82. package/src/compress.ts +210 -0
  83. package/src/controller.ts +248 -0
  84. package/src/dsh-compact-saviour.ts +82 -0
  85. package/src/message-source.ts +4 -0
  86. package/src/pressure.ts +69 -0
  87. package/src/shared.ts +13 -0
  88. package/src/summary-types.ts +11 -0
  89. package/src/vendor/region.ts +583 -0
  90. package/tests/compression.test.ts +130 -0
  91. package/tests/controller.test.ts +279 -0
  92. package/tests/interaction-fixture.tsx +38 -0
  93. package/tests/interaction.browser.mjs +201 -0
  94. package/tests/meter-bridge.browser.mjs +83 -0
  95. package/tests/motion.test.ts +26 -0
  96. package/tests/peer-range.test.ts +23 -0
  97. package/tests/pressure.test.ts +11 -0
  98. package/tests/transactions.test.ts +56 -0
  99. package/tsconfig.json +10 -0
  100. package/tsdown.config.ts +13 -0
@@ -0,0 +1,201 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readFile, mkdir, writeFile } from 'node:fs/promises';
3
+ import { createRequire } from 'node:module';
4
+ import { homedir } from 'node:os';
5
+ import { pathToFileURL, fileURLToPath } from 'node:url';
6
+ import ts from 'typescript';
7
+ const require = createRequire(import.meta.resolve('tsdown'));
8
+ const { rolldown } = await import(pathToFileURL(require.resolve('rolldown')).href);
9
+ const bundle = await rolldown({ input: fileURLToPath(new URL('./interaction-fixture.tsx', import.meta.url)),
10
+ transform: { define: { 'process.env.NODE_ENV': '"production"' } }, plugins: [{ name: 'fixture-typescript', async load(id) {
11
+ if (id.endsWith('.css')) return { code: '', moduleType: 'js' };
12
+ if (/\.tsx?$/.test(id)) return ts.transpileModule(await readFile(id, 'utf8'), { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.ESNext, jsx: ts.JsxEmit.ReactJSX } }).outputText;
13
+ } }] });
14
+ const output = await bundle.generate({ format: 'iife' }); await bundle.close();
15
+ const script = output.output.find(o => o.type === 'chunk').code;
16
+ const { launchPinnedChromium } = await import(pathToFileURL(`${homedir()}/.codex/playwright-runtime/runtime.mjs`).href);
17
+ const browser = await launchPinnedChromium();
18
+ const proofDir = process.env.COMPACT_PROOF_DIR ?? fileURLToPath(new URL('../.proof/interaction/', import.meta.url)); await mkdir(proofDir, { recursive: true });
19
+ try {
20
+ const page = await browser.newPage({ viewport: { width: 600, height: 420 }, deviceScaleFactor: 2 });
21
+ const errors = []; page.on('pageerror', e => { errors.push(String(e)); console.error(String(e)); });
22
+ await page.clock.install();
23
+ await page.setContent('<div id="root"></div>');
24
+ await page.addStyleTag({ content: `${await readFile(new URL('../src/client/style.css', import.meta.url), 'utf8')}
25
+ body{margin:0;background:#fafafa;font:14px -apple-system,sans-serif;color:#535963}.composer{position:absolute;bottom:32px;right:64px;display:flex;align-items:center;gap:12px}.composer>span:first-child{position:relative;display:inline-flex}#ring{min-width:28px;height:28px;display:flex;align-items:center;gap:4px;border:0;border-radius:50%;background:transparent}#ring:hover{background:#eceef1}[role=dialog]{position:fixed;bottom:80px;right:64px;width:264px;padding:12px;box-sizing:border-box;border-radius:16px;background:white;box-shadow:0 4px 24px #0001}.header{display:flex;gap:8px;align-items:center;font-size:12px}.header>span:last-child{margin-left:auto;color:#121821}.bar{height:4px;border-radius:4px;background:#eee;margin:12px 0}.bar>div{width:48%;height:4px;background:#498af7;border-radius:4px}dl{font-size:12px}dl>div{display:flex;justify-content:space-between;margin:10px 0}dd{margin:0}.send{border:0;border-radius:50%;width:34px;height:34px;background:#447ef0;color:white;font-size:22px}
26
+ ` });
27
+ await page.addScriptTag({ content: script });
28
+ if (process.env.COMPACT_LAYOUT === 'legacy') await page.evaluate(() => { layout = 'legacy'; render(); });
29
+ await page.clock.runFor(100); await page.locator('#ring').waitFor({ timeout: 3000 });
30
+ const ring = page.locator('#ring');
31
+ const visibleMeter = async () => {
32
+ // CSS compositor transitions run independently from the mocked JS clock.
33
+ await page.waitForFunction(() => {
34
+ const el = document.querySelector('#ring > svg[viewBox="0 0 14 14"]');
35
+ return el && getComputedStyle(el).opacity === '1';
36
+ }, undefined, { timeout: 3000 });
37
+ const value = await ring.locator(':scope > svg[viewBox="0 0 14 14"]').evaluate(el => {
38
+ const style = getComputedStyle(el), box = el.getBoundingClientRect();
39
+ return { visibility: style.visibility, opacity: Number(style.opacity), width: box.width, height: box.height,
40
+ strokes: [...el.querySelectorAll('circle')].map(c => getComputedStyle(c).stroke) };
41
+ });
42
+ assert.equal(value.visibility, 'visible'); assert.equal(value.opacity, 1);
43
+ assert.equal(value.width, 14); assert.equal(value.height, 14);
44
+ assert.ok(value.strokes.every(stroke => stroke !== 'none' && stroke !== 'transparent'));
45
+ };
46
+ // A rescue arriving through polling never opens a popup, steals focus, or
47
+ // requires a manual POST. Its state is available only when the user opens it.
48
+ await page.locator('.send').focus();
49
+ for (const phase of ['running', 'done', 'failed', 'idle']) {
50
+ await page.evaluate(phase => { job = { phase, message: phase, failures: 2 }; }, phase);
51
+ await page.clock.runFor(1500);
52
+ assert.equal(await page.locator('[role=dialog]').count(), 0);
53
+ assert.equal(await page.evaluate(() => document.activeElement.className), 'send');
54
+ assert.equal(await page.evaluate(() => requests.length), 0);
55
+ }
56
+ await ring.click(); assert.equal(await page.getByRole('button', { name: '手动压缩', exact: true }).count(), 1);
57
+ assert.equal(await page.locator('[data-compact-saviour]').count(), 1);
58
+ // Missing contextBreakdown is supported by the native meter; manual rescue
59
+ // must still be reachable before that optional projection arrives.
60
+ await page.evaluate(() => { hasBreakdown = false; render(); });
61
+ assert.equal(await page.getByRole('button', { name: '手动压缩', exact: true }).count(), 1);
62
+ await page.evaluate(() => { hasBreakdown = true; render(); });
63
+ assert.equal(await page.locator('[role=dialog] > :last-child').getAttribute('data-compact-saviour'), 'footer');
64
+ const geometry = await ring.evaluate(el => {
65
+ const icon = el.querySelector('svg[viewBox="0 0 14 14"]').getBoundingClientRect();
66
+ const art = el.querySelector('.dsh-cs-ring-art').getBoundingClientRect();
67
+ return { dx: (art.left + art.right - icon.left - icon.right) / 2, dy: (art.top + art.bottom - icon.top - icon.bottom) / 2, width: art.width, height: art.height };
68
+ });
69
+ assert.ok(Math.abs(geometry.dx) < .1 && Math.abs(geometry.dy) < .1, JSON.stringify(geometry));
70
+ assert.equal(geometry.width, 20); assert.equal(geometry.height, 20);
71
+ await page.screenshot({ path: `${proofDir}/manual-popup.png` });
72
+ await ring.click(); assert.equal(await page.locator('[role=dialog]').count(), 0);
73
+ await ring.focus(); await page.keyboard.press('Enter');
74
+ assert.equal(await page.getByRole('button', { name: '手动压缩', exact: true }).count(), 1);
75
+ await page.keyboard.press('Enter');
76
+ assert.equal(await page.evaluate(() => requests.length), 0, 'keyboard activation only opens the popup');
77
+ const center = async () => { const b = await ring.boundingBox(); return { x: b.x+b.width/2, y: b.y+b.height/2 }; };
78
+ let pos = await center(); await page.mouse.move(pos.x,pos.y); await page.mouse.down(); await page.clock.runFor(300); await page.mouse.up();
79
+ assert.equal(await page.evaluate(() => requests.length), 0, 'short hold does not compact');
80
+ await ring.click(); // close short-click popup
81
+ await page.mouse.down(); await page.clock.runFor(200); await page.mouse.move(pos.x-50,pos.y); await page.clock.runFor(650); await page.mouse.up();
82
+ assert.equal(await page.evaluate(() => requests.length), 0, 'moving away cancels');
83
+ await page.mouse.move(pos.x,pos.y); await page.mouse.down(); await page.clock.runFor(700); await page.clock.runFor(50);
84
+ assert.equal(await page.evaluate(() => requests.length), 1);
85
+ assert.equal(await ring.getAttribute('data-cs-motion'), 'running');
86
+ await page.clock.runFor(3200); await page.mouse.up();
87
+ assert.equal(await page.locator('[role=dialog]').count(), 1, 'release after a very long hold must not close popup');
88
+ assert.equal(await page.evaluate(() => requests.length), 1, 'holding continues to send only one request');
89
+ const a = await page.locator('.dsh-cs-body').getAttribute('d');
90
+ await page.clock.runFor(500); const b = await page.locator('.dsh-cs-body').getAttribute('d'); assert.notEqual(a,b);
91
+ assert.equal(await page.locator('.dsh-cs-dot').count(), 8);
92
+ // One call needs no subtitle or artificial chunk counter. Multi-call progress
93
+ // and merging stay in the button; backend totals include the merge work.
94
+ const setJob = async patch => {
95
+ await page.evaluate(patch => { job = { phase: 'running', failures: 0, ...patch }; }, patch);
96
+ await page.clock.runFor(1500);
97
+ };
98
+ await setJob({ message: '上下文可一次容纳,正在整段压缩…', completed: 0, total: 1 });
99
+ assert.equal(await page.getByRole('button', { name: '正在压缩', exact: true }).count(), 1);
100
+ assert.equal(await page.locator('.dsh-cs-progress, .dsh-cs-job-status').count(), 0);
101
+ assert.equal(await page.getByText('上下文可一次容纳', { exact: false }).count(), 0);
102
+ const copy = page.locator('.dsh-cs-waiting-copy > [data-active="true"]');
103
+ const initialCopy = await copy.textContent();
104
+ await page.clock.runFor(6100);
105
+ assert.notEqual(await copy.textContent(), initialCopy, 'copy rotates despite 1s job polling');
106
+ assert.equal(await page.getByRole('button', { name: '正在压缩', exact: true }).count(), 1, 'decorative copy is not announced as a new processing stage');
107
+ assert.equal(await page.locator('.dsh-cs-sr-status').textContent(), '正在压缩');
108
+ assert.equal(await page.locator('.dsh-cs-ellipsis').count(), 0, 'no jumping dots');
109
+ assert.ok(await page.locator('.dsh-cs-waiting-copy > span').evaluateAll(els => els.every(el => getComputedStyle(el).animationName === 'none' && getComputedStyle(el).transform === 'none')));
110
+ assert.equal(await copy.evaluate(el => getComputedStyle(el).transitionProperty), 'opacity');
111
+ await setJob({ message: '按压缩模型容量自动分为 4 块,正在压缩… 2/5', completed: 2, total: 5 });
112
+ assert.equal(await page.getByRole('button', { name: '正在压缩 2/5', exact: true }).count(), 1);
113
+ assert.equal(await page.locator('.dsh-cs-job-status').count(), 0);
114
+ const beforeCopy = await copy.textContent();
115
+ const beforeProgress = await page.locator('.dsh-cs-progress').boundingBox();
116
+ const beforeButton = await page.locator('.dsh-cs-compact').boundingBox();
117
+ await page.clock.runFor(6000);
118
+ assert.notEqual(await copy.textContent(), beforeCopy);
119
+ assert.deepEqual(await page.locator('.dsh-cs-progress').boundingBox(), beforeProgress, 'counter does not jump with wording changes');
120
+ assert.deepEqual(await page.locator('.dsh-cs-compact').boundingBox(), beforeButton);
121
+ await page.evaluate(() => { Object.defineProperty(document, 'visibilityState', { configurable: true, value: 'hidden' }); document.dispatchEvent(new Event('visibilitychange')); });
122
+ const hiddenCopy = await copy.textContent();
123
+ await page.clock.runFor(12000);
124
+ assert.equal(await copy.textContent(), hiddenCopy, 'background page pauses copy rotation');
125
+ await page.evaluate(() => { delete document.visibilityState; document.dispatchEvent(new Event('visibilitychange')); });
126
+ assert.equal(await page.locator('.dsh-cs-compact').evaluate(el => getComputedStyle(el, '::before').animationName), 'dsh-cs-shimmer');
127
+ const shimmerFrames = await page.locator('.dsh-cs-compact').evaluate(el => {
128
+ // Sample the actual CSS animation away from its intentional resting interval.
129
+ const animation = el.getAnimations({ subtree: true }).find(a => a.animationName === 'dsh-cs-shimmer');
130
+ animation.pause(); animation.currentTime = 200;
131
+ const first = getComputedStyle(el, '::before').transform;
132
+ animation.currentTime = 1100;
133
+ const second = getComputedStyle(el, '::before').transform;
134
+ animation.play(); return [first, second];
135
+ });
136
+ assert.notEqual(...shimmerFrames);
137
+ await page.screenshot({ path: `${proofDir}/running-light.png` });
138
+ await page.addStyleTag({ content: 'body{background:#1b1d22;color:#bec2ca;--dsw-alias-label-primary:#eef1f8;--dsw-alias-label-secondary:#b2b6c1;--dsw-alias-label-tertiary:#989fad;--dsw-alias-separator:#383b43;--dsw-alias-interactive-bg-hover:#30333b;--dsw-static-blue-550:#82a9ff}[role=dialog]{background:#252830}#ring:hover{background:#30333b}.header>span:last-child{color:#eef1f8}.bar{background:#383b43}' });
139
+ await page.screenshot({ path: `${proofDir}/running-dark.png` });
140
+ await setJob({ message: '模型实际上下文受限,正在缩小超限分块… 2/7', completed: 2, total: 7 });
141
+ assert.equal(await page.getByRole('button', { name: '正在压缩 2/7', exact: true }).count(), 1);
142
+ await setJob({ message: '正在合并摘要,保留待办和关键细节… 6/7', completed: 6, total: 7 });
143
+ assert.equal(await page.getByRole('button', { name: '正在合并摘要 6/7', exact: true }).count(), 1);
144
+ assert.equal(await copy.textContent(), '正在合并摘要…', 'actual stage change resets the copy');
145
+ assert.equal(await page.locator('.dsh-cs-job-status').count(), 0);
146
+ await page.screenshot({ path: `${proofDir}/merging-dark.png` });
147
+ await page.emulateMedia({ reducedMotion: 'reduce' }); await page.clock.runFor(1000);
148
+ await page.waitForFunction(() => [...document.querySelectorAll('.dsh-cs-dot')].every(dot => dot.getAttribute('r') === '0'));
149
+ const still = await page.locator('.dsh-cs-body').getAttribute('d'); await page.clock.runFor(1000);
150
+ assert.equal(await page.locator('.dsh-cs-body').getAttribute('d'), still, 'reduced motion freezes');
151
+ assert.equal(await page.locator('.dsh-cs-compact').evaluate(el => getComputedStyle(el, '::before').animationName), 'none');
152
+ await page.clock.runFor(7000);
153
+ assert.equal(await copy.textContent(), '正在合并摘要…');
154
+ assert.equal(await copy.evaluate(el => getComputedStyle(el).transitionDuration), '0s');
155
+ await page.emulateMedia({ reducedMotion: 'no-preference' });
156
+ await page.getByRole('button',{name:'取消',exact:true}).click(); await page.clock.runFor(500);
157
+ assert.equal(await ring.getAttribute('data-cs-motion'), null, 'cancel restores occupancy ring');
158
+ await visibleMeter();
159
+ assert.equal(await page.evaluate(() => requests.length), 2);
160
+ // Queue shows a resting bot; running starts orbit; all terminal outcomes restore the ring.
161
+ for (const phase of ['queued','running','done','running','failed']) {
162
+ await page.evaluate(phase => { job={phase,message:phase,failures:0}; }, phase); await page.clock.runFor(1500);
163
+ assert.equal(await ring.getAttribute('data-cs-motion'), ['queued','running'].includes(phase) ? phase : null);
164
+ if (['done', 'failed'].includes(phase)) await visibleMeter();
165
+ assert.equal(await page.locator('.dsh-cs-job-status').count(), ['done', 'failed'].includes(phase) ? 1 : 0);
166
+ }
167
+ // Drop the animation's exit callback. Job completion must still restore the
168
+ // *rendered* ring, not just a bookkeeping attribute (the former test gap).
169
+ await page.evaluate(() => {
170
+ window.savedSetRunning = Satellite.prototype.setRunning;
171
+ Satellite.prototype.setRunning = function(running, done) { savedSetRunning.call(this, running); };
172
+ job = { phase: 'running', message: '正在压缩…', failures: 0 };
173
+ });
174
+ await page.clock.runFor(1500);
175
+ await page.evaluate(() => { job = { phase: 'done', message: '压缩完成(按当前消息和工具估算)。', beforeTokens: 472000, afterTokens: 50000, failures: 0 }; });
176
+ await page.clock.runFor(1500);
177
+ assert.equal(await ring.getAttribute('data-cs-motion'), 'settling', 'callback deliberately did not run');
178
+ await visibleMeter();
179
+ assert.match(await page.locator('.dsh-cs-job-status').textContent(), /472K → 50K/);
180
+ assert.equal(await page.locator('.dsh-cs-robot').evaluate(el => getComputedStyle(el).opacity), '0');
181
+ assert.equal(await page.locator('.dsh-cs-compact').evaluate(el => getComputedStyle(el, '::before').animationIterationCount), '1');
182
+ await page.screenshot({ path: `${proofDir}/completed-restored.png` });
183
+ await page.evaluate(() => { Satellite.prototype.setRunning = savedSetRunning; });
184
+ await page.evaluate(() => { apiError=true; });
185
+ await page.getByRole('button',{name:'手动压缩',exact:true}).click(); await page.clock.runFor(1500);
186
+ assert.equal(await page.getByRole('alert').textContent(), '请选择压缩模型。', 'polling must not erase actionable errors');
187
+ const count = await page.evaluate(() => requests.length);
188
+ await page.mouse.move(pos.x,pos.y); await page.mouse.down(); await page.clock.runFor(200);
189
+ await page.evaluate(() => { session='fixture-B'; render(); }); await page.clock.runFor(800); await page.mouse.up();
190
+ assert.equal(await page.evaluate(() => requests.length), count, 'session change cancels pending hold');
191
+ assert.equal(await page.locator('.dsh-cs-ring-art').count(),1, 'no duplicate icon after session change');
192
+ await page.evaluate(() => { bridgeVisible=false; render(); }); await page.clock.runFor(100);
193
+ assert.equal(await ring.locator(':scope > svg').evaluate(el => el.style.opacity), '', 'unmount restores original SVG opacity');
194
+ assert.equal(await ring.locator(':scope > svg').evaluate(el => el.style.visibility), '', 'unmount restores original SVG visibility');
195
+ assert.equal(await ring.getAttribute('data-cs-phase'), null);
196
+ await page.evaluate(() => unmount()); await page.clock.runFor(2000);
197
+ assert.equal(await page.locator('.dsh-cs-ring-art').count(),0);
198
+ assert.deepEqual(errors, []);
199
+ const proof = { layout: process.env.COMPACT_LAYOUT ?? 'portal', optionalBreakdown:true, iconAligned:true, shortClick:true, keyboardAccess:true, cancelledHold:true, dragCancel:true, singleLongPress:true, longRelease:true, orbit:true, backgroundShimmer:true, rotatingWaitingCopy:true, noJumpingText:true, stableProgressPosition:true, backgroundPause:true, stableAccessibleStatus:true, silentAutomaticJob:true, inlineAdaptiveProgress:true, noRunningSubtitle:true, mergingLabel:true, reducedMotion:true, terminalStates:true, renderedMeterRestored:true, droppedExitCallbackSafe:true, persistentErrors:true, sessionCleanup:true, consoleErrors:errors };
200
+ await writeFile(`${proofDir}/proof.json`, JSON.stringify(proof,null,2)); console.log(JSON.stringify(proof,null,2));
201
+ } finally { await browser.close(); }
@@ -0,0 +1,83 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { pathToFileURL } from 'node:url';
5
+ import ts from 'typescript';
6
+
7
+ // Automated fixture only. Never connects to a user's browser or running Host.
8
+ const { launchPinnedChromium } = await import(pathToFileURL(`${homedir()}/.codex/playwright-runtime/runtime.mjs`).href);
9
+ const source = await readFile(new URL('../src/client/meter-bridge.ts', import.meta.url), 'utf8');
10
+ const elements = ts.transpileModule(await readFile(new URL('../src/client/context-elements.ts', import.meta.url), 'utf8'), { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS } }).outputText;
11
+ const compiled = ts.transpileModule(source, { compilerOptions: { target: ts.ScriptTarget.ES2022, module: ts.ModuleKind.CommonJS } }).outputText;
12
+ const browser = await launchPinnedChromium();
13
+ try {
14
+ const page = await browser.newPage();
15
+ await page.setContent(`<main><span><button aria-label="上下文已用 100%" aria-haspopup="dialog" aria-expanded="true"><svg viewBox="0 0 14 14"><circle></circle><circle stroke-dasharray="34.55751918948772 34.55751918948772"></circle></svg></button><div role="dialog"><div><span>上下文已用</span><span>100%</span><span></span><span>~7.8M / 1M</span></div><div><div style="width:4.323308%"></div><div style="width:64.097744%"></div><div style="width:31.578948%"></div></div><dl><div><dt>系统提示词</dt><dd>~2.3K</dd></div><div><dt>工具定义</dt><dd>~34.1K</dd></div><div><dt>对话消息</dt><dd>~16.8K</dd></div></dl><div data-compact-saviour><button>手动压缩</button></div></div></span></main>`);
16
+ await page.addScriptTag({ content: `(()=>{const exports={};const require=()=>{const exports={};${elements};return exports;};${compiled};window.MeterBridge=exports.MeterBridge;})()` });
17
+ const proof = await page.evaluate(async () => {
18
+ const bridge = new window.MeterBridge(), ring = document.querySelector('button[aria-haspopup]');
19
+ const header = document.querySelector('[role=dialog]').firstElementChild;
20
+ const snapshot = () => ({ text: document.querySelector('main').textContent, aria: ring.getAttribute('aria-label'),
21
+ circle: ring.querySelectorAll('circle')[1].getAttribute('stroke-dasharray'), widths: [...header.nextElementSibling.children].map(e => e.style.width) });
22
+ const before = snapshot();
23
+ const textNode = header.children[1].firstChild;
24
+ const context = { corrected: true, reason: 'invalid-usage', usedTokens: 53_200, reportedTokens: 7_800_000, contextWindow: 1_000_000, parts: [2300, 34100, 16800] };
25
+ let mutations = 0;
26
+ const observer = new MutationObserver(() => { mutations++; if (mutations < 100) bridge.apply(ring, context); });
27
+ observer.observe(document.querySelector('main'), { subtree: true, attributes: true, characterData: true, childList: true });
28
+ bridge.apply(ring, context);
29
+ await new Promise(resolve => setTimeout(resolve, 25));
30
+ observer.disconnect();
31
+ const corrected = { reading: header.children[1].textContent, figures: header.children[3].textContent,
32
+ aria: ring.getAttribute('aria-label'), sameTextNode: textNode === header.children[1].firstChild,
33
+ barWidth: [...header.nextElementSibling.children].reduce((n, e) => n + parseFloat(e.style.width), 0),
34
+ circle: parseFloat(ring.querySelectorAll('circle')[1].getAttribute('stroke-dasharray')),
35
+ buttons: document.querySelectorAll('[data-compact-saviour] button').length, mutations };
36
+ bridge.restore();
37
+ // CSSOM may canonicalize whitespace in style attributes; compare owned values.
38
+ const restored = JSON.stringify(snapshot()) === JSON.stringify(before);
39
+ bridge.apply(ring, context);
40
+ header.children[1].firstChild.nodeValue = '40%';
41
+ header.children[3].firstChild.nodeValue = '~400K / 1M';
42
+ ring.setAttribute('aria-label', '上下文已用 40%');
43
+ bridge.apply(ring, context);
44
+ bridge.apply(ring, { ...context, corrected: false });
45
+ return { corrected, restored, laterOwnerReading: header.children[1].textContent, laterOwnerFigures: header.children[3].textContent, laterOwnerAria: ring.getAttribute('aria-label') };
46
+ });
47
+ assert.equal(proof.corrected.reading, '5%'); assert.equal(proof.corrected.figures, '~53.2K / 1M');
48
+ assert.equal(proof.corrected.aria, '上下文已用 5%'); assert.ok(proof.corrected.sameTextNode);
49
+ assert.ok(Math.abs(proof.corrected.barWidth - 5) < .00001);
50
+ assert.ok(Math.abs(proof.corrected.circle - 2 * Math.PI * 5.5 * .05) < .000001);
51
+ assert.equal(proof.corrected.buttons, 1); assert.ok(proof.corrected.mutations < 5, 'observer must settle');
52
+ assert.ok(proof.restored); assert.equal(proof.laterOwnerReading, '40%'); assert.equal(proof.laterOwnerFigures, '~400K / 1M'); assert.equal(proof.laterOwnerAria, '上下文已用 40%');
53
+ console.log(JSON.stringify(proof, null, 2));
54
+ const portalProof = await page.evaluate(() => {
55
+ const bridge = new window.MeterBridge(), ring = document.querySelector('button[aria-haspopup]');
56
+ const panel = document.querySelector('[role=dialog]');
57
+ document.body.append(panel);
58
+ const label = document.createElement('span'); label.textContent = '40%'; ring.append(label);
59
+ panel.querySelector('dl').remove();
60
+ const header = panel.firstElementChild;
61
+ const context = { corrected: true, usedTokens: 53_200, reportedTokens: 7_800_000, contextWindow: 1_000_000, parts: [2300, 34100, 16800] };
62
+ bridge.apply(ring, context);
63
+ const corrected = [label.textContent, header.children[1].textContent, header.children[3].textContent];
64
+ bridge.restore();
65
+ const restored = [label.textContent, header.children[1].textContent];
66
+ // Two simultaneous composers without an explicit aria-controls are
67
+ // ambiguous: the bridge must not write into either one's body portal.
68
+ const other = ring.cloneNode(true); document.body.append(other);
69
+ bridge.apply(ring, context);
70
+ const ambiguousPanel = header.children[1].textContent;
71
+ bridge.restore(); other.remove();
72
+ ring.setAttribute('aria-expanded', 'false');
73
+ bridge.apply(ring, context);
74
+ const closedPanel = header.children[1].textContent;
75
+ bridge.restore();
76
+ return { corrected, restored, ambiguousPanel, closedPanel };
77
+ });
78
+ assert.deepEqual(portalProof.corrected, ['5%', '5%', '~53.2K / 1M']);
79
+ assert.deepEqual(portalProof.restored, ['40%', '40%']);
80
+ assert.equal(portalProof.ambiguousPanel, '40%');
81
+ assert.equal(portalProof.closedPanel, '40%');
82
+ console.log(JSON.stringify({ portalProof }, null, 2));
83
+ } finally { await browser.close(); }
@@ -0,0 +1,26 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import motion from '../src/client/vendor/open-bot-motion.cjs';
4
+ import { orbitTime, settleFrame } from '../src/client/satellite.js';
5
+
6
+ test('satellite loop preserves position and pose across its wrap', () => {
7
+ const before = motion.getBot7State(orbitTime(4.4 - 1e-7));
8
+ const after = motion.getBot7State(orbitTime(4.4 + 1e-7));
9
+ for (const key of Object.keys(before.bot)) {
10
+ if (typeof before.bot[key] === 'number') assert.ok(Math.abs(Number(before.bot[key]) - Number(after.bot[key])) < 1e-4, key);
11
+ }
12
+ before.dots.forEach((dot, i) => {
13
+ for (const key of ['x', 'y', 'r'] as const) assert.ok(Math.abs(dot[key] - after.dots[i]![key]) < 1e-4, `dot ${i} ${key}`);
14
+ });
15
+ });
16
+
17
+ test('satellite completion settles from its current pose without jumping to a fixed phase', () => {
18
+ for (const time of [2.45, 2.8, 3.7, 5.9, 6.3]) {
19
+ const current = motion.getBot7State(time);
20
+ const start = settleFrame(current, 0), end = settleFrame(current, 1);
21
+ for (const key of Object.keys(current.bot)) if (typeof current.bot[key] === 'number') assert.equal(start.bot[key], current.bot[key]);
22
+ assert.deepEqual(start.dots, current.dots);
23
+ assert.deepEqual(end.bot, motion.getBot7State(0).bot);
24
+ assert.ok(end.dots.every(dot => dot.r === 0));
25
+ }
26
+ });
@@ -0,0 +1,23 @@
1
+ import assert from 'node:assert/strict';
2
+ import { readFileSync } from 'node:fs';
3
+ import test from 'node:test';
4
+ import semver from 'semver';
5
+
6
+ const RANGE = '>=0.2.0-rc.1 <0.2.1';
7
+ const ACCEPT = ['0.2.0-rc.2', '0.2.0', '0.2.0-rc.2'];
8
+ const REJECT = ['0.2.0-alpha', '0.2.0-alpha.1', '0.2.0-alpha.2', '0.1.7-rc.2', '0.1.7-rc.1', '0.2.1'];
9
+
10
+ test('Harness peers use >=0.2.0-rc.1 <0.2.1', () => {
11
+ const pkg = JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8'));
12
+ assert.equal(pkg.version, '0.2.6');
13
+ assert.equal(semver.satisfies('0.2.0-rc.2', RANGE), true);
14
+ const peers = Object.entries(pkg.peerDependencies).filter(([name]) => name.startsWith('@deepseek-ai/dsh'));
15
+ assert.ok(peers.length >= 17);
16
+ for (const [name, range] of peers) assert.equal(range, RANGE, name);
17
+ assert.equal(pkg.devDependencies['@deepseek-ai/dsh-agent-loop'], '0.2.0-rc.2');
18
+ assert.equal(pkg.devDependencies['@deepseek-ai/dsh-agent-loop-testkit'], '0.2.0-rc.2');
19
+ assert.equal(pkg.peerDependencies['@deepseek-ai/cordis'], '^4.0.4');
20
+ assert.equal(pkg.peerDependencies['@deepseek-ai/schemastery'], '^3.18.4');
21
+ for (const version of ACCEPT) assert.equal(semver.satisfies(version, RANGE), true, version);
22
+ for (const version of REJECT) assert.equal(semver.satisfies(version, RANGE), false, version);
23
+ });
@@ -0,0 +1,11 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { pressureEstimate } from '../src/pressure.js';
4
+ import type { TokenMeasurement } from '@deepseek-ai/dsh-token-meter';
5
+
6
+ test('oversized real surfaces and missing or valid anchors never get a lower pressure override', () => {
7
+ const value = { baseline: { kind: 'usage', tokens: 8_000_000 }, surfaceTokens: 90_000 } as TokenMeasurement;
8
+ assert.equal(pressureEstimate(value, 100_000), undefined);
9
+ assert.equal(pressureEstimate({ ...value, surfaceTokens: 4000 }, 100_000), 4000);
10
+ assert.equal(pressureEstimate({ ...value, baseline: { kind: 'estimated', tokens: 8_000_000 }, surfaceTokens: 4000 }, 100_000), undefined);
11
+ });
@@ -0,0 +1,56 @@
1
+ import { test } from 'node:test';
2
+ import assert from 'node:assert/strict';
3
+ import { Context } from '@deepseek-ai/cordis';
4
+ import AgentLoop from '@deepseek-ai/dsh-agent-loop';
5
+ import { mountAgentLoopTestDependencies } from '@deepseek-ai/dsh-agent-loop-testkit';
6
+ import TokenMeter from '@deepseek-ai/dsh-token-meter';
7
+ import { createUserMessage, LlmAdapter } from '@deepseek-ai/dsh-llm';
8
+ import { SessionId } from '@deepseek-ai/dsh-session';
9
+ import { compactSurfaceRegion, selectCompactableRange } from '../src/vendor/region.js';
10
+
11
+ class MockAdapter extends LlmAdapter {
12
+ async resolveModel(provider: string, model: string) { return { provider, id: model, name: model, context: { contextWindow: 100000 } }; }
13
+ async *stream() {
14
+ yield { type: 'block-start' as const, index: 0, blockType: 'text' as const };
15
+ yield { type: 'block-end' as const, index: 0, block: { type: 'text' as const, text: 'acknowledged' } };
16
+ yield { type: 'finish' as const, reason: { kind: 'stop' as const } };
17
+ }
18
+ }
19
+ async function harness() {
20
+ const ctx = new Context(); await mountAgentLoopTestDependencies(ctx);
21
+ await ctx.plugin(AgentLoop, { agents: [] }); await ctx.plugin(TokenMeter);
22
+ ctx.llm.registerAdapter(['mock'], new MockAdapter());
23
+ const agent = await ctx.agentLoop.create(SessionId(`cs-${Math.random()}`), { provider: 'mock', model: 'mock' });
24
+ for (const text of ['older history '.repeat(1000), 'recent exact request '.repeat(300)]) {
25
+ agent.followup(createUserMessage({ content: [{ type: 'text', text }], source: { kind: 'user' } })); await agent.whenIdle();
26
+ }
27
+ return { ctx, agent };
28
+ }
29
+ test('real session transaction preserves raw log and recent tail, records honest provenance', async () => {
30
+ const { ctx, agent } = await harness();
31
+ try {
32
+ const raw = agent.session.snapshotEvents(), tail = agent.session.surface.nodes.at(-1);
33
+ const range = selectCompactableRange(agent.session, ctx.tokenMeter.measure(agent.session), 1000)!;
34
+ await agent.runMaintenance(signal => compactSurfaceRegion({ meter: ctx.tokenMeter, summarize: async () => ({ summary: [{ type: 'text', text: 'Historical state. User asked to continue.' }], provider: 'rescue', model: 'fast' }) }, agent.session, range.start, range.end, agent, { owner: null, stability: 'selected-span', flush: async () => { await ctx.sessions.flush(agent.session); } }, signal));
35
+ assert.deepEqual(agent.session.snapshotEvents().slice(0, raw.length), raw);
36
+ assert.ok(agent.session.surface.nodes.includes(tail!));
37
+ const summary = agent.session.snapshotEvents().find(e => e.type === 'compaction/summary');
38
+ assert.equal(summary?.data.model, 'fast'); assert.equal(summary?.data.llmStreamCall, undefined);
39
+ } finally { await ctx.fiber.dispose(); }
40
+ });
41
+ test('failed or nonshrinking summary leaves visible history unchanged and closes official bracket', async () => {
42
+ for (const scenario of ['failure', 'nonshrinking']) {
43
+ const { ctx, agent } = await harness();
44
+ try {
45
+ const before = [...agent.session.surface.nodes];
46
+ const range = selectCompactableRange(agent.session, ctx.tokenMeter.measure(agent.session), 0)!;
47
+ await assert.rejects(agent.runMaintenance(signal => compactSurfaceRegion({ meter: ctx.tokenMeter, summarize: async () => {
48
+ if (scenario === 'failure') throw new Error('quota exhausted');
49
+ return { summary: [{ type: 'text', text: 'large '.repeat(10000) }], provider: 'rescue', model: 'fast' };
50
+ } }, agent.session, range.start, range.end, agent, { owner: null, stability: 'selected-span' }, signal)));
51
+ assert.deepEqual(agent.session.surface.nodes, before);
52
+ assert.equal(agent.session.snapshotEvents().at(-1)?.type, 'compaction/end');
53
+ assert.equal(agent.session.snapshotEvents().filter(e => e.type === 'compaction/summary').length, 0);
54
+ } finally { await ctx.fiber.dispose(); }
55
+ }
56
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,10 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2024", "module": "NodeNext", "moduleResolution": "NodeNext",
4
+ "strict": true, "skipLibCheck": true, "declaration": true,
5
+ "declarationMap": true, "sourceMap": true,
6
+ "rootDir": "src", "outDir": "lib/types", "jsx": "react-jsx",
7
+ "lib": ["ES2024", "DOM", "DOM.Iterable"], "types": ["node", "react", "react-dom"]
8
+ },
9
+ "include": ["src"]
10
+ }
@@ -0,0 +1,13 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+
5
+ // Builds use an explicit checkout, never an implicit production configuration.
6
+ const root = process.env.DSHX_HARNESS;
7
+ if (!root) throw new Error('Set DSHX_HARNESS to the checkout used for this build.');
8
+ const adapter = resolve(root, 'tools/dshx/src/client-build.js');
9
+ if (!existsSync(adapter)) throw new Error('DSHX externalClientBundle adapter is missing.');
10
+ const { externalClientBundle } = await import(pathToFileURL(adapter).href);
11
+ export default externalClientBundle('dsh-compact-saviour', ['lib/types/dsh-compact-saviour.js'], {
12
+ clientEntry: 'src/client/index.tsx',
13
+ });