@dsh-workbench/module-tools 0.3.10 → 1.0.0-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.
- package/LICENSE +21 -0
- package/README.md +223 -177
- package/bin/dsh-wb-module.mjs +89 -34
- package/package.json +18 -3
- package/src/arch.mjs +158 -129
- package/src/build.mjs +6 -30
- package/src/changelog.mjs +167 -0
- package/src/check-flags.mjs +0 -7
- package/src/check.mjs +115 -53
- package/src/checks/alias.mjs +0 -9
- package/src/checks/arch.mjs +3 -25
- package/src/checks/bundles.mjs +2 -34
- package/src/checks/cards.mjs +26 -30
- package/src/checks/contracts.mjs +61 -222
- package/src/checks/kit.mjs +2 -42
- package/src/checks/lint.mjs +9 -82
- package/src/checks/markers.mjs +48 -17
- package/src/checks/primitives.mjs +0 -39
- package/src/checks/protocol.mjs +0 -18
- package/src/checks/source.mjs +163 -29
- package/src/checks/standalone.mjs +4 -14
- package/src/checks/style.mjs +71 -241
- package/src/css-injection.mjs +0 -11
- package/src/css-shell.mjs +3 -31
- package/src/datasource-file.mjs +104 -0
- package/src/declared-tools.mjs +0 -24
- package/src/design.mjs +116 -38
- package/src/discover.mjs +2 -10
- package/src/font-scale.mjs +1 -61
- package/src/init.mjs +2 -17
- package/src/lint.mjs +24 -144
- package/src/market-index.mjs +569 -0
- package/src/market-init.mjs +80 -0
- package/src/primitives-surface.mjs +2 -28
- package/src/publish.mjs +6 -24
- package/src/release-changelog.mjs +29 -0
- package/src/release-journal.mjs +131 -0
- package/src/release-report.mjs +99 -0
- package/src/release-util.mjs +80 -0
- package/src/release.mjs +1251 -0
- package/src/seed.mjs +0 -25
- package/src/shell-type-files.mjs +3 -30
- package/src/sync.mjs +41 -24
- package/template/AGENTS.md.tpl +33 -0
- package/template/CHANGELOG.md.tpl +7 -0
- package/template/README.md.tpl +6 -33
- package/template/package.json.tpl +2 -2
- package/template/src/client.tsx.tpl +1 -18
- package/template/src/host.ts.tpl +2 -15
- package/template/types/globals.d.ts +1 -14
- package/template-diy/AGENTS.md.tpl +33 -0
- package/template-diy/CHANGELOG.md.tpl +7 -0
- package/template-diy/README.md.tpl +4 -133
- package/template-diy/package.json.tpl +2 -2
- package/template-diy/src/client.tsx.tpl +1 -29
- package/template-diy/src/host.ts.tpl +1 -15
- package/template-diy/src/messages.ts.tpl +4 -4
- package/template-diy/src/plugin.test.ts.tpl +1 -1
- package/template-diy/types/globals.d.ts +1 -14
- package/template-diy/types/styles.d.ts +0 -10
package/src/seed.mjs
CHANGED
|
@@ -1,22 +1,3 @@
|
|
|
1
|
-
// dsh 客户端模块表的**平台种子词** —— 一个模块的浏览器产物**天生**能 require 到它们。
|
|
2
|
-
//
|
|
3
|
-
// 名单真值出处:dsh 外壳声明平台模块的地方(rc.2 `packages/client/web/src/platform.ts` 的
|
|
4
|
-
// `PLATFORM_MODULES`,装种子的 `seed.ts` 的 `getStaticModules()` 与它 `satisfies` 互锁);
|
|
5
|
-
// 装机产物里那张表在 `@deepseek-ai/dsh-web-frontend/dist/assets/index-*.js` 的 `SZ()`。
|
|
6
|
-
// 证据与推理见 `.scratch/dsh-ui-alignment/spec.md` §0;「模块可以直接用它」的决定见 ADR-0028。
|
|
7
|
-
//
|
|
8
|
-
// 这份表是**唯一真相**:构建的 external 默认表(`build.mjs`)、契约判据(`contracts.mjs`)、
|
|
9
|
-
// 源码边界(`checks/source.mjs`)与新增的原语判据(`checks/primitives.mjs`)都从这里取。
|
|
10
|
-
// dsh 升版改了种子表,只改这一处。
|
|
11
|
-
//
|
|
12
|
-
// ⚠️ 2026-10-18(dsh 0.2.1-rc.2 对账):这张表**漏过两个词** —— `zod` 与
|
|
13
|
-
// `@tanstack/react-virtual`(dsh 那边一共 11 个,我们只抄了 9 个,`0.2.1-alpha.3` 就少,
|
|
14
|
-
// 不是这次升级引入的)。漏的代价不对称:`build.mjs:17,161` 拿它当模块浏览器产物的默认
|
|
15
|
-
// external 表 ⇒ 模块写 `import 'zod'` 会被**内联出第二份**;`contracts.mjs:39,551` 拿
|
|
16
|
-
// `SEED_SHORT` 判 `dsh.client.external` 能不能落地 ⇒ 声明 `zod` 反而被判「落不到图里任何一行」。
|
|
17
|
-
// 对账脚本:把 dsh 那份 `platform.ts` 的数组原样抄下来与这里逐字比(见两份报告
|
|
18
|
-
// `.scratch/dsh-0.2.1-rc.2/report-client-surface.md` §2.1)。
|
|
19
|
-
|
|
20
1
|
export const SEED_SPECIFIERS = [
|
|
21
2
|
'react',
|
|
22
3
|
'react/jsx-runtime',
|
|
@@ -31,10 +12,6 @@ export const SEED_SPECIFIERS = [
|
|
|
31
12
|
'@deepseek-ai/dsh-client-ui-dockkit',
|
|
32
13
|
];
|
|
33
14
|
|
|
34
|
-
/**
|
|
35
|
-
* 外部声明里允许的**短名**(历史写法:`"ui-primitives"` 这类)与**全名**。
|
|
36
|
-
* 契约判据按它放行,免得老包只因为写法短就被判红。
|
|
37
|
-
*/
|
|
38
15
|
export const SEED_SHORT = new Set([
|
|
39
16
|
'react',
|
|
40
17
|
'react-dom',
|
|
@@ -50,10 +27,8 @@ export const SEED_SHORT = new Set([
|
|
|
50
27
|
|
|
51
28
|
const SEED_LONG = new Set(SEED_SPECIFIERS);
|
|
52
29
|
|
|
53
|
-
/** 是不是种子词(按完整说明符判断)。 */
|
|
54
30
|
export const isSeedSpecifier = (specifier) => SEED_LONG.has(specifier);
|
|
55
31
|
|
|
56
|
-
/** 种子词里那些 **dsh 的** 包 —— 它们只在浏览器侧存在,宿主侧一个都不许碰。 */
|
|
57
32
|
export const BROWSER_SEED_SPECIFIERS = SEED_SPECIFIERS.filter(
|
|
58
33
|
(name) => name.startsWith('@deepseek-ai/'),
|
|
59
34
|
);
|
package/src/shell-type-files.mjs
CHANGED
|
@@ -1,15 +1,3 @@
|
|
|
1
|
-
// kit 成员 → 类型文件:外壳那两个包的类型树(`@dsh-workbench/shared` 的 `dist` 与外壳自己的 `dist/types`)。
|
|
2
|
-
//
|
|
3
|
-
// 为什么要扫:kit 的成员是**运行期**从外壳产物 `client.js` 里跑出来的(见 `checks/kit.mjs`),
|
|
4
|
-
// 产物里没有"这个成员来自哪个模块";而类型树里每个成员的声明都躺在某份 `.d.ts` 里 ——
|
|
5
|
-
// 扫一遍就能把"名字"落到"文件",于是 agent「想问哪个直接去看那份文件」有确切路径。
|
|
6
|
-
//
|
|
7
|
-
// 为什么是名字扫描而不是真正的类型解析:这些 `.d.ts` 是 tsc 生成的、声明形状规整
|
|
8
|
-
// (`export declare const X` / `export interface X` / `export { X } from '…'`),而 kit 的成员
|
|
9
|
-
// 本来就是运行期对象上的键名。扫描不引 TypeScript 依赖、贵在稳;代价是"同名声明出现在多处"时
|
|
10
|
-
// 要挑一个(挑法见 `preferred`),以及极少数成员指到的是 barrel 而不是最终实现文件。
|
|
11
|
-
// 实测本机 190 个 kit 成员 190 个都落得到文件(见 `.scratch/dsh-primitives-face/verify.md` §2)。
|
|
12
|
-
|
|
13
1
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
14
2
|
import { createRequire } from 'node:module';
|
|
15
3
|
import { dirname, join } from 'node:path';
|
|
@@ -39,35 +27,24 @@ function namesIn(clause) {
|
|
|
39
27
|
.filter((name) => /^[A-Za-z_$][\w$]*$/.test(name));
|
|
40
28
|
}
|
|
41
29
|
|
|
42
|
-
/** 外壳那两个包的类型树(`shared` 没装就只给外壳那棵)。 */
|
|
43
30
|
export function shellTypeRoots(shellClientPath) {
|
|
44
31
|
const shellRoot = dirname(shellClientPath);
|
|
45
32
|
const roots = {};
|
|
46
33
|
const shellTypes = join(shellRoot, 'dist', 'types');
|
|
47
34
|
if (existsSync(shellTypes)) roots.shell = shellTypes;
|
|
48
|
-
|
|
35
|
+
|
|
49
36
|
const viaNodeModules = join(shellRoot, 'node_modules', '@dsh-workbench', 'shared', 'dist');
|
|
50
37
|
try {
|
|
51
|
-
|
|
52
|
-
// `./package.json` 这一条,直接 resolve 它的 package.json 会 ERR_PACKAGE_PATH_NOT_EXPORTED。
|
|
38
|
+
|
|
53
39
|
const sharedEntry = createRequire(join(shellRoot, 'package.json')).resolve('@dsh-workbench/shared');
|
|
54
40
|
const sharedTypes = dirname(sharedEntry);
|
|
55
41
|
if (existsSync(sharedTypes)) roots.shared = existsSync(viaNodeModules) ? viaNodeModules : sharedTypes;
|
|
56
42
|
} catch {
|
|
57
|
-
|
|
43
|
+
|
|
58
44
|
}
|
|
59
45
|
return { shellRoot, roots };
|
|
60
46
|
}
|
|
61
47
|
|
|
62
|
-
/**
|
|
63
|
-
* 同名多处时挑哪个:组件 > hooks > 库 > 其他 > **套件内部(`app/` / `modules/`)垫底**,同档挑路径短的。
|
|
64
|
-
*
|
|
65
|
-
* 垫底那一档是 2026-10-20 补的(ADR-0067 的 kit JSDoc 判据撞出来的):kit 的 `readText`(共享包
|
|
66
|
-
* `config/storage.ts`)与外壳内部 `modules/panels/model/diy-report.ts` 里的同名函数撞了,而 `modules/`
|
|
67
|
-
* 那条路径在 `node_modules` 那份类型树旁边**更短**,于是 `--file readText` 一直指到别人的实现上。
|
|
68
|
-
* kit 的成员只从 `client.tsx` 那几处 spread 来(共享包 + 外壳 `shared/**`),套件内部不会是它 ——
|
|
69
|
-
* 所以垫底只影响撞名时的取舍,掉不到"本来该选的那份"。
|
|
70
|
-
*/
|
|
71
48
|
function preferred(candidates) {
|
|
72
49
|
const rank = (file) =>
|
|
73
50
|
file.includes('/components/')
|
|
@@ -82,10 +59,6 @@ function preferred(candidates) {
|
|
|
82
59
|
return [...candidates].sort((a, b) => rank(a.file) - rank(b.file) || a.file.length - b.file.length)[0];
|
|
83
60
|
}
|
|
84
61
|
|
|
85
|
-
/**
|
|
86
|
-
* 把 kit 的成员名落到类型文件。
|
|
87
|
-
* 返回 `{ roots, files: Map<名字, 绝对路径>, missing: 名字[] }` —— `roots` 是两棵类型树的绝对根。
|
|
88
|
-
*/
|
|
89
62
|
export function memberFilesOf(shellClientPath, names) {
|
|
90
63
|
const { roots } = shellTypeRoots(shellClientPath);
|
|
91
64
|
const index = new Map();
|
package/src/sync.mjs
CHANGED
|
@@ -1,8 +1,3 @@
|
|
|
1
|
-
// `dsh-wb-module sync`:按清单生成 `src/declared-tools.ts`(单向,清单 → 代码)。
|
|
2
|
-
//
|
|
3
|
-
// 为什么需要它、生成物长什么样、为什么绝不回写 package.json:见 `declared-tools.mjs` 开头与
|
|
4
|
-
// ADR-0038 的 `tools[].key` 一节。
|
|
5
|
-
|
|
6
1
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
2
|
import { join } from 'node:path';
|
|
8
3
|
import {
|
|
@@ -13,6 +8,13 @@ import {
|
|
|
13
8
|
declaredToolsWithoutKey,
|
|
14
9
|
renderDeclaredTools,
|
|
15
10
|
} from './declared-tools.mjs';
|
|
11
|
+
import { readDesign } from './design.mjs';
|
|
12
|
+
import {
|
|
13
|
+
DATASOURCE_FILE,
|
|
14
|
+
datasourceEntriesOf,
|
|
15
|
+
datasourceProblemOf,
|
|
16
|
+
renderDatasource,
|
|
17
|
+
} from './datasource-file.mjs';
|
|
16
18
|
|
|
17
19
|
export async function sync({ root }) {
|
|
18
20
|
const manifestPath = join(root, 'package.json');
|
|
@@ -23,29 +25,44 @@ export async function sync({ root }) {
|
|
|
23
25
|
if (raw === undefined) throw new Error(`${unit}: package.json 里没有 dsh.workbench —— 这不是模块包`);
|
|
24
26
|
|
|
25
27
|
const tools = Array.isArray(raw.tools) ? raw.tools : [];
|
|
26
|
-
|
|
27
|
-
|
|
28
|
+
const design = readDesign(root);
|
|
29
|
+
const entries = datasourceEntriesOf(design);
|
|
30
|
+
if (tools.length === 0 && entries.length === 0) {
|
|
31
|
+
console.log(`${unit}: 清单里没有工具、design.json 里也没有带 key 的配方,不用生成物`);
|
|
28
32
|
return;
|
|
29
33
|
}
|
|
30
34
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
`${
|
|
39
|
-
|
|
35
|
+
if (tools.length > 0) {
|
|
36
|
+
const missing = declaredToolsWithoutKey(raw);
|
|
37
|
+
if (missing.length > 0) {
|
|
38
|
+
const hint = declaredToolsApplies(raw)
|
|
39
|
+
? '有 key 就走生成物那一档,那就每个工具都得有一个'
|
|
40
|
+
: '给每个工具加一个 lowerCamelCase 的 key,代码里就不再写工具名的字面量';
|
|
41
|
+
throw new Error(
|
|
42
|
+
`${unit}: 清单里 ${missing.join(' / ')} 没有 key —— ${hint}。例如:\n` +
|
|
43
|
+
`${tools.map((tool) => ` "key": "…", // ${JSON.stringify(tool.name)}`).join('\n')}\n` +
|
|
44
|
+
'(key 是代码里那个短名:`DECLARED_TOOLS.<key>.name`;生成物按清单生成它,代码里引用工具名就写它、不写字面量)',
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const at = declaredToolsPathOf(root);
|
|
49
|
+
const had = existsSync(at);
|
|
50
|
+
const stale = declaredToolsProblemOf(root, raw);
|
|
51
|
+
writeFileSync(at, renderDeclaredTools(raw));
|
|
52
|
+
console.log(
|
|
53
|
+
`${unit}: 写了 ${DECLARED_TOOLS_FILE}(${tools.length} 个工具,按清单生成)` +
|
|
54
|
+
(had && stale !== undefined ? ' —— 原来那份和清单对不上,已刷新' : ''),
|
|
40
55
|
);
|
|
41
56
|
}
|
|
42
57
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
if (entries.length > 0) {
|
|
59
|
+
const at = join(root, DATASOURCE_FILE);
|
|
60
|
+
const had = existsSync(at);
|
|
61
|
+
const stale = datasourceProblemOf(root, design);
|
|
62
|
+
writeFileSync(at, renderDatasource(design));
|
|
63
|
+
console.log(
|
|
64
|
+
`${unit}: 写了 ${DATASOURCE_FILE}(${entries.length} 条配方,按 design.json 生成)` +
|
|
65
|
+
(had && stale !== undefined ? ' —— 原来那份和底稿对不上,已刷新' : ''),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
51
68
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# __NAME__ · agent 工作说明
|
|
2
|
+
|
|
3
|
+
这个包是工作台模块(单元 `__UNIT__`)。`README.md` 讲模块本身;这份讲 agent 在这份代码里怎么干活。
|
|
4
|
+
|
|
5
|
+
## 命令
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm install
|
|
9
|
+
pnpm build # dist/host.js + client.js
|
|
10
|
+
pnpm typecheck
|
|
11
|
+
pnpm test
|
|
12
|
+
pnpm selfcheck
|
|
13
|
+
pnpm check # 十三条模块判据
|
|
14
|
+
pnpm lint
|
|
15
|
+
pnpm design check
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
发布(维护者确认之后):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
dsh-wb-module release --target team|public|tarball|git
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`--dry-run` 只预览,`--verify` 只核已发布的包。
|
|
25
|
+
|
|
26
|
+
## 不要
|
|
27
|
+
|
|
28
|
+
- 手改 `client.js` / `dist/**`:构建产物,改 `src/**` 后重新 build。
|
|
29
|
+
|
|
30
|
+
## 先读
|
|
31
|
+
|
|
32
|
+
- `README.md`:模块行为与数据源。
|
|
33
|
+
- 工作台模块指引:`@dsh-workbench/core/skills/workbench-module-diy/`(dsh / 工作台的约束随外壳出货)。
|
package/template/README.md.tpl
CHANGED
|
@@ -19,39 +19,12 @@ pnpm check # dsh-wb-module check
|
|
|
19
19
|
pnpm lint # dsh-wb-module lint(`--fix` 就地改文件;`check` 永不动手)
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
-
|
|
23
|
-
`src/
|
|
24
|
-
`src/messages.ts` 是文案。目录**按需要出现** —— 这份没有纯逻辑,所以没有 `src/model/`;
|
|
25
|
-
一旦有了(换算 / 状态机 / 类型),就放 `src/model/**` 并配一份同目录测试。
|
|
26
|
-
两个面在第 10 层、`components/` 第 2 层、`model/` 与 `messages.ts` 第 1 层:
|
|
27
|
-
上层只许 import 下层的,`arch.config.mjs` 那份方言 + `check` 的 `arch` 判据会当场报
|
|
28
|
-
(宿主侧 import `components/**` 是跨运行时,也红)。
|
|
22
|
+
形状:`src/components/HelloCard.tsx` 是卡片,`src/client.tsx` 只注册,`src/host.ts` 是宿主面,
|
|
23
|
+
`src/messages.ts` 是文案;有纯逻辑就放 `src/model/**` 并配一份同目录测试。
|
|
29
24
|
|
|
30
|
-
|
|
31
|
-
`arch.config.mjs`(`moduleArch()`:层序 / 反向依赖 / 跨运行时)与
|
|
32
|
-
`eslint.config.mjs`(`moduleLint()`:`console` / `as any` / hooks 那几条 / 手搓颜色字面量);
|
|
33
|
-
要开口子都从工厂参数进,别手写一份自己的配置。
|
|
25
|
+
## 开发与发布
|
|
34
26
|
|
|
35
|
-
|
|
27
|
+
开发命令见上面的六个脚本;dsh / 工作台的模块约束见工作台模块指引(随外壳出货);agent 的工作说明见 `AGENTS.md`。
|
|
36
28
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
要加能力:`package.json` 的 `dsh.workbench.tools[]` 里每个工具写 `name` / `writes` / `key`
|
|
42
|
-
(`key` 是代码里那个短名,lowerCamelCase),然后跑一次
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
dsh-wb-module sync # 按清单生成 src/declared-tools.ts
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
代码里写 `DECLARED_TOOLS.<key>.name` —— **工具名的字面量只留在清单这一处**。生成物过期、半写 `key`、
|
|
49
|
-
或源码里又写了一遍工具名字面量,`check` 都会红(`sync` 会告诉你缺哪个工具的 `key`)。
|
|
50
|
-
|
|
51
|
-
面板只读、写入只走 agent 工具;共享代码从 `@dsh-workbench/shared` 拿,
|
|
52
|
-
单例机制(hooks / 容器组件 / 读缝)只能从 `@dsh-workbench/core/client` 的 kit 拿。
|
|
53
|
-
dsh 已有的组件与样式**先从 kit 拿**,别从 DOM + CSS 重画。
|
|
54
|
-
|
|
55
|
-
要发布:把 `package.json` 里的 `private` 去掉,并显式写上发布目标
|
|
56
|
-
(`publishConfig.access: "public"` 或指向内网源的 `publishConfig.registry`)——
|
|
57
|
-
`private: true` 的包不许发到任何源,这是刻意的默认。
|
|
29
|
+
发布(维护者确认之后):`dsh-wb-module release --target team|public|tarball|git`;
|
|
30
|
+
`--dry-run` 只预览,`--verify` 只核已发布的包。
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
".": "./dist/host.js",
|
|
9
9
|
"./client": "./client.js"
|
|
10
10
|
},
|
|
11
|
-
"files": ["dist", "client.js", "cordis.patch.yml"],
|
|
11
|
+
"files": ["dist", "client.js", "cordis.patch.yml", "CHANGELOG.md"],
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "dsh-wb-module build",
|
|
14
14
|
"typecheck": "tsc --noEmit",
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"external": ["@dsh-workbench/core/client", "@deepseek-ai/dsh-client-ui-primitives"]
|
|
27
27
|
},
|
|
28
28
|
"workbench": {
|
|
29
|
-
"apiVersion":
|
|
29
|
+
"apiVersion": 2,
|
|
30
30
|
"unit": "__UNIT__",
|
|
31
31
|
"card": { "titleKey": "__UNIT__.cardTitle", "order": 90 },
|
|
32
32
|
"tools": [],
|
|
@@ -1,26 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ClientContext, UnitClientApi, UnitClientFace } from '@dsh-workbench/core/contract';
|
|
2
2
|
import { HelloCard } from './components/HelloCard';
|
|
3
3
|
import { messages } from './messages';
|
|
4
4
|
|
|
5
5
|
const UNIT_ID = '__UNIT__';
|
|
6
6
|
|
|
7
|
-
type Translate = (key: string, params?: Record<string, unknown>) => string;
|
|
8
|
-
|
|
9
|
-
interface UnitClientFace {
|
|
10
|
-
readonly unit: string;
|
|
11
|
-
readonly messages: { readonly zh: Readonly<Record<string, string>>; readonly en: Readonly<Record<string, string>> };
|
|
12
|
-
readonly card: (props: { readonly t: Translate; readonly onRefresh: () => void }) => ReactElement;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface UnitClientApi {
|
|
16
|
-
registerUnit(face: UnitClientFace): () => void;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
interface ClientContext {
|
|
20
|
-
get<T = unknown>(name: string): T | undefined;
|
|
21
|
-
effect(effect: () => void | (() => void), label?: string): () => void;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
7
|
const face: UnitClientFace = {
|
|
25
8
|
unit: UNIT_ID,
|
|
26
9
|
messages,
|
package/template/src/host.ts.tpl
CHANGED
|
@@ -1,24 +1,11 @@
|
|
|
1
|
+
import type { HostContext, UnitModuleFace, UnitRegistry } from '@dsh-workbench/core/contract';
|
|
2
|
+
|
|
1
3
|
const UNIT_ID = '__UNIT__';
|
|
2
4
|
|
|
3
5
|
export const name = '__NAME__';
|
|
4
6
|
|
|
5
7
|
export const inject = ['workbenchUnits'];
|
|
6
8
|
|
|
7
|
-
interface UnitModuleFace {
|
|
8
|
-
readonly unit: string;
|
|
9
|
-
mount(): { routes: Record<string, never>; registerTools(): Array<() => void> };
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface UnitRegistry {
|
|
13
|
-
register(face: UnitModuleFace): () => void;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
interface HostContext {
|
|
17
|
-
get<T = unknown>(name: string): T | undefined;
|
|
18
|
-
effect(effect: () => void | (() => void), label?: string): () => void;
|
|
19
|
-
logger?: { warn?(message: string): void } | undefined;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
9
|
const face: UnitModuleFace = {
|
|
23
10
|
unit: UNIT_ID,
|
|
24
11
|
mount: () => ({ routes: {}, registerTools: () => [] }),
|
|
@@ -1,16 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 浏览器半侧的运行时垫片。
|
|
3
|
-
*
|
|
4
|
-
* dsh 的客户端模块格式是 `window.__ModuleLoader__.load({ id, factory(require) })`,
|
|
5
|
-
* 所以 `require` 不来自模块系统,而是宿主给的作用域。
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* React 19 的 `@types/react` 移除了全局 `JSX` 命名空间,而本工程用的是
|
|
10
|
-
* **经典 JSX 运行时**(`jsxFactory: "h"`):`react/jsx-runtime` 不在浏览器模块表里,
|
|
11
|
-
* 所以不能切到自动运行时。这里把全局命名空间接到 `React.JSX` 上 —— 这是官方迁移说明
|
|
12
|
-
* 给经典运行时的做法,写完 JSX 就有完整类型,产出仍是 `h(...)`。
|
|
13
|
-
*/
|
|
14
1
|
import type * as ReactTypes from 'react';
|
|
15
2
|
|
|
16
3
|
declare global {
|
|
@@ -18,7 +5,7 @@ declare global {
|
|
|
18
5
|
interface Element extends ReactTypes.JSX.Element {}
|
|
19
6
|
interface ElementClass extends ReactTypes.JSX.ElementClass {}
|
|
20
7
|
interface IntrinsicElements extends ReactTypes.JSX.IntrinsicElements {}
|
|
21
|
-
|
|
8
|
+
|
|
22
9
|
type ElementType = ReactTypes.JSX.ElementType;
|
|
23
10
|
interface IntrinsicAttributes extends ReactTypes.JSX.IntrinsicAttributes {}
|
|
24
11
|
interface ElementAttributesProperty extends ReactTypes.JSX.ElementAttributesProperty {}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# __NAME__ · agent 工作说明
|
|
2
|
+
|
|
3
|
+
这个包是工作台模块(单元 `__UNIT__`)。`README.md` 讲模块本身;这份讲 agent 在这份代码里怎么干活。
|
|
4
|
+
|
|
5
|
+
## 命令
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm install
|
|
9
|
+
pnpm build # dist/host.js + client.js
|
|
10
|
+
pnpm typecheck
|
|
11
|
+
pnpm test
|
|
12
|
+
pnpm selfcheck
|
|
13
|
+
pnpm check # 十三条模块判据
|
|
14
|
+
pnpm lint
|
|
15
|
+
pnpm design check
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
发布(维护者确认之后):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
dsh-wb-module release --target team|public|tarball|git
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`--dry-run` 只预览,`--verify` 只核已发布的包。
|
|
25
|
+
|
|
26
|
+
## 不要
|
|
27
|
+
|
|
28
|
+
- 手改 `client.js` / `dist/**`:构建产物,改 `src/**` 后重新 build。
|
|
29
|
+
|
|
30
|
+
## 先读
|
|
31
|
+
|
|
32
|
+
- `README.md`:模块行为与数据源。
|
|
33
|
+
- 工作台模块指引:`@dsh-workbench/core/skills/workbench-module-diy/`(dsh / 工作台的约束随外壳出货)。
|
|
@@ -161,106 +161,6 @@ GET /api/__UNIT__/overview?path=<绝对路径>&limit=<1..50>
|
|
|
161
161
|
卡片侧读数的 hook 是 `kit.hooks.useApiResource`(`route` 就是 `__UNIT__/overview`)。
|
|
162
162
|
注意它的 `value` 是**整个响应对象**(成功时形如 `{ ok: true, ...payload }`),不是 `payload` 本身。
|
|
163
163
|
|
|
164
|
-
## 这个模块会不会写东西、要不要跑一会儿、靠什么跑起来
|
|
165
|
-
|
|
166
|
-
**这张卡只读** —— 上面那条 `/overview` 就是全部。要动手(提交、发布、批量改)就得再加两样,
|
|
167
|
-
下面三段是**最小可抄的写法**;不需要的整段删掉,别留半截。
|
|
168
|
-
|
|
169
|
-
### 会写:清单里标出来 + 认领审批
|
|
170
|
-
|
|
171
|
-
> 每个工具都写一个 `key`(代码里那个短名,lowerCamelCase),然后 `dsh-wb-module sync` 生成
|
|
172
|
-
> `src/declared-tools.ts`:代码写 `DECLARED_TOOLS.rebuild.name` —— 工具名的字面量只留在清单里。
|
|
173
|
-
|
|
174
|
-
```ts
|
|
175
|
-
tools: [{ name: 'workbench___UNIT___rebuild', writes: true, key: 'rebuild' }],
|
|
176
|
-
|
|
177
|
-
needsApproval: (call) => ({
|
|
178
|
-
kind: 'ask',
|
|
179
|
-
reason: '会覆盖产物',
|
|
180
|
-
question: { zh: '要重建这 3 个模块吗?', en: 'Rebuild these 3 modules?' },
|
|
181
|
-
fallback: { zh: '会覆盖它们的 dist 与 client.js', en: 'Overwrites their dist and client.js' },
|
|
182
|
-
}),
|
|
183
|
-
|
|
184
|
-
approvalDetail: async (call) => ({ zh: '会覆盖 3 个模块的产物,不碰源码', en: '…' }),
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
清单写着 `writes: true` 而不认领,外壳**当场 deny**(用户看到的是一次莫名其妙的拒绝)——
|
|
188
|
-
`dsh-wb-module check --only contracts` 会替你先把这条看一遍。用户侧看到的问话就是
|
|
189
|
-
`question` + `approvalDetail` 合起来的那几行:**一行一个事实,别写"这个操作会写数据"这种空话。**
|
|
190
|
-
|
|
191
|
-
### 跑一会儿:长任务
|
|
192
|
-
|
|
193
|
-
```ts
|
|
194
|
-
mount({ longTasks, ... }) {
|
|
195
|
-
const runs = longTasks?.create({
|
|
196
|
-
key: 'rebuild',
|
|
197
|
-
run: async ({ payload, onStep, signal }) => {
|
|
198
|
-
onStep({ kind: 'start', text: '开始…' });
|
|
199
|
-
// signal 被 abort = 有人按了停止(用户 / agent):每一轮都看一眼 `signal.aborted`,能收手就收手
|
|
200
|
-
/* … */
|
|
201
|
-
return {};
|
|
202
|
-
},
|
|
203
|
-
outcome: (result) => ({ why: '…' }),
|
|
204
|
-
view: (snapshot) => ({ steps: String(snapshot.steps.length) }),
|
|
205
|
-
});
|
|
206
|
-
return { routes: {}, registerTools: () => [], ...(runs === undefined ? {} : { longTasks: runs }) };
|
|
207
|
-
}
|
|
208
|
-
```
|
|
209
|
-
|
|
210
|
-
起这一趟的那把工具里(`execute(args, exec)` 拿得到现场)记下来源:
|
|
211
|
-
|
|
212
|
-
```ts
|
|
213
|
-
runs.start({ workspace, target, payload, session: sessionIdOf(exec) ?? undefined });
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
`sessionIdOf`(`@dsh-workbench/shared/host`)说这次调用是哪个会话发的。**不记来源 = 用户按的**
|
|
217
|
-
(或这一版之前起的)—— 停它要走审批;外壳自带的那两把 `workbench_long_tasks_list` /
|
|
218
|
-
`workbench_long_tasks_stop` 就是这么判"谁的"(自己起的免审批,别人的 / 用户按的要审批,
|
|
219
|
-
不在当前工作区的直接拒绝)。
|
|
220
|
-
|
|
221
|
-
浏览器侧用 kit 那套画(`kit.hooks.useLongTask` + `<LongTaskRow …/>`):状态有五种 ——
|
|
222
|
-
`running` 进行中 / `needs-login` 要登录 / `stopped` 停下的(用户按停)/ `ok` 完成 / `failed` 出错。
|
|
223
|
-
|
|
224
|
-
要给用户那颗停止钮,就在卡片上给那一行的 `LongTaskRow` 传 `stop`
|
|
225
|
-
(词与取值照 dsh 任务列表那行:aria `停止任务 {label}` → 上膛 → 可见字 `确认停止`、
|
|
226
|
-
aria `再次点击确认停止`,3 秒落膛;**失败那半是我们自己的** —— 不往钮里写「停止失败」,
|
|
227
|
-
而是把这次请求失败的原话画在那一行下面,`failed` 给什么画什么);**要停就认 `signal`,不认就别画** ——
|
|
228
|
-
画了就是骗人:外壳只会把这一趟标成 `stopped`,你的活儿还在跑(内部是 opencli 子进程那类编排,
|
|
229
|
-
今天最多做到"我们不再等它")。要用这个新 prop 就得装带这次改动的那份外壳 ——
|
|
230
|
-
把自己 `peerDependencies['@dsh-workbench/core']` 的下限抬到那一版。
|
|
231
|
-
|
|
232
|
-
### 存起来:他能自己配什么(`prefs`)
|
|
233
|
-
|
|
234
|
-
选一次就想让它一直算数(钉哪个环境、忽略哪些、超时多久)→ 走 `prefs` 通道:**按项目记、刷新还在**。
|
|
235
|
-
只是"这次看哪一种"→ 当 route 参数传,别落盘。两种都写进 `design.json` 的 `config[].persistence`
|
|
236
|
-
(`prefs` / `view`)—— 表里写了 `prefs`,产物里就必须真的接上(`check --only contracts` 会拦)。
|
|
237
|
-
|
|
238
|
-
宿主半侧在 `mount` 里返回(最好单独一个纯文件,像 `src/prefs.ts` 那样能单测):
|
|
239
|
-
|
|
240
|
-
```ts
|
|
241
|
-
prefs: {
|
|
242
|
-
resource: 'your-module',
|
|
243
|
-
handle: (payload) => applyPref(deps.store.for(UNIT_ID, workspace), payload),
|
|
244
|
-
}
|
|
245
|
-
```
|
|
246
|
-
|
|
247
|
-
浏览器半侧:`useHostWrite({ channel: 'prefs', resource: 'your-module' })` + `FormRows` / `FormRow` /
|
|
248
|
-
`TextField`,外面套 `SettingsSection`,保存用 `SaveBar`;存失败留在原地。(标签、行距与输入框内边距都是
|
|
249
|
-
外壳给的默认,不用为这个写 CSS。)语义:没写过 ≠ 选空、清空要把文件收掉、不认识的值报错并列出还认的候选。
|
|
250
|
-
|
|
251
|
-
### 靠什么跑起来:`env`
|
|
252
|
-
|
|
253
|
-
```ts
|
|
254
|
-
env: {
|
|
255
|
-
command: 'opencli',
|
|
256
|
-
libs: [{ name: 'x-lib', registryEnv: 'DSH_WORKBENCH_X_REGISTRY', registryDefault: 'https://…/', purpose: '装它干嘛' }],
|
|
257
|
-
plugins: [{ name: 'x', commands: ['a', 'b'], installSource: { kind: 'npm', package: 'opencli-plugin-x', registryEnv: '…' } }],
|
|
258
|
-
}
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
外壳的隔离运行时按它装、按它探(`workbench_modules_list` 里逐条看得到)。**没装好时用户看到的那句话
|
|
262
|
-
由你写**:`libInstallCommand({ need })` 会连源和缓存目录一起拼好,原样给他,别自己拼 npm 命令。
|
|
263
|
-
|
|
264
164
|
## 卡片画哪五态
|
|
265
165
|
|
|
266
166
|
`src/model/overview.ts` 的 `overviewStateOf({ phase, value, error })` 是一条纯函数,
|
|
@@ -279,38 +179,9 @@ env: {
|
|
|
279
179
|
这时刻意继续画旧数据(右上角的转圈由 `Card` 的 `refreshing` 表示),不退回骨架:
|
|
280
180
|
一次刷新不该让整张卡闪一下。
|
|
281
181
|
|
|
282
|
-
##
|
|
182
|
+
## 开发与发布
|
|
283
183
|
|
|
284
|
-
|
|
285
|
-
dsh plugin --profile workbench add <本目录>
|
|
286
|
-
```
|
|
184
|
+
开发命令见上面的六个脚本;dsh / 工作台的模块约束见工作台模块指引(随外壳出货);agent 的工作说明见 `AGENTS.md`。
|
|
287
185
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
* **只用 kit 里真有的成员**:`check:kit` 拿产物实际取的成员去对 peer 下限那份外壳的 kit,
|
|
291
|
-
取了旧外壳没有的成员会在用户机器上首帧渲染就炸(peer 下限因此不许比 devDependency 旧)。
|
|
292
|
-
* **两个运行时不许互引**:宿主侧不许碰 DOM / react,浏览器侧不许碰 `node:` / `process.env` / `Buffer`
|
|
293
|
-
(`check:bundles` 对着产物查)。所以目录读在 `src/host.ts`,卡片只管画。
|
|
294
|
-
跨运行时那一条**层号拦不住**(`components/` 第 2 层、`host.ts` 第 10 层,"向下依赖"看着合法)——
|
|
295
|
-
是 `arch` 判据按面判的:`src/components/**` 整片算 client 面,宿主侧 import 它当场红。
|
|
296
|
-
* **层序**:`src/model/**`(纯逻辑,第 1 层)谁都能读,但它**不碰** react / kit / dsh / `node:*`;
|
|
297
|
-
视图在 `src/components/**`(第 2 层),只许 import 第 1 层;两个面在第 10 层。
|
|
298
|
-
要加一层就在 `arch.config.mjs` 里明写(`moduleArch({ layers: … })`)—— 多出来的目录默认是 S01 红。
|
|
299
|
-
* **语法卫生**(第十三条):`eslint.config.mjs` 那份方言管 `console.log` / `as any` / 手搓颜色字面量 /
|
|
300
|
-
`!important` / hooks 那几条(`rules-of-hooks`、渲染期写 ref、漏依赖是 **error**,其余 recommended 是 **warn**)。
|
|
301
|
-
`check` 把 error 算红、`warn` 只计数;要动手就先 `dsh-wb-module lint --fix`(`check` 一个字节都不改)。
|
|
302
|
-
个别地方真要破例就在 `eslint.config.mjs` 里 `moduleLint({ rules })` 开口子,别悄悄换成手写配置
|
|
303
|
-
(判据按方言指纹认它)。
|
|
304
|
-
* `@/` 只许指自己(`@/modules/__UNIT__/*`);共享代码走包名;`@deepseek-ai/dsh-*` 只在客户端侧用,
|
|
305
|
-
且必须同时写进 `package.json` 的 `dsh.client.external`。
|
|
306
|
-
* **自己的样式走 CSS Modules**(`src/x.module.css` + `import styles from './x.module.css'`):
|
|
307
|
-
类名照常写就行 —— 构建会**按文件路径哈希**(跨模块不会撞),但**别带 `wb-` 前缀**:
|
|
308
|
-
`wb-*` 是外壳的全局类名,模块定义它、或借契约里没有的那一个,`check:style` 都会判红。
|
|
309
|
-
要借外壳契约里的类,写 `:global(.wb-…)`,而且那个类必须在契约里。
|
|
310
|
-
这份骨架带了一份最小的 `src/components/OverviewCard.module.css`(就一档列间距),
|
|
311
|
-
演示"样式与组件同目录";`types/styles.d.ts` 是它给 `tsc` 的类名表声明(少了它 `styles.largest` 直接红)。
|
|
312
|
-
* **源码零注释**(`src/**`):要说的都写在这里。
|
|
313
|
-
|
|
314
|
-
要发布:把 `package.json` 里的 `private` 去掉,并显式写上发布目标
|
|
315
|
-
(`publishConfig.access: "public"` 或指向内网源的 `publishConfig.registry`)——
|
|
316
|
-
`private: true` 的包不许发到任何源,这是刻意的默认。
|
|
186
|
+
发布(维护者确认之后):`dsh-wb-module release --target team|public|tarball|git`;
|
|
187
|
+
`--dry-run` 只预览,`--verify` 只核已发布的包。
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
".": "./dist/host.js",
|
|
9
9
|
"./client": "./client.js"
|
|
10
10
|
},
|
|
11
|
-
"files": ["dist", "client.js", "cordis.patch.yml"],
|
|
11
|
+
"files": ["dist", "client.js", "cordis.patch.yml", "CHANGELOG.md"],
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "dsh-wb-module build",
|
|
14
14
|
"typecheck": "tsc --noEmit",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"external": ["@dsh-workbench/core/client", "@deepseek-ai/dsh-client-ui-primitives"]
|
|
28
28
|
},
|
|
29
29
|
"workbench": {
|
|
30
|
-
"apiVersion":
|
|
30
|
+
"apiVersion": 2,
|
|
31
31
|
"unit": "__UNIT__",
|
|
32
32
|
"card": { "titleKey": "__UNIT__.cardTitle", "order": 90 },
|
|
33
33
|
"tools": [],
|
|
@@ -1,37 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ClientContext, UnitClientApi, UnitClientFace } from '@dsh-workbench/core/contract';
|
|
2
2
|
import { OverviewCard } from './components/OverviewCard';
|
|
3
3
|
import { messages } from './messages';
|
|
4
4
|
|
|
5
5
|
const UNIT_ID = '__UNIT__';
|
|
6
6
|
|
|
7
|
-
type Translate = (key: string, params?: Record<string, unknown>) => string;
|
|
8
|
-
|
|
9
|
-
interface WorkspaceView {
|
|
10
|
-
readonly path: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface BoardProps {
|
|
14
|
-
readonly world: { readonly current: { readonly workspace?: WorkspaceView | null } };
|
|
15
|
-
readonly refreshToken: number;
|
|
16
|
-
readonly onRefresh: () => void;
|
|
17
|
-
readonly t: Translate;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
interface UnitClientFace {
|
|
21
|
-
readonly unit: string;
|
|
22
|
-
readonly messages: { readonly zh: Readonly<Record<string, string>>; readonly en: Readonly<Record<string, string>> };
|
|
23
|
-
readonly card: (props: BoardProps) => ReactElement;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
interface UnitClientApi {
|
|
27
|
-
registerUnit(face: UnitClientFace): () => void;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
interface ClientContext {
|
|
31
|
-
get<T = unknown>(name: string): T | undefined;
|
|
32
|
-
effect(effect: () => void | (() => void), label?: string): () => void;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
7
|
const face: UnitClientFace = {
|
|
36
8
|
unit: UNIT_ID,
|
|
37
9
|
messages,
|