@dsh-workbench/core 1.0.0 → 1.0.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/README.md +13 -3
- package/bin/dsh-workbench +38 -6
- package/client.js +14 -6
- package/dist/client/client.body.js +14 -6
- package/dist/client/index.js +14 -6
- package/package.json +3 -3
- package/scripts/seed-profile.sh +9 -7
- package/scripts/share-data-root.mjs +272 -64
package/README.md
CHANGED
|
@@ -34,6 +34,15 @@ npx @dsh-workbench/core --profile wb2
|
|
|
34
34
|
|
|
35
35
|
全局装过就不用 `npx`:`npm i -g @dsh-workbench/core` 之后直接 `dsh-workbench`。
|
|
36
36
|
|
|
37
|
+
从 1.0.0 之前的独立实例升上来:旧数据**不会**自动并。先退出所有 dsh,再跑:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
dsh-workbench migrate # 切共用根 + 并旧会话/项目/附件
|
|
41
|
+
dsh-workbench migrate --force # 已经迁过一次、想再补并一次
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
只会补缺、不覆盖;旧目录保留。npx 用户把 `dsh-workbench` 换成 `npx @dsh-workbench/core`。
|
|
45
|
+
|
|
37
46
|
先决条件(就三条):
|
|
38
47
|
|
|
39
48
|
| 要有 | 为什么 |
|
|
@@ -48,6 +57,7 @@ npx @dsh-workbench/core --profile wb2
|
|
|
48
57
|
|---|---|
|
|
49
58
|
| `dsh-workbench create` | 交互式新建一个工作台实例 |
|
|
50
59
|
| `dsh-workbench setup` | 幂等建默认的 `workbench` profile;配 `--from` 建别的名字 |
|
|
60
|
+
| `dsh-workbench migrate` | 把旧实例的会话 / 项目 / 附件并到共用数据根(`--force` 可重跑) |
|
|
51
61
|
| `dsh-workbench` | 起工作台(默认端口 `:3081`,其余参数透传给 `dsh web`) |
|
|
52
62
|
| `dsh-workbench --profile <名字>` | 起指定实例 |
|
|
53
63
|
| `dsh-workbench setup --from <来源> --profile <新名>` | 以某个现有 profile 为模板建一个 |
|
|
@@ -78,7 +88,7 @@ npx @dsh-workbench/core --profile wb2
|
|
|
78
88
|
| `profile/` | 出厂 profile 模板:可写根那一行(启动器 `--patch` 用,seed 也抄进 profile) |
|
|
79
89
|
| `skills/` | 模块 DIY 指引,装好后挂给 agent;漏了不报错,只是 agent 不知道要怎么做模块 |
|
|
80
90
|
| `bin/` | `dsh-workbench` 启动器、隔离 opencli 的入口 |
|
|
81
|
-
| `scripts/` | 随包出货的脚本:`seed-profile.sh` / `seed-workshop.sh` / `create-workbench.mjs` / `clone-profile.mjs` |
|
|
91
|
+
| `scripts/` | 随包出货的脚本:`seed-profile.sh` / `seed-workshop.sh` / `create-workbench.mjs` / `clone-profile.mjs` / `share-data-root.mjs` |
|
|
82
92
|
|
|
83
93
|
## 数据落在哪
|
|
84
94
|
|
|
@@ -92,8 +102,8 @@ npx @dsh-workbench/core --profile wb2
|
|
|
92
102
|
| DIY 工坊依赖安装日志 | `~/.dsh/workbench/seed.log` |
|
|
93
103
|
|
|
94
104
|
独立实例**只分 profile,不分数据根**:会话、项目、附件跟 `dsh web` 和 Desktop 是同一份,
|
|
95
|
-
|
|
96
|
-
|
|
105
|
+
只有设置与插件各一份。旧版本分过家的实例,启动时**不会**自动并;先退出所有 dsh,再跑
|
|
106
|
+
`dsh-workbench migrate`(只补不覆盖,旧目录保留)。
|
|
97
107
|
|
|
98
108
|
设置只有一份,写在哪都看得见:
|
|
99
109
|
|
package/bin/dsh-workbench
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
# # 以 web 为模板建一个独立工作台
|
|
9
9
|
# ./bin/dsh-workbench --profile wb2 # ② 起指定 profile,默认 :3081
|
|
10
10
|
# ./bin/dsh-workbench --no-open # 其余参数透传给 dsh web
|
|
11
|
+
# ./bin/dsh-workbench migrate # 把旧实例的会话/项目/附件并到共用数据根(--force 可重跑)
|
|
11
12
|
# ./bin/dsh-workbench --help
|
|
12
13
|
#
|
|
13
14
|
# 与 `dsh web` 并存:不同的 profile、不同的 node_modules、不同的端口、独立的进程。
|
|
@@ -35,6 +36,8 @@ PROFILE_EXPLICIT=0
|
|
|
35
36
|
[ -n "${WORKBENCH_PROFILE:-}" ] && PROFILE_EXPLICIT=1
|
|
36
37
|
SETUP=0
|
|
37
38
|
CREATE=0
|
|
39
|
+
MIGRATE=0
|
|
40
|
+
FORCE=0
|
|
38
41
|
DSH_ARGS=()
|
|
39
42
|
|
|
40
43
|
die() {
|
|
@@ -43,7 +46,7 @@ die() {
|
|
|
43
46
|
}
|
|
44
47
|
|
|
45
48
|
usage() {
|
|
46
|
-
sed -n '3,
|
|
49
|
+
sed -n '3,12p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
if [ "${1:-}" = "create" ]; then
|
|
@@ -54,6 +57,10 @@ if [ "${1:-}" = "setup" ]; then
|
|
|
54
57
|
SETUP=1
|
|
55
58
|
shift
|
|
56
59
|
fi
|
|
60
|
+
if [ "${1:-}" = "migrate" ]; then
|
|
61
|
+
MIGRATE=1
|
|
62
|
+
shift
|
|
63
|
+
fi
|
|
57
64
|
|
|
58
65
|
while [ $# -gt 0 ]; do
|
|
59
66
|
case "$1" in
|
|
@@ -75,6 +82,11 @@ while [ $# -gt 0 ]; do
|
|
|
75
82
|
echo "dsh-workbench: 会话与项目跟 dsh web 共用一份数据根,$1 不再需要(忽略)。" >&2
|
|
76
83
|
shift
|
|
77
84
|
;;
|
|
85
|
+
--force)
|
|
86
|
+
[ "$MIGRATE" = "1" ] || die "--force 只用于 migrate"
|
|
87
|
+
FORCE=1
|
|
88
|
+
shift
|
|
89
|
+
;;
|
|
78
90
|
--no-settings) WB_COPY_SETTINGS=0; shift ;;
|
|
79
91
|
help | --help | -h) usage; exit 0 ;;
|
|
80
92
|
--) shift; DSH_ARGS+=("$@"); break ;;
|
|
@@ -85,6 +97,9 @@ done
|
|
|
85
97
|
if [ "$SETUP" = "1" ] && [ ${#DSH_ARGS[@]} -gt 0 ]; then
|
|
86
98
|
die "setup 不认识这些参数:${DSH_ARGS[*]}"
|
|
87
99
|
fi
|
|
100
|
+
if [ "$MIGRATE" = "1" ] && [ ${#DSH_ARGS[@]} -gt 0 ]; then
|
|
101
|
+
die "migrate 不认识这些参数:${DSH_ARGS[*]}"
|
|
102
|
+
fi
|
|
88
103
|
if [ "${WB_CLONE:-0}" = "1" ] && [ "$PROFILE_EXPLICIT" = "0" ]; then
|
|
89
104
|
die "用 --from 时还要指定 --profile <新名字>(例如 --from web --profile wb2)"
|
|
90
105
|
fi
|
|
@@ -272,6 +287,22 @@ if [ ! -f "$PROFILE_DIR/package.json" ]; then
|
|
|
272
287
|
exit 1
|
|
273
288
|
fi
|
|
274
289
|
|
|
290
|
+
if [ "$MIGRATE" = "1" ]; then
|
|
291
|
+
MIGRATE_ARGS=(--migrate)
|
|
292
|
+
[ "$FORCE" = "1" ] && MIGRATE_ARGS+=(--force)
|
|
293
|
+
WORKBENCH_PROFILE="$PROFILE" DSH_HOME="$DSH_HOME" DSH_WORKBENCH_HOME="$WORKBENCH_HOME" \
|
|
294
|
+
node "$REPO_DIR/scripts/share-data-root.mjs" "${MIGRATE_ARGS[@]}" || exit 1
|
|
295
|
+
if [ "$PROFILE" = "workbench" ]; then
|
|
296
|
+
START_CMD="$REPO_DIR/bin/dsh-workbench"
|
|
297
|
+
else
|
|
298
|
+
START_CMD="$REPO_DIR/bin/dsh-workbench --profile $PROFILE"
|
|
299
|
+
fi
|
|
300
|
+
echo
|
|
301
|
+
echo "迁移结束。现在起工作台:$START_CMD"
|
|
302
|
+
echo "如果 dsh web 刚才也开着,先重启它,让它从共用根重新读一遍。"
|
|
303
|
+
exit 0
|
|
304
|
+
fi
|
|
305
|
+
|
|
275
306
|
# ── 硬门禁:工作台 bundle 必须**被选中**(在 `dsh.profile.bundles` 里)─────────
|
|
276
307
|
#
|
|
277
308
|
# 判据只能是 `dsh.profile.bundles`,**不能** grep 整个 package.json:依赖装着 ≠ 被选中。
|
|
@@ -307,15 +338,16 @@ if [ "$BUNDLE_GUARD" -ne 0 ]; then
|
|
|
307
338
|
exit 1
|
|
308
339
|
fi
|
|
309
340
|
|
|
310
|
-
# ──
|
|
341
|
+
# ── 已有实例:只整理配置,旧数据不自动迁移
|
|
311
342
|
#
|
|
312
343
|
# 旧版本给 workbench profile 追加过隔离的三行(sessions / storages / attachment 指到
|
|
313
|
-
# `$DSH_HOME/workbench
|
|
314
|
-
#
|
|
315
|
-
#
|
|
344
|
+
# `$DSH_HOME/workbench/**`)。现在缺省跟 Desktop 一样「只分 profile、不分数据根」;
|
|
345
|
+
# 但如果旧目录里还有数据、且没有 `.data-root-shared`,这里只打印 migrate 提示,
|
|
346
|
+
# 不改 profile、不动数据 —— 由用户自己决定什么时候并。
|
|
347
|
+
# 必须排在这里(exec dsh 之前):配置要在这一代 dsh 读进去之前就定好。
|
|
316
348
|
WORKBENCH_PROFILE="$PROFILE" DSH_HOME="$DSH_HOME" DSH_WORKBENCH_HOME="$WORKBENCH_HOME" \
|
|
317
349
|
node "$REPO_DIR/scripts/share-data-root.mjs" ||
|
|
318
|
-
echo "dsh-workbench: 提醒 ——
|
|
350
|
+
echo "dsh-workbench: 提醒 —— 数据根配置没整理成(见上一行),这次仍按现在的 profile 起。" >&2
|
|
319
351
|
|
|
320
352
|
# 清掉从父 dsh 会话继承来的身份变量。
|
|
321
353
|
#
|
package/client.js
CHANGED
|
@@ -3086,6 +3086,7 @@ window.__ModuleLoader__.load({
|
|
|
3086
3086
|
"panels.market.selfUpdateFailed": "Self-update failed: {reason}",
|
|
3087
3087
|
"panels.market.selfUpdateUnknown": "the outside process left no reason (see ~/.dsh/workbench/self-update.log)",
|
|
3088
3088
|
"panels.market.selfUpdateRolledBack": "Not swapped; rolled back to {version}.",
|
|
3089
|
+
"panels.market.selfUpdateNeedsMigrate": "Workbench updated. Old sessions and projects are not merged automatically: quit every dsh, then run dsh-workbench migrate (--force to merge again).",
|
|
3089
3090
|
"panels.market.uninstall": "Uninstall",
|
|
3090
3091
|
"panels.market.uninstallLabel": "Uninstall {name}",
|
|
3091
3092
|
"panels.market.installing": "Installing…",
|
|
@@ -3718,6 +3719,7 @@ window.__ModuleLoader__.load({
|
|
|
3718
3719
|
"panels.market.selfUpdateFailed": "自更新没成:{reason}",
|
|
3719
3720
|
"panels.market.selfUpdateUnknown": "外部进程没留下原因(看 ~/.dsh/workbench/self-update.log)",
|
|
3720
3721
|
"panels.market.selfUpdateRolledBack": "没换成,已回滚到 {version}。",
|
|
3722
|
+
"panels.market.selfUpdateNeedsMigrate": "工作台已更新。旧会话 / 项目不会自动并:先退出所有 dsh,再运行 dsh-workbench migrate(--force 可补并)。",
|
|
3721
3723
|
"panels.market.uninstall": "卸载",
|
|
3722
3724
|
"panels.market.uninstallLabel": "卸载 {name}",
|
|
3723
3725
|
"panels.market.installing": "正在安装…",
|
|
@@ -6498,6 +6500,9 @@ window.__ModuleLoader__.load({
|
|
|
6498
6500
|
"remove-bundle",
|
|
6499
6501
|
"uninstall-plugin"
|
|
6500
6502
|
];
|
|
6503
|
+
function marketMigrationNotice(t, update) {
|
|
6504
|
+
return update?.migration === "needed" ? t("panels.market.selfUpdateNeedsMigrate") : null;
|
|
6505
|
+
}
|
|
6501
6506
|
function ConfirmUninstall({ t, name, onConfirm, onCancel }) {
|
|
6502
6507
|
const [acknowledged, setAcknowledged] = (0, react.useState)(false);
|
|
6503
6508
|
return /* @__PURE__ */ (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.RiskConfirmation, {
|
|
@@ -6674,18 +6679,21 @@ window.__ModuleLoader__.load({
|
|
|
6674
6679
|
send: (body) => provision.send(body),
|
|
6675
6680
|
onChanged
|
|
6676
6681
|
});
|
|
6682
|
+
const landed = pendingLanded(pending, updateMap, effective, workbench?.installed, shared, pluginSource === null ? null : pluginList, {
|
|
6683
|
+
units: installed === void 0 ? null : new Set(installed.map((module) => module.unit)),
|
|
6684
|
+
plugins: pluginSource === null ? null : new Set(pluginList.map((row) => row.id))
|
|
6685
|
+
});
|
|
6686
|
+
const effectiveSelfUpdate = mock?.selfUpdate ?? selfUpdate;
|
|
6677
6687
|
useSettleHold({
|
|
6678
6688
|
pending,
|
|
6679
|
-
landed
|
|
6680
|
-
units: installed === void 0 ? null : new Set(installed.map((module) => module.unit)),
|
|
6681
|
-
plugins: pluginSource === null ? null : new Set(pluginList.map((row) => row.id))
|
|
6682
|
-
}),
|
|
6689
|
+
landed,
|
|
6683
6690
|
t,
|
|
6684
6691
|
onChanged,
|
|
6685
|
-
selfUpdate:
|
|
6692
|
+
selfUpdate: effectiveSelfUpdate,
|
|
6686
6693
|
setPending,
|
|
6687
6694
|
setFailed
|
|
6688
6695
|
});
|
|
6696
|
+
const migrationNotice = marketMigrationNotice(t, effectiveSelfUpdate);
|
|
6689
6697
|
const settled = marketSettled(freshness, installedRead);
|
|
6690
6698
|
const nothing = shown.length === 0 && self === void 0 && installedList.length === 0 && pluginList.length === 0;
|
|
6691
6699
|
if (!settled || (settling?.size ?? 0) > 0 && shown.length === 0 && installedList.length === 0) return /* @__PURE__ */ (0, react.createElement)(MarketChecking, { t });
|
|
@@ -6699,7 +6707,7 @@ window.__ModuleLoader__.load({
|
|
|
6699
6707
|
shared,
|
|
6700
6708
|
pending,
|
|
6701
6709
|
failed,
|
|
6702
|
-
notice,
|
|
6710
|
+
notice: notice ?? migrationNotice,
|
|
6703
6711
|
act,
|
|
6704
6712
|
onConfirming: setConfirming,
|
|
6705
6713
|
setDefault,
|
|
@@ -3079,6 +3079,7 @@ var en = {
|
|
|
3079
3079
|
"panels.market.selfUpdateFailed": "Self-update failed: {reason}",
|
|
3080
3080
|
"panels.market.selfUpdateUnknown": "the outside process left no reason (see ~/.dsh/workbench/self-update.log)",
|
|
3081
3081
|
"panels.market.selfUpdateRolledBack": "Not swapped; rolled back to {version}.",
|
|
3082
|
+
"panels.market.selfUpdateNeedsMigrate": "Workbench updated. Old sessions and projects are not merged automatically: quit every dsh, then run dsh-workbench migrate (--force to merge again).",
|
|
3082
3083
|
"panels.market.uninstall": "Uninstall",
|
|
3083
3084
|
"panels.market.uninstallLabel": "Uninstall {name}",
|
|
3084
3085
|
"panels.market.installing": "Installing…",
|
|
@@ -3711,6 +3712,7 @@ var dictionaries = {
|
|
|
3711
3712
|
"panels.market.selfUpdateFailed": "自更新没成:{reason}",
|
|
3712
3713
|
"panels.market.selfUpdateUnknown": "外部进程没留下原因(看 ~/.dsh/workbench/self-update.log)",
|
|
3713
3714
|
"panels.market.selfUpdateRolledBack": "没换成,已回滚到 {version}。",
|
|
3715
|
+
"panels.market.selfUpdateNeedsMigrate": "工作台已更新。旧会话 / 项目不会自动并:先退出所有 dsh,再运行 dsh-workbench migrate(--force 可补并)。",
|
|
3714
3716
|
"panels.market.uninstall": "卸载",
|
|
3715
3717
|
"panels.market.uninstallLabel": "卸载 {name}",
|
|
3716
3718
|
"panels.market.installing": "正在安装…",
|
|
@@ -6491,6 +6493,9 @@ var CONFIRM_UNINSTALL = [
|
|
|
6491
6493
|
"remove-bundle",
|
|
6492
6494
|
"uninstall-plugin"
|
|
6493
6495
|
];
|
|
6496
|
+
function marketMigrationNotice(t, update) {
|
|
6497
|
+
return update?.migration === "needed" ? t("panels.market.selfUpdateNeedsMigrate") : null;
|
|
6498
|
+
}
|
|
6494
6499
|
function ConfirmUninstall({ t, name, onConfirm, onCancel }) {
|
|
6495
6500
|
const [acknowledged, setAcknowledged] = (0, react.useState)(false);
|
|
6496
6501
|
return /* @__PURE__ */ (0, react.createElement)(_deepseek_ai_dsh_client_ui_primitives.RiskConfirmation, {
|
|
@@ -6667,18 +6672,21 @@ function MarketView({ t, manifests, installable, capabilities, freshness, onChan
|
|
|
6667
6672
|
send: (body) => provision.send(body),
|
|
6668
6673
|
onChanged
|
|
6669
6674
|
});
|
|
6675
|
+
const landed = pendingLanded(pending, updateMap, effective, workbench?.installed, shared, pluginSource === null ? null : pluginList, {
|
|
6676
|
+
units: installed === void 0 ? null : new Set(installed.map((module) => module.unit)),
|
|
6677
|
+
plugins: pluginSource === null ? null : new Set(pluginList.map((row) => row.id))
|
|
6678
|
+
});
|
|
6679
|
+
const effectiveSelfUpdate = mock?.selfUpdate ?? selfUpdate;
|
|
6670
6680
|
useSettleHold({
|
|
6671
6681
|
pending,
|
|
6672
|
-
landed
|
|
6673
|
-
units: installed === void 0 ? null : new Set(installed.map((module) => module.unit)),
|
|
6674
|
-
plugins: pluginSource === null ? null : new Set(pluginList.map((row) => row.id))
|
|
6675
|
-
}),
|
|
6682
|
+
landed,
|
|
6676
6683
|
t,
|
|
6677
6684
|
onChanged,
|
|
6678
|
-
selfUpdate:
|
|
6685
|
+
selfUpdate: effectiveSelfUpdate,
|
|
6679
6686
|
setPending,
|
|
6680
6687
|
setFailed
|
|
6681
6688
|
});
|
|
6689
|
+
const migrationNotice = marketMigrationNotice(t, effectiveSelfUpdate);
|
|
6682
6690
|
const settled = marketSettled(freshness, installedRead);
|
|
6683
6691
|
const nothing = shown.length === 0 && self === void 0 && installedList.length === 0 && pluginList.length === 0;
|
|
6684
6692
|
if (!settled || (settling?.size ?? 0) > 0 && shown.length === 0 && installedList.length === 0) return /* @__PURE__ */ (0, react.createElement)(MarketChecking, { t });
|
|
@@ -6692,7 +6700,7 @@ function MarketView({ t, manifests, installable, capabilities, freshness, onChan
|
|
|
6692
6700
|
shared,
|
|
6693
6701
|
pending,
|
|
6694
6702
|
failed,
|
|
6695
|
-
notice,
|
|
6703
|
+
notice: notice ?? migrationNotice,
|
|
6696
6704
|
act,
|
|
6697
6705
|
onConfirming: setConfirming,
|
|
6698
6706
|
setDefault,
|
package/dist/client/index.js
CHANGED
|
@@ -3082,6 +3082,7 @@ var en = {
|
|
|
3082
3082
|
"panels.market.selfUpdateFailed": "Self-update failed: {reason}",
|
|
3083
3083
|
"panels.market.selfUpdateUnknown": "the outside process left no reason (see ~/.dsh/workbench/self-update.log)",
|
|
3084
3084
|
"panels.market.selfUpdateRolledBack": "Not swapped; rolled back to {version}.",
|
|
3085
|
+
"panels.market.selfUpdateNeedsMigrate": "Workbench updated. Old sessions and projects are not merged automatically: quit every dsh, then run dsh-workbench migrate (--force to merge again).",
|
|
3085
3086
|
"panels.market.uninstall": "Uninstall",
|
|
3086
3087
|
"panels.market.uninstallLabel": "Uninstall {name}",
|
|
3087
3088
|
"panels.market.installing": "Installing…",
|
|
@@ -3714,6 +3715,7 @@ var dictionaries = {
|
|
|
3714
3715
|
"panels.market.selfUpdateFailed": "自更新没成:{reason}",
|
|
3715
3716
|
"panels.market.selfUpdateUnknown": "外部进程没留下原因(看 ~/.dsh/workbench/self-update.log)",
|
|
3716
3717
|
"panels.market.selfUpdateRolledBack": "没换成,已回滚到 {version}。",
|
|
3718
|
+
"panels.market.selfUpdateNeedsMigrate": "工作台已更新。旧会话 / 项目不会自动并:先退出所有 dsh,再运行 dsh-workbench migrate(--force 可补并)。",
|
|
3717
3719
|
"panels.market.uninstall": "卸载",
|
|
3718
3720
|
"panels.market.uninstallLabel": "卸载 {name}",
|
|
3719
3721
|
"panels.market.installing": "正在安装…",
|
|
@@ -6494,6 +6496,9 @@ var CONFIRM_UNINSTALL = [
|
|
|
6494
6496
|
"remove-bundle",
|
|
6495
6497
|
"uninstall-plugin"
|
|
6496
6498
|
];
|
|
6499
|
+
function marketMigrationNotice(t, update) {
|
|
6500
|
+
return update?.migration === "needed" ? t("panels.market.selfUpdateNeedsMigrate") : null;
|
|
6501
|
+
}
|
|
6497
6502
|
function ConfirmUninstall({ t, name, onConfirm, onCancel }) {
|
|
6498
6503
|
const [acknowledged, setAcknowledged] = useState(false);
|
|
6499
6504
|
return /* @__PURE__ */ createElement(RiskConfirmation, {
|
|
@@ -6670,18 +6675,21 @@ function MarketView({ t, manifests, installable, capabilities, freshness, onChan
|
|
|
6670
6675
|
send: (body) => provision.send(body),
|
|
6671
6676
|
onChanged
|
|
6672
6677
|
});
|
|
6678
|
+
const landed = pendingLanded(pending, updateMap, effective, workbench?.installed, shared, pluginSource === null ? null : pluginList, {
|
|
6679
|
+
units: installed === void 0 ? null : new Set(installed.map((module) => module.unit)),
|
|
6680
|
+
plugins: pluginSource === null ? null : new Set(pluginList.map((row) => row.id))
|
|
6681
|
+
});
|
|
6682
|
+
const effectiveSelfUpdate = mock?.selfUpdate ?? selfUpdate;
|
|
6673
6683
|
useSettleHold({
|
|
6674
6684
|
pending,
|
|
6675
|
-
landed
|
|
6676
|
-
units: installed === void 0 ? null : new Set(installed.map((module) => module.unit)),
|
|
6677
|
-
plugins: pluginSource === null ? null : new Set(pluginList.map((row) => row.id))
|
|
6678
|
-
}),
|
|
6685
|
+
landed,
|
|
6679
6686
|
t,
|
|
6680
6687
|
onChanged,
|
|
6681
|
-
selfUpdate:
|
|
6688
|
+
selfUpdate: effectiveSelfUpdate,
|
|
6682
6689
|
setPending,
|
|
6683
6690
|
setFailed
|
|
6684
6691
|
});
|
|
6692
|
+
const migrationNotice = marketMigrationNotice(t, effectiveSelfUpdate);
|
|
6685
6693
|
const settled = marketSettled(freshness, installedRead);
|
|
6686
6694
|
const nothing = shown.length === 0 && self === void 0 && installedList.length === 0 && pluginList.length === 0;
|
|
6687
6695
|
if (!settled || (settling?.size ?? 0) > 0 && shown.length === 0 && installedList.length === 0) return /* @__PURE__ */ createElement(MarketChecking, { t });
|
|
@@ -6695,7 +6703,7 @@ function MarketView({ t, manifests, installable, capabilities, freshness, onChan
|
|
|
6695
6703
|
shared,
|
|
6696
6704
|
pending,
|
|
6697
6705
|
failed,
|
|
6698
|
-
notice,
|
|
6706
|
+
notice: notice ?? migrationNotice,
|
|
6699
6707
|
act,
|
|
6700
6708
|
onConfirming: setConfirming,
|
|
6701
6709
|
setDefault,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "个人工作台:Agent 工作台(左栏 + 常驻对话 + 自研右侧数据面板)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"icon": "./icon.svg",
|
|
@@ -100,8 +100,8 @@
|
|
|
100
100
|
"proper-lockfile": "^4.1.2",
|
|
101
101
|
"which": "^7.0.0",
|
|
102
102
|
"write-file-atomic": "^8.0.0",
|
|
103
|
-
"@dsh-workbench/shared": "1.0.
|
|
104
|
-
"@dsh-workbench/adapters-default": "1.0.
|
|
103
|
+
"@dsh-workbench/shared": "1.0.1",
|
|
104
|
+
"@dsh-workbench/adapters-default": "1.0.1"
|
|
105
105
|
},
|
|
106
106
|
"license": "MIT",
|
|
107
107
|
"repository": {
|
package/scripts/seed-profile.sh
CHANGED
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
#
|
|
3
3
|
# 一次性引导:从 shipped 的 web 模板创建 workbench profile,
|
|
4
4
|
# 继承 web 的 **bundle 清单**(这台机器上装了哪些 bundle),
|
|
5
|
-
#
|
|
5
|
+
# 补上可写根那一行;新 profile 直接跟 web 共用数据根,旧 profile 保持原状,等用户跑 migrate。
|
|
6
6
|
#
|
|
7
7
|
# **设置不在这里继承**(这里曾经把 web 的 cordis.patch.yml 快照一份过来):
|
|
8
8
|
# 工作台自己的 patch 是唯一真相,想同步 web 的哪几行由用户走「设置 → 配置同步」。
|
|
9
9
|
# 为什么这么分(安装 vs 设置)、为什么不再是自动的:内部设计记录
|
|
10
10
|
#
|
|
11
|
-
# 幂等 / 可续跑:profile
|
|
12
|
-
# —— 重复运行不会覆盖任何东西。
|
|
11
|
+
# 幂等 / 可续跑:profile 已存在就跳过创建;数据根已经是共用的、或旧数据还没迁
|
|
12
|
+
# (只提示 migrate)就什么都不写 —— 重复运行不会覆盖任何东西。
|
|
13
13
|
# 安全:全程只**读** web 的 profile 目录,只**写** workbench 自己的目录。
|
|
14
14
|
#
|
|
15
15
|
# 用法:
|
|
@@ -176,10 +176,11 @@ console.log("→ profile 里那条 link: 指的是我这份装出来的副本(
|
|
|
176
176
|
'
|
|
177
177
|
fi
|
|
178
178
|
|
|
179
|
-
# ── 第 3
|
|
179
|
+
# ── 第 3 步:数据根配置 + 可写根那一行 ─────────────────────────────────
|
|
180
180
|
#
|
|
181
|
-
#
|
|
182
|
-
#
|
|
181
|
+
# 新 profile 跟 `dsh web` / Desktop 一样只分 profile,数据落在 `$DSH_HOME`。
|
|
182
|
+
# 旧 profile 如果还有没迁的数据,这里只打印 migrate 提示,保持隔离、不动数据;
|
|
183
|
+
# 用户自己跑 `dsh-workbench migrate` 才切共用根并并数据。
|
|
183
184
|
# 每一步都幂等:已经是共用数据根的 profile 一个字节都不写。
|
|
184
185
|
WORKBENCH_PROFILE="$PROFILE" DSH_HOME="$DSH_HOME" DSH_WORKBENCH_HOME="$WORKBENCH_HOME" \
|
|
185
186
|
node "$REPO_DIR/scripts/share-data-root.mjs" || die "整理数据根失败(见上一行)"
|
|
@@ -202,7 +203,8 @@ fi
|
|
|
202
203
|
|
|
203
204
|
cat <<EOF
|
|
204
205
|
|
|
205
|
-
profile '$DST_PROFILE' 的骨架好了(bundle 清单 +
|
|
206
|
+
profile '$DST_PROFILE' 的骨架好了(bundle 清单 + 可写根)。
|
|
207
|
+
旧实例如果还有数据没迁,启动时会提示;要切共用根并并旧数据,跑 dsh-workbench migrate。
|
|
206
208
|
|
|
207
209
|
设置按创建时的勾选从 $SRC_PROFILE_NAME 继承(只同步改已有插件的行;挂新插件的行不同步);
|
|
208
210
|
直接跑 setup 时只继承 bundle 清单,想要 web 那边的模型供应商 / 搜索源 / MCP,起工作台后去
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
statSync,
|
|
11
11
|
writeFileSync,
|
|
12
12
|
} from 'node:fs';
|
|
13
|
+
import { randomUUID } from 'node:crypto';
|
|
13
14
|
import { homedir } from 'node:os';
|
|
14
15
|
import { dirname, join, resolve } from 'node:path';
|
|
15
16
|
import { fileURLToPath } from 'node:url';
|
|
@@ -78,6 +79,58 @@ function unionList(left, right) {
|
|
|
78
79
|
return [...new Set([...(Array.isArray(left) ? left : []), ...(Array.isArray(right) ? right : [])])];
|
|
79
80
|
}
|
|
80
81
|
|
|
82
|
+
function sessionIdsOf(entry) {
|
|
83
|
+
return Array.isArray(entry?.sessionIds) ? entry.sessionIds : [];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function orderedIds(global, table) {
|
|
87
|
+
const seen = new Set();
|
|
88
|
+
const ids = [];
|
|
89
|
+
for (const id of Array.isArray(global?.workspaceIds) ? global.workspaceIds : []) {
|
|
90
|
+
if (table[id] === undefined || seen.has(id)) continue;
|
|
91
|
+
ids.push(id);
|
|
92
|
+
seen.add(id);
|
|
93
|
+
}
|
|
94
|
+
for (const id of Object.keys(table)) {
|
|
95
|
+
if (seen.has(id)) continue;
|
|
96
|
+
ids.push(id);
|
|
97
|
+
seen.add(id);
|
|
98
|
+
}
|
|
99
|
+
return ids;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function repairWorkspaceOrder(targetFile) {
|
|
103
|
+
let target;
|
|
104
|
+
try {
|
|
105
|
+
target = JSON.parse(readFileSync(targetFile, 'utf8'));
|
|
106
|
+
} catch {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
if (target?.unit?.name !== 'workspace') return false;
|
|
110
|
+
const table = target.tables?.workspaces ?? {};
|
|
111
|
+
const global = target.global ?? {};
|
|
112
|
+
if (global.pendingMutation !== undefined) return false;
|
|
113
|
+
const before = Array.isArray(global.workspaceIds) ? global.workspaceIds : [];
|
|
114
|
+
const seen = new Set();
|
|
115
|
+
const order = [];
|
|
116
|
+
for (const id of before) {
|
|
117
|
+
if (table[id] === undefined || seen.has(id)) continue;
|
|
118
|
+
order.push(id);
|
|
119
|
+
seen.add(id);
|
|
120
|
+
}
|
|
121
|
+
if (global.initialized === true) {
|
|
122
|
+
for (const id of Object.keys(table)) {
|
|
123
|
+
if (seen.has(id)) continue;
|
|
124
|
+
order.push(id);
|
|
125
|
+
seen.add(id);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (order.length === before.length && order.every((id, index) => id === before[index])) return false;
|
|
129
|
+
target.global = { ...global, workspaceIds: order };
|
|
130
|
+
writeAtomic(targetFile, `${JSON.stringify(target, null, 2)}\n`);
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
81
134
|
function mergeWorkspaceList(sourceFile, targetFile) {
|
|
82
135
|
let source;
|
|
83
136
|
try {
|
|
@@ -95,67 +148,249 @@ function mergeWorkspaceList(sourceFile, targetFile) {
|
|
|
95
148
|
try {
|
|
96
149
|
target = JSON.parse(readFileSync(targetFile, 'utf8'));
|
|
97
150
|
} catch {
|
|
98
|
-
warn(`${targetFile}
|
|
99
|
-
return
|
|
151
|
+
warn(`${targetFile} 读不出来,这次没并项目列表,下次启动再试。`);
|
|
152
|
+
return null;
|
|
100
153
|
}
|
|
101
154
|
if (target?.unit?.name !== 'workspace') return 0;
|
|
102
155
|
const sourceTable = source.tables?.workspaces ?? {};
|
|
103
156
|
const targetTable = target.tables?.workspaces ?? {};
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
157
|
+
const sourceGlobal = source.global ?? {};
|
|
158
|
+
const targetGlobal = target.global ?? {};
|
|
159
|
+
if (targetGlobal.pendingMutation !== undefined) {
|
|
160
|
+
warn(`${targetFile} 里有一次没做完的项目操作,这次先不并项目列表,下次启动再试。`);
|
|
161
|
+
return null;
|
|
162
|
+
}
|
|
163
|
+
const idByPath = new Map();
|
|
164
|
+
const accounted = new Map();
|
|
165
|
+
for (const [id, entry] of Object.entries(targetTable)) {
|
|
166
|
+
if (typeof entry?.path === 'string') idByPath.set(entry.path, id);
|
|
167
|
+
for (const sessionId of sessionIdsOf(entry)) {
|
|
168
|
+
if (!accounted.has(sessionId)) accounted.set(sessionId, id);
|
|
169
|
+
}
|
|
107
170
|
}
|
|
171
|
+
const order = orderedIds(targetGlobal, targetTable);
|
|
172
|
+
const idMap = new Map();
|
|
108
173
|
let added = 0;
|
|
109
|
-
for (const
|
|
174
|
+
for (const sourceId of orderedIds(sourceGlobal, sourceTable)) {
|
|
175
|
+
const entry = sourceTable[sourceId];
|
|
110
176
|
if (typeof entry?.path !== 'string') continue;
|
|
111
|
-
const
|
|
112
|
-
if (
|
|
113
|
-
targetTable[
|
|
114
|
-
|
|
115
|
-
|
|
177
|
+
const hitId = idByPath.get(entry.path);
|
|
178
|
+
if (hitId !== undefined) {
|
|
179
|
+
const hit = targetTable[hitId];
|
|
180
|
+
const incoming = sessionIdsOf(entry).filter((sessionId) => {
|
|
181
|
+
const holder = accounted.get(sessionId);
|
|
182
|
+
return holder === undefined || holder === hitId;
|
|
183
|
+
});
|
|
184
|
+
hit.sessionIds = unionList(sessionIdsOf(hit), incoming);
|
|
185
|
+
for (const sessionId of hit.sessionIds) {
|
|
186
|
+
if (!accounted.has(sessionId)) accounted.set(sessionId, hitId);
|
|
187
|
+
}
|
|
188
|
+
if (typeof entry.updatedAt === 'string' && (typeof hit.updatedAt !== 'string' || entry.updatedAt > hit.updatedAt)) {
|
|
189
|
+
hit.updatedAt = entry.updatedAt;
|
|
190
|
+
}
|
|
191
|
+
idMap.set(sourceId, hitId);
|
|
116
192
|
continue;
|
|
117
193
|
}
|
|
118
|
-
|
|
119
|
-
if (
|
|
120
|
-
|
|
194
|
+
let newId = sourceId;
|
|
195
|
+
if (targetTable[newId] !== undefined) newId = randomUUID();
|
|
196
|
+
const sessionIds = [];
|
|
197
|
+
for (const sessionId of sessionIdsOf(entry)) {
|
|
198
|
+
if (accounted.has(sessionId)) continue;
|
|
199
|
+
accounted.set(sessionId, newId);
|
|
200
|
+
sessionIds.push(sessionId);
|
|
121
201
|
}
|
|
202
|
+
targetTable[newId] = { ...entry, sessionIds };
|
|
203
|
+
idByPath.set(entry.path, newId);
|
|
204
|
+
order.push(newId);
|
|
205
|
+
added += 1;
|
|
122
206
|
}
|
|
207
|
+
const preferred = targetGlobal.defaultWorkspaceId;
|
|
208
|
+
const fromSource = idMap.get(sourceGlobal.defaultWorkspaceId) ?? sourceGlobal.defaultWorkspaceId;
|
|
209
|
+
const defaultWorkspaceId =
|
|
210
|
+
typeof preferred === 'string' && targetTable[preferred] !== undefined
|
|
211
|
+
? preferred
|
|
212
|
+
: typeof fromSource === 'string' && targetTable[fromSource] !== undefined
|
|
213
|
+
? fromSource
|
|
214
|
+
: undefined;
|
|
123
215
|
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
216
|
target.global = {
|
|
129
|
-
...
|
|
130
|
-
workspaceIds:
|
|
131
|
-
archivedSessionIds: unionList(
|
|
132
|
-
pinnedSessionIds: unionList(
|
|
217
|
+
...targetGlobal,
|
|
218
|
+
workspaceIds: order,
|
|
219
|
+
archivedSessionIds: unionList(targetGlobal.archivedSessionIds, sourceGlobal.archivedSessionIds),
|
|
220
|
+
pinnedSessionIds: unionList(targetGlobal.pinnedSessionIds, sourceGlobal.pinnedSessionIds),
|
|
133
221
|
};
|
|
222
|
+
if (defaultWorkspaceId === undefined) delete target.global.defaultWorkspaceId;
|
|
223
|
+
else target.global.defaultWorkspaceId = defaultWorkspaceId;
|
|
134
224
|
writeAtomic(targetFile, `${JSON.stringify(target, null, 2)}\n`);
|
|
135
225
|
return added;
|
|
136
226
|
}
|
|
137
227
|
|
|
228
|
+
function repairTarget(targetFile) {
|
|
229
|
+
if (repairWorkspaceOrder(targetFile)) say(`项目顺序里有对不上的条目,已按实际项目重排(${targetFile})。`);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function hasEntries(dir) {
|
|
233
|
+
try {
|
|
234
|
+
return readdirSync(dir).some((name) => name !== '.DS_Store');
|
|
235
|
+
} catch {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function legacyDataExists() {
|
|
241
|
+
if (resolve(RUNTIME_ROOT) === resolve(HOME)) return false;
|
|
242
|
+
return (
|
|
243
|
+
existsSync(join(RUNTIME_ROOT, 'storages', 'workspace.json')) ||
|
|
244
|
+
hasEntries(join(RUNTIME_ROOT, 'sessions')) ||
|
|
245
|
+
hasEntries(join(RUNTIME_ROOT, 'attachments'))
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function missingWorkspacePaths() {
|
|
250
|
+
const sourceFile = join(RUNTIME_ROOT, 'storages', 'workspace.json');
|
|
251
|
+
const targetFile = join(HOME, 'storages', 'workspace.json');
|
|
252
|
+
if (!existsSync(sourceFile) || !existsSync(targetFile)) return [];
|
|
253
|
+
let source;
|
|
254
|
+
let target;
|
|
255
|
+
try {
|
|
256
|
+
source = JSON.parse(readFileSync(sourceFile, 'utf8'));
|
|
257
|
+
target = JSON.parse(readFileSync(targetFile, 'utf8'));
|
|
258
|
+
} catch {
|
|
259
|
+
return [];
|
|
260
|
+
}
|
|
261
|
+
if (source?.unit?.name !== 'workspace' || target?.unit?.name !== 'workspace') return [];
|
|
262
|
+
const have = new Set();
|
|
263
|
+
for (const entry of Object.values(target.tables?.workspaces ?? {})) {
|
|
264
|
+
if (typeof entry?.path === 'string') have.add(entry.path);
|
|
265
|
+
}
|
|
266
|
+
const missing = [];
|
|
267
|
+
for (const entry of Object.values(source.tables?.workspaces ?? {})) {
|
|
268
|
+
if (typeof entry?.path !== 'string' || have.has(entry.path)) continue;
|
|
269
|
+
missing.push(typeof entry.title === 'string' && entry.title !== '' ? entry.title : entry.path);
|
|
270
|
+
}
|
|
271
|
+
return missing;
|
|
272
|
+
}
|
|
273
|
+
|
|
138
274
|
function mergeDataRoot() {
|
|
139
|
-
if (resolve(RUNTIME_ROOT) === resolve(HOME)) return;
|
|
140
|
-
|
|
275
|
+
if (resolve(RUNTIME_ROOT) === resolve(HOME)) return { added: 0, files: 0 };
|
|
276
|
+
const targetWorkspace = join(HOME, 'storages', 'workspace.json');
|
|
141
277
|
const sourceStorages = join(RUNTIME_ROOT, 'storages');
|
|
142
|
-
let
|
|
278
|
+
let added;
|
|
279
|
+
let files = 0;
|
|
143
280
|
try {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
281
|
+
added = mergeWorkspaceList(join(sourceStorages, 'workspace.json'), targetWorkspace);
|
|
282
|
+
if (added === null) return null;
|
|
283
|
+
files += mergeTree(join(RUNTIME_ROOT, 'sessions'), join(HOME, 'sessions'));
|
|
284
|
+
files += mergeTree(join(RUNTIME_ROOT, 'attachments'), join(HOME, 'attachments'));
|
|
285
|
+
files += mergeTree(sourceStorages, join(HOME, 'storages'), new Set(['workspace.json']));
|
|
148
286
|
} catch (error) {
|
|
149
|
-
warn(`并回 ${HOME}
|
|
150
|
-
return;
|
|
287
|
+
warn(`并回 ${HOME} 时出错:${error instanceof Error ? error.message : String(error)}`);
|
|
288
|
+
return null;
|
|
151
289
|
}
|
|
290
|
+
repairTarget(targetWorkspace);
|
|
291
|
+
return { added, files };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function writeMarker() {
|
|
152
295
|
mkdirSync(dirname(MARKER), { recursive: true });
|
|
153
296
|
writeFileSync(
|
|
154
297
|
MARKER,
|
|
155
298
|
`${JSON.stringify({ at: new Date().toISOString(), from: RUNTIME_ROOT, to: HOME }, null, 2)}\n`,
|
|
156
299
|
'utf8',
|
|
157
300
|
);
|
|
158
|
-
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
function inspectProfile() {
|
|
304
|
+
const patch = readText(PATCH_PATH);
|
|
305
|
+
if (patch === null) return null;
|
|
306
|
+
const legacy = parseBlocks(patch).filter((block) => DATA_ROOT_IDS.has(block.id));
|
|
307
|
+
return {
|
|
308
|
+
patch,
|
|
309
|
+
ours: legacy.filter((block) => OWN_ROOT.test(block.body)),
|
|
310
|
+
custom: legacy.filter((block) => !OWN_ROOT.test(block.body)),
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function customRootMessage(state) {
|
|
315
|
+
return `${PROFILE} 的会话 / 存储根是你自己改过的(${state.custom.map((block) => block.id).join(' / ')})—— 这次不动它,也不并数据。`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
function applySharedConfig(state) {
|
|
319
|
+
if (state.custom.length > 0) {
|
|
320
|
+
warn(customRootMessage(state));
|
|
321
|
+
return false;
|
|
322
|
+
}
|
|
323
|
+
let patch = state.patch;
|
|
324
|
+
if (state.ours.length > 0) {
|
|
325
|
+
const first = Math.min(...state.ours.map((block) => block.lineStart));
|
|
326
|
+
patch = stripIsolationHeader(patch, first);
|
|
327
|
+
patch = removeBlocks(patch, (block) => DATA_ROOT_IDS.has(block.id) && OWN_ROOT.test(block.body));
|
|
328
|
+
writeAtomic(PATCH_PATH, patch);
|
|
329
|
+
say(`数据根改回共用:从 ${PROFILE} 的 profile 里去掉 ${state.ours.map((block) => block.id).join(' / ')}。`);
|
|
330
|
+
}
|
|
331
|
+
const hasSandbox = parseBlocks(patch).some((block) => block.id === 'sandbox-policy');
|
|
332
|
+
if (!hasSandbox) {
|
|
333
|
+
const sandbox = readText(SANDBOX_PATCH);
|
|
334
|
+
const row = sandbox === null ? undefined : parseBlocks(sandbox).find((block) => block.id === 'sandbox-policy');
|
|
335
|
+
if (row !== undefined) {
|
|
336
|
+
patch = appendBlocks(patch, row.body);
|
|
337
|
+
writeAtomic(PATCH_PATH, patch);
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
repairTarget(join(HOME, 'storages', 'workspace.json'));
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function configureDefault() {
|
|
345
|
+
const state = inspectProfile();
|
|
346
|
+
if (state === null) return;
|
|
347
|
+
if (state.custom.length > 0) {
|
|
348
|
+
warn(customRootMessage(state));
|
|
349
|
+
return;
|
|
350
|
+
}
|
|
351
|
+
if (legacyDataExists() && !existsSync(MARKER)) {
|
|
352
|
+
warn(`检测到 ${RUNTIME_ROOT} 里还有旧会话 / 项目 / 附件,这次没有自动并。`);
|
|
353
|
+
warn('要切到共用数据根,先退出所有 dsh,再运行: dsh-workbench migrate');
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
applySharedConfig(state);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function migrate() {
|
|
360
|
+
const state = inspectProfile();
|
|
361
|
+
if (state === null) {
|
|
362
|
+
warn(`找不到 ${PATCH_PATH},先运行 dsh-workbench setup。`);
|
|
363
|
+
return 1;
|
|
364
|
+
}
|
|
365
|
+
if (state.custom.length > 0) {
|
|
366
|
+
warn(customRootMessage(state));
|
|
367
|
+
return 1;
|
|
368
|
+
}
|
|
369
|
+
if (!legacyDataExists()) {
|
|
370
|
+
applySharedConfig(state);
|
|
371
|
+
say(`没有发现 ${RUNTIME_ROOT} 里的旧数据,数据根已经是共用的。`);
|
|
372
|
+
return 0;
|
|
373
|
+
}
|
|
374
|
+
if (existsSync(MARKER) && !FORCE) {
|
|
375
|
+
const missing = missingWorkspacePaths();
|
|
376
|
+
if (missing.length > 0) {
|
|
377
|
+
say(`已经迁过一次;旧目录里还有 ${missing.length} 个项目不在共用根:${missing.join('、')}。要补并加 --force(这次 profile 保持原样)。`);
|
|
378
|
+
return 0;
|
|
379
|
+
}
|
|
380
|
+
applySharedConfig(state);
|
|
381
|
+
say('已经迁过一次;要再并一次加 --force。');
|
|
382
|
+
return 0;
|
|
383
|
+
}
|
|
384
|
+
warn('迁移前先退出所有 dsh(:3080 / :3081);否则正在运行的实例可能覆盖刚并好的数据。');
|
|
385
|
+
const result = mergeDataRoot();
|
|
386
|
+
if (result === null) {
|
|
387
|
+
warn('这次没有迁移;profile 和旧数据都保持原样。');
|
|
388
|
+
return 1;
|
|
389
|
+
}
|
|
390
|
+
writeMarker();
|
|
391
|
+
applySharedConfig(state);
|
|
392
|
+
say(`迁移完成:新增 ${result.added} 个项目,补了 ${result.files} 个文件;旧目录保留。`);
|
|
393
|
+
return 0;
|
|
159
394
|
}
|
|
160
395
|
|
|
161
396
|
function stripIsolationHeader(text, blockStart) {
|
|
@@ -174,38 +409,11 @@ function stripIsolationHeader(text, blockStart) {
|
|
|
174
409
|
return lines.join('\n');
|
|
175
410
|
}
|
|
176
411
|
|
|
412
|
+
const MIGRATE = process.argv.includes('--migrate') || process.argv.includes('migrate');
|
|
413
|
+
const FORCE = process.argv.includes('--force');
|
|
414
|
+
|
|
177
415
|
const profileManifest = join(PROFILE_DIR, 'package.json');
|
|
178
416
|
if (!existsSync(profileManifest)) process.exit(0);
|
|
179
417
|
|
|
180
|
-
|
|
181
|
-
|
|
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();
|
|
418
|
+
if (MIGRATE) process.exit(migrate());
|
|
419
|
+
configureDefault();
|