@dsh-workbench/core 1.1.14-beta.1 → 1.1.14

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 (40) hide show
  1. package/README.md +1 -1
  2. package/bin/dsh-workbench +6 -52
  3. package/client.js +59 -16
  4. package/cordis.patch.yml +5 -6
  5. package/dist/client/client.body.js +59 -16
  6. package/dist/client/index.js +59 -16
  7. package/dist/host/index.js +1665 -623
  8. package/dist/types/app/layouts/env-gate/gate-text.d.ts +2 -3
  9. package/dist/types/app/units/manifests.d.ts +2 -0
  10. package/dist/types/kernel/host/mount-units.d.ts +2 -1
  11. package/dist/types/modules/panels/model/diy-browser-act.d.ts +3 -3
  12. package/dist/types/modules/panels/model/diy-browser-clip.d.ts +14 -0
  13. package/dist/types/modules/panels/model/diy-browser-tools.d.ts +1 -14
  14. package/dist/types/modules/panels/model/diy-browser.d.ts +6 -6
  15. package/dist/types/modules/panels/model/diy-page.d.ts +3 -2
  16. package/dist/types/modules/panels/model/diy-record-open.d.ts +2 -2
  17. package/dist/types/modules/panels/model/diy-record.d.ts +12 -11
  18. package/dist/types/modules/panels/model/market-discover.d.ts +2 -2
  19. package/dist/types/modules/panels/model/market-follow-up.d.ts +2 -1
  20. package/dist/types/modules/panels/model/market-route.d.ts +2 -3
  21. package/dist/types/modules/panels/model/market-update-bundle.d.ts +2 -3
  22. package/dist/types/modules/panels/model/market-workbench-update.d.ts +2 -2
  23. package/dist/types/modules/panels/model/market.d.ts +2 -2
  24. package/dist/types/modules/panels/model/plugin-actions.d.ts +2 -3
  25. package/dist/types/modules/panels/model/plugin-section.d.ts +3 -4
  26. package/dist/types/shared/api/host/bundle-routes.d.ts +3 -3
  27. package/dist/types/shared/api/host/diy-artifacts.d.ts +1 -2
  28. package/dist/types/shared/api/host/diy-components.d.ts +2 -2
  29. package/dist/types/shared/api/host/diy-release-staging.d.ts +6 -0
  30. package/dist/types/shared/api/host/diy.d.ts +1 -1
  31. package/dist/types/shared/api/host/git-exec.d.ts +1 -1
  32. package/dist/types/shared/api/host/git-process.d.ts +3 -0
  33. package/dist/types/shared/api/host/shipped-adapter-set.d.ts +4 -0
  34. package/dist/types/shared/api/host/workbench-seam.d.ts +3 -0
  35. package/package.json +3 -6
  36. package/scripts/seed-profile.sh +3 -3
  37. package/scripts/share-data-root.mjs +10 -13
  38. package/skills/workbench-module-diy/BUILD.md +38 -0
  39. package/skills/workbench-module-diy/SKILL.md +1 -0
  40. package/profile/sandbox.patch.yml +0 -28
@@ -1,14 +1,14 @@
1
- import path, { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
1
+ import { homedir, tmpdir } from "node:os";
2
2
  import dayjs from "dayjs";
3
- import fs, { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
4
- import { homedir } from "node:os";
5
- import { fileURLToPath, pathToFileURL } from "node:url";
3
+ import fs, { accessSync, closeSync, constants, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
4
+ import path, { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
5
+ import writeFileAtomic from "write-file-atomic";
6
6
  import utc from "dayjs/plugin/utc.js";
7
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
8
  import { createHash, randomUUID } from "node:crypto";
8
9
  import { isDeepStrictEqual } from "node:util";
9
- import { createRequire } from "node:module";
10
- import writeFileAtomic from "write-file-atomic";
11
10
  import { execa } from "execa";
11
+ import { createRequire } from "node:module";
12
12
  import { deflateSync, inflateSync } from "node:zlib";
13
13
  //#region \0rolldown/runtime.js
14
14
  var __create = Object.create;
@@ -566,7 +566,12 @@ var TOOL_KEYS = [
566
566
  "key"
567
567
  ];
568
568
  var TOOL_KEY_SHAPE = /^[a-z][a-zA-Z0-9]*$/;
569
- var HOST_KEYS = ["inject", "optional"];
569
+ var HOST_KEYS = [
570
+ "inject",
571
+ "optional",
572
+ "writes"
573
+ ];
574
+ var UNIT_WRITE_SCOPES = ["unit", "workspace"];
570
575
  function text$2(value) {
571
576
  return typeof value === "string" ? value.trim() : "";
572
577
  }
@@ -666,6 +671,14 @@ function declarationProblemsOf(subject, raw) {
666
671
  if (host.inject === void 0) problems.push(`${subject}: dsh.workbench.host.inject 没写 —— 它声明这个模块要外壳的哪几个服务,外壳靠它判"能不能挂"。一个都不要也要写 \`"inject": []\``);
667
672
  else problems.push(...stringArrayProblems(`${subject}: dsh.workbench.host.inject`, host.inject));
668
673
  if (host.optional !== void 0) problems.push(...stringArrayProblems(`${subject}: dsh.workbench.host.optional`, host.optional));
674
+ if (host.writes !== void 0) {
675
+ const shape = stringArrayProblems(`${subject}: dsh.workbench.host.writes`, host.writes);
676
+ problems.push(...shape);
677
+ if (shape.length === 0) {
678
+ const outsideScopes = host.writes.filter((scope) => !UNIT_WRITE_SCOPES.includes(scope));
679
+ if (outsideScopes.length > 0) problems.push(`${subject}: dsh.workbench.host.writes 里有不认的写入域 ${listOf(outsideScopes)} —— 只认 "unit"(模块自己的数据目录)与 "workspace"(当前会话工作区)两档;模块拿不到产品域与任意路径,别写别的名字(缺省 [] = 不给执行缝门面)`);
680
+ }
681
+ }
669
682
  }
670
683
  }
671
684
  return problems;
@@ -707,7 +720,8 @@ function readDeclaration(subject, raw) {
707
720
  })),
708
721
  host: {
709
722
  inject: [...declaration.host.inject ?? []],
710
- ...declaration.host.optional === void 0 ? {} : { optional: [...declaration.host.optional] }
723
+ ...declaration.host.optional === void 0 ? {} : { optional: [...declaration.host.optional] },
724
+ writes: [...declaration.host.writes ?? []]
711
725
  },
712
726
  ...declaration.needs === void 0 ? {} : { needs: declaration.needs }
713
727
  },
@@ -734,6 +748,88 @@ var BUNDLE_REGISTRIES_ROUTE = "bundle-registries";
734
748
  var BUNDLE_INSPECT_ROUTE = "bundle-inspect";
735
749
  var MODULE_VERSIONS_ROUTE = "module-versions";
736
750
  //#endregion
751
+ //#region ../shared/dist/host/fs-write.js
752
+ function modeOf(path, mode) {
753
+ if (typeof mode === "number") return mode;
754
+ try {
755
+ return statSync(path).mode & 511;
756
+ } catch {
757
+ return 420;
758
+ }
759
+ }
760
+ function prepareOf(path, options) {
761
+ if (options.mkdir !== false) mkdirSync(dirname(path), { recursive: true });
762
+ return modeOf(path, options.mode);
763
+ }
764
+ function atomicOptionsOf(mode, options) {
765
+ return options.fsync === true ? {
766
+ mode,
767
+ fsync: true
768
+ } : { mode };
769
+ }
770
+ /**
771
+ * 把文本写进文件;默认原子写(temp + rename)、目录不存在就建、已有文件保留原权限。
772
+ * @param path 目标文件;父目录按 `mkdir` 决定是否创建。
773
+ * @param text 文本内容。
774
+ * @param options 原子/目录/权限/fsync;`mode` 默认 `'keep'`(新文件 0644)。
775
+ */
776
+ function writeTextFile(path, text, options = {}) {
777
+ const mode = prepareOf(path, options);
778
+ if (options.atomic === false) {
779
+ writeFileSync(path, text, { mode });
780
+ return;
781
+ }
782
+ writeFileAtomic.sync(path, text, atomicOptionsOf(mode, options));
783
+ }
784
+ /**
785
+ * `writeTextFile` 的异步版;语义与默认值相同。
786
+ * @param path 目标文件。
787
+ * @param text 文本内容。
788
+ * @param options 原子/目录/权限/fsync。
789
+ * @returns 落盘完成。
790
+ */
791
+ async function writeTextFileAsync(path, text, options = {}) {
792
+ const mode = prepareOf(path, options);
793
+ if (options.atomic === false) {
794
+ writeFileSync(path, text, { mode });
795
+ return;
796
+ }
797
+ await writeFileAtomic(path, text, atomicOptionsOf(mode, options));
798
+ }
799
+ /**
800
+ * 把二进制写进文件;语义与默认值同 `writeTextFile`。
801
+ * @param path 目标文件。
802
+ * @param bytes 二进制内容。
803
+ * @param options 原子/目录/权限/fsync。
804
+ */
805
+ function writeBytesFile(path, bytes, options = {}) {
806
+ const mode = prepareOf(path, options);
807
+ if (options.atomic === false) {
808
+ writeFileSync(path, bytes, { mode });
809
+ return;
810
+ }
811
+ writeFileAtomic.sync(path, Buffer.from(bytes), atomicOptionsOf(mode, options));
812
+ }
813
+ /**
814
+ * 把 JSON 写进文件:`JSON.stringify(value, null, 2)` 加末尾换行;语义与默认值同 `writeTextFile`。
815
+ * @param path 目标文件。
816
+ * @param value 可序列化的值。
817
+ * @param options 原子/目录/权限/fsync。
818
+ */
819
+ function writeJsonFile(path, value, options = {}) {
820
+ writeTextFile(path, `${JSON.stringify(value, null, 2)}\n`, options);
821
+ }
822
+ /**
823
+ * `writeJsonFile` 的异步版;语义与默认值相同。
824
+ * @param path 目标文件。
825
+ * @param value 可序列化的值。
826
+ * @param options 原子/目录/权限/fsync。
827
+ * @returns 落盘完成。
828
+ */
829
+ async function writeJsonFileAsync(path, value, options = {}) {
830
+ await writeTextFileAsync(path, `${JSON.stringify(value, null, 2)}\n`, options);
831
+ }
832
+ //#endregion
737
833
  //#region ../shared/dist/host/installed-sources.js
738
834
  var FILE_NAME = ".dsh-workbench-sources.json";
739
835
  var FILE_VERSION = 1;
@@ -774,14 +870,10 @@ function packageSourceOf(profileDir, pkg) {
774
870
  return readInstalledSources(profileDir)[pkg];
775
871
  }
776
872
  function writeInstalledSources(profileDir, sources) {
777
- const file = installedSourcesFile(profileDir);
778
- const temp = `${file}.tmp`;
779
- const text = `${JSON.stringify({
873
+ writeTextFile(installedSourcesFile(profileDir), `${JSON.stringify({
780
874
  version: FILE_VERSION,
781
875
  packages: sources
782
- }, null, 2)}\n`;
783
- fs.writeFileSync(temp, text, { mode: 384 });
784
- fs.renameSync(temp, file);
876
+ }, null, 2)}\n`, { mode: 384 });
785
877
  }
