@dsh-workbench/core 1.1.13 → 1.1.14-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/bin/seed-workshop-windows.mjs +10 -13
- package/client.js +158 -132
- package/dist/client/client.body.js +158 -132
- package/dist/client/index.js +158 -132
- package/dist/host/index.js +85 -40
- package/dist/types/app/client.d.ts +4 -1
- package/dist/types/kernel/contract/index.d.ts +2 -1
- package/dist/types/kernel/host/mount-units.d.ts +2 -1
- package/dist/types/shared/api/host/git-exec.d.ts +2 -2
- package/dist/types/shared/hooks/runtime-queries.d.ts +2 -1
- package/dist/types/shared/hooks/use-platform.d.ts +13 -0
- package/dist/types/shared/hooks/use-runtime-capabilities.d.ts +3 -1
- package/package.json +4 -4
- package/scripts/create-workbench.mjs +7 -4
- package/scripts/seed-workshop.sh +45 -7
- package/skills/workbench-module-diy/BUILD.md +33 -24
- package/skills/workbench-module-diy/DATASOURCE.md +3 -2
- package/skills/workbench-module-diy/FAMILY.md +1 -1
- package/skills/workbench-module-diy/FLOW.md +33 -7
- package/skills/workbench-module-diy/KIT.md +3 -1
- package/skills/workbench-module-diy/PANEL.md +7 -6
- package/skills/workbench-module-diy/REVIEW.md +9 -8
- package/skills/workbench-module-diy/SKILL.md +3 -2
- package/skills/workbench-module-diy/STEPS.md +1 -1
- package/skills/workbench-module-diy/TOOLS.md +56 -1
- package/skills/workbench-module-diy/WIRING.md +2 -0
- package/skills/workbench-module-diy/WRITES.md +1 -1
package/dist/host/index.js
CHANGED
|
@@ -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
|
|
417
|
+
* @returns 同一个路径或在其下给 `true`;分隔符先归一(Windows 的 `\` 与 `/` 都认),
|
|
418
|
+
* 盘符路径按大小写不敏感比;`root` 末尾带不带斜杠都认(`/a` 不匹配 `/ab`)。
|
|
414
419
|
*/
|
|
415
420
|
function insideWorkshop(root, target) {
|
|
416
|
-
|
|
417
|
-
const
|
|
418
|
-
|
|
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 = [];
|
|
@@ -867,14 +873,15 @@ async function settleWithin(work, limitMs) {
|
|
|
867
873
|
}
|
|
868
874
|
}
|
|
869
875
|
async function runShell(shell, command, options = {}) {
|
|
870
|
-
const { workdir, timeoutMs = GIT_TIMEOUT_MS, signal } = options;
|
|
876
|
+
const { workdir, timeoutMs = GIT_TIMEOUT_MS, signal, env } = options;
|
|
871
877
|
const spec = shell.resolve({
|
|
872
878
|
command,
|
|
873
879
|
...workdir === void 0 ? {} : { workdir },
|
|
874
880
|
timeoutMs,
|
|
875
881
|
onExpiry: "kill",
|
|
876
882
|
stdoutMaxBytes: DEFAULT_MAX_BYTES$3,
|
|
877
|
-
...signal === void 0 ? {} : { signal }
|
|
883
|
+
...signal === void 0 ? {} : { signal },
|
|
884
|
+
...env === void 0 ? {} : { env }
|
|
878
885
|
});
|
|
879
886
|
return await settleWithin((async () => {
|
|
880
887
|
const result = await (await shell.execute(spec)).result();
|
|
@@ -892,14 +899,22 @@ async function runShell(shell, command, options = {}) {
|
|
|
892
899
|
//#endregion
|
|
893
900
|
//#region ../shared/dist/host/shell-text.js
|
|
894
901
|
/**
|
|
895
|
-
*
|
|
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 也用单引号但按 `''` 逃逸。
|
|
896
911
|
* @param value - 任意值;不是字符串的先 `String()`。
|
|
897
912
|
* @param platform - 目标平台,默认当前 `process.platform`。
|
|
898
913
|
* @returns 可安全拼进该平台 shell 命令的一段文本;含空格 / 元字符的值不会被当成语法。
|
|
899
914
|
*/
|
|
900
915
|
function quoteShellArgFor(value, platform = process.platform) {
|
|
901
916
|
const text = String(value);
|
|
902
|
-
if (platform === "
|
|
917
|
+
if (shellKindOf(platform) === "pwsh") return `'${text.replace(/'/g, "''")}'`;
|
|
903
918
|
return `'${text.replace(/'/g, `'\\''`)}'`;
|
|
904
919
|
}
|
|
905
920
|
/**
|
|
@@ -4356,14 +4371,17 @@ function sessionIdOf(exec) {
|
|
|
4356
4371
|
//#region ../shared/dist/host/host-kit.js
|
|
4357
4372
|
/**
|
|
4358
4373
|
* 建一份宿主 kit;每个挂载点一份,没有模块级状态。
|
|
4359
|
-
* @
|
|
4374
|
+
* @param parts.libInstallCommand 拼「装这个共用依赖」的那条命令。
|
|
4375
|
+
* @param parts.surface 宿主平台事实;由组合根按 profile 与宿主进程算好再交进来。
|
|
4376
|
+
* @returns flow / sessionIdOf / sessionWorkspace / libInstallCommand / surface。
|
|
4360
4377
|
*/
|
|
4361
4378
|
function createHostKit(parts) {
|
|
4362
4379
|
return {
|
|
4363
4380
|
flow: createFlowHost(),
|
|
4364
4381
|
sessionIdOf,
|
|
4365
4382
|
sessionWorkspace,
|
|
4366
|
-
libInstallCommand: parts.libInstallCommand
|
|
4383
|
+
libInstallCommand: parts.libInstallCommand,
|
|
4384
|
+
surface: parts.surface
|
|
4367
4385
|
};
|
|
4368
4386
|
}
|
|
4369
4387
|
//#endregion
|
|
@@ -4753,15 +4771,15 @@ function createValueHandoff(options = {}) {
|
|
|
4753
4771
|
function dshHome() {
|
|
4754
4772
|
const configured = process.env.DSH_HOME?.trim();
|
|
4755
4773
|
if (configured !== void 0 && configured !== "") return configured;
|
|
4756
|
-
return
|
|
4774
|
+
return join(homedir(), ".dsh");
|
|
4757
4775
|
}
|
|
4758
4776
|
function workbenchRoot() {
|
|
4759
4777
|
const configured = process.env.DSH_WORKBENCH_HOME?.trim();
|
|
4760
4778
|
if (configured !== void 0 && configured !== "") return configured;
|
|
4761
|
-
return
|
|
4779
|
+
return join(dshHome(), "workbench");
|
|
4762
4780
|
}
|
|
4763
4781
|
function workspacesRoot() {
|
|
4764
|
-
return
|
|
4782
|
+
return join(workbenchRoot(), "workspaces");
|
|
4765
4783
|
}
|
|
4766
4784
|
//#endregion
|
|
4767
4785
|
//#region ../shared/dist/host/workspace-store.js
|
|
@@ -4773,11 +4791,15 @@ function workspaceKey(workspace) {
|
|
|
4773
4791
|
const abs = path.resolve(workspace);
|
|
4774
4792
|
return `${slug(path.basename(abs))}-${createHash("sha1").update(abs).digest("hex").slice(0, 8)}`;
|
|
4775
4793
|
}
|
|
4776
|
-
function
|
|
4794
|
+
function safeName(name) {
|
|
4777
4795
|
const trimmed = name.trim();
|
|
4778
4796
|
if (trimmed === "" || trimmed === "." || trimmed === "..") throw new Error(`模块文件夹里只接受相对文件名,收到的是「${name}」`);
|
|
4779
|
-
if (trimmed.includes("\0") || path.isAbsolute(trimmed)) throw new Error(`模块只能写自己的文件夹:不接受绝对路径「${name}」`);
|
|
4780
|
-
|
|
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));
|
|
4781
4803
|
if (full !== dir && !full.startsWith(dir + path.sep)) throw new Error(`模块只能写自己的文件夹:路径越界「${name}」`);
|
|
4782
4804
|
return full;
|
|
4783
4805
|
}
|
|
@@ -4786,15 +4808,15 @@ function memoryStore(dir) {
|
|
|
4786
4808
|
return {
|
|
4787
4809
|
dir,
|
|
4788
4810
|
read: (name) => {
|
|
4789
|
-
|
|
4811
|
+
safeName(name);
|
|
4790
4812
|
return files.get(name) ?? null;
|
|
4791
4813
|
},
|
|
4792
4814
|
write: (name, value) => {
|
|
4793
|
-
|
|
4815
|
+
safeName(name);
|
|
4794
4816
|
files.set(name, value);
|
|
4795
4817
|
},
|
|
4796
4818
|
remove: (name) => {
|
|
4797
|
-
|
|
4819
|
+
safeName(name);
|
|
4798
4820
|
files.delete(name);
|
|
4799
4821
|
},
|
|
4800
4822
|
list: () => [...files.keys()].sort()
|
|
@@ -4875,7 +4897,7 @@ var TARGET_FLAGS = [
|
|
|
4875
4897
|
"--dir",
|
|
4876
4898
|
"--store-dir"
|
|
4877
4899
|
];
|
|
4878
|
-
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/;
|
|
4879
4901
|
var NPM_PATH = /^npm (?:error|ERR!) path (.+)$/m;
|
|
4880
4902
|
function commandTokens(command) {
|
|
4881
4903
|
const tokens = [];
|
|
@@ -6805,14 +6827,31 @@ function bundleInspectHandler(ctx) {
|
|
|
6805
6827
|
}
|
|
6806
6828
|
//#endregion
|
|
6807
6829
|
//#region src/shared/api/host/git-exec.ts
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
|
|
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
|
+
}
|
|
6816
6855
|
function withoutAskpassNoise(text) {
|
|
6817
6856
|
return text.split("\n").filter((line) => !/unable to read askpass response/i.test(line)).join("\n").trim();
|
|
6818
6857
|
}
|
|
@@ -6822,10 +6861,7 @@ var DEFAULT_MAX_BYTES$2 = 8388608;
|
|
|
6822
6861
|
var runGitProcess = async (args, options = {}) => {
|
|
6823
6862
|
const result = await execa("git", [...args], {
|
|
6824
6863
|
...options.cwd === void 0 ? {} : { cwd: options.cwd },
|
|
6825
|
-
env:
|
|
6826
|
-
...process.env,
|
|
6827
|
-
...GIT_NO_PROMPT_ENV
|
|
6828
|
-
},
|
|
6864
|
+
env: gitNoPromptProcessEnv(),
|
|
6829
6865
|
stdin: "ignore",
|
|
6830
6866
|
reject: false,
|
|
6831
6867
|
...options.timeoutMs === void 0 ? {} : { timeout: options.timeoutMs },
|
|
@@ -8389,7 +8425,10 @@ function asText$1(value) {
|
|
|
8389
8425
|
return typeof value === "string" ? value.trim() : "";
|
|
8390
8426
|
}
|
|
8391
8427
|
function insideRoot(root, target) {
|
|
8392
|
-
|
|
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);
|
|
8393
8432
|
}
|
|
8394
8433
|
function manifestAt(dir) {
|
|
8395
8434
|
try {
|
|
@@ -13141,7 +13180,7 @@ function sourceProblemText(problem, env) {
|
|
|
13141
13180
|
//#region src/modules/panels/model/plugin-name.ts
|
|
13142
13181
|
function pluginDirOf(entry, files) {
|
|
13143
13182
|
if (entry.local === true || files?.dir === void 0) return entry.package;
|
|
13144
|
-
return
|
|
13183
|
+
return join(files.dir, entry.package);
|
|
13145
13184
|
}
|
|
13146
13185
|
function pluginOnDisk(entry, files) {
|
|
13147
13186
|
return pluginAt(pluginDirOf(entry, files), files?.manifest ?? "") !== void 0;
|
|
@@ -17363,7 +17402,10 @@ function mountUnits(units, deps) {
|
|
|
17363
17402
|
message: "没有注入 DataReader:模块只能走 deps.data 读数据"
|
|
17364
17403
|
}
|
|
17365
17404
|
});
|
|
17366
|
-
const hostKit = createHostKit({
|
|
17405
|
+
const hostKit = createHostKit({
|
|
17406
|
+
libInstallCommand: (options) => adaptersOf(deps.ctx).supply.libInstallCommand(options),
|
|
17407
|
+
surface: deps.surface ?? hostSurface()
|
|
17408
|
+
});
|
|
17367
17409
|
const data = deps.data ?? {
|
|
17368
17410
|
read: missingData,
|
|
17369
17411
|
request: missingData,
|
|
@@ -18295,10 +18337,13 @@ async function apply(ctx, config) {
|
|
|
18295
18337
|
const capabilities = adapters.runtime.capabilities(ctx);
|
|
18296
18338
|
const runtimeProfile = adapters.runtime.profile?.(ctx) ?? null;
|
|
18297
18339
|
const runtimeCheck = adapters.runtime.check;
|
|
18340
|
+
const profileDir = adapters.profile.dirOf(ctx);
|
|
18341
|
+
const surface = hostSurface({ profile: profileDir ?? void 0 });
|
|
18298
18342
|
routes.health = async (search) => ({
|
|
18299
18343
|
...await baseHealth?.(search),
|
|
18300
18344
|
capabilities,
|
|
18301
18345
|
runtimeProfile,
|
|
18346
|
+
surface,
|
|
18302
18347
|
adapterSet: {
|
|
18303
18348
|
configured: adapterSpec,
|
|
18304
18349
|
active: adapters.id,
|
|
@@ -18312,7 +18357,6 @@ async function apply(ctx, config) {
|
|
|
18312
18357
|
}
|
|
18313
18358
|
});
|
|
18314
18359
|
const renderers = /* @__PURE__ */ new Map();
|
|
18315
|
-
const profileDir = adapters.profile.dirOf(ctx);
|
|
18316
18360
|
const packageRoot = adapters.runtime.packageRoot(import.meta.url);
|
|
18317
18361
|
const reconcileProfile = async () => {
|
|
18318
18362
|
const hmr = ctx.get("hmr");
|
|
@@ -18465,6 +18509,7 @@ async function apply(ctx, config) {
|
|
|
18465
18509
|
commands: adapters.commands,
|
|
18466
18510
|
probe: (unit, options) => probeOne(unit, options?.force ?? false),
|
|
18467
18511
|
values,
|
|
18512
|
+
surface,
|
|
18468
18513
|
longTasks: {
|
|
18469
18514
|
scope: process.env["DSH_HOME"] ?? "",
|
|
18470
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;
|
|
@@ -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
|
|
2
|
-
export declare function
|
|
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.
|
|
3
|
+
"version": "1.1.14-beta.1",
|
|
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.
|
|
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/
|
|
104
|
-
"@dsh-workbench/
|
|
103
|
+
"@dsh-workbench/adapters-default": "1.1.14-beta.1",
|
|
104
|
+
"@dsh-workbench/shared": "1.1.14-beta.1"
|
|
105
105
|
},
|
|
106
106
|
"license": "MIT",
|
|
107
107
|
"repository": {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import {
|
|
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 =
|
|
60
|
-
|
|
61
|
-
|
|
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) {
|
package/scripts/seed-workshop.sh
CHANGED
|
@@ -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
|
-
|
|
675
|
-
|
|
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
|
-
|
|
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 那趟也要建起来),再看要不要自愈。
|
|
@@ -33,27 +33,30 @@
|
|
|
33
33
|
## 2 从模板开始
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
|
-
pnpm exec dsh-wb-module init <unit> --dir
|
|
36
|
+
pnpm exec dsh-wb-module init <unit> --dir <目录> --template diy --description "<一句话>"
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
`<unit>`:小写短横线,同时是单元 id、包名后缀、样式前缀;全局唯一(与已装模块、外壳单元冲突时装配抛错)。
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
| `minimal`(默认) | 空壳:卡壳、文案、测试;无数据源 | 已明确读什么 |
|
|
45
|
-
| `diy` | 样板卡:宿主一条只读路由 + 五态 | 需要可运行的数据样板(推荐) |
|
|
40
|
+
`<目录>` 是模块落地处:在某个工作区里就选它 glob 覆盖的目录(`packages/`、`apps/`、`modules/`…都行);
|
|
41
|
+
独立目录也行,那时这个包自己就是根。
|
|
42
|
+
骨架开箱 `check` 绿;`selfcheck` 里还含 `design check`,底稿填完才绿(脚本 8 个)。**只有一个模板**:`--template diy`(默认)——
|
|
43
|
+
真数据源 + 五态卡片 + 可跑的长任务 / 工具 / 动作通道样板;维护两份模板的成本没人付。
|
|
46
44
|
|
|
47
45
|
骨架分层:`src/model/**`(纯逻辑)、`src/components/**`(视图 + 同目录 `*.module.css`)、
|
|
48
|
-
`src/messages.ts`、`src/host.ts
|
|
49
|
-
|
|
46
|
+
`src/messages.ts`、`src/host.ts`(宿主面)+ `src/host/**`(宿主侧内部实现,
|
|
47
|
+
`moduleArch()` 默认认这一层)、`src/client.tsx`、`scripts/smoke.mjs` +
|
|
48
|
+
`scripts/smoke.assert.mjs`、`design.json`。模板原文可直接读。非显然项:
|
|
49
|
+
|
|
50
|
+
宿主面注册那段(`name` / `inject` / `apply`)用 `@dsh-workbench/shared/host` 的
|
|
51
|
+
`defineUnitHostPlugin`;写工具的审批口径用 `writeApprovalOf`;`recordOf` / `textOf` 也从那里取,
|
|
52
|
+
别每个宿主文件抄一份。浏览器侧注册仍是十几行样板(kit 还没有对应的收口)。
|
|
50
53
|
|
|
51
54
|
| 文件 | 要点 |
|
|
52
55
|
|---|---|
|
|
53
|
-
| `arch.config.mjs` |
|
|
56
|
+
| `arch.config.mjs` | `moduleArch()`(`@dsh-workbench/module-tools/arch`);层序、运行时、阈值随工具链 |
|
|
54
57
|
| `eslint.config.mjs` | 两行 `moduleLint({ rootDir: import.meta.dirname })`;`rootDir` 钉 tsconfig 根 |
|
|
55
58
|
| `types/styles.d.ts` | 用 `X.module.css` 必须有;`globals.d.ts` 是模块,其 `declare module` 不算环境声明 |
|
|
56
|
-
| `design.json` | 三张表 / 矩阵 / 数据源 / 验收;README
|
|
59
|
+
| `design.json` | 三张表 / 矩阵 / 数据源 / 验收;README 段由它生成 |
|
|
57
60
|
|
|
58
61
|
修改位置:视图改 `src/components/*.tsx` + 同目录 `*.module.css`;逻辑改 `src/model/**`;文案改 `src/messages.ts`;
|
|
59
62
|
`src/client.tsx` 只留注册。宿主侧不 import `src/components/**`(跨运行时,`arch` 判红)。
|
|
@@ -61,8 +64,8 @@ pnpm exec dsh-wb-module init <unit> --dir <工作区>/packages/module-<unit> --t
|
|
|
61
64
|
## 3 命令
|
|
62
65
|
|
|
63
66
|
用 `pnpm exec dsh-wb-module …`;命令、`--only` 判据名与模板参数看 `--help` / `check --list`,不在指引里抄。
|
|
64
|
-
`package.json`
|
|
65
|
-
`
|
|
67
|
+
`package.json` 至少保留 `build` / `typecheck` / `test` / `selfcheck` 四个脚本,`contracts` 点名缺失;
|
|
68
|
+
模板自带 8 个(另含 `check` / `lint` / `design` / `prepack`)。
|
|
66
69
|
|
|
67
70
|
- `check` 不跑 `pnpm test`;状态、换算、边界测试自己跑。
|
|
68
71
|
- "不适用"不是"通过":`standalone` 需外壳源码在场,`markers` 只扫公开包(DIY 模块 `private`)。
|
|
@@ -73,25 +76,31 @@ pnpm exec dsh-wb-module init <unit> --dir <工作区>/packages/module-<unit> --t
|
|
|
73
76
|
|
|
74
77
|
## 4 两个面
|
|
75
78
|
|
|
76
|
-
|
|
79
|
+
宿主面(`defineUnitHostPlugin` 把 `name` / `inject` / `apply` 一起给):
|
|
77
80
|
|
|
78
81
|
```ts
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
import { defineUnitHostPlugin } from '@dsh-workbench/shared/host';
|
|
83
|
+
|
|
84
|
+
const plugin = defineUnitHostPlugin({
|
|
85
|
+
name: '<包名>',
|
|
86
|
+
unit: '<unit>',
|
|
87
|
+
face: { unit: '<unit>', mount },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
export const name = plugin.name;
|
|
91
|
+
export const inject = plugin.inject;
|
|
92
|
+
export const apply = plugin.apply;
|
|
87
93
|
```
|
|
88
94
|
|
|
95
|
+
展开写就是:`inject = ['workbenchUnits']`,`apply(ctx)` 里从 `ctx.get('workbenchUnits')` 拿注册表,
|
|
96
|
+
再 `ctx.effect(() => units.register(face), '<unit>: 注册')`。
|
|
97
|
+
|
|
89
98
|
`mount({ data })` 的 `data` 是统一数据端口(`DataReader`);读数据用 `data.read(...)`,不要自己 import adapter。
|
|
90
|
-
`mount()` 返回 `
|
|
99
|
+
`mount()` 返回 `UnitMountResult`:`routes`、`registerTools()`,以及可选的 `factsOf` / `gate` / `context` / `warmReads` / `prefs` / `actions` / `provision` / `longTasks` / `approvalDetail` / `needsApproval`。`routes` 挂 `/workbench/api/<路由>`,只许 GET,
|
|
91
100
|
键必须在 `<unit>` 或 `<unit>/…` 之下(否则装配抛错)。错误返回
|
|
92
101
|
`{ ok: false, error: { code, message, hint? } }`(HTTP 200,外壳转发)。`registerTools` 提供模块工具。
|
|
93
102
|
|
|
94
|
-
浏览器面:`units.registerUnit({
|
|
103
|
+
浏览器面:`units.registerUnit({ unit, messages, card })`;清单由外壳从已装清单读,不在面里重复。`card` 收到
|
|
95
104
|
`{ t, world, refreshToken, onRefresh, onSummary }`(`world.current.workspace` 为当前项目),返回元素。
|
|
96
105
|
三态用 kit:加载 `kit.components.SkeletonRows`;空 `kit.components.EmptyState`(无按钮);
|
|
97
106
|
错误 `kit.components.BlockerState` + `kit.lib.blockerOfError(error, t)`。
|
|
@@ -49,8 +49,9 @@
|
|
|
49
49
|
- 脚本约定:不在站点 → `{ ok: false, code: "not-on-site" }`;登录过期 → `{ ok: false, code: "not-logged-in" }`
|
|
50
50
|
(工具归一为 `auth-required`);成功 → `{ ok: true, ...数据 }`。
|
|
51
51
|
- 新 session 的 `eval` 会静默开 `about:blank`(后台容器窗,不抢焦点)。脚本第一行判断
|
|
52
|
-
`location.origin`;不在站点则 `open` 该页,等约 1 秒再 eval
|
|
53
|
-
|
|
52
|
+
`location.origin`;不在站点则 `open` 该页,等约 1 秒再 eval。在 shell 里跑 opencli 时不要自己
|
|
53
|
+
拼命令前缀:`OPENCLI_WINDOW` 这类环境变量走宿主 shell 的 env 覆盖(`env` 是叠加,POSIX / pwsh 通用),
|
|
54
|
+
手写 `env NAME=value` / `$env:NAME` 在另一个平台就是坏的。
|
|
54
55
|
- 读与屏用不同会话:`data.read({ kind: 'browser' })` 默认读会话 `workbench-read`;`workbench` 是看 / 动族的屏会话。同名会把用户的页导航走。需要独立标签就传 `session: "<unit>"`。
|
|
55
56
|
- DOM 读不要在宿主脚本里 `querySelectorAll`:宿主产物禁止 `document.`,且无法单测。探时用
|
|
56
57
|
`workbench_diy_find`(`read=…, args=["--css", "<选择器>"]`);固化用
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
|
|
22
22
|
- 只在 DIY 模式的会话出现;面板得画在屏上。
|
|
23
23
|
- 看 / 动 / 网络 / 日志族先 `_debug_start`(审批一次),否则整族拒、零命令;收工 `_debug_stop`。
|
|
24
|
-
`
|
|
24
|
+
`_debug_pause` 只暂停看屏。不碰屏的:`_status` / `_panel` / `_components` / `_recon` / `_spec` /
|
|
25
25
|
`_install` / `_show` / `_publish`;`_find` / `_eval` 带 `read=` 时也不碰。
|
|
26
26
|
- 录制独立:`_record_prepare` 审批一次,其余不问;同时只有一场,要重来先 `_record_stop`。
|
|
27
27
|
|