@dsh-workbench/core 0.2.6 → 0.2.7

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.
Files changed (32) hide show
  1. package/README.md +7 -7
  2. package/bin/dsh-workbench +2 -0
  3. package/client.js +252 -36
  4. package/dist/client/client.body.js +252 -36
  5. package/dist/client/index.js +252 -36
  6. package/dist/host/index.js +1054 -737
  7. package/dist/types/app/client.d.ts +21 -15
  8. package/dist/types/app/layouts/rightbar/__tests__/market-order.test.d.ts +1 -0
  9. package/dist/types/app/layouts/rightbar/__tests__/market-running-display.test.d.ts +1 -0
  10. package/dist/types/app/layouts/rightbar/__tests__/market-workbench-act.test.d.ts +1 -0
  11. package/dist/types/app/layouts/rightbar/market-act.d.ts +2 -1
  12. package/dist/types/app/layouts/rightbar/market-rows.d.ts +3 -0
  13. package/dist/types/app/layouts/rightbar/market-settle-hold.d.ts +13 -0
  14. package/dist/types/app/layouts/rightbar/market-settle.d.ts +22 -0
  15. package/dist/types/app/layouts/rightbar/market-transient-state.d.ts +22 -0
  16. package/dist/types/app/layouts/rightbar/market-view-text.d.ts +26 -1
  17. package/dist/types/modules/panels/__tests__/market-tools-harness.d.ts +3 -0
  18. package/dist/types/modules/panels/model/market-workbench-update.d.ts +19 -0
  19. package/dist/types/modules/panels/model/market.d.ts +4 -0
  20. package/dist/types/shared/api/bundle-install.d.ts +1 -0
  21. package/dist/types/shared/api/config-sync.d.ts +0 -2
  22. package/dist/types/shared/api/host/__tests__/post-channels.test.d.ts +1 -0
  23. package/dist/types/shared/api/host/__tests__/self-update.test.d.ts +1 -0
  24. package/dist/types/shared/api/host/bundle-manager.d.ts +1 -0
  25. package/dist/types/shared/api/host/bundle-routes.d.ts +1 -1
  26. package/dist/types/shared/api/host/config-sync.d.ts +2 -2
  27. package/dist/types/shared/api/host/post-channels.d.ts +18 -0
  28. package/dist/types/shared/api/host/self-update.d.ts +45 -0
  29. package/docs/HANDOFF.md +3 -3
  30. package/docs/OPERATIONS.md +33 -3
  31. package/package.json +4 -4
  32. package/scripts/seed-profile.sh +9 -0
package/README.md CHANGED
@@ -71,11 +71,11 @@ npx @dsh-workbench/core # ② 起工作台,默认 :3081
71
71
  | --- | --- |
72
72
  | 起工作台 | `npx @dsh-workbench/core` |
73
73
  | 换端口 | `WORKBENCH_PORT=3091 npx @dsh-workbench/core` |
74
- | 升级 | 重跑一次 `npx --yes @dsh-workbench/core@latest setup`(幂等),再重启工作台进程 |
74
+ | 升级 | 重跑一次 `npx --yes @dsh-workbench/core@latest setup`(幂等),刷新一次页面即可(`dsh-hmr` 会重组;它报 `restartReason` 时才重启进程) |
75
75
  | 重建 profile | `mv ~/.dsh/profiles/workbench ~/.dsh/profiles/workbench.bak`,再跑一次 `setup` |
76
76
  | 彻底不用了 | 删 `~/.dsh/profiles/workbench` 与 `~/.dsh/workbench` 两个目录(都不影响 `dsh web`) |
77
77
  | 改设置 | 在 `:3081` 的 Settings 里改(模型供应商 / 搜索源 / MCP 也是这里) |
78
- | 从 `dsh web` 搬设置过来 | 设置 → 「配置同步」→ 选来源 → 勾行 → 同步 → 重启工作台 |
78
+ | 从 `dsh web` 搬设置过来 | 设置 → 「配置同步」→ 选来源 → 勾行 → 同步(即生效) |
79
79
 
80
80
  ## 和 `dsh web` 的关系
81
81
 
@@ -87,7 +87,7 @@ npx @dsh-workbench/core # ② 起工作台,默认 :3081
87
87
  - **设置是工作台自己那份**:模型供应商 / 搜索源 / MCP 都在 `:3081` 的 Settings 里配,
88
88
  落在 `profiles/workbench/cordis.patch.yml` —— 那是它唯一的一份设置文档,每一行都改得动。
89
89
  想用 `dsh web` 那边已经配好的行:`:3081` → `Meta+,` → 「配置同步」→ 选来源 → 勾行 → 同步 →
