dsh-context-compression-improved 0.2.1 → 0.3.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.
- 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,12 +1,9 @@
|
|
|
1
|
-
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/ban-ts-comment -- standalone E2E test script; DSH internal types unavailable */
|
|
2
|
-
// @ts-nocheck
|
|
3
|
-
// internals and cordis augmented services outside the scripts tsconfig scope.
|
|
4
1
|
import { createHash } from 'node:crypto'
|
|
5
2
|
import { spawn } from 'node:child_process'
|
|
6
3
|
import { createReadStream } from 'node:fs'
|
|
7
4
|
import { copyFile, mkdir, mkdtemp, readFile, readdir, realpath, rm, stat, writeFile } from 'node:fs/promises'
|
|
8
5
|
import { readFileSync } from 'node:fs'
|
|
9
|
-
import { createServer
|
|
6
|
+
import { createServer } from 'node:http'
|
|
10
7
|
import { createRequire } from 'node:module'
|
|
11
8
|
import { tmpdir } from 'node:os'
|
|
12
9
|
import { basename, dirname, join } from 'node:path'
|
|
@@ -17,43 +14,21 @@ const root = fileURLToPath(new URL('..', import.meta.url))
|
|
|
17
14
|
// Release mode (default) is fail-closed: the upgrade leg and the official
|
|
18
15
|
// clean-harness lifecycle must both run to green or the gate exits non-zero.
|
|
19
16
|
// Set DSH_E2E_MODE=dev for an offline smoke with explicit skip markers.
|
|
20
|
-
const e2eMode
|
|
17
|
+
const e2eMode = process.env.DSH_E2E_MODE === 'dev' ? 'dev' : 'release'
|
|
21
18
|
const packages = [
|
|
22
19
|
{ directory: join(root, 'packages/selector') },
|
|
23
20
|
]
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
'@deepseek-ai/
|
|
31
|
-
|
|
32
|
-
'@deepseek-ai/dsh-agent-presets@0.1.1-rc.2',
|
|
33
|
-
'@deepseek-ai/dsh-commands@0.1.1-rc.2',
|
|
34
|
-
'@deepseek-ai/dsh-command-compact@0.1.1-rc.2',
|
|
35
|
-
'@deepseek-ai/dsh-compaction-basic@0.1.1-rc.2',
|
|
36
|
-
'@deepseek-ai/dsh-llm@0.1.1-rc.2',
|
|
37
|
-
'@deepseek-ai/dsh-session@0.1.1-rc.2',
|
|
38
|
-
'@deepseek-ai/dsh-scope@0.1.1-rc.2',
|
|
39
|
-
'@deepseek-ai/dsh-settings@0.1.1-rc.2',
|
|
40
|
-
'@deepseek-ai/dsh-system-prompt@0.1.1-rc.2',
|
|
41
|
-
'@deepseek-ai/dsh-token-meter@0.1.1-rc.2',
|
|
42
|
-
'@deepseek-ai/dsh-tools@0.1.1-rc.2',
|
|
43
|
-
]
|
|
44
|
-
|
|
45
|
-
// On Windows npm and pnpm only exist as .cmd shims, which Node refuses to
|
|
46
|
-
// spawn directly, so those two are routed through the shell.
|
|
47
|
-
const spawnTool = (command: string, args: string[], options?: Record<string, unknown>) => spawn(
|
|
48
|
-
command,
|
|
49
|
-
args,
|
|
50
|
-
process.platform === 'win32' && (command === 'npm' || command === 'pnpm')
|
|
51
|
-
? { ...options, shell: true }
|
|
52
|
-
: options,
|
|
53
|
-
)
|
|
21
|
+
// The full @deepseek-ai/* host closure (pinned in the workspace root manifest,
|
|
22
|
+
// currently 0.1.5-rc.2): auto-installed peers must all resolve from the mock
|
|
23
|
+
// registry, so the list must cover every peer the packed plugins and the
|
|
24
|
+
// official packages declare.
|
|
25
|
+
const rootManifest = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'))
|
|
26
|
+
const officialHostPackages = Object.entries(rootManifest.devDependencies)
|
|
27
|
+
.filter(([name]) => name.startsWith('@deepseek-ai/'))
|
|
28
|
+
.map(([name, version]) => `${name}@${version}`)
|
|
54
29
|
|
|
55
|
-
const run = (command
|
|
56
|
-
const child =
|
|
30
|
+
const run = (command, args, options = {}) => new Promise((resolve, reject) => {
|
|
31
|
+
const child = spawn(command, args, { stdio: 'inherit', shell: process.platform === 'win32', ...options })
|
|
57
32
|
child.once('error', reject)
|
|
58
33
|
child.once('exit', (code, signal) => {
|
|
59
34
|
if (code === 0) resolve()
|
|
@@ -61,14 +36,14 @@ const run = (command: string, args: string[], options: Record<string, unknown> =
|
|
|
61
36
|
})
|
|
62
37
|
})
|
|
63
38
|
|
|
64
|
-
const capture = (command
|
|
65
|
-
const child =
|
|
39
|
+
const capture = (command, args, options = {}) => new Promise((resolve, reject) => {
|
|
40
|
+
const child = spawn(command, args, { shell: process.platform === 'win32', ...options, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
66
41
|
let stdout = ''
|
|
67
42
|
let stderr = ''
|
|
68
|
-
child.stdout
|
|
69
|
-
child.stderr
|
|
70
|
-
child.stdout
|
|
71
|
-
child.stderr
|
|
43
|
+
child.stdout.setEncoding('utf8')
|
|
44
|
+
child.stderr.setEncoding('utf8')
|
|
45
|
+
child.stdout.on('data', chunk => { stdout += chunk })
|
|
46
|
+
child.stderr.on('data', chunk => { stderr += chunk })
|
|
72
47
|
child.once('error', reject)
|
|
73
48
|
child.once('exit', (code, signal) => {
|
|
74
49
|
if (code === 0) resolve({ stdout, stderr })
|
|
@@ -80,26 +55,19 @@ const capture = (command: string, args: string[], options: Record<string, unknow
|
|
|
80
55
|
})
|
|
81
56
|
})
|
|
82
57
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
signal: NodeJS.Signals | null
|
|
86
|
-
stdout: string
|
|
87
|
-
stderr: string
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const captureOutcome = (command: string, args: string[], options: Record<string, unknown> = {}): Promise<CaptureOutcome> => new Promise((resolve, reject) => {
|
|
91
|
-
const child = spawnTool(command, args, { ...options, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
58
|
+
const captureOutcome = (command, args, options = {}) => new Promise((resolve, reject) => {
|
|
59
|
+
const child = spawn(command, args, { shell: process.platform === 'win32', ...options, stdio: ['ignore', 'pipe', 'pipe'] })
|
|
92
60
|
let stdout = ''
|
|
93
61
|
let stderr = ''
|
|
94
|
-
child.stdout
|
|
95
|
-
child.stderr
|
|
96
|
-
child.stdout
|
|
97
|
-
child.stderr
|
|
62
|
+
child.stdout.setEncoding('utf8')
|
|
63
|
+
child.stderr.setEncoding('utf8')
|
|
64
|
+
child.stdout.on('data', chunk => { stdout += chunk })
|
|
65
|
+
child.stderr.on('data', chunk => { stderr += chunk })
|
|
98
66
|
child.once('error', reject)
|
|
99
67
|
child.once('exit', (code, signal) => resolve({ code, signal, stdout, stderr }))
|
|
100
68
|
})
|
|
101
69
|
|
|
102
|
-
const allocateLoopbackPort = ()
|
|
70
|
+
const allocateLoopbackPort = () => new Promise((resolve, reject) => {
|
|
103
71
|
const server = createServer()
|
|
104
72
|
server.once('error', reject)
|
|
105
73
|
server.listen(0, '127.0.0.1', () => {
|
|
@@ -112,19 +80,14 @@ const allocateLoopbackPort = (): Promise<number> => new Promise((resolve, reject
|
|
|
112
80
|
})
|
|
113
81
|
})
|
|
114
82
|
|
|
115
|
-
const assert = (condition
|
|
83
|
+
const assert = (condition, message) => {
|
|
116
84
|
if (!condition) throw new Error(`packed Host smoke: ${message}`)
|
|
117
85
|
}
|
|
118
86
|
|
|
119
|
-
|
|
120
|
-
label: string
|
|
121
|
-
pattern: RegExp
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
async function scanPublishedTree(packageRoot: string) {
|
|
87
|
+
async function scanPublishedTree(packageRoot) {
|
|
125
88
|
const pending = [packageRoot]
|
|
126
|
-
const violations
|
|
127
|
-
const forbidden
|
|
89
|
+
const violations = []
|
|
90
|
+
const forbidden = [
|
|
128
91
|
{ label: 'developer absolute path', pattern: /(?:\/home\/|[A-Za-z]:\\Users\\)/u },
|
|
129
92
|
{ label: 'OpenAI-style secret', pattern: /\bsk-[A-Za-z0-9_-]{20,}\b/u },
|
|
130
93
|
{ label: 'NPM token', pattern: /\bnpm_[A-Za-z0-9]{20,}\b/u },
|
|
@@ -132,7 +95,7 @@ async function scanPublishedTree(packageRoot: string) {
|
|
|
132
95
|
{ label: 'assigned DeepSeek API key', pattern: /DEEPSEEK_API_KEY\s*=\s*[^\s"']+/u },
|
|
133
96
|
]
|
|
134
97
|
while (pending.length > 0) {
|
|
135
|
-
const directory = pending.pop()
|
|
98
|
+
const directory = pending.pop()
|
|
136
99
|
for (const entry of await readdir(directory, { withFileTypes: true })) {
|
|
137
100
|
const path = join(directory, entry.name)
|
|
138
101
|
if (entry.isDirectory()) {
|
|
@@ -150,9 +113,9 @@ async function scanPublishedTree(packageRoot: string) {
|
|
|
150
113
|
if (violations.length > 0) throw new Error(`packed content scan failed:\n${violations.join('\n')}`)
|
|
151
114
|
}
|
|
152
115
|
|
|
153
|
-
async function runPackedHostSmoke(consumerRoot
|
|
116
|
+
async function runPackedHostSmoke(consumerRoot) {
|
|
154
117
|
const consumerRequire = createRequire(join(consumerRoot, 'package.json'))
|
|
155
|
-
const load = async
|
|
118
|
+
const load = async specifier => import(pathToFileURL(consumerRequire.resolve(specifier)).href)
|
|
156
119
|
const [
|
|
157
120
|
cordis,
|
|
158
121
|
groupModule,
|
|
@@ -167,6 +130,7 @@ async function runPackedHostSmoke(consumerRoot: string) {
|
|
|
167
130
|
scopeModule,
|
|
168
131
|
settingsModule,
|
|
169
132
|
systemPromptModule,
|
|
133
|
+
sessionProjectionModule,
|
|
170
134
|
tokenMeterModule,
|
|
171
135
|
toolsModule,
|
|
172
136
|
selectorModule,
|
|
@@ -184,10 +148,11 @@ async function runPackedHostSmoke(consumerRoot: string) {
|
|
|
184
148
|
load('@deepseek-ai/dsh-scope'),
|
|
185
149
|
load('@deepseek-ai/dsh-settings'),
|
|
186
150
|
load('@deepseek-ai/dsh-system-prompt'),
|
|
151
|
+
load('@deepseek-ai/dsh-session-projection'),
|
|
187
152
|
load('@deepseek-ai/dsh-token-meter'),
|
|
188
153
|
load('@deepseek-ai/dsh-tools'),
|
|
189
154
|
load('dsh-context-compression-improved'),
|
|
190
|
-
])
|
|
155
|
+
])
|
|
191
156
|
|
|
192
157
|
class MemorySettings extends settingsModule.SettingsProvider {
|
|
193
158
|
writable = true
|
|
@@ -215,46 +180,48 @@ async function runPackedHostSmoke(consumerRoot: string) {
|
|
|
215
180
|
const runtime = new cordis.Context()
|
|
216
181
|
try {
|
|
217
182
|
runtime.baseUrl = `${pathToFileURL(presetRoot).href}/`
|
|
218
|
-
await runtime.plugin(loaderModule.default)
|
|
183
|
+
await runtime.plugin(loaderModule.default).await()
|
|
219
184
|
runtime.loader.builtins.include = includeModule.default
|
|
220
185
|
runtime.loader.builtins.group = groupModule.default
|
|
221
|
-
await runtime.plugin(llmModule.default)
|
|
222
|
-
await runtime.plugin(sessionModule.default)
|
|
223
|
-
await runtime.plugin(systemPromptModule.default, {
|
|
224
|
-
await runtime.plugin(toolsModule.default)
|
|
225
|
-
await runtime.plugin(agentModule.default)
|
|
226
|
-
await runtime.plugin(agentLoopModule.default, { agents: [] })
|
|
227
|
-
await runtime.plugin(commandsModule.default)
|
|
228
|
-
await runtime.plugin(
|
|
229
|
-
await runtime.plugin(
|
|
186
|
+
await runtime.plugin(llmModule.default).await()
|
|
187
|
+
await runtime.plugin(sessionModule.default).await()
|
|
188
|
+
await runtime.plugin(systemPromptModule.default, { personaPrefix: '' }).await()
|
|
189
|
+
await runtime.plugin(toolsModule.default).await()
|
|
190
|
+
await runtime.plugin(agentModule.default).await()
|
|
191
|
+
await runtime.plugin(agentLoopModule.default, { agents: [] }).await()
|
|
192
|
+
await runtime.plugin(commandsModule.default).await()
|
|
193
|
+
await runtime.plugin(sessionProjectionModule.default).await()
|
|
194
|
+
await runtime.plugin(tokenMeterModule.default).await()
|
|
195
|
+
await runtime.plugin(MemorySettings).await()
|
|
230
196
|
await runtime.plugin(presetsModule.default, {
|
|
231
197
|
default: 'standard',
|
|
232
198
|
roots: [{ path: presetRoot, trust: 'system' }],
|
|
199
|
+
includeShippedRoot: false,
|
|
233
200
|
includeUserRoot: false,
|
|
234
|
-
})
|
|
201
|
+
}).await()
|
|
235
202
|
await runtime.plugin({
|
|
236
|
-
apply:
|
|
203
|
+
apply: selectorCtx => selectorModule.apply(selectorCtx, { presetOverlay: true }),
|
|
237
204
|
}).await()
|
|
238
205
|
|
|
239
|
-
const createAgent = async (sessionId
|
|
206
|
+
const createAgent = async (sessionId, preset) => {
|
|
240
207
|
const handle = await runtime.agents.create({
|
|
241
208
|
sessionId: sessionModule.SessionId(sessionId),
|
|
242
|
-
setup: async
|
|
209
|
+
setup: async agentCtx => void await runtime.agentPresets.mount(agentCtx, preset),
|
|
243
210
|
})
|
|
244
211
|
return handle.agent
|
|
245
212
|
}
|
|
246
|
-
const hasRetrieve =
|
|
213
|
+
const hasRetrieve = agent => runtime.tools.get(
|
|
247
214
|
'context_compression_retrieve',
|
|
248
215
|
scopeModule.scopeOf(agent.ctx),
|
|
249
216
|
) !== undefined
|
|
250
|
-
const hasCompact =
|
|
251
|
-
.some(
|
|
252
|
-
const hasCompleteStack =
|
|
217
|
+
const hasCompact = agent => runtime.commands.list(agent)
|
|
218
|
+
.some(command => command.name === 'compact')
|
|
219
|
+
const hasCompleteStack = agent =>
|
|
253
220
|
runtime.agentPresets.serviceFor(agent, 'toolResultPruner') !== undefined
|
|
254
221
|
&& runtime.agentPresets.serviceFor(agent, 'compaction') !== undefined
|
|
255
222
|
&& hasRetrieve(agent)
|
|
256
223
|
&& hasCompact(agent)
|
|
257
|
-
const hasNoStack =
|
|
224
|
+
const hasNoStack = agent =>
|
|
258
225
|
runtime.agentPresets.serviceFor(agent, 'toolResultPruner') === undefined
|
|
259
226
|
&& runtime.agentPresets.serviceFor(agent, 'compaction') === undefined
|
|
260
227
|
&& !hasRetrieve(agent)
|
|
@@ -278,7 +245,7 @@ async function runPackedHostSmoke(consumerRoot: string) {
|
|
|
278
245
|
const parent = await createAgent('packed-host-parent', 'standard')
|
|
279
246
|
const childHandle = await runtime.agents.create({
|
|
280
247
|
sessionId: sessionModule.SessionId('packed-host-child'),
|
|
281
|
-
setup:
|
|
248
|
+
setup: childCtx => void runtime.agentPresets.composeFrom(childCtx, parent.ctx),
|
|
282
249
|
})
|
|
283
250
|
const child = childHandle.agent
|
|
284
251
|
const original = Symbol.for('cordis.original')
|
|
@@ -290,14 +257,14 @@ async function runPackedHostSmoke(consumerRoot: string) {
|
|
|
290
257
|
'parent or child pruner is missing')
|
|
291
258
|
assert(parentCompaction !== undefined && childCompaction !== undefined,
|
|
292
259
|
'parent or child compaction engine is missing')
|
|
293
|
-
assert(Object.is(
|
|
260
|
+
assert(Object.is(childPruner[original], parentPruner[original]),
|
|
294
261
|
'child did not inherit the parent pruner instance')
|
|
295
|
-
assert(Object.is(
|
|
262
|
+
assert(Object.is(childCompaction[original], parentCompaction[original]),
|
|
296
263
|
'child did not inherit the parent compaction instance')
|
|
297
264
|
assert(hasRetrieve(child) && hasCompact(child),
|
|
298
265
|
'child lacks the inherited retrieve tool or compact command')
|
|
299
266
|
|
|
300
|
-
const namespace =
|
|
267
|
+
const namespace = 'context-compression'
|
|
301
268
|
const settings = runtime.settings.get(namespace)
|
|
302
269
|
assert(settings.custom.history.trigger === 500_000,
|
|
303
270
|
'packed Host did not expose the 500000 History default')
|
|
@@ -318,24 +285,19 @@ async function runPackedHostSmoke(consumerRoot: string) {
|
|
|
318
285
|
}
|
|
319
286
|
}
|
|
320
287
|
|
|
321
|
-
async function runOfficialCloneCliSmoke(
|
|
322
|
-
referenceRoot: string,
|
|
323
|
-
registry: string,
|
|
324
|
-
upgradeFrom: string | undefined,
|
|
325
|
-
candidateVersion: string,
|
|
326
|
-
) {
|
|
288
|
+
async function runOfficialCloneCliSmoke(referenceRoot, registry, upgradeFrom, candidateVersion) {
|
|
327
289
|
const clone = await realpath(referenceRoot)
|
|
328
|
-
const git = async (...args
|
|
290
|
+
const git = async (...args) => (await capture('git', args, { cwd: clone })).stdout.trim()
|
|
329
291
|
const before = {
|
|
330
292
|
tag: await git('describe', '--tags', '--exact-match'),
|
|
331
293
|
commit: await git('rev-parse', 'HEAD'),
|
|
332
294
|
tree: await git('rev-parse', 'HEAD^{tree}'),
|
|
333
295
|
status: await git('status', '--porcelain'),
|
|
334
296
|
}
|
|
335
|
-
assert(before.tag === 'dsh-v0.1.
|
|
336
|
-
assert(before.commit === '
|
|
297
|
+
assert(before.tag === 'dsh-v0.1.5-rc.2', `official clone tag is ${before.tag}`)
|
|
298
|
+
assert(before.commit === 'fb2c4b9e698e30edb738bca4cf0618587db7d203',
|
|
337
299
|
`official clone commit is ${before.commit}`)
|
|
338
|
-
assert(before.tree === '
|
|
300
|
+
assert(before.tree === 'bd7dd6d90010a35d3d6ff9f12c1f6207d5b6fe38',
|
|
339
301
|
`official clone tree is ${before.tree}`)
|
|
340
302
|
assert(before.status === '', 'official clone is dirty before CLI smoke')
|
|
341
303
|
|
|
@@ -344,12 +306,13 @@ async function runOfficialCloneCliSmoke(
|
|
|
344
306
|
const dshHome = join(temporaryRoot, 'dsh-home')
|
|
345
307
|
let added = false
|
|
346
308
|
const environment = { ...process.env, DSH_HOME: dshHome }
|
|
347
|
-
const dsh = (...args
|
|
309
|
+
const dsh = (...args) => run('pnpm', ['dsh', ...args], { cwd: worktree, env: environment })
|
|
348
310
|
const dumpConfig = () => capture('pnpm', ['dsh', '--profile', 'web', '--dump-config'], {
|
|
349
311
|
cwd: worktree,
|
|
350
312
|
env: environment,
|
|
351
313
|
}).then(captured => captured.stdout)
|
|
352
314
|
|
|
315
|
+
/** Installed manifest of the profile-resolved plugin package. */
|
|
353
316
|
const profilePackages = () => {
|
|
354
317
|
const profileRoot = join(dshHome, 'profiles/web')
|
|
355
318
|
const profileRequire = createRequire(join(profileRoot, 'package.json'))
|
|
@@ -357,25 +320,45 @@ async function runOfficialCloneCliSmoke(
|
|
|
357
320
|
return {
|
|
358
321
|
profileRoot,
|
|
359
322
|
selectorPath,
|
|
360
|
-
selector: JSON.parse(readFileSync(selectorPath, 'utf8'))
|
|
323
|
+
selector: JSON.parse(readFileSync(selectorPath, 'utf8')),
|
|
361
324
|
}
|
|
362
325
|
}
|
|
363
326
|
|
|
327
|
+
/**
|
|
328
|
+
* Peers a headless official profile can never provide: pure web-host UI
|
|
329
|
+
* packages supplied by the web app, not the CLI installation. The list is
|
|
330
|
+
* REVIEWED and exact — any OTHER unresolved selector peer fails the gate,
|
|
331
|
+
* and every web/client peer is still import-verified from the built
|
|
332
|
+
* client libraries below.
|
|
333
|
+
*/
|
|
364
334
|
const WEB_ONLY_HEADLESS_UNRESOLVED_PEERS = [
|
|
365
335
|
'@deepseek-ai/dsh-client-ui-primitives',
|
|
366
336
|
'@deepseek-ai/dsh-client-ui-slots',
|
|
367
337
|
]
|
|
368
338
|
|
|
339
|
+
/**
|
|
340
|
+
* Verify every web/client peer exists as a BUILT artifact resolvable from
|
|
341
|
+
* the packages that depend on it. Executable loading of the client stack
|
|
342
|
+
* happens under the web bundler, not bare Node (the UI packages ship CSS
|
|
343
|
+
* modules only a bundler can import): that load leg is the `build:web`
|
|
344
|
+
* step of the worktree's `pnpm run build` above, which fails closed when
|
|
345
|
+
* the client graph is broken.
|
|
346
|
+
*/
|
|
369
347
|
const proveBuiltClientPeersLoad = async () => {
|
|
370
348
|
const clientPeers = [
|
|
371
349
|
'@deepseek-ai/dsh-client-locale',
|
|
372
|
-
'@deepseek-ai/dsh-client-
|
|
350
|
+
'@deepseek-ai/dsh-client-store',
|
|
373
351
|
'@deepseek-ai/dsh-client-ui-primitives',
|
|
374
352
|
'@deepseek-ai/dsh-client-ui-settings',
|
|
375
353
|
'@deepseek-ai/dsh-client-ui-slots',
|
|
376
354
|
'@deepseek-ai/dsh-client-ui-workspace',
|
|
377
355
|
'react',
|
|
378
356
|
]
|
|
357
|
+
// pnpm's isolated layout keeps each package's dependents in ITS own
|
|
358
|
+
// node_modules, so anchor resolution at the packages that actually
|
|
359
|
+
// depend on the client stack: the web-app bundle (locale, store,
|
|
360
|
+
// ui-settings, ui-workspace), the client-locale package (react,
|
|
361
|
+
// ui-primitives, ui-slots), and the web app.
|
|
379
362
|
const anchors = JSON.stringify([
|
|
380
363
|
join(worktree, 'packages/bundle/web-app/package.json'),
|
|
381
364
|
join(worktree, 'packages/client/locale/package.json'),
|
|
@@ -398,6 +381,15 @@ async function runOfficialCloneCliSmoke(
|
|
|
398
381
|
return clientPeers.length
|
|
399
382
|
}
|
|
400
383
|
|
|
384
|
+
/**
|
|
385
|
+
* Prove the plugin actually loads in the official profile: pnpm's peers
|
|
386
|
+
* check cannot see the healed profiles/node_modules fallback by design, so
|
|
387
|
+
* the release gate resolves and imports instead. Fail-closed parts: every
|
|
388
|
+
* RUNTIME peer (the engine surface the headless host must provide) and the
|
|
389
|
+
* selector's node entry with a callable apply(). The selector's remaining
|
|
390
|
+
* unresolved peers must equal the REVIEWED web-only whitelist above —
|
|
391
|
+
* anything else (a newly missing host dependency) fails immediately.
|
|
392
|
+
*/
|
|
401
393
|
const provePluginLoads = async () => {
|
|
402
394
|
const { profileRoot, selector } = profilePackages()
|
|
403
395
|
const selectorPeers = Object.keys(selector.peerDependencies ?? {})
|
|
@@ -431,7 +423,7 @@ async function runOfficialCloneCliSmoke(
|
|
|
431
423
|
throw new Error(`official profile failed to load the plugin and its engine peers:\n${outcome.stdout}\n${outcome.stderr}`)
|
|
432
424
|
}
|
|
433
425
|
const unresolved = JSON.parse(marker.slice('LOAD_OK '.length))
|
|
434
|
-
const unexpected = unresolved.filter(
|
|
426
|
+
const unexpected = unresolved.filter(peer => !WEB_ONLY_HEADLESS_UNRESOLVED_PEERS.includes(peer))
|
|
435
427
|
if (unexpected.length > 0) {
|
|
436
428
|
throw new Error(`selector peers failed to resolve headless outside the reviewed web-only whitelist: ${unexpected.join(', ')}`)
|
|
437
429
|
}
|
|
@@ -443,7 +435,18 @@ async function runOfficialCloneCliSmoke(
|
|
|
443
435
|
}
|
|
444
436
|
}
|
|
445
437
|
|
|
446
|
-
|
|
438
|
+
/**
|
|
439
|
+
* Boot the official profile FOR REAL through the CLI's own profile-boot
|
|
440
|
+
* path — not --dump-config, which composes YAML without booting or
|
|
441
|
+
* executing plugins. The probe mounts the full bundle tree (selector
|
|
442
|
+
* included), then proves: the settings service resolves the registered
|
|
443
|
+
* document (before the upgrade: the previous release's schema defaults;
|
|
444
|
+
* after: the seeded savings/73 document parsed with the INSTALLED runtime),
|
|
445
|
+
* the AgentPresets service composed the selector's standing overlay, and
|
|
446
|
+
* the overlay's generated composition exists with the deterministic
|
|
447
|
+
* identity filename. Any failure exits non-zero.
|
|
448
|
+
*/
|
|
449
|
+
const runProfileBootProbe = async (phase, expectSeeded) => {
|
|
447
450
|
const profilePort = await allocateLoopbackPort()
|
|
448
451
|
const { profileRoot } = profilePackages()
|
|
449
452
|
const settingsProof = expectSeeded
|
|
@@ -457,6 +460,9 @@ async function runOfficialCloneCliSmoke(
|
|
|
457
460
|
' }',
|
|
458
461
|
]
|
|
459
462
|
: [
|
|
463
|
+
// The previous release predates the autoCompact section and the
|
|
464
|
+
// public parser; prove its OWN schema resolved the registered
|
|
465
|
+
// namespace into a complete supported document instead.
|
|
460
466
|
" if (raw?.profile !== 'balanced' || raw?.custom?.version !== 3) {",
|
|
461
467
|
" throw new Error('boot probe: previous-release settings defaults did not resolve: ' + JSON.stringify(raw))",
|
|
462
468
|
' }',
|
|
@@ -470,6 +476,10 @@ async function runOfficialCloneCliSmoke(
|
|
|
470
476
|
`const worktree = String.raw\`${worktree}\``,
|
|
471
477
|
`const profileRoot = String.raw\`${join(profileRoot, 'package.json')}\``,
|
|
472
478
|
'const bootModule = await import(pathToFileURL(join(worktree, \'apps/cli/src/profile-boot.ts\')).href)',
|
|
479
|
+
// Import the workspace packages from their source trees (pinned, like
|
|
480
|
+
// the profile-boot path above): the probe runs under tsx, and their
|
|
481
|
+
// lib/ artifacts are only produced by the full release build, not the
|
|
482
|
+
// library faces.
|
|
473
483
|
"const appBoot = await import(pathToFileURL(join(worktree, 'packages/boot/app-boot/src/index.ts')).href)",
|
|
474
484
|
"const settingsModule = await import(pathToFileURL(join(worktree, 'packages/settings/settings/src/index.ts')).href)",
|
|
475
485
|
'const preBootStores = new Set(await readdir(tmpdir()))',
|
|
@@ -482,7 +492,7 @@ async function runOfficialCloneCliSmoke(
|
|
|
482
492
|
'try {',
|
|
483
493
|
" const settings = ctx.get('settings')",
|
|
484
494
|
" if (settings === undefined) throw new Error('boot probe: settings service missing')",
|
|
485
|
-
" const raw = settings.get(
|
|
495
|
+
" const raw = settings.get('context-compression')",
|
|
486
496
|
...settingsProof,
|
|
487
497
|
" const presets = ctx.get('agentPresets')",
|
|
488
498
|
" if (presets === undefined) throw new Error('boot probe: agentPresets service missing')",
|
|
@@ -490,6 +500,8 @@ async function runOfficialCloneCliSmoke(
|
|
|
490
500
|
' if (key?.agentPreset !== \'standard\') {',
|
|
491
501
|
" throw new Error('boot probe: standing key did not compose the default preset: ' + JSON.stringify(key))",
|
|
492
502
|
' }',
|
|
503
|
+
// Only stores CREATED BY THIS BOOT count: stale leftovers from earlier
|
|
504
|
+
// runs or concurrent harnesses must never satisfy the overlay proof.
|
|
493
505
|
' const generated = []',
|
|
494
506
|
" for (const entry of await readdir(tmpdir(), { withFileTypes: true })) {",
|
|
495
507
|
' if (!entry.isDirectory() || preBootStores.has(entry.name)) continue',
|
|
@@ -498,7 +510,7 @@ async function runOfficialCloneCliSmoke(
|
|
|
498
510
|
' try {',
|
|
499
511
|
' children = await readdir(join(tmpdir(), entry.name))',
|
|
500
512
|
' } catch {',
|
|
501
|
-
' continue',
|
|
513
|
+
' continue // a concurrent process disposed its store mid-scan',
|
|
502
514
|
' }',
|
|
503
515
|
" for (const child of children) {",
|
|
504
516
|
" if (/^standard-[0-9a-f]{24}\\.agent\\.cordis\\.yml$/u.test(child)) generated.push(child)",
|
|
@@ -526,6 +538,7 @@ async function runOfficialCloneCliSmoke(
|
|
|
526
538
|
return JSON.parse(marker.slice('BOOT_PROBE_OK '.length))
|
|
527
539
|
}
|
|
528
540
|
|
|
541
|
+
/** Seed real user settings through the plugin's own public surface. */
|
|
529
542
|
const seedSettings = async () => {
|
|
530
543
|
const { profileRoot } = profilePackages()
|
|
531
544
|
const settingsPath = join(dshHome, 'settings.yaml')
|
|
@@ -558,7 +571,8 @@ async function runOfficialCloneCliSmoke(
|
|
|
558
571
|
return settingsPath
|
|
559
572
|
}
|
|
560
573
|
|
|
561
|
-
|
|
574
|
+
/** Read the seeded settings back through the upgraded plugin's parser. */
|
|
575
|
+
const proveSettingsPreserved = async (settingsPath) => {
|
|
562
576
|
const { profileRoot } = profilePackages()
|
|
563
577
|
const script = [
|
|
564
578
|
"const { readFileSync } = require('node:fs')",
|
|
@@ -587,8 +601,15 @@ async function runOfficialCloneCliSmoke(
|
|
|
587
601
|
await run('git', ['worktree', 'add', '--detach', worktree, before.commit], { cwd: clone })
|
|
588
602
|
added = true
|
|
589
603
|
await run('pnpm', ['install', '--frozen-lockfile', '--ignore-scripts'], { cwd: worktree })
|
|
604
|
+
// A source checkout ships no built artifacts (install above skips
|
|
605
|
+
// prepare scripts). The boot probes run the REAL web profile —
|
|
606
|
+
// dsh-base + dsh-web-app, the only bundle that mounts agent-presets and
|
|
607
|
+
// therefore the selector's preset overlay — so build both library faces
|
|
608
|
+
// AND the web frontend; the healed profiles/node_modules fallback also
|
|
609
|
+
// symlinks the CLI's own workspace packages.
|
|
590
610
|
await run('pnpm', ['run', 'build'], { cwd: worktree })
|
|
591
611
|
|
|
612
|
+
// Real lifecycle: start on the published previous release.
|
|
592
613
|
if (upgradeFrom === undefined) throw new Error('release gate requires the previous published release for the official lifecycle')
|
|
593
614
|
await dsh('plugin', '--profile', 'web', 'add',
|
|
594
615
|
`dsh-context-compression-improved@${upgradeFrom}`, '--registry', registry)
|
|
@@ -597,14 +618,24 @@ async function runOfficialCloneCliSmoke(
|
|
|
597
618
|
throw new Error(`official lifecycle started on ${String(beforeUp.selector.version)}, expected ${upgradeFrom}`)
|
|
598
619
|
}
|
|
599
620
|
|
|
621
|
+
// Boot the profile once on the previous release. This asserts the added
|
|
622
|
+
// bundle layer is live before any update and heals
|
|
623
|
+
// $DSH_HOME/profiles/node_modules — the shared-module fallback the
|
|
624
|
+
// plugin's harness peers resolve through in every later raw-node proof.
|
|
600
625
|
const addedDump = await dumpConfig()
|
|
601
626
|
assert(addedDump.includes('context-compression-selector-bundle'),
|
|
602
627
|
'official post-add dump lacks the selector Bundle layer')
|
|
603
628
|
|
|
629
|
+
// Real profile start on the previous release, BEFORE seeding: the
|
|
630
|
+
// previous-release schema predates the autoCompact section, so this probe
|
|
631
|
+
// proves its own defaults resolve; the seeded document is asserted by the
|
|
632
|
+
// post-up probe through the upgraded runtime.
|
|
604
633
|
const postAddBoot = await runProfileBootProbe('post-add', false)
|
|
605
634
|
|
|
606
635
|
const settingsPath = await seedSettings()
|
|
607
636
|
|
|
637
|
+
// Standard update command moves the one install-facing package; assert the
|
|
638
|
+
// version it lands before anything else runs.
|
|
608
639
|
await dsh('plugin', '--profile', 'web', 'up',
|
|
609
640
|
'dsh-context-compression-improved@latest', '--registry', registry)
|
|
610
641
|
const afterUp = profilePackages()
|
|
@@ -621,6 +652,8 @@ async function runOfficialCloneCliSmoke(
|
|
|
621
652
|
assert(upDump.match(/context-compression-selector-bundle/gu)?.length === 1,
|
|
622
653
|
'official post-up dump contains more than one selector Bundle layer')
|
|
623
654
|
|
|
655
|
+
// pnpm's peers check is informational: plugin peers resolve through the
|
|
656
|
+
// healed profiles/node_modules fallback, which pnpm cannot see by design.
|
|
624
657
|
const peers = await captureOutcome('pnpm', ['peers', 'check'], {
|
|
625
658
|
cwd: join(dshHome, 'profiles/web'),
|
|
626
659
|
env: environment,
|
|
@@ -642,7 +675,7 @@ async function runOfficialCloneCliSmoke(
|
|
|
642
675
|
commit: before.commit,
|
|
643
676
|
tree: before.tree,
|
|
644
677
|
status: 'clean',
|
|
645
|
-
install: '
|
|
678
|
+
install: 'entry-package-only',
|
|
646
679
|
upgrade: {
|
|
647
680
|
from: upgradeFrom,
|
|
648
681
|
to: candidateVersion,
|
|
@@ -674,24 +707,13 @@ const comparisonRoot = fixedArtifactRoot === undefined
|
|
|
674
707
|
? undefined
|
|
675
708
|
: await mkdtemp(join(tmpdir(), 'dsh-selector-rebuild-'))
|
|
676
709
|
const consumerRoot = await mkdtemp(join(tmpdir(), 'dsh-selector-consumer-'))
|
|
677
|
-
let server
|
|
710
|
+
let server
|
|
678
711
|
|
|
679
712
|
try {
|
|
680
|
-
const registryPackages = new Map
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
sha1: string
|
|
685
|
-
sha256?: string
|
|
686
|
-
integrity: string
|
|
687
|
-
}>()
|
|
688
|
-
const previousVersions = new Map<string, {
|
|
689
|
-
filename: string
|
|
690
|
-
manifest: Record<string, any>
|
|
691
|
-
tarball: string
|
|
692
|
-
sha1: string
|
|
693
|
-
integrity: string
|
|
694
|
-
}>()
|
|
713
|
+
const registryPackages = new Map()
|
|
714
|
+
// Published previous-release metadata served alongside the packed candidate
|
|
715
|
+
// so the upgrade leg can install the real shipped release first.
|
|
716
|
+
const previousVersions = new Map()
|
|
695
717
|
// The predecessor is whatever the registry actually offers. This used to be
|
|
696
718
|
// the pinned literal '0.1.0-beta.2', which only ever existed under this
|
|
697
719
|
// package's pre-rename name, so it could never resolve under the current one.
|
|
@@ -710,13 +732,8 @@ try {
|
|
|
710
732
|
break
|
|
711
733
|
}
|
|
712
734
|
if (!packumentResponse.ok) throw new Error(`packument responded ${packumentResponse.status}`)
|
|
713
|
-
const packument = await packumentResponse.json()
|
|
714
|
-
|
|
715
|
-
time?: Record<string, string>
|
|
716
|
-
}
|
|
717
|
-
const localVersion = JSON.parse(
|
|
718
|
-
await readFile(join(packages[0].directory, 'package.json'), 'utf8'),
|
|
719
|
-
).version as string
|
|
735
|
+
const packument = await packumentResponse.json()
|
|
736
|
+
const localVersion = JSON.parse(await readFile(join(packages[0].directory, 'package.json'), 'utf8')).version
|
|
720
737
|
const times = packument.time ?? {}
|
|
721
738
|
// Publication order, not semver order: the version a user would be
|
|
722
739
|
// upgrading from is the most recently published one that is not the
|
|
@@ -732,7 +749,7 @@ try {
|
|
|
732
749
|
previousRelease = earlier[0]
|
|
733
750
|
const response = await fetch(`https://registry.npmjs.org/${name}/${previousRelease}`)
|
|
734
751
|
if (!response.ok) throw new Error(`packument responded ${response.status}`)
|
|
735
|
-
const manifest = await response.json()
|
|
752
|
+
const manifest = await response.json()
|
|
736
753
|
const tarballResponse = await fetch(manifest.dist.tarball)
|
|
737
754
|
if (!tarballResponse.ok) throw new Error(`tarball responded ${tarballResponse.status}`)
|
|
738
755
|
const bytes = Buffer.from(await tarballResponse.arrayBuffer())
|
|
@@ -760,7 +777,7 @@ try {
|
|
|
760
777
|
predecessorFound = true
|
|
761
778
|
}
|
|
762
779
|
for (const descriptor of packages) {
|
|
763
|
-
const manifest = JSON.parse(await readFile(join(descriptor.directory, 'package.json'), 'utf8'))
|
|
780
|
+
const manifest = JSON.parse(await readFile(join(descriptor.directory, 'package.json'), 'utf8'))
|
|
764
781
|
if (fixedArtifactRoot === undefined) {
|
|
765
782
|
await run('npm', ['pack', '--pack-destination', artifactRoot, descriptor.directory], { cwd: root })
|
|
766
783
|
}
|
|
@@ -787,13 +804,13 @@ try {
|
|
|
787
804
|
})
|
|
788
805
|
}
|
|
789
806
|
|
|
790
|
-
server = createServer(async (request
|
|
807
|
+
server = createServer(async (request, response) => {
|
|
791
808
|
try {
|
|
792
809
|
const requestUrl = new URL(request.url ?? '/', 'http://127.0.0.1')
|
|
793
810
|
const decodedPath = decodeURIComponent(requestUrl.pathname)
|
|
794
811
|
for (const [name, descriptor] of registryPackages) {
|
|
795
812
|
if (decodedPath === `/${name}`) {
|
|
796
|
-
const address = server
|
|
813
|
+
const address = server.address()
|
|
797
814
|
if (address === null || typeof address === 'string') throw new Error('registry has no TCP address')
|
|
798
815
|
const tarballUrl = `http://127.0.0.1:${address.port}/${name}/-/${descriptor.filename}`
|
|
799
816
|
const version = {
|
|
@@ -850,22 +867,22 @@ try {
|
|
|
850
867
|
method: request.method,
|
|
851
868
|
headers: { accept: request.headers.accept ?? '*/*' },
|
|
852
869
|
})
|
|
853
|
-
const headers
|
|
870
|
+
const headers = {}
|
|
854
871
|
for (const name of ['content-type', 'content-length', 'cache-control', 'etag', 'last-modified']) {
|
|
855
872
|
const value = upstream.headers.get(name)
|
|
856
873
|
if (value !== null) headers[name] = value
|
|
857
874
|
}
|
|
858
875
|
response.writeHead(upstream.status, headers)
|
|
859
876
|
if (request.method === 'HEAD' || upstream.body === null) response.end()
|
|
860
|
-
else Readable.fromWeb(upstream.body
|
|
877
|
+
else Readable.fromWeb(upstream.body).pipe(response)
|
|
861
878
|
} catch (error) {
|
|
862
879
|
response.writeHead(502, { 'content-type': 'text/plain' })
|
|
863
880
|
response.end(error instanceof Error ? error.message : String(error))
|
|
864
881
|
}
|
|
865
882
|
})
|
|
866
|
-
await new Promise
|
|
867
|
-
server
|
|
868
|
-
server
|
|
883
|
+
await new Promise((resolve, reject) => {
|
|
884
|
+
server.once('error', reject)
|
|
885
|
+
server.listen(0, '127.0.0.1', resolve)
|
|
869
886
|
})
|
|
870
887
|
const address = server.address()
|
|
871
888
|
if (address === null || typeof address === 'string') throw new Error('registry has no TCP address')
|
|
@@ -876,8 +893,13 @@ try {
|
|
|
876
893
|
version: '0.0.0',
|
|
877
894
|
private: true,
|
|
878
895
|
}, null, 2))
|
|
879
|
-
|
|
896
|
+
// Stop pnpm's workspace walk-up: an ancestor with its own pnpm-workspace.yaml
|
|
897
|
+
// (e.g. a user home project) would otherwise absorb this consumer and land
|
|
898
|
+
// the install — and every resolution — outside the packed consumer.
|
|
899
|
+
await writeFile(join(consumerRoot, 'pnpm-workspace.yaml'), ['packages:', ' - .'].join(String.fromCharCode(10)))
|
|
880
900
|
if (upgradeLeg === 'installed') {
|
|
901
|
+
// Real upgrade path: install the published previous release, then move to
|
|
902
|
+
// the packed candidate through the standard update command.
|
|
881
903
|
await run('pnpm', [
|
|
882
904
|
'add',
|
|
883
905
|
`dsh-context-compression-improved@${previousRelease}`,
|
|
@@ -894,6 +916,7 @@ try {
|
|
|
894
916
|
'dsh-context-compression-improved@latest',
|
|
895
917
|
'--registry', registry,
|
|
896
918
|
], { cwd: consumerRoot })
|
|
919
|
+
// The up command must land the packed candidate version.
|
|
897
920
|
const upgradedSelectorDir = await realpath(join(consumerRoot, 'node_modules/dsh-context-compression-improved'))
|
|
898
921
|
const upgradedSelector = JSON.parse(await readFile(join(upgradedSelectorDir, 'package.json'), 'utf8'))
|
|
899
922
|
const candidateVersion = registryPackages.get('dsh-context-compression-improved')?.manifest.version
|
|
@@ -917,22 +940,24 @@ try {
|
|
|
917
940
|
throw new Error('packed package is not guarded by publishConfig.tag=latest')
|
|
918
941
|
}
|
|
919
942
|
for (const peer of ['@deepseek-ai/dsh-command-compact', '@deepseek-ai/dsh-compaction-basic']) {
|
|
920
|
-
if (selector.peerDependencies?.[peer] !== '>=0.1.
|
|
943
|
+
if (selector.peerDependencies?.[peer] !== '>=0.1.5-rc.2 <0.2.0-0') {
|
|
921
944
|
throw new Error(`packed selector has an invalid ${peer} peer range`)
|
|
922
945
|
}
|
|
923
946
|
}
|
|
924
|
-
const
|
|
925
|
-
|
|
926
|
-
|
|
947
|
+
for (const directory of [selectorDir]) {
|
|
948
|
+
const notice = await readFile(join(directory, 'THIRD_PARTY_NOTICES.md'), 'utf8')
|
|
949
|
+
if (!notice.includes('Copyright (c) 2026 DeepSeek')) {
|
|
950
|
+
throw new Error(`${basename(directory)} lacks the full DeepSeek Harness MIT notice`)
|
|
951
|
+
}
|
|
952
|
+
await scanPublishedTree(directory)
|
|
927
953
|
}
|
|
928
|
-
await scanPublishedTree(selectorDir)
|
|
929
954
|
|
|
930
|
-
const productionLicenses
|
|
955
|
+
const productionLicenses = Object.fromEntries(await Promise.all([
|
|
931
956
|
['dsh-context-compression-improved', selectorDir, 'MIT'],
|
|
932
957
|
['@huggingface/tokenizers', join(dirname(selectorDir), '@huggingface/tokenizers'), 'Apache-2.0'],
|
|
933
958
|
['js-yaml', join(dirname(selectorDir), 'js-yaml'), 'MIT'],
|
|
934
959
|
].map(async ([name, directory, expected]) => {
|
|
935
|
-
const resolved = await realpath(directory
|
|
960
|
+
const resolved = await realpath(directory)
|
|
936
961
|
const manifest = JSON.parse(await readFile(join(resolved, 'package.json'), 'utf8'))
|
|
937
962
|
if (manifest.name !== name) throw new Error(`expected ${name}, found ${String(manifest.name)}`)
|
|
938
963
|
if (manifest.license !== expected) {
|
|
@@ -948,7 +973,7 @@ try {
|
|
|
948
973
|
}
|
|
949
974
|
productionLicenses.argparse = argparse.license
|
|
950
975
|
|
|
951
|
-
const packedTokenizerArtifacts
|
|
976
|
+
const packedTokenizerArtifacts = {}
|
|
952
977
|
for (const artifactDir of ['deepseek-v4', 'deepseek-v4-vision-exp']) {
|
|
953
978
|
const manifest = JSON.parse(await readFile(join(selectorDir, 'assets', artifactDir, 'manifest.json'), 'utf8'))
|
|
954
979
|
const tokenizerBytes = await readFile(join(selectorDir, 'assets', artifactDir, 'tokenizer.json'))
|
|
@@ -964,9 +989,8 @@ try {
|
|
|
964
989
|
if (!client.startsWith('window.__ModuleLoader__.load({')) throw new Error('packed client is not lazy-CJS')
|
|
965
990
|
if (/(?:\/home\/|[A-Za-z]:\\Users\\)/u.test(client)) throw new Error('packed client leaks a developer path')
|
|
966
991
|
const hostSmoke = await runPackedHostSmoke(consumerRoot)
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
await copyFile(join(root, 'scripts-dist/packed-components-smoke.js'), installedComponentsScript)
|
|
992
|
+
const installedComponentsScript = join(consumerRoot, 'packed-components-smoke.mjs')
|
|
993
|
+
await copyFile(join(root, 'scripts/packed-components-smoke.mjs'), installedComponentsScript)
|
|
970
994
|
const installedComponentsOutput = await capture(process.execPath, [installedComponentsScript], {
|
|
971
995
|
cwd: consumerRoot,
|
|
972
996
|
})
|
|
@@ -1002,7 +1026,7 @@ try {
|
|
|
1002
1026
|
&& packedVisionSmoke.imageSession.exactRewriteIneligible === true
|
|
1003
1027
|
&& packedVisionSmoke.imageSession.originalIntact === true,
|
|
1004
1028
|
'installed vision smoke did not prove estimate propagation and exact-only safety')
|
|
1005
|
-
let officialCloneSmoke
|
|
1029
|
+
let officialCloneSmoke = null
|
|
1006
1030
|
const candidateVersion = registryPackages.get('dsh-context-compression-improved')?.manifest.version
|
|
1007
1031
|
if (candidateVersion === undefined) throw new Error('packed candidate version is unknown')
|
|
1008
1032
|
const previousForLifecycle = predecessorFound ? previousRelease : undefined
|
|
@@ -1019,10 +1043,13 @@ try {
|
|
|
1019
1043
|
candidateVersion,
|
|
1020
1044
|
)
|
|
1021
1045
|
} else {
|
|
1046
|
+
// Auto-provision a clean official checkout so the standard add → up →
|
|
1047
|
+
// remove lifecycle runs without manual setup. Release mode fails closed;
|
|
1048
|
+
// only dev mode may skip with an explicit marker.
|
|
1022
1049
|
const cloneRoot = join(artifactRoot, 'official-clone')
|
|
1023
1050
|
try {
|
|
1024
1051
|
await run('git', [
|
|
1025
|
-
'clone', '--depth', '1', '--branch', 'dsh-v0.1.
|
|
1052
|
+
'clone', '--depth', '1', '--branch', 'dsh-v0.1.5-rc.2',
|
|
1026
1053
|
'https://github.com/deepseek-ai/deepseek-harness.git', cloneRoot,
|
|
1027
1054
|
])
|
|
1028
1055
|
officialCloneSmoke = await runOfficialCloneCliSmoke(
|
|
@@ -1050,7 +1077,8 @@ try {
|
|
|
1050
1077
|
upgradeLeg,
|
|
1051
1078
|
artifactSource: fixedArtifactRoot === undefined ? 'fresh-pack' : artifactRoot,
|
|
1052
1079
|
workspaceRebuildMatched: fixedArtifactRoot === undefined ? null : true,
|
|
1053
|
-
|
|
1080
|
+
selector: selector.version,
|
|
1081
|
+
prunerEntry: 'dsh-context-compression-improved/pruner',
|
|
1054
1082
|
productionLicenses,
|
|
1055
1083
|
tokenizerArtifacts: packedTokenizerArtifacts,
|
|
1056
1084
|
tokenizerBytes: tokenizer.length,
|
|
@@ -1065,7 +1093,7 @@ try {
|
|
|
1065
1093
|
}])),
|
|
1066
1094
|
}, null, 2))
|
|
1067
1095
|
} finally {
|
|
1068
|
-
if (server !== undefined) await new Promise
|
|
1096
|
+
if (server !== undefined) await new Promise(resolve => server.close(resolve))
|
|
1069
1097
|
if (comparisonRoot !== undefined) await rm(comparisonRoot, { recursive: true, force: true })
|
|
1070
1098
|
if (ownsArtifactRoot) await rm(artifactRoot, { recursive: true, force: true })
|
|
1071
1099
|
await rm(consumerRoot, { recursive: true, force: true })
|