dsh-context-compression-improved 0.2.1 → 0.3.0
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.
- package/.gitattributes +1 -1
- package/.github/workflows/ci.yml +1 -7
- package/CHANGELOG.ja.md +18 -21
- package/CHANGELOG.ko.md +17 -21
- package/CHANGELOG.md +16 -17
- package/CHANGELOG.zh.md +15 -18
- package/CONTRIBUTING.md +1 -1
- package/README.ja.md +1 -20
- package/README.ko.md +12 -30
- package/README.md +1 -19
- package/README.zh.md +5 -21
- package/THIRD_PARTY_NOTICES.md +2 -2
- package/docs/compat-0.1.5-report.md +77 -0
- package/docs/installation.ja.md +6 -4
- package/docs/installation.ko.md +9 -7
- package/docs/installation.md +3 -1
- package/docs/installation.zh.md +3 -2
- package/docs/repair-log.md +54 -33
- package/eslint.config.js +2 -2
- package/package.json +56 -36
- package/packages/selector/README.md +7 -12
- package/packages/selector/README.zh.md +6 -13
- package/packages/selector/THIRD_PARTY_NOTICES.md +2 -8
- package/packages/selector/cordis.patch.yml +7 -5
- package/packages/selector/dsh.plugin.json +2 -2
- package/packages/selector/lib/client.d.ts +31 -5
- package/packages/selector/lib/client.js +100 -82
- package/packages/selector/lib/index.d.ts +8 -20
- package/packages/selector/lib/index.js +9 -14
- package/packages/selector/lib/invariant.js +4 -4
- package/packages/selector/lib/pruner.d.ts +13 -4
- package/packages/selector/lib/pruner.js +91 -46
- package/packages/selector/lib/tail-trim.js +13 -4
- package/packages/selector/package.json +23 -23
- package/packages/selector/screenshots.json +1 -4
- package/packages/selector/src/client/CompressionProfileControls.tsx +5 -8
- package/packages/selector/src/client/CompressionProfileSelector.tsx +9 -5
- package/packages/selector/src/client/EstimatorControls.tsx +28 -32
- package/packages/selector/src/client/index.ts +101 -63
- package/packages/selector/src/client/locales.ts +0 -2
- package/packages/selector/src/client/preset-options.ts +49 -35
- package/packages/selector/src/client/settings-section.tsx +4 -6
- package/packages/selector/src/deepseek-v4-tokenizer.ts +0 -5
- package/packages/selector/src/index.ts +41 -46
- package/packages/selector/src/invariant.ts +5 -5
- package/packages/selector/src/pruner.ts +67 -36
- package/packages/selector/src/runtime/measurement.ts +6 -2
- package/packages/selector/src/runtime/session-events.ts +14 -1
- package/packages/selector/src/runtime/tail-trim.ts +4 -4
- package/packages/selector/src/runtime/tokenpilot/estimator.ts +34 -3
- package/packages/selector/src/runtime/types.ts +1 -1
- package/packages/selector/tests/auto-compact.client.spec.tsx +0 -13
- package/packages/selector/tests/built/client-artifact.spec.ts +5 -1
- package/packages/selector/tests/code-skeleton.client.spec.ts +2 -1
- package/packages/selector/tests/custom-contract.client.spec.ts +2 -1
- package/packages/selector/tests/estimator-channel.client.spec.tsx +16 -11
- package/packages/selector/tests/estimator-route-registration.host.spec.ts +12 -0
- package/packages/selector/tests/host-preset-overlay.host.spec.ts +6 -4
- package/packages/selector/tests/preset-options-write.client.spec.ts +87 -79
- package/packages/selector/tests/preset-overlay-loader.e2e.host.spec.ts +17 -12
- package/packages/selector/tests/preset-overlay.host.spec.ts +5 -2
- package/packages/selector/tests/profiles.client.spec.tsx +3 -3
- package/packages/selector/tests/public/package-contract.client.spec.ts +27 -3
- package/packages/selector/tests/runtime/public/public-runtime.spec.ts +103 -81
- package/packages/selector/tests/runtime/session-events.spec.ts +1 -1
- package/packages/selector/tests/settings-seat.client.spec.ts +86 -0
- package/packages/selector/tests/standing-generation.host.spec.ts +88 -71
- package/packages/selector/tests/subagent-cache-reuse.host.spec.ts +24 -21
- package/pnpm-workspace.yaml +52 -5
- package/scripts/capture-profile-baseline.mjs +42 -0
- package/scripts/generate-tokenizer-fixtures.py +5 -5
- package/scripts/generate-vision-fixtures.py +2 -2
- package/scripts/{packed-components-smoke.ts → packed-components-smoke.mjs} +116 -119
- package/scripts/{packed-install-e2e.ts → packed-install-e2e.mjs} +188 -160
- package/scripts/{verify-release.ts → verify-release.mjs} +70 -94
- package/tests/TEST_INVENTORY.md +6 -7
- package/scripts/capture-profile-baseline.ts +0 -80
- package/tsconfig.scripts.json +0 -13
- /package/{packages/selector/assets/screenshots → docs/assets}/context-compression-selector-profiles.jpg +0 -0
- /package/{packages/selector/assets/screenshots → docs/assets}/context-compression-selector-settings.png +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { afterEach, describe, expect, it, vi } from 'vitest'
|
|
2
2
|
import { Context } from '@deepseek-ai/cordis'
|
|
3
3
|
import {
|
|
4
|
-
CallId,
|
|
4
|
+
ToolCallId as CallId,
|
|
5
5
|
createMessage,
|
|
6
6
|
createUserMessage,
|
|
7
7
|
createToolResultMessage,
|
|
@@ -18,25 +18,26 @@ import BasicCompactionEngine from '@deepseek-ai/dsh-compaction-basic'
|
|
|
18
18
|
import SessionStore, {
|
|
19
19
|
Session,
|
|
20
20
|
SessionId,
|
|
21
|
+
SessionSeq,
|
|
21
22
|
canonicalHeader,
|
|
22
23
|
} from '@deepseek-ai/dsh-session'
|
|
23
24
|
import {
|
|
24
25
|
agentEvents,
|
|
25
|
-
Inbox,
|
|
26
26
|
type Agent,
|
|
27
27
|
} from '@deepseek-ai/dsh-agent'
|
|
28
28
|
import {
|
|
29
29
|
SettingsProvider,
|
|
30
|
-
settingsNamespace,
|
|
31
30
|
type SettingsNamespace,
|
|
32
31
|
} from '@deepseek-ai/dsh-settings'
|
|
33
32
|
import SystemPrompt from '@deepseek-ai/dsh-system-prompt'
|
|
33
|
+
import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection'
|
|
34
34
|
import TokenMeter from '@deepseek-ai/dsh-token-meter'
|
|
35
35
|
import ToolRuntime from '@deepseek-ai/dsh-tools'
|
|
36
36
|
import { CompactionId } from '@deepseek-ai/dsh-compaction'
|
|
37
37
|
import InvariantRegistry from '@deepseek-ai/dsh-invariants'
|
|
38
38
|
import * as SelectorHost from '../../../src/index.ts'
|
|
39
39
|
import * as RuntimeInvariant from '../../../src/invariant.ts'
|
|
40
|
+
import { sessionEvents } from '../../../src/runtime/session-events.ts'
|
|
40
41
|
import ToolResultPruner, {
|
|
41
42
|
CONTEXT_COMPRESSION_SETTINGS_NAMESPACE,
|
|
42
43
|
DEFAULT_CUSTOM_COMPRESSION_POLICY,
|
|
@@ -130,10 +131,11 @@ class NativeSummaryAdapter extends LlmAdapter {
|
|
|
130
131
|
async function runtimeContext(): Promise<Context> {
|
|
131
132
|
const ctx = new Context()
|
|
132
133
|
activeContexts.push(ctx)
|
|
133
|
-
await ctx.plugin(SessionStore)
|
|
134
|
-
await ctx.plugin(SystemPrompt)
|
|
135
|
-
await ctx.plugin(ToolRuntime)
|
|
136
|
-
await ctx.plugin(
|
|
134
|
+
await ctx.plugin(SessionStore).await()
|
|
135
|
+
await ctx.plugin(SystemPrompt).await()
|
|
136
|
+
await ctx.plugin(ToolRuntime).await()
|
|
137
|
+
await ctx.plugin(SessionProjectionRegistry).await()
|
|
138
|
+
await ctx.plugin(TokenMeter).await()
|
|
137
139
|
return ctx
|
|
138
140
|
}
|
|
139
141
|
|
|
@@ -180,6 +182,7 @@ function appendToolTurn(
|
|
|
180
182
|
}
|
|
181
183
|
session.append('step/start', { turn, step: 1 })
|
|
182
184
|
const assistant = session.append('assistant/message', {
|
|
185
|
+
stream: [],
|
|
183
186
|
turn,
|
|
184
187
|
step: 1,
|
|
185
188
|
message: createMessage({
|
|
@@ -229,6 +232,7 @@ function appendToolBatchTurn(
|
|
|
229
232
|
}
|
|
230
233
|
session.append('step/start', { turn, step: 1 })
|
|
231
234
|
const assistant = session.append('assistant/message', {
|
|
235
|
+
stream: [],
|
|
232
236
|
turn,
|
|
233
237
|
step: 1,
|
|
234
238
|
message: createMessage({
|
|
@@ -267,12 +271,18 @@ function appendToolBatchTurn(
|
|
|
267
271
|
return { assistantSeq: assistant.seq, resultSeqs }
|
|
268
272
|
}
|
|
269
273
|
|
|
274
|
+
// 0.1.5 removed the settingsNamespace() wrapper; namespaces are branded
|
|
275
|
+
// strings validated at runtime by SettingsProvider.parse.
|
|
276
|
+
const nsBrand = (value: string): SettingsNamespace => value as unknown as SettingsNamespace
|
|
277
|
+
|
|
270
278
|
function stubAgent(ctx: Context, session: Session): Agent {
|
|
271
279
|
return {
|
|
272
280
|
id: session.id,
|
|
273
281
|
options: {},
|
|
274
282
|
session,
|
|
275
|
-
|
|
283
|
+
// 0.1.5 moved the durable Inbox behind the loop driver; the pruner only
|
|
284
|
+
// reads id/session, so an inert face is enough for the stub.
|
|
285
|
+
inbox: undefined as unknown as Agent['inbox'],
|
|
276
286
|
status: 'idle',
|
|
277
287
|
ctx,
|
|
278
288
|
send: () => {},
|
|
@@ -350,16 +360,16 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
350
360
|
const result = pruner.pruneSession(session, { stage: 'pressure' })
|
|
351
361
|
|
|
352
362
|
expect(result.pruned).toHaveLength(1)
|
|
353
|
-
const manifest = session.
|
|
354
|
-
const replacement = session.
|
|
363
|
+
const manifest = sessionEvents(session).at(-2)
|
|
364
|
+
const replacement = sessionEvents(session).at(-1)
|
|
355
365
|
expect(manifest?.type).toBe('compaction/prune')
|
|
356
|
-
expect(session.
|
|
366
|
+
expect(sessionEvents(session).some(event => event.type === ('compaction/group-trim' as string))).toBe(false)
|
|
357
367
|
expect(replacement?.type).toBe('tool/result')
|
|
358
368
|
if (replacement?.type !== 'tool/result') throw new Error('Native prune did not append a tool result replacement')
|
|
359
369
|
expect(replacement.surfaceOp).toEqual({
|
|
360
370
|
op: 'replace',
|
|
361
|
-
|
|
362
|
-
|
|
371
|
+
startSeq: source.resultSeq,
|
|
372
|
+
endSeq: source.resultSeq,
|
|
363
373
|
})
|
|
364
374
|
expect(replacement.sourceEventSeqs).toContain(source.resultSeq)
|
|
365
375
|
expect(rewrites(audit.records())).toContainEqual(expect.objectContaining({
|
|
@@ -521,7 +531,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
521
531
|
await ctx.plugin(TestSettings).await()
|
|
522
532
|
await ctx.plugin(SelectorHost).await()
|
|
523
533
|
const audit = captureAudit(ctx)
|
|
524
|
-
const namespace =
|
|
534
|
+
const namespace = nsBrand(CONTEXT_COMPRESSION_SETTINGS_NAMESPACE)
|
|
525
535
|
|
|
526
536
|
// A high threshold delays the micro-compact last-chance gate past the old
|
|
527
537
|
// fixed 0.7 ratio: pressure between 0.7*C and D must NOT age history.
|
|
@@ -557,7 +567,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
557
567
|
await ctx.plugin(TestSettings).await()
|
|
558
568
|
await ctx.plugin(SelectorHost).await()
|
|
559
569
|
const audit = captureAudit(ctx)
|
|
560
|
-
const namespace =
|
|
570
|
+
const namespace = nsBrand(CONTEXT_COMPRESSION_SETTINGS_NAMESPACE)
|
|
561
571
|
|
|
562
572
|
// A low threshold pulls the micro-compact deadline below the old fixed
|
|
563
573
|
// 0.7 ratio: pressure between D and 0.7*C must age history now.
|
|
@@ -591,7 +601,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
591
601
|
await ctx.plugin(TestSettings).await()
|
|
592
602
|
await ctx.plugin(SelectorHost).await()
|
|
593
603
|
const audit = captureAudit(ctx)
|
|
594
|
-
const namespace =
|
|
604
|
+
const namespace = nsBrand(CONTEXT_COMPRESSION_SETTINGS_NAMESPACE)
|
|
595
605
|
// The preset overlay captured 70% into this generation's deployment
|
|
596
606
|
// config; the user then moved the global setting to 90 before the first
|
|
597
607
|
// prune. Auto Compact and micro compact must both stay on 70%.
|
|
@@ -627,7 +637,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
627
637
|
await ctx.plugin(TestSettings).await()
|
|
628
638
|
await ctx.plugin(SelectorHost).await()
|
|
629
639
|
const audit = captureAudit(ctx)
|
|
630
|
-
const namespace =
|
|
640
|
+
const namespace = nsBrand(CONTEXT_COMPRESSION_SETTINGS_NAMESPACE)
|
|
631
641
|
// A hand-edited store can surface a document the schema rejects (unknown
|
|
632
642
|
// top-level key). The runtime must not fall back to a lossy-capable
|
|
633
643
|
// profile: the session freezes effectively off and keeps every original
|
|
@@ -677,7 +687,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
677
687
|
await ctx.plugin(TestSettings).await()
|
|
678
688
|
await ctx.plugin(SelectorHost).await()
|
|
679
689
|
const audit = captureAudit(ctx)
|
|
680
|
-
const namespace =
|
|
690
|
+
const namespace = nsBrand(CONTEXT_COMPRESSION_SETTINGS_NAMESPACE)
|
|
681
691
|
// Schemastery `.default(...)` would silently replace these present-but-null
|
|
682
692
|
// sections with the balanced/default-v3 policy; the runtime must reject the
|
|
683
693
|
// document and freeze the session losslessly instead.
|
|
@@ -714,7 +724,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
714
724
|
await ctx.plugin(TestSettings).await()
|
|
715
725
|
await ctx.plugin(SelectorHost).await()
|
|
716
726
|
const audit = captureAudit(ctx)
|
|
717
|
-
const namespace =
|
|
727
|
+
const namespace = nsBrand(CONTEXT_COMPRESSION_SETTINGS_NAMESPACE)
|
|
718
728
|
const malformed = {
|
|
719
729
|
profile: 'balanced' as const,
|
|
720
730
|
custom: structuredClone(DEFAULT_CUSTOM_COMPRESSION_POLICY),
|
|
@@ -814,7 +824,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
814
824
|
await ctx.plugin(TestSettings).await()
|
|
815
825
|
await ctx.plugin(SelectorHost).await()
|
|
816
826
|
const audit = captureAudit(ctx)
|
|
817
|
-
const namespace =
|
|
827
|
+
const namespace = nsBrand(CONTEXT_COMPRESSION_SETTINGS_NAMESPACE)
|
|
818
828
|
await ctx.settings.update(namespace, { autoCompact: { thresholdPercent: 73 } })
|
|
819
829
|
await ctx.plugin(ToolResultPruner, { profile: 'balanced' }).await()
|
|
820
830
|
const session = Session.create(SessionId('public-autocompact-coordination'))
|
|
@@ -911,7 +921,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
911
921
|
await ctx.plugin(TestSettings).await()
|
|
912
922
|
await ctx.plugin(SelectorHost).await()
|
|
913
923
|
const audit = captureAudit(ctx)
|
|
914
|
-
const namespace =
|
|
924
|
+
const namespace = nsBrand(CONTEXT_COMPRESSION_SETTINGS_NAMESPACE)
|
|
915
925
|
await ctx.plugin(ToolResultPruner, { profile: 'balanced' }).await()
|
|
916
926
|
|
|
917
927
|
const first = Session.create(SessionId('public-autocompact-freeze-first'))
|
|
@@ -975,6 +985,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
975
985
|
}), { surfaceOp: 'append' })
|
|
976
986
|
session.append('step/start', { turn: 1, step: 1 })
|
|
977
987
|
session.append('assistant/message', {
|
|
988
|
+
stream: [],
|
|
978
989
|
turn: 1,
|
|
979
990
|
step: 1,
|
|
980
991
|
message: createMessage({
|
|
@@ -1002,7 +1013,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1002
1013
|
|
|
1003
1014
|
const before = measureForCompaction(ctx, session)
|
|
1004
1015
|
const toolResultTokens = before.measuredNodes
|
|
1005
|
-
.filter(node => session
|
|
1016
|
+
.filter(node => sessionEvents(session)[node.seq]?.type === 'tool/result')
|
|
1006
1017
|
.reduce((sum, node) => sum + (node.count.kind === 'exact-tokenizer' ? node.count.tokens : 0), 0)
|
|
1007
1018
|
// Deadline D = 7000 is reached by the complete request, not by the tool
|
|
1008
1019
|
// results, which stay far below every trigger in force.
|
|
@@ -1175,6 +1186,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1175
1186
|
session.append('turn/start', { turn })
|
|
1176
1187
|
session.append('step/start', { turn, step: 1 })
|
|
1177
1188
|
session.append('assistant/message', {
|
|
1189
|
+
stream: [],
|
|
1178
1190
|
turn,
|
|
1179
1191
|
step: 1,
|
|
1180
1192
|
message: createMessage({
|
|
@@ -1224,6 +1236,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1224
1236
|
})
|
|
1225
1237
|
onlyUnsafe.append('step/start', { turn: 1, step: 1 })
|
|
1226
1238
|
onlyUnsafe.append('assistant/message', {
|
|
1239
|
+
stream: [],
|
|
1227
1240
|
turn: 1,
|
|
1228
1241
|
step: 1,
|
|
1229
1242
|
message: createMessage({
|
|
@@ -1382,9 +1395,10 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1382
1395
|
it('audits recovery-tool-unavailable when a committed batch cannot land without the recovery tool', async () => {
|
|
1383
1396
|
const ctx = new Context()
|
|
1384
1397
|
activeContexts.push(ctx)
|
|
1385
|
-
await ctx.plugin(SessionStore)
|
|
1386
|
-
await ctx.plugin(SystemPrompt)
|
|
1387
|
-
await ctx.plugin(
|
|
1398
|
+
await ctx.plugin(SessionStore).await()
|
|
1399
|
+
await ctx.plugin(SystemPrompt).await()
|
|
1400
|
+
await ctx.plugin(SessionProjectionRegistry).await()
|
|
1401
|
+
await ctx.plugin(TokenMeter).await()
|
|
1388
1402
|
const audit = captureAudit(ctx)
|
|
1389
1403
|
await ctx.plugin(ToolResultPruner, {
|
|
1390
1404
|
profile: 'balanced',
|
|
@@ -1435,7 +1449,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1435
1449
|
}
|
|
1436
1450
|
custom.prefixPolicy = 'pressure-break'
|
|
1437
1451
|
custom.tailTrim = { enabled: false, trigger: 700_000 }
|
|
1438
|
-
await ctx.settings.update(
|
|
1452
|
+
await ctx.settings.update(nsBrand(CONTEXT_COMPRESSION_SETTINGS_NAMESPACE), {
|
|
1439
1453
|
profile: 'custom',
|
|
1440
1454
|
custom,
|
|
1441
1455
|
})
|
|
@@ -1520,7 +1534,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1520
1534
|
activeContexts.push(ctx)
|
|
1521
1535
|
await mountAgentLoopTestDependencies(ctx)
|
|
1522
1536
|
await ctx.plugin(AgentLoop, { agents: [] })
|
|
1523
|
-
await ctx.plugin(TokenMeter)
|
|
1537
|
+
await ctx.plugin(TokenMeter).await()
|
|
1524
1538
|
const audit = captureAudit(ctx)
|
|
1525
1539
|
ctx.llm.registerAdapter(
|
|
1526
1540
|
['deepseek'],
|
|
@@ -1538,7 +1552,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1538
1552
|
historyMinReclaimTokens: 1,
|
|
1539
1553
|
}).await()
|
|
1540
1554
|
|
|
1541
|
-
const agent = ctx.agentLoop.create(SessionId('public-history-capacity-request-boundary'), {
|
|
1555
|
+
const agent = await ctx.agentLoop.create(SessionId('public-history-capacity-request-boundary'), {
|
|
1542
1556
|
provider: 'deepseek',
|
|
1543
1557
|
model: MODEL,
|
|
1544
1558
|
})
|
|
@@ -1604,10 +1618,11 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1604
1618
|
activeContexts.push(ctx)
|
|
1605
1619
|
await ctx.plugin(TestSettings).await()
|
|
1606
1620
|
await ctx.plugin(SelectorHost).await()
|
|
1607
|
-
await ctx.plugin(SessionStore)
|
|
1608
|
-
await ctx.plugin(SystemPrompt)
|
|
1609
|
-
await ctx.plugin(ToolRuntime)
|
|
1610
|
-
await ctx.plugin(
|
|
1621
|
+
await ctx.plugin(SessionStore).await()
|
|
1622
|
+
await ctx.plugin(SystemPrompt).await()
|
|
1623
|
+
await ctx.plugin(ToolRuntime).await()
|
|
1624
|
+
await ctx.plugin(SessionProjectionRegistry).await()
|
|
1625
|
+
await ctx.plugin(TokenMeter).await()
|
|
1611
1626
|
const audit = captureAudit(ctx)
|
|
1612
1627
|
|
|
1613
1628
|
const firstCustom = structuredClone(DEFAULT_CUSTOM_COMPRESSION_POLICY) as CustomCompressionPolicy
|
|
@@ -1615,7 +1630,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1615
1630
|
firstCustom.aggregate.enabled = false
|
|
1616
1631
|
firstCustom.history.enabled = false
|
|
1617
1632
|
if (firstCustom.version === 3) firstCustom.tailTrim.enabled = false
|
|
1618
|
-
const namespace =
|
|
1633
|
+
const namespace = nsBrand(CONTEXT_COMPRESSION_SETTINGS_NAMESPACE)
|
|
1619
1634
|
await ctx.settings.update(namespace, { profile: 'custom', custom: firstCustom })
|
|
1620
1635
|
await ctx.plugin(ToolResultPruner, {
|
|
1621
1636
|
profile: 'off',
|
|
@@ -1665,10 +1680,11 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1665
1680
|
activeContexts.push(ctx)
|
|
1666
1681
|
await ctx.plugin(TestSettings).await()
|
|
1667
1682
|
await ctx.plugin(SelectorHost).await()
|
|
1668
|
-
await ctx.plugin(SessionStore)
|
|
1669
|
-
await ctx.plugin(SystemPrompt)
|
|
1670
|
-
await ctx.plugin(ToolRuntime)
|
|
1671
|
-
await ctx.plugin(
|
|
1683
|
+
await ctx.plugin(SessionStore).await()
|
|
1684
|
+
await ctx.plugin(SystemPrompt).await()
|
|
1685
|
+
await ctx.plugin(ToolRuntime).await()
|
|
1686
|
+
await ctx.plugin(SessionProjectionRegistry).await()
|
|
1687
|
+
await ctx.plugin(TokenMeter).await()
|
|
1672
1688
|
const audit = captureAudit(ctx)
|
|
1673
1689
|
|
|
1674
1690
|
const policy = structuredClone(DEFAULT_CUSTOM_COMPRESSION_POLICY) as CustomCompressionPolicy
|
|
@@ -1681,7 +1697,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1681
1697
|
policy.history.keepRecentTokens = 0
|
|
1682
1698
|
policy.history.minReclaim = 1
|
|
1683
1699
|
policy.tailTrim = { enabled: true, trigger: 8 }
|
|
1684
|
-
await ctx.settings.update(
|
|
1700
|
+
await ctx.settings.update(nsBrand(CONTEXT_COMPRESSION_SETTINGS_NAMESPACE), {
|
|
1685
1701
|
profile: 'custom',
|
|
1686
1702
|
custom: policy,
|
|
1687
1703
|
})
|
|
@@ -1697,7 +1713,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1697
1713
|
|
|
1698
1714
|
pruner.pruneSession(session, { stage: 'pressure' })
|
|
1699
1715
|
|
|
1700
|
-
const manifest = session.
|
|
1716
|
+
const manifest = sessionEvents(session).findLast(event => event.type === 'compaction/prune')
|
|
1701
1717
|
expect(manifest?.type).toBe('compaction/prune')
|
|
1702
1718
|
if (manifest?.type !== 'compaction/prune') throw new Error('TailTrim did not publish a standard prune')
|
|
1703
1719
|
const publication = validatePublishedTailTrim(session, manifest.seq)
|
|
@@ -1732,7 +1748,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1732
1748
|
expect(recoveredText).toContain('kind: tailtrim-group')
|
|
1733
1749
|
expect(recoveredText).toContain('old candidate result')
|
|
1734
1750
|
|
|
1735
|
-
const persistedEvents = JSON.parse(JSON.stringify(session
|
|
1751
|
+
const persistedEvents = JSON.parse(JSON.stringify(sessionEvents(session)))
|
|
1736
1752
|
const replay = Session.create(session.id, persistedEvents)
|
|
1737
1753
|
const replayPublication = validatePublishedTailTrim(replay, manifest.seq)
|
|
1738
1754
|
expect(replayPublication?.ref).toBe(publication?.ref)
|
|
@@ -1760,10 +1776,11 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1760
1776
|
activeContexts.push(ctx)
|
|
1761
1777
|
await ctx.plugin(TestSettings).await()
|
|
1762
1778
|
await ctx.plugin(SelectorHost).await()
|
|
1763
|
-
await ctx.plugin(SessionStore)
|
|
1764
|
-
await ctx.plugin(SystemPrompt)
|
|
1765
|
-
await ctx.plugin(ToolRuntime)
|
|
1766
|
-
await ctx.plugin(
|
|
1779
|
+
await ctx.plugin(SessionStore).await()
|
|
1780
|
+
await ctx.plugin(SystemPrompt).await()
|
|
1781
|
+
await ctx.plugin(ToolRuntime).await()
|
|
1782
|
+
await ctx.plugin(SessionProjectionRegistry).await()
|
|
1783
|
+
await ctx.plugin(TokenMeter).await()
|
|
1767
1784
|
const audit = captureAudit(ctx)
|
|
1768
1785
|
|
|
1769
1786
|
const policy = structuredClone(DEFAULT_CUSTOM_COMPRESSION_POLICY) as CustomCompressionPolicy
|
|
@@ -1776,7 +1793,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1776
1793
|
policy.history.keepRecentTokens = 0
|
|
1777
1794
|
policy.history.minReclaim = options.minReclaim
|
|
1778
1795
|
policy.tailTrim = { enabled: true, trigger: options.trigger }
|
|
1779
|
-
await ctx.settings.update(
|
|
1796
|
+
await ctx.settings.update(nsBrand(CONTEXT_COMPRESSION_SETTINGS_NAMESPACE), {
|
|
1780
1797
|
profile: 'custom',
|
|
1781
1798
|
custom: policy,
|
|
1782
1799
|
})
|
|
@@ -1842,12 +1859,13 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1842
1859
|
activeContexts.push(ctx)
|
|
1843
1860
|
await ctx.plugin(TestSettings).await()
|
|
1844
1861
|
await ctx.plugin(SelectorHost).await()
|
|
1845
|
-
await ctx.plugin(SessionStore)
|
|
1846
|
-
await ctx.plugin(InvariantRegistry)
|
|
1847
|
-
await ctx.plugin(RuntimeInvariant)
|
|
1848
|
-
await ctx.plugin(SystemPrompt)
|
|
1849
|
-
await ctx.plugin(ToolRuntime)
|
|
1850
|
-
await ctx.plugin(
|
|
1862
|
+
await ctx.plugin(SessionStore).await()
|
|
1863
|
+
await ctx.plugin(InvariantRegistry).await()
|
|
1864
|
+
await ctx.plugin(RuntimeInvariant).await()
|
|
1865
|
+
await ctx.plugin(SystemPrompt).await()
|
|
1866
|
+
await ctx.plugin(ToolRuntime).await()
|
|
1867
|
+
await ctx.plugin(SessionProjectionRegistry).await()
|
|
1868
|
+
await ctx.plugin(TokenMeter).await()
|
|
1851
1869
|
const audit = captureAudit(ctx)
|
|
1852
1870
|
|
|
1853
1871
|
const policy = structuredClone(DEFAULT_CUSTOM_COMPRESSION_POLICY) as CustomCompressionPolicy
|
|
@@ -1859,7 +1877,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1859
1877
|
policy.history.keepRecentTokens = 0
|
|
1860
1878
|
policy.history.minReclaim = 1
|
|
1861
1879
|
policy.tailTrim = { enabled: true, trigger: 8 }
|
|
1862
|
-
await ctx.settings.update(
|
|
1880
|
+
await ctx.settings.update(nsBrand(CONTEXT_COMPRESSION_SETTINGS_NAMESPACE), {
|
|
1863
1881
|
profile: 'custom',
|
|
1864
1882
|
custom: policy,
|
|
1865
1883
|
})
|
|
@@ -1884,27 +1902,27 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1884
1902
|
|
|
1885
1903
|
expect(() => ctx.toolResultPruner.pruneSession(session, { stage: 'pressure' })).not.toThrow()
|
|
1886
1904
|
append.mockRestore()
|
|
1887
|
-
expect(session.
|
|
1905
|
+
expect(sessionEvents(session).at(-1)?.type).toBe('compaction/prune')
|
|
1888
1906
|
expect(session.surface.nodes).toEqual(beforeSurface)
|
|
1889
1907
|
expect(rewrites(audit.records())).toHaveLength(0)
|
|
1890
1908
|
expect(audit.records()).toContainEqual(expect.objectContaining({
|
|
1891
1909
|
kind: 'failure',
|
|
1892
1910
|
operation: 'publication',
|
|
1893
1911
|
component: 'tail-trim',
|
|
1894
|
-
manifestSeq: session.
|
|
1912
|
+
manifestSeq: sessionEvents(session).length - 1,
|
|
1895
1913
|
}))
|
|
1896
1914
|
|
|
1897
1915
|
expect(() => session.append('turn/end', {
|
|
1898
1916
|
turn: 3,
|
|
1899
1917
|
reason: { kind: 'completed' },
|
|
1900
1918
|
})).not.toThrow()
|
|
1901
|
-
const persisted = JSON.parse(JSON.stringify(session
|
|
1919
|
+
const persisted = JSON.parse(JSON.stringify(sessionEvents(session)))
|
|
1902
1920
|
|
|
1903
1921
|
const resumedCtx = new Context()
|
|
1904
1922
|
activeContexts.push(resumedCtx)
|
|
1905
|
-
await resumedCtx.plugin(SessionStore)
|
|
1906
|
-
await resumedCtx.plugin(InvariantRegistry)
|
|
1907
|
-
await resumedCtx.plugin(RuntimeInvariant)
|
|
1923
|
+
await resumedCtx.plugin(SessionStore).await()
|
|
1924
|
+
await resumedCtx.plugin(InvariantRegistry).await()
|
|
1925
|
+
await resumedCtx.plugin(RuntimeInvariant).await()
|
|
1908
1926
|
const resumed = resumedCtx.sessions.create(session.id, { seed: persisted })
|
|
1909
1927
|
expect(resumed.surface.nodes).toEqual(beforeSurface)
|
|
1910
1928
|
expect(() => resumed.append('turn/start', { turn: 4 })).not.toThrow()
|
|
@@ -1913,23 +1931,23 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1913
1931
|
it('still rejects a malformed adjacent replacement instead of treating it as an orphan', async () => {
|
|
1914
1932
|
const ctx = new Context()
|
|
1915
1933
|
activeContexts.push(ctx)
|
|
1916
|
-
await ctx.plugin(SessionStore)
|
|
1917
|
-
await ctx.plugin(InvariantRegistry)
|
|
1918
|
-
await ctx.plugin(RuntimeInvariant)
|
|
1934
|
+
await ctx.plugin(SessionStore).await()
|
|
1935
|
+
await ctx.plugin(InvariantRegistry).await()
|
|
1936
|
+
await ctx.plugin(RuntimeInvariant).await()
|
|
1919
1937
|
const session = ctx.sessions.create(SessionId('public-malformed-companion'))
|
|
1920
1938
|
const source = appendToolTurn(session, 1, 'source remains intact', false)
|
|
1921
1939
|
session.append('compaction/prune', {
|
|
1922
|
-
shadowedRange: { start: source.resultSeq, end: source.resultSeq },
|
|
1923
|
-
shadowedSeqs: [source.resultSeq],
|
|
1940
|
+
shadowedRange: { start: SessionSeq(source.resultSeq), end: SessionSeq(source.resultSeq) },
|
|
1941
|
+
shadowedSeqs: [SessionSeq(source.resultSeq)],
|
|
1924
1942
|
shadowedTokenCount: 1,
|
|
1925
1943
|
})
|
|
1926
|
-
const result = session
|
|
1944
|
+
const result = sessionEvents(session)[source.resultSeq]
|
|
1927
1945
|
if (result?.type !== 'tool/result') throw new Error('missing source result')
|
|
1928
1946
|
const before = session.seq
|
|
1929
1947
|
|
|
1930
1948
|
expect(() => session.append('tool/result', result.data, {
|
|
1931
|
-
surfaceOp: { op: 'replace',
|
|
1932
|
-
sourceEventSeqs: [source.resultSeq],
|
|
1949
|
+
surfaceOp: { op: 'replace', startSeq: SessionSeq(source.assistantSeq), endSeq: SessionSeq(source.assistantSeq) },
|
|
1950
|
+
sourceEventSeqs: [SessionSeq(source.resultSeq)],
|
|
1933
1951
|
})).toThrow(/sourceEventSeqs|does not replace compaction\/prune range/)
|
|
1934
1952
|
expect(session.seq).toBe(before)
|
|
1935
1953
|
})
|
|
@@ -1942,7 +1960,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1942
1960
|
const summary = session.append('compaction/summary', {
|
|
1943
1961
|
compactionId: CompactionId('public-native-auto'),
|
|
1944
1962
|
summary: [{ type: 'text', text: 'summary' }],
|
|
1945
|
-
shadowedRange: { start: 0, end: 0 },
|
|
1963
|
+
shadowedRange: { start: SessionSeq(0), end: SessionSeq(0) },
|
|
1946
1964
|
shadowedSeqs: [],
|
|
1947
1965
|
shadowedTokenCount: 321,
|
|
1948
1966
|
provider: 'deepseek',
|
|
@@ -1968,7 +1986,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1968
1986
|
activeContexts.push(ctx)
|
|
1969
1987
|
await mountAgentLoopTestDependencies(ctx)
|
|
1970
1988
|
await ctx.plugin(AgentLoop, { agents: [] })
|
|
1971
|
-
await ctx.plugin(TokenMeter)
|
|
1989
|
+
await ctx.plugin(TokenMeter).await()
|
|
1972
1990
|
const audit = captureAudit(ctx)
|
|
1973
1991
|
ctx.llm.registerAdapter(
|
|
1974
1992
|
['deepseek'],
|
|
@@ -1986,7 +2004,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
1986
2004
|
maxTokens: 100,
|
|
1987
2005
|
compactionRetries: 0,
|
|
1988
2006
|
})
|
|
1989
|
-
const agent = ctx.agentLoop.create(SessionId('public-native-auto-real'), {
|
|
2007
|
+
const agent = await ctx.agentLoop.create(SessionId('public-native-auto-real'), {
|
|
1990
2008
|
provider: 'deepseek',
|
|
1991
2009
|
model: MODEL,
|
|
1992
2010
|
})
|
|
@@ -2003,7 +2021,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
2003
2021
|
await agent.whenIdle()
|
|
2004
2022
|
|
|
2005
2023
|
expect(rewrites(audit.records())).toHaveLength(0)
|
|
2006
|
-
const summary = agent.session.
|
|
2024
|
+
const summary = sessionEvents(agent.session).findLast(event => event.type === 'compaction/summary')
|
|
2007
2025
|
expect(summary?.type).toBe('compaction/summary')
|
|
2008
2026
|
expect(audit.records()).toContainEqual(expect.objectContaining({
|
|
2009
2027
|
kind: 'native-auto-compact',
|
|
@@ -2021,11 +2039,12 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
2021
2039
|
activeContexts.push(ctx)
|
|
2022
2040
|
await ctx.plugin(TestSettings).await()
|
|
2023
2041
|
await ctx.plugin(SelectorHost).await()
|
|
2024
|
-
await ctx.plugin(LlmRuntime)
|
|
2025
|
-
await ctx.plugin(SessionStore)
|
|
2026
|
-
await ctx.plugin(SystemPrompt)
|
|
2027
|
-
await ctx.plugin(ToolRuntime)
|
|
2028
|
-
await ctx.plugin(
|
|
2042
|
+
await ctx.plugin(LlmRuntime).await()
|
|
2043
|
+
await ctx.plugin(SessionStore).await()
|
|
2044
|
+
await ctx.plugin(SystemPrompt).await()
|
|
2045
|
+
await ctx.plugin(ToolRuntime).await()
|
|
2046
|
+
await ctx.plugin(SessionProjectionRegistry).await()
|
|
2047
|
+
await ctx.plugin(TokenMeter).await()
|
|
2029
2048
|
const audit = captureAudit(ctx)
|
|
2030
2049
|
|
|
2031
2050
|
const policy = structuredClone(DEFAULT_CUSTOM_COMPRESSION_POLICY) as CustomCompressionPolicy
|
|
@@ -2045,7 +2064,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
2045
2064
|
}
|
|
2046
2065
|
policy.prefixPolicy = 'pressure-break'
|
|
2047
2066
|
policy.tailTrim = { enabled: true, trigger: 8 }
|
|
2048
|
-
await ctx.settings.update(
|
|
2067
|
+
await ctx.settings.update(nsBrand(CONTEXT_COMPRESSION_SETTINGS_NAMESPACE), {
|
|
2049
2068
|
profile: 'custom',
|
|
2050
2069
|
custom: policy,
|
|
2051
2070
|
})
|
|
@@ -2100,7 +2119,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
2100
2119
|
() => Promise.resolve({ kind: 'enter' as const, messages: [] }),
|
|
2101
2120
|
)
|
|
2102
2121
|
expect(decision).toEqual({ kind: 'enter', messages: [] })
|
|
2103
|
-
const summary = session.
|
|
2122
|
+
const summary = sessionEvents(session).findLast(event => event.type === 'compaction/summary')
|
|
2104
2123
|
expect(summary?.type).toBe('compaction/summary')
|
|
2105
2124
|
|
|
2106
2125
|
const records = audit.records()
|
|
@@ -2123,7 +2142,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
2123
2142
|
expect(firstIndex('history')).toBeLessThan(firstIndex('tail-trim'))
|
|
2124
2143
|
expect(records.findIndex(record => record.kind === 'native-auto-compact'))
|
|
2125
2144
|
.toBeGreaterThan(firstIndex('tail-trim'))
|
|
2126
|
-
expect(session.
|
|
2145
|
+
expect(sessionEvents(session).some(event => event.type === ('compaction/group-trim' as string))).toBe(false)
|
|
2127
2146
|
})
|
|
2128
2147
|
|
|
2129
2148
|
it('compresses text tool results exactly in a vision session that also carries a user image', async () => {
|
|
@@ -2155,6 +2174,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
2155
2174
|
}), { surfaceOp: 'append' })
|
|
2156
2175
|
session.append('step/start', { turn: 1, step: 1 })
|
|
2157
2176
|
session.append('assistant/message', {
|
|
2177
|
+
stream: [],
|
|
2158
2178
|
turn: 1,
|
|
2159
2179
|
step: 1,
|
|
2160
2180
|
message: createMessage({
|
|
@@ -2222,6 +2242,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
2222
2242
|
})
|
|
2223
2243
|
session.append('step/start', { turn: 1, step: 1 })
|
|
2224
2244
|
session.append('assistant/message', {
|
|
2245
|
+
stream: [],
|
|
2225
2246
|
turn: 1,
|
|
2226
2247
|
step: 1,
|
|
2227
2248
|
message: createMessage({
|
|
@@ -2253,7 +2274,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
2253
2274
|
expect(fresh.pruned).toHaveLength(0)
|
|
2254
2275
|
expect(pressure.pruned).toHaveLength(0)
|
|
2255
2276
|
// The original image-bearing result stays on the surface untouched.
|
|
2256
|
-
const original = session
|
|
2277
|
+
const original = sessionEvents(session)[imageResult.seq]
|
|
2257
2278
|
expect(original?.type).toBe('tool/result')
|
|
2258
2279
|
expect(audit.records()).toContainEqual(expect.objectContaining({
|
|
2259
2280
|
kind: 'component-evaluation',
|
|
@@ -2460,7 +2481,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
2460
2481
|
await ctx.plugin(TestSettings).await()
|
|
2461
2482
|
await ctx.plugin(SelectorHost).await()
|
|
2462
2483
|
const audit = captureAudit(ctx)
|
|
2463
|
-
await ctx.settings.update(
|
|
2484
|
+
await ctx.settings.update(nsBrand(CONTEXT_COMPRESSION_SETTINGS_NAMESPACE), {
|
|
2464
2485
|
profile: 'custom',
|
|
2465
2486
|
custom: {
|
|
2466
2487
|
version: 3,
|
|
@@ -2481,6 +2502,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
2481
2502
|
})
|
|
2482
2503
|
session.append('step/start', { turn: 1, step: 1 })
|
|
2483
2504
|
session.append('assistant/message', {
|
|
2505
|
+
stream: [],
|
|
2484
2506
|
turn: 1,
|
|
2485
2507
|
step: 1,
|
|
2486
2508
|
message: createMessage({
|
|
@@ -2511,7 +2533,7 @@ describe('standalone runtime on published Harness APIs', () => {
|
|
|
2511
2533
|
expect(result.pruned).toHaveLength(0)
|
|
2512
2534
|
expect(rewrites(audit.records()).some(record =>
|
|
2513
2535
|
record.sessionId === String(session.id) && record.component === 'tail-trim')).toBe(false)
|
|
2514
|
-
expect(session.
|
|
2536
|
+
expect(sessionEvents(session).some(event =>
|
|
2515
2537
|
event.type === 'tool/result'
|
|
2516
2538
|
&& event.data.message.content.some(block => block.type === 'tool-result'
|
|
2517
2539
|
&& block.content.some(inner => inner.type === 'image')))).toBe(true)
|
|
@@ -8,7 +8,7 @@ const EVENTS = Object.freeze([
|
|
|
8
8
|
|
|
9
9
|
describe('sessionEvents', () => {
|
|
10
10
|
it('retains the rc.2 events accessor fallback', () => {
|
|
11
|
-
const session = { events: EVENTS } as Session
|
|
11
|
+
const session = { events: EVENTS } as unknown as Session
|
|
12
12
|
expect(sessionEvents(session)).toBe(EVENTS)
|
|
13
13
|
})
|
|
14
14
|
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Settings-seat contract pin for dsh-context-compression-improved's client.
|
|
3
|
+
*
|
|
4
|
+
* This spec locks WHERE the compression settings panel mounts: exactly ONE
|
|
5
|
+
* seat — the standalone `settings.section` entry (设置 → 上下文压缩) — and
|
|
6
|
+
* explicitly NOT the Plugins-section surfaces. History this pins against: the
|
|
7
|
+
* 0.1.5 line first lost every entry (a lazy `ctx.get` of locale/settingsScope
|
|
8
|
+
* raced the settings client and apply bailed), then showed the panel twice
|
|
9
|
+
* (item card + tab on top of the standalone section). Both were fixed by the
|
|
10
|
+
* declarative-inject + single-section shape below. When a future DSH line
|
|
11
|
+
* moves the seat again, migrate src/client/index.ts AND this file together.
|
|
12
|
+
*/
|
|
13
|
+
import { describe, expect, it, vi } from 'vitest'
|
|
14
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis'
|
|
15
|
+
import { apply, inject } from '../src/client/index.ts'
|
|
16
|
+
import { ContextCompressionSettingsSection } from '../src/client/CompressionProfileSelector.tsx'
|
|
17
|
+
|
|
18
|
+
vi.mock('@deepseek-ai/dsh-client-ui-primitives', () => ({
|
|
19
|
+
IconChevronDownOutline14: () => null,
|
|
20
|
+
Menu: () => null,
|
|
21
|
+
}))
|
|
22
|
+
|
|
23
|
+
interface CapturedRegistration {
|
|
24
|
+
readonly slot: string
|
|
25
|
+
readonly options: Record<string, unknown>
|
|
26
|
+
readonly component: unknown
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Drive apply against a stub host and capture every slot registration. */
|
|
30
|
+
function collectRegistrations(): { declared: string[]; registrations: CapturedRegistration[] } {
|
|
31
|
+
const declared: string[] = []
|
|
32
|
+
const registrations: CapturedRegistration[] = []
|
|
33
|
+
const ctx = {
|
|
34
|
+
locale: { register: vi.fn(() => () => {}), bind: () => (key: string) => key },
|
|
35
|
+
settingsScope: { bind: vi.fn(() => ({}) as never) },
|
|
36
|
+
slots: {
|
|
37
|
+
inject: (slot: string, factory: () => (() => void) | Generator<() => void>) => {
|
|
38
|
+
declared.push(slot)
|
|
39
|
+
const result = factory()
|
|
40
|
+
const steps: Iterable<() => void> = typeof (result as IteratorObject)?.[Symbol.iterator] === 'function'
|
|
41
|
+
? (result as Generator<() => void>)
|
|
42
|
+
: [result as () => void]
|
|
43
|
+
for (const step of steps) { void step }
|
|
44
|
+
return () => {}
|
|
45
|
+
},
|
|
46
|
+
register: (options: Record<string, unknown>, component: unknown) => {
|
|
47
|
+
registrations.push({ slot: String(options['name']), options, component })
|
|
48
|
+
return () => {}
|
|
49
|
+
},
|
|
50
|
+
},
|
|
51
|
+
}
|
|
52
|
+
apply(ctx as unknown as ClientContext)
|
|
53
|
+
return { declared, registrations }
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
describe('settings-seat contract (standalone settings.section only)', () => {
|
|
57
|
+
it('declares the services apply consumes (cordis waits; no lazy-get race)', () => {
|
|
58
|
+
expect(inject).toEqual(['slots', 'locale', 'settingsScope'])
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
it('injects exactly the settings.section seat and nothing else', () => {
|
|
62
|
+
const { declared, registrations } = collectRegistrations()
|
|
63
|
+
expect(declared).toEqual(['settings.section'])
|
|
64
|
+
expect(registrations).toHaveLength(1)
|
|
65
|
+
expect(registrations[0]!.slot).toBe('settings.section')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('pins the section identity (id/order/label/locale) and the panel component', () => {
|
|
69
|
+
const { registrations } = collectRegistrations()
|
|
70
|
+
const { options, component } = registrations[0]!
|
|
71
|
+
expect(options['id']).toBe('context-compression')
|
|
72
|
+
expect(options['order']).toBe(17)
|
|
73
|
+
expect(options['locale']).toBe('context-compression')
|
|
74
|
+
expect((options['label'] as () => string)()).toBe('nav')
|
|
75
|
+
expect(typeof options['inject']).toBe('function')
|
|
76
|
+
const face = (options['inject'] as () => Record<string, unknown>)()
|
|
77
|
+
expect(Object.keys(face).sort()).toEqual(['hooks', 'saveAutoCompact', 'saveCodeSkeleton', 'saveCustom', 'savePresetOptions', 'select', 'resetCustom'].sort())
|
|
78
|
+
expect(component).toBe(ContextCompressionSettingsSection)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
it('never adds a Plugins-section card or tab alongside the standalone section', () => {
|
|
82
|
+
const { declared } = collectRegistrations()
|
|
83
|
+
expect(declared).not.toContain('settings.plugins.tab')
|
|
84
|
+
expect(declared).not.toContain('settings.plugin.item')
|
|
85
|
+
})
|
|
86
|
+
})
|