786
878
  function rememberPackageSource(profileDir, pkg, source) {
787
879
  const trimmed = pkg.trim();
@@ -847,54 +939,58 @@ function refusalReason() {
847
939
  return "这个包既不是工作台的模块,也不是运行时的插件,装了也用不上";
848
940
  }
849
941
  //#endregion
850
- //#region ../shared/dist/host/shell.js
851
- var DEFAULT_MAX_BYTES$3 = 8388608;
852
- var GUARD_SLACK_MS = 1e3;
853
- function stalledResult() {
854
- return {
855
- exitCode: null,
856
- timedOut: true,
857
- aborted: false,
858
- stdout: "",
859
- stderr: "",
860
- truncated: false,
861
- stalled: true
862
- };
942
+ //#region ../shared/dist/host/time.js
943
+ dayjs.extend(utc);
944
+ /**
945
+ * 把时间戳/ISO 字符串转成毫秒;非法输入给 `NaN`(调用方据此回退)。
946
+ * @param value ISO 字符串、时间戳或 Date。
947
+ * @returns 毫秒时间戳或 `NaN`。
948
+ */
949
+ function epochOf(value) {
950
+ return dayjs(value).valueOf();
863
951
  }
864
- async function settleWithin(work, limitMs) {
865
- let timer;
866
- const guard = new Promise((resolve) => {
867
- timer = setTimeout(() => resolve(null), limitMs);
868
- });
869
- try {
870
- return await Promise.race([work, guard]);
871
- } finally {
872
- if (timer !== void 0) clearTimeout(timer);
952
+ /**
953
+ * 当前时间(毫秒)。宿主里要「现在几点」只走这里,测试可以点住 `Date.now`。
954
+ * @returns 毫秒时间戳。
955
+ */
956
+ function now() {
957
+ return Date.now();
958
+ }
959
+ /**
960
+ * 当前时间的 ISO 字符串。
961
+ * @returns `new Date().toISOString()` 的等价物。
962
+ */
963
+ function nowIso() {
964
+ return new Date(now()).toISOString();
965
+ }
966
+ //#endregion
967
+ //#region ../shared/dist/host/seam/intent.js
968
+ var SeamError = class extends Error {
969
+ code;
970
+ path;
971
+ scope;
972
+ constructor(code, message, extra = {}) {
973
+ super(message);
974
+ this.name = "SeamError";
975
+ this.code = code;
976
+ this.path = extra.path;
977
+ this.scope = extra.scope;
873
978
  }
979
+ };
980
+ function productWrite() {
981
+ return { kind: "product" };
874
982
  }
875
- async function runShell(shell, command, options = {}) {
876
- const { workdir, timeoutMs = GIT_TIMEOUT_MS, signal, env } = options;
877
- const spec = shell.resolve({
878
- command,
879
- ...workdir === void 0 ? {} : { workdir },
880
- timeoutMs,
881
- onExpiry: "kill",
882
- stdoutMaxBytes: DEFAULT_MAX_BYTES$3,
883
- ...signal === void 0 ? {} : { signal },
884
- ...env === void 0 ? {} : { env }
885
- });
886
- return await settleWithin((async () => {
887
- const result = await (await shell.execute(spec)).result();
888
- return {
889
- exitCode: result.exitCode,
890
- timedOut: result.timedOut === true,
891
- aborted: result.aborted === true,
892
- stdout: result.stdout.text,
893
- stderr: result.stderr.text,
894
- truncated: result.stdout.truncated || result.stderr.truncated,
895
- stalled: false
896
- };
897
- })(), timeoutMs + GUARD_SLACK_MS) ?? stalledResult();
983
+ function workspaceWrite() {
984
+ return { kind: "workspace" };
985
+ }
986
+ function pathsWrite(paths) {
987
+ return {
988
+ kind: "paths",
989
+ paths
990
+ };
991
+ }
992
+ function noWrite() {
993
+ return { kind: "none" };
898
994
  }
899
995
  //#endregion
900
996
  //#region ../shared/dist/host/shell-text.js
@@ -2406,6 +2502,73 @@ function npmRegistryOf(source, env) {
2406
2502
  //#region ../shared/dist/contract/protocol/config-sync.js
2407
2503
  var CONFIG_SYNC_ROUTE = "config-sync";
2408
2504
  //#endregion
2505
+ //#region ../shared/dist/host/patch-blocks.js
2506
+ var ID_LINE = /^- id:[ \t]*"?([^"\s#]+)/;
2507
+ var INSERT_LINE = /^- insert:[ \t]*$/;
2508
+ var NESTED_ID = /^[ \t]+-[ \t]*id:[ \t]*"?([^"\s#]+)/;
2509
+ var NAME_LINE = /^[ \t]*-?[ \t]*name:[ \t]*['"]?([^'"\s#]+)/;
2510
+ function trimmedTail(lines) {
2511
+ let end = lines.length;
2512
+ while (end > 0 && (lines[end - 1] ?? "").trim() === "") end -= 1;
2513
+ return lines.slice(0, end);
2514
+ }
2515
+ function matched(text, pattern) {
2516
+ const found = pattern.exec(text);
2517
+ return found === null ? void 0 : found[1];
2518
+ }
2519
+ function blockOf$1(body, index) {
2520
+ const lines = body.split("\n");
2521
+ const first = lines[0] ?? "";
2522
+ const id = matched(first, ID_LINE);
2523
+ const nested = lines.flatMap((line) => {
2524
+ const one = matched(line, NESTED_ID);
2525
+ return one === void 0 ? [] : [one];
2526
+ });
2527
+ const insert = id === void 0 && INSERT_LINE.test(first);
2528
+ const label = id ?? nested.join(", ");
2529
+ const key = id !== void 0 ? `id:${id}` : insert ? `insert:${nested.join(", ")}` : `block:${String(index)}`;
2530
+ const names = lines.flatMap((line) => {
2531
+ const one = matched(line, NAME_LINE);
2532
+ return one === void 0 ? [] : [one];
2533
+ });
2534
+ return {
2535
+ key,
2536
+ id: label === "" ? key : label,
2537
+ insert,
2538
+ body,
2539
+ names
2540
+ };
2541
+ }
2542
+ function spannedBlocks(text) {
2543
+ const lines = text.split("\n");
2544
+ const starts = [];
2545
+ lines.forEach((line, index) => {
2546
+ if (/^- /.test(line) || line === "-") starts.push(index);
2547
+ });
2548
+ const blocks = [];
2549
+ for (let i = 0; i < starts.length; i += 1) {
2550
+ const from = starts[i] ?? 0;
2551
+ const to = i + 1 < starts.length ? starts[i + 1] ?? lines.length : lines.length;
2552
+ const body = trimmedTail(lines.slice(from, to)).join("\n");
2553
+ if (body.trim() === "") continue;
2554
+ blocks.push({
2555
+ ...blockOf$1(body, blocks.length),
2556
+ from,
2557
+ to
2558
+ });
2559
+ }
2560
+ return blocks;
2561
+ }
2562
+ function patchBlocks(text) {
2563
+ return spannedBlocks(text).map(({ from: _from, to: _to, ...block }) => block);
2564
+ }
2565
+ //#endregion
2566
+ //#region ../shared/dist/host/patch.js
2567
+ /** 把 patch 文本写回原文件:原子写,保留已有权限位(新文件 0644)。 */
2568
+ function writePatchFile(path, text) {
2569
+ writeTextFile(path, text, { mode: "keep" });
2570
+ }
2571
+ //#endregion
2409
2572
  //#region ../shared/dist/contract/protocol/profile-create.js
2410
2573
  var PROFILE_CREATE_ROUTE = "profile-create";
2411
2574
  //#endregion
@@ -2448,17 +2611,6 @@ function parseMarkers(text) {
2448
2611
  return refs;
2449
2612
  }
2450
2613
  //#endregion
2451
- //#region ../shared/dist/host/time.js
2452
- dayjs.extend(utc);
2453
- /**
2454
- * 把时间戳/ISO 字符串转成毫秒;非法输入给 `NaN`(调用方据此回退)。
2455
- * @param value ISO 字符串、时间戳或 Date。
2456
- * @returns 毫秒时间戳或 `NaN`。
2457
- */
2458
- function epochOf(value) {
2459
- return dayjs(value).valueOf();
2460
- }
2461
- //#endregion
2462
2614
  //#region ../shared/dist/host/diy-debug.js
2463
2615
  /**
2464
2616
  * 把工具入参收成人话动作。
@@ -2764,6 +2916,121 @@ function createRecordStore() {
2764
2916
  };
2765
2917
  }
2766
2918
  //#endregion
2919
+ //#region ../shared/dist/host/data-root.js
2920
+ function dshHome() {
2921
+ const configured = process.env.DSH_HOME?.trim();
2922
+ if (configured !== void 0 && configured !== "") return configured;
2923
+ return join(homedir(), ".dsh");
2924
+ }
2925
+ function workbenchRoot() {
2926
+ const configured = process.env.DSH_WORKBENCH_HOME?.trim();
2927
+ if (configured !== void 0 && configured !== "") return configured;
2928
+ return join(dshHome(), "workbench");
2929
+ }
2930
+ function workspacesRoot() {
2931
+ return join(workbenchRoot(), "workspaces");
2932
+ }
2933
+ //#endregion
2934
+ //#region ../shared/dist/host/env-policy.js
2935
+ /**
2936
+ * 把父进程环境里的字符串值拷成一层 env(丢掉 undefined)。
2937
+ * @param parent 父环境(通常是 `process.env`)。
2938
+ * @returns 可继续叠加的 env 层。
2939
+ */
2940
+ function inheritedEnvOf(parent) {
2941
+ const out = {};
2942
+ for (const [key, value] of Object.entries(parent)) if (typeof value === "string") out[key] = value;
2943
+ return out;
2944
+ }
2945
+ function existingPointer(realHome, name) {
2946
+ const file = join(realHome, name);
2947
+ return existsSync(file) ? file : void 0;
2948
+ }
2949
+ /**
2950
+ * 产品域(运行时根)的受控 env:隔离 HOME、npm/pnpm 缓存都钉在运行时根,真 npmrc/gitconfig 只读指过去。
2951
+ * @param input 运行时根、可选的 HOME 与真实用户目录。
2952
+ * @returns 产品域 env。
2953
+ */
2954
+ function productEnvOf(input) {
2955
+ const runtimeRoot = input.runtimeRoot;
2956
+ const realHome = input.realHome ?? homedir();
2957
+ const env = {
2958
+ HOME: input.home ?? join(runtimeRoot, "home"),
2959
+ DSH_HOME: dshHome(),
2960
+ DSH_WORKBENCH_HOME: runtimeRoot,
2961
+ npm_config_cache: join(runtimeRoot, "npm-cache"),
2962
+ NPM_CONFIG_CACHE: join(runtimeRoot, "npm-cache"),
2963
+ npm_config_logs_dir: join(runtimeRoot, "npm-logs"),
2964
+ npm_config_store_dir: join(runtimeRoot, "pnpm-store"),
2965
+ npm_config_cache_dir: join(runtimeRoot, "pnpm-cache"),
2966
+ npm_config_state_dir: join(runtimeRoot, "pnpm-state"),
2967
+ npm_config_update_notifier: "false",
2968
+ npm_config_fund: "false",
2969
+ NO_UPDATE_NOTIFIER: "1"
2970
+ };
2971
+ const npmrc = existingPointer(realHome, ".npmrc");
2972
+ if (npmrc !== void 0) env["NPM_CONFIG_USERCONFIG"] = npmrc;
2973
+ const gitconfig = existingPointer(realHome, ".gitconfig");
2974
+ if (gitconfig !== void 0) env["GIT_CONFIG_GLOBAL"] = gitconfig;
2975
+ return env;
2976
+ }
2977
+ /**
2978
+ * 模块单元的受控 env:在产品域基础上把 HOME 与 npm/pnpm 目录全钉到单元数据目录。
2979
+ * @param input 运行时根、单元数据目录、可选的 TRUE 用户目录。
2980
+ * @returns 单元 env。
2981
+ */
2982
+ function unitEnvOf(input) {
2983
+ const unitRoot = input.unitRoot;
2984
+ return {
2985
+ ...productEnvOf({
2986
+ ...input,
2987
+ home: join(unitRoot, "home")
2988
+ }),
2989
+ npm_config_cache: join(unitRoot, "npm-cache"),
2990
+ NPM_CONFIG_CACHE: join(unitRoot, "npm-cache"),
2991
+ npm_config_logs_dir: join(unitRoot, "npm-logs"),
2992
+ npm_config_store_dir: join(unitRoot, "pnpm-store"),
2993
+ npm_config_cache_dir: join(unitRoot, "pnpm-cache"),
2994
+ npm_config_state_dir: join(unitRoot, "pnpm-state")
2995
+ };
2996
+ }
2997
+ /**
2998
+ * git 的非交互 env:关掉终端提示/askpass;Windows 上不给 askpass 指 POSIX 路径(那台机器上没有)。
2999
+ * @param platform 宿主平台。
3000
+ * @returns git 非交互 env。
3001
+ */
3002
+ function promptlessGitEnvOf(platform) {
3003
+ const shared = {
3004
+ GIT_TERMINAL_PROMPT: "0",
3005
+ SSH_ASKPASS_REQUIRE: "never",
3006
+ GCM_INTERACTIVE: "never",
3007
+ GIT_SSH_COMMAND: "ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new"
3008
+ };
3009
+ if (platform === "win32") return shared;
3010
+ return {
3011
+ ...shared,
3012
+ GIT_ASKPASS: "/usr/bin/false",
3013
+ SSH_ASKPASS: "/usr/bin/false"
3014
+ };
3015
+ }
3016
+ /**
3017
+ * 在父环境上叠加 `promptlessGitEnvOf`,得到可传给执行缝的 git env。
3018
+ * @param parent 父环境。
3019
+ * @param platform 宿主平台。
3020
+ * @returns 只含字符串的 env。
3021
+ */
3022
+ function promptlessProcessEnvOf(parent, platform) {
3023
+ const env = {
3024
+ ...inheritedEnvOf(parent),
3025
+ ...promptlessGitEnvOf(platform)
3026
+ };
3027
+ if (platform === "win32") {
3028
+ delete env["GIT_ASKPASS"];
3029
+ delete env["SSH_ASKPASS"];
3030
+ }
3031
+ return env;
3032
+ }
3033
+ //#endregion
2767
3034
  //#region ../shared/dist/host/facts.js
2768
3035
  function createFactLog(options = {}) {
2769
3036
  const epoch = options.epoch ?? `e${randomUUID()}`;
@@ -2938,7 +3205,7 @@ function parseCrashReport(payload) {
2938
3205
  workspace,
2939
3206
  unit,
2940
3207
  reason,
2941
- at: atOf(record["at"]) ?? Date.now()
3208
+ at: atOf(record["at"]) ?? now()
2942
3209
  }
2943
3210
  };
2944
3211
  }
@@ -3459,7 +3726,7 @@ function healthHandler(ctx, runtimes) {
3459
3726
  profile: dir === void 0 ? null : dir.split("/").pop(),
3460
3727
  profileDir: dir ?? null,
3461
3728
  pid: process.pid,
3462
- now: (/* @__PURE__ */ new Date()).toISOString(),
3729
+ now: nowIso(),
3463
3730
  ...runtimes === void 0 ? {} : { runtimes }
3464
3731
  };
3465
3732
  };
@@ -3498,7 +3765,7 @@ function registerPanelState(ctx, store) {
3498
3765
  store.cards(parsed.value);
3499
3766
  return {
3500
3767
  ok: true,
3501
- at: Date.now()
3768
+ at: now()
3502
3769
  };
3503
3770
  }
3504
3771
  }, {
@@ -3512,7 +3779,7 @@ function registerPanelState(ctx, store) {
3512
3779
  store.crash(parsed.value);
3513
3780
  return {
3514
3781
  ok: true,
3515
- at: Date.now()
3782
+ at: now()
3516
3783
  };
3517
3784
  }
3518
3785
  }]
@@ -3543,9 +3810,9 @@ var RECENT_MS = 6e5;
3543
3810
  function createLongTaskRegistry(deps) {
3544
3811
  const runs = /* @__PURE__ */ new Map();
3545
3812
  const controls = /* @__PURE__ */ new Map();
3546
- const now = deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
3813
+ const now$7 = deps.now ?? (() => nowIso());
3547
3814
  let counter = 0;
3548
- const nextId = deps.nextId ?? (() => `run-${Date.now()}-${counter += 1}`);
3815
+ const nextId = deps.nextId ?? (() => `run-${now()}-${counter += 1}`);
3549
3816
  const recentMs = deps.recentMs ?? RECENT_MS;
3550
3817
  const listeners = /* @__PURE__ */ new Set();
3551
3818
  const live = (id) => {
@@ -3564,7 +3831,7 @@ function createLongTaskRegistry(deps) {
3564
3831
  const patched = {
3565
3832
  ...current,
3566
3833
  ...next,
3567
- at: now()
3834
+ at: now$7()
3568
3835
  };
3569
3836
  runs.set(id, patched);
3570
3837
  notify({
@@ -3581,7 +3848,7 @@ function createLongTaskRegistry(deps) {
3581
3848
  return null;
3582
3849
  },
3583
3850
  current(workspace) {
3584
- const floor = epochOf(now()) - recentMs;
3851
+ const floor = epochOf(now$7()) - recentMs;
3585
3852
  const newest = /* @__PURE__ */ new Map();
3586
3853
  for (const snapshot of runs.values()) {
3587
3854
  if (snapshot.workspace !== workspace) continue;
@@ -3599,7 +3866,7 @@ function createLongTaskRegistry(deps) {
3599
3866
  const running = this.active(options.workspace, options.target);
3600
3867
  if (running !== null) return running;
3601
3868
  const id = nextId();
3602
- const startedAt = now();
3869
+ const startedAt = now$7();
3603
3870
  runs.set(id, {
3604
3871
  id,
3605
3872
  workspace: options.workspace,
@@ -4010,7 +4277,7 @@ function defaultSleep(ms, signal) {
4010
4277
  signal.addEventListener("abort", onAbort, { once: true });
4011
4278
  });
4012
4279
  }
4013
- function targetOf(flow, scope) {
4280
+ function targetOf$1(flow, scope) {
4014
4281
  if (flow.target === void 0) return flow.id;
4015
4282
  const value = resolveValue(flow.target, scope);
4016
4283
  return typeof value === "string" && value.trim() !== "" ? value.trim() : flow.id;
@@ -4148,14 +4415,14 @@ async function runOne(options) {
4148
4415
  async function runFlow(options) {
4149
4416
  const { flow, input, actions, signal, runId, workspace, onStep } = options;
4150
4417
  const sleep = options.sleep ?? defaultSleep;
4151
- const now = options.now ?? (() => Date.now());
4418
+ const now$6 = options.now ?? (() => now());
4152
4419
  const steps = {};
4153
4420
  const emit = (event) => {
4154
4421
  try {
4155
4422
  onStep(event);
4156
4423
  } catch {}
4157
4424
  };
4158
- const target = targetOf(flow, {
4425
+ const target = targetOf$1(flow, {
4159
4426
  in: input,
4160
4427
  steps
4161
4428
  });
@@ -4218,7 +4485,7 @@ async function runFlow(options) {
4218
4485
  });
4219
4486
  },
4220
4487
  sleep,
4221
- now
4488
+ now: now$6
4222
4489
  });
4223
4490
  if (nested.status === "failed") {
4224
4491
  const failed = nested.failedStep === void 0 ? step.id : `${step.id}.${nested.failedStep}`;
@@ -4263,7 +4530,7 @@ async function runFlow(options) {
4263
4530
  runId,
4264
4531
  workspace,
4265
4532
  sleep,
4266
- now,
4533
+ now: now$6,
4267
4534
  emit
4268
4535
  }));
4269
4536
  }
@@ -4293,7 +4560,7 @@ async function runFlow(options) {
4293
4560
  runId,
4294
4561
  workspace,
4295
4562
  sleep,
4296
- now,
4563
+ now: now$6,
4297
4564
  emit
4298
4565
  });
4299
4566
  emit({
@@ -4373,7 +4640,8 @@ function sessionIdOf(exec) {
4373
4640
  * 建一份宿主 kit;每个挂载点一份,没有模块级状态。
4374
4641
  * @param parts.libInstallCommand 拼「装这个共用依赖」的那条命令。
4375
4642
  * @param parts.surface 宿主平台事实;由组合根按 profile 与宿主进程算好再交进来。
4376
- * @returns flow / sessionIdOf / sessionWorkspace / libInstallCommand / surface。
4643
+ * @param parts.seam 按 unit 绑定好的执行缝门面;模块清单没声明写入域时缺省不给。
4644
+ * @returns flow / sessionIdOf / sessionWorkspace / libInstallCommand / surface,有门面时带上 seam。
4377
4645
  */
4378
4646
  function createHostKit(parts) {
4379
4647
  return {
@@ -4381,7 +4649,8 @@ function createHostKit(parts) {
4381
4649
  sessionIdOf,
4382
4650
  sessionWorkspace,
4383
4651
  libInstallCommand: parts.libInstallCommand,
4384
- surface: parts.surface
4652
+ surface: parts.surface,
4653
+ ...parts.seam === void 0 ? {} : { seam: parts.seam }
4385
4654
  };
4386
4655
  }
4387
4656
  //#endregion
@@ -4751,37 +5020,22 @@ function createValueHandoff(options = {}) {
4751
5020
  const ttlMs = options.ttlMs ?? 15e3;
4752
5021
  const held = /* @__PURE__ */ new Map();
4753
5022
  return {
4754
- hand(entry, value, now = Date.now()) {
5023
+ hand(entry, value, now$4 = now()) {
4755
5024
  held.set(handoffId(entry), {
4756
5025
  value,
4757
- at: now
5026
+ at: now$4
4758
5027
  });
4759
5028
  },
4760
- take(entry, now = Date.now()) {
5029
+ take(entry, now$5 = now()) {
4761
5030
  const id = handoffId(entry);
4762
5031
  const hit = held.get(id);
4763
5032
  if (hit === void 0) return void 0;
4764
5033
  held.delete(id);
4765
- return now - hit.at >= ttlMs ? void 0 : hit;
5034
+ return now$5 - hit.at >= ttlMs ? void 0 : hit;
4766
5035
  }
4767
5036
  };
4768
5037
  }
4769
5038
  //#endregion
4770
- //#region ../shared/dist/host/data-root.js
4771
- function dshHome() {
4772
- const configured = process.env.DSH_HOME?.trim();
4773
- if (configured !== void 0 && configured !== "") return configured;
4774
- return join(homedir(), ".dsh");
4775
- }
4776
- function workbenchRoot() {
4777
- const configured = process.env.DSH_WORKBENCH_HOME?.trim();
4778
- if (configured !== void 0 && configured !== "") return configured;
4779
- return join(dshHome(), "workbench");
4780
- }
4781
- function workspacesRoot() {
4782
- return join(workbenchRoot(), "workspaces");
4783
- }
4784
- //#endregion
4785
5039
  //#region ../shared/dist/host/workspace-store.js
4786
5040
  function slug(raw, max = 40) {
4787
5041
  const cleaned = raw.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, max);
@@ -4823,9 +5077,6 @@ function memoryStore(dir) {
4823
5077
  };
4824
5078
  }
4825
5079
  function fileStore(dir) {
4826
- const ensureFor = (file) => {
4827
- fs.mkdirSync(path.dirname(file), { recursive: true });
4828
- };
4829
5080
  return {
4830
5081
  dir,
4831
5082
  read: (name) => {
@@ -4837,11 +5088,7 @@ function fileStore(dir) {
4837
5088
  }
4838
5089
  },
4839
5090
  write: (name, value) => {
4840
- const file = inside(dir, name);
4841
- ensureFor(file);
4842
- const tmp = `${file}.tmp`;
4843
- fs.writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, "utf8");
4844
- fs.renameSync(tmp, file);
5091
+ writeJsonFile(inside(dir, name), value);
4845
5092
  },
4846
5093
  remove: (name) => {
4847
5094
  const file = inside(dir, name);
@@ -4890,6 +5137,703 @@ function createWorkspaceStore(options) {
4890
5137
  };
4891
5138
  }
4892
5139
  //#endregion
5140
+ //#region ../shared/dist/host/shell.js
5141
+ var DEFAULT_MAX_BYTES$3 = 8388608;
5142
+ var GUARD_SLACK_MS = 1e3;
5143
+ function stalledResult() {
5144
+ return {
5145
+ exitCode: null,
5146
+ timedOut: true,
5147
+ aborted: false,
5148
+ stdout: "",
5149
+ stderr: "",
5150
+ truncated: false,
5151
+ stalled: true
5152
+ };
5153
+ }
5154
+ async function settleWithin(work, limitMs) {
5155
+ let timer;
5156
+ const guard = new Promise((resolve) => {
5157
+ timer = setTimeout(() => resolve(null), limitMs);
5158
+ });
5159
+ try {
5160
+ return await Promise.race([work, guard]);
5161
+ } finally {
5162
+ if (timer !== void 0) clearTimeout(timer);
5163
+ }
5164
+ }
5165
+ async function runShell(shell, command, options = {}) {
5166
+ const { workdir, timeoutMs = GIT_TIMEOUT_MS, signal, env, sandboxPolicy } = options;
5167
+ const spec = shell.resolve({
5168
+ command,
5169
+ ...workdir === void 0 ? {} : { workdir },
5170
+ timeoutMs,
5171
+ onExpiry: "kill",
5172
+ stdoutMaxBytes: DEFAULT_MAX_BYTES$3,
5173
+ ...signal === void 0 ? {} : { signal },
5174
+ ...env === void 0 ? {} : { env },
5175
+ ...sandboxPolicy === void 0 ? {} : { sandboxPolicy }
5176
+ });
5177
+ return await settleWithin((async () => {
5178
+ const result = await (await shell.execute(spec)).result();
5179
+ return {
5180
+ exitCode: result.exitCode,
5181
+ timedOut: result.timedOut === true,
5182
+ aborted: result.aborted === true,
5183
+ stdout: result.stdout.text,
5184
+ stderr: result.stderr.text,
5185
+ truncated: result.stdout.truncated || result.stderr.truncated,
5186
+ stalled: false,
5187
+ ...result.sandbox === void 0 ? {} : { sandbox: result.sandbox }
5188
+ };
5189
+ })(), timeoutMs + GUARD_SLACK_MS) ?? stalledResult();
5190
+ }
5191
+ //#endregion
5192
+ //#region ../shared/dist/host/seam/policy.js
5193
+ function seamModeOf(value) {
5194
+ const text = value?.trim();
5195
+ return text === "read-only" || text === "workspace-write" || text === "danger-full-access" ? text : "workspace-write";
5196
+ }
5197
+ function rank(mode) {
5198
+ return mode === "read-only" ? 0 : mode === "workspace-write" ? 1 : 2;
5199
+ }
5200
+ function narrowerMode(left, right) {
5201
+ return rank(left) <= rank(right) ? left : right;
5202
+ }
5203
+ function insideOf(root, path) {
5204
+ const confined = resolve(root);
5205
+ const target = resolve(path);
5206
+ if (target === confined) return true;
5207
+ return target.startsWith(confined.endsWith(sep) ? confined : `${confined}${sep}`);
5208
+ }
5209
+ function productPolicy(ceiling, productRoot) {
5210
+ return {
5211
+ mode: ceiling === "read-only" ? "read-only" : "workspace-write",
5212
+ workspaceRoot: resolve(productRoot)
5213
+ };
5214
+ }
5215
+ function workspacePolicy(ceiling, grant) {
5216
+ if (grant === void 0) throw new SeamError("workspace-unknown", "这条会话解析不到工作区:先在左栏选一个工作区再跑需要写工作区的命令");
5217
+ const mode = narrowerMode(ceiling, grant.mode);
5218
+ if (mode === "read-only") throw new SeamError("read-only", "当前会话是只读模式:要写工作区,先在权限设置里切换模式");
5219
+ return {
5220
+ mode,
5221
+ workspaceRoot: resolve(grant.workspaceRoot)
5222
+ };
5223
+ }
5224
+ function commonDirectory(paths) {
5225
+ const dirs = paths.map((path) => dirname(resolve(path)));
5226
+ let shared = dirs[0];
5227
+ if (shared === void 0) return void 0;
5228
+ for (const dir of dirs.slice(1)) while (shared !== dirname(shared) && !insideOf(shared, dir)) shared = dirname(shared);
5229
+ return shared;
5230
+ }
5231
+ function pathsPolicy(ceiling, paths) {
5232
+ if (paths.length === 0) throw new SeamError("paths-span-trees", "这次写入没有给出任何路径");
5233
+ if (!paths.every((path) => isAbsolute(path))) throw new SeamError("paths-span-trees", "外部路径必须是绝对路径");
5234
+ if (ceiling === "read-only") throw new SeamError("read-only", "当前部署是只读模式:这条命令不能写外部路径");
5235
+ const root = commonDirectory(paths);
5236
+ if (root === void 0 || root === sep && paths.length > 1) throw new SeamError("paths-span-trees", "这次写入跨了多个目录:请分开做,或把产物改到工作台目录");
5237
+ if (root === sep) throw new SeamError("paths-span-trees", "不能把整个文件系统声明成写入域");
5238
+ return {
5239
+ mode: "workspace-write",
5240
+ workspaceRoot: root
5241
+ };
5242
+ }
5243
+ function denialScopeOf(path, policy, productRoot, grant) {
5244
+ if (path === void 0) return "unknown";
5245
+ if (insideOf(productRoot, path)) return "product";
5246
+ if (grant !== void 0 && insideOf(grant.workspaceRoot, path)) return "workspace";
5247
+ if (insideOf(policy.workspaceRoot, path)) return policy.workspaceRoot === resolve(productRoot) ? "product" : "workspace";
5248
+ return "external";
5249
+ }
5250
+ function policyOf(intent, ceiling, productRoot, grant) {
5251
+ if (intent.kind === "product" || intent.kind === "none") return productPolicy(ceiling, productRoot);
5252
+ if (intent.kind === "workspace") return workspacePolicy(ceiling, grant);
5253
+ return pathsPolicy(ceiling, intent.paths);
5254
+ }
5255
+ //#endregion
5256
+ //#region ../shared/dist/host/seam/env.js
5257
+ /**
5258
+ * 产品域(运行时根)的受控 env:隔离 HOME、npm/pnpm 缓存钉在运行时根、真 npmrc/gitconfig 只读指过去。
5259
+ * @param input 运行时根、可选的 HOME 与真实用户目录。
5260
+ * @returns 产品域 env。
5261
+ */
5262
+ function seamProductEnv(input) {
5263
+ return productEnvOf(input);
5264
+ }
5265
+ /**
5266
+ * 模块单元的受控 env:HOME 与 npm/pnpm 目录钉到单元数据目录。
5267
+ * @param input 运行时根、单元数据目录、可选的 TRUE 用户目录。
5268
+ * @returns 单元 env。
5269
+ */
5270
+ function seamUnitEnv(input) {
5271
+ return unitEnvOf(input);
5272
+ }
5273
+ //#endregion
5274
+ //#region ../shared/dist/host/seam/denial.js
5275
+ var PATTERNS = [
5276
+ /npm (?:error|ERR!) path (.+)$/m,
5277
+ /(?:EPERM|EACCES):[^,\n]*, (?:open|unlink|mkdir|scandir|rename|access|stat|lstat|read|write|copyfile|rmdir|opendir|utimes|chmod|chown) '([^']+)'/i,
5278
+ /(?:Operation not permitted|Permission denied)[^\n]*?'([^']+)'/i,
5279
+ /fatal: could not create work tree dir '([^']+)'/i,
5280
+ /mkdir: cannot create directory '([^']+)'/i,
5281
+ /cannot (?:create|mkdir) (?:directory )?'([^']+)'/i,
5282
+ /(?:Operation not permitted|Permission denied):\s*([^\s'\n]+)/i
5283
+ ];
5284
+ function firstMatch(text) {
5285
+ for (const pattern of PATTERNS) {
5286
+ const value = pattern.exec(text)?.[1]?.trim();
5287
+ if (value !== void 0 && value !== "") return value;
5288
+ }
5289
+ }
5290
+ function deniedPathOf(stderr, stdout = "") {
5291
+ return firstMatch(stderr) ?? firstMatch(stdout);
5292
+ }
5293
+ function sandboxDeniedOf(facts) {
5294
+ return facts?.denied === true;
5295
+ }
5296
+ //#endregion
5297
+ //#region ../shared/dist/host/seam/host-env.js
5298
+ function hostEnvOf(env) {
5299
+ const merged = { ...process.env };
5300
+ for (const [key, value] of Object.entries(env ?? {})) if (value === void 0) delete merged[key];
5301
+ else merged[key] = value;
5302
+ return merged;
5303
+ }
5304
+ function definedEnvOf(env) {
5305
+ const out = {};
5306
+ for (const [key, value] of Object.entries(env ?? {})) if (value !== void 0) out[key] = value;
5307
+ return out;
5308
+ }
5309
+ //#endregion
5310
+ //#region ../shared/dist/host/seam/process.js
5311
+ function streamOf(readable) {
5312
+ if (readable === null || readable === void 0) return null;
5313
+ return { onData(listener) {
5314
+ const handler = (chunk) => listener(String(chunk));
5315
+ readable.on("data", handler);
5316
+ return () => {
5317
+ readable.off("data", handler);
5318
+ };
5319
+ } };
5320
+ }
5321
+ function createNodeProcessHost() {
5322
+ return { spawn(options) {
5323
+ const logFd = options.stdio === "log" && options.logFile !== void 0 ? openSync(options.logFile, "a") : void 0;
5324
+ const stdio = options.stdio === "pipe" ? "pipe" : logFd === void 0 ? "ignore" : [
5325
+ "ignore",
5326
+ logFd,
5327
+ logFd
5328
+ ];
5329
+ let logClosed = false;
5330
+ const closeLog = () => {
5331
+ if (logClosed || logFd === void 0) return;
5332
+ logClosed = true;
5333
+ closeSync(logFd);
5334
+ };
5335
+ let child;
5336
+ try {
5337
+ child = execa(options.file, [...options.args], {
5338
+ ...options.cwd === void 0 ? {} : { cwd: options.cwd },
5339
+ env: { ...options.env },
5340
+ detached: options.detached,
5341
+ stdio,
5342
+ windowsHide: options.windowsHide,
5343
+ reject: false,
5344
+ buffer: false
5345
+ });
5346
+ } catch (error) {
5347
+ closeLog();
5348
+ throw error;
5349
+ }
5350
+ if (logFd !== void 0) {
5351
+ child.nodeChildProcess.once("spawn", closeLog);
5352
+ child.nodeChildProcess.once("error", closeLog);
5353
+ }
5354
+ const stdin = child.stdin;
5355
+ return {
5356
+ pid: child.pid,
5357
+ stdin: stdin === null || stdin === void 0 ? null : { write: (chunk) => stdin.write(chunk) },
5358
+ stdout: streamOf(child.stdout),
5359
+ stderr: streamOf(child.stderr),
5360
+ onExit(listener) {
5361
+ const handler = (code, signal) => listener({
5362
+ code,
5363
+ signal
5364
+ });
5365
+ child.nodeChildProcess.on("exit", handler);
5366
+ return () => {
5367
+ child.nodeChildProcess.off("exit", handler);
5368
+ };
5369
+ },
5370
+ onError(listener) {
5371
+ child.nodeChildProcess.on("error", listener);
5372
+ return () => {
5373
+ child.nodeChildProcess.off("error", listener);
5374
+ };
5375
+ },
5376
+ unref() {
5377
+ child.nodeChildProcess.unref();
5378
+ },
5379
+ kill(signal) {
5380
+ child.kill(signal);
5381
+ }
5382
+ };
5383
+ } };
5384
+ }
5385
+ //#endregion
5386
+ //#region ../shared/dist/host/seam/run.js
5387
+ var DENIAL_MARKER = /\[sandbox: file access denied under [a-z-]+ mode\]/i;
5388
+ var DEFAULT_MAX_BYTES$2 = 8388608;
5389
+ function scopeText(scope) {
5390
+ if (scope === "product") return "工作台自己的目录";
5391
+ if (scope === "workspace") return "会话工作区";
5392
+ if (scope === "external") return "工作区之外的路径";
5393
+ return "没认出来的路径";
5394
+ }
5395
+ function denialMessageOf(scope, path, output) {
5396
+ const where = path === void 0 ? "" : `:${path}`;
5397
+ const marker = DENIAL_MARKER.test(output) ? "" : "(输出里没有沙箱标记,按拒绝处理)";
5398
+ const next = scope === "external" ? "要写工作区外的路径得先让用户点头:把目标改到工作区,或让工具补上审批后再试" : "这是工作台自己的声明问题,不是用户的配置问题:把命令、域与路径一起记下来交给维护者";
5399
+ return `被沙箱挡住了(${scopeText(scope)}${where})${marker}。${next}`;
5400
+ }
5401
+ function settle$1(result, denial) {
5402
+ return {
5403
+ exitCode: result.exitCode,
5404
+ timedOut: result.timedOut,
5405
+ aborted: result.aborted,
5406
+ stdout: result.stdout,
5407
+ stderr: result.stderr,
5408
+ truncated: result.truncated,
5409
+ stalled: result.stalled,
5410
+ ...denial === void 0 ? {} : { denial }
5411
+ };
5412
+ }
5413
+ function targetOf(request) {
5414
+ const hasCommand = request.command !== void 0;
5415
+ const hasArgv = request.argv !== void 0;
5416
+ if (hasArgv && !hasCommand) {
5417
+ const argv = request.argv ?? [];
5418
+ if (argv.length === 0) throw new SeamError("scope-not-declared", "argv 是空的:至少给一个可执行文件");
5419
+ return { argv };
5420
+ }
5421
+ if (hasCommand && !hasArgv) return { line: request.command ?? "" };
5422
+ throw new SeamError("scope-not-declared", "这条执行请求要么给 argv,要么给一条命令行,不能两个都给或都不给");
5423
+ }
5424
+ function displayOf(target) {
5425
+ return "argv" in target ? target.argv.map((one) => quoteShellArg(one)).join(" ") : target.line;
5426
+ }
5427
+ function shellInvocationOf(shellKind, line) {
5428
+ return shellKind === "pwsh" ? {
5429
+ file: "pwsh",
5430
+ args: [
5431
+ "-NoProfile",
5432
+ "-NonInteractive",
5433
+ "-Command",
5434
+ line
5435
+ ]
5436
+ } : {
5437
+ file: "bash",
5438
+ args: ["-c", line]
5439
+ };
5440
+ }
5441
+ function invocationOf(target, shellKind) {
5442
+ if ("argv" in target) {
5443
+ const [file, ...args] = target.argv;
5444
+ if (file === void 0) throw new SeamError("scope-not-declared", "argv 是空的:至少给一个可执行文件");
5445
+ return {
5446
+ file,
5447
+ args
5448
+ };
5449
+ }
5450
+ return shellInvocationOf(shellKind ?? shellKindOf(), target.line);
5451
+ }
5452
+ function sandboxedLineOf(target, shellKind) {
5453
+ if ("argv" in target) return target.argv.map((one) => quoteShellArg(one)).join(" ");
5454
+ if (shellKind === void 0) return target.line;
5455
+ const invocation = shellInvocationOf(shellKind, target.line);
5456
+ return [invocation.file, ...invocation.args].map((one) => quoteShellArg(one)).join(" ");
5457
+ }
5458
+ function createSeam(ports) {
5459
+ const runtimeRoot = ports.runtimeRoot ?? workbenchRoot();
5460
+ const ceiling = ports.ceiling ?? seamModeOf(process.env["DSH_PERMISSION_MODE"]);
5461
+ const allowHost = ports.allowHost === true;
5462
+ const processes = ports.processes ?? createNodeProcessHost();
5463
+ const limit = ports.factLimit ?? 80;
5464
+ const facts = [];
5465
+ function record(fact) {
5466
+ facts.push(fact);
5467
+ if (facts.length > limit) facts.splice(0, facts.length - limit);
5468
+ }
5469
+ function grantFor(session) {
5470
+ if (ports.grantOf === void 0 || session === void 0 || session === null) return void 0;
5471
+ try {
5472
+ return ports.grantOf(session);
5473
+ } catch {
5474
+ return;
5475
+ }
5476
+ }
5477
+ function requireHost(intent) {
5478
+ if (intent.kind !== "host") throw new SeamError("scope-not-declared", "这条调用要走宿主进程执行,但写入域不是 host");
5479
+ if (!allowHost) throw new SeamError("scope-not-declared", "宿主域只给宿主侧接线开出来;模块与普通调用点拿不到");
5480
+ }
5481
+ async function attempt(request, policy, line) {
5482
+ return runShell(ports.shell, line, {
5483
+ ...request.workdir === void 0 ? {} : { workdir: request.workdir },
5484
+ ...request.timeoutMs === void 0 ? {} : { timeoutMs: request.timeoutMs },
5485
+ ...request.signal === void 0 ? {} : { signal: request.signal },
5486
+ env: {
5487
+ ...seamProductEnv({
5488
+ runtimeRoot,
5489
+ realHome: ports.realHome
5490
+ }),
5491
+ ...definedEnvOf(request.env)
5492
+ },
5493
+ sandboxPolicy: policy
5494
+ });
5495
+ }
5496
+ function healPolicyOf(intent, scope, granted) {
5497
+ if (scope === "product" && intent.kind !== "product") return productPolicy(ceiling, runtimeRoot);
5498
+ if (scope === "workspace" && intent.kind === "workspace") {
5499
+ if (granted === void 0) return void 0;
5500
+ try {
5501
+ return workspacePolicy(ceiling, granted);
5502
+ } catch {
5503
+ return;
5504
+ }
5505
+ }
5506
+ }
5507
+ async function runHost(request, target) {
5508
+ const invocation = invocationOf(target, request.shellKind);
5509
+ const child = processes.spawn({
5510
+ file: invocation.file,
5511
+ args: invocation.args,
5512
+ env: hostEnvOf(request.env),
5513
+ ...request.workdir === void 0 ? {} : { cwd: request.workdir },
5514
+ detached: false,
5515
+ stdio: "pipe",
5516
+ windowsHide: true
5517
+ });
5518
+ const state = {
5519
+ stdout: "",
5520
+ stderr: "",
5521
+ truncated: false
5522
+ };
5523
+ const append = (which, chunk) => {
5524
+ const total = state.stdout.length + state.stderr.length;
5525
+ const room = (request.maxBytes ?? DEFAULT_MAX_BYTES$2) - total;
5526
+ if (room <= 0) {
5527
+ state.truncated = true;
5528
+ return;
5529
+ }
5530
+ const text = chunk.length > room ? chunk.slice(0, room) : chunk;
5531
+ state[which] += text;
5532
+ if (chunk.length > room) state.truncated = true;
5533
+ };
5534
+ child.stdout?.onData((chunk) => append("stdout", chunk));
5535
+ child.stderr?.onData((chunk) => append("stderr", chunk));
5536
+ let timer;
5537
+ let timedOut = false;
5538
+ if (request.timeoutMs !== void 0) timer = setTimeout(() => {
5539
+ timedOut = true;
5540
+ child.kill("SIGKILL");
5541
+ }, request.timeoutMs);
5542
+ const exit = await new Promise((resolve) => {
5543
+ child.onExit((event) => resolve(event));
5544
+ child.onError((error) => {
5545
+ state.stderr = `${state.stderr}${String(error)}`;
5546
+ resolve({
5547
+ code: null,
5548
+ signal: null
5549
+ });
5550
+ });
5551
+ });
5552
+ if (timer !== void 0) clearTimeout(timer);
5553
+ return {
5554
+ exitCode: exit.code,
5555
+ timedOut,
5556
+ aborted: false,
5557
+ stdout: state.stdout,
5558
+ stderr: state.stderr,
5559
+ truncated: state.truncated,
5560
+ stalled: false
5561
+ };
5562
+ }
5563
+ function spawnHost(request) {
5564
+ const invocation = invocationOf(request.target, void 0);
5565
+ const logFile = request.logFile ?? join(runtimeRoot, "logs", `${request.label ?? "spawn"}-${String(now())}.log`);
5566
+ mkdirSync(dirname(logFile), { recursive: true });
5567
+ const child = processes.spawn({
5568
+ file: invocation.file,
5569
+ args: invocation.args,
5570
+ env: hostEnvOf(request.env),
5571
+ ...request.cwd === void 0 ? {} : { cwd: request.cwd },
5572
+ detached: true,
5573
+ stdio: "log",
5574
+ logFile,
5575
+ windowsHide: true
5576
+ });
5577
+ child.unref();
5578
+ record({
5579
+ at: now(),
5580
+ verb: "spawn",
5581
+ command: displayOf(request.target),
5582
+ intent: "host",
5583
+ mode: "host",
5584
+ workspaceRoot: "",
5585
+ exitCode: null,
5586
+ timedOut: false,
5587
+ stalled: false
5588
+ });
5589
+ const display = displayOf(request.target);
5590
+ const errorListeners = /* @__PURE__ */ new Set();
5591
+ const exitWaiters = [];
5592
+ let settled;
5593
+ let failure;
5594
+ const settleExit = (event) => {
5595
+ if (settled !== void 0) return;
5596
+ settled = event;
5597
+ for (const waiter of exitWaiters.splice(0)) waiter(event);
5598
+ };
5599
+ child.onExit((event) => settleExit(event));
5600
+ child.onError((error) => {
5601
+ failure = error;
5602
+ settleExit({
5603
+ code: null,
5604
+ signal: null
5605
+ });
5606
+ if (errorListeners.size === 0) {
5607
+ ports.logger?.warn?.(`[dsh-workbench] 起进程没跑起来(${display}):${String(error)}`);
5608
+ return;
5609
+ }
5610
+ for (const listener of errorListeners) listener(error);
5611
+ });
5612
+ return {
5613
+ pid: child.pid,
5614
+ logFile,
5615
+ exit: () => settled === void 0 ? new Promise((resolve) => exitWaiters.push(resolve)) : Promise.resolve(settled),
5616
+ onError(listener) {
5617
+ if (failure !== void 0) {
5618
+ listener(failure);
5619
+ return () => {};
5620
+ }
5621
+ errorListeners.add(listener);
5622
+ return () => errorListeners.delete(listener);
5623
+ },
5624
+ kill: (signal) => child.kill(signal ?? "SIGTERM")
5625
+ };
5626
+ }
5627
+ function openHost(request) {
5628
+ const invocation = invocationOf(request.target, void 0);
5629
+ const child = processes.spawn({
5630
+ file: invocation.file,
5631
+ args: invocation.args,
5632
+ env: hostEnvOf(request.env),
5633
+ ...request.cwd === void 0 ? {} : { cwd: request.cwd },
5634
+ detached: false,
5635
+ stdio: "pipe",
5636
+ windowsHide: true
5637
+ });
5638
+ record({
5639
+ at: now(),
5640
+ verb: "open",
5641
+ command: displayOf(request.target),
5642
+ intent: "host",
5643
+ mode: "host",
5644
+ workspaceRoot: "",
5645
+ exitCode: null,
5646
+ timedOut: false,
5647
+ stalled: false
5648
+ });
5649
+ const display = displayOf(request.target);
5650
+ const errorListeners = /* @__PURE__ */ new Set();
5651
+ const exitListeners = /* @__PURE__ */ new Set();
5652
+ let failure;
5653
+ let settled;
5654
+ const settleExit = (event) => {
5655
+ if (settled !== void 0) return;
5656
+ settled = event;
5657
+ for (const listener of exitListeners) listener(event);
5658
+ };
5659
+ child.onExit((event) => settleExit(event));
5660
+ child.onError((error) => {
5661
+ failure = error;
5662
+ settleExit({
5663
+ code: null,
5664
+ signal: null
5665
+ });
5666
+ if (errorListeners.size === 0) {
5667
+ ports.logger?.warn?.(`[dsh-workbench] 双向通道起不来(${display}):${String(error)}`);
5668
+ return;
5669
+ }
5670
+ for (const listener of errorListeners) listener(error);
5671
+ });
5672
+ return {
5673
+ pid: child.pid,
5674
+ write: (chunk) => child.stdin?.write(chunk),
5675
+ onData(listener) {
5676
+ const offOut = child.stdout?.onData(listener) ?? (() => {});
5677
+ const offErr = child.stderr?.onData(listener) ?? (() => {});
5678
+ return () => {
5679
+ offOut();
5680
+ offErr();
5681
+ };
5682
+ },
5683
+ onExit(listener) {
5684
+ if (settled !== void 0) {
5685
+ listener(settled);
5686
+ return () => {};
5687
+ }
5688
+ exitListeners.add(listener);
5689
+ return () => exitListeners.delete(listener);
5690
+ },
5691
+ onError(listener) {
5692
+ if (failure !== void 0) {
5693
+ listener(failure);
5694
+ return () => {};
5695
+ }
5696
+ errorListeners.add(listener);
5697
+ return () => errorListeners.delete(listener);
5698
+ },
5699
+ kill: (signal) => child.kill(signal ?? "SIGTERM")
5700
+ };
5701
+ }
5702
+ return {
5703
+ async run(request) {
5704
+ const target = targetOf(request);
5705
+ if (request.intent.kind === "host") {
5706
+ requireHost(request.intent);
5707
+ const started = now();
5708
+ const result = await runHost(request, target);
5709
+ record({
5710
+ at: started,
5711
+ verb: "run",
5712
+ command: displayOf(target),
5713
+ intent: "host",
5714
+ mode: "host",
5715
+ workspaceRoot: "",
5716
+ exitCode: result.exitCode,
5717
+ timedOut: result.timedOut,
5718
+ stalled: result.stalled
5719
+ });
5720
+ return settle$1(result);
5721
+ }
5722
+ const granted = grantFor(request.session);
5723
+ const intent = request.intent;
5724
+ const policy = policyOf(intent, ceiling, runtimeRoot, granted);
5725
+ const line = sandboxedLineOf(target, request.shellKind);
5726
+ let result = await attempt(request, policy, line);
5727
+ if (!sandboxDeniedOf(result.sandbox)) {
5728
+ record({
5729
+ at: now(),
5730
+ verb: "run",
5731
+ command: line,
5732
+ intent: intent.kind,
5733
+ mode: policy.mode,
5734
+ workspaceRoot: policy.workspaceRoot,
5735
+ exitCode: result.exitCode,
5736
+ timedOut: result.timedOut,
5737
+ stalled: result.stalled
5738
+ });
5739
+ return settle$1(result);
5740
+ }
5741
+ const path = deniedPathOf(result.stderr, result.stdout);
5742
+ const scope = denialScopeOf(path, policy, runtimeRoot, granted);
5743
+ const healedPolicy = request.retry === "safe" ? healPolicyOf(intent, scope, granted) : void 0;
5744
+ let healed = false;
5745
+ if (healedPolicy !== void 0) {
5746
+ const retried = await attempt(request, healedPolicy, line).catch(() => void 0);
5747
+ if (retried !== void 0 && !sandboxDeniedOf(retried.sandbox)) {
5748
+ result = retried;
5749
+ healed = true;
5750
+ }
5751
+ }
5752
+ const denial = {
5753
+ ...path === void 0 ? {} : { path },
5754
+ scope,
5755
+ ...healed ? { healed: true } : {}
5756
+ };
5757
+ record({
5758
+ at: now(),
5759
+ verb: "run",
5760
+ command: line,
5761
+ intent: intent.kind,
5762
+ mode: policy.mode,
5763
+ workspaceRoot: policy.workspaceRoot,
5764
+ exitCode: result.exitCode,
5765
+ timedOut: result.timedOut,
5766
+ stalled: result.stalled,
5767
+ denial
5768
+ });
5769
+ if (healed) return settle$1(result, denial);
5770
+ if (request.bestEffort === true) {
5771
+ ports.logger?.warn?.(`[dsh-workbench] ${denialMessageOf(scope, path, `${result.stderr}\n${result.stdout}`)}`);
5772
+ return settle$1(result, denial);
5773
+ }
5774
+ throw new SeamError("sandbox-denied", denialMessageOf(scope, path, `${result.stderr}\n${result.stdout}`), {
5775
+ ...path === void 0 ? {} : { path },
5776
+ scope
5777
+ });
5778
+ },
5779
+ spawn(request) {
5780
+ requireHost(request.intent);
5781
+ return spawnHost(request);
5782
+ },
5783
+ open(request) {
5784
+ requireHost(request.intent);
5785
+ return openHost(request);
5786
+ },
5787
+ facts: () => facts
5788
+ };
5789
+ }
5790
+ //#endregion
5791
+ //#region ../shared/dist/host/seam/unit-seam.js
5792
+ function unitDataRoot(runtimeRoot, unit) {
5793
+ return join(runtimeRoot, "units", unit);
5794
+ }
5795
+ function createUnitSeam(ports) {
5796
+ const unitRoot = unitDataRoot(ports.runtimeRoot, ports.unit);
5797
+ mkdirSync(unitRoot, { recursive: true });
5798
+ const declared = new Set(ports.declared ?? []);
5799
+ const env = seamUnitEnv({
5800
+ runtimeRoot: ports.runtimeRoot,
5801
+ unitRoot,
5802
+ realHome: ports.realHome
5803
+ });
5804
+ return { async run(request) {
5805
+ const common = {
5806
+ command: request.command,
5807
+ ...request.workdir === void 0 ? {} : { workdir: request.workdir },
5808
+ ...request.timeoutMs === void 0 ? {} : { timeoutMs: request.timeoutMs },
5809
+ ...request.signal === void 0 ? {} : { signal: request.signal },
5810
+ ...request.retry === void 0 ? {} : { retry: request.retry }
5811
+ };
5812
+ if (request.scope === "unit") return ports.seam.run({
5813
+ ...common,
5814
+ intent: { kind: "product" },
5815
+ env: {
5816
+ ...env,
5817
+ ...request.env ?? {}
5818
+ }
5819
+ });
5820
+ if (!declared.has("workspace")) throw new SeamError("scope-not-declared", `${ports.unit} 的清单没有声明 host.writes 里的 workspace:要写当前工作区,先在清单里声明`);
5821
+ if (request.session === void 0 || request.session === null) throw new SeamError("workspace-unknown", `${ports.unit} 请求写工作区,但这次调用没有会话`);
5822
+ return ports.seam.run({
5823
+ ...common,
5824
+ intent: { kind: "workspace" },
5825
+ session: request.session,
5826
+ env: { ...request.env ?? {} }
5827
+ });
5828
+ } };
5829
+ }
5830
+ //#endregion
5831
+ //#region ../shared/dist/host/seam/service.js
5832
+ var WORKBENCH_SEAM = "workbenchSeam";
5833
+ function seamOf(ctx) {
5834
+ return ctx.get(WORKBENCH_SEAM, false);
5835
+ }
5836
+ //#endregion
4893
5837
  //#region ../shared/dist/host/install-failure.js
4894
5838
  var TARGET_FLAGS = [
4895
5839
  "--prefix",
@@ -5316,123 +6260,40 @@ function buildSupplyIndex(declarations, options = {}) {
5316
6260
  const detail = `${need.kind === "extension" ? "插件" : "共用库"} ${need.name} 来自不同的包:${seen.package} / ${entry.package}`;
5317
6261
  entry.conflicted = true;
5318
6262
  seen.conflicted = true;
5319
- conflicts.push({
5320
- kind: "name-mismatch",
5321
- key: entry.key,
5322
- names: [need.name],
5323
- declarers: [.../* @__PURE__ */ new Set([...seen.declarers, ...entry.declarers])],
5324
- detail
5325
- });
5326
- } else if (seen === void 0) byName.set(nameKey, entry);
5327
- }
5328
- const deduped = /* @__PURE__ */ new Map();
5329
- for (const conflict of conflicts) deduped.set(`${conflict.kind}:${conflict.key}`, conflict);
5330
- const frozenEntries = /* @__PURE__ */ new Map();
5331
- for (const [key, entry] of entries) frozenEntries.set(key, {
5332
- key: entry.key,
5333
- kind: entry.kind,
5334
- names: [...entry.names],
5335
- package: entry.package,
5336
- ...entry.runtime === void 0 ? {} : { runtime: entry.runtime },
5337
- sources: entry.sources.map((source) => ({ ...source })),
5338
- declarers: [...entry.declarers],
5339
- commands: [...entry.commands],
5340
- conflicted: entry.conflicted
5341
- });
5342
- const entryOf = (kind, name) => byName.get(`${kind}:${name}`);
5343
- return {
5344
- entries: frozenEntries,
5345
- conflicts: [...deduped.values()],
5346
- problems: [...deduped.values()].map((conflict) => conflict.detail),
5347
- entryOf,
5348
- sourcesOf: (kind, name) => {
5349
- const entry = entryOf(kind, name);
5350
- return entry === void 0 || entry.conflicted ? [] : entry.sources;
5351
- }
5352
- };
5353
- }
5354
- //#endregion
5355
- //#region ../shared/dist/host/patch.js
5356
- var ID_LINE = /^- id:[ \t]*"?([^"\s#]+)/;
5357
- var INSERT_LINE = /^- insert:[ \t]*$/;
5358
- var NESTED_ID = /^[ \t]+-[ \t]*id:[ \t]*"?([^"\s#]+)/;
5359
- var NAME_LINE = /^[ \t]*-?[ \t]*name:[ \t]*['"]?([^'"\s#]+)/;
5360
- function trimmedTail(lines) {
5361
- let end = lines.length;
5362
- while (end > 0 && (lines[end - 1] ?? "").trim() === "") end -= 1;
5363
- return lines.slice(0, end);
5364
- }
5365
- function matched(text, pattern) {
5366
- const found = pattern.exec(text);
5367
- return found === null ? void 0 : found[1];
5368
- }
5369
- function blockOf$1(body, index) {
5370
- const lines = body.split("\n");
5371
- const first = lines[0] ?? "";
5372
- const id = matched(first, ID_LINE);
5373
- const nested = lines.flatMap((line) => {
5374
- const one = matched(line, NESTED_ID);
5375
- return one === void 0 ? [] : [one];
5376
- });
5377
- const insert = id === void 0 && INSERT_LINE.test(first);
5378
- const label = id ?? nested.join(", ");
5379
- const key = id !== void 0 ? `id:${id}` : insert ? `insert:${nested.join(", ")}` : `block:${String(index)}`;
5380
- const names = lines.flatMap((line) => {
5381
- const one = matched(line, NAME_LINE);
5382
- return one === void 0 ? [] : [one];
5383
- });
5384
- return {
5385
- key,
5386
- id: label === "" ? key : label,
5387
- insert,
5388
- body,
5389
- names
5390
- };
5391
- }
5392
- function spannedBlocks(text) {
5393
- const lines = text.split("\n");
5394
- const starts = [];
5395
- lines.forEach((line, index) => {
5396
- if (/^- /.test(line) || line === "-") starts.push(index);
5397
- });
5398
- const blocks = [];
5399
- for (let i = 0; i < starts.length; i += 1) {
5400
- const from = starts[i] ?? 0;
5401
- const to = i + 1 < starts.length ? starts[i + 1] ?? lines.length : lines.length;
5402
- const body = trimmedTail(lines.slice(from, to)).join("\n");
5403
- if (body.trim() === "") continue;
5404
- blocks.push({
5405
- ...blockOf$1(body, blocks.length),
5406
- from,
5407
- to
5408
- });
5409
- }
5410
- return blocks;
5411
- }
5412
- function patchBlocks(text) {
5413
- return spannedBlocks(text).map(({ from: _from, to: _to, ...block }) => block);
5414
- }
5415
- function patchMode(path) {
5416
- try {
5417
- return statSync(path).mode & 511;
5418
- } catch {
5419
- return 420;
6263
+ conflicts.push({
6264
+ kind: "name-mismatch",
6265
+ key: entry.key,
6266
+ names: [need.name],
6267
+ declarers: [.../* @__PURE__ */ new Set([...seen.declarers, ...entry.declarers])],
6268
+ detail
6269
+ });
6270
+ } else if (seen === void 0) byName.set(nameKey, entry);
5420
6271
  }
5421
- }
5422
- function writePatchFile(path, text) {
5423
- const mode = patchMode(path);
5424
- const tmp = `${path}.wb-sync-${String(process.pid)}`;
5425
- writeFileSync(tmp, text, { mode });
5426
- try {
5427
- renameSync(tmp, path);
5428
- } catch (error) {
5429
- try {
5430
- unlinkSync(tmp);
5431
- } catch {
5432
- throw error;
6272
+ const deduped = /* @__PURE__ */ new Map();
6273
+ for (const conflict of conflicts) deduped.set(`${conflict.kind}:${conflict.key}`, conflict);
6274
+ const frozenEntries = /* @__PURE__ */ new Map();
6275
+ for (const [key, entry] of entries) frozenEntries.set(key, {
6276
+ key: entry.key,
6277
+ kind: entry.kind,
6278
+ names: [...entry.names],
6279
+ package: entry.package,
6280
+ ...entry.runtime === void 0 ? {} : { runtime: entry.runtime },
6281
+ sources: entry.sources.map((source) => ({ ...source })),
6282
+ declarers: [...entry.declarers],
6283
+ commands: [...entry.commands],
6284
+ conflicted: entry.conflicted
6285
+ });
6286
+ const entryOf = (kind, name) => byName.get(`${kind}:${name}`);
6287
+ return {
6288
+ entries: frozenEntries,
6289
+ conflicts: [...deduped.values()],
6290
+ problems: [...deduped.values()].map((conflict) => conflict.detail),
6291
+ entryOf,
6292
+ sourcesOf: (kind, name) => {
6293
+ const entry = entryOf(kind, name);
6294
+ return entry === void 0 || entry.conflicted ? [] : entry.sources;
5433
6295
  }
5434
- throw error;
5435
- }
6296
+ };
5436
6297
  }
5437
6298
  //#endregion
5438
6299
  //#region ../shared/dist/host/adapter-set.js
@@ -5492,7 +6353,6 @@ var FULL_METHODS = [
5492
6353
  ["runtime", "runners"],
5493
6354
  ["runtime", "health"],
5494
6355
  ["runtime", "seed"],
5495
- ["runtime", "warnUnwritableRoot"],
5496
6356
  ["plugins", "declarationLookup"],
5497
6357
  ["plugins", "createUnits"],
5498
6358
  ["actions", "configSyncHandler"],
@@ -6297,8 +7157,8 @@ var SUBSECTION = /^###[ \t]/;
6297
7157
  function changelogVersion(value) {
6298
7158
  return String(value ?? "").trim().replace(/^v/, "");
6299
7159
  }
6300
- function changelogDate(now = /* @__PURE__ */ new Date()) {
6301
- return dayjs(now).format("YYYY-MM-DD");
7160
+ function changelogDate(now$3 = new Date(now())) {
7161
+ return dayjs(now$3).format("YYYY-MM-DD");
6302
7162
  }
6303
7163
  function normalizeChangelogText(text) {
6304
7164
  return String(text ?? "").replace(/\r\n?/g, "\n");
@@ -6458,6 +7318,82 @@ function normalizeWhitespace(rows) {
6458
7318
  return out.join("\n").replace(/\n+$/, "");
6459
7319
  }
6460
7320
  //#endregion
7321
+ //#region src/shared/api/host/workbench-seam.ts
7322
+ function provideWorkbenchSeam(ctx) {
7323
+ const shell = ctx.get("shell");
7324
+ if (shell === void 0) return void 0;
7325
+ const runtimeRoot = workbenchRoot();
7326
+ const seam = createSeam({
7327
+ shell,
7328
+ runtimeRoot,
7329
+ realHome: homedir(),
7330
+ allowHost: true,
7331
+ logger: ctx.logger,
7332
+ grantOf: (session) => {
7333
+ const resolved = ctx.get("sandboxPolicy")?.resolve?.({ session });
7334
+ if (resolved === void 0 || resolved.mode === void 0 || resolved.workspaceRoot === void 0) return;
7335
+ return {
7336
+ mode: seamModeOf(resolved.mode),
7337
+ workspaceRoot: resolved.workspaceRoot
7338
+ };
7339
+ }
7340
+ });
7341
+ ctx.provide(WORKBENCH_SEAM, seam);
7342
+ ctx.logger?.info?.(`[dsh-workbench] 执行缝:产品域 ${runtimeRoot}`);
7343
+ return seam;
7344
+ }
7345
+ //#endregion
7346
+ //#region src/shared/api/host/git-exec.ts
7347
+ function gitNoPromptProcessEnv(parent = process.env, platform = process.platform) {
7348
+ return promptlessProcessEnvOf(parent, platform);
7349
+ }
7350
+ function withoutAskpassNoise(text) {
7351
+ return text.split("\n").filter((line) => !/unable to read askpass response/i.test(line)).join("\n").trim();
7352
+ }
7353
+ //#endregion
7354
+ //#region src/shared/api/host/git-process.ts
7355
+ var gitSeam;
7356
+ function promptlessEnv() {
7357
+ return gitNoPromptProcessEnv();
7358
+ }
7359
+ function provideGitProcessSeam(seam) {
7360
+ gitSeam = seam;
7361
+ }
7362
+ function createGitRunner(seam) {
7363
+ return async (args, options = {}) => {
7364
+ const outcome = await seam.run({
7365
+ argv: ["git", ...args],
7366
+ intent: { kind: "host" },
7367
+ env: promptlessEnv(),
7368
+ ...options.cwd === void 0 ? {} : { workdir: options.cwd },
7369
+ ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
7370
+ });
7371
+ return {
7372
+ exitCode: outcome.exitCode ?? null,
7373
+ stdout: outcome.stdout,
7374
+ stderr: outcome.stderr,
7375
+ timedOut: outcome.timedOut
7376
+ };
7377
+ };
7378
+ }
7379
+ var runGitProcess = async (args, options = {}) => {
7380
+ if (gitSeam === void 0) throw new Error("git 命令还没接上执行缝(宿主域)—— 这个进程里没有可用的 shell 服务。");
7381
+ return createGitRunner(gitSeam)(args, options);
7382
+ };
7383
+ //#endregion
7384
+ //#region src/shared/api/host/shipped-adapter-set.ts
7385
+ function shippedAdapterSetSpec(moduleUrl) {
7386
+ const spec = JSON.parse(readFileSync(new URL("../../package.json", moduleUrl), "utf8")).workbench?.adapterSet;
7387
+ if (typeof spec !== "string" || spec.trim() === "") throw new Error("package.json 没写 workbench.adapterSet:外壳不知道该装哪份适配器集合");
7388
+ return spec.trim();
7389
+ }
7390
+ async function shippedAdapterSet(ctx) {
7391
+ const spec = shippedAdapterSetSpec(import.meta.url);
7392
+ const factory = (await import(spec)).createAdapterSet;
7393
+ if (typeof factory !== "function") throw new Error(`随包的适配器集合 ${spec} 没导出 createAdapterSet(ctx)`);
7394
+ return await factory(ctx);
7395
+ }
7396
+ //#endregion
6461
7397
  //#region ../shared/dist/contract/protocol/dsh-plugins.js
6462
7398
  var DSH_PLUGINS_ROUTE = "dsh-plugins";
6463
7399
  //#endregion
@@ -6481,13 +7417,11 @@ function isFollowUpDismissed(runtimeRoot, id) {
6481
7417
  return Object.prototype.hasOwnProperty.call(readDismissed(runtimeRoot), id);
6482
7418
  }
6483
7419
  function dismissFollowUp(runtimeRoot, id) {
6484
- const file = fileOf$1(runtimeRoot);
6485
- mkdirSync(dirname(file), { recursive: true });
6486
7420
  const dismissed = {
6487
7421
  ...readDismissed(runtimeRoot),
6488
- [id]: { at: (/* @__PURE__ */ new Date()).toISOString() }
7422
+ [id]: { at: nowIso() }
6489
7423
  };
6490
- writeFileAtomic.sync(file, `${JSON.stringify({ dismissed }, null, 2)}\n`);
7424
+ writeJsonFile(fileOf$1(runtimeRoot), { dismissed });
6491
7425
  }
6492
7426
  //#endregion
6493
7427
  //#region src/shared/api/host/migration-follow-up.ts
@@ -6609,7 +7543,7 @@ function titleOf(raw) {
6609
7543
  }
6610
7544
  async function latestIfReleaseReady(supply, input) {
6611
7545
  const missing = await supply.missingReleaseParts({
6612
- shell: input.shell,
7546
+ seam: input.seam,
6613
7547
  name: input.pkg,
6614
7548
  registry: input.registry,
6615
7549
  version: input.version,
@@ -6630,18 +7564,18 @@ async function workbenchVersion(supply, input) {
6630
7564
  if (read === void 0) return void 0;
6631
7565
  const spec = dependencyReader(input.dir)(PLUGIN_ID);
6632
7566
  const registry = read.registry ?? input.fallback;
6633
- const shell = input.shell;
6634
- const queried = read.local === true || !input.check || shell === void 0 || registry === void 0 ? void 0 : await supply.latestVersion({
6635
- shell,
7567
+ const seam = input.seam;
7568
+ const queried = read.local === true || !input.check || seam === void 0 || registry === void 0 ? void 0 : await supply.latestVersion({
7569
+ seam,
6636
7570
  name: read.pkg,
6637
7571
  registry,
6638
7572
  force: input.force
6639
7573
  });
6640
- const latest = queried === void 0 || shell === void 0 || registry === void 0 || !supply.isNewerVersion(queried, read.version) ? void 0 : await latestIfReleaseReady(supply, {
7574
+ const latest = queried === void 0 || seam === void 0 || registry === void 0 || !supply.isNewerVersion(queried, read.version) ? void 0 : await latestIfReleaseReady(supply, {
6641
7575
  pkg: read.pkg,
6642
7576
  version: queried,
6643
7577
  registry,
6644
- shell,
7578
+ seam,
6645
7579
  force: input.force,
6646
7580
  logger: input.logger
6647
7581
  });
@@ -6656,7 +7590,7 @@ async function workbenchVersion(supply, input) {
6656
7590
  ...registry === void 0 || read.local === true ? {} : { registry }
6657
7591
  };
6658
7592
  }
6659
- function moduleVersionsHandler(ctx, shell, running, selfUpdate) {
7593
+ function moduleVersionsHandler(ctx, seam, running, selfUpdate) {
6660
7594
  return async (search) => {
6661
7595
  const supply = supplyPortOf(ctx);
6662
7596
  const manager = supply.readBundleManager(ctx);
@@ -6676,8 +7610,8 @@ function moduleVersionsHandler(ctx, shell, running, selfUpdate) {
6676
7610
  const fromRegistry = module.fromRegistry !== false;
6677
7611
  const registry = fromRegistry ? module.registry ?? fallback : void 0;
6678
7612
  const spec = dependencyOf(module.pkg);
6679
- const latest = local || !fromRegistry || !check || shell === void 0 || registry === void 0 ? void 0 : await supply.latestVersion({
6680
- shell,
7613
+ const latest = local || !fromRegistry || !check || seam === void 0 || registry === void 0 ? void 0 : await supply.latestVersion({
7614
+ seam,
6681
7615
  name: module.pkg,
6682
7616
  registry,
6683
7617
  force
@@ -6686,8 +7620,8 @@ function moduleVersionsHandler(ctx, shell, running, selfUpdate) {
6686
7620
  const declared = raw === void 0 ? [] : declarationProblemsOf(module.pkg, raw);
6687
7621
  const title = titleOf(raw);
6688
7622
  const refused = refusals.get(module.unit) ?? (declared.length === 0 ? void 0 : declared);
6689
- const latestRaw = latest === void 0 || latest === module.version || shell === void 0 || registry === void 0 ? void 0 : await supply.registryDeclarationOf({
6690
- shell,
7623
+ const latestRaw = latest === void 0 || latest === module.version || seam === void 0 || registry === void 0 ? void 0 : await supply.registryDeclarationOf({
7624
+ seam,
6691
7625
  name: module.pkg,
6692
7626
  registry,
6693
7627
  force
@@ -6727,7 +7661,7 @@ function moduleVersionsHandler(ctx, shell, running, selfUpdate) {
6727
7661
  });
6728
7662
  const workbench = await workbenchVersion(supply, {
6729
7663
  dir,
6730
- shell,
7664
+ seam,
6731
7665
  fallback,
6732
7666
  force,
6733
7667
  check,
@@ -6755,23 +7689,23 @@ async function declarationRefusal(supply, spec, inspection, ctx, manager) {
6755
7689
  if (inspection.status !== "accepted" || inspection.name === void 0) return void 0;
6756
7690
  if (inspection.kind === "path") return refusedInspection(supply.declarationRefusalOf(inspection.name, supply.pathDeclarationOf(spec)));
6757
7691
  if (inspection.kind !== "registry") return void 0;
6758
- const shell = ctx.get("shell");
6759
- if (shell === void 0) return void 0;
7692
+ const seam = seamOf(ctx);
7693
+ if (seam === void 0) return void 0;
6760
7694
  const registry = typeof inspection.registry === "string" ? inspection.registry : (await manager.registries())?.resolved ?? void 0;
6761
7695
  if (registry === void 0) return void 0;
6762
7696
  const raw = await supply.registryDeclarationOf({
6763
- shell,
7697
+ seam,
6764
7698
  name: inspection.name,
6765
7699
  registry
6766
7700
  });
6767
7701
  return refusedInspection(supply.declarationRefusalOf(inspection.name, raw));
6768
7702
  }
6769
7703
  async function packageOverride(supply, spec, inspection, ctx, manager, asked) {
6770
- const shell = ctx.get("shell");
7704
+ const seam = seamOf(ctx);
6771
7705
  const registry = (inspection.status === "accepted" && typeof inspection.registry === "string" ? inspection.registry : void 0) ?? (typeof asked === "string" ? asked : (await manager.registries())?.resolved ?? void 0);
6772
7706
  const route = await supply.packageRouteOf({
6773
7707
  spec,
6774
- ...shell === void 0 ? {} : { shell },
7708
+ ...seam === void 0 ? {} : { seam },
6775
7709
  ...registry === void 0 ? {} : { registry }
6776
7710
  });
6777
7711
  if (route !== void 0 && route.kind === "runtime-plugin") {
@@ -6826,57 +7760,6 @@ function bundleInspectHandler(ctx) {
6826
7760
  };
6827
7761
  }
6828
7762
  //#endregion
6829
- //#region src/shared/api/host/git-exec.ts
6830
- function gitNoPromptEnv(platform = process.platform) {
6831
- const shared = {
6832
- GIT_TERMINAL_PROMPT: "0",
6833
- SSH_ASKPASS_REQUIRE: "never",
6834
- GCM_INTERACTIVE: "never",
6835
- GIT_SSH_COMMAND: "ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new"
6836
- };
6837
- if (platform === "win32") return shared;
6838
- return {
6839
- ...shared,
6840
- GIT_ASKPASS: "/usr/bin/false",
6841
- SSH_ASKPASS: "/usr/bin/false"
6842
- };
6843
- }
6844
- function gitNoPromptProcessEnv(parent = process.env, platform = process.platform) {
6845
- const env = {
6846
- ...parent,
6847
- ...gitNoPromptEnv(platform)
6848
- };
6849
- if (platform === "win32") {
6850
- delete env.GIT_ASKPASS;
6851
- delete env.SSH_ASKPASS;
6852
- }
6853
- return env;
6854
- }
6855
- function withoutAskpassNoise(text) {
6856
- return text.split("\n").filter((line) => !/unable to read askpass response/i.test(line)).join("\n").trim();
6857
- }
6858
- //#endregion
6859
- //#region src/shared/api/host/git-process.ts
6860
- var DEFAULT_MAX_BYTES$2 = 8388608;
6861
- var runGitProcess = async (args, options = {}) => {
6862
- const result = await execa("git", [...args], {
6863
- ...options.cwd === void 0 ? {} : { cwd: options.cwd },
6864
- env: gitNoPromptProcessEnv(),
6865
- stdin: "ignore",
6866
- reject: false,
6867
- ...options.timeoutMs === void 0 ? {} : { timeout: options.timeoutMs },
6868
- killSignal: "SIGKILL",
6869
- maxBuffer: options.maxBytes ?? DEFAULT_MAX_BYTES$2,
6870
- stripFinalNewline: false
6871
- });
6872
- return {
6873
- exitCode: result.exitCode ?? null,
6874
- stdout: result.stdout ?? "",
6875
- stderr: result.stderr ?? "",
6876
- timedOut: result.timedOut === true
6877
- };
6878
- };
6879
- //#endregion
6880
7763
  //#region src/shared/api/host/git-probe.ts
6881
7764
  var PROBE_TIMEOUT_MS = 1e4;
6882
7765
  var AUTH_TEXT = /authentication failed|could not read (username|password)|permission denied|terminal prompts disabled|host key verification failed|repository not found|no such identity|access denied|unauthorized|forbidden/;
@@ -6910,10 +7793,10 @@ var PROBE_CACHE_MS = 6e4;
6910
7793
  var probeCache = /* @__PURE__ */ new Map();
6911
7794
  async function probeGitAddressCached(url, options = {}) {
6912
7795
  const hit = probeCache.get(url);
6913
- if (hit !== void 0 && Date.now() - hit.at < PROBE_CACHE_MS) return hit.payload;
7796
+ if (hit !== void 0 && now() - hit.at < PROBE_CACHE_MS) return hit.payload;
6914
7797
  const payload = await probeGitAddress(url, options);
6915
7798
  probeCache.set(url, {
6916
- at: Date.now(),
7799
+ at: now(),
6917
7800
  payload
6918
7801
  });
6919
7802
  return payload;
@@ -6947,18 +7830,15 @@ function createHostRoutes(input) {
6947
7830
  [BUNDLE_REGISTRIES_ROUTE]: bundleRegistriesHandler(ctx),
6948
7831
  [BUNDLE_INSPECT_ROUTE]: bundleInspectHandler(ctx),
6949
7832
  [MARKET_GIT_PROBE_ROUTE]: gitProbeHandler(),
6950
- [MODULE_VERSIONS_ROUTE]: moduleVersionsHandler(ctx, ctx.get("shell"), running, () => profile.selfUpdateView(dshHome(), basename(profile.dirOf(ctx) ?? "")))
7833
+ [MODULE_VERSIONS_ROUTE]: moduleVersionsHandler(ctx, seamOf(ctx), running, () => profile.selfUpdateView(dshHome(), basename(profile.dirOf(ctx) ?? "")))
6951
7834
  };
6952
7835
  }
6953
- var WORKSHOP_RELATIVE = "workbench/diy";
6954
7836
  var WORKSHOP_TITLE = "工作台DIY";
6955
7837
  //#endregion
6956
7838
  //#region src/shared/api/host/diy.ts
6957
- function workshopPathOf(ctx) {
6958
- const homePath = ctx.get("dshHomePath");
6959
- if (typeof homePath !== "function") return null;
7839
+ function workshopPathOf(_ctx) {
6960
7840
  try {
6961
- return homePath(WORKSHOP_RELATIVE);
7841
+ return join(workbenchRoot(), "diy");
6962
7842
  } catch {
6963
7843
  return null;
6964
7844
  }
@@ -7135,7 +8015,7 @@ var panelsUnit = {
7135
8015
  }))],
7136
8016
  host: {
7137
8017
  inject: [],
7138
- optional: ["tools", "shell"]
8018
+ optional: ["tools", "workbenchSeam"]
7139
8019
  }
7140
8020
  };
7141
8021
  //#endregion
@@ -8015,8 +8895,10 @@ function toolchainBinOf(root) {
8015
8895
  function queryCommandOf(bin, only) {
8016
8896
  return `${quoteShellArg(bin)} check --only ${only} --json`;
8017
8897
  }
8018
- async function runSurfaceQuery(shell, bin, only, root, fallbackBin) {
8019
- const result = await runShell(shell, queryCommandOf(bin, only), {
8898
+ async function runSurfaceQuery(seam, bin, only, root, fallbackBin) {
8899
+ const result = await seam.run({
8900
+ command: queryCommandOf(bin, only),
8901
+ intent: noWrite(),
8020
8902
  workdir: root,
8021
8903
  timeoutMs: SURFACE_TIMEOUT_MS
8022
8904
  });
@@ -8024,7 +8906,9 @@ async function runSurfaceQuery(shell, bin, only, root, fallbackBin) {
8024
8906
  if (parsed !== null) return withoutProblems(parsed);
8025
8907
  const retry = fallbackBin === void 0 || fallbackBin === bin ? void 0 : fallbackBin;
8026
8908
  if (retry !== void 0) {
8027
- const secondParsed = jsonOf$1((await runShell(shell, queryCommandOf(retry, only), {
8909
+ const secondParsed = jsonOf$1((await seam.run({
8910
+ command: queryCommandOf(retry, only),
8911
+ intent: noWrite(),
8028
8912
  workdir: root,
8029
8913
  timeoutMs: SURFACE_TIMEOUT_MS
8030
8914
  })).stdout);
@@ -8790,11 +9674,11 @@ function componentsTool(ctx) {
8790
9674
  })).find((one) => one.bin !== null) ?? null;
8791
9675
  const bin = local?.bin ?? shellBin;
8792
9676
  if (bin === null) throw new Error(`这个工作区(和它各模块)里还没有 dsh-wb-module(${join(root, "node_modules")} 下没有)—— 在它根目录跑一次 pnpm install 就有了;workbench_diy_status 会告诉你卡在哪一步`);
8793
- const shell = ctx.get("shell");
8794
- if (shell === void 0) throw new Error("这台机器没给 shell 服务,读不了组件清单");
9677
+ const seam = seamOf(ctx);
9678
+ if (seam === void 0) throw new Error("这台机器没给 shell 服务,读不了组件清单");
8795
9679
  const workdir = local?.dir ?? shellRoot ?? root;
8796
9680
  const fallbackBin = local !== null && shellBin !== null && shellBin !== bin ? shellBin : void 0;
8797
- const [primitives, kit] = await Promise.all([wantsPrimitives(scope) ? runSurfaceQuery(shell, bin, "primitives", workdir, fallbackBin) : Promise.resolve(void 0), wantsKit(scope) ? runSurfaceQuery(shell, bin, "kit", workdir, fallbackBin) : Promise.resolve(void 0)]);
9681
+ const [primitives, kit] = await Promise.all([wantsPrimitives(scope) ? runSurfaceQuery(seam, bin, "primitives", workdir, fallbackBin) : Promise.resolve(void 0), wantsKit(scope) ? runSurfaceQuery(seam, bin, "kit", workdir, fallbackBin) : Promise.resolve(void 0)]);
8798
9682
  const query = {
8799
9683
  ...primitives === void 0 ? {} : { primitives },
8800
9684
  ...kit === void 0 ? {} : { kit }
@@ -8825,12 +9709,10 @@ function registerComponentsTool(ctx, tools) {
8825
9709
  }
8826
9710
  //#endregion
8827
9711
  //#region src/shared/api/host/diy-artifacts.ts
8828
- function artifactsDirOf(ctx, root) {
8829
- const homePath = ctx.get("dshHomePath");
8830
- if (typeof homePath !== "function") throw new Error("这台机器没有 dshHomePath 服务 —— 落不了截图与录制(取证目录由它给)");
9712
+ function artifactsDirOf(root) {
8831
9713
  const name = basename(root).replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "workspace";
8832
9714
  const hash = createHash("sha1").update(root).digest("hex").slice(0, 8);
8833
- return homePath(join("workbench", "artifacts", `${name}-${hash}`));
9715
+ return join(workbenchRoot(), "artifacts", `${name}-${hash}`);
8834
9716
  }
8835
9717
  function ensureDirOf(dir) {
8836
9718
  mkdirSync(dir, { recursive: true });
@@ -8877,8 +9759,12 @@ var LOOK_TIMEOUT_MS = 3e4;
8877
9759
  function plainLine(command, argv) {
8878
9760
  return [command, ...argv].map(quoteShellArg).join(" ");
8879
9761
  }
8880
- async function runBrowserStep(shell, command, argv, timeoutMs = LOOK_TIMEOUT_MS, line = plainLine) {
8881
- const result = await runShell(shell, line(command, argv), { timeoutMs });
9762
+ async function runBrowserStep(seam, command, argv, timeoutMs = LOOK_TIMEOUT_MS, line = plainLine, intent = productWrite()) {
9763
+ const result = await seam.run({
9764
+ command: line(command, argv),
9765
+ intent,
9766
+ timeoutMs
9767
+ });
8882
9768
  return {
8883
9769
  ok: result.exitCode === 0 && !result.timedOut,
8884
9770
  stdout: result.stdout.trim(),
@@ -8902,8 +9788,8 @@ function jsonOf(raw) {
8902
9788
  function textOf$2(value) {
8903
9789
  return typeof value === "string" ? value.trim() : "";
8904
9790
  }
8905
- async function bindCurrentTab(shell, command, session, timeoutMs, line) {
8906
- const step = await runBrowserStep(shell, command, [
9791
+ async function bindCurrentTab(seam, command, session, timeoutMs, line) {
9792
+ const step = await runBrowserStep(seam, command, [
8907
9793
  "browser",
8908
9794
  session,
8909
9795
  "bind",
@@ -8934,8 +9820,8 @@ async function bindCurrentTab(shell, command, session, timeoutMs, line) {
8934
9820
  reason: ""
8935
9821
  };
8936
9822
  }
8937
- async function workbenchPageOf(shell, command, session) {
8938
- const parsed = jsonOf((await runBrowserStep(shell, command, [
9823
+ async function workbenchPageOf(seam, command, session) {
9824
+ const parsed = jsonOf((await runBrowserStep(seam, command, [
8939
9825
  "browser",
8940
9826
  session,
8941
9827
  "eval",
@@ -8963,22 +9849,22 @@ async function workbenchPageOf(shell, command, session) {
8963
9849
  shown: parsed["shown"] === true
8964
9850
  };
8965
9851
  }
8966
- async function readyPageOf(shell, command, session) {
8967
- const already = await workbenchPageOf(shell, command, session);
9852
+ async function readyPageOf(seam, command, session) {
9853
+ const already = await workbenchPageOf(seam, command, session);
8968
9854
  if (already.ok) return {
8969
9855
  ok: true,
8970
9856
  bound: false,
8971
9857
  page: already,
8972
9858
  reason: ""
8973
9859
  };
8974
- const bound = await bindCurrentTab(shell, command, session);
9860
+ const bound = await bindCurrentTab(seam, command, session);
8975
9861
  if (!bound.ok) return {
8976
9862
  ok: false,
8977
9863
  bound: false,
8978
9864
  page: already,
8979
9865
  reason: bound.reason
8980
9866
  };
8981
- const after = await workbenchPageOf(shell, command, session);
9867
+ const after = await workbenchPageOf(seam, command, session);
8982
9868
  return {
8983
9869
  ok: after.ok,
8984
9870
  bound: true,
@@ -9033,8 +9919,8 @@ async function pageAt(ctx, exec, raw, options = {}) {
9033
9919
  const pool = units.map((one) => one.unit);
9034
9920
  const wanted = asked === "" && pool.length === 1 ? pool[0] ?? "" : asked;
9035
9921
  if (wanted === "" && options.requireUnit !== false) throw new Error(`看一眼要一个 unit(装过的:${pool.join(", ") || "(空)"})`);
9036
- const shell = ctx.get("shell");
9037
- if (shell === void 0) throw new Error("这台机器上没有 shell 服务,动不了浏览器");
9922
+ const seam = seamOf(ctx);
9923
+ if (seam === void 0) throw new Error("这台机器上没有 shell 服务,动不了浏览器");
9038
9924
  const held = ctx.get("workbenchDiyDebug")?.live(Date.now()) ?? null;
9039
9925
  const sessionId = sessionIdOf(exec);
9040
9926
  if (options.requirePact !== false) {
@@ -9044,7 +9930,7 @@ async function pageAt(ctx, exec, raw, options = {}) {
9044
9930
  }
9045
9931
  const command = cliCommandOf(ctx);
9046
9932
  const session = asText(args["session"]) || "workbench";
9047
- const handshake = await readyPageOf(shell, command, session);
9933
+ const handshake = await readyPageOf(seam, command, session);
9048
9934
  if (!handshake.ok && options.requireWorkbench !== false && !hostSurface().capabilities.pageDebug) throw new Error(desktopPageText(handshake.page));
9049
9935
  if (!handshake.ok && !handshake.bound) throw new Error(`连不上浏览器:${handshake.reason}。先让用户把工作台那一页切到前台(只有第一次要这样);还不行就跑一次运行时自检(doctor)看扩展连没连上。`);
9050
9936
  if (!handshake.ok && options.requireWorkbench !== false) {
@@ -9057,10 +9943,10 @@ async function pageAt(ctx, exec, raw, options = {}) {
9057
9943
  if (options.requireBoard !== false && (board === "other" || board === "hidden")) throw new Error(boardGateText(situation, wanted, handshake.page, title));
9058
9944
  return {
9059
9945
  root,
9060
- artifactsDir: artifactsDirOf(ctx, root),
9946
+ artifactsDir: artifactsDirOf(root),
9061
9947
  unit: wanted,
9062
9948
  args,
9063
- shell,
9949
+ seam,
9064
9950
  command,
9065
9951
  session,
9066
9952
  ...surfaceOf(ctx)?.background === void 0 ? {} : { line: surfaceOf(ctx)?.background },
@@ -9220,8 +10106,8 @@ function registerDebugTools(ctx, tools, store) {
9220
10106
  //#endregion
9221
10107
  //#region src/modules/panels/model/diy-browser-act.ts
9222
10108
  var ACT_TIMEOUT_MS = 15e3;
9223
- async function actBrowser(shell, command, session, argv, timeoutMs = ACT_TIMEOUT_MS, line) {
9224
- const step = await runBrowserStep(shell, command, [
10109
+ async function actBrowser(seam, command, session, argv, timeoutMs = ACT_TIMEOUT_MS, line) {
10110
+ const step = await runBrowserStep(seam, command, [
9225
10111
  "browser",
9226
10112
  session,
9227
10113
  ...argv,
@@ -9244,10 +10130,10 @@ function actErrorOf(step) {
9244
10130
  return step.stderr || step.stdout || "运行时报了个错";
9245
10131
  }
9246
10132
  var SWEEP_TIMEOUT_MS = 4e3;
9247
- async function stopNetworkExact(shell, file, timeoutMs = SWEEP_TIMEOUT_MS) {
10133
+ async function stopNetworkExact(seam, file, timeoutMs = SWEEP_TIMEOUT_MS) {
9248
10134
  const pidFile = quoteShellArg(`${file}.pid`);
9249
10135
  const reaperFile = quoteShellArg(`${file}.reaper`);
9250
- return (await runShell(shell, [
10136
+ const line = [
9251
10137
  `p="$(cat ${pidFile} 2>/dev/null)"`,
9252
10138
  `r="$(cat ${reaperFile} 2>/dev/null)"`,
9253
10139
  `[ -n "$p" ] && pkill -P "$p" 2>/dev/null`,
@@ -9256,7 +10142,12 @@ async function stopNetworkExact(shell, file, timeoutMs = SWEEP_TIMEOUT_MS) {
9256
10142
  `[ -n "$r" ] && kill "$r" 2>/dev/null`,
9257
10143
  `if [ "$(cat ${pidFile} 2>/dev/null)" = "$p" ]; then rm -f ${pidFile} ${reaperFile}; fi`,
9258
10144
  "true"
9259
- ].join("; "), { timeoutMs })).exitCode === 0;
10145
+ ].join("; ");
10146
+ return (await seam.run({
10147
+ command: line,
10148
+ intent: productWrite(),
10149
+ timeoutMs
10150
+ })).exitCode === 0;
9260
10151
  }
9261
10152
  //#endregion
9262
10153
  //#region src/modules/panels/model/diy-logs.ts
@@ -9379,7 +10270,7 @@ function registerLogTools(ctx, tools) {
9379
10270
  });
9380
10271
  return {
9381
10272
  page,
9382
- step: await actBrowser(page.shell, page.command, page.session, ["eval", script])
10273
+ step: await actBrowser(page.seam, page.command, page.session, ["eval", script])
9383
10274
  };
9384
10275
  }
9385
10276
  const watchTool = {
@@ -9632,7 +10523,7 @@ function cropPngFile(input, output, rect) {
9632
10523
  reason: cropped.reason
9633
10524
  };
9634
10525
  try {
9635
- writeFileSync(output, cropped.bytes);
10526
+ writeBytesFile(output, cropped.bytes);
9636
10527
  } catch (error) {
9637
10528
  return {
9638
10529
  ok: false,
@@ -9647,6 +10538,44 @@ function cropPngFile(input, output, rect) {
9647
10538
  };
9648
10539
  }
9649
10540
  //#endregion
10541
+ //#region src/modules/panels/model/diy-browser-clip.ts
10542
+ function finite(value) {
10543
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
10544
+ }
10545
+ function clipOf(value) {
10546
+ if (typeof value !== "object" || value === null) return void 0;
10547
+ const raw = value;
10548
+ const x = finite(raw["x"]);
10549
+ const y = finite(raw["y"]);
10550
+ const width = finite(raw["width"]);
10551
+ const height = finite(raw["height"]);
10552
+ if (x === void 0 || y === void 0 || width === void 0 || height === void 0) return void 0;
10553
+ if (x < 0 || y < 0 || width <= 0 || height <= 0) return void 0;
10554
+ return {
10555
+ x,
10556
+ y,
10557
+ width,
10558
+ height
10559
+ };
10560
+ }
10561
+ function deviceClipRect(clip, dpr) {
10562
+ const scale = Number.isFinite(dpr) && dpr > 0 ? dpr : 1;
10563
+ return {
10564
+ x: Math.max(0, Math.round(clip.x * scale)),
10565
+ y: Math.max(0, Math.round(clip.y * scale)),
10566
+ width: Math.max(1, Math.round(clip.width * scale)),
10567
+ height: Math.max(1, Math.round(clip.height * scale))
10568
+ };
10569
+ }
10570
+ function clipNote(clip, viewport) {
10571
+ if (viewport.width <= 0 || viewport.height <= 0) return "没读到视口尺寸(那条量 dpr / innerWidth 的 eval 没回来)—— 给的是整屏图,裁不了";
10572
+ if (clip.x + clip.width <= viewport.width && clip.y + clip.height <= viewport.height && viewport.width > 0) return "";
10573
+ return `要裁的那块 ${String(clip.x)},${String(clip.y)} ${String(clip.width)}×${String(clip.height)} 不在视口里(视口 ${String(viewport.width)}×${String(viewport.height)})—— 给的是整屏图`;
10574
+ }
10575
+ function defaultShotPath(dir, unit, stamp) {
10576
+ return join(dir, `${unit === "" ? "page" : unit}-${String(stamp)}.png`);
10577
+ }
10578
+ //#endregion
9650
10579
  //#region src/modules/panels/model/diy-schema.ts
9651
10580
  var UNIT_PROP = {
9652
10581
  type: "string",
@@ -9718,42 +10647,6 @@ function argvOf(row, raw, unit) {
9718
10647
  if (row.css.kind === "flag") return prefixValue(tokens, row.css.flag, card);
9719
10648
  return prefixPositional(tokens, row.css.at, card, row.css.onlyWhenFirst);
9720
10649
  }
9721
- function finite(value) {
9722
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
9723
- }
9724
- function clipOf(value) {
9725
- if (typeof value !== "object" || value === null) return void 0;
9726
- const raw = value;
9727
- const x = finite(raw["x"]);
9728
- const y = finite(raw["y"]);
9729
- const width = finite(raw["width"]);
9730
- const height = finite(raw["height"]);
9731
- if (x === void 0 || y === void 0 || width === void 0 || height === void 0) return void 0;
9732
- if (x < 0 || y < 0 || width <= 0 || height <= 0) return void 0;
9733
- return {
9734
- x,
9735
- y,
9736
- width,
9737
- height
9738
- };
9739
- }
9740
- function deviceClipRect(clip, dpr) {
9741
- const scale = Number.isFinite(dpr) && dpr > 0 ? dpr : 1;
9742
- return {
9743
- x: Math.max(0, Math.round(clip.x * scale)),
9744
- y: Math.max(0, Math.round(clip.y * scale)),
9745
- width: Math.max(1, Math.round(clip.width * scale)),
9746
- height: Math.max(1, Math.round(clip.height * scale))
9747
- };
9748
- }
9749
- function clipNote(clip, viewport) {
9750
- if (viewport.width <= 0 || viewport.height <= 0) return "没读到视口尺寸(那条量 dpr / innerWidth 的 eval 没回来)—— 给的是整屏图,裁不了";
9751
- if (clip.x + clip.width <= viewport.width && clip.y + clip.height <= viewport.height && viewport.width > 0) return "";
9752
- return `要裁的那块 ${String(clip.x)},${String(clip.y)} ${String(clip.width)}×${String(clip.height)} 不在视口里(视口 ${String(viewport.width)}×${String(viewport.height)})—— 给的是整屏图`;
9753
- }
9754
- function defaultShotPath(dir, unit, stamp) {
9755
- return join(dir, `${unit === "" ? "page" : unit}-${String(stamp)}.png`);
9756
- }
9757
10650
  var BROWSER_TOOLS = [
9758
10651
  {
9759
10652
  tool: DIY_STATE_TOOL,
@@ -9934,7 +10827,7 @@ function parametersOf(row, command) {
9934
10827
  };
9935
10828
  }
9936
10829
  async function viewportOf(page) {
9937
- const parsed = jsonOf((await runBrowserStep(page.shell, page.command, [
10830
+ const parsed = jsonOf((await runBrowserStep(page.seam, page.command, [
9938
10831
  "browser",
9939
10832
  page.session,
9940
10833
  "eval",
@@ -9960,8 +10853,8 @@ function hostOf$1(url) {
9960
10853
  return "";
9961
10854
  }
9962
10855
  }
9963
- async function onSiteOnce(shell, command, site, line) {
9964
- const step = await runBrowserStep(shell, command, [
10856
+ async function onSiteOnce(seam, command, site, line) {
10857
+ const step = await runBrowserStep(seam, command, [
9965
10858
  "browser",
9966
10859
  BROWSER_READ_SESSION,
9967
10860
  "get",
@@ -9971,7 +10864,7 @@ async function onSiteOnce(shell, command, site, line) {
9971
10864
  ], LOOK_TIMEOUT_MS, line);
9972
10865
  const want = hostOf$1(site);
9973
10866
  if (want === "" || hostOf$1(step.stdout.trim()) === want) return false;
9974
- await runBrowserStep(shell, command, [
10867
+ await runBrowserStep(seam, command, [
9975
10868
  "browser",
9976
10869
  BROWSER_READ_SESSION,
9977
10870
  "open",
@@ -9994,15 +10887,15 @@ function readRefusalOf(row, raw) {
9994
10887
  async function runReadTool(row, ctx, raw, site) {
9995
10888
  const refused = readRefusalOf(row, raw);
9996
10889
  if (refused !== "") throw new Error(refused);
9997
- const shell = ctx.get("shell");
9998
- if (shell === void 0) throw new Error("这台机器上没有 shell 服务,驱动不了浏览器");
10890
+ const seam = seamOf(ctx);
10891
+ if (seam === void 0) throw new Error("这台机器上没有 shell 服务,驱动不了浏览器");
9999
10892
  const command = cliCommandOf(ctx);
10000
10893
  const requested = asNumber(raw["timeoutMs"]);
10001
10894
  const timeoutMs = requested !== void 0 && requested > 0 ? requested : DEFAULT_TIMEOUT_MS$4;
10002
10895
  const argv = stringsOf$1(raw["args"]);
10003
10896
  const line = surfaceOf(ctx)?.background;
10004
- const opened = await onSiteOnce(shell, command, site, line);
10005
- const step = await runBrowserStep(shell, command, [
10897
+ const opened = await onSiteOnce(seam, command, site, line);
10898
+ const step = await runBrowserStep(seam, command, [
10006
10899
  "browser",
10007
10900
  BROWSER_READ_SESSION,
10008
10901
  ...row.verb,
@@ -10037,22 +10930,26 @@ async function runBrowserTool(row, ctx, raw, exec) {
10037
10930
  const clipAsked = row.clip === true && page.args["clip"] !== void 0;
10038
10931
  let file = "";
10039
10932
  let finalArgv = argv;
10933
+ let intent = productWrite();
10040
10934
  if (row.clip === true) {
10041
10935
  const given = argv[0] !== void 0 && !argv[0].startsWith("-") ? argv[0] : "";
10042
10936
  if (given === "") {
10043
10937
  file = defaultShotPath(page.artifactsDir, page.unit, Date.now());
10044
10938
  finalArgv = [file, ...argv];
10045
10939
  ensureDirOf(dirname(file));
10046
- } else file = given;
10940
+ } else {
10941
+ file = given;
10942
+ intent = pathsWrite([file]);
10943
+ }
10047
10944
  }
10048
- const step = await runBrowserStep(page.shell, page.command, [
10945
+ const step = await runBrowserStep(page.seam, page.command, [
10049
10946
  "browser",
10050
10947
  page.session,
10051
10948
  ...row.verb,
10052
10949
  ...finalArgv,
10053
10950
  "--window",
10054
10951
  "background"
10055
- ], timeoutMs, page.line);
10952
+ ], timeoutMs, page.line, intent);
10056
10953
  const shot = {};
10057
10954
  if (row.clip === true) {
10058
10955
  shot["file"] = step.ok ? file : "";
@@ -10138,11 +11035,15 @@ function specTool(ctx) {
10138
11035
  const args = asRecord$1(raw);
10139
11036
  const argv = specArgvOf(asText(args["command"]));
10140
11037
  if (argv.length === 0) throw new Error("给一条命令,site 与名字两个词 —— 例 `inventory publish`(`inventory/publish` 也认)。");
10141
- const shell = ctx.get("shell");
10142
- if (shell === void 0) throw new Error("这台机器上没有 shell 服务,问不了命令的形状");
11038
+ const seam = seamOf(ctx);
11039
+ if (seam === void 0) throw new Error("这台机器上没有 shell 服务,问不了命令的形状");
10143
11040
  const asked = asNumber(args["timeoutMs"]);
10144
11041
  const timeoutMs = asked !== void 0 && asked > 0 ? asked : SPEC_TIMEOUT_MS;
10145
- const result = await runShell(shell, (surfaceOf(ctx)?.background ?? plainLine)(cliCommandOf(ctx), argv), { timeoutMs });
11042
+ const result = await seam.run({
11043
+ command: (surfaceOf(ctx)?.background ?? plainLine)(cliCommandOf(ctx), argv),
11044
+ intent: productWrite(),
11045
+ timeoutMs
11046
+ });
10146
11047
  const spec = jsonOf(result.stdout);
10147
11048
  return plainJson({
10148
11049
  ok: result.exitCode === 0 && !result.timedOut,
@@ -10160,8 +11061,139 @@ function registerSpecTool(ctx, tools) {
10160
11061
  return [tools.register(specTool(ctx))];
10161
11062
  }
10162
11063
  //#endregion
11064
+ //#region src/shared/api/host/diy-release-staging.ts
11065
+ var STAGING_ROOT = "dsh-wb-release";
11066
+ function stagingDirOf(unit) {
11067
+ return join(tmpdir(), STAGING_ROOT, unit.replace(/[^A-Za-z0-9._-]+/g, "-"));
11068
+ }
11069
+ function journalFileOf(staging, unit) {
11070
+ return join(staging, "releases", `${unit}.json`);
11071
+ }
11072
+ function prepareStaging(unit, resume) {
11073
+ const staging = stagingDirOf(unit);
11074
+ if (!resume) rmSync(staging, {
11075
+ recursive: true,
11076
+ force: true
11077
+ });
11078
+ mkdirSync(staging, { recursive: true });
11079
+ return staging;
11080
+ }
11081
+ function cleanupStaging(unit) {
11082
+ rmSync(stagingDirOf(unit), {
11083
+ recursive: true,
11084
+ force: true
11085
+ });
11086
+ }
11087
+ function adoptTarballFile(file, unit) {
11088
+ if (file === "" || !existsSync(file)) return void 0;
11089
+ const dest = join(workbenchRoot(), "artifacts", unit, basename(file));
11090
+ if (dest === file) return dest;
11091
+ mkdirSync(dirname(dest), { recursive: true });
11092
+ copyFileSync(file, dest);
11093
+ rmSync(file, { force: true });
11094
+ return dest;
11095
+ }
11096
+ function mirrorJournal(staging, unit) {
11097
+ const from = journalFileOf(staging, unit);
11098
+ if (!existsSync(from)) return;
11099
+ const to = join(workbenchRoot(), "releases", `${unit}.json`);
11100
+ mkdirSync(dirname(to), { recursive: true });
11101
+ copyFileSync(from, to);
11102
+ }
11103
+ //#endregion
10163
11104
  //#region src/modules/panels/model/diy-publish.ts
10164
11105
  var PUBLISH_TIMEOUT_MS = 18e5;
11106
+ function adoptTarball(payload, unit) {
11107
+ const file = asText(payload["file"]);
11108
+ const dest = adoptTarballFile(file, unit);
11109
+ if (dest === void 0) return payload;
11110
+ const install = asText(payload["install"]);
11111
+ return {
11112
+ ...payload,
11113
+ file: dest,
11114
+ ...install === "" ? {} : { install: install.split(file).join(dest) }
11115
+ };
11116
+ }
11117
+ function needsUserPayload(payload) {
11118
+ const needs = asRecord$1(payload["needsUser"]);
11119
+ const stepText = asText(needs["text"]);
11120
+ const command = asText(needs["command"]);
11121
+ return plainJson({
11122
+ ...payload,
11123
+ text: [
11124
+ `还差一步:${stepText}`,
11125
+ ...command === "" ? [] : [`命令:${command}`],
11126
+ "做完跟我说「好了」,我接着发。"
11127
+ ].join("\n")
11128
+ });
11129
+ }
11130
+ function resumablePayload(payload, unit) {
11131
+ const resumeInfo = asRecord$1(payload["resume"]);
11132
+ return plainJson({
11133
+ ...payload,
11134
+ text: [`发布中断在第 ${asText(resumeInfo["phase"])} 步:${asText(payload["text"])}`, `再调一次 workbench_diy_publish(unit=${unit}, resume=true)就能接着发。`].join("\n")
11135
+ });
11136
+ }
11137
+ function outcomeOf$1(result, unit) {
11138
+ const payload = jsonOf(result.stdout);
11139
+ if (result.exitCode === 3 && payload !== void 0) return { payload: needsUserPayload(payload) };
11140
+ if (result.exitCode === 4 && payload !== void 0) return { payload: resumablePayload(payload, unit) };
11141
+ if (result.exitCode !== 0 || payload === void 0) {
11142
+ const tail = `${result.stderr}\n${result.stdout}`.trim().split("\n").slice(-6).join(" | ");
11143
+ return { error: /* @__PURE__ */ new Error(`发布没走通(exit ${String(result.exitCode)})${tail === "" ? "" : `:${tail}`}`) };
11144
+ }
11145
+ return { payload };
11146
+ }
11147
+ function successPayloadOf(payload, found, options) {
11148
+ const name = asText(payload["name"]);
11149
+ const released = asText(payload["version"]);
11150
+ const registry = asText(payload["registry"]);
11151
+ const file = asText(payload["file"]);
11152
+ const install = asText(payload["install"]);
11153
+ const git = asRecord$1(payload["git"]);
11154
+ const targetOfPayload = asText(payload["target"]);
11155
+ const text = options.verify ? [
11156
+ `已核过 ${name}@${released}`,
11157
+ `安装:${install}`,
11158
+ "没有重新发布。"
11159
+ ].join("\n") : payload["refresh"] === true ? [payload["refreshed"] === true ? `已刷新 ${name} 的市集元数据(版本 ${released} 不变)` : `${name} 的市集元数据本来就是这样,没动`, `市场索引:${asText(asRecord$1(payload["market"])["url"])}`].join("\n") : targetOfPayload === "tarball" ? [
11160
+ `安装包在 ${file}`,
11161
+ `本机装:${install}`,
11162
+ "文件可以从对话里带走,发给同事。"
11163
+ ].join("\n") : targetOfPayload === "git" ? [
11164
+ `已推到 ${asText(git["url"])}(tag ${asText(git["tag"])})`,
11165
+ `安装:${install}`,
11166
+ "已核过 tag 里的产物。"
11167
+ ].join("\n") : targetOfPayload === "market" ? [
11168
+ `已发到市场索引 ${asText(asRecord$1(payload["market"])["url"])}`,
11169
+ `安装:${install}`,
11170
+ "已从索引仓库侧核过产物。"
11171
+ ].join("\n") : targetOfPayload === "local" ? "没发,留在本机。" : [
11172
+ `已发布 ${name}@${released} → ${registry}`,
11173
+ `同事安装:${install}`,
11174
+ "包内容已经从源上核过(dist/host.js / client.js / cordis.patch.yml 都在)。"
11175
+ ].join("\n");
11176
+ const changelog = asRecord$1(payload["changelog"]);
11177
+ const changelogLine = asText(changelog["action"]) === "written" ? `更新日志:已写进 ${CHANGELOG_FILE} 的 ${asText(changelog["version"])}` : asText(changelog["action"]) === "kept" ? `更新日志:这一版已有(${asText(changelog["version"])}),没动` : "";
11178
+ const labelList = Array.isArray(payload["labels"]) ? payload["labels"].map((one) => asText(one)).filter((one) => one !== "") : [];
11179
+ const tail = [changelogLine, labelList.length === 0 || targetOfPayload !== "market" && payload["refresh"] !== true ? "" : `标签:${labelList.join(" · ")}`].filter((one) => one !== "").join("\n");
11180
+ return plainJson({
11181
+ ...payload,
11182
+ unit: found.unit,
11183
+ text: tail === "" ? text : `${text}\n${tail}`
11184
+ });
11185
+ }
11186
+ function withPhasePayloads(first, second) {
11187
+ const merged = {
11188
+ ...first,
11189
+ ...second
11190
+ };
11191
+ delete merged["stop"];
11192
+ if (merged["changelog"] === void 0 && first["changelog"] !== void 0) merged["changelog"] = first["changelog"];
11193
+ if (merged["changes"] === void 0 && first["changes"] !== void 0) merged["changes"] = first["changes"];
11194
+ if (merged["labels"] === void 0 && first["labels"] !== void 0) merged["labels"] = first["labels"];
11195
+ return merged;
11196
+ }
10165
11197
  function registerPublishTool(ctx, tools, deps) {
10166
11198
  const publishTool = {
10167
11199
  name: DIY_PUBLISH_TOOL,
@@ -10235,20 +11267,22 @@ function registerPublishTool(ctx, tools, deps) {
10235
11267
  async execute(raw, exec) {
10236
11268
  const { root } = await workspaceOf(ctx, exec);
10237
11269
  const found = deps.resolve(root, raw);
10238
- const shell = ctx.get("shell");
10239
- if (shell === void 0) throw new Error("这台机器上没有 shell 服务,发布跑不起来");
11270
+ const seam = seamOf(ctx);
11271
+ if (seam === void 0) throw new Error("这台机器上没有 shell 服务,发布跑不起来");
10240
11272
  const bin = toolchainBinOf(root) ?? toolchainBinOf(found.dir);
10241
11273
  if (bin === null) throw new Error(`这个工作区里没有 dsh-wb-module(${join(root, "node_modules")} 下没有)—— 先 pnpm install`);
10242
- const target = asText(asRecord$1(raw)["target"]);
10243
- const source = asText(asRecord$1(raw)["source"]);
10244
- const version = asText(asRecord$1(raw)["version"]);
10245
- const license = asText(asRecord$1(raw)["license"]);
10246
- const changelogNote = asText(asRecord$1(raw)["changelogNote"]);
10247
- const changelogType = asText(asRecord$1(raw)["changelogType"]);
10248
- const labels = labelsOf(asRecord$1(raw)["labels"]);
10249
- let author = asText(asRecord$1(raw)["author"]);
10250
- const verify = asRecord$1(raw)["verify"] === true;
10251
- const resume = asRecord$1(raw)["resume"] === true;
11274
+ const args = asRecord$1(raw);
11275
+ const target = asText(args["target"]);
11276
+ const source = asText(args["source"]);
11277
+ const version = asText(args["version"]);
11278
+ const license = asText(args["license"]);
11279
+ const changelogNote = asText(args["changelogNote"]);
11280
+ const changelogType = asText(args["changelogType"]);
11281
+ const labels = labelsOf(args["labels"]);
11282
+ let author = asText(args["author"]);
11283
+ const verify = args["verify"] === true;
11284
+ const resume = args["resume"] === true;
11285
+ const refresh = args["refresh"] === true;
10252
11286
  if (!verify && target !== "local" && author === "" && (deps.previewOf(found.unit)?.author ?? "") === "") author = await deps.inferredAuthorOf(found.unit) ?? "";
10253
11287
  const argv = [
10254
11288
  bin,
@@ -10268,70 +11302,43 @@ function registerPublishTool(ctx, tools, deps) {
10268
11302
  if (changelogNote !== "") argv.push("--changelog-note", changelogNote);
10269
11303
  if (changelogType !== "") argv.push("--changelog-type", changelogType);
10270
11304
  for (const label of labels) argv.push("--label", label);
10271
- if (asRecord$1(raw)["refresh"] === true) argv.push("--refresh");
10272
- const result = await runShell(shell, argv.map((one) => quoteShellArg(one)).join(" "), {
11305
+ if (refresh) argv.push("--refresh");
11306
+ const run = async (extra, request) => seam.run({
11307
+ command: [...argv, ...extra].map((one) => quoteShellArg(one)).join(" "),
11308
+ intent: request.intent,
11309
+ ...request.session === void 0 ? {} : { session: request.session },
11310
+ ...request.env === void 0 ? {} : { env: request.env },
10273
11311
  workdir: root,
10274
11312
  timeoutMs: PUBLISH_TIMEOUT_MS
10275
11313
  });
10276
- const payload = jsonOf(result.stdout);
10277
- if (result.exitCode === 3 && payload !== void 0) {
10278
- const needs = asRecord$1(payload["needsUser"]);
10279
- const stepText = asText(needs["text"]);
10280
- const command = asText(needs["command"]);
10281
- return plainJson({
10282
- ...payload,
10283
- text: [
10284
- `还差一步:${stepText}`,
10285
- ...command === "" ? [] : [`命令:${command}`],
10286
- "做完跟我说「好了」,我接着发。"
10287
- ].join("\n")
10288
- });
10289
- }
10290
- if (result.exitCode === 4 && payload !== void 0) {
10291
- const resumeInfo = asRecord$1(payload["resume"]);
10292
- return plainJson({
10293
- ...payload,
10294
- text: [`发布中断在第 ${asText(resumeInfo["phase"])} 步:${asText(payload["text"])}`, `再调一次 workbench_diy_publish(unit=${found.unit}, resume=true)就能接着发。`].join("\n")
10295
- });
11314
+ if (verify || refresh) {
11315
+ const outcome = outcomeOf$1(await run([], { intent: productWrite() }), found.unit);
11316
+ if ("error" in outcome) throw outcome.error;
11317
+ return plainJson(successPayloadOf(outcome.payload, found, { verify }));
10296
11318
  }
10297
- if (result.exitCode !== 0 || payload === void 0) {
10298
- const tail = `${result.stderr}\n${result.stdout}`.trim().split("\n").slice(-6).join(" | ");
10299
- throw new Error(`发布没走通(exit ${String(result.exitCode)})${tail === "" ? "" : `:${tail}`}`);
11319
+ const staging = prepareStaging(found.unit, resume);
11320
+ const env = { DSH_WB_RELEASE_HOME: staging };
11321
+ const session = exec.agent?.session;
11322
+ const firstOutcome = outcomeOf$1(await run(["--workspace-only"], {
11323
+ intent: workspaceWrite(),
11324
+ ...session === void 0 ? {} : { session },
11325
+ env
11326
+ }), found.unit);
11327
+ mirrorJournal(staging, found.unit);
11328
+ if ("error" in firstOutcome) throw firstOutcome.error;
11329
+ const first = firstOutcome.payload;
11330
+ if (first["stop"] !== "workspace-only" || asText(first["target"]) === "local") {
11331
+ cleanupStaging(found.unit);
11332
+ return plainJson(first);
10300
11333
  }
10301
- const name = asText(payload["name"]);
10302
- const released = asText(payload["version"]);
10303
- const registry = asText(payload["registry"]);
10304
- const file = asText(payload["file"]);
10305
- const install = asText(payload["install"]);
10306
- const git = asRecord$1(payload["git"]);
10307
- const targetOfPayload = asText(payload["target"]);
10308
- const text = verify ? [
10309
- `已核过 ${name}@${released}`,
10310
- `安装:${install}`,
10311
- "没有重新发布。"
10312
- ].join("\n") : payload["refresh"] === true ? [payload["refreshed"] === true ? `已刷新 ${name} 的市集元数据(版本 ${released} 不变)` : `${name} 的市集元数据本来就是这样,没动`, `市场索引:${asText(asRecord$1(payload["market"])["url"])}`].join("\n") : targetOfPayload === "tarball" ? [
10313
- `安装包在 ${file}`,
10314
- `本机装:${install}`,
10315
- "文件可以从对话里带走,发给同事。"
10316
- ].join("\n") : targetOfPayload === "git" ? [
10317
- `已推到 ${asText(git["url"])}(tag ${asText(git["tag"])})`,
10318
- `安装:${install}`,
10319
- "已核过 tag 里的产物。"
10320
- ].join("\n") : targetOfPayload === "market" ? [
10321
- `已发到市场索引 ${asText(asRecord$1(payload["market"])["url"])}`,
10322
- `安装:${install}`,
10323
- "已从索引仓库侧核过产物。"
10324
- ].join("\n") : targetOfPayload === "local" ? "没发,留在本机。" : [
10325
- `已发布 ${name}@${released} → ${registry}`,
10326
- `同事安装:${install}`,
10327
- "包内容已经从源上核过(dist/host.js / client.js / cordis.patch.yml 都在)。"
10328
- ].join("\n");
10329
- const changelog = asRecord$1(payload["changelog"]);
10330
- const tail = [asText(changelog["action"]) === "written" ? `更新日志:已写进 ${CHANGELOG_FILE} 的 ${asText(changelog["version"])}` : asText(changelog["action"]) === "kept" ? `更新日志:这一版已有(${asText(changelog["version"])}),没动` : "", labels.length === 0 || targetOfPayload !== "market" && payload["refresh"] !== true ? "" : `标签:${labels.join(" · ")}`].filter((one) => one !== "").join("\n");
10331
- return plainJson({
10332
- ...payload,
10333
- text: tail === "" ? text : `${text}\n${tail}`
10334
- });
11334
+ const indexOutcome = outcomeOf$1(await run(["--index-only"], {
11335
+ intent: productWrite(),
11336
+ env
11337
+ }), found.unit);
11338
+ mirrorJournal(staging, found.unit);
11339
+ if ("error" in indexOutcome) throw indexOutcome.error;
11340
+ cleanupStaging(found.unit);
11341
+ return plainJson(successPayloadOf(adoptTarball(withPhasePayloads(first, indexOutcome.payload), found.unit), found, { verify: false }));
10335
11342
  }
10336
11343
  };
10337
11344
  return tools.register(publishTool);
@@ -11339,14 +12346,14 @@ function changelogOf(dir, version, note, kind) {
11339
12346
  //#endregion
11340
12347
  //#region src/modules/panels/model/diy-observers.ts
11341
12348
  async function stopObservers(ctx, workspace, session = BROWSER_SCREEN_SESSION) {
11342
- const shell = ctx.get("shell");
11343
- if (workspace === null || workspace === void 0 || workspace === "" || shell === void 0) return {
12349
+ const seam = seamOf(ctx);
12350
+ if (workspace === null || workspace === void 0 || workspace === "" || seam === void 0) return {
11344
12351
  net: false,
11345
12352
  logs: false
11346
12353
  };
11347
- const file = join(artifactsDirOf(ctx, workspace), `net-${session}.jsonl`);
12354
+ const file = join(artifactsDirOf(workspace), `net-${session}.jsonl`);
11348
12355
  const command = cliCommandOf(ctx);
11349
- const [net, step] = await Promise.all([stopNetworkExact(shell, file), actBrowser(shell, command, session, ["eval", LOGS_STOP_SCRIPT], SWEEP_TIMEOUT_MS)]);
12356
+ const [net, step] = await Promise.all([stopNetworkExact(seam, file), actBrowser(seam, command, session, ["eval", LOGS_STOP_SCRIPT], SWEEP_TIMEOUT_MS)]);
11350
12357
  return {
11351
12358
  net,
11352
12359
  logs: typeof step.payload?.["unhooked"] === "number"
@@ -11498,7 +12505,7 @@ var REFUSAL$1 = {
11498
12505
  "not-owner": "这场调试不是这个会话的"
11499
12506
  };
11500
12507
  function debugActResource(deps) {
11501
- const now = () => deps.now?.() ?? Date.now();
12508
+ const now$2 = () => deps.now?.() ?? now();
11502
12509
  const settled = (intent, delivered, summary, state) => ({
11503
12510
  ok: true,
11504
12511
  intent,
@@ -11519,7 +12526,7 @@ function debugActResource(deps) {
11519
12526
  ok: false,
11520
12527
  text: "现在没有调试态 —— 没什么可暂停 / 继续 / 结束的"
11521
12528
  };
11522
- const at = now();
12529
+ const at = now$2();
11523
12530
  const held = store.live(at);
11524
12531
  if (held === null) return {
11525
12532
  ok: false,
@@ -11532,8 +12539,8 @@ function debugActResource(deps) {
11532
12539
  };
11533
12540
  const notice = deps.notice?.(intent, move.state) ?? debugNotice(intent, move.state);
11534
12541
  const delivered = deliverDebugNotice(deps.agents(), held.sessionId, notice.message, NOTICE_MODE$1[intent]);
11535
- if (intent !== "stop") return settled(intent, delivered, notice.summary, store.live(now()));
11536
- return (store.settle?.() ?? Promise.resolve()).then(() => settled(intent, delivered, notice.summary, store.live(now())));
12542
+ if (intent !== "stop") return settled(intent, delivered, notice.summary, store.live(now$2()));
12543
+ return (store.settle?.() ?? Promise.resolve()).then(() => settled(intent, delivered, notice.summary, store.live(now$2())));
11537
12544
  }
11538
12545
  };
11539
12546
  }
@@ -12192,7 +13199,7 @@ async function takeShotOf(input, held, n) {
12192
13199
  const path = shotFileOf(input.captureFile, held.unit, n);
12193
13200
  const at = Date.now();
12194
13201
  try {
12195
- const step = await actBrowser(input.shell, input.command, BROWSER_RECORD_SESSION, ["screenshot", path]);
13202
+ const step = await actBrowser(input.seam, input.command, BROWSER_RECORD_SESSION, ["screenshot", path]);
12196
13203
  if (step.ok) input.store.shotTaken(input.sessionId, {
12197
13204
  n,
12198
13205
  at,
@@ -12214,7 +13221,7 @@ async function takeShotOf(input, held, n) {
12214
13221
  }, Date.now());
12215
13222
  }
12216
13223
  }
12217
- async function startRecordCapture(shell, command, session, file, makeLine = plainLine) {
13224
+ async function startRecordCapture(seam, command, session, file, makeLine = plainLine) {
12218
13225
  const bin = command === "" ? "runtime" : command;
12219
13226
  const arm = makeLine(bin, [
12220
13227
  "browser",
@@ -12234,7 +13241,7 @@ async function startRecordCapture(shell, command, session, file, makeLine = plai
12234
13241
  "--window",
12235
13242
  "background"
12236
13243
  ]);
12237
- return (await runShell(shell, [
13244
+ const line = [
12238
13245
  `f=${quoteShellArg(file)}`,
12239
13246
  `r=${quoteShellArg(`${file}.round`)}`,
12240
13247
  ": > \"$f\"",
@@ -12242,11 +13249,16 @@ async function startRecordCapture(shell, command, session, file, makeLine = plai
12242
13249
  `( ${arm} >> "$f" 2>/dev/null & p=$!; sleep ${String(CAPTURE_ARM_S)}; kill "$p" 2>/dev/null )`,
12243
13250
  `( while kill -0 ${HOST_PID} 2>/dev/null; do if ${snap} > "$r" 2>/dev/null; then printf ok > "$f.first"; else printf fail > "$f.first"; fi; if [ -s "$r" ]; then if command -v node >/dev/null 2>&1; then tr -d '\\n' < "$r" | node -e ${quoteShellArg(DROP_STATIC_JS)} ${quoteShellArg(STATIC_URL_PATTERN)} >> "$f"; else tr -d '\\n' < "$r" >> "$f"; printf '\\n' >> "$f"; fi; fi; rm -f "$r"; sleep ${String(CAPTURE_POLL_S)}; done & echo $! > "$f.pid" ) & echo $! > "$f.reaper"`,
12244
13251
  "kill -0 \"$(cat \"$f.pid\" 2>/dev/null)\" 2>/dev/null"
12245
- ].join("; "), { timeoutMs: START_TIMEOUT_MS })).exitCode === 0;
13252
+ ].join("; ");
13253
+ return (await seam.run({
13254
+ command: line,
13255
+ intent: productWrite(),
13256
+ timeoutMs: START_TIMEOUT_MS
13257
+ })).exitCode === 0;
12246
13258
  }
12247
13259
  async function releaseLeftoversOf(input) {
12248
13260
  await releaseQuietly({
12249
- shell: input.shell,
13261
+ seam: input.seam,
12250
13262
  command: input.command,
12251
13263
  session: BROWSER_RECORD_SESSION,
12252
13264
  url: input.url,
@@ -12255,14 +13267,14 @@ async function releaseLeftoversOf(input) {
12255
13267
  }
12256
13268
  async function stopCaptureQuiet(input) {
12257
13269
  try {
12258
- return await stopNetworkExact(input.shell, input.captureFile);
13270
+ return await stopNetworkExact(input.seam, input.captureFile);
12259
13271
  } catch {
12260
13272
  return false;
12261
13273
  }
12262
13274
  }
12263
13275
  async function closeRecordTabQuiet(input) {
12264
13276
  try {
12265
- return await closeRecordTab(input.shell, input.command, BROWSER_RECORD_SESSION, input.url);
13277
+ return await closeRecordTab(input.seam, input.command, BROWSER_RECORD_SESSION, input.url);
12266
13278
  } catch {
12267
13279
  return false;
12268
13280
  }
@@ -12290,7 +13302,7 @@ async function watchFinishStepOf(input) {
12290
13302
  let page;
12291
13303
  const started = Date.now();
12292
13304
  try {
12293
- page = recordPageOf((await actBrowser(input.shell, input.command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT])).payload);
13305
+ page = recordPageOf((await actBrowser(input.seam, input.command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT])).payload);
12294
13306
  } catch {
12295
13307
  return {
12296
13308
  stop: false,
@@ -12360,26 +13372,38 @@ function watchRecordFinish(input) {
12360
13372
  };
12361
13373
  setTimeout(tick, 0);
12362
13374
  }
12363
- async function captureLinesOf(shell, file, timeoutMs = START_TIMEOUT_MS) {
12364
- const step = await runShell(shell, `wc -l < ${quoteShellArg(file)} 2>/dev/null || echo 0`, { timeoutMs });
13375
+ async function captureLinesOf(seam, file, timeoutMs = START_TIMEOUT_MS) {
13376
+ const step = await seam.run({
13377
+ command: `wc -l < ${quoteShellArg(file)} 2>/dev/null || echo 0`,
13378
+ intent: productWrite(),
13379
+ timeoutMs
13380
+ });
12365
13381
  const value = Number.parseInt(step.stdout.trim() === "" ? "0" : step.stdout.trim(), 10);
12366
13382
  return Number.isFinite(value) ? value : 0;
12367
13383
  }
12368
- async function captureRoundOf(shell, file, timeoutMs = START_TIMEOUT_MS) {
12369
- const value = (await runShell(shell, `cat ${quoteShellArg(`${file}.first`)} 2>/dev/null || true`, { timeoutMs })).stdout.trim();
13384
+ async function captureRoundOf(seam, file, timeoutMs = START_TIMEOUT_MS) {
13385
+ const value = (await seam.run({
13386
+ command: `cat ${quoteShellArg(`${file}.first`)} 2>/dev/null || true`,
13387
+ intent: productWrite(),
13388
+ timeoutMs
13389
+ })).stdout.trim();
12370
13390
  return value === "ok" || value === "fail" ? value : "";
12371
13391
  }
12372
- async function captureTextOf(shell, file, timeoutMs = START_TIMEOUT_MS) {
12373
- const step = await runShell(shell, `tail -n ${String(CAPTURE_TAIL_LINES)} ${quoteShellArg(file)} 2>/dev/null || true`, { timeoutMs });
13392
+ async function captureTextOf(seam, file, timeoutMs = START_TIMEOUT_MS) {
13393
+ const step = await seam.run({
13394
+ command: `tail -n ${String(CAPTURE_TAIL_LINES)} ${quoteShellArg(file)} 2>/dev/null || true`,
13395
+ intent: productWrite(),
13396
+ timeoutMs
13397
+ });
12374
13398
  return {
12375
13399
  text: step.stdout,
12376
13400
  truncated: step.truncated
12377
13401
  };
12378
13402
  }
12379
- async function captureReadOf(shell, file, timeoutMs = START_TIMEOUT_MS) {
12380
- const totalLines = await captureLinesOf(shell, file, timeoutMs);
12381
- const read = await captureTextOf(shell, file, timeoutMs);
12382
- const round = await captureRoundOf(shell, file, timeoutMs);
13403
+ async function captureReadOf(seam, file, timeoutMs = START_TIMEOUT_MS) {
13404
+ const totalLines = await captureLinesOf(seam, file, timeoutMs);
13405
+ const read = await captureTextOf(seam, file, timeoutMs);
13406
+ const round = await captureRoundOf(seam, file, timeoutMs);
12383
13407
  return {
12384
13408
  text: read.text,
12385
13409
  totalLines,
@@ -12388,14 +13412,24 @@ async function captureReadOf(shell, file, timeoutMs = START_TIMEOUT_MS) {
12388
13412
  round
12389
13413
  };
12390
13414
  }
12391
- async function keepCaptureOf(shell, file, timeoutMs = START_TIMEOUT_MS) {
12392
- return (await runShell(shell, `[ -f ${quoteShellArg(file)} ] && mv ${quoteShellArg(file)} ${quoteShellArg(lastCaptureFileOf(file))} && [ -s ${quoteShellArg(lastCaptureFileOf(file))} ]`, { timeoutMs })).exitCode === 0;
13415
+ async function keepCaptureOf(seam, file, timeoutMs = START_TIMEOUT_MS) {
13416
+ const line = `[ -f ${quoteShellArg(file)} ] && mv ${quoteShellArg(file)} ${quoteShellArg(lastCaptureFileOf(file))} && [ -s ${quoteShellArg(lastCaptureFileOf(file))} ]`;
13417
+ return (await seam.run({
13418
+ command: line,
13419
+ intent: productWrite(),
13420
+ timeoutMs
13421
+ })).exitCode === 0;
12393
13422
  }
12394
- async function captureLinesWithKeyOf(shell, file, key, timeoutMs = START_TIMEOUT_MS) {
13423
+ async function captureLinesWithKeyOf(seam, file, key, timeoutMs = START_TIMEOUT_MS) {
12395
13424
  const quoted = quoteShellArg(file);
12396
13425
  const compact = quoteShellArg(`"key":"${key}"`);
12397
13426
  const spaced = quoteShellArg(`"key": "${key}"`);
12398
- return (await runShell(shell, [`[ -f ${quoted} ] || exit 0`, `grep -m 20 -F ${compact} ${quoted} 2>/dev/null || grep -m 20 -F ${spaced} ${quoted} 2>/dev/null || true`].join("; "), { timeoutMs })).stdout;
13427
+ const line = [`[ -f ${quoted} ] || exit 0`, `grep -m 20 -F ${compact} ${quoted} 2>/dev/null || grep -m 20 -F ${spaced} ${quoted} 2>/dev/null || true`].join("; ");
13428
+ return (await seam.run({
13429
+ command: line,
13430
+ intent: productWrite(),
13431
+ timeoutMs
13432
+ })).stdout;
12399
13433
  }
12400
13434
  function bundleOf(capture, page, state) {
12401
13435
  const entries = captureEntriesOf(capture.text);
@@ -12505,32 +13539,32 @@ function tabsOf(raw) {
12505
13539
  return null;
12506
13540
  }
12507
13541
  }
12508
- async function closeRecordTab(shell, command, session, url) {
12509
- const pages = tabsOf((await actBrowser(shell, command, session, ["tab", "list"])).stdout);
13542
+ async function closeRecordTab(seam, command, session, url) {
13543
+ const pages = tabsOf((await actBrowser(seam, command, session, ["tab", "list"])).stdout);
12510
13544
  if (pages !== null && pages.length === 0) return true;
12511
13545
  const pick = pages?.find((one) => typeof one["url"] === "string" && one["url"] === url) ?? pages?.[0];
12512
13546
  const page = pick === void 0 ? "" : typeof pick["page"] === "string" ? pick["page"] : "";
12513
13547
  if (page !== "") {
12514
- if ((await actBrowser(shell, command, session, [
13548
+ if ((await actBrowser(seam, command, session, [
12515
13549
  "tab",
12516
13550
  "close",
12517
13551
  page
12518
13552
  ])).ok) {
12519
- for (const one of blanksOf(pages, page)) await actBrowser(shell, command, session, [
13553
+ for (const one of blanksOf(pages, page)) await actBrowser(seam, command, session, [
12520
13554
  "tab",
12521
13555
  "close",
12522
13556
  one
12523
13557
  ]);
12524
- await actBrowser(shell, command, session, ["close"]);
13558
+ await actBrowser(seam, command, session, ["close"]);
12525
13559
  return true;
12526
13560
  }
12527
13561
  }
12528
- return (await actBrowser(shell, command, session, ["close"])).ok;
13562
+ return (await actBrowser(seam, command, session, ["close"])).ok;
12529
13563
  }
12530
13564
  async function releaseRecord(input) {
12531
- const capture = await stopNetworkExact(input.shell, input.captureFile);
13565
+ const capture = await stopNetworkExact(input.seam, input.captureFile);
12532
13566
  return {
12533
- tab: await closeRecordTab(input.shell, input.command, input.session, input.url),
13567
+ tab: await closeRecordTab(input.seam, input.command, input.session, input.url),
12534
13568
  capture
12535
13569
  };
12536
13570
  }
@@ -12552,12 +13586,12 @@ function samePageOf(here, want) {
12552
13586
  return here !== "" && norm(here) === norm(want);
12553
13587
  }
12554
13588
  async function whereAtOf(input) {
12555
- const step = await actBrowser(input.shell, input.command, input.session, ["eval", PAGE_HREF_SCRIPT]);
13589
+ const step = await actBrowser(input.seam, input.command, input.session, ["eval", PAGE_HREF_SCRIPT]);
12556
13590
  const href = step.payload === void 0 ? "" : step.payload["href"];
12557
13591
  return typeof href === "string" ? href : "";
12558
13592
  }
12559
13593
  async function settleOf(input) {
12560
- await runBrowserStep(input.shell, input.command, [
13594
+ await runBrowserStep(input.seam, input.command, [
12561
13595
  "browser",
12562
13596
  input.session,
12563
13597
  "wait",
@@ -12567,7 +13601,7 @@ async function settleOf(input) {
12567
13601
  }
12568
13602
  async function openRecordPage(input) {
12569
13603
  await releaseQuietly(input);
12570
- const opened = await runBrowserStep(input.shell, input.command, [
13604
+ const opened = await runBrowserStep(input.seam, input.command, [
12571
13605
  "browser",
12572
13606
  input.session,
12573
13607
  "tab",
@@ -12584,7 +13618,7 @@ async function openRecordPage(input) {
12584
13618
  await settleOf(input);
12585
13619
  let at = await whereAtOf(input);
12586
13620
  if (!samePageOf(at, input.url)) {
12587
- await runBrowserStep(input.shell, input.command, [
13621
+ await runBrowserStep(input.seam, input.command, [
12588
13622
  "browser",
12589
13623
  input.session,
12590
13624
  "open",
@@ -12652,7 +13686,7 @@ function registerRecordTools(ctx, store) {
12652
13686
  output: OUTPUT$3,
12653
13687
  async execute(raw, exec) {
12654
13688
  const { root } = await workspaceOf(ctx, exec);
12655
- const artifactsDir = artifactsDirOf(ctx, root);
13689
+ const artifactsDir = artifactsDirOf(root);
12656
13690
  const args = asRecord$1(raw);
12657
13691
  const asked = asText(args["unit"]);
12658
13692
  const { units } = unitsOf(ctx, root);
@@ -12670,8 +13704,8 @@ function registerRecordTools(ctx, store) {
12670
13704
  method,
12671
13705
  urlContains
12672
13706
  };
12673
- const shell = ctx.get("shell");
12674
- if (shell === void 0) throw new Error("这台机器上没有 shell 服务,开不了录制窗");
13707
+ const seam = seamOf(ctx);
13708
+ if (seam === void 0) throw new Error("这台机器上没有 shell 服务,开不了录制窗");
12675
13709
  const command = cliCommandOf(ctx);
12676
13710
  const sessionId = sessionIdOf(exec) ?? "";
12677
13711
  const held = store.live(Date.now());
@@ -12690,21 +13724,21 @@ function registerRecordTools(ctx, store) {
12690
13724
  }), held);
12691
13725
  try {
12692
13726
  const opened = await openRecordPage({
12693
- shell,
13727
+ seam,
12694
13728
  command,
12695
13729
  session: BROWSER_RECORD_SESSION,
12696
13730
  url,
12697
13731
  captureFile
12698
13732
  });
12699
13733
  if (!opened.ok) throw new Error(opened.why);
12700
- const armed = await actBrowser(shell, command, BROWSER_RECORD_SESSION, ["eval", RECORD_ARM_SCRIPT]);
13734
+ const armed = await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_ARM_SCRIPT]);
12701
13735
  const page = recordPageOf(armed.payload);
12702
13736
  if (!armed.ok || !page.ok) throw new Error(`浮条没装上:${armed.stderr || armed.stdout || "那一页还在加载"} —— 等用户那一页站稳了再试。`);
12703
- await stopNetworkExact(shell, captureFile);
12704
- const capture = await startRecordCapture(shell, command, BROWSER_RECORD_SESSION, captureFile);
13737
+ await stopNetworkExact(seam, captureFile);
13738
+ const capture = await startRecordCapture(seam, command, BROWSER_RECORD_SESSION, captureFile);
12705
13739
  watchRecordFinish({
12706
13740
  ctx,
12707
- shell,
13741
+ seam,
12708
13742
  command,
12709
13743
  sessionId,
12710
13744
  store,
@@ -12727,7 +13761,7 @@ function registerRecordTools(ctx, store) {
12727
13761
  });
12728
13762
  } catch (error) {
12729
13763
  await releaseQuietly({
12730
- shell,
13764
+ seam,
12731
13765
  command,
12732
13766
  session: BROWSER_RECORD_SESSION,
12733
13767
  url,
@@ -12751,8 +13785,8 @@ function registerRecordTools(ctx, store) {
12751
13785
  },
12752
13786
  output: OUTPUT$3,
12753
13787
  async execute(raw, exec) {
12754
- const shell = ctx.get("shell");
12755
- if (shell === void 0) throw new Error("这台机器上没有 shell 服务,读不了录制");
13788
+ const seam = seamOf(ctx);
13789
+ if (seam === void 0) throw new Error("这台机器上没有 shell 服务,读不了录制");
12756
13790
  const sessionId = sessionIdOf(exec) ?? "";
12757
13791
  const held = store.live(Date.now());
12758
13792
  if (held === null) throw new Error(recordMissingText());
@@ -12777,14 +13811,14 @@ function registerRecordTools(ctx, store) {
12777
13811
  let stepOk = true;
12778
13812
  let page = recordPageOf(void 0);
12779
13813
  if (!over) {
12780
- await actBrowser(shell, command, BROWSER_RECORD_SESSION, ["eval", RECORD_ARM_SCRIPT]);
12781
- let step = await actBrowser(shell, command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT]);
13814
+ await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_ARM_SCRIPT]);
13815
+ let step = await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT]);
12782
13816
  page = recordPageOf(step.payload);
12783
13817
  stepOk = step.ok;
12784
13818
  sync(page);
12785
13819
  while (budget > 0 && page.phase !== "finished" && Date.now() < deadline) {
12786
13820
  await pauseFor(Math.min(POLL_MS, Math.max(0, deadline - Date.now())));
12787
- step = await actBrowser(shell, command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT]);
13821
+ step = await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT]);
12788
13822
  const next = recordPageOf(step.payload);
12789
13823
  if (next.ok) page = next;
12790
13824
  sync(page);
@@ -12798,7 +13832,7 @@ function registerRecordTools(ctx, store) {
12798
13832
  }
12799
13833
  const state = store.live(Date.now()) ?? held;
12800
13834
  const ledger = state.shots;
12801
- const capture = await captureReadOf(shell, state.captureFile);
13835
+ const capture = await captureReadOf(seam, state.captureFile);
12802
13836
  const bundle = bundleOf(capture, page, state);
12803
13837
  const hookCaught = page.calls.length;
12804
13838
  const nav = page.href !== "" && page.here !== "" && page.href !== page.here;
@@ -12882,9 +13916,9 @@ function registerRecordTools(ctx, store) {
12882
13916
  output: OUTPUT$3,
12883
13917
  async execute(raw, exec) {
12884
13918
  const { root } = await workspaceOf(ctx, exec);
12885
- const artifactsDir = artifactsDirOf(ctx, root);
12886
- const shell = ctx.get("shell");
12887
- if (shell === void 0) throw new Error("这台机器上没有 shell 服务,取不了详情");
13919
+ const artifactsDir = artifactsDirOf(root);
13920
+ const seam = seamOf(ctx);
13921
+ if (seam === void 0) throw new Error("这台机器上没有 shell 服务,取不了详情");
12888
13922
  const args = asRecord$1(raw);
12889
13923
  const unit = asText(args["unit"]);
12890
13924
  const askedKey = asText(args["key"]).trim();
@@ -12908,8 +13942,8 @@ function registerRecordTools(ctx, store) {
12908
13942
  if (key === "") {
12909
13943
  if (index === null) throw new Error("给一个 key(清单里那一条的)或 index(清单里的序号)。");
12910
13944
  if (held === null) throw new Error("收工之后只能用 key —— index 是\"那一场清单里\"的序号,这一场已经清掉了。");
12911
- page = recordPageOf((await actBrowser(shell, command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT])).payload);
12912
- const bundle = bundleOf(await captureReadOf(shell, file), page, held);
13945
+ page = recordPageOf((await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT])).payload);
13946
+ const bundle = bundleOf(await captureReadOf(seam, file), page, held);
12913
13947
  const row = bundle.rows[index];
12914
13948
  if (row === void 0) throw new Error(`这一段清单里没有第 ${String(index)} 条(一共 ${String(bundle.rows.length)} 条)—— 先 ${DIY_RECORD_READ_TOOL} 拿一份新的清单。`);
12915
13949
  key = row.key;
@@ -12919,11 +13953,11 @@ function registerRecordTools(ctx, store) {
12919
13953
  if (only !== null) return detailOfCall(only, maxBytes);
12920
13954
  }
12921
13955
  }
12922
- const candidates = pickAllOf(captureEntriesOf(await captureLinesWithKeyOf(shell, file, key)), key);
13956
+ const candidates = pickAllOf(captureEntriesOf(await captureLinesWithKeyOf(seam, file, key)), key);
12923
13957
  const fromCapture = pickOf(candidates, key, at);
12924
13958
  if (fromCapture !== null) return detailOfEntry(fromCapture, maxBytes, candidates.length);
12925
13959
  const over = held === null || held.phase === "finished";
12926
- const livePage = page !== null || over ? page : recordPageOf((await actBrowser(shell, command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT])).payload);
13960
+ const livePage = page !== null || over ? page : recordPageOf((await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT])).payload);
12927
13961
  const call = livePage === null ? null : hookCallOf(livePage, key);
12928
13962
  if (call !== null) return detailOfCall(call, maxBytes);
12929
13963
  return plainJson({
@@ -12963,8 +13997,8 @@ function registerRecordTools(ctx, store) {
12963
13997
  },
12964
13998
  output: OUTPUT$3,
12965
13999
  async execute(_raw, exec) {
12966
- const shell = ctx.get("shell");
12967
- if (shell === void 0) throw new Error("这台机器上没有 shell 服务,收不了工");
14000
+ const seam = seamOf(ctx);
14001
+ if (seam === void 0) throw new Error("这台机器上没有 shell 服务,收不了工");
12968
14002
  const sessionId = sessionIdOf(exec) ?? "";
12969
14003
  const held = store.live(Date.now());
12970
14004
  if (held === null) throw new Error(recordMissingText());
@@ -12973,14 +14007,14 @@ function registerRecordTools(ctx, store) {
12973
14007
  code: "not-owner"
12974
14008
  }, held);
12975
14009
  const command = cliCommandOf(ctx);
12976
- await actBrowser(shell, command, BROWSER_RECORD_SESSION, ["eval", RECORD_ARM_SCRIPT]);
12977
- const page = recordPageOf((await actBrowser(shell, command, BROWSER_RECORD_SESSION, ["eval", RECORD_STOP_SCRIPT])).payload);
12978
- const bundle = bundleOf(await captureReadOf(shell, held.captureFile), page, held);
14010
+ await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_ARM_SCRIPT]);
14011
+ const page = recordPageOf((await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_STOP_SCRIPT])).payload);
14012
+ const bundle = bundleOf(await captureReadOf(seam, held.captureFile), page, held);
12979
14013
  const hookCaught = page.calls.length;
12980
14014
  const noBody = bodyMissingOf(bundle.merged);
12981
- const kept = await keepCaptureOf(shell, held.captureFile);
14015
+ const kept = await keepCaptureOf(seam, held.captureFile);
12982
14016
  const cleaned = await releaseQuietly({
12983
- shell,
14017
+ seam,
12984
14018
  command,
12985
14019
  session: BROWSER_RECORD_SESSION,
12986
14020
  url: held.url,
@@ -13277,11 +14311,11 @@ async function settle(deps, spec, options, change) {
13277
14311
  };
13278
14312
  }
13279
14313
  async function installRouted(deps) {
13280
- const { spec, options, registry, shell } = deps;
14314
+ const { spec, options, registry, seam } = deps;
13281
14315
  const supply = deps.supply ?? requireSupply();
13282
14316
  const route = await supply.packageRouteOf({
13283
14317
  spec,
13284
- ...shell === void 0 ? {} : { shell },
14318
+ ...seam === void 0 ? {} : { seam },
13285
14319
  ...registry === void 0 ? {} : { registry }
13286
14320
  });
13287
14321
  if (route === void 0 || route.kind === "dsh-bundle") return installDshBundle(deps, spec, options);
@@ -13491,15 +14525,15 @@ async function updateModuleBundle(unit, deps) {
13491
14525
  pkg
13492
14526
  };
13493
14527
  const registry = read.kind === "module" ? read.registry : void 0;
13494
- const latest = deps.shell === void 0 || registry === void 0 ? void 0 : await supply.latestVersion({
13495
- shell: deps.shell,
14528
+ const latest = deps.seam === void 0 || registry === void 0 ? void 0 : await supply.latestVersion({
14529
+ seam: deps.seam,
13496
14530
  name: pkg,
13497
14531
  registry,
13498
14532
  force: true
13499
14533
  });
13500
- if (latest !== void 0 && deps.shell !== void 0 && registry !== void 0) {
14534
+ if (latest !== void 0 && deps.seam !== void 0 && registry !== void 0) {
13501
14535
  const declared = await supply.registryDeclarationOf({
13502
- shell: deps.shell,
14536
+ seam: deps.seam,
13503
14537
  name: pkg,
13504
14538
  registry,
13505
14539
  force: true
@@ -13701,17 +14735,17 @@ async function updateWorkbenchOnce(deps) {
13701
14735
  const read = deps.workbench?.(target);
13702
14736
  if (read === void 0) throw new Error(`读不到 profile 里装的 ${target} —— 没法判断装的是哪一份,更新做不了。`);
13703
14737
  if (read.local === true) throw new Error("这份工作台是从源码目录装进来的(开发副本),不从源上更新。");
13704
- const shell = deps.shell;
14738
+ const seam = deps.seam;
13705
14739
  const registry = read.registry ?? await deps.profileRegistry?.();
13706
- const askedLatest = shell === void 0 || registry === void 0 ? void 0 : await supply.latestVersion({
13707
- shell,
14740
+ const askedLatest = seam === void 0 || registry === void 0 ? void 0 : await supply.latestVersion({
14741
+ seam,
13708
14742
  name: read.pkg,
13709
14743
  registry,
13710
14744
  force: true
13711
14745
  });
13712
- if (shell !== void 0 && registry !== void 0 && askedLatest !== void 0) {
14746
+ if (seam !== void 0 && registry !== void 0 && askedLatest !== void 0) {
13713
14747
  const missing = await supply.missingReleaseParts({
13714
- shell,
14748
+ seam,
13715
14749
  name: read.pkg,
13716
14750
  registry,
13717
14751
  version: askedLatest,
@@ -13774,25 +14808,29 @@ async function migrateWorkbench(input) {
13774
14808
  }
13775
14809
  //#endregion
13776
14810
  //#region src/modules/panels/model/market-follow-up.ts
14811
+ var MIGRATION_TIMEOUT_MS = 12e4;
13777
14812
  function createWorkbenchFollowUps(deps) {
13778
14813
  const runMigration = deps.runMigration ?? (async (input) => {
13779
- const result = await execa(process.execPath, [
13780
- input.script,
13781
- "--migrate",
13782
- "--force"
13783
- ], {
14814
+ const seam = deps.seam;
14815
+ if (seam === void 0) throw new Error("没有执行缝(shell 服务不可用),迁移跑不起来。");
14816
+ const outcome = await seam.run({
14817
+ argv: [
14818
+ process.execPath,
14819
+ input.script,
14820
+ "--migrate",
14821
+ "--force"
14822
+ ],
14823
+ intent: { kind: "host" },
13784
14824
  env: {
13785
- ...process.env,
13786
14825
  ELECTRON_RUN_AS_NODE: "1",
13787
14826
  ...input.env
13788
14827
  },
13789
- reject: false,
13790
- timeout: 12e4
14828
+ timeoutMs: MIGRATION_TIMEOUT_MS
13791
14829
  });
13792
14830
  return {
13793
- exitCode: result.exitCode ?? 1,
13794
- stdout: result.stdout,
13795
- stderr: result.stderr
14831
+ exitCode: outcome.exitCode ?? 1,
14832
+ stdout: outcome.stdout,
14833
+ stderr: outcome.stderr
13796
14834
  };
13797
14835
  });
13798
14836
  function homeAndProfile() {
@@ -13849,7 +14887,7 @@ function pluginSweptText(pkg) {
13849
14887
  return `清掉了 ${pkg}:先前要它的都放手了,也没别人在用,文件跟着删了。`;
13850
14888
  }
13851
14889
  function createPluginActions(options) {
13852
- const { ledger, shell, command, runAll, logger } = options;
14890
+ const { ledger, seam, command, runAll, logger } = options;
13853
14891
  const supply = options.supply ?? requireSupply();
13854
14892
  const pluginFilesOf = () => {
13855
14893
  const dir = supply.vendorPluginsDir?.();
@@ -13911,16 +14949,16 @@ function createPluginActions(options) {
13911
14949
  };
13912
14950
  }
13913
14951
  async function removeFiles(plugin) {
13914
- if (shell === void 0) throw new Error("没有执行缝(shell 服务不可用),删不掉插件文件。");
14952
+ if (seam === void 0) throw new Error("没有执行缝(shell 服务不可用),删不掉插件文件。");
13915
14953
  await runAll([{ line: `${quoteShellArg(command)} plugin uninstall ${quoteShellArg(plugin)}` }]);
13916
14954
  }
13917
14955
  return {
13918
14956
  async update(pkg) {
13919
14957
  const { entry } = requireHeld(pkg);
13920
14958
  if (entry.local === true || entry.registry === void 0) throw new Error(`「${entry.package}」是从本地装的,没有地方查新版。`);
13921
- if (shell === void 0) throw new Error("没有执行缝(shell 服务不可用),更新做不了。");
14959
+ if (seam === void 0) throw new Error("没有执行缝(shell 服务不可用),更新做不了。");
13922
14960
  const latest = await supply.latestVersion({
13923
- shell,
14961
+ seam,
13924
14962
  name: entry.package,
13925
14963
  registry: entry.registry,
13926
14964
  force: true
@@ -14060,7 +15098,7 @@ function restartLogLine(change) {
14060
15098
  //#endregion
14061
15099
  //#region src/modules/panels/model/market.ts
14062
15100
  function createMarketService(options) {
14063
- const { manifests, manifestsNow, probe, shell, logger, supply: supplyOption, onInstalled, onUninstalled, isReleased, bundles, installed, modules, workbench, registries, profileDir, ledger, references, dshHome, profileName } = options;
15101
+ const { manifests, manifestsNow, probe, seam, logger, supply: supplyOption, onInstalled, onUninstalled, isReleased, bundles, installed, modules, workbench, registries, profileDir, ledger, references, dshHome, profileName } = options;
14064
15102
  const supply = supplyOption ?? requireSupply();
14065
15103
  const command = options.command;
14066
15104
  const catalog = () => manifestsNow?.() ?? manifests;
@@ -14098,13 +15136,17 @@ function createMarketService(options) {
14098
15136
  };
14099
15137
  }
14100
15138
  async function run(line, optional = false) {
14101
- if (shell === void 0) throw new Error("没有执行缝(shell 服务不可用),装 / 卸做不了。");
15139
+ if (seam === void 0) throw new Error("没有执行缝(shell 服务不可用),装 / 卸做不了。");
14102
15140
  const blocked = installWriteProblem({
14103
15141
  command: line,
14104
15142
  fallback: supply.modules.isolatedRoot()
14105
15143
  });
14106
15144
  if (blocked !== void 0) throw installError(blocked);
14107
- const result = await runShell(shell, line, { timeoutMs: INSTALL_TIMEOUT_MS });
15145
+ const result = await seam.run({
15146
+ command: line,
15147
+ intent: productWrite(),
15148
+ timeoutMs: INSTALL_TIMEOUT_MS
15149
+ });
14108
15150
  if (result.exitCode !== 0 && !optional) throw installError(diagnoseInstallFailure({
14109
15151
  command: line,
14110
15152
  exitCode: result.exitCode,
@@ -14214,7 +15256,7 @@ function createMarketService(options) {
14214
15256
  registryFix,
14215
15257
  ...logger === void 0 ? {} : { logger }
14216
15258
  }),
14217
- ...shell === void 0 ? {} : { shell },
15259
+ ...seam === void 0 ? {} : { seam },
14218
15260
  ...installed === void 0 ? {} : { installed },
14219
15261
  ...ledger === void 0 ? {} : { ledger },
14220
15262
  ...logger === void 0 ? {} : { logger }
@@ -14222,7 +15264,7 @@ function createMarketService(options) {
14222
15264
  }
14223
15265
  const plugins = createPluginActions({
14224
15266
  ...ledger === void 0 ? {} : { ledger },
14225
- ...shell === void 0 ? {} : { shell },
15267
+ ...seam === void 0 ? {} : { seam },
14226
15268
  command,
14227
15269
  runAll,
14228
15270
  claimedBy: (plugin) => pluginUsersOf(plugin),
@@ -14377,7 +15419,7 @@ function createMarketService(options) {
14377
15419
  supply,
14378
15420
  modules,
14379
15421
  installed,
14380
- shell,
15422
+ seam,
14381
15423
  manager,
14382
15424
  ...profile === void 0 ? {} : {
14383
15425
  profile,
@@ -14399,7 +15441,7 @@ function createMarketService(options) {
14399
15441
  supply,
14400
15442
  asked: pkg,
14401
15443
  ...workbench === void 0 ? {} : { workbench },
14402
- ...shell === void 0 ? {} : { shell },
15444
+ ...seam === void 0 ? {} : { seam },
14403
15445
  ...installed === void 0 ? {} : { installed },
14404
15446
  ...dshHome === void 0 ? {} : { dshHome },
14405
15447
  ...profileName === void 0 ? {} : { profileName },
@@ -14428,6 +15470,7 @@ function createMarketService(options) {
14428
15470
  dshHome,
14429
15471
  profileName,
14430
15472
  workbench,
15473
+ ...seam === void 0 ? {} : { seam },
14431
15474
  ...options.runMigration === void 0 ? {} : { runMigration: options.runMigration }
14432
15475
  }),
14433
15476
  updateDependency: updateDependencyRow$1
@@ -14436,18 +15479,11 @@ function createMarketService(options) {
14436
15479
  }
14437
15480
  //#endregion
14438
15481
  //#region src/modules/panels/model/market-discover.ts
14439
- var SANDBOX_BLOCK = [
14440
- "- id: sandbox-policy",
14441
- " config:",
14442
- " mode: !!js process.env.DSH_PERMISSION_MODE ?? 'workspace-write'",
14443
- " workspaceRoot: !!js dshHomePath('workbench')"
14444
- ].join("\n");
14445
15482
  function explainProblem(problem, profilePath) {
14446
15483
  if (problem.kind !== "sandbox" || profilePath === void 0 || profilePath === "") return problem;
14447
- const file = join(profilePath, "cordis.patch.yml");
14448
15484
  return {
14449
15485
  ...problem,
14450
- detail: `被系统拦住了:这个 profile 的可写根盖不住工作台的缓存目录。\n退出 dsh 后,把下面这段加进 ${file},再启动:\n${SANDBOX_BLOCK}`
15486
+ detail: `被系统拦住了:工作台按 $DSH_HOME 声明的可写根没盖住它的缓存目录,这条命令被沙箱挡下了。\n这是工作台自己的问题,不用改 profile —— 把这一屏和 $DSH_HOME 报给工作台维护者,或先在工作台外面手动加这个源(${profilePath})。`
14451
15487
  };
14452
15488
  }
14453
15489
  var DEFAULT_PEER_TTL_MS = 6e5;
@@ -14507,7 +15543,7 @@ function createMarketDiscover(options) {
14507
15543
  const git = version.source;
14508
15544
  if (git.type !== "git") return void 0;
14509
15545
  if (normalizedRepo(git.url) !== normalizedRepo(source.url)) return void 0;
14510
- if (options.shell === void 0) return void 0;
15546
+ if (options.seam === void 0) return void 0;
14511
15547
  const key = `${git.commit}|${git.path ?? ""}`;
14512
15548
  const cached = peerCache.get(key);
14513
15549
  if (cached !== void 0 && now() - cached.at < peerTtlMs) return cached.peer;
@@ -14515,7 +15551,12 @@ function createMarketDiscover(options) {
14515
15551
  const directory = (git.path ?? "").replace(/^\/+|\/+$/g, "");
14516
15552
  const file = directory === "" ? "package.json" : `${directory}/package.json`;
14517
15553
  try {
14518
- const result = await runShell(options.shell, `git -C ${quoteShellArg(repo)} show ${git.commit}:${quoteShellArg(file)}`, { timeoutMs });
15554
+ const result = await options.seam.run({
15555
+ command: `git -C ${quoteShellArg(repo)} show ${git.commit}:${quoteShellArg(file)}`,
15556
+ intent: noWrite(),
15557
+ retry: "safe",
15558
+ timeoutMs
15559
+ });
14519
15560
  if (result.exitCode !== 0) throw new Error(result.stderr.trim());
14520
15561
  const peer = JSON.parse(result.stdout).peerDependencies?.[CORE_PEER];
14521
15562
  peerCache.set(key, {
@@ -14909,7 +15950,7 @@ function repoOf(root, source) {
14909
15950
  return join(directoryOf(root, source), "repo.git");
14910
15951
  }
14911
15952
  function writeMeta(path, meta) {
14912
- return writeFileAtomic(path, `${JSON.stringify(meta, null, 2)}\n`);
15953
+ return writeJsonFileAsync(path, meta);
14913
15954
  }
14914
15955
  function readMeta(path) {
14915
15956
  try {
@@ -15045,7 +16086,7 @@ function createMarketIndexReader(options) {
15045
16086
  };
15046
16087
  const fetchedAt = now();
15047
16088
  mkdirSync(directoryOf(options.root, source), { recursive: true });
15048
- await writeFileAtomic(indexFileOf(options.root, source), text);
16089
+ await writeTextFileAsync(indexFileOf(options.root, source), text);
15049
16090
  await writeMeta(metaFileOf(options.root, source), { fetchedAt });
15050
16091
  return {
15051
16092
  ok: true,
@@ -15196,7 +16237,7 @@ function createMarketIndexReader(options) {
15196
16237
  const entries = rows.filter((one) => one.type === "blob" && one.file !== path && one.file.startsWith(`${path}/`) && one.file.endsWith(".json"));
15197
16238
  const finish = async (index, text) => {
15198
16239
  const fetchedAt = now();
15199
- await writeFileAtomic(indexFileOf(options.root, source), text);
16240
+ await writeTextFileAsync(indexFileOf(options.root, source), text);
15200
16241
  await writeMeta(metaFileOf(options.root, source), {
15201
16242
  fetchedAt,
15202
16243
  commit
@@ -15472,7 +16513,7 @@ function readPayload(path, logger) {
15472
16513
  }
15473
16514
  async function writePayload(path, payload) {
15474
16515
  mkdirSync(dirname(path), { recursive: true });
15475
- await writeFileAtomic(path, `${JSON.stringify(payload, null, 2)}\n`);
16516
+ await writeJsonFileAsync(path, payload);
15476
16517
  }
15477
16518
  function upsert(sources, source) {
15478
16519
  const found = sources.findIndex((item) => item.id === source.id);
@@ -15660,9 +16701,7 @@ function createMarketSourceInitializer(options) {
15660
16701
  const migrated = marketEntryFilesOfLegacy(readFileSync(legacyFile, "utf8"), indexPath);
15661
16702
  if (migrated !== void 0) {
15662
16703
  for (const file of migrated) {
15663
- const target = join(workdir, ...file.path.split("/"));
15664
- mkdirSync(dirname(target), { recursive: true });
15665
- writeFileSync(target, file.text);
16704
+ writeTextFile(join(workdir, ...file.path.split("/")), file.text);
15666
16705
  written.push(file.path);
15667
16706
  }
15668
16707
  rmSync(legacyFile);
@@ -15671,12 +16710,12 @@ function createMarketSourceInitializer(options) {
15671
16710
  }
15672
16711
  if (!existsSync(indexMarker)) {
15673
16712
  mkdirSync(indexDir, { recursive: true });
15674
- writeFileSync(indexMarker, MARKET_ENTRIES_README_LINES.join("\n"));
16713
+ writeTextFile(indexMarker, MARKET_ENTRIES_README_LINES.join("\n"));
15675
16714
  written.push(`${indexPath}/README.md`);
15676
16715
  }
15677
16716
  const readmeFile = join(workdir, "README.md");
15678
16717
  if (!existsSync(readmeFile)) {
15679
- writeFileSync(readmeFile, MARKET_INDEX_README_LINES.join("\n"));
16718
+ writeTextFile(readmeFile, MARKET_INDEX_README_LINES.join("\n"));
15680
16719
  written.push("README.md");
15681
16720
  }
15682
16721
  }
@@ -16710,12 +17749,12 @@ var INSTALLED_PLUGINS_ROUTE = "panels/plugins";
16710
17749
  //#endregion
16711
17750
  //#region src/modules/panels/model/plugin-section.ts
16712
17751
  async function installedPlugins(options) {
16713
- const { ledger, shell, force = false, check = true } = options;
17752
+ const { ledger, seam, force = false, check = true } = options;
16714
17753
  const supply = options.supply ?? requireSupply();
16715
17754
  const rows = [];
16716
17755
  for (const entry of ledger.list()) {
16717
- const release = shell === void 0 || entry.local === true || entry.registry === void 0 ? void 0 : await supply.probeRelease({
16718
- shell,
17756
+ const release = seam === void 0 || entry.local === true || entry.registry === void 0 ? void 0 : await supply.probeRelease({
17757
+ seam,
16719
17758
  package: entry.package,
16720
17759
  registry: entry.registry,
16721
17760
  force,
@@ -16742,7 +17781,7 @@ function pluginRoutes(options) {
16742
17781
  ok: true,
16743
17782
  plugins: await installedPlugins({
16744
17783
  ledger: options.ledger,
16745
- ...options.shell === void 0 ? {} : { shell: options.shell },
17784
+ ...options.seam === void 0 ? {} : { seam: options.seam },
16746
17785
  ...options.supply === void 0 ? {} : { supply: options.supply },
16747
17786
  force: param(search, "force") === "1",
16748
17787
  check: param(search, "check") !== "0"
@@ -17010,13 +18049,13 @@ var OWNED = [{
17010
18049
  const record = {
17011
18050
  ...createRecordStore(),
17012
18051
  release: (state) => {
17013
- const shellNow = ctx.get("shell");
17014
- if (shellNow === void 0) return Promise.resolve({
18052
+ const seamNow = seamOf(ctx);
18053
+ if (seamNow === void 0) return Promise.resolve({
17015
18054
  tab: false,
17016
18055
  capture: false
17017
18056
  });
17018
18057
  return releaseQuietly({
17019
- shell: shellNow,
18058
+ seam: seamNow,
17020
18059
  command: cliCommandOf(ctx),
17021
18060
  session: BROWSER_RECORD_SESSION,
17022
18061
  url: state.url,
@@ -17026,7 +18065,7 @@ var OWNED = [{
17026
18065
  };
17027
18066
  ctx.provide(DIY_RECORD_SERVICE, record);
17028
18067
  const profileDir = supply.profileDirOf(ctx);
17029
- const shell = ctx.get("shell");
18068
+ const seam = seamOf(ctx);
17030
18069
  const moduleManifests = () => {
17031
18070
  const list = manifests();
17032
18071
  if (installable === void 0) return list.filter((manifest) => supplyNeedsOf(manifest).length > 0);
@@ -17038,7 +18077,7 @@ var OWNED = [{
17038
18077
  manifests: moduleManifests(),
17039
18078
  manifestsNow: moduleManifests,
17040
18079
  ...probe === void 0 ? {} : { probe },
17041
- shell,
18080
+ seam,
17042
18081
  command: cliCommandOf(ctx),
17043
18082
  supply,
17044
18083
  bundles,
@@ -17099,7 +18138,7 @@ var OWNED = [{
17099
18138
  return;
17100
18139
  }
17101
18140
  },
17102
- shell,
18141
+ seam,
17103
18142
  ...logger === void 0 ? {} : { logger }
17104
18143
  });
17105
18144
  return {
@@ -17108,7 +18147,7 @@ var OWNED = [{
17108
18147
  ...pluginRoutes({
17109
18148
  ledger,
17110
18149
  supply,
17111
- ...shell === void 0 ? {} : { shell }
18150
+ ...seam === void 0 ? {} : { seam }
17112
18151
  }),
17113
18152
  ...diyDebugRoutes(debug),
17114
18153
  ...diyRecordRoutes(record),
@@ -17393,6 +18432,17 @@ function declaredEnvOf(manifest) {
17393
18432
  for (const need of manifest.needs ?? []) if (need.kind === "env") out[need.name] = process.env[need.name];
17394
18433
  return out;
17395
18434
  }
18435
+ function unitSeamOf(seam, unit, manifest) {
18436
+ const declared = manifest.host.writes ?? [];
18437
+ if (seam === void 0 || declared.length === 0) return void 0;
18438
+ return createUnitSeam({
18439
+ seam,
18440
+ unit,
18441
+ runtimeRoot: workbenchRoot(),
18442
+ declared,
18443
+ realHome: homedir()
18444
+ });
18445
+ }
17396
18446
  function mountUnits(units, deps) {
17397
18447
  const store = deps.store ?? createWorkspaceStore({ root: null });
17398
18448
  const missingData = async () => ({
@@ -17447,6 +18497,7 @@ function mountUnits(units, deps) {
17447
18497
  });
17448
18498
  let result;
17449
18499
  try {
18500
+ const seam = unitSeamOf(deps.seam, unit, host.manifest);
17450
18501
  result = host.face.mount({
17451
18502
  manifest: host.manifest,
17452
18503
  ctx: mountCtx,
@@ -17456,6 +18507,7 @@ function mountUnits(units, deps) {
17456
18507
  ...deps.installable === void 0 ? {} : { installable: deps.installable },
17457
18508
  data,
17458
18509
  ...hostKit,
18510
+ ...seam === void 0 ? {} : { seam },
17459
18511
  env: declaredEnvOf(host.manifest),
17460
18512
  ...deps.libs === void 0 ? {} : { libs: deps.libs },
17461
18513
  ...deps.git === void 0 ? {} : { git: deps.git },
@@ -17608,7 +18660,7 @@ async function releaseHeld(store, held) {
17608
18660
  }
17609
18661
  }
17610
18662
  function recordActResource(deps) {
17611
- const now = () => deps.now?.() ?? Date.now();
18663
+ const now$1 = () => deps.now?.() ?? now();
17612
18664
  return {
17613
18665
  resource: "act",
17614
18666
  async handle(payload) {
@@ -17618,7 +18670,7 @@ function recordActResource(deps) {
17618
18670
  text: "不认识的意图(只认 peek / halt / stop)"
17619
18671
  };
17620
18672
  const store = deps.store();
17621
- const at = now();
18673
+ const at = now$1();
17622
18674
  const held = store?.live(at) ?? null;
17623
18675
  if (store === void 0 || held === null) return {
17624
18676
  ok: false,
@@ -17630,7 +18682,7 @@ function recordActResource(deps) {
17630
18682
  intent,
17631
18683
  delivered: deliverDebugNotice(deps.agents(), held.sessionId, notice.message, NOTICE_MODE[intent]),
17632
18684
  summary: notice.summary,
17633
- state: store.live(now())
18685
+ state: store.live(now$1())
17634
18686
  };
17635
18687
  const cleaned = await releaseHeld(store, held);
17636
18688
  const move = store.stop(held.sessionId, at);
@@ -17641,7 +18693,7 @@ function recordActResource(deps) {
17641
18693
  summary: notice.summary,
17642
18694
  cleaned,
17643
18695
  refused: move.ok ? "" : move.code,
17644
- state: store.live(now())
18696
+ state: store.live(now$1())
17645
18697
  };
17646
18698
  }
17647
18699
  };
@@ -18295,18 +19347,9 @@ function notifyLongTaskStop(agents, unit, change) {
18295
19347
  //#region src/app/host.ts
18296
19348
  var name = "dsh-workbench";
18297
19349
  var inject = ["webServer"];
18298
- function shippedAdapterSetSpec(moduleUrl) {
18299
- const spec = JSON.parse(readFileSync(new URL("../../package.json", moduleUrl), "utf8")).workbench?.adapterSet;
18300
- if (typeof spec !== "string" || spec.trim() === "") throw new Error("package.json 没写 workbench.adapterSet:外壳不知道该装哪份适配器集合");
18301
- return spec.trim();
18302
- }
18303
- async function shippedAdapterSet(ctx) {
18304
- const spec = shippedAdapterSetSpec(import.meta.url);
18305
- const factory = (await import(spec)).createAdapterSet;
18306
- if (typeof factory !== "function") throw new Error(`随包的适配器集合 ${spec} 没导出 createAdapterSet(ctx)`);
18307
- return await factory(ctx);
18308
- }
18309
19350
  async function apply(ctx, config) {
19351
+ const seam = provideWorkbenchSeam(ctx);
19352
+ if (seam !== void 0) provideGitProcessSeam(seam);
18310
19353
  const facts = createFactLog();
18311
19354
  const values = createValueHandoff();
18312
19355
  const panelState = createPanelStateStore();
@@ -18317,13 +19360,11 @@ async function apply(ctx, config) {
18317
19360
  corePackageUrl: import.meta.url,
18318
19361
  log: (message) => warn(ctx, message)
18319
19362
  });
18320
- adapters.runtime.warnUnwritableRoot(ctx);
18321
19363
  if (loaded.ok) ctx.logger?.info?.(`[dsh-workbench] 适配器集合:${adapters.id}@${adapters.version}(${loaded.spec})`);
18322
19364
  else if (loaded.failure !== "not-configured") ctx.logger?.warn?.(`[dsh-workbench] 适配器集合没生效(${loaded.failure}):${loaded.detail};先用默认集合`);
18323
19365
  ctx.provide(WORKBENCH_ADAPTERS, adapters);
18324
19366
  const runtimeCommands = adapters.runtime.commands(ctx);
18325
19367
  const libRunners = adapters.libs === void 0 ? void 0 : withLibRunners(adapters.libs, adapters.runtime.runners(ctx, adapters.data), ctx.logger);
18326
- const shell = ctx.get("shell");
18327
19368
  const routes = createHostRoutes({
18328
19369
  ctx,
18329
19370
  facts,
@@ -18411,11 +19452,11 @@ async function apply(ctx, config) {
18411
19452
  });
18412
19453
  const releasedOf = (unit) => ctx.get("workbenchPanels")?.isReleased(unit) ?? false;
18413
19454
  const probeOne = async (unit, force = false) => {
18414
- if (shell === void 0) return void 0;
19455
+ if (seam === void 0) return void 0;
18415
19456
  const manifest = manifestOf(unit);
18416
19457
  if (manifest === void 0 || !hasEnvNeeds(manifest)) return void 0;
18417
19458
  const common = {
18418
- shell,
19459
+ seam,
18419
19460
  runtimeCommands,
18420
19461
  force
18421
19462
  };
@@ -18435,7 +19476,7 @@ async function apply(ctx, config) {
18435
19476
  deniedTools: (context) => deniedNow(context),
18436
19477
  logger: ctx.logger
18437
19478
  });
18438
- const pluginTools = shell === void 0 ? void 0 : adapters.plugins.createUnits({
19479
+ const pluginTools = seam === void 0 ? void 0 : adapters.plugins.createUnits({
18439
19480
  registry: units,
18440
19481
  rows: () => ctx.get("workbenchPanels")?.listPlugins(null) ?? [],
18441
19482
  declared: (plugin) => adapters.probe.declaredPluginCommands(profileDir).get(plugin) ?? /* @__PURE__ */ new Set(),
@@ -18510,6 +19551,7 @@ async function apply(ctx, config) {
18510
19551
  probe: (unit, options) => probeOne(unit, options?.force ?? false),
18511
19552
  values,
18512
19553
  surface,
19554
+ ...seam === void 0 ? {} : { seam },
18513
19555
  longTasks: {
18514
19556
  scope: process.env["DSH_HOME"] ?? "",
18515
19557
  bump: (names) => facts.bump([...names, LONG_TASKS_ROUTE]),
@@ -18593,12 +19635,12 @@ async function apply(ctx, config) {
18593
19635
  }
18594
19636
  rebindPost();
18595
19637
  }
18596
- if (shell === void 0) ctx.logger?.warn?.("[dsh-workbench] 没有 shell 服务:需要执行缝的套件会降级");
19638
+ if (seam === void 0) ctx.logger?.warn?.("[dsh-workbench] 没有 shell 服务:需要执行缝的套件会降级");
18597
19639
  else {
18598
19640
  adapters.probe.registerPreflight({
18599
19641
  routes,
18600
19642
  ctx,
18601
- shell,
19643
+ seam,
18602
19644
  runtimeCommands,
18603
19645
  runtimeCapabilities: capabilities,
18604
19646
  ...runtimeCheck === void 0 ? {} : { runtimeCheck: () => runtimeCheck(ctx) },
@@ -18608,7 +19650,7 @@ async function apply(ctx, config) {
18608
19650
  routes.capability = capabilityHandler(async (unit, force) => {
18609
19651
  if (unit === void 0) {
18610
19652
  const common = {
18611
- shell,
19653
+ seam,
18612
19654
  runtimeCommands,
18613
19655
  force
18614
19656
  };