90
- **重启工作台**(只搬显式配过的行;同 id 内容不同默认不覆盖)。web 后来改的不会自动过来,
90
+ **即生效**(只搬显式配过的行;同 id 内容不同默认不覆盖)。web 后来改的不会自动过来,
91
91
  再打开这一页拉一次即可。见 [ADR-0044](../../docs/adr/0044-config-sync-replaces-the-web-patch-overlay.md)。
92
92
  - ⚠️ **别在 Settings → 插件里把 `@dsh-workbench/core` 关掉**:关掉 = 它那份 bundle patch 整份不再应用
93
93
  (包括里面的"禁用出厂左栏"),界面会**退化成原版 dsh web**(左栏回来、右侧面板消失)。
@@ -103,8 +103,8 @@ npx @dsh-workbench/core # ② 起工作台,默认 :3081
103
103
  | 打开面板 / 按刷新要等 5–8 秒 | 正常:opencli 每次调用有**固定约 5 秒**开销(起浏览器会话)。**没有缓存**,数据一律实时 |
104
104
  | 体检那屏说「检测浏览器通道 · 没连上」,可面板照样能用 | 本机全局 opencli 与隔离运行时版本不一致,daemon 被顶成对方那版;下一条浏览器命令会自动把它重启回来,体检复检后改回「已连接」 |
105
105
  | 一切正常,但左栏是出厂样子、右边没有面板 | 工作台插件在 Settings 里被关了(见上一条 ⚠️) |
106
- | 模型列表里只有出厂的几个、没有自己配的供应商 | 新装的 profile 不带设置(ADR-0044):设置 → 「配置同步」→ 选来源 → 勾 `llm-` 那几行 → 同步 → 重启 |
107
- | 「配置同步」里某一行说 `@x 还没装` | 那一行点名的包不在这个 profile 的 `node_modules` 里:`dsh plugin --profile workbench add @x`(或在模块市场装),再重启 |
106
+ | 模型列表里只有出厂的几个、没有自己配的供应商 | 新装的 profile 不带设置(ADR-0044):设置 → 「配置同步」→ 选来源 → 勾 `llm-` 那几行 → 同步(即生效)|
107
+ | 「配置同步」里某一行说 `@x 还没装` | 那一行点名的包不在这个 profile 的 `node_modules` 里:`dsh plugin --profile workbench add @x`(或在模块市场装),(装完 dsh 会自行重组;它报 `restartReason` 时才重启)|
108
108
 
