@dsh-workbench/core 1.1.14-beta.2 → 1.1.15

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 (41) 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 +1873 -680
  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 +2 -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 +14 -14
  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-capture-live.d.ts +25 -0
  29. package/dist/types/shared/api/host/diy-components.d.ts +2 -2
  30. package/dist/types/shared/api/host/diy-release-staging.d.ts +6 -0
  31. package/dist/types/shared/api/host/diy.d.ts +1 -1
  32. package/dist/types/shared/api/host/git-exec.d.ts +1 -1
  33. package/dist/types/shared/api/host/git-process.d.ts +3 -0
  34. package/dist/types/shared/api/host/shipped-adapter-set.d.ts +4 -0
  35. package/dist/types/shared/api/host/workbench-seam.d.ts +3 -0
  36. package/package.json +3 -6
  37. package/scripts/seed-profile.sh +3 -3
  38. package/scripts/share-data-root.mjs +10 -13
  39. package/skills/workbench-module-diy/BUILD.md +38 -0
  40. package/skills/workbench-module-diy/SKILL.md +1 -0
  41. 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, appendFileSync, closeSync, constants, copyFileSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
4
+ import path, { basename, dirname, isAbsolute, join, parse, 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,97 @@ 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
+ * 往文件末尾追加文本;文件或目录不存在就建。追加不是原子写,只给「日志/采集」这类只增不改的流。
786
+ * @param path 目标文件。
787
+ * @param text 追加内容。
788
+ */
789
+ function appendTextFile(path, text) {
790
+ mkdirSync(dirname(path), { recursive: true });
791
+ appendFileSync(path, text, { mode: modeOf(path, "keep") });
792
+ }
793
+ /**
794
+ * `writeTextFile` 的异步版;语义与默认值相同。
795
+ * @param path 目标文件。
796
+ * @param text 文本内容。
797
+ * @param options 原子/目录/权限/fsync。
798
+ * @returns 落盘完成。
799
+ */
800
+ async function writeTextFileAsync(path, text, options = {}) {
801
+ const mode = prepareOf(path, options);
802
+ if (options.atomic === false) {
803
+ writeFileSync(path, text, { mode });
804
+ return;
805
+ }
806
+ await writeFileAtomic(path, text, atomicOptionsOf(mode, options));
807
+ }
808
+ /**
809
+ * 把二进制写进文件;语义与默认值同 `writeTextFile`。
810
+ * @param path 目标文件。
811
+ * @param bytes 二进制内容。
812
+ * @param options 原子/目录/权限/fsync。
813
+ */
814
+ function writeBytesFile(path, bytes, options = {}) {
815
+ const mode = prepareOf(path, options);
816
+ if (options.atomic === false) {
817
+ writeFileSync(path, bytes, { mode });
818
+ return;
819
+ }
820
+ writeFileAtomic.sync(path, Buffer.from(bytes), atomicOptionsOf(mode, options));
821
+ }
822
+ /**
823
+ * 把 JSON 写进文件:`JSON.stringify(value, null, 2)` 加末尾换行;语义与默认值同 `writeTextFile`。
824
+ * @param path 目标文件。
825
+ * @param value 可序列化的值。
826
+ * @param options 原子/目录/权限/fsync。
827
+ */
828
+ function writeJsonFile(path, value, options = {}) {
829
+ writeTextFile(path, `${JSON.stringify(value, null, 2)}\n`, options);
830
+ }
831
+ /**
832
+ * `writeJsonFile` 的异步版;语义与默认值相同。
833
+ * @param path 目标文件。
834
+ * @param value 可序列化的值。
835
+ * @param options 原子/目录/权限/fsync。
836
+ * @returns 落盘完成。
837
+ */
838
+ async function writeJsonFileAsync(path, value, options = {}) {
839
+ await writeTextFileAsync(path, `${JSON.stringify(value, null, 2)}\n`, options);
840
+ }
841
+ //#endregion
737
842
  //#region ../shared/dist/host/installed-sources.js
738
843
  var FILE_NAME = ".dsh-workbench-sources.json";
739
844
  var FILE_VERSION = 1;
@@ -774,14 +879,10 @@ function packageSourceOf(profileDir, pkg) {
774
879
  return readInstalledSources(profileDir)[pkg];
775
880
  }
776
881
  function writeInstalledSources(profileDir, sources) {
777
- const file = installedSourcesFile(profileDir);
778
- const temp = `${file}.tmp`;
779
- const text = `${JSON.stringify({
882
+ writeTextFile(installedSourcesFile(profileDir), `${JSON.stringify({
780
883
  version: FILE_VERSION,
781
884
  packages: sources
782
- }, null, 2)}\n`;
783
- fs.writeFileSync(temp, text, { mode: 384 });
784
- fs.renameSync(temp, file);
885
+ }, null, 2)}\n`, { mode: 384 });
785
886
  }
786
887
  function rememberPackageSource(profileDir, pkg, source) {
787
888
  const trimmed = pkg.trim();
@@ -847,54 +948,58 @@ function refusalReason() {
847
948
  return "这个包既不是工作台的模块,也不是运行时的插件,装了也用不上";
848
949
  }
849
950
  //#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
- };
951
+ //#region ../shared/dist/host/time.js
952
+ dayjs.extend(utc);
953
+ /**
954
+ * 把时间戳/ISO 字符串转成毫秒;非法输入给 `NaN`(调用方据此回退)。
955
+ * @param value ISO 字符串、时间戳或 Date。
956
+ * @returns 毫秒时间戳或 `NaN`。
957
+ */
958
+ function epochOf(value) {
959
+ return dayjs(value).valueOf();
863
960
  }
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);
961
+ /**
962
+ * 当前时间(毫秒)。宿主里要「现在几点」只走这里,测试可以点住 `Date.now`。
963
+ * @returns 毫秒时间戳。
964
+ */
965
+ function now() {
966
+ return Date.now();
967
+ }
968
+ /**
969
+ * 当前时间的 ISO 字符串。
970
+ * @returns `new Date().toISOString()` 的等价物。
971
+ */
972
+ function nowIso() {
973
+ return new Date(now()).toISOString();
974
+ }
975
+ //#endregion
976
+ //#region ../shared/dist/host/seam/intent.js
977
+ var SeamError = class extends Error {
978
+ code;
979
+ path;
980
+ scope;
981
+ constructor(code, message, extra = {}) {
982
+ super(message);
983
+ this.name = "SeamError";
984
+ this.code = code;
985
+ this.path = extra.path;
986
+ this.scope = extra.scope;
873
987
  }
988
+ };
989
+ function productWrite() {
990
+ return { kind: "product" };
874
991
  }
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();
992
+ function workspaceWrite() {
993
+ return { kind: "workspace" };
994
+ }
995
+ function pathsWrite(paths) {
996
+ return {
997
+ kind: "paths",
998
+ paths
999
+ };
1000
+ }
1001
+ function noWrite() {
1002
+ return { kind: "none" };
898
1003
  }
899
1004
  //#endregion
900
1005
  //#region ../shared/dist/host/shell-text.js
@@ -2406,6 +2511,73 @@ function npmRegistryOf(source, env) {
2406
2511
  //#region ../shared/dist/contract/protocol/config-sync.js
2407
2512
  var CONFIG_SYNC_ROUTE = "config-sync";
2408
2513
  //#endregion
2514
+ //#region ../shared/dist/host/patch-blocks.js
2515
+ var ID_LINE = /^- id:[ \t]*"?([^"\s#]+)/;
2516
+ var INSERT_LINE = /^- insert:[ \t]*$/;
2517
+ var NESTED_ID = /^[ \t]+-[ \t]*id:[ \t]*"?([^"\s#]+)/;
2518
+ var NAME_LINE = /^[ \t]*-?[ \t]*name:[ \t]*['"]?([^'"\s#]+)/;
2519
+ function trimmedTail(lines) {
2520
+ let end = lines.length;
2521
+ while (end > 0 && (lines[end - 1] ?? "").trim() === "") end -= 1;
2522
+ return lines.slice(0, end);
2523
+ }
2524
+ function matched(text, pattern) {
2525
+ const found = pattern.exec(text);
2526
+ return found === null ? void 0 : found[1];
2527
+ }
2528
+ function blockOf$1(body, index) {
2529
+ const lines = body.split("\n");
2530
+ const first = lines[0] ?? "";
2531
+ const id = matched(first, ID_LINE);
2532
+ const nested = lines.flatMap((line) => {
2533
+ const one = matched(line, NESTED_ID);
2534
+ return one === void 0 ? [] : [one];
2535
+ });
2536
+ const insert = id === void 0 && INSERT_LINE.test(first);
2537
+ const label = id ?? nested.join(", ");
2538
+ const key = id !== void 0 ? `id:${id}` : insert ? `insert:${nested.join(", ")}` : `block:${String(index)}`;
2539
+ const names = lines.flatMap((line) => {
2540
+ const one = matched(line, NAME_LINE);
2541
+ return one === void 0 ? [] : [one];
2542
+ });
2543
+ return {
2544
+ key,
2545
+ id: label === "" ? key : label,
2546
+ insert,
2547
+ body,
2548
+ names
2549
+ };
2550
+ }
2551
+ function spannedBlocks(text) {
2552
+ const lines = text.split("\n");
2553
+ const starts = [];
2554
+ lines.forEach((line, index) => {
2555
+ if (/^- /.test(line) || line === "-") starts.push(index);
2556
+ });
2557
+ const blocks = [];
2558
+ for (let i = 0; i < starts.length; i += 1) {
2559
+ const from = starts[i] ?? 0;
2560
+ const to = i + 1 < starts.length ? starts[i + 1] ?? lines.length : lines.length;
2561
+ const body = trimmedTail(lines.slice(from, to)).join("\n");
2562
+ if (body.trim() === "") continue;
2563
+ blocks.push({
2564
+ ...blockOf$1(body, blocks.length),
2565
+ from,
2566
+ to
2567
+ });
2568
+ }
2569
+ return blocks;
2570
+ }
2571
+ function patchBlocks(text) {
2572
+ return spannedBlocks(text).map(({ from: _from, to: _to, ...block }) => block);
2573
+ }
2574
+ //#endregion
2575
+ //#region ../shared/dist/host/patch.js
2576
+ /** 把 patch 文本写回原文件:原子写,保留已有权限位(新文件 0644)。 */
2577
+ function writePatchFile(path, text) {
2578
+ writeTextFile(path, text, { mode: "keep" });
2579
+ }
2580
+ //#endregion
2409
2581
  //#region ../shared/dist/contract/protocol/profile-create.js
2410
2582
  var PROFILE_CREATE_ROUTE = "profile-create";
2411
2583
  //#endregion
@@ -2448,17 +2620,6 @@ function parseMarkers(text) {
2448
2620
  return refs;
2449
2621
  }
2450
2622
  //#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
2623
  //#region ../shared/dist/host/diy-debug.js
2463
2624
  /**
2464
2625
  * 把工具入参收成人话动作。
@@ -2764,6 +2925,121 @@ function createRecordStore() {
2764
2925
  };
2765
2926
  }
2766
2927
  //#endregion
2928
+ //#region ../shared/dist/host/data-root.js
2929
+ function dshHome() {
2930
+ const configured = process.env.DSH_HOME?.trim();
2931
+ if (configured !== void 0 && configured !== "") return configured;
2932
+ return join(homedir(), ".dsh");
2933
+ }
2934
+ function workbenchRoot() {
2935
+ const configured = process.env.DSH_WORKBENCH_HOME?.trim();
2936
+ if (configured !== void 0 && configured !== "") return configured;
2937
+ return join(dshHome(), "workbench");
2938
+ }
2939
+ function workspacesRoot() {
2940
+ return join(workbenchRoot(), "workspaces");
2941
+ }
2942
+ //#endregion
2943
+ //#region ../shared/dist/host/env-policy.js
2944
+ /**
2945
+ * 把父进程环境里的字符串值拷成一层 env(丢掉 undefined)。
2946
+ * @param parent 父环境(通常是 `process.env`)。
2947
+ * @returns 可继续叠加的 env 层。
2948
+ */
2949
+ function inheritedEnvOf(parent) {
2950
+ const out = {};
2951
+ for (const [key, value] of Object.entries(parent)) if (typeof value === "string") out[key] = value;
2952
+ return out;
2953
+ }
2954
+ function existingPointer(realHome, name) {
2955
+ const file = join(realHome, name);
2956
+ return existsSync(file) ? file : void 0;
2957
+ }
2958
+ /**
2959
+ * 产品域(运行时根)的受控 env:隔离 HOME、npm/pnpm 缓存都钉在运行时根,真 npmrc/gitconfig 只读指过去。
2960
+ * @param input 运行时根、可选的 HOME 与真实用户目录。
2961
+ * @returns 产品域 env。
2962
+ */
2963
+ function productEnvOf(input) {
2964
+ const runtimeRoot = input.runtimeRoot;
2965
+ const realHome = input.realHome ?? homedir();
2966
+ const env = {
2967
+ HOME: input.home ?? join(runtimeRoot, "home"),
2968
+ DSH_HOME: dshHome(),
2969
+ DSH_WORKBENCH_HOME: runtimeRoot,
2970
+ npm_config_cache: join(runtimeRoot, "npm-cache"),
2971
+ NPM_CONFIG_CACHE: join(runtimeRoot, "npm-cache"),
2972
+ npm_config_logs_dir: join(runtimeRoot, "npm-logs"),
2973
+ npm_config_store_dir: join(runtimeRoot, "pnpm-store"),
2974
+ npm_config_cache_dir: join(runtimeRoot, "pnpm-cache"),
2975
+ npm_config_state_dir: join(runtimeRoot, "pnpm-state"),
2976
+ npm_config_update_notifier: "false",
2977
+ npm_config_fund: "false",
2978
+ NO_UPDATE_NOTIFIER: "1"
2979
+ };
2980
+ const npmrc = existingPointer(realHome, ".npmrc");
2981
+ if (npmrc !== void 0) env["NPM_CONFIG_USERCONFIG"] = npmrc;
2982
+ const gitconfig = existingPointer(realHome, ".gitconfig");
2983
+ if (gitconfig !== void 0) env["GIT_CONFIG_GLOBAL"] = gitconfig;
2984
+ return env;
2985
+ }
2986
+ /**
2987
+ * 模块单元的受控 env:在产品域基础上把 HOME 与 npm/pnpm 目录全钉到单元数据目录。
2988
+ * @param input 运行时根、单元数据目录、可选的 TRUE 用户目录。
2989
+ * @returns 单元 env。
2990
+ */
2991
+ function unitEnvOf(input) {
2992
+ const unitRoot = input.unitRoot;
2993
+ return {
2994
+ ...productEnvOf({
2995
+ ...input,
2996
+ home: join(unitRoot, "home")
2997
+ }),
2998
+ npm_config_cache: join(unitRoot, "npm-cache"),
2999
+ NPM_CONFIG_CACHE: join(unitRoot, "npm-cache"),
3000
+ npm_config_logs_dir: join(unitRoot, "npm-logs"),
3001
+ npm_config_store_dir: join(unitRoot, "pnpm-store"),
3002
+ npm_config_cache_dir: join(unitRoot, "pnpm-cache"),
3003
+ npm_config_state_dir: join(unitRoot, "pnpm-state")
3004
+ };
3005
+ }
3006
+ /**
3007
+ * git 的非交互 env:关掉终端提示/askpass;Windows 上不给 askpass 指 POSIX 路径(那台机器上没有)。
3008
+ * @param platform 宿主平台。
3009
+ * @returns git 非交互 env。
3010
+ */
3011
+ function promptlessGitEnvOf(platform) {
3012
+ const shared = {
3013
+ GIT_TERMINAL_PROMPT: "0",
3014
+ SSH_ASKPASS_REQUIRE: "never",
3015
+ GCM_INTERACTIVE: "never",
3016
+ GIT_SSH_COMMAND: "ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new"
3017
+ };
3018
+ if (platform === "win32") return shared;
3019
+ return {
3020
+ ...shared,
3021
+ GIT_ASKPASS: "/usr/bin/false",
3022
+ SSH_ASKPASS: "/usr/bin/false"
3023
+ };
3024
+ }
3025
+ /**
3026
+ * 在父环境上叠加 `promptlessGitEnvOf`,得到可传给执行缝的 git env。
3027
+ * @param parent 父环境。
3028
+ * @param platform 宿主平台。
3029
+ * @returns 只含字符串的 env。
3030
+ */
3031
+ function promptlessProcessEnvOf(parent, platform) {
3032
+ const env = {
3033
+ ...inheritedEnvOf(parent),
3034
+ ...promptlessGitEnvOf(platform)
3035
+ };
3036
+ if (platform === "win32") {
3037
+ delete env["GIT_ASKPASS"];
3038
+ delete env["SSH_ASKPASS"];
3039
+ }
3040
+ return env;
3041
+ }
3042
+ //#endregion
2767
3043
  //#region ../shared/dist/host/facts.js
2768
3044
  function createFactLog(options = {}) {
2769
3045
  const epoch = options.epoch ?? `e${randomUUID()}`;
@@ -2938,7 +3214,7 @@ function parseCrashReport(payload) {
2938
3214
  workspace,
2939
3215
  unit,
2940
3216
  reason,
2941
- at: atOf(record["at"]) ?? Date.now()
3217
+ at: atOf(record["at"]) ?? now()
2942
3218
  }
2943
3219
  };
2944
3220
  }
@@ -3459,7 +3735,7 @@ function healthHandler(ctx, runtimes) {
3459
3735
  profile: dir === void 0 ? null : dir.split("/").pop(),
3460
3736
  profileDir: dir ?? null,
3461
3737
  pid: process.pid,
3462
- now: (/* @__PURE__ */ new Date()).toISOString(),
3738
+ now: nowIso(),
3463
3739
  ...runtimes === void 0 ? {} : { runtimes }
3464
3740
  };
3465
3741
  };
@@ -3498,7 +3774,7 @@ function registerPanelState(ctx, store) {
3498
3774
  store.cards(parsed.value);
3499
3775
  return {
3500
3776
  ok: true,
3501
- at: Date.now()
3777
+ at: now()
3502
3778
  };
3503
3779
  }
3504
3780
  }, {
@@ -3512,7 +3788,7 @@ function registerPanelState(ctx, store) {
3512
3788
  store.crash(parsed.value);
3513
3789
  return {
3514
3790
  ok: true,
3515
- at: Date.now()
3791
+ at: now()
3516
3792
  };
3517
3793
  }
3518
3794
  }]
@@ -3538,19 +3814,19 @@ function registerDiyRecordAct(ctx, resource, log) {
3538
3814
  }
3539
3815
  //#endregion
3540
3816
  //#region ../shared/dist/host/long-task.js
3541
- var LIVE = /* @__PURE__ */ new Set(["running", "needs-login"]);
3817
+ var LIVE$1 = /* @__PURE__ */ new Set(["running", "needs-login"]);
3542
3818
  var RECENT_MS = 6e5;
