@aiwayds/dsh-dcp 0.6.0 → 0.8.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/README.en.md +24 -0
- package/README.md +24 -0
- package/cordis.patch.yml +1 -0
- package/lib/setup.js +67 -1
- package/package.json +44 -30
- package/scripts/setup.mjs +42 -5
- package/scripts/smoke-boot.mjs +55 -20
package/README.en.md
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
Deterministic context-compaction backend for dsh (DeepSeek Harness): **context
|
|
4
4
|
compaction without an LLM call**, works out of the box.
|
|
5
5
|
|
|
6
|
+
**Requires dsh >= 0.1.2-rc.1** — this plugin targets the dsh RC/stable line only (CI and releases resolve the newest of the `latest`/`next` dist-tags at runtime). **The alpha line is no longer supported.**
|
|
7
|
+
|
|
6
8
|
> [简体中文](README.md) · **English**
|
|
7
9
|
|
|
8
10
|
## Why
|
|
@@ -103,6 +105,28 @@ npx dsh-dcp-setup # safe: date-stamped backup → append-only
|
|
|
103
105
|
> mount it. The web profile does not bundle the TUI, so it keeps the official
|
|
104
106
|
> backend and is unaffected.
|
|
105
107
|
|
|
108
|
+
## Uninstall
|
|
109
|
+
|
|
110
|
+
**Bundle path** (`dsh plugin add`, or listed under `bundles`):
|
|
111
|
+
|
|
112
|
+
```sh
|
|
113
|
+
dsh plugin --profile <name> remove @aiwayds/dsh-dcp
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
The host reconciles automatically: the bundles entry is spliced, the patch layer drops with the package, and the stock LLM summarizer (`compaction-basic`) re-enables.
|
|
117
|
+
|
|
118
|
+
**Setup-script path** (the patch block `npx dsh-dcp-setup` wrote):
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
npx dsh-dcp-setup --remove # default home patch
|
|
122
|
+
npx dsh-dcp-setup --remove --profile tui # a named profile
|
|
123
|
+
npx dsh-dcp-setup --remove /path/to/cordis.patch.yml
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`--remove` strips only the setup-written mount block (including any config you tuned), with the same date-stamped backup before modifying; hand-written mounts are left alone; the file is deleted when nothing else remains. Two WARN lines matter: a leftover `compaction-basic` disable entry keeps the stock summarizer off (remove it by hand if it was only for dsh-dcp), and another dsh-dcp mount in the same file is untouched.
|
|
127
|
+
|
|
128
|
+
Removing the package without the reverse step leaves the mount pointing at the vanished absolute entry path, and the profile boot fails with module-not-found.
|
|
129
|
+
|
|
106
130
|
## /dcp command
|
|
107
131
|
|
|
108
132
|
| Command | Effect |
|
package/README.md
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
dsh(DeepSeek Harness)的确定性压缩后端:**上下文压缩不调 LLM**,开箱即用。
|
|
4
4
|
|
|
5
|
+
**要求 dsh >= 0.1.2-rc.1** — 本插件只跟随 dsh RC/stable 线(CI 与发版在运行时解析 latest/next 中更新的 dist-tag)。**不再支持 alpha 线。**
|
|
6
|
+
|
|
5
7
|
> **简体中文** · [English](README.en.md)
|
|
6
8
|
|
|
7
9
|
## 为什么做
|
|
@@ -82,6 +84,28 @@ npx dsh-dcp-setup # 安全脚本:带日期备份 → 只追
|
|
|
82
84
|
|
|
83
85
|
> dsh-dcp 挂在 dsh 的压缩接口上,只对挂载了它的 profile 生效。web profile 没挂 tui,继续用官方压缩,不受影响。
|
|
84
86
|
|
|
87
|
+
## 卸载
|
|
88
|
+
|
|
89
|
+
**Bundle 方式**(`dsh plugin add` 或列在 `bundles`):
|
|
90
|
+
|
|
91
|
+
```sh
|
|
92
|
+
dsh plugin --profile <name> remove @aiwayds/dsh-dcp
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
宿主自动收敛:bundles 条目移除、patch 层随包消失,官方 LLM 压缩后端 `compaction-basic` 自动恢复。
|
|
96
|
+
|
|
97
|
+
**setup 脚本方式**(`npx dsh-dcp-setup` 写入的 patch 块):
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
npx dsh-dcp-setup --remove # 默认 home patch
|
|
101
|
+
npx dsh-dcp-setup --remove --profile tui # 指定 profile
|
|
102
|
+
npx dsh-dcp-setup --remove /path/to/cordis.patch.yml
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`--remove` 只删除 setup 写入的挂载块(含你调过的 config),修改前同样带日期备份;手工写的挂载块不受影响;删完文件为空时自动删除文件。两条 WARN 要留意:文件里若还留有 `compaction-basic` 的 disable 条目,官方压缩后端会保持关闭 —— 不是给 dsh-dcp 用的就手动删掉;同一文件里另一处 dsh-dcp 挂载不会被碰。
|
|
106
|
+
|
|
107
|
+
不执行反向步骤直接卸包,patch 里的绝对路径会指向已消失的安装目录,profile 启动将以 module-not-found 失败。
|
|
108
|
+
|
|
85
109
|
## /dcp 命令
|
|
86
110
|
|
|
87
111
|
| 命令 | 作用 |
|
package/cordis.patch.yml
CHANGED
package/lib/setup.js
CHANGED
|
@@ -11,8 +11,12 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { readdirSync, readFileSync } from 'node:fs'
|
|
13
13
|
|
|
14
|
-
/** The patch entry that disables the default LLM summarizer.
|
|
14
|
+
/** The patch entry that disables the default LLM summarizer. The `name` is
|
|
15
|
+
* a guard, not an override: if a future host renames or drops the row, the
|
|
16
|
+
* entry is skipped with a loader warning instead of silently disabling an
|
|
17
|
+
* unrelated component (the same pattern dsh-tui-pi's patch uses). */
|
|
15
18
|
const DISABLE_ENTRY = `- id: compaction-basic
|
|
19
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
16
20
|
disabled: true`
|
|
17
21
|
|
|
18
22
|
/** The dsh-dcp mount block, one YAML list item, appended to the file. */
|
|
@@ -93,3 +97,65 @@ export function backupStamp(date = new Date()) {
|
|
|
93
97
|
const p = (n) => String(n).padStart(2, '0')
|
|
94
98
|
return `${date.getFullYear()}${p(date.getMonth() + 1)}${p(date.getDate())}-${p(date.getHours())}${p(date.getMinutes())}`
|
|
95
99
|
}
|
|
100
|
+
|
|
101
|
+
/** The marker comment every setup-written mount block starts with. */
|
|
102
|
+
const MOUNT_MARKER = 'added by @aiwayds/dsh-dcp setup'
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Reverse of planPatch/mountBlock: remove the setup-appended dsh-dcp mount
|
|
106
|
+
* block — the marker comment, the disable entry it added, and the insert
|
|
107
|
+
* item including any config the user tuned inside it — from one patch
|
|
108
|
+
* file's text. Hand-written mounts without the marker are NOT touched (the
|
|
109
|
+
* human wrote it; the human removes it); user entries after the block are
|
|
110
|
+
* preserved byte-for-byte.
|
|
111
|
+
*
|
|
112
|
+
* Uninstalling the package without this reverse step leaves the mount
|
|
113
|
+
* pointing at the vanished absolute entry path, which fails the profile's
|
|
114
|
+
* whole boot with a module-not-found error.
|
|
115
|
+
*
|
|
116
|
+
* @param {string|undefined} text - the patch file content.
|
|
117
|
+
* @returns {{ removed: boolean, text: string, note?: string }} `text` with
|
|
118
|
+
* the block gone ('' when nothing else remains — the caller may delete the
|
|
119
|
+
* file); `removed: false` leaves `text` untouched and sets `note`.
|
|
120
|
+
*/
|
|
121
|
+
export function planRemoval(text) {
|
|
122
|
+
if (!text) return { removed: false, text: text ?? '' }
|
|
123
|
+
const lines = text.split('\n')
|
|
124
|
+
const marker = lines.findIndex((line) => line.includes(MOUNT_MARKER))
|
|
125
|
+
if (marker === -1) {
|
|
126
|
+
return { removed: false, text, note: 'no setup-written dsh-dcp mount block found (hand-written mounts are left alone)' }
|
|
127
|
+
}
|
|
128
|
+
let i = marker + 1
|
|
129
|
+
let end = lines.length
|
|
130
|
+
let sawInsert = false
|
|
131
|
+
while (i < lines.length) {
|
|
132
|
+
const line = lines[i]
|
|
133
|
+
if (line === '') { i++; continue }
|
|
134
|
+
if (line.startsWith('- ')) {
|
|
135
|
+
// One top-level list item: the entry line plus its indented body (and
|
|
136
|
+
// any blank lines inside it the user added while tuning config). The
|
|
137
|
+
// split artifact after a final newline is EOF, not a blank to consume.
|
|
138
|
+
let j = i + 1
|
|
139
|
+
while (j < lines.length && (lines[j] === '' ? j < lines.length - 1 : lines[j].startsWith(' ') || lines[j].startsWith('\t'))) j++
|
|
140
|
+
if (line.startsWith('- insert')) {
|
|
141
|
+
sawInsert = true
|
|
142
|
+
end = j
|
|
143
|
+
break
|
|
144
|
+
}
|
|
145
|
+
i = j
|
|
146
|
+
continue
|
|
147
|
+
}
|
|
148
|
+
// Anything else at column 0 (a user comment or entry) ends our block.
|
|
149
|
+
end = i
|
|
150
|
+
break
|
|
151
|
+
}
|
|
152
|
+
if (!sawInsert) {
|
|
153
|
+
return { removed: false, text, note: 'the setup marker was found but the dsh-dcp insert item is missing — leaving the file untouched' }
|
|
154
|
+
}
|
|
155
|
+
let start = marker
|
|
156
|
+
// The block was appended with one leading blank line; take it so repeated
|
|
157
|
+
// mount/unmount cycles do not accumulate blank runs.
|
|
158
|
+
if (start > 0 && lines[start - 1].trim() === '') start -= 1
|
|
159
|
+
const remaining = [...lines.slice(0, start), ...lines.slice(end)].join('\n')
|
|
160
|
+
return { removed: true, text: remaining.trim() === '' ? '' : remaining }
|
|
161
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aiwayds/dsh-dcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Deterministic context-pruning compaction backend for dsh (DeepSeek Harness) — zero-LLM summaries, /dcp command, works out of the box. Design references Opencode-DCP/opencode-dynamic-context-pruning.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
@@ -52,16 +52,16 @@
|
|
|
52
52
|
"dependencies": {},
|
|
53
53
|
"peerDependencies": {
|
|
54
54
|
"@deepseek-ai/cordis": "^4.0.2",
|
|
55
|
-
"@deepseek-ai/dsh-agent": ">=0.1.2-
|
|
56
|
-
"@deepseek-ai/dsh-brand": ">=0.1.2-
|
|
57
|
-
"@deepseek-ai/dsh-commands": ">=0.1.2-
|
|
58
|
-
"@deepseek-ai/dsh-compaction": ">=0.1.2-
|
|
59
|
-
"@deepseek-ai/dsh-compaction-basic": ">=0.1.2-
|
|
60
|
-
"@deepseek-ai/dsh-compaction-tool-result-pruner": ">=0.1.2-
|
|
61
|
-
"@deepseek-ai/dsh-invariants": ">=0.1.2-
|
|
62
|
-
"@deepseek-ai/dsh-llm": ">=0.1.2-
|
|
63
|
-
"@deepseek-ai/dsh-session": ">=0.1.2-
|
|
64
|
-
"@deepseek-ai/dsh-token-meter": ">=0.1.2-
|
|
55
|
+
"@deepseek-ai/dsh-agent": ">=0.1.2-rc.1",
|
|
56
|
+
"@deepseek-ai/dsh-brand": ">=0.1.2-rc.1",
|
|
57
|
+
"@deepseek-ai/dsh-commands": ">=0.1.2-rc.1",
|
|
58
|
+
"@deepseek-ai/dsh-compaction": ">=0.1.2-rc.1",
|
|
59
|
+
"@deepseek-ai/dsh-compaction-basic": ">=0.1.2-rc.1",
|
|
60
|
+
"@deepseek-ai/dsh-compaction-tool-result-pruner": ">=0.1.2-rc.1",
|
|
61
|
+
"@deepseek-ai/dsh-invariants": ">=0.1.2-rc.1",
|
|
62
|
+
"@deepseek-ai/dsh-llm": ">=0.1.2-rc.1",
|
|
63
|
+
"@deepseek-ai/dsh-session": ">=0.1.2-rc.1",
|
|
64
|
+
"@deepseek-ai/dsh-token-meter": ">=0.1.2-rc.1",
|
|
65
65
|
"@deepseek-ai/schemastery": "^3.18.2"
|
|
66
66
|
},
|
|
67
67
|
"peerDependenciesMeta": {
|
|
@@ -105,33 +105,47 @@
|
|
|
105
105
|
"devDependencies": {
|
|
106
106
|
"@types/node": "^24.0.0",
|
|
107
107
|
"@deepseek-ai/cordis": "4.0.2",
|
|
108
|
-
"@deepseek-ai/dsh-agent": "0.1.2-
|
|
109
|
-
"@deepseek-ai/dsh-brand": "0.1.2-
|
|
110
|
-
"@deepseek-ai/dsh-commands": "0.1.2-
|
|
111
|
-
"@deepseek-ai/dsh-compaction": "0.1.2-
|
|
112
|
-
"@deepseek-ai/dsh-compaction-basic": "0.1.2-
|
|
113
|
-
"@deepseek-ai/dsh-compaction-tool-result-pruner": "0.1.2-
|
|
114
|
-
"@deepseek-ai/dsh-invariants": "0.1.2-
|
|
115
|
-
"@deepseek-ai/dsh-llm": "0.1.2-
|
|
116
|
-
"@deepseek-ai/dsh-session": "0.1.2-
|
|
117
|
-
"@deepseek-ai/dsh-token-meter": "0.1.2-
|
|
108
|
+
"@deepseek-ai/dsh-agent": "0.1.2-rc.1",
|
|
109
|
+
"@deepseek-ai/dsh-brand": "0.1.2-rc.1",
|
|
110
|
+
"@deepseek-ai/dsh-commands": "0.1.2-rc.1",
|
|
111
|
+
"@deepseek-ai/dsh-compaction": "0.1.2-rc.1",
|
|
112
|
+
"@deepseek-ai/dsh-compaction-basic": "0.1.2-rc.1",
|
|
113
|
+
"@deepseek-ai/dsh-compaction-tool-result-pruner": "0.1.2-rc.1",
|
|
114
|
+
"@deepseek-ai/dsh-invariants": "0.1.2-rc.1",
|
|
115
|
+
"@deepseek-ai/dsh-llm": "0.1.2-rc.1",
|
|
116
|
+
"@deepseek-ai/dsh-session": "0.1.2-rc.1",
|
|
117
|
+
"@deepseek-ai/dsh-token-meter": "0.1.2-rc.1",
|
|
118
118
|
"@deepseek-ai/schemastery": "3.18.2",
|
|
119
119
|
"typescript": "~5.9.0"
|
|
120
120
|
},
|
|
121
121
|
"overrides": {
|
|
122
|
-
"@deepseek-ai/dsh-agent": "0.1.2-
|
|
123
|
-
"@deepseek-ai/dsh-brand": "0.1.2-
|
|
124
|
-
"@deepseek-ai/dsh-commands": "0.1.2-
|
|
125
|
-
"@deepseek-ai/dsh-compaction": "0.1.2-
|
|
126
|
-
"@deepseek-ai/dsh-compaction-tool-result-pruner": "0.1.2-
|
|
127
|
-
"@deepseek-ai/dsh-invariants": "0.1.2-
|
|
128
|
-
"@deepseek-ai/dsh-llm": "0.1.2-
|
|
129
|
-
"@deepseek-ai/dsh-session": "0.1.2-
|
|
130
|
-
"@deepseek-ai/dsh-token-meter": "0.1.2-
|
|
122
|
+
"@deepseek-ai/dsh-agent": "0.1.2-rc.1",
|
|
123
|
+
"@deepseek-ai/dsh-brand": "0.1.2-rc.1",
|
|
124
|
+
"@deepseek-ai/dsh-commands": "0.1.2-rc.1",
|
|
125
|
+
"@deepseek-ai/dsh-compaction": "0.1.2-rc.1",
|
|
126
|
+
"@deepseek-ai/dsh-compaction-tool-result-pruner": "0.1.2-rc.1",
|
|
127
|
+
"@deepseek-ai/dsh-invariants": "0.1.2-rc.1",
|
|
128
|
+
"@deepseek-ai/dsh-llm": "0.1.2-rc.1",
|
|
129
|
+
"@deepseek-ai/dsh-session": "0.1.2-rc.1",
|
|
130
|
+
"@deepseek-ai/dsh-token-meter": "0.1.2-rc.1",
|
|
131
|
+
"@deepseek-ai/dsh-scope": "0.1.2-rc.1",
|
|
132
|
+
"@deepseek-ai/dsh-attachment": "0.1.2-rc.1",
|
|
133
|
+
"@deepseek-ai/dsh-session-projection": "0.1.2-rc.1",
|
|
134
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.2-rc.1",
|
|
135
|
+
"@deepseek-ai/dsh-llm-retry": "0.1.2-rc.1"
|
|
131
136
|
},
|
|
132
137
|
"dsh": {
|
|
133
138
|
"bundle": {
|
|
134
139
|
"patch": "./cordis.patch.yml"
|
|
135
140
|
}
|
|
141
|
+
},
|
|
142
|
+
"pnpm": {
|
|
143
|
+
"overrides": {
|
|
144
|
+
"@deepseek-ai/dsh-scope": "0.1.2-rc.1",
|
|
145
|
+
"@deepseek-ai/dsh-attachment": "0.1.2-rc.1",
|
|
146
|
+
"@deepseek-ai/dsh-session-projection": "0.1.2-rc.1",
|
|
147
|
+
"@deepseek-ai/dsh-system-prompt": "0.1.2-rc.1",
|
|
148
|
+
"@deepseek-ai/dsh-llm-retry": "0.1.2-rc.1"
|
|
149
|
+
}
|
|
136
150
|
}
|
|
137
151
|
}
|
package/scripts/setup.mjs
CHANGED
|
@@ -20,11 +20,13 @@
|
|
|
20
20
|
* node scripts/setup.mjs # home patch
|
|
21
21
|
* node scripts/setup.mjs --profile tui # tui profile's patch
|
|
22
22
|
* node scripts/setup.mjs /path/to/cordis.patch.yml
|
|
23
|
+
* node scripts/setup.mjs --remove [...] # reverse: remove the setup-written
|
|
24
|
+
* # mount block (same target forms)
|
|
23
25
|
*/
|
|
24
26
|
import fs from 'node:fs'
|
|
25
27
|
import path from 'node:path'
|
|
26
28
|
import { fileURLToPath } from 'node:url'
|
|
27
|
-
import { planPatch, backupStamp, findBundledProfiles } from '../lib/setup.js'
|
|
29
|
+
import { planPatch, planRemoval, hasEntry, isMounted, backupStamp, findBundledProfiles } from '../lib/setup.js'
|
|
28
30
|
|
|
29
31
|
const PKG = '@aiwayds/dsh-dcp'
|
|
30
32
|
const pkgRoot = path.dirname(path.dirname(fileURLToPath(import.meta.url)))
|
|
@@ -36,18 +38,53 @@ function home() {
|
|
|
36
38
|
|
|
37
39
|
function parseArgs(argv) {
|
|
38
40
|
const force = argv.includes('--force')
|
|
41
|
+
const remove = argv.includes('--remove')
|
|
39
42
|
if (argv.includes('--profile')) {
|
|
40
43
|
const index = argv.indexOf('--profile')
|
|
41
44
|
const name = argv[index + 1]
|
|
42
45
|
if (!name) throw new Error('setup: --profile requires a profile name')
|
|
43
|
-
return { target: path.join(home(), 'profiles', name, 'cordis.patch.yml'), kind: 'profile', profileName: name, force }
|
|
46
|
+
return { target: path.join(home(), 'profiles', name, 'cordis.patch.yml'), kind: 'profile', profileName: name, force, remove }
|
|
44
47
|
}
|
|
45
48
|
const explicit = argv.find((a) => !a.startsWith('-'))
|
|
46
|
-
if (explicit) return { target: path.resolve(explicit), kind: 'explicit', force }
|
|
47
|
-
return { target: path.join(home(), 'cordis.patch.yml'), kind: 'home', force }
|
|
49
|
+
if (explicit) return { target: path.resolve(explicit), kind: 'explicit', force, remove }
|
|
50
|
+
return { target: path.join(home(), 'cordis.patch.yml'), kind: 'home', force, remove }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const { target, kind, profileName, force, remove } = parseArgs(process.argv.slice(2))
|
|
54
|
+
|
|
55
|
+
if (remove) {
|
|
56
|
+
const existed = fs.existsSync(target)
|
|
57
|
+
const text = existed ? fs.readFileSync(target, 'utf8') : undefined
|
|
58
|
+
if (!existed) {
|
|
59
|
+
console.log(`no ${target} — nothing to remove.`)
|
|
60
|
+
process.exit(0)
|
|
61
|
+
}
|
|
62
|
+
const plan = planRemoval(text)
|
|
63
|
+
if (!plan.removed) {
|
|
64
|
+
console.log(`dsh-dcp mount not removed from ${target}.`)
|
|
65
|
+
if (plan.note) console.log(`note: ${plan.note}`)
|
|
66
|
+
process.exit(0)
|
|
67
|
+
}
|
|
68
|
+
const backup = `${target}.bak.${backupStamp()}`
|
|
69
|
+
fs.copyFileSync(target, backup)
|
|
70
|
+
console.log(`backup: ${backup}`)
|
|
71
|
+
if (plan.text === '') {
|
|
72
|
+
fs.rmSync(target)
|
|
73
|
+
console.log(`removed ${target} (the file held nothing else)`)
|
|
74
|
+
} else {
|
|
75
|
+
fs.writeFileSync(target, plan.text)
|
|
76
|
+
console.log(`removed the dsh-dcp mount block from ${target}`)
|
|
77
|
+
}
|
|
78
|
+
if (plan.text !== '' && hasEntry(plan.text, 'compaction-basic')) {
|
|
79
|
+
console.warn('WARN: a compaction-basic entry remains in this patch file — the stock LLM summarizer stays disabled; remove that entry by hand if it was only for dsh-dcp.')
|
|
80
|
+
}
|
|
81
|
+
if (plan.text !== '' && isMounted(plan.text)) {
|
|
82
|
+
console.warn('WARN: another dsh-dcp mount remains in this patch file — leaving it alone.')
|
|
83
|
+
}
|
|
84
|
+
console.log('restart dsh to apply.')
|
|
85
|
+
process.exit(0)
|
|
48
86
|
}
|
|
49
87
|
|
|
50
|
-
const { target, kind, profileName, force } = parseArgs(process.argv.slice(2))
|
|
51
88
|
const existed = fs.existsSync(target)
|
|
52
89
|
const text = existed ? fs.readFileSync(target, 'utf8') : undefined
|
|
53
90
|
if (!existed) console.log(`no ${target} — will generate a fresh patch file`)
|
package/scripts/smoke-boot.mjs
CHANGED
|
@@ -12,11 +12,16 @@
|
|
|
12
12
|
// as a file: dependency, and a cordis.patch.yml carrying the
|
|
13
13
|
// disable-compaction-basic + insert-dsh-dcp mount block
|
|
14
14
|
// 3. pnpm install in the profile
|
|
15
|
-
// 4.
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
15
|
+
// 4. Phase A: setup.mjs mounts the profile patch (marker block), the
|
|
16
|
+
// composed tree carries dsh-dcp with compaction-basic disabled, then
|
|
17
|
+
// `setup.mjs --remove` strips it and the recomposed tree is back to
|
|
18
|
+
// stock (this is what breaks a profile when the package is removed
|
|
19
|
+
// without the reverse step)
|
|
20
|
+
// 5. Phase B: a hand-written mount pointing into the installed tarball —
|
|
21
|
+
// the true user shape — composes, and a real boot under a timeout must
|
|
22
|
+
// load the plugin tree without a loader error (a healthy boot is silent
|
|
23
|
+
// and survives to the kill signal; a broken plugin dies within ~1s with
|
|
24
|
+
// the loader error)
|
|
20
25
|
//
|
|
21
26
|
// The dsh CLI comes from $DSH_BIN if set (e.g. a scratch alpha closure:
|
|
22
27
|
// DSH_BIN=~/tmp/dsh-alpha-closure/node_modules/@deepseek-ai/dsh/lib/bin.js),
|
|
@@ -57,18 +62,9 @@ if (pack.status !== 0 || pack.error) fail('npm pack failed', `${pack.stdout}\n${
|
|
|
57
62
|
const tarball = path.join(work, pack.stdout.trim().split('\n').at(-1) ?? '')
|
|
58
63
|
|
|
59
64
|
writeFileSync(path.join(profile, 'cordis.yml'), '# dsh profile root — empty; the tree is composed from the bundle patches\n[]\n')
|
|
60
|
-
// The
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
- id: compaction-basic
|
|
64
|
-
disabled: true
|
|
65
|
-
- insert:
|
|
66
|
-
- id: ${entryId}
|
|
67
|
-
name: ${path.join(profile, 'node_modules', '@aiwayds', 'dsh-dcp', 'lib', 'index.js')}
|
|
68
|
-
config:
|
|
69
|
-
thresholdRatio: 0.7
|
|
70
|
-
language: zh
|
|
71
|
-
`)
|
|
65
|
+
// The patch file starts EMPTY: Phase A below drives the full setup.mjs
|
|
66
|
+
// mount → remove lifecycle against it, then Phase B hand-writes the mount
|
|
67
|
+
// (isomorphic with setup.mjs's output) for the real-boot proof.
|
|
72
68
|
writeFileSync(path.join(profile, 'pnpm-workspace.yaml'), 'packages:\n - .\n\nnodeLinker: hoisted\nautoInstallPeers: false\n')
|
|
73
69
|
writeFileSync(path.join(profile, 'package.json'), JSON.stringify({
|
|
74
70
|
name: 'dsh-profile-smoke',
|
|
@@ -90,8 +86,47 @@ writeFileSync(path.join(profile, 'package.json'), JSON.stringify({
|
|
|
90
86
|
const install = spawnSync('pnpm', ['install'], { cwd: profile, encoding: 'utf8' })
|
|
91
87
|
if (install.status !== 0 || install.error) fail('pnpm install in the scratch profile failed', `${install.stdout}\n${install.stderr}`)
|
|
92
88
|
|
|
93
|
-
// Phase
|
|
94
|
-
//
|
|
89
|
+
// Phase A — the setup.mjs lifecycle: mount → compose → remove → stock tree.
|
|
90
|
+
// This is the exact pair of commands a standalone user runs (`npx dsh-dcp-setup`
|
|
91
|
+
// and its `--remove`), against the profile patch file they target.
|
|
92
|
+
const patchFile = path.join(profile, 'cordis.patch.yml')
|
|
93
|
+
const setup = spawnSync(process.execPath, [path.join(repoRoot, 'scripts', 'setup.mjs'), patchFile], { encoding: 'utf8' })
|
|
94
|
+
if (setup.status !== 0 || setup.error) fail('setup.mjs mount failed', `${setup.stdout}\n${setup.stderr}`)
|
|
95
|
+
const dumpMounted = dsh(['--profile', 'smoke', '--dump-config'])
|
|
96
|
+
if (dumpMounted.status !== 0 || dumpMounted.error) fail('dsh --dump-config failed on the setup-mounted profile', `${dumpMounted.stdout}\n${dumpMounted.stderr}`)
|
|
97
|
+
if (!dumpMounted.stdout.includes(entryId) || !/compaction-basic\n( name: .*\n)? disabled: true/.test(dumpMounted.stdout)) {
|
|
98
|
+
fail('the setup-mounted tree does not carry dsh-dcp with compaction-basic disabled', dumpMounted.stdout)
|
|
99
|
+
}
|
|
100
|
+
const remove = spawnSync(process.execPath, [path.join(repoRoot, 'scripts', 'setup.mjs'), '--remove', patchFile], { encoding: 'utf8' })
|
|
101
|
+
if (remove.status !== 0 || remove.error) fail('setup.mjs --remove failed', `${remove.stdout}\n${remove.stderr}`)
|
|
102
|
+
const dumpAfter = dsh(['--profile', 'smoke', '--dump-config'])
|
|
103
|
+
if (dumpAfter.status !== 0 || dumpAfter.error) fail('dsh --dump-config failed after removal', `${dumpAfter.stdout}\n${dumpAfter.stderr}`)
|
|
104
|
+
if (dumpAfter.stdout.includes(entryId)) {
|
|
105
|
+
fail(`the composed tree still contains the "${entryId}" entry after removal`, dumpAfter.stdout)
|
|
106
|
+
}
|
|
107
|
+
if (/compaction-basic\n( name: .*\n)? disabled: true/.test(dumpAfter.stdout)) {
|
|
108
|
+
fail('compaction-basic is still disabled after removal — the stock backend did not come back', dumpAfter.stdout)
|
|
109
|
+
}
|
|
110
|
+
if (!dumpAfter.stdout.includes('compaction-basic')) {
|
|
111
|
+
fail('compaction-basic vanished from the composed tree after removal', dumpAfter.stdout)
|
|
112
|
+
}
|
|
113
|
+
console.log('smoke-boot: setup.mjs mount → remove lifecycle restored the stock tree')
|
|
114
|
+
|
|
115
|
+
// Phase B — mount proof: the composed tree must carry the dsh-dcp entry, with
|
|
116
|
+
// compaction-basic disabled rather than removed-by-default. The hand-written
|
|
117
|
+
// mount points INTO the installed tarball (the true user shape), so the boot
|
|
118
|
+
// below exercises the packed artifact.
|
|
119
|
+
writeFileSync(path.join(profile, 'cordis.patch.yml'), `# scratch smoke profile: dsh-dcp mounted the setup.mjs way
|
|
120
|
+
- id: compaction-basic
|
|
121
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
122
|
+
disabled: true
|
|
123
|
+
- insert:
|
|
124
|
+
- id: ${entryId}
|
|
125
|
+
name: ${path.join(profile, 'node_modules', '@aiwayds', 'dsh-dcp', 'lib', 'index.js')}
|
|
126
|
+
config:
|
|
127
|
+
thresholdRatio: 0.7
|
|
128
|
+
language: zh
|
|
129
|
+
`)
|
|
95
130
|
const dump = dsh(['--profile', 'smoke', '--dump-config'])
|
|
96
131
|
if (dump.status !== 0 || dump.error) fail('dsh --dump-config failed on the scratch profile', `${dump.stdout}\n${dump.stderr}`)
|
|
97
132
|
if (!dump.stdout.includes(entryId)) {
|
|
@@ -118,5 +153,5 @@ if (boot.signal !== 'SIGKILL' && boot.status !== 0) {
|
|
|
118
153
|
fail(`dsh exited early with code ${boot.status} and no loader error — unexpected`, output)
|
|
119
154
|
}
|
|
120
155
|
|
|
121
|
-
console.log(`smoke-boot: PASS — ${ownName} mounted via cordis.patch.yml (compaction-basic disabled), composed into the scratch profile tree,
|
|
156
|
+
console.log(`smoke-boot: PASS — ${ownName} mounted via cordis.patch.yml (compaction-basic disabled), composed into the scratch profile tree, booted clean in real dsh (${boot.signal === 'SIGKILL' ? `survived ${bootSeconds}s boot window` : `exited ${boot.status}`}); setup mount → remove lifecycle restored the stock tree`)
|
|
122
157
|
rmSync(work, { recursive: true, force: true })
|