109
109
  排障全表在仓库的 [`docs/OPERATIONS.md`](https://github.com/code-farmer-i/dsh-workbench/blob/main/packages/workbench/docs/OPERATIONS.md);
110
110
  把工作台装到别人机器上的前置清单见 [`docs/HANDOFF.md`](https://github.com/code-farmer-i/dsh-workbench/blob/main/packages/workbench/docs/HANDOFF.md)。
@@ -116,7 +116,7 @@ git clone git@github.com:code-farmer-i/dsh-workbench.git
116
116
  cd dsh-workbench/packages/workbench
117
117
  pnpm install
118
118
  pnpm run dev # 监听 src/**(重建产物)+ 起工作台 :3081;改完不用重启、不用刷新页面
119
- pnpm run check # 门禁:类型 / 覆盖率 / 架构 / 运行时边界 / 接线自检
119
+ pnpm run check # 门禁:整仓那一套(类型 / lint / 覆盖率 / 架构 / 文档 / 运行时边界 / 接线 / 模块判据)
120
120
  ./bin/dsh-workbench # 只想起工作台、不需要监听改动时用它
121
121
  ```
122
122
 
@@ -124,7 +124,7 @@ pnpm run check # 门禁:类型 / 覆盖率 / 架构 / 运行时边界 / 接
124
124
  `:3081` 已经有实例在跑时就只监听、不重复起第二个。改界面时开着它就够了:
125
125
  浏览器半侧重建 `client.js` 后由 dsh 的 HMR 热换插件(不刷新页面),宿主半侧按模块路径热重载。
126
126
 
127
- 只有两种情况要重启进程:改 `cordis.patch.yml`(组合层)与 `package.json` 的 bundle 清单。
127
+ `cordis.patch.yml` 与 `package.json` 的 bundle 清单也**不用重启**(`dsh-hmr` 变了就重组);要重启的是改 `vite.config.ts`。
128
128
 
129
129
  结构与边界(两个运行时、槽位接线、模块包的门禁)见仓库的
130
130
  [`docs/ARCHITECTURE.md`](https://github.com/code-farmer-i/dsh-workbench/blob/main/docs/ARCHITECTURE.md) ·
package/bin/dsh-workbench CHANGED
@@ -69,6 +69,8 @@ run_setup() {
69
69
  else
70
70
  SPEC="$PKG@^$(pkg_version)"
71
71
  echo "→ 从内网源装 ${SPEC}(源在 package.json 的 publishConfig.registry)"
72
+ echo " 这一份启动器是装在哪就是哪一代:换 profile / 重建 profile 之前,先"
73
+ echo " npm i -g ${PKG}@latest。已经在跑的那个 profile 升级走市场里工作台那一行的「更新」。"
72
74
  fi
73
75
 
74
76
  dsh plugin --profile "$PROFILE" add "$SPEC"
package/client.js CHANGED
@@ -1388,6 +1388,7 @@ window.__ModuleLoader__.load({
1388
1388
  "panels.market.lead": "Installed on this machine, shared by every project.",
1389
1389
  "panels.market.install": "Install",
1390
1390
  "panels.market.update": "Update",
1391
+ "panels.market.swapSlow": "Not swapped yet — maybe a slow or failed download. Press Update again; if it stays like this, restart dsh.",
1391
1392
  "panels.market.uninstall": "Uninstall",
1392
1393
  "panels.market.uninstallLabel": "Uninstall {name}",
1393
1394
  "panels.market.installing": "Installing…",
@@ -1639,7 +1640,7 @@ window.__ModuleLoader__.load({
1639
1640
  "panel.conversation": "Conversation",
1640
1641
  "panels.label": "Global panels",
1641
1642
  "configSync.nav": "Config sync",
1642
- "configSync.lead": "Bring over the rows another profile configures explicitly — pick any profile as the source. A restart applies them.",
1643
+ "configSync.lead": "Bring over the rows another profile configures explicitly — pick any profile as the source.",
1643
1644
  "configSync.source": "Source",
1644
1645
  "configSync.sourceBroken": "its config is unreadable",
1645
1646
  "configSync.sourceRows": "{name}: {count} rows are new or different for the workbench",
@@ -1663,7 +1664,7 @@ window.__ModuleLoader__.load({
1663
1664
  "configSync.apply": "Sync {count} selected",
1664
1665
  "configSync.applyNone": "Pick some rows first",
1665
1666
  "configSync.applying": "Writing…",
1666
- "configSync.applied": "Written to the workbench config · restart to apply",
1667
+ "configSync.applied": "Written to the workbench config · applied",
1667
1668
  "configSync.appliedIds": "Written this time: {ids}",
1668
1669
  "configSync.skippedIds": "Left alone (already here): {ids}",
1669
1670
  "configSync.nothing": "Nothing to sync",
@@ -1782,6 +1783,7 @@ window.__ModuleLoader__.load({
1782
1783
  "panels.market.lead": "装在这台机器上,所有项目共用。",
1783
1784
  "panels.market.install": "安装",
1784
1785
  "panels.market.update": "更新",
1786
+ "panels.market.swapSlow": "还没换上:可能网慢或没装上。再点一次「更新」;一直这样就把 dsh 重启一遍。",
1785
1787
  "panels.market.uninstall": "卸载",
1786
1788
  "panels.market.uninstallLabel": "卸载 {name}",
1787
1789
  "panels.market.installing": "正在安装…",
@@ -2033,7 +2035,7 @@ window.__ModuleLoader__.load({
2033
2035
  "panel.conversation": "对话",
2034
2036
  "panels.label": "全局面板",
2035
2037
  "configSync.nav": "配置同步",
2036
- "configSync.lead": "把另一个 profile 里显式配过的行搬过来 —— 来源随你选。搬完要重启工作台才生效。",
2038
+ "configSync.lead": "把另一个 profile 里显式配过的行搬过来 —— 来源随你选。",
2037
2039
  "configSync.source": "来源",
2038
2040
  "configSync.sourceBroken": "读不到它的配置",
2039
2041
  "configSync.sourceRows": "{name} 这份里你有 {count} 行可搬",
@@ -2057,7 +2059,7 @@ window.__ModuleLoader__.load({
2057
2059
  "configSync.apply": "同步选中的 {count} 行",
2058
2060
  "configSync.applyNone": "先勾几行",
2059
2061
  "configSync.applying": "正在写入…",
2060
- "configSync.applied": "已写入工作台配置 · 重启工作台后生效",
2062
+ "configSync.applied": "已写入工作台配置 · 已生效",
2061
2063
  "configSync.appliedIds": "这次写进去:{ids}",
2062
2064
  "configSync.skippedIds": "这几行工作台自己已有,没动:{ids}",
2063
2065
  "configSync.nothing": "没有需要同步的内容",
@@ -2887,6 +2889,38 @@ window.__ModuleLoader__.load({
2887
2889
  const fresh = previous === null ? [] : pending.filter((unit) => !previous.has(unit));
2888
2890
  return pending.filter((unit) => active.has(unit) || fresh.includes(unit));
2889
2891
  }
2892
+ var WORKBENCH_SWAP_MS = 18e4;
2893
+ function swapLanded(until, installed) {
2894
+ return until !== void 0 && installed === until;
2895
+ }
2896
+ function swapUntil(action, application, latest) {
2897
+ if (action !== "update-workbench" || application !== "restart-required") return void 0;
2898
+ return latest;
2899
+ }
2900
+ var READ_BACK_ACTIONS = /* @__PURE__ */ new Set(["update", "update-bundle"]);
2901
+ function awaitsReadBack(action, application, version) {
2902
+ return application === "applied" && version !== void 0 && READ_BACK_ACTIONS.has(action);
2903
+ }
2904
+ function readBackDone(input) {
2905
+ if (input.action === "update") return !input.outdated;
2906
+ if (input.entry === void 0 || input.entry.local === true || input.entry.latest === void 0) return true;
2907
+ if (input.entry.latest === input.entry.installed) return true;
2908
+ if (input.clicked === void 0) return true;
2909
+ return input.entry.installed !== input.clicked;
2910
+ }
2911
+ function settleLanded(input) {
2912
+ if (input.until !== void 0) return swapLanded(input.until, input.installed);
2913
+ if (input.readBack !== true) return true;
2914
+ return readBackDone({
2915
+ action: input.action,
2916
+ entry: input.entry,
2917
+ clicked: input.clicked,
2918
+ outdated: input.outdated
2919
+ });
2920
+ }
2921
+ function holdWindowMs(until) {
2922
+ return until === void 0 ? MODULE_SETTLE_MS : WORKBENCH_SWAP_MS;
2923
+ }
2890
2924
  //#endregion
2891
2925
  //#region src/app/layouts/rightbar/market-installed-mock.ts
2892
2926
  function installedMocks(input) {
@@ -4019,6 +4053,7 @@ window.__ModuleLoader__.load({
4019
4053
  key: "panels.market.workbenchLocal",
4020
4054
  params: {}
4021
4055
  } : void 0;
4056
+ const stale = !local && version.running !== void 0 && version.running !== version.installed;
4022
4057
  return {
4023
4058
  id: WORKBENCH_ROW_ID,
4024
4059
  titleKey: WORKBENCH_TITLE_KEY,
@@ -4028,6 +4063,7 @@ window.__ModuleLoader__.load({
4028
4063
  canUpdate: update !== void 0,
4029
4064
  canUninstall: false,
4030
4065
  ...note === void 0 ? {} : { note },
4066
+ ...stale ? { hint: "panels.market.moduleRestartHint" } : {},
4031
4067
  version: {
4032
4068
  installed: version.installed,
4033
4069
  ...latest === void 0 ? {} : { latest }
@@ -4090,6 +4126,10 @@ window.__ModuleLoader__.load({
4090
4126
  }
4091
4127
  return next ?? known;
4092
4128
  }
4129
+ function installedIdSet(installed) {
4130
+ if (installed === void 0) return null;
4131
+ return new Set(installed.map((item) => item.unit));
4132
+ }
4093
4133
  function installedRows(modules, manifests, listed, known, defaults = /* @__PURE__ */ new Set(), changed = {}) {
4094
4134
  const titles = new Map(manifests.map((manifest) => [manifest.id, manifest.card?.titleKey]));
4095
4135
  const rows = [];
@@ -4152,6 +4192,9 @@ window.__ModuleLoader__.load({
4152
4192
  if (capability === void 0) return false;
4153
4193
  return capability.status === "missing-commands";
4154
4194
  }
4195
+ function capabilityOutdated(capabilities, unit) {
4196
+ return isOutdated(capabilities?.[unit]);
4197
+ }
4155
4198
  function envNameOf(source) {
4156
4199
  if (source === void 0) return "";
4157
4200
  if (source.kind === "local") return source.env;
@@ -4213,6 +4256,12 @@ window.__ModuleLoader__.load({
4213
4256
  const self = workbenchRow(workbench);
4214
4257
  return marketHasUpdates([...marketRows(manifests, capabilities, marketUpdatesOf(modules)), ...self === void 0 ? [] : [self]], sharedRows(manifests, capabilities));
4215
4258
  }
4259
+ function marketOrder(rows, titleOf) {
4260
+ return [...rows].sort((left, right) => {
4261
+ const byTitle = titleOf(left).localeCompare(titleOf(right), "zh-Hans-CN");
4262
+ return byTitle === 0 ? left.id.localeCompare(right.id) : byTitle;
4263
+ });
4264
+ }
4216
4265
  function marketSections(rows) {
4217
4266
  const installed = rows.filter((row) => isOnMachine(row.state));
4218
4267
  const rest = rows.filter((row) => !isOnMachine(row.state));
@@ -4350,6 +4399,74 @@ window.__ModuleLoader__.load({
4350
4399
  return busyView(active, visible);
4351
4400
  }
4352
4401
  //#endregion
4402
+ //#region src/app/layouts/rightbar/market-settle-hold.ts
4403
+ function useSettleHold(input) {
4404
+ const { pending, landed, t, onChanged, setPending, setFailed } = input;
4405
+ const until = pending?.until;
4406
+ const id = pending?.id ?? "";
4407
+ const watching = pending !== null && (pending.until !== void 0 || pending.readBack === true);
4408
+ const sinceRef = useRef(0);
4409
+ useEffect(() => {
4410
+ if (!watching || !landed) return;
4411
+ setPending(null);
4412
+ }, [
4413
+ watching,
4414
+ landed,
4415
+ setPending
4416
+ ]);
4417
+ useEffect(() => {
4418
+ if (!watching) {
4419
+ sinceRef.current = 0;
4420
+ return;
4421
+ }
4422
+ if (sinceRef.current === 0) sinceRef.current = Date.now();
4423
+ const deadline = sinceRef.current + holdWindowMs(until);
4424
+ const tick = window.setInterval(() => {
4425
+ onChanged();
4426
+ if (Date.now() < deadline) return;
4427
+ window.clearInterval(tick);
4428
+ setPending(null);
4429
+ setFailed({
4430
+ id,
4431
+ message: t("panels.market.swapSlow")
4432
+ });
4433
+ }, 800);
4434
+ return () => window.clearInterval(tick);
4435
+ }, [
4436
+ watching,
4437
+ landed,
4438
+ until,
4439
+ id,
4440
+ onChanged,
4441
+ setPending,
4442
+ setFailed,
4443
+ t
4444
+ ]);
4445
+ }
4446
+ //#endregion
4447
+ //#region src/app/layouts/rightbar/market-transient-state.ts
4448
+ function useMarketTransient(input) {
4449
+ const { mock, onPending } = input;
4450
+ const [pending, setPending] = useState(mock?.pending ?? null);
4451
+ const [confirming, setConfirming] = useState(mock?.confirming ?? null);
4452
+ const [failed, setFailed] = useState(mock?.failure ?? null);
4453
+ const [notice, setNotice] = useState(mock?.notice ?? null);
4454
+ useEffect(() => {
4455
+ onPending?.(pending !== null);
4456
+ return () => onPending?.(false);
4457
+ }, [pending, onPending]);
4458
+ return {
4459
+ pending,
4460
+ setPending,
4461
+ confirming,
4462
+ setConfirming,
4463
+ failed,
4464
+ setFailed,
4465
+ notice,
4466
+ setNotice
4467
+ };
4468
+ }
4469
+ //#endregion
4353
4470
  //#region src/app/layouts/rightbar/market-plugin-rows.ts
4354
4471
  function installedPluginKept(value) {
4355
4472
  if (typeof value !== "object" || value === null) return void 0;
@@ -4512,6 +4629,80 @@ window.__ModuleLoader__.load({
4512
4629
  "uninstall-plugin": "panels.market.uninstall",
4513
4630
  "update-workbench": "panels.market.update"
4514
4631
  };
4632
+ function holdOf(pending, application) {
4633
+ if (pending === null || pending.row === void 0) return void 0;
4634
+ if (application !== "applied") return void 0;
4635
+ return {
4636
+ id: pending.id,
4637
+ row: pending.row
4638
+ };
4639
+ }
4640
+ function pendingLanded(pending, updates, capabilities, installed) {
4641
+ if (pending === null) return true;
4642
+ return settleLanded({
4643
+ action: pending.action,
4644
+ until: pending.until,
4645
+ readBack: pending.readBack,
4646
+ installed,
4647
+ entry: updates[pending.id],
4648
+ clicked: pending.version,
4649
+ outdated: capabilityOutdated(capabilities, pending.id)
4650
+ });
4651
+ }
4652
+ function holdExpired(held, backInRoster, elapsedMs) {
4653
+ if (held === void 0) return true;
4654
+ return backInRoster || elapsedMs >= 15e3;
4655
+ }
4656
+ function heldIdOf(pending, held) {
4657
+ return pending?.id ?? held?.id ?? null;
4658
+ }
4659
+ function pinnedRowOf(pending, held) {
4660
+ return pending?.row ?? held?.row;
4661
+ }
4662
+ function withPinnedRow(rows, pinned) {
4663
+ if (pinned === void 0) return [...rows];
4664
+ if (!rows.some((row) => row.id === pinned.id)) return [...rows, pinned];
4665
+ return rows.map((row) => row.id === pinned.id ? pinned : row);
4666
+ }
4667
+ function marketFootVisible(shown, installed) {
4668
+ return shown.length > 0 || installed.length > 0;
4669
+ }
4670
+ function useMarketTitles(manifests, mocked) {
4671
+ const [titles, setTitles] = useState(/* @__PURE__ */ new Map());
4672
+ useEffect(() => {
4673
+ if (!mocked) setTitles((known) => mergeTitles(known, manifests));
4674
+ }, [manifests, mocked]);
4675
+ return titles;
4676
+ }
4677
+ function useHeldRow(pending, backInRoster) {
4678
+ const [held, setHeld] = useState(void 0);
4679
+ const heldAtRef = useRef(0);
4680
+ const hold = useCallback((next) => {
4681
+ if (next !== void 0) heldAtRef.current = Date.now();
4682
+ setHeld(next);
4683
+ }, []);
4684
+ const back = held === void 0 ? false : backInRoster(held.id);
4685
+ useEffect(() => {
4686
+ if (held === void 0) return;
4687
+ if (holdExpired(held, back, Date.now() - heldAtRef.current)) {
4688
+ setHeld(void 0);
4689
+ return;
4690
+ }
4691
+ const timer = setTimeout(() => setHeld(void 0), MODULE_SETTLE_MS);
4692
+ return () => clearTimeout(timer);
4693
+ }, [held, back]);
4694
+ useEffect(() => {
4695
+ if (pending !== null) setHeld(void 0);
4696
+ }, [pending]);
4697
+ return {
4698
+ held,
4699
+ hold
4700
+ };
4701
+ }
4702
+ function removingIdOf(pending, failed) {
4703
+ if (pending === null || pending.action !== "remove-bundle") return null;
4704
+ return failed?.id === pending.id ? null : pending.id;
4705
+ }
4515
4706
  function pendingRowOf(input) {
4516
4707
  const { pending, removing } = input;
4517
4708
  if (removing === null || pending === null) return void 0;
@@ -4908,18 +5099,21 @@ window.__ModuleLoader__.load({
4908
5099
  }
4909
5100
  //#endregion
4910
5101
  //#region src/app/layouts/rightbar/market-act.ts
5102
+ var STEADY_ACTIONS = /* @__PURE__ */ new Set(["update", "update-bundle"]);
4911
5103
  function marketActOf(options) {
4912
5104
  const { t, mocked, rowOf, seats, send, onChanged } = options;
4913
5105
  return async (id, action) => {
4914
5106
  if (mocked) return;
4915
5107
  const row = rowOf(id);
4916
- seats.setPending({
5108
+ const next = {
4917
5109
  id,
4918
5110
  action,
4919
5111
  ...row?.titleKey === void 0 ? {} : { titleKey: row.titleKey },
4920
5112
  ...row?.pkg === void 0 ? {} : { pkg: row.pkg },
4921
- ...row?.version?.installed === void 0 ? {} : { version: row.version.installed }
4922
- });
5113
+ ...row?.version?.installed === void 0 ? {} : { version: row.version.installed },
5114
+ ...row === void 0 || !STEADY_ACTIONS.has(action) ? {} : { row }
5115
+ };
5116
+ seats.setPending(next);
4923
5117
  seats.setConfirming(null);
4924
5118
  seats.setFailed(null);
4925
5119
  if (action !== "uninstall-plugin") seats.setNotice(null);
@@ -4933,8 +5127,9 @@ window.__ModuleLoader__.load({
4933
5127
  action,
4934
5128
  unit: id
4935
5129
  });
4936
- seats.setPending(null);
4937
5130
  if (!outcome.ok) {
5131
+ seats.setPending(null);
5132
+ seats.setHeld(void 0);
4938
5133
  onChanged();
4939
5134
  seats.setFailed({
4940
5135
  id,
@@ -4942,8 +5137,17 @@ window.__ModuleLoader__.load({
4942
5137
  });
4943
5138
  return;
4944
5139
  }
5140
+ const change = changeOf(outcome.value);
5141
+ const until = swapUntil(action, change?.application, row?.version?.latest);
5142
+ const readBack = awaitsReadBack(action, change?.application, next.version);
5143
+ seats.setPending(until === void 0 && !readBack ? null : {
5144
+ ...next,
5145
+ ...until === void 0 ? {} : { until },
5146
+ ...readBack ? { readBack: true } : {}
5147
+ });
5148
+ seats.setHeld(readBack ? void 0 : holdOf(next, change?.application));
4945
5149
  if (action === "uninstall-plugin") seats.setNotice(installedPluginNotice(outcome.value, t));
4946
- const reason = changeFailure(changeOf(outcome.value), t);
5150
+ const reason = changeFailure(change, t);
4947
5151
  onChanged();
4948
5152
  if (reason !== void 0) seats.setFailed({
4949
5153
  id,
@@ -4986,6 +5190,7 @@ window.__ModuleLoader__.load({
4986
5190
  }
4987
5191
  function MarketBody({ t, self, shown, pluginList, installedList, shared, pending, failed, notice, act, onConfirming, setDefault, boardBusy }) {
4988
5192
  const failureOf = (id) => failed?.id === id ? failed.message : null;
5193
+ const foot = marketFootVisible(shown, installedList);
4989
5194
  return /* @__PURE__ */ h(Fragment, null, notice === null ? null : /* @__PURE__ */ h("div", { className: MarketPage_module_default.marketNote }, notice), /* @__PURE__ */ h(MarketSection, {
4990
5195
  t,
4991
5196
  rows: self === void 0 ? [] : [self],
@@ -4999,7 +5204,7 @@ window.__ModuleLoader__.load({
4999
5204
  }), self === void 0 ? null : /* @__PURE__ */ h("div", {
5000
5205
  className: MarketPage_module_default.marketSelfDivider,
5001
5206
  "aria-hidden": "true"
5002
- }), marketSections([...shown, ...installedList]).map((section) => /* @__PURE__ */ h(MarketSection, {
5207
+ }), marketSections(marketOrder([...shown, ...installedList], (row) => marketRowTitle(row, t))).map((section) => /* @__PURE__ */ h(MarketSection, {
5003
5208
  key: section.id,
5004
5209
  t,
5005
5210
  titleKey: section.titleKey,
@@ -5024,10 +5229,10 @@ window.__ModuleLoader__.load({
5024
5229
  act,
5025
5230
  confirmActions: CONFIRM_UNINSTALL,
5026
5231
  onConfirming
5027
- }), /* @__PURE__ */ h(PanelFoot, {
5232
+ }), foot ? /* @__PURE__ */ h(PanelFoot, {
5028
5233
  size: "md",
5029
5234
  text: t("panels.market.foot")
5030
- }), /* @__PURE__ */ h(SharedSection, {
5235
+ }) : null, /* @__PURE__ */ h(SharedSection, {
5031
5236
  t,
5032
5237
  rows: shared,
5033
5238
  pending,
@@ -5036,22 +5241,19 @@ window.__ModuleLoader__.load({
5036
5241
  }));
5037
5242
  }
5038
5243
  function MarketView({ t, manifests, installable, capabilities, freshness, onChanged, onPending, updates, installed, installedRead, workbench, plugins, settling, mock, defaults = [], changed = {} }) {
5039
- const [titles, setTitles] = useState(/* @__PURE__ */ new Map());
5040
- useEffect(() => {
5041
- if (mock === void 0) setTitles((known) => mergeTitles(known, manifests));
5042
- }, [manifests, mock]);
5244
+ const titles = useMarketTitles(manifests, mock !== void 0);
5043
5245
  const catalog = mock?.manifests ?? manifests;
5044
5246
  const marketable = mock === void 0 ? marketCatalog(manifests, installable) : catalog;
5045
5247
  const effective = mock === void 0 ? capabilities : mock.capabilities;
5046
- const source = liveManifests(marketable, effective, mock === void 0 && installed !== void 0 ? new Set(installed.map((item) => item.unit)) : null);
5248
+ const source = liveManifests(marketable, effective, installedIdSet(mock === void 0 ? installed : void 0));
5047
5249
  const byDefault = new Set(defaults);
5048
- const rows = marketRows(source, effective, mock?.updates ?? updates ?? {}, byDefault, changed);
5250
+ const updateMap = mock?.updates ?? updates ?? {};
5251
+ const rows = marketRows(source, effective, updateMap, byDefault, changed);
5049
5252
  const shared = sharedRows(source, effective);
5050
- const [pending, setPending] = useState(mock?.pending ?? null);
5051
- useEffect(() => {
5052
- onPending?.(pending !== null);
5053
- return () => onPending?.(false);
5054
- }, [pending, onPending]);
5253
+ const { pending, setPending, confirming, setConfirming, failed, setFailed, notice, setNotice } = useMarketTransient({
5254
+ mock,
5255
+ onPending
5256
+ });
5055
5257
  const provision = useHostWrite({
5056
5258
  channel: "provision",
5057
5259
  networkText: t("panels.failed")
@@ -5079,19 +5281,19 @@ window.__ModuleLoader__.load({
5079
5281
  setNotice(t("panels.failed"));
5080
5282
  }
5081
5283
  };
5082
- const [confirming, setConfirming] = useState(mock?.confirming ?? null);
5083
- const [failed, setFailed] = useState(mock?.failure ?? null);
5084
- const [notice, setNotice] = useState(mock?.notice ?? null);
5085
- const removing = pending !== null && pending.action === "remove-bundle" && failed?.id !== pending.id ? pending.id : null;
5086
- const installedList = installedRows((mock?.installed ?? installed ?? []).filter((module) => !(settling?.has(module.unit) ?? false) && module.unit !== removing), catalog, new Set(rows.map((row) => row.id)), titles, byDefault, changed);
5284
+ const removing = removingIdOf(pending, failed);
5285
+ const steady = useHeldRow(pending, (id) => catalog.some((manifest) => manifest.id === id));
5286
+ const pinned = pinnedRowOf(pending, steady.held);
5287
+ const installedList = installedRows((mock?.installed ?? installed ?? []).filter((module) => module.unit !== pinned?.id && (!(settling?.has(module.unit) ?? false) || module.unit === heldIdOf(pending, steady.held)) && module.unit !== removing), catalog, new Set(rows.map((row) => row.id)), titles, byDefault, changed);
5087
5288
  const pendingRow = pendingRowOf({
5088
5289
  pending,
5089
5290
  removing,
5090
5291
  rows,
5091
5292
  titles
5092
5293
  });
5093
- const shown = pendingRow === void 0 ? rows : [...rows, pendingRow];
5094
- const self = workbenchRow(mock === void 0 ? workbench : mock.workbench);
5294
+ const selfRow = workbenchRow(mock === void 0 ? workbench : mock.workbench);
5295
+ const shown = withPinnedRow(pendingRow === void 0 ? rows : [...rows, pendingRow], pinned);
5296
+ const self = selfRow;
5095
5297
  const pluginList = installedPluginRows(mock?.plugins ?? plugins ?? [], byDefault, changed);
5096
5298
  const rowById = new Map([
5097
5299
  ...shown,
@@ -5100,19 +5302,29 @@ window.__ModuleLoader__.load({
5100
5302
  ...pluginList
5101
5303
  ].map((row) => [row.id, row]));
5102
5304
  const confirmingRow = confirming === null ? void 0 : rowById.get(confirming);
5305
+ const seats = {
5306
+ setPending,
5307
+ setHeld: steady.hold,
5308
+ setConfirming,
5309
+ setFailed,
5310
+ setNotice
5311
+ };
5103
5312
  const act = marketActOf({
5104
5313
  t,
5105
5314
  mocked: mock !== void 0,
5106
5315
  rowOf: (id) => rowById.get(id),
5107
- seats: {
5108
- setPending,
5109
- setConfirming,
5110
- setFailed,
5111
- setNotice
5112
- },
5316
+ seats,
5113
5317
  send: (body) => provision.send(body),
5114
5318
  onChanged
5115
5319
  });
5320
+ useSettleHold({
5321
+ pending,
5322
+ landed: pendingLanded(pending, updateMap, effective, workbench?.installed),
5323
+ t,
5324
+ onChanged,
5325
+ setPending,
5326
+ setFailed
5327
+ });
5116
5328
  const settled = marketSettled(freshness, installedRead);
5117
5329
  const nothing = shown.length === 0 && self === void 0 && installedList.length === 0 && pluginList.length === 0;
5118
5330
  if (!settled || (settling?.size ?? 0) > 0 && shown.length === 0 && installedList.length === 0) return /* @__PURE__ */ h(MarketChecking, { t });
@@ -6871,6 +7083,10 @@ window.__ModuleLoader__.load({
6871
7083
  }) : null, rows.length > 0 ? /* @__PURE__ */ h("div", { className: ConfigSyncSection_module_default.foot }, /* @__PURE__ */ h(_deepseek_ai_dsh_client_ui_primitives.Button, {
6872
7084
  variant: "primary",
6873
7085
  size: "md",
7086
+ icon: /* @__PURE__ */ h(_deepseek_ai_dsh_client_ui_primitives.IconRefreshOutlineRegular, {
7087
+ size: 16,
7088
+ "aria-hidden": "true"
7089
+ }),
6874
7090
  disabled: chosen.length === 0 || busy.busy,
6875
7091
  onClick: apply
6876
7092
  }, chosen.length === 0 ? t("configSync.applyNone") : busy.busy ? t("configSync.applying") : t("configSync.apply", { count: chosen.length }))) : null) : null));