3543
3819
  function createLongTaskRegistry(deps) {
3544
3820
  const runs = /* @__PURE__ */ new Map();
3545
3821
  const controls = /* @__PURE__ */ new Map();
3546
- const now = deps.now ?? (() => (/* @__PURE__ */ new Date()).toISOString());
3822
+ const now$7 = deps.now ?? (() => nowIso());
3547
3823
  let counter = 0;
3548
- const nextId = deps.nextId ?? (() => `run-${Date.now()}-${counter += 1}`);
3824
+ const nextId = deps.nextId ?? (() => `run-${now()}-${counter += 1}`);
3549
3825
  const recentMs = deps.recentMs ?? RECENT_MS;
3550
3826
  const listeners = /* @__PURE__ */ new Set();
3551
3827
  const live = (id) => {
3552
3828
  const current = runs.get(id);
3553
- if (current === void 0 || !LIVE.has(current.status)) return null;
3829
+ if (current === void 0 || !LIVE$1.has(current.status)) return null;
3554
3830
  return current;
3555
3831
  };
3556
3832
  const notify = (change) => {
@@ -3564,7 +3840,7 @@ function createLongTaskRegistry(deps) {
3564
3840
  const patched = {
3565
3841
  ...current,
3566
3842
  ...next,
3567
- at: now()
3843
+ at: now$7()
3568
3844
  };
3569
3845
  runs.set(id, patched);
3570
3846
  notify({
@@ -3581,25 +3857,25 @@ function createLongTaskRegistry(deps) {
3581
3857
  return null;
3582
3858
  },
3583
3859
  current(workspace) {
3584
- const floor = epochOf(now()) - recentMs;
3860
+ const floor = epochOf(now$7()) - recentMs;
3585
3861
  const newest = /* @__PURE__ */ new Map();
3586
3862
  for (const snapshot of runs.values()) {
3587
3863
  if (snapshot.workspace !== workspace) continue;
3588
3864
  newest.set(snapshot.target, snapshot);
3589
3865
  }
3590
- return [...newest.values()].filter((snapshot) => LIVE.has(snapshot.status) || epochOf(snapshot.at) >= floor).reverse().map((snapshot) => ({
3866
+ return [...newest.values()].filter((snapshot) => LIVE$1.has(snapshot.status) || epochOf(snapshot.at) >= floor).reverse().map((snapshot) => ({
3591
3867
  ...deps.view(snapshot),
3592
3868
  id: snapshot.id
3593
3869
  }));
3594
3870
  },
3595
3871
  live() {
3596
- return [...runs.values()].filter((snapshot) => LIVE.has(snapshot.status));
3872
+ return [...runs.values()].filter((snapshot) => LIVE$1.has(snapshot.status));
3597
3873
  },
3598
3874
  start(options) {
3599
3875
  const running = this.active(options.workspace, options.target);
3600
3876
  if (running !== null) return running;
3601
3877
  const id = nextId();
3602
- const startedAt = now();
3878
+ const startedAt = now$7();
3603
3879
  runs.set(id, {
3604
3880
  id,
3605
3881
  workspace: options.workspace,
@@ -4010,7 +4286,7 @@ function defaultSleep(ms, signal) {
4010
4286
  signal.addEventListener("abort", onAbort, { once: true });
4011
4287
  });
4012
4288
  }
4013
- function targetOf(flow, scope) {
4289
+ function targetOf$1(flow, scope) {
4014
4290
  if (flow.target === void 0) return flow.id;
4015
4291
  const value = resolveValue(flow.target, scope);
4016
4292
  return typeof value === "string" && value.trim() !== "" ? value.trim() : flow.id;
@@ -4148,14 +4424,14 @@ async function runOne(options) {
4148
4424
  async function runFlow(options) {
4149
4425
  const { flow, input, actions, signal, runId, workspace, onStep } = options;
4150
4426
  const sleep = options.sleep ?? defaultSleep;
4151
- const now = options.now ?? (() => Date.now());
4427
+ const now$6 = options.now ?? (() => now());
4152
4428
  const steps = {};
4153
4429
  const emit = (event) => {
4154
4430
  try {
4155
4431
  onStep(event);
4156
4432
  } catch {}
4157
4433
  };
4158
- const target = targetOf(flow, {
4434
+ const target = targetOf$1(flow, {
4159
4435
  in: input,
4160
4436
  steps
4161
4437
  });
@@ -4218,7 +4494,7 @@ async function runFlow(options) {
4218
4494
  });
4219
4495
  },
4220
4496
  sleep,
4221
- now
4497
+ now: now$6
4222
4498
  });
4223
4499
  if (nested.status === "failed") {
4224
4500
  const failed = nested.failedStep === void 0 ? step.id : `${step.id}.${nested.failedStep}`;
@@ -4263,7 +4539,7 @@ async function runFlow(options) {
4263
4539
  runId,
4264
4540
  workspace,
4265
4541
  sleep,
4266
- now,
4542
+ now: now$6,
4267
4543
  emit
4268
4544
  }));
4269
4545
  }
@@ -4293,7 +4569,7 @@ async function runFlow(options) {
4293
4569
  runId,
4294
4570
  workspace,
4295
4571
  sleep,
4296
- now,
4572
+ now: now$6,
4297
4573
  emit
4298
4574
  });
4299
4575
  emit({
@@ -4373,7 +4649,8 @@ function sessionIdOf(exec) {
4373
4649
  * 建一份宿主 kit;每个挂载点一份,没有模块级状态。
4374
4650
  * @param parts.libInstallCommand 拼「装这个共用依赖」的那条命令。
4375
4651
  * @param parts.surface 宿主平台事实;由组合根按 profile 与宿主进程算好再交进来。
4376
- * @returns flow / sessionIdOf / sessionWorkspace / libInstallCommand / surface。
4652
+ * @param parts.seam 按 unit 绑定好的执行缝门面;模块清单没声明写入域时缺省不给。
4653
+ * @returns flow / sessionIdOf / sessionWorkspace / libInstallCommand / surface,有门面时带上 seam。
4377
4654
  */
4378
4655
  function createHostKit(parts) {
4379
4656
  return {
@@ -4381,7 +4658,8 @@ function createHostKit(parts) {
4381
4658
  sessionIdOf,
4382
4659
  sessionWorkspace,
4383
4660
  libInstallCommand: parts.libInstallCommand,
4384
- surface: parts.surface
4661
+ surface: parts.surface,
4662
+ ...parts.seam === void 0 ? {} : { seam: parts.seam }
4385
4663
  };
4386
4664
  }
4387
4665
  //#endregion
@@ -4751,41 +5029,26 @@ function createValueHandoff(options = {}) {
4751
5029
  const ttlMs = options.ttlMs ?? 15e3;
4752
5030
  const held = /* @__PURE__ */ new Map();
4753
5031
  return {
4754
- hand(entry, value, now = Date.now()) {
5032
+ hand(entry, value, now$4 = now()) {
4755
5033
  held.set(handoffId(entry), {
4756
5034
  value,
4757
- at: now
5035
+ at: now$4
4758
5036
  });
4759
5037
  },
4760
- take(entry, now = Date.now()) {
5038
+ take(entry, now$5 = now()) {
4761
5039
  const id = handoffId(entry);
4762
5040
  const hit = held.get(id);
4763
5041
  if (hit === void 0) return void 0;
4764
5042
  held.delete(id);
4765
- return now - hit.at >= ttlMs ? void 0 : hit;
5043
+ return now$5 - hit.at >= ttlMs ? void 0 : hit;
4766
5044
  }
4767
5045
  };
4768
5046
  }
4769
5047
  //#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
- //#region ../shared/dist/host/workspace-store.js
4786
- function slug(raw, max = 40) {
4787
- const cleaned = raw.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, max);
4788
- return cleaned === "" ? "workspace" : cleaned;
5048
+ //#region ../shared/dist/host/workspace-store.js
5049
+ function slug(raw, max = 40) {
5050
+ const cleaned = raw.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^[-.]+|[-.]+$/g, "").slice(0, max);
5051
+ return cleaned === "" ? "workspace" : cleaned;
4789
5052
  }
4790
5053
  function workspaceKey(workspace) {
4791
5054
  const abs = path.resolve(workspace);
@@ -4823,9 +5086,6 @@ function memoryStore(dir) {
4823
5086
  };
4824
5087
  }
4825
5088
  function fileStore(dir) {
4826
- const ensureFor = (file) => {
4827
- fs.mkdirSync(path.dirname(file), { recursive: true });
4828
- };
4829
5089
  return {
4830
5090
  dir,
4831
5091
  read: (name) => {
@@ -4837,11 +5097,7 @@ function fileStore(dir) {
4837
5097
  }
4838
5098
  },
4839
5099
  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);
5100
+ writeJsonFile(inside(dir, name), value);
4845
5101
  },
4846
5102
  remove: (name) => {
4847
5103
  const file = inside(dir, name);
@@ -4890,6 +5146,718 @@ function createWorkspaceStore(options) {
4890
5146
  };
4891
5147
  }
4892
5148
  //#endregion
5149
+ //#region ../shared/dist/host/shell-render.js
5150
+ var QUOTED_HEAD = /^\s*['"]/;
5151
+ function callOperatorOf(kind, script) {
5152
+ if (kind !== "pwsh" || !QUOTED_HEAD.test(script)) return script;
5153
+ return `& ${script}`;
5154
+ }
5155
+ function invocationOf(kind, script) {
5156
+ return kind === "pwsh" ? {
5157
+ file: "pwsh",
5158
+ args: [
5159
+ "-NoProfile",
5160
+ "-NonInteractive",
5161
+ "-Command",
5162
+ callOperatorOf("pwsh", script)
5163
+ ]
5164
+ } : {
5165
+ file: "bash",
5166
+ args: ["-c", script]
5167
+ };
5168
+ }
5169
+ function quotedArgvLine(argv, platform = process.platform) {
5170
+ return argv.map((one) => quoteShellArgFor(one, platform)).join(" ");
5171
+ }
5172
+ function quotedLineOf(invocation, platform = process.platform) {
5173
+ return quotedArgvLine([invocation.file, ...invocation.args], platform);
5174
+ }
5175
+ //#endregion
5176
+ //#region ../shared/dist/host/shell.js
5177
+ var DEFAULT_MAX_BYTES$3 = 8388608;
5178
+ var GUARD_SLACK_MS = 1e3;
5179
+ function stalledResult() {
5180
+ return {
5181
+ exitCode: null,
5182
+ timedOut: true,
5183
+ aborted: false,
5184
+ stdout: "",
5185
+ stderr: "",
5186
+ truncated: false,
5187
+ stalled: true
5188
+ };
5189
+ }
5190
+ async function settleWithin(work, limitMs) {
5191
+ let timer;
5192
+ const guard = new Promise((resolve) => {
5193
+ timer = setTimeout(() => resolve(null), limitMs);
5194
+ });
5195
+ try {
5196
+ return await Promise.race([work, guard]);
5197
+ } finally {
5198
+ if (timer !== void 0) clearTimeout(timer);
5199
+ }
5200
+ }
5201
+ async function runShell(shell, command, options = {}) {
5202
+ const { workdir, timeoutMs = GIT_TIMEOUT_MS, signal, env, sandboxPolicy } = options;
5203
+ const spec = shell.resolve({
5204
+ command,
5205
+ ...workdir === void 0 ? {} : { workdir },
5206
+ timeoutMs,
5207
+ onExpiry: "kill",
5208
+ stdoutMaxBytes: DEFAULT_MAX_BYTES$3,
5209
+ ...signal === void 0 ? {} : { signal },
5210
+ ...env === void 0 ? {} : { env },
5211
+ ...sandboxPolicy === void 0 ? {} : { sandboxPolicy }
5212
+ });
5213
+ return await settleWithin((async () => {
5214
+ const result = await (await shell.execute(spec)).result();
5215
+ return {
5216
+ exitCode: result.exitCode,
5217
+ timedOut: result.timedOut === true,
5218
+ aborted: result.aborted === true,
5219
+ stdout: result.stdout.text,
5220
+ stderr: result.stderr.text,
5221
+ truncated: result.stdout.truncated || result.stderr.truncated,
5222
+ stalled: false,
5223
+ ...result.sandbox === void 0 ? {} : { sandbox: result.sandbox }
5224
+ };
5225
+ })(), timeoutMs + GUARD_SLACK_MS) ?? stalledResult();
5226
+ }
5227
+ //#endregion
5228
+ //#region ../shared/dist/host/seam/policy.js
5229
+ function seamModeOf(value) {
5230
+ const text = value?.trim();
5231
+ return text === "read-only" || text === "workspace-write" || text === "danger-full-access" ? text : "workspace-write";
5232
+ }
5233
+ function rank(mode) {
5234
+ return mode === "read-only" ? 0 : mode === "workspace-write" ? 1 : 2;
5235
+ }
5236
+ function narrowerMode(left, right) {
5237
+ return rank(left) <= rank(right) ? left : right;
5238
+ }
5239
+ function insideOf(root, path) {
5240
+ const confined = resolve(root);
5241
+ const target = resolve(path);
5242
+ if (target === confined) return true;
5243
+ return target.startsWith(confined.endsWith(sep) ? confined : `${confined}${sep}`);
5244
+ }
5245
+ function productPolicy(ceiling, productRoot) {
5246
+ return {
5247
+ mode: ceiling === "read-only" ? "read-only" : ceiling === "danger-full-access" ? "danger-full-access" : "workspace-write",
5248
+ workspaceRoot: resolve(productRoot)
5249
+ };
5250
+ }
5251
+ function workspacePolicy(ceiling, grant) {
5252
+ if (grant === void 0) throw new SeamError("workspace-unknown", "这条会话解析不到工作区:先在左栏选一个工作区再跑需要写工作区的命令");
5253
+ const mode = narrowerMode(ceiling, grant.mode);
5254
+ if (mode === "read-only") throw new SeamError("read-only", "当前会话是只读模式:要写工作区,先在权限设置里切换模式");
5255
+ return {
5256
+ mode,
5257
+ workspaceRoot: resolve(grant.workspaceRoot)
5258
+ };
5259
+ }
5260
+ function atFilesystemRoot(path) {
5261
+ return parse(path).root === path;
5262
+ }
5263
+ function commonDirectory(paths) {
5264
+ const dirs = paths.map((path) => dirname(resolve(path)));
5265
+ let shared = dirs[0];
5266
+ if (shared === void 0) return void 0;
5267
+ for (const dir of dirs.slice(1)) while (shared !== dirname(shared) && !insideOf(shared, dir)) shared = dirname(shared);
5268
+ return shared;
5269
+ }
5270
+ function pathsPolicy(ceiling, paths) {
5271
+ if (paths.length === 0) throw new SeamError("paths-span-trees", "这次写入没有给出任何路径");
5272
+ if (!paths.every((path) => isAbsolute(path))) throw new SeamError("paths-span-trees", "外部路径必须是绝对路径");
5273
+ if (ceiling === "read-only") throw new SeamError("read-only", "当前部署是只读模式:这条命令不能写外部路径");
5274
+ const root = commonDirectory(paths);
5275
+ if (root === void 0) throw new SeamError("paths-span-trees", "这次写入没有给出任何路径");
5276
+ if (atFilesystemRoot(root)) throw new SeamError("paths-span-trees", paths.length > 1 ? "这次写入跨了多个目录:请分开做,或把产物改到工作台目录" : "不能把整个文件系统声明成写入域");
5277
+ return {
5278
+ mode: "workspace-write",
5279
+ workspaceRoot: root
5280
+ };
5281
+ }
5282
+ function denialScopeOf(path, policy, productRoot, grant) {
5283
+ if (path === void 0) return "unknown";
5284
+ if (insideOf(productRoot, path)) return "product";
5285
+ if (grant !== void 0 && insideOf(grant.workspaceRoot, path)) return "workspace";
5286
+ if (insideOf(policy.workspaceRoot, path)) return policy.workspaceRoot === resolve(productRoot) ? "product" : "workspace";
5287
+ return "external";
5288
+ }
5289
+ function policyOf(intent, ceiling, productRoot, grant) {
5290
+ if (intent.kind === "product" || intent.kind === "none") return productPolicy(ceiling, productRoot);
5291
+ if (intent.kind === "workspace") return workspacePolicy(ceiling, grant);
5292
+ return pathsPolicy(ceiling, intent.paths);
5293
+ }
5294
+ //#endregion
5295
+ //#region ../shared/dist/host/seam/env.js
5296
+ /**
5297
+ * 产品域(运行时根)的受控 env:隔离 HOME、npm/pnpm 缓存钉在运行时根、真 npmrc/gitconfig 只读指过去。
5298
+ * @param input 运行时根、可选的 HOME 与真实用户目录。
5299
+ * @returns 产品域 env。
5300
+ */
5301
+ function seamProductEnv(input) {
5302
+ return productEnvOf(input);
5303
+ }
5304
+ /**
5305
+ * 模块单元的受控 env:HOME 与 npm/pnpm 目录钉到单元数据目录。
5306
+ * @param input 运行时根、单元数据目录、可选的 TRUE 用户目录。
5307
+ * @returns 单元 env。
5308
+ */
5309
+ function seamUnitEnv(input) {
5310
+ return unitEnvOf(input);
5311
+ }
5312
+ //#endregion
5313
+ //#region ../shared/dist/host/seam/denial.js
5314
+ var PATTERNS = [
5315
+ /npm (?:error|ERR!) path (.+)$/m,
5316
+ /(?:EPERM|EACCES):[^,\n]*, (?:open|unlink|mkdir|scandir|rename|access|stat|lstat|read|write|copyfile|rmdir|opendir|utimes|chmod|chown) '([^']+)'/i,
5317
+ /(?:Operation not permitted|Permission denied)[^\n]*?'([^']+)'/i,
5318
+ /fatal: could not create work tree dir '([^']+)'/i,
5319
+ /mkdir: cannot create directory '([^']+)'/i,
5320
+ /cannot (?:create|mkdir) (?:directory )?'([^']+)'/i,
5321
+ /(?:Operation not permitted|Permission denied):\s*([^\s'\n]+)/i
5322
+ ];
5323
+ function firstMatch(text) {
5324
+ for (const pattern of PATTERNS) {
5325
+ const value = pattern.exec(text)?.[1]?.trim();
5326
+ if (value !== void 0 && value !== "") return value;
5327
+ }
5328
+ }
5329
+ function deniedPathOf(stderr, stdout = "") {
5330
+ return firstMatch(stderr) ?? firstMatch(stdout);
5331
+ }
5332
+ function sandboxDeniedOf(facts) {
5333
+ return facts?.denied === true;
5334
+ }
5335
+ //#endregion
5336
+ //#region ../shared/dist/host/seam/host-env.js
5337
+ function hostEnvOf(env) {
5338
+ const merged = { ...process.env };
5339
+ for (const [key, value] of Object.entries(env ?? {})) if (value === void 0) delete merged[key];
5340
+ else merged[key] = value;
5341
+ return merged;
5342
+ }
5343
+ function definedEnvOf(env) {
5344
+ const out = {};
5345
+ for (const [key, value] of Object.entries(env ?? {})) if (value !== void 0) out[key] = value;
5346
+ return out;
5347
+ }
5348
+ //#endregion
5349
+ //#region ../shared/dist/host/seam/process.js
5350
+ function streamOf(readable) {
5351
+ if (readable === null || readable === void 0) return null;
5352
+ return { onData(listener) {
5353
+ const handler = (chunk) => listener(String(chunk));
5354
+ readable.on("data", handler);
5355
+ return () => {
5356
+ readable.off("data", handler);
5357
+ };
5358
+ } };
5359
+ }
5360
+ function createNodeProcessHost() {
5361
+ return { spawn(options) {
5362
+ const logFd = options.stdio === "log" && options.logFile !== void 0 ? openSync(options.logFile, "a") : void 0;
5363
+ const stdio = options.stdio === "pipe" ? "pipe" : logFd === void 0 ? "ignore" : [
5364
+ "ignore",
5365
+ logFd,
5366
+ logFd
5367
+ ];
5368
+ let logClosed = false;
5369
+ const closeLog = () => {
5370
+ if (logClosed || logFd === void 0) return;
5371
+ logClosed = true;
5372
+ closeSync(logFd);
5373
+ };
5374
+ let child;
5375
+ try {
5376
+ child = execa(options.file, [...options.args], {
5377
+ ...options.cwd === void 0 ? {} : { cwd: options.cwd },
5378
+ env: { ...options.env },
5379
+ detached: options.detached,
5380
+ stdio,
5381
+ windowsHide: options.windowsHide,
5382
+ reject: false,
5383
+ buffer: false
5384
+ });
5385
+ } catch (error) {
5386
+ closeLog();
5387
+ throw error;
5388
+ }
5389
+ if (logFd !== void 0) {
5390
+ child.nodeChildProcess.once("spawn", closeLog);
5391
+ child.nodeChildProcess.once("error", closeLog);
5392
+ }
5393
+ const stdin = child.stdin;
5394
+ return {
5395
+ pid: child.pid,
5396
+ stdin: stdin === null || stdin === void 0 ? null : { write: (chunk) => stdin.write(chunk) },
5397
+ stdout: streamOf(child.stdout),
5398
+ stderr: streamOf(child.stderr),
5399
+ onExit(listener) {
5400
+ const handler = (code, signal) => listener({
5401
+ code,
5402
+ signal
5403
+ });
5404
+ child.nodeChildProcess.on("exit", handler);
5405
+ return () => {
5406
+ child.nodeChildProcess.off("exit", handler);
5407
+ };
5408
+ },
5409
+ onError(listener) {
5410
+ child.nodeChildProcess.on("error", listener);
5411
+ return () => {
5412
+ child.nodeChildProcess.off("error", listener);
5413
+ };
5414
+ },
5415
+ unref() {
5416
+ child.nodeChildProcess.unref();
5417
+ },
5418
+ kill(signal) {
5419
+ child.kill(signal);
5420
+ }
5421
+ };
5422
+ } };
5423
+ }
5424
+ //#endregion
5425
+ //#region ../shared/dist/host/seam/run.js
5426
+ var DENIAL_MARKER = /\[sandbox: file access denied under [a-z-]+ mode\]/i;
5427
+ var DEFAULT_MAX_BYTES$2 = 8388608;
5428
+ function scopeText(scope) {
5429
+ if (scope === "product") return "工作台自己的目录";
5430
+ if (scope === "workspace") return "会话工作区";
5431
+ if (scope === "external") return "工作区之外的路径";
5432
+ return "没认出来的路径";
5433
+ }
5434
+ function denialMessageOf(scope, path, output) {
5435
+ const where = path === void 0 ? "" : `:${path}`;
5436
+ const marker = DENIAL_MARKER.test(output) ? "" : "(输出里没有沙箱标记,按拒绝处理)";
5437
+ const next = scope === "external" ? "要写工作区外的路径得先让用户点头:把目标改到工作区,或让工具补上审批后再试" : "这是工作台自己的声明问题,不是用户的配置问题:把命令、域与路径一起记下来交给维护者";
5438
+ return `被沙箱挡住了(${scopeText(scope)}${where})${marker}。${next}`;
5439
+ }
5440
+ function settle$1(result, denial) {
5441
+ return {
5442
+ exitCode: result.exitCode,
5443
+ timedOut: result.timedOut,
5444
+ aborted: result.aborted,
5445
+ stdout: result.stdout,
5446
+ stderr: result.stderr,
5447
+ truncated: result.truncated,
5448
+ stalled: result.stalled,
5449
+ ...denial === void 0 ? {} : { denial }
5450
+ };
5451
+ }
5452
+ function targetOf(request) {
5453
+ const hasCommand = request.command !== void 0;
5454
+ const hasArgv = request.argv !== void 0;
5455
+ if (hasArgv && !hasCommand) {
5456
+ const argv = request.argv ?? [];
5457
+ if (argv.length === 0) throw new SeamError("scope-not-declared", "argv 是空的:至少给一个可执行文件");
5458
+ return { argv };
5459
+ }
5460
+ if (hasCommand && !hasArgv) return { line: request.command ?? "" };
5461
+ throw new SeamError("scope-not-declared", "这条执行请求要么给 argv,要么给一条命令行,不能两个都给或都不给");
5462
+ }
5463
+ function displayOf(target) {
5464
+ return "argv" in target ? quotedArgvLine(target.argv) : target.line;
5465
+ }
5466
+ function targetInvocationOf(target, shellKind) {
5467
+ if ("argv" in target) {
5468
+ const [file, ...args] = target.argv;
5469
+ if (file === void 0) throw new SeamError("scope-not-declared", "argv 是空的:至少给一个可执行文件");
5470
+ return {
5471
+ file,
5472
+ args
5473
+ };
5474
+ }
5475
+ return invocationOf(shellKind ?? shellKindOf(), target.line);
5476
+ }
5477
+ function sandboxedLineOf(target, shellKind) {
5478
+ const line = "argv" in target ? quotedArgvLine(target.argv) : target.line;
5479
+ if (shellKind === void 0) return callOperatorOf(shellKindOf(), line);
5480
+ return callOperatorOf(shellKindOf(), quotedLineOf(invocationOf(shellKind, line)));
5481
+ }
5482
+ function createSeam(ports) {
5483
+ const runtimeRoot = ports.runtimeRoot ?? workbenchRoot();
5484
+ const ceiling = ports.ceiling ?? seamModeOf(process.env["DSH_PERMISSION_MODE"]);
5485
+ const allowHost = ports.allowHost === true;
5486
+ const processes = ports.processes ?? createNodeProcessHost();
5487
+ const limit = ports.factLimit ?? 80;
5488
+ const facts = [];
5489
+ function record(fact) {
5490
+ facts.push(fact);
5491
+ if (facts.length > limit) facts.splice(0, facts.length - limit);
5492
+ }
5493
+ function grantFor(session) {
5494
+ if (ports.grantOf === void 0 || session === void 0 || session === null) return void 0;
5495
+ try {
5496
+ return ports.grantOf(session);
5497
+ } catch {
5498
+ return;
5499
+ }
5500
+ }
5501
+ function requireHost(intent) {
5502
+ if (intent.kind !== "host") throw new SeamError("scope-not-declared", "这条调用要走宿主进程执行,但写入域不是 host");
5503
+ if (!allowHost) throw new SeamError("scope-not-declared", "宿主域只给宿主侧接线开出来;模块与普通调用点拿不到");
5504
+ }
5505
+ async function attempt(request, policy, line) {
5506
+ return runShell(ports.shell, line, {
5507
+ ...request.workdir === void 0 ? {} : { workdir: request.workdir },
5508
+ ...request.timeoutMs === void 0 ? {} : { timeoutMs: request.timeoutMs },
5509
+ ...request.signal === void 0 ? {} : { signal: request.signal },
5510
+ env: {
5511
+ ...seamProductEnv({
5512
+ runtimeRoot,
5513
+ realHome: ports.realHome
5514
+ }),
5515
+ ...definedEnvOf(request.env)
5516
+ },
5517
+ sandboxPolicy: policy
5518
+ });
5519
+ }
5520
+ function healPolicyOf(intent, scope, granted) {
5521
+ if (scope === "product" && intent.kind !== "product") return productPolicy(ceiling, runtimeRoot);
5522
+ if (scope === "workspace" && intent.kind === "workspace") {
5523
+ if (granted === void 0) return void 0;
5524
+ try {
5525
+ return workspacePolicy(ceiling, granted);
5526
+ } catch {
5527
+ return;
5528
+ }
5529
+ }
5530
+ }
5531
+ async function runHost(request, target) {
5532
+ const invocation = targetInvocationOf(target, request.shellKind);
5533
+ const child = processes.spawn({
5534
+ file: invocation.file,
5535
+ args: invocation.args,
5536
+ env: hostEnvOf(request.env),
5537
+ ...request.workdir === void 0 ? {} : { cwd: request.workdir },
5538
+ detached: false,
5539
+ stdio: "pipe",
5540
+ windowsHide: true
5541
+ });
5542
+ const state = {
5543
+ stdout: "",
5544
+ stderr: "",
5545
+ truncated: false
5546
+ };
5547
+ const append = (which, chunk) => {
5548
+ const total = state.stdout.length + state.stderr.length;
5549
+ const room = (request.maxBytes ?? DEFAULT_MAX_BYTES$2) - total;
5550
+ if (room <= 0) {
5551
+ state.truncated = true;
5552
+ return;
5553
+ }
5554
+ const text = chunk.length > room ? chunk.slice(0, room) : chunk;
5555
+ state[which] += text;
5556
+ if (chunk.length > room) state.truncated = true;
5557
+ };
5558
+ child.stdout?.onData((chunk) => append("stdout", chunk));
5559
+ child.stderr?.onData((chunk) => append("stderr", chunk));
5560
+ let timer;
5561
+ let timedOut = false;
5562
+ if (request.timeoutMs !== void 0) timer = setTimeout(() => {
5563
+ timedOut = true;
5564
+ child.kill("SIGKILL");
5565
+ }, request.timeoutMs);
5566
+ const exit = await new Promise((resolve) => {
5567
+ child.onExit((event) => resolve(event));
5568
+ child.onError((error) => {
5569
+ state.stderr = `${state.stderr}${String(error)}`;
5570
+ resolve({
5571
+ code: null,
5572
+ signal: null
5573
+ });
5574
+ });
5575
+ });
5576
+ if (timer !== void 0) clearTimeout(timer);
5577
+ return {
5578
+ exitCode: exit.code,
5579
+ timedOut,
5580
+ aborted: false,
5581
+ stdout: state.stdout,
5582
+ stderr: state.stderr,
5583
+ truncated: state.truncated,
5584
+ stalled: false
5585
+ };
5586
+ }
5587
+ function spawnHost(request) {
5588
+ const invocation = targetInvocationOf(request.target, void 0);
5589
+ const logFile = request.logFile ?? join(runtimeRoot, "logs", `${request.label ?? "spawn"}-${String(now())}.log`);
5590
+ mkdirSync(dirname(logFile), { recursive: true });
5591
+ const child = processes.spawn({
5592
+ file: invocation.file,
5593
+ args: invocation.args,
5594
+ env: hostEnvOf(request.env),
5595
+ ...request.cwd === void 0 ? {} : { cwd: request.cwd },
5596
+ detached: true,
5597
+ stdio: "log",
5598
+ logFile,
5599
+ windowsHide: true
5600
+ });
5601
+ child.unref();
5602
+ record({
5603
+ at: now(),
5604
+ verb: "spawn",
5605
+ command: displayOf(request.target),
5606
+ intent: "host",
5607
+ mode: "host",
5608
+ workspaceRoot: "",
5609
+ exitCode: null,
5610
+ timedOut: false,
5611
+ stalled: false
5612
+ });
5613
+ const display = displayOf(request.target);
5614
+ const errorListeners = /* @__PURE__ */ new Set();
5615
+ const exitWaiters = [];
5616
+ let settled;
5617
+ let failure;
5618
+ const settleExit = (event) => {
5619
+ if (settled !== void 0) return;
5620
+ settled = event;
5621
+ for (const waiter of exitWaiters.splice(0)) waiter(event);
5622
+ };
5623
+ child.onExit((event) => settleExit(event));
5624
+ child.onError((error) => {
5625
+ failure = error;
5626
+ settleExit({
5627
+ code: null,
5628
+ signal: null
5629
+ });
5630
+ if (errorListeners.size === 0) {
5631
+ ports.logger?.warn?.(`[dsh-workbench] 起进程没跑起来(${display}):${String(error)}`);
5632
+ return;
5633
+ }
5634
+ for (const listener of errorListeners) listener(error);
5635
+ });
5636
+ return {
5637
+ pid: child.pid,
5638
+ logFile,
5639
+ exit: () => settled === void 0 ? new Promise((resolve) => exitWaiters.push(resolve)) : Promise.resolve(settled),
5640
+ onError(listener) {
5641
+ if (failure !== void 0) {
5642
+ listener(failure);
5643
+ return () => {};
5644
+ }
5645
+ errorListeners.add(listener);
5646
+ return () => errorListeners.delete(listener);
5647
+ },
5648
+ kill: (signal) => child.kill(signal ?? "SIGTERM")
5649
+ };
5650
+ }
5651
+ function openHost(request) {
5652
+ const invocation = targetInvocationOf(request.target, void 0);
5653
+ const child = processes.spawn({
5654
+ file: invocation.file,
5655
+ args: invocation.args,
5656
+ env: hostEnvOf(request.env),
5657
+ ...request.cwd === void 0 ? {} : { cwd: request.cwd },
5658
+ detached: false,
5659
+ stdio: "pipe",
5660
+ windowsHide: true
5661
+ });
5662
+ record({
5663
+ at: now(),
5664
+ verb: "open",
5665
+ command: displayOf(request.target),
5666
+ intent: "host",
5667
+ mode: "host",
5668
+ workspaceRoot: "",
5669
+ exitCode: null,
5670
+ timedOut: false,
5671
+ stalled: false
5672
+ });
5673
+ const display = displayOf(request.target);
5674
+ const errorListeners = /* @__PURE__ */ new Set();
5675
+ const exitListeners = /* @__PURE__ */ new Set();
5676
+ let failure;
5677
+ let settled;
5678
+ const settleExit = (event) => {
5679
+ if (settled !== void 0) return;
5680
+ settled = event;
5681
+ for (const listener of exitListeners) listener(event);
5682
+ };
5683
+ child.onExit((event) => settleExit(event));
5684
+ child.onError((error) => {
5685
+ failure = error;
5686
+ settleExit({
5687
+ code: null,
5688
+ signal: null
5689
+ });
5690
+ if (errorListeners.size === 0) {
5691
+ ports.logger?.warn?.(`[dsh-workbench] 双向通道起不来(${display}):${String(error)}`);
5692
+ return;
5693
+ }
5694
+ for (const listener of errorListeners) listener(error);
5695
+ });
5696
+ return {
5697
+ pid: child.pid,
5698
+ write: (chunk) => child.stdin?.write(chunk),
5699
+ onData(listener) {
5700
+ const offOut = child.stdout?.onData(listener) ?? (() => {});
5701
+ const offErr = child.stderr?.onData(listener) ?? (() => {});
5702
+ return () => {
5703
+ offOut();
5704
+ offErr();
5705
+ };
5706
+ },
5707
+ onExit(listener) {
5708
+ if (settled !== void 0) {
5709
+ listener(settled);
5710
+ return () => {};
5711
+ }
5712
+ exitListeners.add(listener);
5713
+ return () => exitListeners.delete(listener);
5714
+ },
5715
+ onError(listener) {
5716
+ if (failure !== void 0) {
5717
+ listener(failure);
5718
+ return () => {};
5719
+ }
5720
+ errorListeners.add(listener);
5721
+ return () => errorListeners.delete(listener);
5722
+ },
5723
+ kill: (signal) => child.kill(signal ?? "SIGTERM")
5724
+ };
5725
+ }
5726
+ return {
5727
+ async run(request) {
5728
+ const target = targetOf(request);
5729
+ if (request.intent.kind === "host") {
5730
+ requireHost(request.intent);
5731
+ const started = now();
5732
+ const result = await runHost(request, target);
5733
+ record({
5734
+ at: started,
5735
+ verb: "run",
5736
+ command: displayOf(target),
5737
+ intent: "host",
5738
+ mode: "host",
5739
+ workspaceRoot: "",
5740
+ exitCode: result.exitCode,
5741
+ timedOut: result.timedOut,
5742
+ stalled: result.stalled
5743
+ });
5744
+ return settle$1(result);
5745
+ }
5746
+ const granted = grantFor(request.session);
5747
+ const intent = request.intent;
5748
+ const policy = policyOf(intent, ceiling, runtimeRoot, granted);
5749
+ const line = sandboxedLineOf(target, request.shellKind);
5750
+ let result = await attempt(request, policy, line);
5751
+ if (!sandboxDeniedOf(result.sandbox)) {
5752
+ record({
5753
+ at: now(),
5754
+ verb: "run",
5755
+ command: line,
5756
+ intent: intent.kind,
5757
+ mode: policy.mode,
5758
+ workspaceRoot: policy.workspaceRoot,
5759
+ exitCode: result.exitCode,
5760
+ timedOut: result.timedOut,
5761
+ stalled: result.stalled
5762
+ });
5763
+ return settle$1(result);
5764
+ }
5765
+ const path = deniedPathOf(result.stderr, result.stdout);
5766
+ const scope = denialScopeOf(path, policy, runtimeRoot, granted);
5767
+ const healedPolicy = request.retry === "safe" ? healPolicyOf(intent, scope, granted) : void 0;
5768
+ let healed = false;
5769
+ if (healedPolicy !== void 0) {
5770
+ const retried = await attempt(request, healedPolicy, line).catch(() => void 0);
5771
+ if (retried !== void 0 && !sandboxDeniedOf(retried.sandbox)) {
5772
+ result = retried;
5773
+ healed = true;
5774
+ }
5775
+ }
5776
+ const denial = {
5777
+ ...path === void 0 ? {} : { path },
5778
+ scope,
5779
+ ...healed ? { healed: true } : {}
5780
+ };
5781
+ record({
5782
+ at: now(),
5783
+ verb: "run",
5784
+ command: line,
5785
+ intent: intent.kind,
5786
+ mode: policy.mode,
5787
+ workspaceRoot: policy.workspaceRoot,
5788
+ exitCode: result.exitCode,
5789
+ timedOut: result.timedOut,
5790
+ stalled: result.stalled,
5791
+ denial
5792
+ });
5793
+ if (healed) return settle$1(result, denial);
5794
+ if (request.bestEffort === true) {
5795
+ ports.logger?.warn?.(`[dsh-workbench] ${denialMessageOf(scope, path, `${result.stderr}\n${result.stdout}`)}`);
5796
+ return settle$1(result, denial);
5797
+ }
5798
+ throw new SeamError("sandbox-denied", denialMessageOf(scope, path, `${result.stderr}\n${result.stdout}`), {
5799
+ ...path === void 0 ? {} : { path },
5800
+ scope
5801
+ });
5802
+ },
5803
+ spawn(request) {
5804
+ requireHost(request.intent);
5805
+ return spawnHost(request);
5806
+ },
5807
+ open(request) {
5808
+ requireHost(request.intent);
5809
+ return openHost(request);
5810
+ },
5811
+ facts: () => facts
5812
+ };
5813
+ }
5814
+ //#endregion
5815
+ //#region ../shared/dist/host/seam/unit-seam.js
5816
+ function unitDataRoot(runtimeRoot, unit) {
5817
+ return join(runtimeRoot, "units", unit);
5818
+ }
5819
+ function createUnitSeam(ports) {
5820
+ const unitRoot = unitDataRoot(ports.runtimeRoot, ports.unit);
5821
+ mkdirSync(unitRoot, { recursive: true });
5822
+ const declared = new Set(ports.declared ?? []);
5823
+ const env = seamUnitEnv({
5824
+ runtimeRoot: ports.runtimeRoot,
5825
+ unitRoot,
5826
+ realHome: ports.realHome
5827
+ });
5828
+ return { async run(request) {
5829
+ const common = {
5830
+ command: request.command,
5831
+ ...request.workdir === void 0 ? {} : { workdir: request.workdir },
5832
+ ...request.timeoutMs === void 0 ? {} : { timeoutMs: request.timeoutMs },
5833
+ ...request.signal === void 0 ? {} : { signal: request.signal },
5834
+ ...request.retry === void 0 ? {} : { retry: request.retry }
5835
+ };
5836
+ if (request.scope === "unit") return ports.seam.run({
5837
+ ...common,
5838
+ intent: { kind: "product" },
5839
+ env: {
5840
+ ...env,
5841
+ ...request.env ?? {}
5842
+ }
5843
+ });
5844
+ if (!declared.has("workspace")) throw new SeamError("scope-not-declared", `${ports.unit} 的清单没有声明 host.writes 里的 workspace:要写当前工作区,先在清单里声明`);
5845
+ if (request.session === void 0 || request.session === null) throw new SeamError("workspace-unknown", `${ports.unit} 请求写工作区,但这次调用没有会话`);
5846
+ return ports.seam.run({
5847
+ ...common,
5848
+ intent: { kind: "workspace" },
5849
+ session: request.session,
5850
+ env: { ...request.env ?? {} }
5851
+ });
5852
+ } };
5853
+ }
5854
+ //#endregion
5855
+ //#region ../shared/dist/host/seam/service.js
5856
+ var WORKBENCH_SEAM = "workbenchSeam";
5857
+ function seamOf(ctx) {
5858
+ return ctx.get(WORKBENCH_SEAM, false);
5859
+ }
5860
+ //#endregion
4893
5861
  //#region ../shared/dist/host/install-failure.js
4894
5862
  var TARGET_FLAGS = [
4895
5863
  "--prefix",
@@ -5352,89 +6320,6 @@ function buildSupplyIndex(declarations, options = {}) {
5352
6320
  };
5353
6321
  }
5354
6322
  //#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;
5420
- }
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;
5433
- }
5434
- throw error;
5435
- }
5436
- }
5437
- //#endregion
5438
6323
  //#region ../shared/dist/host/adapter-set.js
5439
6324
  var CORE_MEMBERS = [
5440
6325
  "data",
@@ -5492,7 +6377,6 @@ var FULL_METHODS = [
5492
6377
  ["runtime", "runners"],
5493
6378
  ["runtime", "health"],
5494
6379
  ["runtime", "seed"],
5495
- ["runtime", "warnUnwritableRoot"],
5496
6380
  ["plugins", "declarationLookup"],
5497
6381
  ["plugins", "createUnits"],
5498
6382
  ["actions", "configSyncHandler"],
@@ -6297,8 +7181,8 @@ var SUBSECTION = /^###[ \t]/;
6297
7181
  function changelogVersion(value) {
6298
7182
  return String(value ?? "").trim().replace(/^v/, "");
6299
7183
  }
6300
- function changelogDate(now = /* @__PURE__ */ new Date()) {
6301
- return dayjs(now).format("YYYY-MM-DD");
7184
+ function changelogDate(now$3 = new Date(now())) {
7185
+ return dayjs(now$3).format("YYYY-MM-DD");
6302
7186
  }
6303
7187
  function normalizeChangelogText(text) {
6304
7188
  return String(text ?? "").replace(/\r\n?/g, "\n");
@@ -6458,6 +7342,82 @@ function normalizeWhitespace(rows) {
6458
7342
  return out.join("\n").replace(/\n+$/, "");
6459
7343
  }
6460
7344
  //#endregion
7345
+ //#region src/shared/api/host/workbench-seam.ts
7346
+ function provideWorkbenchSeam(ctx) {
7347
+ const shell = ctx.get("shell");
7348
+ if (shell === void 0) return void 0;
7349
+ const runtimeRoot = workbenchRoot();
7350
+ const seam = createSeam({
7351
+ shell,
7352
+ runtimeRoot,
7353
+ realHome: homedir(),
7354
+ allowHost: true,
7355
+ logger: ctx.logger,
7356
+ grantOf: (session) => {
7357
+ const resolved = ctx.get("sandboxPolicy")?.resolve?.({ session });
7358
+ if (resolved === void 0 || resolved.mode === void 0 || resolved.workspaceRoot === void 0) return;
7359
+ return {
7360
+ mode: seamModeOf(resolved.mode),
7361
+ workspaceRoot: resolved.workspaceRoot
7362
+ };
7363
+ }
7364
+ });
7365
+ ctx.provide(WORKBENCH_SEAM, seam);
7366
+ ctx.logger?.info?.(`[dsh-workbench] 执行缝:产品域 ${runtimeRoot}`);
7367
+ return seam;
7368
+ }
7369
+ //#endregion
7370
+ //#region src/shared/api/host/git-exec.ts
7371
+ function gitNoPromptProcessEnv(parent = process.env, platform = process.platform) {
7372
+ return promptlessProcessEnvOf(parent, platform);
7373
+ }
7374
+ function withoutAskpassNoise(text) {
7375
+ return text.split("\n").filter((line) => !/unable to read askpass response/i.test(line)).join("\n").trim();
7376
+ }
7377
+ //#endregion
7378
+ //#region src/shared/api/host/git-process.ts
7379
+ var gitSeam;
7380
+ function promptlessEnv() {
7381
+ return gitNoPromptProcessEnv();
7382
+ }
7383
+ function provideGitProcessSeam(seam) {
7384
+ gitSeam = seam;
7385
+ }
7386
+ function createGitRunner(seam) {
7387
+ return async (args, options = {}) => {
7388
+ const outcome = await seam.run({
7389
+ argv: ["git", ...args],
7390
+ intent: { kind: "host" },
7391
+ env: promptlessEnv(),
7392
+ ...options.cwd === void 0 ? {} : { workdir: options.cwd },
7393
+ ...options.timeoutMs === void 0 ? {} : { timeoutMs: options.timeoutMs }
7394
+ });
7395
+ return {
7396
+ exitCode: outcome.exitCode ?? null,
7397
+ stdout: outcome.stdout,
7398
+ stderr: outcome.stderr,
7399
+ timedOut: outcome.timedOut
7400
+ };
7401
+ };
7402
+ }
7403
+ var runGitProcess = async (args, options = {}) => {
7404
+ if (gitSeam === void 0) throw new Error("git 命令还没接上执行缝(宿主域)—— 这个进程里没有可用的 shell 服务。");
7405
+ return createGitRunner(gitSeam)(args, options);
7406
+ };
7407
+ //#endregion
7408
+ //#region src/shared/api/host/shipped-adapter-set.ts
7409
+ function shippedAdapterSetSpec(moduleUrl) {
7410
+ const spec = JSON.parse(readFileSync(new URL("../../package.json", moduleUrl), "utf8")).workbench?.adapterSet;
7411
+ if (typeof spec !== "string" || spec.trim() === "") throw new Error("package.json 没写 workbench.adapterSet:外壳不知道该装哪份适配器集合");
7412
+ return spec.trim();
7413
+ }
7414
+ async function shippedAdapterSet(ctx) {
7415
+ const spec = shippedAdapterSetSpec(import.meta.url);
7416
+ const factory = (await import(spec)).createAdapterSet;
7417
+ if (typeof factory !== "function") throw new Error(`随包的适配器集合 ${spec} 没导出 createAdapterSet(ctx)`);
7418
+ return await factory(ctx);
7419
+ }
7420
+ //#endregion
6461
7421
  //#region ../shared/dist/contract/protocol/dsh-plugins.js
6462
7422
  var DSH_PLUGINS_ROUTE = "dsh-plugins";
6463
7423
  //#endregion
@@ -6481,13 +7441,11 @@ function isFollowUpDismissed(runtimeRoot, id) {
6481
7441
  return Object.prototype.hasOwnProperty.call(readDismissed(runtimeRoot), id);
6482
7442
  }
6483
7443
  function dismissFollowUp(runtimeRoot, id) {
6484
- const file = fileOf$1(runtimeRoot);
6485
- mkdirSync(dirname(file), { recursive: true });
6486
7444
  const dismissed = {
6487
7445
  ...readDismissed(runtimeRoot),
6488
- [id]: { at: (/* @__PURE__ */ new Date()).toISOString() }
7446
+ [id]: { at: nowIso() }
6489
7447
  };
6490
- writeFileAtomic.sync(file, `${JSON.stringify({ dismissed }, null, 2)}\n`);
7448
+ writeJsonFile(fileOf$1(runtimeRoot), { dismissed });
6491
7449
  }
6492
7450
  //#endregion
6493
7451
  //#region src/shared/api/host/migration-follow-up.ts
@@ -6609,7 +7567,7 @@ function titleOf(raw) {
6609
7567
  }
6610
7568
  async function latestIfReleaseReady(supply, input) {
6611
7569
  const missing = await supply.missingReleaseParts({
6612
- shell: input.shell,
7570
+ seam: input.seam,
6613
7571
  name: input.pkg,
6614
7572
  registry: input.registry,
6615
7573
  version: input.version,
@@ -6630,18 +7588,18 @@ async function workbenchVersion(supply, input) {
6630
7588
  if (read === void 0) return void 0;
6631
7589
  const spec = dependencyReader(input.dir)(PLUGIN_ID);
6632
7590
  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,
7591
+ const seam = input.seam;
7592
+ const queried = read.local === true || !input.check || seam === void 0 || registry === void 0 ? void 0 : await supply.latestVersion({
7593
+ seam,
6636
7594
  name: read.pkg,
6637
7595
  registry,
6638
7596
  force: input.force
6639
7597
  });
6640
- const latest = queried === void 0 || shell === void 0 || registry === void 0 || !supply.isNewerVersion(queried, read.version) ? void 0 : await latestIfReleaseReady(supply, {
7598
+ const latest = queried === void 0 || seam === void 0 || registry === void 0 || !supply.isNewerVersion(queried, read.version) ? void 0 : await latestIfReleaseReady(supply, {
6641
7599
  pkg: read.pkg,
6642
7600
  version: queried,
6643
7601
  registry,
6644
- shell,
7602
+ seam,
6645
7603
  force: input.force,
6646
7604
  logger: input.logger
6647
7605
  });
@@ -6656,7 +7614,7 @@ async function workbenchVersion(supply, input) {
6656
7614
  ...registry === void 0 || read.local === true ? {} : { registry }
6657
7615
  };
6658
7616
  }
6659
- function moduleVersionsHandler(ctx, shell, running, selfUpdate) {
7617
+ function moduleVersionsHandler(ctx, seam, running, selfUpdate) {
6660
7618
  return async (search) => {
6661
7619
  const supply = supplyPortOf(ctx);
6662
7620
  const manager = supply.readBundleManager(ctx);
@@ -6676,8 +7634,8 @@ function moduleVersionsHandler(ctx, shell, running, selfUpdate) {
6676
7634
  const fromRegistry = module.fromRegistry !== false;
6677
7635
  const registry = fromRegistry ? module.registry ?? fallback : void 0;
6678
7636
  const spec = dependencyOf(module.pkg);
6679
- const latest = local || !fromRegistry || !check || shell === void 0 || registry === void 0 ? void 0 : await supply.latestVersion({
6680
- shell,
7637
+ const latest = local || !fromRegistry || !check || seam === void 0 || registry === void 0 ? void 0 : await supply.latestVersion({
7638
+ seam,
6681
7639
  name: module.pkg,
6682
7640
  registry,
6683
7641
  force
@@ -6686,8 +7644,8 @@ function moduleVersionsHandler(ctx, shell, running, selfUpdate) {
6686
7644
  const declared = raw === void 0 ? [] : declarationProblemsOf(module.pkg, raw);
6687
7645
  const title = titleOf(raw);
6688
7646
  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,
7647
+ const latestRaw = latest === void 0 || latest === module.version || seam === void 0 || registry === void 0 ? void 0 : await supply.registryDeclarationOf({
7648
+ seam,
6691
7649
  name: module.pkg,
6692
7650
  registry,
6693
7651
  force
@@ -6727,7 +7685,7 @@ function moduleVersionsHandler(ctx, shell, running, selfUpdate) {
6727
7685
  });
6728
7686
  const workbench = await workbenchVersion(supply, {
6729
7687
  dir,
6730
- shell,
7688
+ seam,
6731
7689
  fallback,
6732
7690
  force,
6733
7691
  check,
@@ -6755,23 +7713,23 @@ async function declarationRefusal(supply, spec, inspection, ctx, manager) {
6755
7713
  if (inspection.status !== "accepted" || inspection.name === void 0) return void 0;
6756
7714
  if (inspection.kind === "path") return refusedInspection(supply.declarationRefusalOf(inspection.name, supply.pathDeclarationOf(spec)));
6757
7715
  if (inspection.kind !== "registry") return void 0;
6758
- const shell = ctx.get("shell");
6759
- if (shell === void 0) return void 0;
7716
+ const seam = seamOf(ctx);
7717
+ if (seam === void 0) return void 0;
6760
7718
  const registry = typeof inspection.registry === "string" ? inspection.registry : (await manager.registries())?.resolved ?? void 0;
6761
7719
  if (registry === void 0) return void 0;
6762
7720
  const raw = await supply.registryDeclarationOf({
6763
- shell,
7721
+ seam,
6764
7722
  name: inspection.name,
6765
7723
  registry
6766
7724
  });
6767
7725
  return refusedInspection(supply.declarationRefusalOf(inspection.name, raw));
6768
7726
  }
6769
7727
  async function packageOverride(supply, spec, inspection, ctx, manager, asked) {
6770
- const shell = ctx.get("shell");
7728
+ const seam = seamOf(ctx);
6771
7729
  const registry = (inspection.status === "accepted" && typeof inspection.registry === "string" ? inspection.registry : void 0) ?? (typeof asked === "string" ? asked : (await manager.registries())?.resolved ?? void 0);
6772
7730
  const route = await supply.packageRouteOf({
6773
7731
  spec,
6774
- ...shell === void 0 ? {} : { shell },
7732
+ ...seam === void 0 ? {} : { seam },
6775
7733
  ...registry === void 0 ? {} : { registry }
6776
7734
  });
6777
7735
  if (route !== void 0 && route.kind === "runtime-plugin") {
@@ -6806,76 +7764,25 @@ function bundleInspectHandler(ctx) {
6806
7764
  if (manager === void 0) return {
6807
7765
  ok: false,
6808
7766
  error: {
6809
- code: "workbench/no-bundle-manager",
6810
- message: "这个 profile 里没有插件管理器,装不了模块。"
6811
- }
6812
- };
6813
- const asked = registryOf(search);
6814
- const inspection = await manager.inspect(spec, asked);
6815
- if (inspection === null) return {
6816
- ok: false,
6817
- error: {
6818
- code: "workbench/bad-inspection",
6819
- message: "插件管理器回的预检结果看不懂。"
6820
- }
6821
- };
6822
- return {
6823
- ok: true,
6824
- inspection: await packageOverride(supply, spec, inspection, ctx, manager, asked) ?? await declarationRefusal(supply, spec, inspection, ctx, manager) ?? inspection
6825
- };
6826
- };
6827
- }
6828
- //#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)
7767
+ code: "workbench/no-bundle-manager",
7768
+ message: "这个 profile 里没有插件管理器,装不了模块。"
7769
+ }
7770
+ };
7771
+ const asked = registryOf(search);
7772
+ const inspection = await manager.inspect(spec, asked);
7773
+ if (inspection === null) return {
7774
+ ok: false,
7775
+ error: {
7776
+ code: "workbench/bad-inspection",
7777
+ message: "插件管理器回的预检结果看不懂。"
7778
+ }
7779
+ };
7780
+ return {
7781
+ ok: true,
7782
+ inspection: await packageOverride(supply, spec, inspection, ctx, manager, asked) ?? await declarationRefusal(supply, spec, inspection, ctx, manager) ?? inspection
7783
+ };
6848
7784
  };
6849
- if (platform === "win32") {
6850
- delete env.GIT_ASKPASS;
6851
- delete env.SSH_ASKPASS;
6852
- }
6853
- return env;
6854
7785
  }
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
7786
  //#endregion
6880
7787
  //#region src/shared/api/host/git-probe.ts
6881
7788
  var PROBE_TIMEOUT_MS = 1e4;
@@ -6910,10 +7817,10 @@ var PROBE_CACHE_MS = 6e4;
6910
7817
  var probeCache = /* @__PURE__ */ new Map();
6911
7818
  async function probeGitAddressCached(url, options = {}) {
6912
7819
  const hit = probeCache.get(url);
6913
- if (hit !== void 0 && Date.now() - hit.at < PROBE_CACHE_MS) return hit.payload;
7820
+ if (hit !== void 0 && now() - hit.at < PROBE_CACHE_MS) return hit.payload;
6914
7821
  const payload = await probeGitAddress(url, options);
6915
7822
  probeCache.set(url, {
6916
- at: Date.now(),
7823
+ at: now(),
6917
7824
  payload
6918
7825
  });
6919
7826
  return payload;
@@ -6947,18 +7854,15 @@ function createHostRoutes(input) {
6947
7854
  [BUNDLE_REGISTRIES_ROUTE]: bundleRegistriesHandler(ctx),
6948
7855
  [BUNDLE_INSPECT_ROUTE]: bundleInspectHandler(ctx),
6949
7856
  [MARKET_GIT_PROBE_ROUTE]: gitProbeHandler(),
6950
- [MODULE_VERSIONS_ROUTE]: moduleVersionsHandler(ctx, ctx.get("shell"), running, () => profile.selfUpdateView(dshHome(), basename(profile.dirOf(ctx) ?? "")))
7857
+ [MODULE_VERSIONS_ROUTE]: moduleVersionsHandler(ctx, seamOf(ctx), running, () => profile.selfUpdateView(dshHome(), basename(profile.dirOf(ctx) ?? "")))
6951
7858
  };
6952
7859
  }
6953
- var WORKSHOP_RELATIVE = "workbench/diy";
6954
7860
  var WORKSHOP_TITLE = "工作台DIY";
6955
7861
  //#endregion
6956
7862
  //#region src/shared/api/host/diy.ts
6957
- function workshopPathOf(ctx) {
6958
- const homePath = ctx.get("dshHomePath");
6959
- if (typeof homePath !== "function") return null;
7863
+ function workshopPathOf(_ctx) {
6960
7864
  try {
6961
- return homePath(WORKSHOP_RELATIVE);
7865
+ return join(workbenchRoot(), "diy");
6962
7866
  } catch {
6963
7867
  return null;
6964
7868
  }
@@ -7135,7 +8039,7 @@ var panelsUnit = {
7135
8039
  }))],
7136
8040
  host: {
7137
8041
  inject: [],
7138
- optional: ["tools", "shell"]
8042
+ optional: ["tools", "workbenchSeam"]
7139
8043
  }
7140
8044
  };
7141
8045
  //#endregion
@@ -8015,8 +8919,10 @@ function toolchainBinOf(root) {
8015
8919
  function queryCommandOf(bin, only) {
8016
8920
  return `${quoteShellArg(bin)} check --only ${only} --json`;
8017
8921
  }
8018
- async function runSurfaceQuery(shell, bin, only, root, fallbackBin) {
8019
- const result = await runShell(shell, queryCommandOf(bin, only), {
8922
+ async function runSurfaceQuery(seam, bin, only, root, fallbackBin) {
8923
+ const result = await seam.run({
8924
+ command: queryCommandOf(bin, only),
8925
+ intent: noWrite(),
8020
8926
  workdir: root,
8021
8927
  timeoutMs: SURFACE_TIMEOUT_MS
8022
8928
  });
@@ -8024,7 +8930,9 @@ async function runSurfaceQuery(shell, bin, only, root, fallbackBin) {
8024
8930
  if (parsed !== null) return withoutProblems(parsed);
8025
8931
  const retry = fallbackBin === void 0 || fallbackBin === bin ? void 0 : fallbackBin;
8026
8932
  if (retry !== void 0) {
8027
- const secondParsed = jsonOf$1((await runShell(shell, queryCommandOf(retry, only), {
8933
+ const secondParsed = jsonOf$1((await seam.run({
8934
+ command: queryCommandOf(retry, only),
8935
+ intent: noWrite(),
8028
8936
  workdir: root,
8029
8937
  timeoutMs: SURFACE_TIMEOUT_MS
8030
8938
  })).stdout);
@@ -8790,11 +9698,11 @@ function componentsTool(ctx) {
8790
9698
  })).find((one) => one.bin !== null) ?? null;
8791
9699
  const bin = local?.bin ?? shellBin;
8792
9700
  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 服务,读不了组件清单");
9701
+ const seam = seamOf(ctx);
9702
+ if (seam === void 0) throw new Error("这台机器没给 shell 服务,读不了组件清单");
8795
9703
  const workdir = local?.dir ?? shellRoot ?? root;
8796
9704
  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)]);
9705
+ 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
9706
  const query = {
8799
9707
  ...primitives === void 0 ? {} : { primitives },
8800
9708
  ...kit === void 0 ? {} : { kit }
@@ -8825,12 +9733,10 @@ function registerComponentsTool(ctx, tools) {
8825
9733
  }
8826
9734
  //#endregion
8827
9735
  //#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 服务 —— 落不了截图与录制(取证目录由它给)");
9736
+ function artifactsDirOf(root) {
8831
9737
  const name = basename(root).replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "") || "workspace";
8832
9738
  const hash = createHash("sha1").update(root).digest("hex").slice(0, 8);
8833
- return homePath(join("workbench", "artifacts", `${name}-${hash}`));
9739
+ return join(workbenchRoot(), "artifacts", `${name}-${hash}`);
8834
9740
  }
8835
9741
  function ensureDirOf(dir) {
8836
9742
  mkdirSync(dir, { recursive: true });
@@ -8877,8 +9783,12 @@ var LOOK_TIMEOUT_MS = 3e4;
8877
9783
  function plainLine(command, argv) {
8878
9784
  return [command, ...argv].map(quoteShellArg).join(" ");
8879
9785
  }
8880
- async function runBrowserStep(shell, command, argv, timeoutMs = LOOK_TIMEOUT_MS, line = plainLine) {
8881
- const result = await runShell(shell, line(command, argv), { timeoutMs });
9786
+ async function runBrowserStep(seam, command, argv, timeoutMs = LOOK_TIMEOUT_MS, line = plainLine, intent = productWrite()) {
9787
+ const result = await seam.run({
9788
+ command: line(command, argv),
9789
+ intent,
9790
+ timeoutMs
9791
+ });
8882
9792
  return {
8883
9793
  ok: result.exitCode === 0 && !result.timedOut,
8884
9794
  stdout: result.stdout.trim(),
@@ -8902,8 +9812,8 @@ function jsonOf(raw) {
8902
9812
  function textOf$2(value) {
8903
9813
  return typeof value === "string" ? value.trim() : "";
8904
9814
  }
8905
- async function bindCurrentTab(shell, command, session, timeoutMs, line) {
8906
- const step = await runBrowserStep(shell, command, [
9815
+ async function bindCurrentTab(seam, command, session, timeoutMs, line) {
9816
+ const step = await runBrowserStep(seam, command, [
8907
9817
  "browser",
8908
9818
  session,
8909
9819
  "bind",
@@ -8934,8 +9844,8 @@ async function bindCurrentTab(shell, command, session, timeoutMs, line) {
8934
9844
  reason: ""
8935
9845
  };
8936
9846
  }
8937
- async function workbenchPageOf(shell, command, session) {
8938
- const parsed = jsonOf((await runBrowserStep(shell, command, [
9847
+ async function workbenchPageOf(seam, command, session) {
9848
+ const parsed = jsonOf((await runBrowserStep(seam, command, [
8939
9849
  "browser",
8940
9850
  session,
8941
9851
  "eval",
@@ -8963,22 +9873,22 @@ async function workbenchPageOf(shell, command, session) {
8963
9873
  shown: parsed["shown"] === true
8964
9874
  };
8965
9875
  }
8966
- async function readyPageOf(shell, command, session) {
8967
- const already = await workbenchPageOf(shell, command, session);
9876
+ async function readyPageOf(seam, command, session) {
9877
+ const already = await workbenchPageOf(seam, command, session);
8968
9878
  if (already.ok) return {
8969
9879
  ok: true,
8970
9880
  bound: false,
8971
9881
  page: already,
8972
9882
  reason: ""
8973
9883
  };
8974
- const bound = await bindCurrentTab(shell, command, session);
9884
+ const bound = await bindCurrentTab(seam, command, session);
8975
9885
  if (!bound.ok) return {
8976
9886
  ok: false,
8977
9887
  bound: false,
8978
9888
  page: already,
8979
9889
  reason: bound.reason
8980
9890
  };
8981
- const after = await workbenchPageOf(shell, command, session);
9891
+ const after = await workbenchPageOf(seam, command, session);
8982
9892
  return {
8983
9893
  ok: after.ok,
8984
9894
  bound: true,
@@ -9033,8 +9943,8 @@ async function pageAt(ctx, exec, raw, options = {}) {
9033
9943
  const pool = units.map((one) => one.unit);
9034
9944
  const wanted = asked === "" && pool.length === 1 ? pool[0] ?? "" : asked;
9035
9945
  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 服务,动不了浏览器");
9946
+ const seam = seamOf(ctx);
9947
+ if (seam === void 0) throw new Error("这台机器上没有 shell 服务,动不了浏览器");
9038
9948
  const held = ctx.get("workbenchDiyDebug")?.live(Date.now()) ?? null;
9039
9949
  const sessionId = sessionIdOf(exec);
9040
9950
  if (options.requirePact !== false) {
@@ -9044,7 +9954,7 @@ async function pageAt(ctx, exec, raw, options = {}) {
9044
9954
  }
9045
9955
  const command = cliCommandOf(ctx);
9046
9956
  const session = asText(args["session"]) || "workbench";
9047
- const handshake = await readyPageOf(shell, command, session);
9957
+ const handshake = await readyPageOf(seam, command, session);
9048
9958
  if (!handshake.ok && options.requireWorkbench !== false && !hostSurface().capabilities.pageDebug) throw new Error(desktopPageText(handshake.page));
9049
9959
  if (!handshake.ok && !handshake.bound) throw new Error(`连不上浏览器:${handshake.reason}。先让用户把工作台那一页切到前台(只有第一次要这样);还不行就跑一次运行时自检(doctor)看扩展连没连上。`);
9050
9960
  if (!handshake.ok && options.requireWorkbench !== false) {
@@ -9057,10 +9967,10 @@ async function pageAt(ctx, exec, raw, options = {}) {
9057
9967
  if (options.requireBoard !== false && (board === "other" || board === "hidden")) throw new Error(boardGateText(situation, wanted, handshake.page, title));
9058
9968
  return {
9059
9969
  root,
9060
- artifactsDir: artifactsDirOf(ctx, root),
9970
+ artifactsDir: artifactsDirOf(root),
9061
9971
  unit: wanted,
9062
9972
  args,
9063
- shell,
9973
+ seam,
9064
9974
  command,
9065
9975
  session,
9066
9976
  ...surfaceOf(ctx)?.background === void 0 ? {} : { line: surfaceOf(ctx)?.background },
@@ -9220,8 +10130,8 @@ function registerDebugTools(ctx, tools, store) {
9220
10130
  //#endregion
9221
10131
  //#region src/modules/panels/model/diy-browser-act.ts
9222
10132
  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, [
10133
+ async function actBrowser(seam, command, session, argv, timeoutMs = ACT_TIMEOUT_MS, line) {
10134
+ const step = await runBrowserStep(seam, command, [
9225
10135
  "browser",
9226
10136
  session,
9227
10137
  ...argv,
@@ -9244,20 +10154,6 @@ function actErrorOf(step) {
9244
10154
  return step.stderr || step.stdout || "运行时报了个错";
9245
10155
  }
9246
10156
  var SWEEP_TIMEOUT_MS = 4e3;
9247
- async function stopNetworkExact(shell, file, timeoutMs = SWEEP_TIMEOUT_MS) {
9248
- const pidFile = quoteShellArg(`${file}.pid`);
9249
- const reaperFile = quoteShellArg(`${file}.reaper`);
9250
- return (await runShell(shell, [
9251
- `p="$(cat ${pidFile} 2>/dev/null)"`,
9252
- `r="$(cat ${reaperFile} 2>/dev/null)"`,
9253
- `[ -n "$p" ] && pkill -P "$p" 2>/dev/null`,
9254
- `[ -n "$p" ] && kill "$p" 2>/dev/null`,
9255
- `[ -n "$r" ] && pkill -P "$r" 2>/dev/null`,
9256
- `[ -n "$r" ] && kill "$r" 2>/dev/null`,
9257
- `if [ "$(cat ${pidFile} 2>/dev/null)" = "$p" ]; then rm -f ${pidFile} ${reaperFile}; fi`,
9258
- "true"
9259
- ].join("; "), { timeoutMs })).exitCode === 0;
9260
- }
9261
10157
  //#endregion
9262
10158
  //#region src/modules/panels/model/diy-logs.ts
9263
10159
  var LOGS_KEY = "__dshDiyLogs";
@@ -9379,7 +10275,7 @@ function registerLogTools(ctx, tools) {
9379
10275
  });
9380
10276
  return {
9381
10277
  page,
9382
- step: await actBrowser(page.shell, page.command, page.session, ["eval", script])
10278
+ step: await actBrowser(page.seam, page.command, page.session, ["eval", script])
9383
10279
  };
9384
10280
  }
9385
10281
  const watchTool = {
@@ -9632,7 +10528,7 @@ function cropPngFile(input, output, rect) {
9632
10528
  reason: cropped.reason
9633
10529
  };
9634
10530
  try {
9635
- writeFileSync(output, cropped.bytes);
10531
+ writeBytesFile(output, cropped.bytes);
9636
10532
  } catch (error) {
9637
10533
  return {
9638
10534
  ok: false,
@@ -9647,6 +10543,44 @@ function cropPngFile(input, output, rect) {
9647
10543
  };
9648
10544
  }
9649
10545
  //#endregion
10546
+ //#region src/modules/panels/model/diy-browser-clip.ts
10547
+ function finite(value) {
10548
+ return typeof value === "number" && Number.isFinite(value) ? value : void 0;
10549
+ }
10550
+ function clipOf(value) {
10551
+ if (typeof value !== "object" || value === null) return void 0;
10552
+ const raw = value;
10553
+ const x = finite(raw["x"]);
10554
+ const y = finite(raw["y"]);
10555
+ const width = finite(raw["width"]);
10556
+ const height = finite(raw["height"]);
10557
+ if (x === void 0 || y === void 0 || width === void 0 || height === void 0) return void 0;
10558
+ if (x < 0 || y < 0 || width <= 0 || height <= 0) return void 0;
10559
+ return {
10560
+ x,
10561
+ y,
10562
+ width,
10563
+ height
10564
+ };
10565
+ }
10566
+ function deviceClipRect(clip, dpr) {
10567
+ const scale = Number.isFinite(dpr) && dpr > 0 ? dpr : 1;
10568
+ return {
10569
+ x: Math.max(0, Math.round(clip.x * scale)),
10570
+ y: Math.max(0, Math.round(clip.y * scale)),
10571
+ width: Math.max(1, Math.round(clip.width * scale)),
10572
+ height: Math.max(1, Math.round(clip.height * scale))
10573
+ };
10574
+ }
10575
+ function clipNote(clip, viewport) {
10576
+ if (viewport.width <= 0 || viewport.height <= 0) return "没读到视口尺寸(那条量 dpr / innerWidth 的 eval 没回来)—— 给的是整屏图,裁不了";
10577
+ if (clip.x + clip.width <= viewport.width && clip.y + clip.height <= viewport.height && viewport.width > 0) return "";
10578
+ return `要裁的那块 ${String(clip.x)},${String(clip.y)} ${String(clip.width)}×${String(clip.height)} 不在视口里(视口 ${String(viewport.width)}×${String(viewport.height)})—— 给的是整屏图`;
10579
+ }
10580
+ function defaultShotPath(dir, unit, stamp) {
10581
+ return join(dir, `${unit === "" ? "page" : unit}-${String(stamp)}.png`);
10582
+ }
10583
+ //#endregion
9650
10584
  //#region src/modules/panels/model/diy-schema.ts
9651
10585
  var UNIT_PROP = {
9652
10586
  type: "string",
@@ -9710,7 +10644,7 @@ function wrapFirst(argv, card) {
9710
10644
  if (first === void 0 || first.startsWith("-")) return [...argv];
9711
10645
  return [evalWrapped(first, card), ...argv.slice(1)];
9712
10646
  }
9713
- function argvOf(row, raw, unit) {
10647
+ function argvOf$1(row, raw, unit) {
9714
10648
  const card = cardSelectorIn(unit);
9715
10649
  const declared = stringsOf$1(raw["args"]);
9716
10650
  const tokens = withToken(row.evalInCard === true && asText(raw["in"]) === "card" ? wrapFirst(declared, card) : declared, card);
@@ -9718,42 +10652,6 @@ function argvOf(row, raw, unit) {
9718
10652
  if (row.css.kind === "flag") return prefixValue(tokens, row.css.flag, card);
9719
10653
  return prefixPositional(tokens, row.css.at, card, row.css.onlyWhenFirst);
9720
10654
  }
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
10655
  var BROWSER_TOOLS = [
9758
10656
  {
9759
10657
  tool: DIY_STATE_TOOL,
@@ -9934,7 +10832,7 @@ function parametersOf(row, command) {
9934
10832
  };
9935
10833
  }
9936
10834
  async function viewportOf(page) {
9937
- const parsed = jsonOf((await runBrowserStep(page.shell, page.command, [
10835
+ const parsed = jsonOf((await runBrowserStep(page.seam, page.command, [
9938
10836
  "browser",
9939
10837
  page.session,
9940
10838
  "eval",
@@ -9960,8 +10858,8 @@ function hostOf$1(url) {
9960
10858
  return "";
9961
10859
  }
9962
10860
  }
9963
- async function onSiteOnce(shell, command, site, line) {
9964
- const step = await runBrowserStep(shell, command, [
10861
+ async function onSiteOnce(seam, command, site, line) {
10862
+ const step = await runBrowserStep(seam, command, [
9965
10863
  "browser",
9966
10864
  BROWSER_READ_SESSION,
9967
10865
  "get",
@@ -9971,7 +10869,7 @@ async function onSiteOnce(shell, command, site, line) {
9971
10869
  ], LOOK_TIMEOUT_MS, line);
9972
10870
  const want = hostOf$1(site);
9973
10871
  if (want === "" || hostOf$1(step.stdout.trim()) === want) return false;
9974
- await runBrowserStep(shell, command, [
10872
+ await runBrowserStep(seam, command, [
9975
10873
  "browser",
9976
10874
  BROWSER_READ_SESSION,
9977
10875
  "open",
@@ -9994,15 +10892,15 @@ function readRefusalOf(row, raw) {
9994
10892
  async function runReadTool(row, ctx, raw, site) {
9995
10893
  const refused = readRefusalOf(row, raw);
9996
10894
  if (refused !== "") throw new Error(refused);
9997
- const shell = ctx.get("shell");
9998
- if (shell === void 0) throw new Error("这台机器上没有 shell 服务,驱动不了浏览器");
10895
+ const seam = seamOf(ctx);
10896
+ if (seam === void 0) throw new Error("这台机器上没有 shell 服务,驱动不了浏览器");
9999
10897
  const command = cliCommandOf(ctx);
10000
10898
  const requested = asNumber(raw["timeoutMs"]);
10001
10899
  const timeoutMs = requested !== void 0 && requested > 0 ? requested : DEFAULT_TIMEOUT_MS$4;
10002
10900
  const argv = stringsOf$1(raw["args"]);
10003
10901
  const line = surfaceOf(ctx)?.background;
10004
- const opened = await onSiteOnce(shell, command, site, line);
10005
- const step = await runBrowserStep(shell, command, [
10902
+ const opened = await onSiteOnce(seam, command, site, line);
10903
+ const step = await runBrowserStep(seam, command, [
10006
10904
  "browser",
10007
10905
  BROWSER_READ_SESSION,
10008
10906
  ...row.verb,
@@ -10032,27 +10930,31 @@ async function runBrowserTool(row, ctx, raw, exec) {
10032
10930
  if (needsCardOf(row, page.args) && page.unit === "") throw new Error("要卡作用域(scope=card / $card / in=card)就得给一个 unit —— 这个工作区里不止一个模块时,我不知道你说哪一张卡。");
10033
10931
  const requested = asNumber(page.args["timeoutMs"]);
10034
10932
  const timeoutMs = requested !== void 0 && requested > 0 ? requested : DEFAULT_TIMEOUT_MS$4;
10035
- const argv = argvOf(row, page.args, page.unit);
10933
+ const argv = argvOf$1(row, page.args, page.unit);
10036
10934
  const clip = row.clip === true ? clipOf(page.args["clip"]) : void 0;
10037
10935
  const clipAsked = row.clip === true && page.args["clip"] !== void 0;
10038
10936
  let file = "";
10039
10937
  let finalArgv = argv;
10938
+ let intent = productWrite();
10040
10939
  if (row.clip === true) {
10041
10940
  const given = argv[0] !== void 0 && !argv[0].startsWith("-") ? argv[0] : "";
10042
10941
  if (given === "") {
10043
10942
  file = defaultShotPath(page.artifactsDir, page.unit, Date.now());
10044
10943
  finalArgv = [file, ...argv];
10045
10944
  ensureDirOf(dirname(file));
10046
- } else file = given;
10945
+ } else {
10946
+ file = given;
10947
+ intent = pathsWrite([file]);
10948
+ }
10047
10949
  }
10048
- const step = await runBrowserStep(page.shell, page.command, [
10950
+ const step = await runBrowserStep(page.seam, page.command, [
10049
10951
  "browser",
10050
10952
  page.session,
10051
10953
  ...row.verb,
10052
10954
  ...finalArgv,
10053
10955
  "--window",
10054
10956
  "background"
10055
- ], timeoutMs, page.line);
10957
+ ], timeoutMs, page.line, intent);
10056
10958
  const shot = {};
10057
10959
  if (row.clip === true) {
10058
10960
  shot["file"] = step.ok ? file : "";
@@ -10138,11 +11040,15 @@ function specTool(ctx) {
10138
11040
  const args = asRecord$1(raw);
10139
11041
  const argv = specArgvOf(asText(args["command"]));
10140
11042
  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 服务,问不了命令的形状");
11043
+ const seam = seamOf(ctx);
11044
+ if (seam === void 0) throw new Error("这台机器上没有 shell 服务,问不了命令的形状");
10143
11045
  const asked = asNumber(args["timeoutMs"]);
10144
11046
  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 });
11047
+ const result = await seam.run({
11048
+ command: (surfaceOf(ctx)?.background ?? plainLine)(cliCommandOf(ctx), argv),
11049
+ intent: productWrite(),
11050
+ timeoutMs
11051
+ });
10146
11052
  const spec = jsonOf(result.stdout);
10147
11053
  return plainJson({
10148
11054
  ok: result.exitCode === 0 && !result.timedOut,
@@ -10160,8 +11066,169 @@ function registerSpecTool(ctx, tools) {
10160
11066
  return [tools.register(specTool(ctx))];
10161
11067
  }
10162
11068
  //#endregion
11069
+ //#region src/shared/api/host/diy-release-staging.ts
11070
+ var STAGING_ROOT = "dsh-wb-release";
11071
+ function stagingDirOf(unit) {
11072
+ return join(tmpdir(), STAGING_ROOT, unit.replace(/[^A-Za-z0-9._-]+/g, "-"));
11073
+ }
11074
+ function journalFileOf(staging, unit) {
11075
+ return join(staging, "releases", `${unit}.json`);
11076
+ }
11077
+ function prepareStaging(unit, resume) {
11078
+ const staging = stagingDirOf(unit);
11079
+ if (!resume) rmSync(staging, {
11080
+ recursive: true,
11081
+ force: true
11082
+ });
11083
+ mkdirSync(staging, { recursive: true });
11084
+ if (resume) seedJournal(staging, unit);
11085
+ return staging;
11086
+ }
11087
+ function seedJournal(staging, unit) {
11088
+ const to = journalFileOf(staging, unit);
11089
+ if (existsSync(to)) return;
11090
+ const from = join(workbenchRoot(), "releases", `${unit}.json`);
11091
+ if (!existsSync(from)) return;
11092
+ mkdirSync(dirname(to), { recursive: true });
11093
+ copyFileSync(from, to);
11094
+ }
11095
+ function cleanupStaging(unit) {
11096
+ rmSync(stagingDirOf(unit), {
11097
+ recursive: true,
11098
+ force: true
11099
+ });
11100
+ }
11101
+ function adoptTarballFile(file, unit) {
11102
+ if (file === "" || !existsSync(file)) return void 0;
11103
+ const dest = join(workbenchRoot(), "artifacts", unit, basename(file));
11104
+ if (dest === file) return dest;
11105
+ mkdirSync(dirname(dest), { recursive: true });
11106
+ copyFileSync(file, dest);
11107
+ rmSync(file, { force: true });
11108
+ return dest;
11109
+ }
11110
+ function mirrorJournal(staging, unit) {
11111
+ const from = journalFileOf(staging, unit);
11112
+ if (!existsSync(from)) return;
11113
+ const to = join(workbenchRoot(), "releases", `${unit}.json`);
11114
+ mkdirSync(dirname(to), { recursive: true });
11115
+ copyFileSync(from, to);
11116
+ }
11117
+ //#endregion
10163
11118
  //#region src/modules/panels/model/diy-publish.ts
10164
11119
  var PUBLISH_TIMEOUT_MS = 18e5;
11120
+ function adoptTarball(payload, unit) {
11121
+ const file = asText(payload["file"]);
11122
+ const dest = adoptTarballFile(file, unit);
11123
+ if (dest === void 0) return payload;
11124
+ const install = asText(payload["install"]);
11125
+ return {
11126
+ ...payload,
11127
+ file: dest,
11128
+ ...install === "" ? {} : { install: install.split(file).join(dest) }
11129
+ };
11130
+ }
11131
+ function needsUserPayload(payload) {
11132
+ const needs = asRecord$1(payload["needsUser"]);
11133
+ const stepText = asText(needs["text"]);
11134
+ const command = asText(needs["command"]);
11135
+ return plainJson({
11136
+ ...payload,
11137
+ text: [
11138
+ `还差一步:${stepText}`,
11139
+ ...command === "" ? [] : [`命令:${command}`],
11140
+ "做完跟我说「好了」,我接着发。"
11141
+ ].join("\n")
11142
+ });
11143
+ }
11144
+ function resumablePayload(payload, unit) {
11145
+ const resumeInfo = asRecord$1(payload["resume"]);
11146
+ return plainJson({
11147
+ ...payload,
11148
+ text: [`发布中断在第 ${asText(resumeInfo["phase"])} 步:${asText(payload["text"])}`, `再调一次 workbench_diy_publish(unit=${unit}, resume=true)就能接着发。`].join("\n")
11149
+ });
11150
+ }
11151
+ function outcomeOf$1(result, unit) {
11152
+ const payload = jsonOf(result.stdout);
11153
+ if (result.exitCode === 3 && payload !== void 0) return {
11154
+ kind: "needs-user",
11155
+ payload: needsUserPayload(payload)
11156
+ };
11157
+ if (result.exitCode === 4 && payload !== void 0) return {
11158
+ kind: "resumable",
11159
+ payload: resumablePayload(payload, unit)
11160
+ };
11161
+ if (result.exitCode !== 0 || payload === void 0) {
11162
+ const tail = `${result.stderr}\n${result.stdout}`.trim().split("\n").slice(-6).join(" | ");
11163
+ return {
11164
+ kind: "failed",
11165
+ error: /* @__PURE__ */ new Error(`发布没走通(exit ${String(result.exitCode)})${tail === "" ? "" : `:${tail}`}`)
11166
+ };
11167
+ }
11168
+ return {
11169
+ kind: "done",
11170
+ payload
11171
+ };
11172
+ }
11173
+ function publishProblemOf(payload) {
11174
+ if (payload["ok"] === false) {
11175
+ const reason = asText(payload["text"]);
11176
+ return `市场索引这一步回的是失败,这次没发出去${reason === "" ? "" : `:${reason}`}`;
11177
+ }
11178
+ if (asText(payload["target"]) !== "market") return void 0;
11179
+ if (asText(asRecord$1(payload["market"])["url"]) !== "" && asText(payload["install"]) !== "") return void 0;
11180
+ return "市场索引这一步没给出市场地址或安装命令 —— 这次没发出去。重发一次;再这样先把本地索引镜像同步到 origin 再发。";
11181
+ }
11182
+ function successPayloadOf(payload, found, options) {
11183
+ const name = asText(payload["name"]);
11184
+ const released = asText(payload["version"]);
11185
+ const registry = asText(payload["registry"]);
11186
+ const file = asText(payload["file"]);
11187
+ const install = asText(payload["install"]);
11188
+ const git = asRecord$1(payload["git"]);
11189
+ const targetOfPayload = asText(payload["target"]);
11190
+ const text = options.verify ? [
11191
+ `已核过 ${name}@${released}`,
11192
+ `安装:${install}`,
11193
+ "没有重新发布。"
11194
+ ].join("\n") : payload["refresh"] === true ? [payload["refreshed"] === true ? `已刷新 ${name} 的市集元数据(版本 ${released} 不变)` : `${name} 的市集元数据本来就是这样,没动`, `市场索引:${asText(asRecord$1(payload["market"])["url"])}`].join("\n") : targetOfPayload === "tarball" ? [
11195
+ `安装包在 ${file}`,
11196
+ `本机装:${install}`,
11197
+ "文件可以从对话里带走,发给同事。"
11198
+ ].join("\n") : targetOfPayload === "git" ? [
11199
+ `已推到 ${asText(git["url"])}(tag ${asText(git["tag"])})`,
11200
+ `安装:${install}`,
11201
+ "已核过 tag 里的产物。"
11202
+ ].join("\n") : targetOfPayload === "market" ? [
11203
+ `已发到市场索引 ${asText(asRecord$1(payload["market"])["url"])}`,
11204
+ `安装:${install}`,
11205
+ "已从索引仓库侧核过产物。"
11206
+ ].join("\n") : targetOfPayload === "local" ? "没发,留在本机。" : [
11207
+ `已发布 ${name}@${released} → ${registry}`,
11208
+ `同事安装:${install}`,
11209
+ "包内容已经从源上核过(dist/host.js / client.js / cordis.patch.yml 都在)。"
11210
+ ].join("\n");
11211
+ const changelog = asRecord$1(payload["changelog"]);
11212
+ const changelogLine = asText(changelog["action"]) === "written" ? `更新日志:已写进 ${CHANGELOG_FILE} 的 ${asText(changelog["version"])}` : asText(changelog["action"]) === "kept" ? `更新日志:这一版已有(${asText(changelog["version"])}),没动` : "";
11213
+ const labelList = Array.isArray(payload["labels"]) ? payload["labels"].map((one) => asText(one)).filter((one) => one !== "") : [];
11214
+ const tail = [changelogLine, labelList.length === 0 || targetOfPayload !== "market" && payload["refresh"] !== true ? "" : `标签:${labelList.join(" · ")}`].filter((one) => one !== "").join("\n");
11215
+ return plainJson({
11216
+ ...payload,
11217
+ unit: found.unit,
11218
+ text: tail === "" ? text : `${text}\n${tail}`
11219
+ });
11220
+ }
11221
+ function withPhasePayloads(first, second) {
11222
+ const merged = {
11223
+ ...first,
11224
+ ...second
11225
+ };
11226
+ delete merged["stop"];
11227
+ if (merged["changelog"] === void 0 && first["changelog"] !== void 0) merged["changelog"] = first["changelog"];
11228
+ if (merged["changes"] === void 0 && first["changes"] !== void 0) merged["changes"] = first["changes"];
11229
+ if (merged["labels"] === void 0 && first["labels"] !== void 0) merged["labels"] = first["labels"];
11230
+ return merged;
11231
+ }
10165
11232
  function registerPublishTool(ctx, tools, deps) {
10166
11233
  const publishTool = {
10167
11234
  name: DIY_PUBLISH_TOOL,
@@ -10235,20 +11302,22 @@ function registerPublishTool(ctx, tools, deps) {
10235
11302
  async execute(raw, exec) {
10236
11303
  const { root } = await workspaceOf(ctx, exec);
10237
11304
  const found = deps.resolve(root, raw);
10238
- const shell = ctx.get("shell");
10239
- if (shell === void 0) throw new Error("这台机器上没有 shell 服务,发布跑不起来");
11305
+ const seam = seamOf(ctx);
11306
+ if (seam === void 0) throw new Error("这台机器上没有 shell 服务,发布跑不起来");
10240
11307
  const bin = toolchainBinOf(root) ?? toolchainBinOf(found.dir);
10241
11308
  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;
11309
+ const args = asRecord$1(raw);
11310
+ const target = asText(args["target"]);
11311
+ const source = asText(args["source"]);
11312
+ const version = asText(args["version"]);
11313
+ const license = asText(args["license"]);
11314
+ const changelogNote = asText(args["changelogNote"]);
11315
+ const changelogType = asText(args["changelogType"]);
11316
+ const labels = labelsOf(args["labels"]);
11317
+ let author = asText(args["author"]);
11318
+ const verify = args["verify"] === true;
11319
+ const resume = args["resume"] === true;
11320
+ const refresh = args["refresh"] === true;
10252
11321
  if (!verify && target !== "local" && author === "" && (deps.previewOf(found.unit)?.author ?? "") === "") author = await deps.inferredAuthorOf(found.unit) ?? "";
10253
11322
  const argv = [
10254
11323
  bin,
@@ -10268,70 +11337,49 @@ function registerPublishTool(ctx, tools, deps) {
10268
11337
  if (changelogNote !== "") argv.push("--changelog-note", changelogNote);
10269
11338
  if (changelogType !== "") argv.push("--changelog-type", changelogType);
10270
11339
  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(" "), {
11340
+ if (refresh) argv.push("--refresh");
11341
+ const run = async (extra, request) => seam.run({
11342
+ command: [...argv, ...extra].map((one) => quoteShellArg(one)).join(" "),
11343
+ intent: request.intent,
11344
+ ...request.session === void 0 ? {} : { session: request.session },
11345
+ ...request.env === void 0 ? {} : { env: request.env },
10273
11346
  workdir: root,
10274
11347
  timeoutMs: PUBLISH_TIMEOUT_MS
10275
11348
  });
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
- });
11349
+ if (verify || refresh) {
11350
+ const outcome = outcomeOf$1(await run([], { intent: productWrite() }), found.unit);
11351
+ if (outcome.kind === "failed") throw outcome.error;
11352
+ if (outcome.kind !== "done") return plainJson(outcome.payload);
11353
+ return plainJson(successPayloadOf(outcome.payload, found, { verify }));
10289
11354
  }
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
- });
11355
+ const staging = prepareStaging(found.unit, resume);
11356
+ const env = { DSH_WB_RELEASE_HOME: staging };
11357
+ const session = exec.agent?.session;
11358
+ const firstOutcome = outcomeOf$1(await run(["--workspace-only"], {
11359
+ intent: workspaceWrite(),
11360
+ ...session === void 0 ? {} : { session },
11361
+ env
11362
+ }), found.unit);
11363
+ mirrorJournal(staging, found.unit);
11364
+ if (firstOutcome.kind === "failed") throw firstOutcome.error;
11365
+ if (firstOutcome.kind !== "done") return plainJson(firstOutcome.payload);
11366
+ const first = firstOutcome.payload;
11367
+ if (first["stop"] !== "workspace-only" || asText(first["target"]) === "local") {
11368
+ cleanupStaging(found.unit);
11369
+ return plainJson(first);
10296
11370
  }
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}`}`);
10300
- }
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
- });
11371
+ const indexOutcome = outcomeOf$1(await run(["--index-only"], {
11372
+ intent: productWrite(),
11373
+ env
11374
+ }), found.unit);
11375
+ mirrorJournal(staging, found.unit);
11376
+ if (indexOutcome.kind === "failed") throw indexOutcome.error;
11377
+ if (indexOutcome.kind !== "done") return plainJson(indexOutcome.payload);
11378
+ const merged = adoptTarball(withPhasePayloads(first, indexOutcome.payload), found.unit);
11379
+ const problem = publishProblemOf(merged);
11380
+ if (problem !== void 0) throw new Error(problem);
11381
+ cleanupStaging(found.unit);
11382
+ return plainJson(successPayloadOf(merged, found, { verify: false }));
10335
11383
  }
10336
11384
  };
10337
11385
  return tools.register(publishTool);
@@ -10711,7 +11759,7 @@ function ageText(at, now) {
10711
11759
  function stampOf(at) {
10712
11760
  return `${dayjs(at).utc().format("HH:mm:ss")}Z`;
10713
11761
  }
10714
- function readText$1(read, now) {
11762
+ function readText$2(read, now) {
10715
11763
  const when = `${stampOf(read.at)} · ${ageText(read.at, now)}`;
10716
11764
  if (read.phase === "ready") return `${read.route} 读到了(${when})`;
10717
11765
  if (read.phase === "loading") return `${read.route} 正在读(${when})`;
@@ -10728,7 +11776,7 @@ function renderPanel(options) {
10728
11776
  const title = `—— ${row.unit}${row.name === "" ? "" : `(${row.name})`} · ${healthText(row.health)}`;
10729
11777
  const report = row.report;
10730
11778
  if (report === void 0) return [title, ` 没上报:卡片没渲染出来,或者用户那一页还没刷新到带这份补丁的版本`].join("\n");
10731
- const reads = report.cards.slice(0, 6).map((one) => ` 数据 · ${readText$1(one, now)}`);
11779
+ const reads = report.cards.slice(0, 6).map((one) => ` 数据 · ${readText$2(one, now)}`);
10732
11780
  const more = report.cards.length > 6 ? [` 数据 · …还有 ${String(report.cards.length - 6)} 条`] : [];
10733
11781
  const crash = report.crash === void 0 ? " 崩过:没有" : ` 崩过:是 —— ${report.crash.reason}(${stampOf(report.crash.at)} · 共 ${String(report.crash.count)} 次)`;
10734
11782
  return [
@@ -11337,18 +12385,203 @@ function changelogOf(dir, version, note, kind) {
11337
12385
  };
11338
12386
  }
11339
12387
  //#endregion
12388
+ //#region src/shared/api/host/diy-capture-live.ts
12389
+ var LIVE_POLL_MS = 3e3;
12390
+ var LIVE_ARM_MS = 2e3;
12391
+ var LIVE_SNAP_TIMEOUT_MS = 15e3;
12392
+ var LIVE_TAIL_LINES = 2e4;
12393
+ var STATIC_URL = new RegExp(`^(data:)|(\\.(js|css|png|jpe?g|gif|svg|woff2?|ico|map|webp|mp4)(\\?|$))`, "i");
12394
+ var LIVE = /* @__PURE__ */ new Map();
12395
+ function plainOf(value) {
12396
+ return typeof value === "object" && value !== null && !Array.isArray(value) ? value : void 0;
12397
+ }
12398
+ function argvOf(command, session, args) {
12399
+ return [
12400
+ command === "" ? "runtime" : command,
12401
+ "browser",
12402
+ session,
12403
+ ...args,
12404
+ "--window",
12405
+ "background"
12406
+ ];
12407
+ }
12408
+ function filterStaticLines(raw) {
12409
+ const kept = [];
12410
+ for (const line of raw.split("\n")) {
12411
+ const text = line.trim();
12412
+ if (text === "") continue;
12413
+ let parsed;
12414
+ try {
12415
+ parsed = JSON.parse(text);
12416
+ } catch {
12417
+ kept.push(text);
12418
+ continue;
12419
+ }
12420
+ const record = plainOf(parsed);
12421
+ const entries = record === void 0 ? void 0 : record["entries"];
12422
+ if (record === void 0 || !Array.isArray(entries)) {
12423
+ kept.push(text);
12424
+ continue;
12425
+ }
12426
+ const inside = entries.filter((one) => !STATIC_URL.test(String(plainOf(one)?.["url"] ?? "")));
12427
+ if (inside.length === 0) continue;
12428
+ kept.push(JSON.stringify({
12429
+ ...record,
12430
+ entries: inside
12431
+ }));
12432
+ }
12433
+ return kept.join("\n");
12434
+ }
12435
+ async function captureTickOnce(seam, command, session, file, timeoutMs = LIVE_SNAP_TIMEOUT_MS) {
12436
+ const step = await seam.run({
12437
+ argv: argvOf(command, session, [
12438
+ "network",
12439
+ "--all",
12440
+ "--raw"
12441
+ ]),
12442
+ intent: productWrite(),
12443
+ timeoutMs
12444
+ });
12445
+ const round = step.exitCode === 0 ? "ok" : "fail";
12446
+ writeTextFile(`${file}.first`, round);
12447
+ const text = filterStaticLines(step.stdout);
12448
+ if (text !== "") appendTextFile(file, `${text}\n`);
12449
+ return round;
12450
+ }
12451
+ async function startLiveCapture(input) {
12452
+ const { seam, command, session, file } = input;
12453
+ stopLiveCapture(file);
12454
+ writeTextFile(file, "");
12455
+ for (const suffix of [
12456
+ ".first",
12457
+ ".last",
12458
+ ".round",
12459
+ ".pid",
12460
+ ".reaper"
12461
+ ]) rmSync(`${file}${suffix}`, { force: true });
12462
+ try {
12463
+ const arm = await seam.run({
12464
+ argv: argvOf(command, session, [
12465
+ "network",
12466
+ "--follow",
12467
+ "--all"
12468
+ ]),
12469
+ intent: productWrite(),
12470
+ timeoutMs: LIVE_ARM_MS
12471
+ });
12472
+ if (!arm.timedOut && arm.exitCode !== 0) return false;
12473
+ const text = arm.stdout.trim();
12474
+ if (text !== "") appendTextFile(file, `${text}\n`);
12475
+ } catch {
12476
+ return false;
12477
+ }
12478
+ const entry = {
12479
+ file,
12480
+ seam,
12481
+ command,
12482
+ session,
12483
+ timer: void 0,
12484
+ stopped: false
12485
+ };
12486
+ LIVE.set(file, entry);
12487
+ scheduleTick(entry);
12488
+ return true;
12489
+ }
12490
+ function scheduleTick(entry) {
12491
+ entry.timer = setTimeout(() => {
12492
+ runTick(entry);
12493
+ }, LIVE_POLL_MS);
12494
+ entry.timer.unref?.();
12495
+ }
12496
+ async function runTick(entry) {
12497
+ if (entry.stopped) return;
12498
+ try {
12499
+ await captureTickOnce(entry.seam, entry.command, entry.session, entry.file);
12500
+ } catch {
12501
+ entry.timer = void 0;
12502
+ }
12503
+ if (entry.stopped) return;
12504
+ scheduleTick(entry);
12505
+ }
12506
+ function stopLiveCapture(file) {
12507
+ const live = LIVE.get(file);
12508
+ for (const suffix of [
12509
+ ".pid",
12510
+ ".reaper",
12511
+ ".round"
12512
+ ]) rmSync(`${file}${suffix}`, { force: true });
12513
+ if (live !== void 0) {
12514
+ live.stopped = true;
12515
+ if (live.timer !== void 0) clearTimeout(live.timer);
12516
+ LIVE.delete(file);
12517
+ }
12518
+ return live !== void 0 || existsSync(`${file}.last`) || existsSync(`${file}.first`);
12519
+ }
12520
+ function liveCaptureOf(file) {
12521
+ return LIVE.has(file);
12522
+ }
12523
+ function readText$1(file) {
12524
+ try {
12525
+ return readFileSync(file, "utf8");
12526
+ } catch {
12527
+ return "";
12528
+ }
12529
+ }
12530
+ function readCaptureLines(file) {
12531
+ return (readText$1(file).match(/\n/g) ?? []).length;
12532
+ }
12533
+ function readCaptureRound(file) {
12534
+ const text = readText$1(`${file}.first`).trim();
12535
+ return text === "ok" || text === "fail" ? text : "";
12536
+ }
12537
+ function readCaptureText(file) {
12538
+ const text = readText$1(file);
12539
+ if (text === "") return {
12540
+ text: "",
12541
+ truncated: false
12542
+ };
12543
+ const lines = text.split("\n");
12544
+ if (text.endsWith("\n")) lines.pop();
12545
+ const kept = lines.slice(-2e4);
12546
+ return {
12547
+ text: kept.length === 0 ? "" : `${kept.join("\n")}\n`,
12548
+ truncated: lines.length > LIVE_TAIL_LINES
12549
+ };
12550
+ }
12551
+ function readCaptureLinesWithKey(file, key) {
12552
+ const text = readText$1(file);
12553
+ if (text === "") return "";
12554
+ const compact = `"key":"${key}"`;
12555
+ const spaced = `"key": "${key}"`;
12556
+ const out = [];
12557
+ for (const line of text.split("\n")) {
12558
+ if (out.length >= 20) break;
12559
+ if (line.includes(compact) || line.includes(spaced)) out.push(line);
12560
+ }
12561
+ return out.length === 0 ? "" : `${out.join("\n")}\n`;
12562
+ }
12563
+ function keepCaptureFile(file) {
12564
+ const last = `${file}.last`;
12565
+ try {
12566
+ renameSync(file, last);
12567
+ return statSync(last).size > 0;
12568
+ } catch {
12569
+ return false;
12570
+ }
12571
+ }
12572
+ //#endregion
11340
12573
  //#region src/modules/panels/model/diy-observers.ts
11341
12574
  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 {
12575
+ const seam = seamOf(ctx);
12576
+ if (workspace === null || workspace === void 0 || workspace === "" || seam === void 0) return {
11344
12577
  net: false,
11345
12578
  logs: false
11346
12579
  };
11347
- const file = join(artifactsDirOf(ctx, workspace), `net-${session}.jsonl`);
12580
+ const file = join(artifactsDirOf(workspace), `net-${session}.jsonl`);
11348
12581
  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)]);
12582
+ const [stopped, step] = await Promise.all([Promise.resolve(stopLiveCapture(file)), actBrowser(seam, command, session, ["eval", LOGS_STOP_SCRIPT], SWEEP_TIMEOUT_MS)]);
11350
12583
  return {
11351
- net,
12584
+ net: stopped || !liveCaptureOf(file),
11352
12585
  logs: typeof step.payload?.["unhooked"] === "number"
11353
12586
  };
11354
12587
  }
@@ -11498,7 +12731,7 @@ var REFUSAL$1 = {
11498
12731
  "not-owner": "这场调试不是这个会话的"
11499
12732
  };
11500
12733
  function debugActResource(deps) {
11501
- const now = () => deps.now?.() ?? Date.now();
12734
+ const now$2 = () => deps.now?.() ?? now();
11502
12735
  const settled = (intent, delivered, summary, state) => ({
11503
12736
  ok: true,
11504
12737
  intent,
@@ -11519,7 +12752,7 @@ function debugActResource(deps) {
11519
12752
  ok: false,
11520
12753
  text: "现在没有调试态 —— 没什么可暂停 / 继续 / 结束的"
11521
12754
  };
11522
- const at = now();
12755
+ const at = now$2();
11523
12756
  const held = store.live(at);
11524
12757
  if (held === null) return {
11525
12758
  ok: false,
@@ -11532,8 +12765,8 @@ function debugActResource(deps) {
11532
12765
  };
11533
12766
  const notice = deps.notice?.(intent, move.state) ?? debugNotice(intent, move.state);
11534
12767
  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())));
12768
+ if (intent !== "stop") return settled(intent, delivered, notice.summary, store.live(now$2()));
12769
+ return (store.settle?.() ?? Promise.resolve()).then(() => settled(intent, delivered, notice.summary, store.live(now$2())));
11537
12770
  }
11538
12771
  };
11539
12772
  }
@@ -12153,19 +13386,13 @@ function pickOf(entries, key, at = null) {
12153
13386
  //#endregion
12154
13387
  //#region src/modules/panels/model/diy-record.ts
12155
13388
  var OPEN_TIMEOUT_MS = 3e4;
12156
- var START_TIMEOUT_MS = 8e3;
12157
13389
  var SETTLE_MS = 2e3;
12158
13390
  var WAIT_CAP_MS = 9e5;
12159
13391
  var POLL_MS = 1e3;
12160
- var CAPTURE_TAIL_LINES = 2e4;
12161
- var CAPTURE_POLL_S = 3;
12162
- var CAPTURE_ARM_S = 2;
12163
- var STATIC_URL_PATTERN = `^(data:)|(\\.(js|css|png|jpe?g|gif|svg|woff2?|ico|map|webp|mp4)(\\?|$))`;
13392
+ var CAPTURE_TAIL_LINES = LIVE_TAIL_LINES;
12164
13393
  var RECORD_WATCH_MS = 3e3;
12165
13394
  var RECORD_WATCH_TRIES = 5;
12166
- var DROP_STATIC_JS = "let s=\"\";process.stdin.on(\"data\",(d)=>s+=d).on(\"end\",()=>{const re=new RegExp(process.argv[1],\"i\");for(const line of s.split(\"\\n\")){const t=line.trim();if(t===\"\")continue;let one;try{one=JSON.parse(t)}catch{process.stdout.write(t+\"\\n\");continue}if(Array.isArray(one.entries)){one.entries=one.entries.filter((e)=>!re.test(String((e||{}).url||\"\")));if(one.entries.length===0)continue}process.stdout.write(JSON.stringify(one)+\"\\n\")}});";
12167
13395
  var DETAIL_MAX_BYTES = 6e4;
12168
- var HOST_PID = String(process.pid);
12169
13396
  var pauseFor = (ms) => new Promise((resolve) => {
12170
13397
  setTimeout(resolve, ms);
12171
13398
  });
@@ -12192,7 +13419,7 @@ async function takeShotOf(input, held, n) {
12192
13419
  const path = shotFileOf(input.captureFile, held.unit, n);
12193
13420
  const at = Date.now();
12194
13421
  try {
12195
- const step = await actBrowser(input.shell, input.command, BROWSER_RECORD_SESSION, ["screenshot", path]);
13422
+ const step = await actBrowser(input.seam, input.command, BROWSER_RECORD_SESSION, ["screenshot", path]);
12196
13423
  if (step.ok) input.store.shotTaken(input.sessionId, {
12197
13424
  n,
12198
13425
  at,
@@ -12214,39 +13441,17 @@ async function takeShotOf(input, held, n) {
12214
13441
  }, Date.now());
12215
13442
  }
12216
13443
  }
12217
- async function startRecordCapture(shell, command, session, file, makeLine = plainLine) {
12218
- const bin = command === "" ? "runtime" : command;
12219
- const arm = makeLine(bin, [
12220
- "browser",
12221
- session,
12222
- "network",
12223
- "--follow",
12224
- "--all",
12225
- "--window",
12226
- "background"
12227
- ]);
12228
- const snap = makeLine(bin, [
12229
- "browser",
13444
+ async function startRecordCapture(seam, command, session, file) {
13445
+ return startLiveCapture({
13446
+ seam,
13447
+ command,
12230
13448
  session,
12231
- "network",
12232
- "--all",
12233
- "--raw",
12234
- "--window",
12235
- "background"
12236
- ]);
12237
- return (await runShell(shell, [
12238
- `f=${quoteShellArg(file)}`,
12239
- `r=${quoteShellArg(`${file}.round`)}`,
12240
- ": > \"$f\"",
12241
- "rm -f \"$f.last\" \"$f.first\" \"$r\"",
12242
- `( ${arm} >> "$f" 2>/dev/null & p=$!; sleep ${String(CAPTURE_ARM_S)}; kill "$p" 2>/dev/null )`,
12243
- `( 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
- "kill -0 \"$(cat \"$f.pid\" 2>/dev/null)\" 2>/dev/null"
12245
- ].join("; "), { timeoutMs: START_TIMEOUT_MS })).exitCode === 0;
13449
+ file
13450
+ });
12246
13451
  }
12247
13452
  async function releaseLeftoversOf(input) {
12248
13453
  await releaseQuietly({
12249
- shell: input.shell,
13454
+ seam: input.seam,
12250
13455
  command: input.command,
12251
13456
  session: BROWSER_RECORD_SESSION,
12252
13457
  url: input.url,
@@ -12255,14 +13460,14 @@ async function releaseLeftoversOf(input) {
12255
13460
  }
12256
13461
  async function stopCaptureQuiet(input) {
12257
13462
  try {
12258
- return await stopNetworkExact(input.shell, input.captureFile);
13463
+ return stopLiveCapture(input.captureFile);
12259
13464
  } catch {
12260
13465
  return false;
12261
13466
  }
12262
13467
  }
12263
13468
  async function closeRecordTabQuiet(input) {
12264
13469
  try {
12265
- return await closeRecordTab(input.shell, input.command, BROWSER_RECORD_SESSION, input.url);
13470
+ return await closeRecordTab(input.seam, input.command, BROWSER_RECORD_SESSION, input.url);
12266
13471
  } catch {
12267
13472
  return false;
12268
13473
  }
@@ -12290,7 +13495,7 @@ async function watchFinishStepOf(input) {
12290
13495
  let page;
12291
13496
  const started = Date.now();
12292
13497
  try {
12293
- page = recordPageOf((await actBrowser(input.shell, input.command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT])).payload);
13498
+ page = recordPageOf((await actBrowser(input.seam, input.command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT])).payload);
12294
13499
  } catch {
12295
13500
  return {
12296
13501
  stop: false,
@@ -12360,42 +13565,22 @@ function watchRecordFinish(input) {
12360
13565
  };
12361
13566
  setTimeout(tick, 0);
12362
13567
  }
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 });
12365
- const value = Number.parseInt(step.stdout.trim() === "" ? "0" : step.stdout.trim(), 10);
12366
- return Number.isFinite(value) ? value : 0;
12367
- }
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();
12370
- return value === "ok" || value === "fail" ? value : "";
12371
- }
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 });
13568
+ function captureReadOf(file) {
13569
+ const totalLines = readCaptureLines(file);
13570
+ const text = readCaptureText(file);
12374
13571
  return {
12375
- text: step.stdout,
12376
- truncated: step.truncated
12377
- };
12378
- }
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);
12383
- return {
12384
- text: read.text,
13572
+ text: text.text,
12385
13573
  totalLines,
12386
13574
  tailLimit: CAPTURE_TAIL_LINES,
12387
- truncated: read.truncated || totalLines > CAPTURE_TAIL_LINES,
12388
- round
13575
+ truncated: text.truncated || totalLines > CAPTURE_TAIL_LINES,
13576
+ round: readCaptureRound(file)
12389
13577
  };
12390
13578
  }
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;
13579
+ function keepCaptureOf(file) {
13580
+ return keepCaptureFile(file);
12393
13581
  }
12394
- async function captureLinesWithKeyOf(shell, file, key, timeoutMs = START_TIMEOUT_MS) {
12395
- const quoted = quoteShellArg(file);
12396
- const compact = quoteShellArg(`"key":"${key}"`);
12397
- 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;
13582
+ function captureLinesWithKeyOf(file, key) {
13583
+ return readCaptureLinesWithKey(file, key);
12399
13584
  }
12400
13585
  function bundleOf(capture, page, state) {
12401
13586
  const entries = captureEntriesOf(capture.text);
@@ -12505,32 +13690,32 @@ function tabsOf(raw) {
12505
13690
  return null;
12506
13691
  }
12507
13692
  }
12508
- async function closeRecordTab(shell, command, session, url) {
12509
- const pages = tabsOf((await actBrowser(shell, command, session, ["tab", "list"])).stdout);
13693
+ async function closeRecordTab(seam, command, session, url) {
13694
+ const pages = tabsOf((await actBrowser(seam, command, session, ["tab", "list"])).stdout);
12510
13695
  if (pages !== null && pages.length === 0) return true;
12511
13696
  const pick = pages?.find((one) => typeof one["url"] === "string" && one["url"] === url) ?? pages?.[0];
12512
13697
  const page = pick === void 0 ? "" : typeof pick["page"] === "string" ? pick["page"] : "";
12513
13698
  if (page !== "") {
12514
- if ((await actBrowser(shell, command, session, [
13699
+ if ((await actBrowser(seam, command, session, [
12515
13700
  "tab",
12516
13701
  "close",
12517
13702
  page
12518
13703
  ])).ok) {
12519
- for (const one of blanksOf(pages, page)) await actBrowser(shell, command, session, [
13704
+ for (const one of blanksOf(pages, page)) await actBrowser(seam, command, session, [
12520
13705
  "tab",
12521
13706
  "close",
12522
13707
  one
12523
13708
  ]);
12524
- await actBrowser(shell, command, session, ["close"]);
13709
+ await actBrowser(seam, command, session, ["close"]);
12525
13710
  return true;
12526
13711
  }
12527
13712
  }
12528
- return (await actBrowser(shell, command, session, ["close"])).ok;
13713
+ return (await actBrowser(seam, command, session, ["close"])).ok;
12529
13714
  }
12530
13715
  async function releaseRecord(input) {
12531
- const capture = await stopNetworkExact(input.shell, input.captureFile);
13716
+ const capture = stopLiveCapture(input.captureFile);
12532
13717
  return {
12533
- tab: await closeRecordTab(input.shell, input.command, input.session, input.url),
13718
+ tab: await closeRecordTab(input.seam, input.command, input.session, input.url),
12534
13719
  capture
12535
13720
  };
12536
13721
  }
@@ -12552,12 +13737,12 @@ function samePageOf(here, want) {
12552
13737
  return here !== "" && norm(here) === norm(want);
12553
13738
  }
12554
13739
  async function whereAtOf(input) {
12555
- const step = await actBrowser(input.shell, input.command, input.session, ["eval", PAGE_HREF_SCRIPT]);
13740
+ const step = await actBrowser(input.seam, input.command, input.session, ["eval", PAGE_HREF_SCRIPT]);
12556
13741
  const href = step.payload === void 0 ? "" : step.payload["href"];
12557
13742
  return typeof href === "string" ? href : "";
12558
13743
  }
12559
13744
  async function settleOf(input) {
12560
- await runBrowserStep(input.shell, input.command, [
13745
+ await runBrowserStep(input.seam, input.command, [
12561
13746
  "browser",
12562
13747
  input.session,
12563
13748
  "wait",
@@ -12567,7 +13752,7 @@ async function settleOf(input) {
12567
13752
  }
12568
13753
  async function openRecordPage(input) {
12569
13754
  await releaseQuietly(input);
12570
- const opened = await runBrowserStep(input.shell, input.command, [
13755
+ const opened = await runBrowserStep(input.seam, input.command, [
12571
13756
  "browser",
12572
13757
  input.session,
12573
13758
  "tab",
@@ -12584,7 +13769,7 @@ async function openRecordPage(input) {
12584
13769
  await settleOf(input);
12585
13770
  let at = await whereAtOf(input);
12586
13771
  if (!samePageOf(at, input.url)) {
12587
- await runBrowserStep(input.shell, input.command, [
13772
+ await runBrowserStep(input.seam, input.command, [
12588
13773
  "browser",
12589
13774
  input.session,
12590
13775
  "open",
@@ -12652,7 +13837,7 @@ function registerRecordTools(ctx, store) {
12652
13837
  output: OUTPUT$3,
12653
13838
  async execute(raw, exec) {
12654
13839
  const { root } = await workspaceOf(ctx, exec);
12655
- const artifactsDir = artifactsDirOf(ctx, root);
13840
+ const artifactsDir = artifactsDirOf(root);
12656
13841
  const args = asRecord$1(raw);
12657
13842
  const asked = asText(args["unit"]);
12658
13843
  const { units } = unitsOf(ctx, root);
@@ -12670,8 +13855,8 @@ function registerRecordTools(ctx, store) {
12670
13855
  method,
12671
13856
  urlContains
12672
13857
  };
12673
- const shell = ctx.get("shell");
12674
- if (shell === void 0) throw new Error("这台机器上没有 shell 服务,开不了录制窗");
13858
+ const seam = seamOf(ctx);
13859
+ if (seam === void 0) throw new Error("这台机器上没有 shell 服务,开不了录制窗");
12675
13860
  const command = cliCommandOf(ctx);
12676
13861
  const sessionId = sessionIdOf(exec) ?? "";
12677
13862
  const held = store.live(Date.now());
@@ -12690,21 +13875,21 @@ function registerRecordTools(ctx, store) {
12690
13875
  }), held);
12691
13876
  try {
12692
13877
  const opened = await openRecordPage({
12693
- shell,
13878
+ seam,
12694
13879
  command,
12695
13880
  session: BROWSER_RECORD_SESSION,
12696
13881
  url,
12697
13882
  captureFile
12698
13883
  });
12699
13884
  if (!opened.ok) throw new Error(opened.why);
12700
- const armed = await actBrowser(shell, command, BROWSER_RECORD_SESSION, ["eval", RECORD_ARM_SCRIPT]);
13885
+ const armed = await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_ARM_SCRIPT]);
12701
13886
  const page = recordPageOf(armed.payload);
12702
13887
  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);
13888
+ stopLiveCapture(captureFile);
13889
+ const capture = await startRecordCapture(seam, command, BROWSER_RECORD_SESSION, captureFile);
12705
13890
  watchRecordFinish({
12706
13891
  ctx,
12707
- shell,
13892
+ seam,
12708
13893
  command,
12709
13894
  sessionId,
12710
13895
  store,
@@ -12727,7 +13912,7 @@ function registerRecordTools(ctx, store) {
12727
13912
  });
12728
13913
  } catch (error) {
12729
13914
  await releaseQuietly({
12730
- shell,
13915
+ seam,
12731
13916
  command,
12732
13917
  session: BROWSER_RECORD_SESSION,
12733
13918
  url,
@@ -12751,8 +13936,8 @@ function registerRecordTools(ctx, store) {
12751
13936
  },
12752
13937
  output: OUTPUT$3,
12753
13938
  async execute(raw, exec) {
12754
- const shell = ctx.get("shell");
12755
- if (shell === void 0) throw new Error("这台机器上没有 shell 服务,读不了录制");
13939
+ const seam = seamOf(ctx);
13940
+ if (seam === void 0) throw new Error("这台机器上没有 shell 服务,读不了录制");
12756
13941
  const sessionId = sessionIdOf(exec) ?? "";
12757
13942
  const held = store.live(Date.now());
12758
13943
  if (held === null) throw new Error(recordMissingText());
@@ -12777,14 +13962,14 @@ function registerRecordTools(ctx, store) {
12777
13962
  let stepOk = true;
12778
13963
  let page = recordPageOf(void 0);
12779
13964
  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]);
13965
+ await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_ARM_SCRIPT]);
13966
+ let step = await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT]);
12782
13967
  page = recordPageOf(step.payload);
12783
13968
  stepOk = step.ok;
12784
13969
  sync(page);
12785
13970
  while (budget > 0 && page.phase !== "finished" && Date.now() < deadline) {
12786
13971
  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]);
13972
+ step = await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT]);
12788
13973
  const next = recordPageOf(step.payload);
12789
13974
  if (next.ok) page = next;
12790
13975
  sync(page);
@@ -12798,7 +13983,7 @@ function registerRecordTools(ctx, store) {
12798
13983
  }
12799
13984
  const state = store.live(Date.now()) ?? held;
12800
13985
  const ledger = state.shots;
12801
- const capture = await captureReadOf(shell, state.captureFile);
13986
+ const capture = captureReadOf(state.captureFile);
12802
13987
  const bundle = bundleOf(capture, page, state);
12803
13988
  const hookCaught = page.calls.length;
12804
13989
  const nav = page.href !== "" && page.here !== "" && page.href !== page.here;
@@ -12882,9 +14067,9 @@ function registerRecordTools(ctx, store) {
12882
14067
  output: OUTPUT$3,
12883
14068
  async execute(raw, exec) {
12884
14069
  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 服务,取不了详情");
14070
+ const artifactsDir = artifactsDirOf(root);
14071
+ const seam = seamOf(ctx);
14072
+ if (seam === void 0) throw new Error("这台机器上没有 shell 服务,取不了详情");
12888
14073
  const args = asRecord$1(raw);
12889
14074
  const unit = asText(args["unit"]);
12890
14075
  const askedKey = asText(args["key"]).trim();
@@ -12908,8 +14093,8 @@ function registerRecordTools(ctx, store) {
12908
14093
  if (key === "") {
12909
14094
  if (index === null) throw new Error("给一个 key(清单里那一条的)或 index(清单里的序号)。");
12910
14095
  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);
14096
+ page = recordPageOf((await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT])).payload);
14097
+ const bundle = bundleOf(captureReadOf(file), page, held);
12913
14098
  const row = bundle.rows[index];
12914
14099
  if (row === void 0) throw new Error(`这一段清单里没有第 ${String(index)} 条(一共 ${String(bundle.rows.length)} 条)—— 先 ${DIY_RECORD_READ_TOOL} 拿一份新的清单。`);
12915
14100
  key = row.key;
@@ -12919,11 +14104,11 @@ function registerRecordTools(ctx, store) {
12919
14104
  if (only !== null) return detailOfCall(only, maxBytes);
12920
14105
  }
12921
14106
  }
12922
- const candidates = pickAllOf(captureEntriesOf(await captureLinesWithKeyOf(shell, file, key)), key);
14107
+ const candidates = pickAllOf(captureEntriesOf(captureLinesWithKeyOf(file, key)), key);
12923
14108
  const fromCapture = pickOf(candidates, key, at);
12924
14109
  if (fromCapture !== null) return detailOfEntry(fromCapture, maxBytes, candidates.length);
12925
14110
  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);
14111
+ const livePage = page !== null || over ? page : recordPageOf((await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_READ_SCRIPT])).payload);
12927
14112
  const call = livePage === null ? null : hookCallOf(livePage, key);
12928
14113
  if (call !== null) return detailOfCall(call, maxBytes);
12929
14114
  return plainJson({
@@ -12963,8 +14148,8 @@ function registerRecordTools(ctx, store) {
12963
14148
  },
12964
14149
  output: OUTPUT$3,
12965
14150
  async execute(_raw, exec) {
12966
- const shell = ctx.get("shell");
12967
- if (shell === void 0) throw new Error("这台机器上没有 shell 服务,收不了工");
14151
+ const seam = seamOf(ctx);
14152
+ if (seam === void 0) throw new Error("这台机器上没有 shell 服务,收不了工");
12968
14153
  const sessionId = sessionIdOf(exec) ?? "";
12969
14154
  const held = store.live(Date.now());
12970
14155
  if (held === null) throw new Error(recordMissingText());
@@ -12973,14 +14158,14 @@ function registerRecordTools(ctx, store) {
12973
14158
  code: "not-owner"
12974
14159
  }, held);
12975
14160
  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);
14161
+ await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_ARM_SCRIPT]);
14162
+ const page = recordPageOf((await actBrowser(seam, command, BROWSER_RECORD_SESSION, ["eval", RECORD_STOP_SCRIPT])).payload);
14163
+ const bundle = bundleOf(captureReadOf(held.captureFile), page, held);
12979
14164
  const hookCaught = page.calls.length;
12980
14165
  const noBody = bodyMissingOf(bundle.merged);
12981
- const kept = await keepCaptureOf(shell, held.captureFile);
14166
+ const kept = keepCaptureOf(held.captureFile);
12982
14167
  const cleaned = await releaseQuietly({
12983
- shell,
14168
+ seam,
12984
14169
  command,
12985
14170
  session: BROWSER_RECORD_SESSION,
12986
14171
  url: held.url,
@@ -13277,11 +14462,11 @@ async function settle(deps, spec, options, change) {
13277
14462
  };
13278
14463
  }
13279
14464
  async function installRouted(deps) {
13280
- const { spec, options, registry, shell } = deps;
14465
+ const { spec, options, registry, seam } = deps;
13281
14466
  const supply = deps.supply ?? requireSupply();
13282
14467
  const route = await supply.packageRouteOf({
13283
14468
  spec,
13284
- ...shell === void 0 ? {} : { shell },
14469
+ ...seam === void 0 ? {} : { seam },
13285
14470
  ...registry === void 0 ? {} : { registry }
13286
14471
  });
13287
14472
  if (route === void 0 || route.kind === "dsh-bundle") return installDshBundle(deps, spec, options);
@@ -13491,15 +14676,15 @@ async function updateModuleBundle(unit, deps) {
13491
14676
  pkg
13492
14677
  };
13493
14678
  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,
14679
+ const latest = deps.seam === void 0 || registry === void 0 ? void 0 : await supply.latestVersion({
14680
+ seam: deps.seam,
13496
14681
  name: pkg,
13497
14682
  registry,
13498
14683
  force: true
13499
14684
  });
13500
- if (latest !== void 0 && deps.shell !== void 0 && registry !== void 0) {
14685
+ if (latest !== void 0 && deps.seam !== void 0 && registry !== void 0) {
13501
14686
  const declared = await supply.registryDeclarationOf({
13502
- shell: deps.shell,
14687
+ seam: deps.seam,
13503
14688
  name: pkg,
13504
14689
  registry,
13505
14690
  force: true
@@ -13701,17 +14886,17 @@ async function updateWorkbenchOnce(deps) {
13701
14886
  const read = deps.workbench?.(target);
13702
14887
  if (read === void 0) throw new Error(`读不到 profile 里装的 ${target} —— 没法判断装的是哪一份,更新做不了。`);
13703
14888
  if (read.local === true) throw new Error("这份工作台是从源码目录装进来的(开发副本),不从源上更新。");
13704
- const shell = deps.shell;
14889
+ const seam = deps.seam;
13705
14890
  const registry = read.registry ?? await deps.profileRegistry?.();
13706
- const askedLatest = shell === void 0 || registry === void 0 ? void 0 : await supply.latestVersion({
13707
- shell,
14891
+ const askedLatest = seam === void 0 || registry === void 0 ? void 0 : await supply.latestVersion({
14892
+ seam,
13708
14893
  name: read.pkg,
13709
14894
  registry,
13710
14895
  force: true
13711
14896
  });
13712
- if (shell !== void 0 && registry !== void 0 && askedLatest !== void 0) {
14897
+ if (seam !== void 0 && registry !== void 0 && askedLatest !== void 0) {
13713
14898
  const missing = await supply.missingReleaseParts({
13714
- shell,
14899
+ seam,
13715
14900
  name: read.pkg,
13716
14901
  registry,
13717
14902
  version: askedLatest,
@@ -13774,25 +14959,29 @@ async function migrateWorkbench(input) {
13774
14959
  }
13775
14960
  //#endregion
13776
14961
  //#region src/modules/panels/model/market-follow-up.ts
14962
+ var MIGRATION_TIMEOUT_MS = 12e4;
13777
14963
  function createWorkbenchFollowUps(deps) {
13778
14964
  const runMigration = deps.runMigration ?? (async (input) => {
13779
- const result = await execa(process.execPath, [
13780
- input.script,
13781
- "--migrate",
13782
- "--force"
13783
- ], {
14965
+ const seam = deps.seam;
14966
+ if (seam === void 0) throw new Error("没有执行缝(shell 服务不可用),迁移跑不起来。");
14967
+ const outcome = await seam.run({
14968
+ argv: [
14969
+ process.execPath,
14970
+ input.script,
14971
+ "--migrate",
14972
+ "--force"
14973
+ ],
14974
+ intent: { kind: "host" },
13784
14975
  env: {
13785
- ...process.env,
13786
14976
  ELECTRON_RUN_AS_NODE: "1",
13787
14977
  ...input.env
13788
14978
  },
13789
- reject: false,
13790
- timeout: 12e4
14979
+ timeoutMs: MIGRATION_TIMEOUT_MS
13791
14980
  });
13792
14981
  return {
13793
- exitCode: result.exitCode ?? 1,
13794
- stdout: result.stdout,
13795
- stderr: result.stderr
14982
+ exitCode: outcome.exitCode ?? 1,
14983
+ stdout: outcome.stdout,
14984
+ stderr: outcome.stderr
13796
14985
  };
13797
14986
  });
13798
14987
  function homeAndProfile() {
@@ -13849,7 +15038,7 @@ function pluginSweptText(pkg) {
13849
15038
  return `清掉了 ${pkg}:先前要它的都放手了,也没别人在用,文件跟着删了。`;
13850
15039
  }
13851
15040
  function createPluginActions(options) {
13852
- const { ledger, shell, command, runAll, logger } = options;
15041
+ const { ledger, seam, command, runAll, logger } = options;
13853
15042
  const supply = options.supply ?? requireSupply();
13854
15043
  const pluginFilesOf = () => {
13855
15044
  const dir = supply.vendorPluginsDir?.();
@@ -13911,16 +15100,16 @@ function createPluginActions(options) {
13911
15100
  };
13912
15101
  }
13913
15102
  async function removeFiles(plugin) {
13914
- if (shell === void 0) throw new Error("没有执行缝(shell 服务不可用),删不掉插件文件。");
15103
+ if (seam === void 0) throw new Error("没有执行缝(shell 服务不可用),删不掉插件文件。");
13915
15104
  await runAll([{ line: `${quoteShellArg(command)} plugin uninstall ${quoteShellArg(plugin)}` }]);
13916
15105
  }
13917
15106
  return {
13918
15107
  async update(pkg) {
13919
15108
  const { entry } = requireHeld(pkg);
13920
15109
  if (entry.local === true || entry.registry === void 0) throw new Error(`「${entry.package}」是从本地装的,没有地方查新版。`);
13921
- if (shell === void 0) throw new Error("没有执行缝(shell 服务不可用),更新做不了。");
15110
+ if (seam === void 0) throw new Error("没有执行缝(shell 服务不可用),更新做不了。");
13922
15111
  const latest = await supply.latestVersion({
13923
- shell,
15112
+ seam,
13924
15113
  name: entry.package,
13925
15114
  registry: entry.registry,
13926
15115
  force: true
@@ -14060,7 +15249,7 @@ function restartLogLine(change) {
14060
15249
  //#endregion
14061
15250
  //#region src/modules/panels/model/market.ts
14062
15251
  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;
15252
+ const { manifests, manifestsNow, probe, seam, logger, supply: supplyOption, onInstalled, onUninstalled, isReleased, bundles, installed, modules, workbench, registries, profileDir, ledger, references, dshHome, profileName } = options;
14064
15253
  const supply = supplyOption ?? requireSupply();
14065
15254
  const command = options.command;
14066
15255
  const catalog = () => manifestsNow?.() ?? manifests;
@@ -14098,13 +15287,17 @@ function createMarketService(options) {
14098
15287
  };
14099
15288
  }
14100
15289
  async function run(line, optional = false) {
14101
- if (shell === void 0) throw new Error("没有执行缝(shell 服务不可用),装 / 卸做不了。");
15290
+ if (seam === void 0) throw new Error("没有执行缝(shell 服务不可用),装 / 卸做不了。");
14102
15291
  const blocked = installWriteProblem({
14103
15292
  command: line,
14104
15293
  fallback: supply.modules.isolatedRoot()
14105
15294
  });
14106
15295
  if (blocked !== void 0) throw installError(blocked);
14107
- const result = await runShell(shell, line, { timeoutMs: INSTALL_TIMEOUT_MS });
15296
+ const result = await seam.run({
15297
+ command: line,
15298
+ intent: productWrite(),
15299
+ timeoutMs: INSTALL_TIMEOUT_MS
15300
+ });
14108
15301
  if (result.exitCode !== 0 && !optional) throw installError(diagnoseInstallFailure({
14109
15302
  command: line,
14110
15303
  exitCode: result.exitCode,
@@ -14214,7 +15407,7 @@ function createMarketService(options) {
14214
15407
  registryFix,
14215
15408
  ...logger === void 0 ? {} : { logger }
14216
15409
  }),
14217
- ...shell === void 0 ? {} : { shell },
15410
+ ...seam === void 0 ? {} : { seam },
14218
15411
  ...installed === void 0 ? {} : { installed },
14219
15412
  ...ledger === void 0 ? {} : { ledger },
14220
15413
  ...logger === void 0 ? {} : { logger }
@@ -14222,7 +15415,7 @@ function createMarketService(options) {
14222
15415
  }
14223
15416
  const plugins = createPluginActions({
14224
15417
  ...ledger === void 0 ? {} : { ledger },
14225
- ...shell === void 0 ? {} : { shell },
15418
+ ...seam === void 0 ? {} : { seam },
14226
15419
  command,
14227
15420
  runAll,
14228
15421
  claimedBy: (plugin) => pluginUsersOf(plugin),
@@ -14377,7 +15570,7 @@ function createMarketService(options) {
14377
15570
  supply,
14378
15571
  modules,
14379
15572
  installed,
14380
- shell,
15573
+ seam,
14381
15574
  manager,
14382
15575
  ...profile === void 0 ? {} : {
14383
15576
  profile,
@@ -14399,7 +15592,7 @@ function createMarketService(options) {
14399
15592
  supply,
14400
15593
  asked: pkg,
14401
15594
  ...workbench === void 0 ? {} : { workbench },
14402
- ...shell === void 0 ? {} : { shell },
15595
+ ...seam === void 0 ? {} : { seam },
14403
15596
  ...installed === void 0 ? {} : { installed },
14404
15597
  ...dshHome === void 0 ? {} : { dshHome },
14405
15598
  ...profileName === void 0 ? {} : { profileName },
@@ -14428,6 +15621,7 @@ function createMarketService(options) {
14428
15621
  dshHome,
14429
15622
  profileName,
14430
15623
  workbench,
15624
+ ...seam === void 0 ? {} : { seam },
14431
15625
  ...options.runMigration === void 0 ? {} : { runMigration: options.runMigration }
14432
15626
  }),
14433
15627
  updateDependency: updateDependencyRow$1
@@ -14436,18 +15630,11 @@ function createMarketService(options) {
14436
15630
  }
14437
15631
  //#endregion
14438
15632
  //#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
15633
  function explainProblem(problem, profilePath) {
14446
15634
  if (problem.kind !== "sandbox" || profilePath === void 0 || profilePath === "") return problem;
14447
- const file = join(profilePath, "cordis.patch.yml");
14448
15635
  return {
14449
15636
  ...problem,
14450
- detail: `被系统拦住了:这个 profile 的可写根盖不住工作台的缓存目录。\n退出 dsh 后,把下面这段加进 ${file},再启动:\n${SANDBOX_BLOCK}`
15637
+ detail: `被系统拦住了:工作台按 $DSH_HOME 声明的可写根没盖住它的缓存目录,这条命令被沙箱挡下了。\n这是工作台自己的问题,不用改 profile —— 把这一屏和 $DSH_HOME 报给工作台维护者,或先在工作台外面手动加这个源(${profilePath})。`
14451
15638
  };
14452
15639
  }
14453
15640
  var DEFAULT_PEER_TTL_MS = 6e5;
@@ -14507,7 +15694,7 @@ function createMarketDiscover(options) {
14507
15694
  const git = version.source;
14508
15695
  if (git.type !== "git") return void 0;
14509
15696
  if (normalizedRepo(git.url) !== normalizedRepo(source.url)) return void 0;
14510
- if (options.shell === void 0) return void 0;
15697
+ if (options.seam === void 0) return void 0;
14511
15698
  const key = `${git.commit}|${git.path ?? ""}`;
14512
15699
  const cached = peerCache.get(key);
14513
15700
  if (cached !== void 0 && now() - cached.at < peerTtlMs) return cached.peer;
@@ -14515,7 +15702,12 @@ function createMarketDiscover(options) {
14515
15702
  const directory = (git.path ?? "").replace(/^\/+|\/+$/g, "");
14516
15703
  const file = directory === "" ? "package.json" : `${directory}/package.json`;
14517
15704
  try {
14518
- const result = await runShell(options.shell, `git -C ${quoteShellArg(repo)} show ${git.commit}:${quoteShellArg(file)}`, { timeoutMs });
15705
+ const result = await options.seam.run({
15706
+ command: `git -C ${quoteShellArg(repo)} show ${git.commit}:${quoteShellArg(file)}`,
15707
+ intent: noWrite(),
15708
+ retry: "safe",
15709
+ timeoutMs
15710
+ });
14519
15711
  if (result.exitCode !== 0) throw new Error(result.stderr.trim());
14520
15712
  const peer = JSON.parse(result.stdout).peerDependencies?.[CORE_PEER];
14521
15713
  peerCache.set(key, {
@@ -14909,7 +16101,7 @@ function repoOf(root, source) {
14909
16101
  return join(directoryOf(root, source), "repo.git");
14910
16102
  }
14911
16103
  function writeMeta(path, meta) {
14912
- return writeFileAtomic(path, `${JSON.stringify(meta, null, 2)}\n`);
16104
+ return writeJsonFileAsync(path, meta);
14913
16105
  }
14914
16106
  function readMeta(path) {
14915
16107
  try {
@@ -15045,7 +16237,7 @@ function createMarketIndexReader(options) {
15045
16237
  };
15046
16238
  const fetchedAt = now();
15047
16239
  mkdirSync(directoryOf(options.root, source), { recursive: true });
15048
- await writeFileAtomic(indexFileOf(options.root, source), text);
16240
+ await writeTextFileAsync(indexFileOf(options.root, source), text);
15049
16241
  await writeMeta(metaFileOf(options.root, source), { fetchedAt });
15050
16242
  return {
15051
16243
  ok: true,
@@ -15196,7 +16388,7 @@ function createMarketIndexReader(options) {
15196
16388
  const entries = rows.filter((one) => one.type === "blob" && one.file !== path && one.file.startsWith(`${path}/`) && one.file.endsWith(".json"));
15197
16389
  const finish = async (index, text) => {
15198
16390
  const fetchedAt = now();
15199
- await writeFileAtomic(indexFileOf(options.root, source), text);
16391
+ await writeTextFileAsync(indexFileOf(options.root, source), text);
15200
16392
  await writeMeta(metaFileOf(options.root, source), {
15201
16393
  fetchedAt,
15202
16394
  commit
@@ -15472,7 +16664,7 @@ function readPayload(path, logger) {
15472
16664
  }
15473
16665
  async function writePayload(path, payload) {
15474
16666
  mkdirSync(dirname(path), { recursive: true });
15475
- await writeFileAtomic(path, `${JSON.stringify(payload, null, 2)}\n`);
16667
+ await writeJsonFileAsync(path, payload);
15476
16668
  }
15477
16669
  function upsert(sources, source) {
15478
16670
  const found = sources.findIndex((item) => item.id === source.id);
@@ -15660,9 +16852,7 @@ function createMarketSourceInitializer(options) {
15660
16852
  const migrated = marketEntryFilesOfLegacy(readFileSync(legacyFile, "utf8"), indexPath);
15661
16853
  if (migrated !== void 0) {
15662
16854
  for (const file of migrated) {
15663
- const target = join(workdir, ...file.path.split("/"));
15664
- mkdirSync(dirname(target), { recursive: true });
15665
- writeFileSync(target, file.text);
16855
+ writeTextFile(join(workdir, ...file.path.split("/")), file.text);
15666
16856
  written.push(file.path);
15667
16857
  }
15668
16858
  rmSync(legacyFile);
@@ -15671,12 +16861,12 @@ function createMarketSourceInitializer(options) {
15671
16861
  }
15672
16862
  if (!existsSync(indexMarker)) {
15673
16863
  mkdirSync(indexDir, { recursive: true });
15674
- writeFileSync(indexMarker, MARKET_ENTRIES_README_LINES.join("\n"));
16864
+ writeTextFile(indexMarker, MARKET_ENTRIES_README_LINES.join("\n"));
15675
16865
  written.push(`${indexPath}/README.md`);
15676
16866
  }
15677
16867
  const readmeFile = join(workdir, "README.md");
15678
16868
  if (!existsSync(readmeFile)) {
15679
- writeFileSync(readmeFile, MARKET_INDEX_README_LINES.join("\n"));
16869
+ writeTextFile(readmeFile, MARKET_INDEX_README_LINES.join("\n"));
15680
16870
  written.push("README.md");
15681
16871
  }
15682
16872
  }
@@ -16710,12 +17900,12 @@ var INSTALLED_PLUGINS_ROUTE = "panels/plugins";
16710
17900
  //#endregion
16711
17901
  //#region src/modules/panels/model/plugin-section.ts
16712
17902
  async function installedPlugins(options) {
16713
- const { ledger, shell, force = false, check = true } = options;
17903
+ const { ledger, seam, force = false, check = true } = options;
16714
17904
  const supply = options.supply ?? requireSupply();
16715
17905
  const rows = [];
16716
17906
  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,
17907
+ const release = seam === void 0 || entry.local === true || entry.registry === void 0 ? void 0 : await supply.probeRelease({
17908
+ seam,
16719
17909
  package: entry.package,
16720
17910
  registry: entry.registry,
16721
17911
  force,
@@ -16742,7 +17932,7 @@ function pluginRoutes(options) {
16742
17932
  ok: true,
16743
17933
  plugins: await installedPlugins({
16744
17934
  ledger: options.ledger,
16745
- ...options.shell === void 0 ? {} : { shell: options.shell },
17935
+ ...options.seam === void 0 ? {} : { seam: options.seam },
16746
17936
  ...options.supply === void 0 ? {} : { supply: options.supply },
16747
17937
  force: param(search, "force") === "1",
16748
17938
  check: param(search, "check") !== "0"
@@ -17010,13 +18200,13 @@ var OWNED = [{
17010
18200
  const record = {
17011
18201
  ...createRecordStore(),
17012
18202
  release: (state) => {
17013
- const shellNow = ctx.get("shell");
17014
- if (shellNow === void 0) return Promise.resolve({
18203
+ const seamNow = seamOf(ctx);
18204
+ if (seamNow === void 0) return Promise.resolve({
17015
18205
  tab: false,
17016
18206
  capture: false
17017
18207
  });
17018
18208
  return releaseQuietly({
17019
- shell: shellNow,
18209
+ seam: seamNow,
17020
18210
  command: cliCommandOf(ctx),
17021
18211
  session: BROWSER_RECORD_SESSION,
17022
18212
  url: state.url,
@@ -17026,7 +18216,7 @@ var OWNED = [{
17026
18216
  };
17027
18217
  ctx.provide(DIY_RECORD_SERVICE, record);
17028
18218
  const profileDir = supply.profileDirOf(ctx);
17029
- const shell = ctx.get("shell");
18219
+ const seam = seamOf(ctx);
17030
18220
  const moduleManifests = () => {
17031
18221
  const list = manifests();
17032
18222
  if (installable === void 0) return list.filter((manifest) => supplyNeedsOf(manifest).length > 0);
@@ -17038,7 +18228,7 @@ var OWNED = [{
17038
18228
  manifests: moduleManifests(),
17039
18229
  manifestsNow: moduleManifests,
17040
18230
  ...probe === void 0 ? {} : { probe },
17041
- shell,
18231
+ seam,
17042
18232
  command: cliCommandOf(ctx),
17043
18233
  supply,
17044
18234
  bundles,
@@ -17099,7 +18289,7 @@ var OWNED = [{
17099
18289
  return;
17100
18290
  }
17101
18291
  },
17102
- shell,
18292
+ seam,
17103
18293
  ...logger === void 0 ? {} : { logger }
17104
18294
  });
17105
18295
  return {
@@ -17108,7 +18298,7 @@ var OWNED = [{
17108
18298
  ...pluginRoutes({
17109
18299
  ledger,
17110
18300
  supply,
17111
- ...shell === void 0 ? {} : { shell }
18301
+ ...seam === void 0 ? {} : { seam }
17112
18302
  }),
17113
18303
  ...diyDebugRoutes(debug),
17114
18304
  ...diyRecordRoutes(record),
@@ -17393,6 +18583,17 @@ function declaredEnvOf(manifest) {
17393
18583
  for (const need of manifest.needs ?? []) if (need.kind === "env") out[need.name] = process.env[need.name];
17394
18584
  return out;
17395
18585
  }
18586
+ function unitSeamOf(seam, unit, manifest) {
18587
+ const declared = manifest.host.writes ?? [];
18588
+ if (seam === void 0 || declared.length === 0) return void 0;
18589
+ return createUnitSeam({
18590
+ seam,
18591
+ unit,
18592
+ runtimeRoot: workbenchRoot(),
18593
+ declared,
18594
+ realHome: homedir()
18595
+ });
18596
+ }
17396
18597
  function mountUnits(units, deps) {
17397
18598
  const store = deps.store ?? createWorkspaceStore({ root: null });
17398
18599
  const missingData = async () => ({
@@ -17447,6 +18648,7 @@ function mountUnits(units, deps) {
17447
18648
  });
17448
18649
  let result;
17449
18650
  try {
18651
+ const seam = unitSeamOf(deps.seam, unit, host.manifest);
17450
18652
  result = host.face.mount({
17451
18653
  manifest: host.manifest,
17452
18654
  ctx: mountCtx,
@@ -17456,6 +18658,7 @@ function mountUnits(units, deps) {
17456
18658
  ...deps.installable === void 0 ? {} : { installable: deps.installable },
17457
18659
  data,
17458
18660
  ...hostKit,
18661
+ ...seam === void 0 ? {} : { seam },
17459
18662
  env: declaredEnvOf(host.manifest),
17460
18663
  ...deps.libs === void 0 ? {} : { libs: deps.libs },
17461
18664
  ...deps.git === void 0 ? {} : { git: deps.git },
@@ -17608,7 +18811,7 @@ async function releaseHeld(store, held) {
17608
18811
  }
17609
18812
  }
17610
18813
  function recordActResource(deps) {
17611
- const now = () => deps.now?.() ?? Date.now();
18814
+ const now$1 = () => deps.now?.() ?? now();
17612
18815
  return {
17613
18816
  resource: "act",
17614
18817
  async handle(payload) {
@@ -17618,7 +18821,7 @@ function recordActResource(deps) {
17618
18821
  text: "不认识的意图(只认 peek / halt / stop)"
17619
18822
  };
17620
18823
  const store = deps.store();
17621
- const at = now();
18824
+ const at = now$1();
17622
18825
  const held = store?.live(at) ?? null;
17623
18826
  if (store === void 0 || held === null) return {
17624
18827
  ok: false,
@@ -17630,7 +18833,7 @@ function recordActResource(deps) {
17630
18833
  intent,
17631
18834
  delivered: deliverDebugNotice(deps.agents(), held.sessionId, notice.message, NOTICE_MODE[intent]),
17632
18835
  summary: notice.summary,
17633
- state: store.live(now())
18836
+ state: store.live(now$1())
17634
18837
  };
17635
18838
  const cleaned = await releaseHeld(store, held);
17636
18839
  const move = store.stop(held.sessionId, at);
@@ -17641,7 +18844,7 @@ function recordActResource(deps) {
17641
18844
  summary: notice.summary,
17642
18845
  cleaned,
17643
18846
  refused: move.ok ? "" : move.code,
17644
- state: store.live(now())
18847
+ state: store.live(now$1())
17645
18848
  };
17646
18849
  }
17647
18850
  };
@@ -18295,18 +19498,9 @@ function notifyLongTaskStop(agents, unit, change) {
18295
19498
  //#region src/app/host.ts
18296
19499
  var name = "dsh-workbench";
18297
19500
  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
19501
  async function apply(ctx, config) {
19502
+ const seam = provideWorkbenchSeam(ctx);
19503
+ if (seam !== void 0) provideGitProcessSeam(seam);
18310
19504
  const facts = createFactLog();
18311
19505
  const values = createValueHandoff();
18312
19506
  const panelState = createPanelStateStore();
@@ -18317,13 +19511,11 @@ async function apply(ctx, config) {
18317
19511
  corePackageUrl: import.meta.url,
18318
19512
  log: (message) => warn(ctx, message)
18319
19513
  });
18320
- adapters.runtime.warnUnwritableRoot(ctx);
18321
19514
  if (loaded.ok) ctx.logger?.info?.(`[dsh-workbench] 适配器集合:${adapters.id}@${adapters.version}(${loaded.spec})`);
18322
19515
  else if (loaded.failure !== "not-configured") ctx.logger?.warn?.(`[dsh-workbench] 适配器集合没生效(${loaded.failure}):${loaded.detail};先用默认集合`);
18323
19516
  ctx.provide(WORKBENCH_ADAPTERS, adapters);
18324
19517
  const runtimeCommands = adapters.runtime.commands(ctx);
18325
19518
  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
19519
  const routes = createHostRoutes({
18328
19520
  ctx,
18329
19521
  facts,
@@ -18411,11 +19603,11 @@ async function apply(ctx, config) {
18411
19603
  });
18412
19604
  const releasedOf = (unit) => ctx.get("workbenchPanels")?.isReleased(unit) ?? false;
18413
19605
  const probeOne = async (unit, force = false) => {
18414
- if (shell === void 0) return void 0;
19606
+ if (seam === void 0) return void 0;
18415
19607
  const manifest = manifestOf(unit);
18416
19608
  if (manifest === void 0 || !hasEnvNeeds(manifest)) return void 0;
18417
19609
  const common = {
18418
- shell,
19610
+ seam,
18419
19611
  runtimeCommands,
18420
19612
  force
18421
19613
  };
@@ -18435,7 +19627,7 @@ async function apply(ctx, config) {
18435
19627
  deniedTools: (context) => deniedNow(context),
18436
19628
  logger: ctx.logger
18437
19629
  });
18438
- const pluginTools = shell === void 0 ? void 0 : adapters.plugins.createUnits({
19630
+ const pluginTools = seam === void 0 ? void 0 : adapters.plugins.createUnits({
18439
19631
  registry: units,
18440
19632
  rows: () => ctx.get("workbenchPanels")?.listPlugins(null) ?? [],
18441
19633
  declared: (plugin) => adapters.probe.declaredPluginCommands(profileDir).get(plugin) ?? /* @__PURE__ */ new Set(),
@@ -18510,6 +19702,7 @@ async function apply(ctx, config) {
18510
19702
  probe: (unit, options) => probeOne(unit, options?.force ?? false),
18511
19703
  values,
18512
19704
  surface,
19705
+ ...seam === void 0 ? {} : { seam },
18513
19706
  longTasks: {
18514
19707
  scope: process.env["DSH_HOME"] ?? "",
18515
19708
  bump: (names) => facts.bump([...names, LONG_TASKS_ROUTE]),
@@ -18593,12 +19786,12 @@ async function apply(ctx, config) {
18593
19786
  }
18594
19787
  rebindPost();
18595
19788
  }
18596
- if (shell === void 0) ctx.logger?.warn?.("[dsh-workbench] 没有 shell 服务:需要执行缝的套件会降级");
19789
+ if (seam === void 0) ctx.logger?.warn?.("[dsh-workbench] 没有 shell 服务:需要执行缝的套件会降级");
18597
19790
  else {
18598
19791
  adapters.probe.registerPreflight({
18599
19792
  routes,
18600
19793
  ctx,
18601
- shell,
19794
+ seam,
18602
19795
  runtimeCommands,
18603
19796
  runtimeCapabilities: capabilities,
18604
19797
  ...runtimeCheck === void 0 ? {} : { runtimeCheck: () => runtimeCheck(ctx) },
@@ -18608,7 +19801,7 @@ async function apply(ctx, config) {
18608
19801
  routes.capability = capabilityHandler(async (unit, force) => {
18609
19802
  if (unit === void 0) {
18610
19803
  const common = {
18611
- shell,
19804
+ seam,
18612
19805
  runtimeCommands,
18613
19806
  force
18614
19807
  };