@dsh-workbench/core 1.1.12 → 1.1.14-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
- import path, { basename, dirname, join, relative, resolve, sep } from "node:path";
1
+ import path, { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
2
2
  import dayjs from "dayjs";
3
3
  import fs, { accessSync, constants, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, unlinkSync, writeFileSync } from "node:fs";
4
- import "node:os";
4
+ import { homedir } from "node:os";
5
5
  import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  import utc from "dayjs/plugin/utc.js";
7
7
  import { createHash, randomUUID } from "node:crypto";
@@ -406,16 +406,22 @@ var DAY = 864e5;
406
406
  365 * DAY;
407
407
  //#endregion
408
408
  //#region ../shared/dist/lib/board-fact.js
409
+ function normalizePath(value) {
410
+ const forward = value.replace(/\\/g, "/").replace(/\/+$/g, "");
411
+ return /^[A-Za-z]:\//.test(forward) || forward.startsWith("//") ? forward.toLowerCase() : forward;
412
+ }
409
413
  /**
410
414
  * 判一个路径落不落在这个工作区里(按路径前缀比,不是字符串包含)。
411
415
  * @param root - 工作区根路径。
412
416
  * @param target - 要判的路径。
413
- * @returns 同一个路径或在其下给 `true`;`root` 末尾带不带斜杠都认(`/a` 不匹配 `/ab`)。
417
+ * @returns 同一个路径或在其下给 `true`;分隔符先归一(Windows 的 `\` 与 `/` 都认),
418
+ * 盘符路径按大小写不敏感比;`root` 末尾带不带斜杠都认(`/a` 不匹配 `/ab`)。
414
419
  */
415
420
  function insideWorkshop(root, target) {
416
- if (target === root) return true;
417
- const base = root.endsWith("/") ? root : `${root}/`;
418
- return target.startsWith(base);
421
+ const base = normalizePath(root);
422
+ const real = normalizePath(target);
423
+ if (real === base) return true;
424
+ return real.startsWith(base === "" ? "/" : `${base}/`);
419
425
  }
420
426
  /**
421
427
  * 把探针读数归成左栏那句人话的档位。
@@ -596,9 +602,9 @@ function stringArrayProblems(at, value) {
596
602
  function declarationProblemsOf(subject, raw) {
597
603
  if (!isRecord(raw)) return [`${subject}: dsh.workbench 不是一个对象 —— ${JSON.stringify(raw)}`];
598
604
  const version = raw.apiVersion;
599
- if (version === void 0) return [`${subject}: dsh.workbench 没有 apiVersion —— 协议从 v3 起,写 \`"apiVersion": 3\`。缺它 = 外壳不认这个模块`];
605
+ if (version === void 0) return [`${subject}: dsh.workbench 没有 apiVersion —— 协议从 v3 起,写 \`"apiVersion": 3\`。缺它 = 外壳不认这个模块(\`dsh-wb-module migrate\` 能自动补这一项)`];
600
606
  if (!Number.isInteger(version) || !SUPPORTED_API_VERSIONS.includes(version)) {
601
- const hint = version === 2 ? " —— v3 把卡片标题挪成了顶层 `title`(`{ \"zh\": \"…\", \"en\": \"…\" }` 字面量):把 `card.titleKey` 删掉、在顶层写 title,再把 apiVersion 改成 3" : "";
607
+ const hint = version === 2 ? " —— v3 把卡片标题挪成了顶层 `title`(`{ \"zh\": \"…\", \"en\": \"…\" }` 字面量):把 `card.titleKey` 删掉、在顶层写 title,再把 apiVersion 改成 3" : " —— 先跑 `dsh-wb-module migrate` 把版本号改到 3,再按 check 的提示改源码";
602
608
  return [`${subject}: dsh.workbench.apiVersion 是 ${JSON.stringify(version)},这里只认 ${SUPPORTED_API_VERSIONS.join(" / ")}(要整数)${hint}`];
603
609
  }
604
610
  const problems = [];
@@ -843,37 +849,72 @@ function refusalReason() {
843
849
  //#endregion
844
850
  //#region ../shared/dist/host/shell.js
845
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
+ };
863
+ }
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);
873
+ }
874
+ }
846
875
  async function runShell(shell, command, options = {}) {
847
- const { workdir, timeoutMs = GIT_TIMEOUT_MS, signal } = options;
876
+ const { workdir, timeoutMs = GIT_TIMEOUT_MS, signal, env } = options;
848
877
  const spec = shell.resolve({
849
878
  command,
850
879
  ...workdir === void 0 ? {} : { workdir },
851
880
  timeoutMs,
852
881
  onExpiry: "kill",
853
882
  stdoutMaxBytes: DEFAULT_MAX_BYTES$3,
854
- ...signal === void 0 ? {} : { signal }
883
+ ...signal === void 0 ? {} : { signal },
884
+ ...env === void 0 ? {} : { env }
855
885
  });
856
- const result = await (await shell.execute(spec)).result();
857
- return {
858
- exitCode: result.exitCode,
859
- timedOut: result.timedOut === true,
860
- aborted: result.aborted === true,
861
- stdout: result.stdout.text,
862
- stderr: result.stderr.text,
863
- truncated: result.stdout.truncated || result.stderr.truncated
864
- };
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();
865
898
  }
866
899
  //#endregion
867
900
  //#region ../shared/dist/host/shell-text.js
868
901
  /**
869
- * 按目标平台把一个值裹成 shell 字面量:POSIX 用单引号,win32 用双引号(cmd 与 PowerShell 都认)。
902
+ * 按目标平台判 shell 种类:win32 是 pwsh(dsh 的 Windows 执行器就是 PowerShell),其余是 POSIX。
903
+ * @param platform - 目标平台,默认当前 `process.platform`。
904
+ * @returns `'pwsh'` 或 `'posix'`。
905
+ */
906
+ function shellKindOf(platform = process.platform) {
907
+ return platform === "win32" ? "pwsh" : "posix";
908
+ }
909
+ /**
910
+ * 按目标平台把一个值裹成 shell 字面量:POSIX 用单引号并按 `'\''` 逃逸,pwsh 也用单引号但按 `''` 逃逸。
870
911
  * @param value - 任意值;不是字符串的先 `String()`。
871
912
  * @param platform - 目标平台,默认当前 `process.platform`。
872
913
  * @returns 可安全拼进该平台 shell 命令的一段文本;含空格 / 元字符的值不会被当成语法。
873
914
  */
874
915
  function quoteShellArgFor(value, platform = process.platform) {
875
916
  const text = String(value);
876
- if (platform === "win32") return `"${text.replace(/"/g, "\\\"")}"`;
917
+ if (shellKindOf(platform) === "pwsh") return `'${text.replace(/'/g, "''")}'`;
877
918
  return `'${text.replace(/'/g, `'\\''`)}'`;
878
919
  }
879
920
  /**
@@ -4330,14 +4371,17 @@ function sessionIdOf(exec) {
4330
4371
  //#region ../shared/dist/host/host-kit.js
4331
4372
  /**
4332
4373
  * 建一份宿主 kit;每个挂载点一份,没有模块级状态。
4333
- * @returns flow / sessionIdOf / sessionWorkspace / libInstallCommand。
4374
+ * @param parts.libInstallCommand 拼「装这个共用依赖」的那条命令。
4375
+ * @param parts.surface 宿主平台事实;由组合根按 profile 与宿主进程算好再交进来。
4376
+ * @returns flow / sessionIdOf / sessionWorkspace / libInstallCommand / surface。
4334
4377
  */
4335
4378
  function createHostKit(parts) {
4336
4379
  return {
4337
4380
  flow: createFlowHost(),
4338
4381
  sessionIdOf,
4339
4382
  sessionWorkspace,
4340
- libInstallCommand: parts.libInstallCommand
4383
+ libInstallCommand: parts.libInstallCommand,
4384
+ surface: parts.surface
4341
4385
  };
4342
4386
  }
4343
4387
  //#endregion
@@ -4727,15 +4771,15 @@ function createValueHandoff(options = {}) {
4727
4771
  function dshHome() {
4728
4772
  const configured = process.env.DSH_HOME?.trim();
4729
4773
  if (configured !== void 0 && configured !== "") return configured;
4730
- return `${process.env.HOME ?? ""}/.dsh`;
4774
+ return join(homedir(), ".dsh");
4731
4775
  }
4732
4776
  function workbenchRoot() {
4733
4777
  const configured = process.env.DSH_WORKBENCH_HOME?.trim();
4734
4778
  if (configured !== void 0 && configured !== "") return configured;
4735
- return `${dshHome()}/workbench`;
4779
+ return join(dshHome(), "workbench");
4736
4780
  }
4737
4781
  function workspacesRoot() {
4738
- return `${workbenchRoot()}/workspaces`;
4782
+ return join(workbenchRoot(), "workspaces");
4739
4783
  }
4740
4784
  //#endregion
4741
4785
  //#region ../shared/dist/host/workspace-store.js
@@ -4747,11 +4791,15 @@ function workspaceKey(workspace) {
4747
4791
  const abs = path.resolve(workspace);
4748
4792
  return `${slug(path.basename(abs))}-${createHash("sha1").update(abs).digest("hex").slice(0, 8)}`;
4749
4793
  }
4750
- function inside(dir, name) {
4794
+ function safeName(name) {
4751
4795
  const trimmed = name.trim();
4752
4796
  if (trimmed === "" || trimmed === "." || trimmed === "..") throw new Error(`模块文件夹里只接受相对文件名,收到的是「${name}」`);
4753
- if (trimmed.includes("\0") || path.isAbsolute(trimmed)) throw new Error(`模块只能写自己的文件夹:不接受绝对路径「${name}」`);
4754
- const full = path.resolve(dir, trimmed);
4797
+ if (trimmed.includes("\0") || path.isAbsolute(trimmed) || /^[A-Za-z]:[\\/]/.test(trimmed)) throw new Error(`模块只能写自己的文件夹:不接受绝对路径「${name}」`);
4798
+ if (trimmed.replace(/\\/g, "/").split("/").includes("..")) throw new Error(`模块只能写自己的文件夹:路径越界「${name}」`);
4799
+ return trimmed;
4800
+ }
4801
+ function inside(dir, name) {
4802
+ const full = path.resolve(dir, safeName(name));
4755
4803
  if (full !== dir && !full.startsWith(dir + path.sep)) throw new Error(`模块只能写自己的文件夹:路径越界「${name}」`);
4756
4804
  return full;
4757
4805
  }
@@ -4760,15 +4808,15 @@ function memoryStore(dir) {
4760
4808
  return {
4761
4809
  dir,
4762
4810
  read: (name) => {
4763
- inside(dir, name);
4811
+ safeName(name);
4764
4812
  return files.get(name) ?? null;
4765
4813
  },
4766
4814
  write: (name, value) => {
4767
- inside(dir, name);
4815
+ safeName(name);
4768
4816
  files.set(name, value);
4769
4817
  },
4770
4818
  remove: (name) => {
4771
- inside(dir, name);
4819
+ safeName(name);
4772
4820
  files.delete(name);
4773
4821
  },
4774
4822
  list: () => [...files.keys()].sort()
@@ -4849,7 +4897,7 @@ var TARGET_FLAGS = [
4849
4897
  "--dir",
4850
4898
  "--store-dir"
4851
4899
  ];
4852
- var INSTALL_VERBS = /^\s*npm\s+(i|install|add)\b/;
4900
+ var INSTALL_VERBS = /^\s*(?:npm\s+(?:i|install|add)|pnpm\s+(?:add|install))\b/;
4853
4901
  var NPM_PATH = /^npm (?:error|ERR!) path (.+)$/m;
4854
4902
  function commandTokens(command) {
4855
4903
  const tokens = [];
@@ -6779,14 +6827,31 @@ function bundleInspectHandler(ctx) {
6779
6827
  }
6780
6828
  //#endregion
6781
6829
  //#region src/shared/api/host/git-exec.ts
6782
- var GIT_NO_PROMPT_ENV = {
6783
- GIT_TERMINAL_PROMPT: "0",
6784
- GIT_ASKPASS: "/usr/bin/false",
6785
- SSH_ASKPASS: "/usr/bin/false",
6786
- SSH_ASKPASS_REQUIRE: "never",
6787
- GCM_INTERACTIVE: "never",
6788
- GIT_SSH_COMMAND: "ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new"
6789
- };
6830
+ function gitNoPromptEnv(platform = process.platform) {
6831
+ const shared = {
6832
+ GIT_TERMINAL_PROMPT: "0",
6833
+ SSH_ASKPASS_REQUIRE: "never",
6834
+ GCM_INTERACTIVE: "never",
6835
+ GIT_SSH_COMMAND: "ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new"
6836
+ };
6837
+ if (platform === "win32") return shared;
6838
+ return {
6839
+ ...shared,
6840
+ GIT_ASKPASS: "/usr/bin/false",
6841
+ SSH_ASKPASS: "/usr/bin/false"
6842
+ };
6843
+ }
6844
+ function gitNoPromptProcessEnv(parent = process.env, platform = process.platform) {
6845
+ const env = {
6846
+ ...parent,
6847
+ ...gitNoPromptEnv(platform)
6848
+ };
6849
+ if (platform === "win32") {
6850
+ delete env.GIT_ASKPASS;
6851
+ delete env.SSH_ASKPASS;
6852
+ }
6853
+ return env;
6854
+ }
6790
6855
  function withoutAskpassNoise(text) {
6791
6856
  return text.split("\n").filter((line) => !/unable to read askpass response/i.test(line)).join("\n").trim();
6792
6857
  }
@@ -6796,10 +6861,7 @@ var DEFAULT_MAX_BYTES$2 = 8388608;
6796
6861
  var runGitProcess = async (args, options = {}) => {
6797
6862
  const result = await execa("git", [...args], {
6798
6863
  ...options.cwd === void 0 ? {} : { cwd: options.cwd },
6799
- env: {
6800
- ...process.env,
6801
- ...GIT_NO_PROMPT_ENV
6802
- },
6864
+ env: gitNoPromptProcessEnv(),
6803
6865
  stdin: "ignore",
6804
6866
  reject: false,
6805
6867
  ...options.timeoutMs === void 0 ? {} : { timeout: options.timeoutMs },
@@ -8363,7 +8425,10 @@ function asText$1(value) {
8363
8425
  return typeof value === "string" ? value.trim() : "";
8364
8426
  }
8365
8427
  function insideRoot(root, target) {
8366
- return insideWorkshop(existsSync(root) ? realpathSync(root) : resolve(root), existsSync(target) ? realpathSync(target) : resolve(target));
8428
+ const base = existsSync(root) ? realpathSync(root) : resolve(root);
8429
+ const real = existsSync(target) ? realpathSync(target) : resolve(target);
8430
+ const rel = relative(base, real);
8431
+ return rel === "" || rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute(rel);
8367
8432
  }
8368
8433
  function manifestAt(dir) {
8369
8434
  try {
@@ -13115,7 +13180,7 @@ function sourceProblemText(problem, env) {
13115
13180
  //#region src/modules/panels/model/plugin-name.ts
13116
13181
  function pluginDirOf(entry, files) {
13117
13182
  if (entry.local === true || files?.dir === void 0) return entry.package;
13118
- return `${files.dir}/${entry.package}`;
13183
+ return join(files.dir, entry.package);
13119
13184
  }
13120
13185
  function pluginOnDisk(entry, files) {
13121
13186
  return pluginAt(pluginDirOf(entry, files), files?.manifest ?? "") !== void 0;
@@ -17337,7 +17402,10 @@ function mountUnits(units, deps) {
17337
17402
  message: "没有注入 DataReader:模块只能走 deps.data 读数据"
17338
17403
  }
17339
17404
  });
17340
- const hostKit = createHostKit({ libInstallCommand: (options) => adaptersOf(deps.ctx).supply.libInstallCommand(options) });
17405
+ const hostKit = createHostKit({
17406
+ libInstallCommand: (options) => adaptersOf(deps.ctx).supply.libInstallCommand(options),
17407
+ surface: deps.surface ?? hostSurface()
17408
+ });
17341
17409
  const data = deps.data ?? {
17342
17410
  read: missingData,
17343
17411
  request: missingData,
@@ -18269,10 +18337,13 @@ async function apply(ctx, config) {
18269
18337
  const capabilities = adapters.runtime.capabilities(ctx);
18270
18338
  const runtimeProfile = adapters.runtime.profile?.(ctx) ?? null;
18271
18339
  const runtimeCheck = adapters.runtime.check;
18340
+ const profileDir = adapters.profile.dirOf(ctx);
18341
+ const surface = hostSurface({ profile: profileDir ?? void 0 });
18272
18342
  routes.health = async (search) => ({
18273
18343
  ...await baseHealth?.(search),
18274
18344
  capabilities,
18275
18345
  runtimeProfile,
18346
+ surface,
18276
18347
  adapterSet: {
18277
18348
  configured: adapterSpec,
18278
18349
  active: adapters.id,
@@ -18286,7 +18357,6 @@ async function apply(ctx, config) {
18286
18357
  }
18287
18358
  });
18288
18359
  const renderers = /* @__PURE__ */ new Map();
18289
- const profileDir = adapters.profile.dirOf(ctx);
18290
18360
  const packageRoot = adapters.runtime.packageRoot(import.meta.url);
18291
18361
  const reconcileProfile = async () => {
18292
18362
  const hmr = ctx.get("hmr");
@@ -18439,6 +18509,7 @@ async function apply(ctx, config) {
18439
18509
  commands: adapters.commands,
18440
18510
  probe: (unit, options) => probeOne(unit, options?.force ?? false),
18441
18511
  values,
18512
+ surface,
18442
18513
  longTasks: {
18443
18514
  scope: process.env["DSH_HOME"] ?? "",
18444
18515
  bump: (names) => facts.bump([...names, LONG_TASKS_ROUTE]),
@@ -9,10 +9,12 @@ import * as longTaskModule from '../shared/hooks/use-long-task';
9
9
  import * as capabilityGateModule from '../shared/hooks/use-capability-gate';
10
10
  import * as unitBoardModule from '../shared/hooks/use-unit-board';
11
11
  import * as visibleBusyModule from '../shared/hooks/use-visible-busy';
12
+ import { usePlatform } from '../shared/hooks/use-platform';
12
13
  import * as blockerStateModule from '../shared/components/BlockerState';
13
- import { ActivityAction, ActivityBlock, ActivityLine, ApprovalDetail, ApprovalPlan, Card, CardAnchor, CardBoundary, CardHead, CenteredState, ChoiceGroup, ConfirmBar, EmptyState, Field, Form, FormActions, FormRow, FormRows, IconBrandMark, IconConversation, IconModuleMark, IconNpm, IconOpencli, IconPanelScreen, Link, LongTaskRow, longTaskIsLive, Note, Row, Rows, SaveBar, SelectField, SettingsSection, SkeletonRows, Spinner, Stack, StateBlock, TextField, apiGet, apiPost, blockerOfCapability, blockerOfError, cardIdOf, cx, defaultSelectFilter, duplicateKeysOf, errorReasonOf, excerpt, factOfRoute, filterSelectOptions, firstProblemOf, formFirstProblemOf, formValuesOf, holdBusy, parseJsonLoose, parseLocalTimestamp, readText, relativeTime, relativeTimeAgo, relativeTimeBucket, relativeTimeCompact, revealFirstProblem, searchTextOf, selectCursorValue, selectEntries, shortenPath, stepSelectCursor, writeText } from '@dsh-workbench/shared';
14
+ import { ActivityAction, ActivityBlock, ActivityLine, ApprovalDetail, ApprovalPlan, Card, CardAnchor, CardBoundary, CardHead, CenteredState, ChoiceGroup, ConfirmBar, EmptyState, Field, Form, FormActions, FormRow, FormRows, IconBrandMark, IconConversation, IconModuleMark, IconNpm, IconOpencli, IconPanelScreen, Link, LongTaskRow, longTaskIsLive, Note, Row, Rows, SaveBar, SelectField, SettingsSection, SkeletonRows, Spinner, Stack, StateBlock, TextField, apiGet, apiPost, blockerOfCapability, blockerOfError, cardIdOf, cx, defaultSelectFilter, duplicateKeysOf, errorReasonOf, excerpt, factOfRoute, filterSelectOptions, firstProblemOf, formFirstProblemOf, formValuesOf, holdBusy, parseJsonLoose, parseLocalTimestamp, pathBase, readText, relativeTime, relativeTimeAgo, relativeTimeBucket, relativeTimeCompact, revealFirstProblem, searchTextOf, selectCursorValue, selectEntries, shortenPath, stepSelectCursor, writeText } from '@dsh-workbench/shared';
14
15
  export declare const kit: {
15
16
  hooks: {
17
+ usePlatform: typeof usePlatform;
16
18
  heldPhase(phase: visibleBusyModule.HeldPhase, refreshing: boolean): visibleBusyModule.HeldPhase;
17
19
  busyView(active: boolean, visible: boolean): visibleBusyModule.BusyView;
18
20
  useVisibleBusy(active: boolean, floorMs: number): visibleBusyModule.BusyView;
@@ -121,6 +123,7 @@ export declare const kit: {
121
123
  holdBusy: typeof holdBusy;
122
124
  parseJsonLoose: typeof parseJsonLoose;
123
125
  parseLocalTimestamp: typeof parseLocalTimestamp;
126
+ pathBase: typeof pathBase;
124
127
  readText: typeof readText;
125
128
  relativeTime: typeof relativeTime;
126
129
  relativeTimeAgo: typeof relativeTimeAgo;
@@ -2,6 +2,11 @@ import type { Translate } from '@dsh-workbench/shared';
2
2
  import type { PreflightBridgeIssue, PreflightGuidance, PreflightSnapshot, PreflightStep } from '@dsh-workbench/shared/host';
3
3
  import type { RuntimeProfile } from '@dsh-workbench/shared';
4
4
  export declare function failedStepText(guidance: PreflightGuidance | undefined, t: Translate): string;
5
+ export interface GuidanceText {
6
+ readonly why: string;
7
+ readonly step: string;
8
+ }
9
+ export declare function guidanceTextOf(guidance: PreflightGuidance | undefined, t: Translate): GuidanceText;
5
10
  export declare function stepDetailText(step: PreflightStep, snapshot: PreflightSnapshot, t: Translate): string;
6
11
  export declare function gateHintOf(snapshot: PreflightSnapshot, t: Translate): string;
7
12
  export declare function showsRawDetails(snapshot: PreflightSnapshot): boolean;
@@ -1,6 +1,7 @@
1
1
  export type { UnitModuleFace, UnitMountDeps, UnitMountResult } from '@dsh-workbench/shared/host';
2
2
  export type { FlowAction, FlowActionContext, FlowActionRegistry, FlowActionStep, FlowCheck, FlowDefinition, FlowForEach, FlowHost, FlowPoll, FlowPredicate, FlowProblem, FlowResult, FlowRetry, FlowRunOptions, FlowScope, FlowStep, FlowStepEvent, FlowSubFlowStep, FlowValue, HostKit, } from '@dsh-workbench/shared/host';
3
3
  export type { ExtensionCaller, ExtensionCallOptions, ExtensionNeed, ExtensionSession, CommandReader, ExtensionValue, GitReader, LibReader } from '@dsh-workbench/shared';
4
- export type { BoardModuleProps as BoardProps, CallOverrides, DataError, DataReader, DataRequest, DataResult, RequestRecipe, UnitClientApi, UnitClientFace, } from '@dsh-workbench/shared';
4
+ export type { BoardModuleProps as BoardProps, CallOverrides, DataError, DataReader, DataRequest, DataResult, DshToolDefinition, DshToolRegistry, DshToolRunContext, RequestRecipe, UnitClientApi, UnitClientFace, } from '@dsh-workbench/shared';
5
5
  export type { DshClientContext as ClientContext, DshHostContext as HostContext } from '@dsh-workbench/shared';
6
+ export type { ClientCapabilities, ClientShell, ClientSurface, Deployment, HostCapabilities, HostOs, HostRuntime, HostSurface, ProfileName, SelfUpdateCli, ToolchainPlan, } from '@dsh-workbench/shared';
6
7
  export type { UnitRegistry } from '../host/unit-registry.js';
@@ -1,4 +1,4 @@
1
- import type { DshHostContext } from '@dsh-workbench/shared';
1
+ import type { DshHostContext, HostSurface } from '@dsh-workbench/shared';
2
2
  import type { UnitManifest } from '@dsh-workbench/shared/host';
3
3
  import { type RouteTable } from '@dsh-workbench/shared/host';
4
4
  import type { ContextRenderer, LongTaskBase, LongTaskChange, LongTaskRegistry, UnitMountDeps, UnitMountResult, UnitCall } from '@dsh-workbench/shared/host';
@@ -68,6 +68,7 @@ export declare function mountUnits(units: readonly HostUnit[], deps: {
68
68
  probe?: UnitMountDeps['probe'];
69
69
  store?: UnitMountDeps['store'];
70
70
  values?: UnitMountDeps['values'];
71
+ surface?: HostSurface | undefined;
71
72
  longTasks?: {
72
73
  readonly scope: string;
73
74
  readonly bump: (facts: readonly string[]) => void;
@@ -1,3 +1,3 @@
1
- export declare const GIT_NO_PROMPT_ENV: Readonly<Record<string, string>>;
2
- export declare function gitNoPromptPrefix(): string;
1
+ export declare function gitNoPromptEnv(platform?: NodeJS.Platform): Readonly<Record<string, string>>;
2
+ export declare function gitNoPromptProcessEnv(parent?: NodeJS.ProcessEnv, platform?: NodeJS.Platform): NodeJS.ProcessEnv;
3
3
  export declare function withoutAskpassNoise(text: string): string;
@@ -1,6 +1,7 @@
1
- import type { ApiResponse, RuntimeCapabilities, RuntimeProfile } from '@dsh-workbench/shared';
1
+ import type { ApiResponse, HostSurface, RuntimeCapabilities, RuntimeProfile } from '@dsh-workbench/shared';
2
2
  export type RuntimeCapabilitiesPayload = {
3
3
  readonly capabilities: RuntimeCapabilities;
4
4
  readonly runtimeProfile?: RuntimeProfile | null;
5
+ readonly surface?: HostSurface | null;
5
6
  };
6
7
  export declare function readRuntimeCapabilities(): Promise<ApiResponse<RuntimeCapabilitiesPayload>>;
@@ -0,0 +1,13 @@
1
+ import type { ClientSurface, HostSurface } from '@dsh-workbench/shared';
2
+ /**
3
+ * 卡片看到的平台事实:客户端侧现算,宿主侧从 health 读;没读到之前宿主侧是 `undefined`。
4
+ */
5
+ export interface PlatformView {
6
+ readonly client: ClientSurface;
7
+ readonly host: HostSurface | undefined;
8
+ }
9
+ /**
10
+ * 卡片里拿平台事实的唯一入口:部署形态 / 能力 / 操作系统的差异按这里的值分叉,别自己读 `window` 或 `process`。
11
+ * @returns `client` 是浏览器侧事实(deployment / shell / 宿主机 OS / capabilities),`host` 是宿主侧 surface(可能还没读到)。
12
+ */
13
+ export declare function usePlatform(): PlatformView;
@@ -1,6 +1,8 @@
1
- import type { RuntimeCapabilities, RuntimeProfile } from '@dsh-workbench/shared';
1
+ import type { HostSurface, RuntimeCapabilities, RuntimeProfile } from '@dsh-workbench/shared';
2
2
  export declare function pluginMarketOf(capabilities: RuntimeCapabilities | undefined): boolean;
3
3
  export declare function runtimeCapabilitiesValue(): RuntimeCapabilities | undefined;
4
4
  export declare function runtimeProfileValue(): RuntimeProfile | undefined;
5
+ export declare function hostSurfaceValue(): HostSurface | undefined;
5
6
  export declare function resetRuntimeCapabilities(): void;
6
7
  export declare function useRuntimeCapabilities(): RuntimeCapabilities | undefined;
8
+ export declare function useHostSurface(): HostSurface | undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-workbench/core",
3
- "version": "1.1.12",
3
+ "version": "1.1.14-beta.0",
4
4
  "description": "个人工作台:Agent 工作台(左栏 + 常驻对话 + 自研右侧数据面板)。",
5
5
  "type": "module",
6
6
  "icon": "./icon.svg",
@@ -76,7 +76,7 @@
76
76
  }
77
77
  },
78
78
  "devDependencies": {
79
- "@arch-guard/core": "^0.11.2",
79
+ "@arch-guard/core": "^0.11.3",
80
80
  "@deepseek-ai/dsh-client-ui-primitives": "0.2.1-rc.2",
81
81
  "@types/node": "^24.19.0",
82
82
  "@types/proper-lockfile": "^4.1.4",
@@ -100,8 +100,8 @@
100
100
  "proper-lockfile": "^4.1.2",
101
101
  "which": "^7.0.0",
102
102
  "write-file-atomic": "^8.0.0",
103
- "@dsh-workbench/adapters-default": "1.1.12",
104
- "@dsh-workbench/shared": "1.1.12"
103
+ "@dsh-workbench/shared": "1.1.14-beta.0",
104
+ "@dsh-workbench/adapters-default": "1.1.14-beta.0"
105
105
  },
106
106
  "license": "MIT",
107
107
  "repository": {
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { spawnSync } from 'node:child_process';
3
+ import { execaSync } from 'execa';
4
4
  import { existsSync, readdirSync } from 'node:fs';
5
5
  import { homedir } from 'node:os';
6
6
  import { dirname, join } from 'node:path';
@@ -56,9 +56,12 @@ function nameProblem(value, source, profiles) {
56
56
  }
57
57
 
58
58
  function runSetup(args) {
59
- const child = spawnSync('bash', [join(PKG_ROOT, 'bin', 'dsh-workbench'), ...args], { stdio: 'inherit' });
60
- if (child.error !== undefined) fail(child.error.message);
61
- process.exit(child.status ?? 1);
59
+ const child = execaSync('bash', [join(PKG_ROOT, 'bin', 'dsh-workbench'), ...args], {
60
+ stdio: 'inherit',
61
+ reject: false,
62
+ });
63
+ if (child.failed) fail(child.shortMessage ?? `bash 起不来(退出码 ${child.exitCode ?? '未知'})`);
64
+ process.exit(child.exitCode ?? 1);
62
65
  }
63
66
 
64
67
  if (!process.stdin.isTTY || !process.stdout.isTTY) {
@@ -648,10 +648,32 @@ EOF
648
648
  - 造一个新模块:`dsh-wb-module init <名字> --dir packages/module-<名字>`
649
649
  (`dsh-wb-module` 挂在 PATH 上、与 cwd 无关;写成 `pnpm exec dsh-wb-module …` 也对)
650
650
  - 每个模块自己的四件事:`pnpm build` / `pnpm typecheck` / `pnpm test` / `pnpm selfcheck`,
651
- 外加 `dsh-wb-module check` 的十条判据。
651
+ 外加 `dsh-wb-module check` 的十三条判据。
652
652
 
653
653
  不要手改这个目录下的 `package.json` / `pnpm-workspace.yaml` / `.npmrc`:
654
654
  它们是 `dsh-workbench setup` 生成的,模块要加依赖就加在**模块自己**的 `package.json` 里。
655
+ EOF
656
+ cat >"$DIY_DIR/AGENTS.md" <<'EOF'
657
+ # DIY 工坊 · agent 说明
658
+
659
+ 模块住在 `packages/`,一个模块一个目录。开新模块:
660
+
661
+ dsh-wb-module init <名字> --dir packages/module-<名字>
662
+
663
+ ## 依赖
664
+
665
+ 依赖装在这个工坊根(`packages/` 的上一层),已经装好;模块包里不要再 `pnpm install`:
666
+
667
+ - 缺依赖 / 工具链换代:在**宿主终端**到工坊根跑 `pnpm install`;`dsh-workbench setup` 也做这件事。
668
+ - 会话沙箱里直接跑 `pnpm install` 会 EPERM:link 目标在可写根之外。
669
+ - 模块自己加依赖,写进**模块自己**的 `package.json`,再在工坊根装一次。
670
+
671
+ ## 每个模块
672
+
673
+ `pnpm build` / `pnpm typecheck` / `pnpm test` / `pnpm selfcheck`,外加 `dsh-wb-module check`
674
+ (十三条判据);单跑一条:`dsh-wb-module check --only <判据>`。
675
+
676
+ 这个目录的 `package.json` / `pnpm-workspace.yaml` / `.npmrc` 由 setup 生成,手改会被覆盖。
655
677
  EOF
656
678
  fi
657
679
 
@@ -665,17 +687,24 @@ fi
665
687
  # 用 pnpm 的 overrides 而不是改模块模板:模板里写死 link 路径会把"装到别人机器上"这条弄坏,
666
688
  # overrides 活在**这个工坊**里,模块的 package.json 仍旧是干净的 `^0.1.x`。
667
689
  # 放在第一次 `pnpm install` **之前**,这样一次装就是对的;已存在的工坊再跑一次 setup 也会被修好。
690
+ #
691
+ # devDependencies 与 overrides **必须同一套值、一起重写**:只刷 overrides 的话,老清单里的
692
+ # devDependencies 会永远停在旧路径(实测:dev 指向早已不存在的 `packages/workbench`、overrides
693
+ # 指向 `packages/core`),`init` 读外壳 manifest 时还会踩到悬空链接。
668
694
  if [ "$IN_REPO" = "1" ] && [ -f "$DIY_DIR/package.json" ]; then
669
695
  node - "$DIY_DIR/package.json" "$CORE_SPEC" "$TOOLS_SPEC" "$SHARED_SPEC" <<'NODE'
670
696
  const [file, core, tools, shared] = process.argv.slice(2);
671
697
  const fs = require('node:fs');
672
698
  const manifest = JSON.parse(fs.readFileSync(file, 'utf8'));
699
+ const want = {
700
+ '@dsh-workbench/core': core,
701
+ '@dsh-workbench/module-tools': tools,
702
+ '@dsh-workbench/shared': shared,
703
+ };
704
+ manifest.devDependencies = { ...(manifest.devDependencies ?? {}), ...want };
673
705
  manifest.pnpm = {
674
- overrides: {
675
- '@dsh-workbench/core': core,
676
- '@dsh-workbench/module-tools': tools,
677
- '@dsh-workbench/shared': shared,
678
- },
706
+ ...(manifest.pnpm ?? {}),
707
+ overrides: { ...(manifest.pnpm?.overrides ?? {}), ...want },
679
708
  };
680
709
  fs.writeFileSync(file, `${JSON.stringify(manifest, null, 2)}\n`);
681
710
  NODE
@@ -688,7 +717,16 @@ elif ! command -v pnpm >/dev/null 2>&1; then
688
717
  echo " cd $DIY_DIR && pnpm install" >&2
689
718
  else
690
719
  echo "→ 装工坊依赖(第一次会下载 react / typescript / vitest,几百 MB,之后不再下)"
691
- (cd "$DIY_DIR" && pnpm install 2>&1 | tail -6)
720
+ install_log="${TMPDIR:-/tmp}/dsh-wb-install.$$.log"
721
+ if (cd "$DIY_DIR" && pnpm install >"$install_log" 2>&1); then
722
+ tail -6 "$install_log"
723
+ rm -f "$install_log"
724
+ else
725
+ tail -20 "$install_log" >&2
726
+ rm -f "$install_log"
727
+ echo "→ ⚠️ 装工坊依赖失败。若上面报 EPERM:link 目标在可写根之外 —— 到宿主终端重跑 setup(它由人跑、不受会话沙箱)。" >&2
728
+ echo " 装好前工坊里的模块跑不了 pnpm build / check;恢复后在工坊根重跑 pnpm install 即可。" >&2
729
+ fi
692
730
  fi
693
731
 
694
732
  # 完整的这一趟跑完:先挂指引(不管有没有基准 —— setup 那趟也要建起来),再看要不要自愈。