@dsh-workbench/core 1.0.0-beta.0 → 1.0.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.md +18 -15
- package/bin/dsh-workbench +27 -26
- package/client.js +240 -332
- package/dist/client/client.body.js +240 -332
- package/dist/client/index.js +240 -332
- package/dist/host/index.js +1555 -169
- package/dist/types/app/layouts/market/discover-sources-dialog.d.ts +1 -0
- package/dist/types/app/layouts/market/use-discover.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/market-shared-rows.d.ts +0 -1
- package/dist/types/app/layouts/workbench-settings/ConfigSyncRows.d.ts +1 -3
- package/dist/types/kernel/contract/config-sync.d.ts +1 -1
- package/dist/types/kernel/contract/index.d.ts +1 -0
- package/dist/types/kernel/host/mount-units.d.ts +3 -0
- package/dist/types/modules/panels/model/diy-context.d.ts +14 -4
- package/dist/types/modules/panels/model/diy-report.d.ts +2 -0
- package/dist/types/modules/panels/model/diy-tools.d.ts +2 -1
- package/package.json +5 -3
- package/profile/sandbox.patch.yml +7 -5
- package/scripts/clone-profile.mjs +11 -199
- package/scripts/create-workbench.mjs +7 -20
- package/scripts/patch-blocks.mjs +60 -0
- package/scripts/seed-profile.sh +14 -27
- package/scripts/share-data-root.mjs +211 -0
- package/skills/workbench-module-diy/FAMILY.md +4 -6
- package/skills/workbench-module-diy/KIT.md +4 -2
- package/skills/workbench-module-diy/SKILL.md +4 -5
- package/skills/workbench-module-diy/WIRING.md +4 -13
- package/dist/types/shared/api/host/git.test.d.ts +0 -1
- package/profile/isolation.patch.yml +0 -46
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
copyFileSync,
|
|
5
|
+
existsSync,
|
|
6
|
+
mkdirSync,
|
|
7
|
+
readFileSync,
|
|
8
|
+
readdirSync,
|
|
9
|
+
renameSync,
|
|
10
|
+
statSync,
|
|
11
|
+
writeFileSync,
|
|
12
|
+
} from 'node:fs';
|
|
13
|
+
import { homedir } from 'node:os';
|
|
14
|
+
import { dirname, join, resolve } from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { appendBlocks, parseBlocks, removeBlocks } from './patch-blocks.mjs';
|
|
17
|
+
|
|
18
|
+
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
const PKG_ROOT = dirname(SCRIPT_DIR);
|
|
20
|
+
const HOME = process.env.DSH_HOME?.trim() || join(homedir(), '.dsh');
|
|
21
|
+
const PROFILE = process.env.WORKBENCH_PROFILE?.trim() || 'workbench';
|
|
22
|
+
const PROFILE_DIR = join(HOME, 'profiles', PROFILE);
|
|
23
|
+
const RUNTIME_ROOT = process.env.DSH_WORKBENCH_HOME?.trim() || join(HOME, PROFILE);
|
|
24
|
+
const PATCH_PATH = join(PROFILE_DIR, 'cordis.patch.yml');
|
|
25
|
+
const SANDBOX_PATCH = join(PKG_ROOT, 'profile', 'sandbox.patch.yml');
|
|
26
|
+
const MARKER = join(RUNTIME_ROOT, '.data-root-shared');
|
|
27
|
+
|
|
28
|
+
const DATA_ROOT_IDS = new Set(['session-persistence-jsonl', 'storage-json', 'attachment-local']);
|
|
29
|
+
const OWN_ROOT = /DSH_WORKBENCH_HOME|dshHomePath\(\s*['"]workbench/;
|
|
30
|
+
|
|
31
|
+
function say(message) {
|
|
32
|
+
process.stdout.write(`→ ${message}\n`);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function warn(message) {
|
|
36
|
+
process.stderr.write(`dsh-workbench: ${message}\n`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function readText(file) {
|
|
40
|
+
try {
|
|
41
|
+
return readFileSync(file, 'utf8');
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function writeAtomic(file, text) {
|
|
48
|
+
const tmp = `${file}.tmp`;
|
|
49
|
+
writeFileSync(tmp, text, 'utf8');
|
|
50
|
+
renameSync(tmp, file);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function mergeTree(from, to, skip = new Set()) {
|
|
54
|
+
let stat;
|
|
55
|
+
try {
|
|
56
|
+
stat = statSync(from);
|
|
57
|
+
} catch {
|
|
58
|
+
return 0;
|
|
59
|
+
}
|
|
60
|
+
if (!stat.isDirectory()) return 0;
|
|
61
|
+
mkdirSync(to, { recursive: true });
|
|
62
|
+
let count = 0;
|
|
63
|
+
for (const entry of readdirSync(from, { withFileTypes: true })) {
|
|
64
|
+
if (entry.name === '.DS_Store' || skip.has(entry.name)) continue;
|
|
65
|
+
const source = join(from, entry.name);
|
|
66
|
+
const target = join(to, entry.name);
|
|
67
|
+
if (entry.isDirectory()) {
|
|
68
|
+
count += mergeTree(source, target);
|
|
69
|
+
} else if (entry.isFile() && !existsSync(target)) {
|
|
70
|
+
copyFileSync(source, target);
|
|
71
|
+
count += 1;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return count;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function unionList(left, right) {
|
|
78
|
+
return [...new Set([...(Array.isArray(left) ? left : []), ...(Array.isArray(right) ? right : [])])];
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function mergeWorkspaceList(sourceFile, targetFile) {
|
|
82
|
+
let source;
|
|
83
|
+
try {
|
|
84
|
+
source = JSON.parse(readFileSync(sourceFile, 'utf8'));
|
|
85
|
+
} catch {
|
|
86
|
+
return 0;
|
|
87
|
+
}
|
|
88
|
+
if (source?.unit?.name !== 'workspace') return 0;
|
|
89
|
+
if (!existsSync(targetFile)) {
|
|
90
|
+
mkdirSync(dirname(targetFile), { recursive: true });
|
|
91
|
+
copyFileSync(sourceFile, targetFile);
|
|
92
|
+
return 1;
|
|
93
|
+
}
|
|
94
|
+
let target;
|
|
95
|
+
try {
|
|
96
|
+
target = JSON.parse(readFileSync(targetFile, 'utf8'));
|
|
97
|
+
} catch {
|
|
98
|
+
warn(`${targetFile} 读不出来,这次没并项目列表。`);
|
|
99
|
+
return 0;
|
|
100
|
+
}
|
|
101
|
+
if (target?.unit?.name !== 'workspace') return 0;
|
|
102
|
+
const sourceTable = source.tables?.workspaces ?? {};
|
|
103
|
+
const targetTable = target.tables?.workspaces ?? {};
|
|
104
|
+
const byPath = new Map();
|
|
105
|
+
for (const entry of Object.values(targetTable)) {
|
|
106
|
+
if (typeof entry?.path === 'string') byPath.set(entry.path, entry);
|
|
107
|
+
}
|
|
108
|
+
let added = 0;
|
|
109
|
+
for (const [id, entry] of Object.entries(sourceTable)) {
|
|
110
|
+
if (typeof entry?.path !== 'string') continue;
|
|
111
|
+
const hit = byPath.get(entry.path);
|
|
112
|
+
if (hit === undefined) {
|
|
113
|
+
targetTable[id] = entry;
|
|
114
|
+
byPath.set(entry.path, entry);
|
|
115
|
+
added += 1;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
hit.sessionIds = unionList(hit.sessionIds, entry.sessionIds);
|
|
119
|
+
if (typeof entry.updatedAt === 'string' && (typeof hit.updatedAt !== 'string' || entry.updatedAt > hit.updatedAt)) {
|
|
120
|
+
hit.updatedAt = entry.updatedAt;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
target.tables = { ...(target.tables ?? {}), workspaces: targetTable };
|
|
124
|
+
const global = target.global ?? {};
|
|
125
|
+
const sourceGlobal = source.global ?? {};
|
|
126
|
+
const known = new Set(Array.isArray(global.workspaceIds) ? global.workspaceIds : []);
|
|
127
|
+
for (const id of Array.isArray(sourceGlobal.workspaceIds) ? sourceGlobal.workspaceIds : []) known.add(id);
|
|
128
|
+
target.global = {
|
|
129
|
+
...global,
|
|
130
|
+
workspaceIds: [...known],
|
|
131
|
+
archivedSessionIds: unionList(global.archivedSessionIds, sourceGlobal.archivedSessionIds),
|
|
132
|
+
pinnedSessionIds: unionList(global.pinnedSessionIds, sourceGlobal.pinnedSessionIds),
|
|
133
|
+
};
|
|
134
|
+
writeAtomic(targetFile, `${JSON.stringify(target, null, 2)}\n`);
|
|
135
|
+
return added;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function mergeDataRoot() {
|
|
139
|
+
if (resolve(RUNTIME_ROOT) === resolve(HOME)) return;
|
|
140
|
+
if (existsSync(MARKER)) return;
|
|
141
|
+
const sourceStorages = join(RUNTIME_ROOT, 'storages');
|
|
142
|
+
let count = 0;
|
|
143
|
+
try {
|
|
144
|
+
count += mergeWorkspaceList(join(sourceStorages, 'workspace.json'), join(HOME, 'storages', 'workspace.json'));
|
|
145
|
+
count += mergeTree(join(RUNTIME_ROOT, 'sessions'), join(HOME, 'sessions'));
|
|
146
|
+
count += mergeTree(join(RUNTIME_ROOT, 'attachments'), join(HOME, 'attachments'));
|
|
147
|
+
count += mergeTree(sourceStorages, join(HOME, 'storages'), new Set(['workspace.json']));
|
|
148
|
+
} catch (error) {
|
|
149
|
+
warn(`并回 ${HOME} 时出错,下次启动再试:${error instanceof Error ? error.message : String(error)}`);
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
mkdirSync(dirname(MARKER), { recursive: true });
|
|
153
|
+
writeFileSync(
|
|
154
|
+
MARKER,
|
|
155
|
+
`${JSON.stringify({ at: new Date().toISOString(), from: RUNTIME_ROOT, to: HOME }, null, 2)}\n`,
|
|
156
|
+
'utf8',
|
|
157
|
+
);
|
|
158
|
+
if (count > 0) say(`已把 ${RUNTIME_ROOT} 里的会话 / 项目 / 附件并回 ${HOME}(${count} 个文件,旧目录保留)。`);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function stripIsolationHeader(text, blockStart) {
|
|
162
|
+
const lines = text.split('\n');
|
|
163
|
+
let marker = -1;
|
|
164
|
+
for (let i = blockStart - 1; i >= 0; i -= 1) {
|
|
165
|
+
const line = lines[i] ?? '';
|
|
166
|
+
if (/^#.*会话\s*\/\s*存储隔离层/.test(line)) {
|
|
167
|
+
marker = i;
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
if (line.trim() !== '' && !line.startsWith('#')) break;
|
|
171
|
+
}
|
|
172
|
+
if (marker === -1) return text;
|
|
173
|
+
lines.splice(marker, blockStart - marker);
|
|
174
|
+
return lines.join('\n');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const profileManifest = join(PROFILE_DIR, 'package.json');
|
|
178
|
+
if (!existsSync(profileManifest)) process.exit(0);
|
|
179
|
+
|
|
180
|
+
let patch = readText(PATCH_PATH);
|
|
181
|
+
if (patch === null) process.exit(0);
|
|
182
|
+
|
|
183
|
+
const legacy = parseBlocks(patch).filter((block) => DATA_ROOT_IDS.has(block.id));
|
|
184
|
+
const ours = legacy.filter((block) => OWN_ROOT.test(block.body));
|
|
185
|
+
const custom = legacy.filter((block) => !OWN_ROOT.test(block.body));
|
|
186
|
+
if (ours.length > 0) {
|
|
187
|
+
const first = Math.min(...ours.map((block) => block.lineStart));
|
|
188
|
+
patch = stripIsolationHeader(patch, first);
|
|
189
|
+
patch = removeBlocks(patch, (block) => DATA_ROOT_IDS.has(block.id) && OWN_ROOT.test(block.body));
|
|
190
|
+
writeAtomic(PATCH_PATH, patch);
|
|
191
|
+
say(`数据根改回共用:从 ${PROFILE} 的 profile 里去掉 ${ours.map((block) => block.id).join(' / ')}。`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const hasSandbox = parseBlocks(patch).some((block) => block.id === 'sandbox-policy');
|
|
195
|
+
if (!hasSandbox) {
|
|
196
|
+
const sandbox = readText(SANDBOX_PATCH);
|
|
197
|
+
const row = sandbox === null ? undefined : parseBlocks(sandbox).find((block) => block.id === 'sandbox-policy');
|
|
198
|
+
if (row !== undefined) {
|
|
199
|
+
patch = appendBlocks(patch, row.body);
|
|
200
|
+
writeAtomic(PATCH_PATH, patch);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (custom.length > 0) {
|
|
205
|
+
warn(
|
|
206
|
+
`${PROFILE} 的会话 / 存储根是你自己改过的(${custom.map((block) => block.id).join(' / ')})—— 这次不动它,也不并数据。`,
|
|
207
|
+
);
|
|
208
|
+
process.exit(0);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
mergeDataRoot();
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
| 要做什么 | 用 |
|
|
9
9
|
|---|---|
|
|
10
|
-
| 认路、装没装、起不起得来 | `_status`(`unit`
|
|
10
|
+
| 认路、装没装、起不起得来 | `_status`(`unit` 可省);它的「下一步」就是下一件事;带 `unit` 时 text 末尾多一条「发布出口:…」 |
|
|
11
11
|
| 卡片空白 / 崩了 / 读不到 | `_panel` |
|
|
12
12
|
| 找 opencli 命令、看命令形状 | `_recon` → `_spec`(`--help -f json` 原样) |
|
|
13
13
|
| kit 有什么 / 令牌叫什么 | `_components`(`bucket` / `name` 取声明;`scope: tokens` 现读 `--dsw-*` / `--wb-*`) |
|
|
@@ -45,8 +45,6 @@
|
|
|
45
45
|
|
|
46
46
|
## 4 发布
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
需要用户接管时返回 `needsUser: { step, text, command? }` 而不抛异常:把 `text` 说给用户、给命令,
|
|
52
|
-
等「好了」再跑;不向用户要 token,不展示 `package.json`。
|
|
48
|
+
只在用户明确说「帮我发布」后调;先 `_changelog` 把这一版写进 `CHANGELOG.md`。
|
|
49
|
+
出口、署名、标题、安装命令由工具与审批屏处理,不用问、不用补。
|
|
50
|
+
需要用户接管时返回 `needsUser: { step, text, command? }`:把 `text` 说给用户,等「好了」再跑;不向用户要 token。
|
|
@@ -104,12 +104,14 @@ h(Form<{ branch: string }>, {
|
|
|
104
104
|
|
|
105
105
|
```tsx
|
|
106
106
|
h(FormRows, null,
|
|
107
|
-
h(FormRow, { label: t('myunit.mode'), hint: t('myunit.modeHint') },
|
|
108
|
-
h(SelectField, { value, options, onChange: setMode })))
|
|
107
|
+
h(FormRow, { label: t('myunit.mode'), htmlFor: 'myunit-mode', hint: t('myunit.modeHint') },
|
|
108
|
+
h(SelectField, { id: 'myunit-mode', value, options, onChange: setMode })))
|
|
109
109
|
```
|
|
110
110
|
|
|
111
111
|
- 标签归 `FormRow`(默认左置;`layout: 'stacked'` 堆叠)。进了 `FormRow` 就不再给控件 `label` / `hint`。
|
|
112
112
|
`hint` 与 `problem` 二选一,错误优先。
|
|
113
|
+
- 行是 `<div>`,`<label>` 只包标签文字;要点标签聚焦 / 展开控件,给 `FormRow.htmlFor` 与控件的 `id` 同一个值
|
|
114
|
+
(`Form` 内部按 `field.key` 自动接成 `wb-form-<key>`)。不给 `htmlFor` 就只是排布,点标签不聚焦。
|
|
113
115
|
- 错误:位置与外观归 `FormRow.problem` + 控件 `invalid`;校验逻辑留在模块(纯函数 + 文案,交给 `validate`)。
|
|
114
116
|
"是否提交过"与首错聚焦归 `Form`。
|
|
115
117
|
- 选择:候选多 / 需搜索 / 需分组 / 可能读不到 → `SelectField`;候选少 / 要全览 / 多选 → `ChoiceGroup`。
|
|
@@ -45,10 +45,9 @@ description: 在工作台里把当前面板调明白,或者给它造一块新
|
|
|
45
45
|
看 / 动屏前 `workbench_diy_debug_start`(否则整族拒;Desktop 跳过,改用 `read=` / 录制);`_panel` / `_status` / `read=` 档不用。
|
|
46
46
|
工具全表 [FAMILY.md](FAMILY.md)。走通才 `dsh-wb-module design accept`;走不通如实记"未验"。
|
|
47
47
|
审批项标注「要你真用一次」,不打勾。结论只报:已装、在面板哪一格。
|
|
48
|
-
8. **发布** ——
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
不展示 `package.json` / npm / 版本字段,也不要问 token。出口与 `needsUser` 处理见 [WIRING.md](WIRING.md) §5。
|
|
48
|
+
8. **发布** —— 只在用户说「帮我发布」之后:`workbench_diy_changelog`(`note=…`)记这一版 → `workbench_diy_publish`(`unit=…`)。
|
|
49
|
+
不手改 `package.json`、不补标题/署名、不问出口:工具与审批屏已经处理;他明确点名安装包 / 公开 / git 才传 target。
|
|
50
|
+
验收没过先 `design accept`;`needsUser` 照 `text` 转述。细节见 [WIRING.md](WIRING.md) §5。
|
|
52
51
|
|
|
53
52
|
## 索引
|
|
54
53
|
|
|
@@ -79,5 +78,5 @@ description: 在工作台里把当前面板调明白,或者给它造一块新
|
|
|
79
78
|
## 硬约束
|
|
80
79
|
|
|
81
80
|
- dsh 已有的从 `kit` 拿(`@dsh-workbench/core/client`);不手搓 DOM + CSS,不直接 import dsh 的包。
|
|
82
|
-
- DIY 模块默认 `private: true`,不发任何源;用户确认后走 `workbench_diy_publish
|
|
81
|
+
- DIY 模块默认 `private: true`,不发任何源;用户确认后走 `workbench_diy_publish`。
|
|
83
82
|
- 源码零注释(`//`、块注释、`.css` 注释都不许);说明写模块 `README.md` 或 spec。
|
|
@@ -63,16 +63,7 @@ DIY 族仅在选了 DIY 模式的会话出现(按模式,不按工作区)
|
|
|
63
63
|
默认 `private: true`,不发任何源。用户说「帮我发布」时,**不要手改 `package.json`、不要让他看 npm**,
|
|
64
64
|
走 `workbench_diy_publish`(`unit=…`):
|
|
65
65
|
|
|
66
|
-
1.
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
发布审批屏会把这一版的更新日志按 markdown 展示给用户。
|
|
71
|
-
3. 出口只有四种:团队源(配了私源时默认)、公网 npm(必须用户明确说“公开”)、安装包(没私源时默认,
|
|
72
|
-
零凭据)、git(提交构建产物 + tag + push)。只问「给团队用、公开发、还是先给你一个安装包」;
|
|
73
|
-
凭据细节由预检接管。
|
|
74
|
-
4. 工具回 `needsUser` 时,照它那句 `text` 对用户说一句,给能照抄的命令,等他说「好了」再跑;
|
|
75
|
-
**不在对话里要 token / OTP**,不把红字贴给用户。`otp` 交接后用 `workbench_diy_publish` 的 `verify=true`
|
|
76
|
-
(或 `release --verify`)只核不发。
|
|
77
|
-
5. `dsh-wb-module publish` 仍是**发货前清单**(只 dry run);`release --dry-run` 只预览;
|
|
78
|
-
`--verify` 只核已发布的包。公网源必须显式 `--target public` + license,默认只认团队源 / 安装包。
|
|
66
|
+
1. 先把这一版写进 `CHANGELOG.md`(`workbench_diy_changelog`,`note=…`),真卡片验收过(`design accept`)。
|
|
67
|
+
出口、署名、标题、安装命令都由工具与审批屏处理:不用问、不用补;用户没点名出口就不传 target。
|
|
68
|
+
2. 工具回 `needsUser` 时照 `text` 转述,不在对话里要 token / OTP;`otp` 交接后 `verify=true` 只核不发。
|
|
69
|
+
3. 只改市集元数据(标题 / 作者 / 说明)不升版本:`refresh=true`。
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
# workbench profile 的会话/存储隔离层(片段)。
|
|
2
|
-
#
|
|
3
|
-
# 这个文件是"补丁层片段",不是独立补丁文件:seed-profile.sh 会把它
|
|
4
|
-
# 追加进 ~/.dsh/profiles/workbench/cordis.patch.yml。
|
|
5
|
-
#
|
|
6
|
-
# 为什么需要它:dsh-base 把会话与存储的根都挂在 $DSH_HOME 上
|
|
7
|
-
# (dshHomePath('sessions') / dshHomePath('storages')),所以默认情况下
|
|
8
|
-
# 两个 profile 共用同一份会话历史。工作台要提供自己的定制工具,等于一个
|
|
9
|
-
# 独立的 agent 环境,共享会话没有意义 —— 这三行把数据分开。
|
|
10
|
-
#
|
|
11
|
-
# 隔离后(默认 profile;新 profile 由 DSH_WORKBENCH_HOME 指到自己的根):
|
|
12
|
-
# 会话 $DSH_HOME/workbench/sessions/
|
|
13
|
-
# 工作区 $DSH_HOME/workbench/storages/ (workspace 走 storage-domain)
|
|
14
|
-
# 附件 $DSH_HOME/workbench/attachments/v1/
|
|
15
|
-
# 有意不隔离:
|
|
16
|
-
# 凭据 ~/.dsh/.credentials.yaml (两边共用同一把 API key)
|
|
17
|
-
#
|
|
18
|
-
# 注意:patch 的 config 是整体替换、不深合并。这三行原本各自只声明了
|
|
19
|
-
# root / dshHome,所以只重述这一个字段是安全的;若你给这些行配过别的
|
|
20
|
-
# 字段(例如 session-persistence-jsonl 的 compression),必须一并重述。
|
|
21
|
-
|
|
22
|
-
- id: session-persistence-jsonl
|
|
23
|
-
name: '@deepseek-ai/dsh-session-persistence-jsonl'
|
|
24
|
-
config:
|
|
25
|
-
root: !!js (process.env.DSH_WORKBENCH_HOME ?? dshHomePath('workbench')) + '/sessions'
|
|
26
|
-
|
|
27
|
-
- id: storage-json
|
|
28
|
-
name: '@deepseek-ai/dsh-storage-json'
|
|
29
|
-
config:
|
|
30
|
-
root: !!js (process.env.DSH_WORKBENCH_HOME ?? dshHomePath('workbench')) + '/storages'
|
|
31
|
-
|
|
32
|
-
- id: attachment-local
|
|
33
|
-
name: '@deepseek-ai/dsh-attachment-local'
|
|
34
|
-
config:
|
|
35
|
-
dshHome: !!js process.env.DSH_WORKBENCH_HOME ?? dshHomePath('workbench')
|
|
36
|
-
|
|
37
|
-
# 可写根:**无会话**的插件命令(市场装模块、自举跑的那几条)回落到它。
|
|
38
|
-
#
|
|
39
|
-
# 跟启动器的 profile/sandbox.patch.yml 同值,两处都在是因为入口不同:启动器每次启动叠
|
|
40
|
-
# 那一层;直接用 `dsh workbench` 起的没有 `--patch`,就靠这一行。两处同值 ⇒ 谁先谁后都不算变更,
|
|
41
|
-
# 不会触发在线重组。**别把它放回 bundle 层**(在线启用会撞 session-controller 的重组竞态,
|
|
42
|
-
# 见 ADR-0037 的 2026-10-22 补记)。
|
|
43
|
-
- id: sandbox-policy
|
|
44
|
-
config:
|
|
45
|
-
mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'
|
|
46
|
-
workspaceRoot: !!js process.env.DSH_WORKBENCH_HOME ?? dshHomePath('workbench')
|