@dsh-workbench/core 0.2.10 → 0.2.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/client.js +942 -211
  2. package/dist/board.css +0 -12
  3. package/dist/client/client.body.js +942 -211
  4. package/dist/client/index.js +943 -212
  5. package/dist/host/index.js +2902 -859
  6. package/dist/types/app/client.d.ts +42 -6
  7. package/dist/types/app/layouts/rightbar/board-plan.d.ts +2 -0
  8. package/dist/types/app/layouts/rightbar/kit-preview-controls.d.ts +10 -0
  9. package/dist/types/app/layouts/rightbar/kit-preview-frame.d.ts +14 -0
  10. package/dist/types/app/layouts/rightbar/kit-preview-rows.d.ts +5 -0
  11. package/dist/types/app/layouts/rightbar/kit-preview.d.ts +3 -0
  12. package/dist/types/app/layouts/rightbar/market-row-icon.d.ts +1 -1
  13. package/dist/types/app/layouts/rightbar/market-section-icon.d.ts +1 -1
  14. package/dist/types/app/layouts/sidebar/workspace-groups.d.ts +0 -1
  15. package/dist/types/app/layouts/sidebar/workspace-items.d.ts +2 -3
  16. package/dist/types/modules/panels/__tests__/diy-harness.d.ts +32 -0
  17. package/dist/types/modules/panels/__tests__/market-tools-harness.d.ts +1 -1
  18. package/dist/types/modules/panels/__tests__/scope-support.d.ts +1 -1
  19. package/dist/types/modules/panels/model/diy-act.d.ts +4 -0
  20. package/dist/types/modules/panels/model/diy-act.test.d.ts +1 -0
  21. package/dist/types/modules/panels/model/diy-browser-act.d.ts +47 -0
  22. package/dist/types/modules/panels/model/diy-browser.d.ts +47 -0
  23. package/dist/types/modules/panels/model/diy-browser.test.d.ts +1 -0
  24. package/dist/types/modules/panels/model/diy-context.d.ts +102 -0
  25. package/dist/types/modules/panels/model/diy-context.test.d.ts +1 -0
  26. package/dist/types/modules/panels/model/diy-logs.d.ts +18 -0
  27. package/dist/types/modules/panels/model/diy-logs.test.d.ts +1 -0
  28. package/dist/types/modules/panels/model/diy-look.d.ts +14 -0
  29. package/dist/types/modules/panels/model/diy-look.test.d.ts +1 -0
  30. package/dist/types/modules/panels/model/diy-net.d.ts +3 -0
  31. package/dist/types/modules/panels/model/diy-net.test.d.ts +1 -0
  32. package/dist/types/modules/panels/model/diy-page.d.ts +15 -0
  33. package/dist/types/modules/panels/model/diy-page.test.d.ts +1 -0
  34. package/dist/types/modules/panels/model/diy-schema.d.ts +9 -0
  35. package/dist/types/modules/panels/model/diy-schema.test.d.ts +1 -0
  36. package/dist/types/modules/panels/model/diy-scripts.d.ts +64 -0
  37. package/dist/types/modules/panels/unit.d.ts +43 -3
  38. package/dist/types/shared/api/diy.d.ts +4 -0
  39. package/dist/types/shared/api/host/__tests__/png-fixtures.d.ts +17 -0
  40. package/dist/types/shared/api/host/mount-units.d.ts +6 -0
  41. package/dist/types/shared/api/host/png-crop.d.ts +21 -0
  42. package/dist/types/shared/api/host/png-crop.test.d.ts +1 -0
  43. package/package.json +2 -2
  44. package/skills/workbench-module-diy/BUILD.md +4 -3
  45. package/skills/workbench-module-diy/DATASOURCE.md +4 -4
  46. package/skills/workbench-module-diy/DESIGN.md +1 -1
  47. package/skills/workbench-module-diy/KIT.md +121 -0
  48. package/skills/workbench-module-diy/PANEL.md +12 -2
  49. package/skills/workbench-module-diy/REVIEW.md +12 -6
  50. package/skills/workbench-module-diy/SKILL.md +7 -6
  51. package/skills/workbench-module-diy/TOOLING.md +5 -4
  52. package/skills/workbench-module-diy/TOOLS.md +3 -3
  53. package/skills/workbench-module-diy/WIRING.md +6 -6
  54. package/dist/types/app/layouts/sidebar/WorkspaceSearchField.d.ts +0 -9
@@ -0,0 +1,64 @@
1
+ export declare const PAGE_PROBE = "(async () => {\n const answer = await fetch('/workbench/api/health').then((one) => one.json()).catch(() => null);\n return JSON.stringify({\n plugin: answer === null ? null : answer.plugin,\n pid: answer === null ? null : answer.pid,\n url: location.href,\n title: document.title,\n });\n})()";
2
+ export declare function cardSelectorOf(unit: string): string;
3
+ export type DomMode = 'none' | 'outline' | 'html';
4
+ export interface PickRequest {
5
+ readonly props: readonly string[];
6
+ readonly parts: readonly string[];
7
+ readonly dom: DomMode;
8
+ }
9
+ export declare const DOM_MODES: readonly DomMode[];
10
+ export interface PickFacts {
11
+ readonly card: Readonly<Record<string, string>>;
12
+ readonly parts: Readonly<Record<string, Readonly<Record<string, string>>>>;
13
+ }
14
+ export declare const DEFAULT_PROPS: readonly ["display", "flexDirection", "gap", "padding", "width", "height", "overflow", "backgroundColor", "color", "borderRadius", "fontSize", "lineHeight", "fontFamily", "boxSizing", "--dsw-radius-xl", "--dsw-alias-bg-layer-1", "--dsw-alias-label-primary"];
15
+ export declare const DEFAULT_PARTS: readonly [".wb-cardHead", ".wb-cardBody"];
16
+ export declare function cleanNames(value: unknown, cap: number): readonly string[];
17
+ export declare function pickOf(raw: Record<string, unknown>): PickRequest;
18
+ export interface CardView {
19
+ readonly ok: boolean;
20
+ readonly anchored: boolean;
21
+ readonly width: number;
22
+ readonly height: number;
23
+ readonly head: string;
24
+ readonly text: string;
25
+ readonly clipped: boolean;
26
+ readonly cards: number;
27
+ readonly rect: {
28
+ readonly x: number;
29
+ readonly y: number;
30
+ readonly width: number;
31
+ readonly height: number;
32
+ };
33
+ readonly dpr: number;
34
+ readonly viewport: {
35
+ readonly width: number;
36
+ readonly height: number;
37
+ };
38
+ readonly pick: PickFacts;
39
+ readonly slot: SlotFacts;
40
+ readonly outline: readonly CardNode[];
41
+ readonly html: string;
42
+ }
43
+ export interface SlotFacts {
44
+ readonly parentWidth: number;
45
+ readonly parentDisplay: string;
46
+ readonly columns: string;
47
+ readonly index: number;
48
+ readonly cards: number;
49
+ readonly viewportHeight: number;
50
+ readonly scrollHeight: number;
51
+ }
52
+ export interface CardNode {
53
+ readonly depth: number;
54
+ readonly tag: string;
55
+ readonly cls: string;
56
+ readonly role: string;
57
+ readonly name: string;
58
+ readonly text: string;
59
+ }
60
+ export declare const EMPTY_CARD: CardView;
61
+ export declare function measureScriptOf(unit: string, pick: PickRequest): string;
62
+ export declare function nodesOf(value: unknown): readonly CardNode[];
63
+ export declare function slotOf(value: unknown): SlotFacts;
64
+ export declare function pickFactsOf(value: unknown): PickFacts;
@@ -1,5 +1,45 @@
1
1
  import type { UnitManifest } from '@dsh-workbench/shared';
2
- export declare const PANELS_TOOL = "workbench_panels";
3
- export declare const MODULES_TOOL = "workbench_modules";
4
- export declare const DIY_TOOL = "workbench_diy";
2
+ export declare const PANELS_LIST_TOOL = "workbench_panels_list";
3
+ export declare const PANELS_SHOW_TOOL = "workbench_panels_show";
4
+ export declare const PANELS_HIDE_TOOL = "workbench_panels_hide";
5
+ export declare const PANELS_MOVE_TOOL = "workbench_panels_move";
6
+ export declare const PANELS_RESET_TOOL = "workbench_panels_reset";
7
+ export declare const PANELS_FOLLOW_TOOL = "workbench_panels_follow";
8
+ export declare const MODULES_LIST_TOOL = "workbench_modules_list";
9
+ export declare const MODULES_INSTALL_TOOL = "workbench_modules_install";
10
+ export declare const MODULES_UPDATE_TOOL = "workbench_modules_update";
11
+ export declare const MODULES_UNINSTALL_TOOL = "workbench_modules_uninstall";
12
+ export declare const MODULES_INSTALL_BUNDLE_TOOL = "workbench_modules_install_bundle";
13
+ export declare const MODULES_UPDATE_DEPENDENCY_TOOL = "workbench_modules_update_dependency";
14
+ export declare const MODULES_UPDATE_PLUGIN_TOOL = "workbench_modules_update_plugin";
15
+ export declare const MODULES_UNINSTALL_PLUGIN_TOOL = "workbench_modules_uninstall_plugin";
16
+ export declare const MODULES_UPDATE_WORKBENCH_TOOL = "workbench_modules_update_workbench";
17
+ export declare const DIY_LIST_TOOL = "workbench_diy_list";
18
+ export declare const DIY_STATUS_TOOL = "workbench_diy_status";
19
+ export declare const DIY_PANEL_TOOL = "workbench_diy_panel";
20
+ export declare const DIY_RECON_TOOL = "workbench_diy_recon";
21
+ export declare const DIY_PROBE_TOOL = "workbench_diy_probe";
22
+ export declare const DIY_LOOK_TOOL = "workbench_diy_look";
23
+ export declare const DIY_INSTALL_TOOL = "workbench_diy_install";
24
+ export declare const DIY_SHOW_TOOL = "workbench_diy_show";
25
+ export declare const DIY_CLICK_TOOL = "workbench_diy_click";
26
+ export declare const DIY_TYPE_TOOL = "workbench_diy_type";
27
+ export declare const DIY_FILL_TOOL = "workbench_diy_fill";
28
+ export declare const DIY_KEYS_TOOL = "workbench_diy_keys";
29
+ export declare const DIY_HOVER_TOOL = "workbench_diy_hover";
30
+ export declare const DIY_SELECT_TOOL = "workbench_diy_select";
31
+ export declare const DIY_SCROLL_TOOL = "workbench_diy_scroll";
32
+ export declare const DIY_RELOAD_TOOL = "workbench_diy_reload";
33
+ export declare const DIY_VIEWPORT_TOOL = "workbench_diy_viewport";
34
+ export declare const DIY_NET_WATCH_TOOL = "workbench_diy_net_watch";
35
+ export declare const DIY_NET_READ_TOOL = "workbench_diy_net_read";
36
+ export declare const DIY_NET_STOP_TOOL = "workbench_diy_net_stop";
37
+ export declare const DIY_LOGS_WATCH_TOOL = "workbench_diy_logs_watch";
38
+ export declare const DIY_LOGS_READ_TOOL = "workbench_diy_logs_read";
39
+ export declare const DIY_LOGS_STOP_TOOL = "workbench_diy_logs_stop";
40
+ export declare const ALL_TOOLS: readonly ["workbench_panels_list", "workbench_panels_show", "workbench_panels_hide", "workbench_panels_move", "workbench_panels_reset", "workbench_panels_follow", "workbench_modules_list", "workbench_modules_install", "workbench_modules_update", "workbench_modules_uninstall", "workbench_modules_install_bundle", "workbench_modules_update_dependency", "workbench_modules_update_plugin", "workbench_modules_uninstall_plugin", "workbench_modules_update_workbench", "workbench_diy_list", "workbench_diy_status", "workbench_diy_panel", "workbench_diy_recon", "workbench_diy_probe", "workbench_diy_look", "workbench_diy_install", "workbench_diy_show", "workbench_diy_click", "workbench_diy_type", "workbench_diy_fill", "workbench_diy_keys", "workbench_diy_hover", "workbench_diy_select", "workbench_diy_scroll", "workbench_diy_reload", "workbench_diy_viewport", "workbench_diy_net_watch", "workbench_diy_net_read", "workbench_diy_net_stop", "workbench_diy_logs_watch", "workbench_diy_logs_read", "workbench_diy_logs_stop"];
41
+ export type PanelsToolName = (typeof ALL_TOOLS)[number];
42
+ export declare const READ_TOOLS: readonly ["workbench_panels_list", "workbench_modules_list", "workbench_diy_list", "workbench_diy_status", "workbench_diy_panel", "workbench_diy_recon", "workbench_diy_probe", "workbench_diy_look", "workbench_diy_click", "workbench_diy_type", "workbench_diy_fill", "workbench_diy_keys", "workbench_diy_hover", "workbench_diy_select", "workbench_diy_scroll", "workbench_diy_reload", "workbench_diy_viewport", "workbench_diy_net_watch", "workbench_diy_net_read", "workbench_diy_net_stop", "workbench_diy_logs_watch", "workbench_diy_logs_read", "workbench_diy_logs_stop"];
43
+ export declare const WRITE_TOOLS: readonly ["workbench_panels_show", "workbench_panels_hide", "workbench_panels_move", "workbench_panels_reset", "workbench_panels_follow", "workbench_modules_install", "workbench_modules_update", "workbench_modules_uninstall", "workbench_modules_install_bundle", "workbench_modules_update_dependency", "workbench_modules_update_plugin", "workbench_modules_uninstall_plugin", "workbench_modules_update_workbench", "workbench_diy_install", "workbench_diy_show"];
44
+ export declare const DIY_TOOLS: readonly ["workbench_diy_list", "workbench_diy_status", "workbench_diy_panel", "workbench_diy_recon", "workbench_diy_probe", "workbench_diy_look", "workbench_diy_install", "workbench_diy_show", "workbench_diy_click", "workbench_diy_type", "workbench_diy_fill", "workbench_diy_keys", "workbench_diy_hover", "workbench_diy_select", "workbench_diy_scroll", "workbench_diy_reload", "workbench_diy_viewport", "workbench_diy_net_watch", "workbench_diy_net_read", "workbench_diy_net_stop", "workbench_diy_logs_watch", "workbench_diy_logs_read", "workbench_diy_logs_stop"];
5
45
  export declare const panelsUnit: UnitManifest;
@@ -3,3 +3,7 @@ export declare const WORKSHOP_RELATIVE = "workbench/diy";
3
3
  export declare const WORKSHOP_TITLE = "\u5DE5\u4F5C\u53F0DIY";
4
4
  export declare const WORKSHOP_PAST_TITLES: readonly string[];
5
5
  export declare function isWorkshopPath(path: string): boolean;
6
+ export interface DiyStartOptions {
7
+ readonly agentPreset: string;
8
+ }
9
+ export declare function diyStartOptions(workspaceId: string, workshopId: string | undefined): DiyStartOptions | undefined;
@@ -0,0 +1,17 @@
1
+ export declare const PNG_SIGNATURE: Buffer<ArrayBuffer>;
2
+ export declare function fixtureOf(options: {
3
+ width: number;
4
+ height: number;
5
+ color?: number;
6
+ depth?: number;
7
+ interlace?: number;
8
+ filter?: number;
9
+ }): Buffer;
10
+ export interface Pixels {
11
+ readonly width: number;
12
+ readonly height: number;
13
+ readonly color: number;
14
+ readonly pixels: Buffer;
15
+ }
16
+ export declare function pixelsOf(bytes: Buffer): Pixels;
17
+ export declare function blockOf(x: number, y: number, width: number, height: number, color: number): Buffer;
@@ -12,6 +12,7 @@ export interface MountedUnit {
12
12
  readonly host: HostUnit;
13
13
  readonly result: UnitMountResult;
14
14
  readonly readiness: UnitReadiness;
15
+ readonly rollbackTools: () => void;
15
16
  }
16
17
  export interface UnitsPlan {
17
18
  readonly mounted: readonly MountedUnit[];
@@ -41,6 +42,11 @@ interface NamedEntry {
41
42
  handle(payload: unknown): unknown;
42
43
  }
43
44
  export declare function failureReasonOf(error: unknown): string;
45
+ export interface PlanFailureSinks {
46
+ readonly note: (unit: string, reason: string) => void;
47
+ readonly warn: (message: string) => void;
48
+ }
49
+ export declare function applyPlanFailures(failures: readonly UnitFailure[], sinks: PlanFailureSinks): void;
44
50
  export declare function mountUnits(units: readonly HostUnit[], deps: {
45
51
  ctx: DshHostContext;
46
52
  logger?: {
@@ -0,0 +1,21 @@
1
+ export interface PngRect {
2
+ readonly x: number;
3
+ readonly y: number;
4
+ readonly width: number;
5
+ readonly height: number;
6
+ }
7
+ export interface PngCrop {
8
+ readonly ok: boolean;
9
+ readonly bytes: Buffer;
10
+ readonly width: number;
11
+ readonly height: number;
12
+ readonly clamped: boolean;
13
+ readonly reason: string;
14
+ }
15
+ export interface PngCropFile {
16
+ readonly ok: boolean;
17
+ readonly clamped: boolean;
18
+ readonly reason: string;
19
+ }
20
+ export declare function cropPng(bytes: Buffer, rect: PngRect): PngCrop;
21
+ export declare function cropPngFile(input: string, output: string, rect: PngRect): PngCropFile;
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-workbench/core",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "个人工作台:Agent 工作台外壳(左栏 + 常驻对话 + 自研右侧数据面板)。",
5
5
  "type": "module",
6
6
  "icon": "./icon.svg",
@@ -87,7 +87,7 @@
87
87
  "access": "public"
88
88
  },
89
89
  "dependencies": {
90
- "@dsh-workbench/shared": "0.2.10"
90
+ "@dsh-workbench/shared": "0.2.11"
91
91
  },
92
92
  "scripts": {
93
93
  "build": "node scripts/build.mjs && node scripts/build-types.mjs",
@@ -53,7 +53,7 @@ pnpm exec dsh-wb-module init <unit> --dir <工坊>/packages/module-<unit> --temp
53
53
  | 模板 | 是什么 | 什么时候用 |
54
54
  |---|---|---|
55
55
  | `minimal`(不写就是它) | 空壳:卡壳、文案、测试都接好了,但**没有数据源** | 你已经清楚要读什么、想从零写 |
56
- | `diy` | 一张**读当前项目的样板卡**:宿主侧一条真读目录的只读路由、卡片三态、`?wbMock=1` 三档夹具 | 你要照着改出一个"真能出数据"的最小版本 —— **建议先用它** |
56
+ | `diy` | 一张**读当前项目的样板卡**:宿主侧一条真读目录的只读路由、卡片五态(待机 / 加载 / 就绪 / 就绪但空 / 出错) | 你要照着改出一个"真能出数据"的最小版本 —— **建议先用它** |
57
57
 
58
58
  ⚠️ `diy` 那份是**给别人照抄的**,所以它自己也必须开箱全绿 —— 门禁会**两个模板各 init 一遍、各跑一遍全套**。
59
59
 
@@ -112,8 +112,9 @@ pnpm selfcheck # 构建 + 把 client.js 当加载器跑一遍(`diy` 骨架
112
112
  这次没查的那几条逐条列出来;**要验它们,回工作台仓库根跑一次 `dsh-wb-module check`**。
113
113
  所以「8 通过 · 2 不适用」不等于十一条全绿 —— 别把这句话念给用户听。
114
114
 
115
- 装上板那一步不是命令,是一把 agent 工具:`workbench_diy`(`action=list` 看工坊里有什么、`action=status, unit=<单元 id>`
116
- 看它现在走到哪一步、`action=install` 装上并上板、`action=open` 只上板)—— 见 §7。
115
+ 装上板那一步不是命令,是一套 agent 工具(一个动词一把):`workbench_diy_list` 看工坊里有什么、
116
+ `workbench_diy_status`(`unit=<单元 id>`)看它现在走到哪一步、`workbench_diy_install` 装上并上板、
117
+ `workbench_diy_show` 只上板 —— 见 §7。**上完板看一眼**:`workbench_diy_look`(只截那张卡 + 按你点的项读样式)—— 用户那一页不会被改动,但他得把工作台切到前台。卡片有没有报错先看 `workbench_diy_panel` 的「崩过 / 读到了」;真要这一页的控制台,用 `workbench_diy_logs_watch` → 做那件事 → `workbench_diy_logs_read`(钩子有上限,见 [REVIEW.md](REVIEW.md) 四步取证的 4)。
117
118
 
118
119
  ## 4 两个面的形状
119
120
 
@@ -20,7 +20,7 @@
20
20
  2. **数据从哪来 —— 四层,从上往下试**(**用户不知道 opencli 是什么,这一整步是你的活**:
21
21
  别问他"你用哪个插件 / 有没有 API",他说的是"我想看 X",把 X 翻成关键词是你的活)。
22
22
 
23
- ① **先看 opencli 自带的**:`workbench_diy`(`action=recon, keyword=<站点名 / 系统名 / 域名>`,
23
+ ① **先看 opencli 自带的**:`workbench_diy_recon`(`keyword=<站点名 / 系统名 / 域名>`,
24
24
  也可以直接给 `url` 让它取域名)。它回这台机器上**已经能用**的能力:站点、命令、`strategy`
25
25
  (PUBLIC / COOKIE / INTERCEPT / UI)以及**要不要用户的 Chrome 登录**,还有能直接照抄的那一行。
26
26
  **有就直接复用:不用装、不用点头。** 官方那份发现指引:`opencli skills read opencli-usage`。
@@ -41,7 +41,7 @@
41
41
  - 想长期用 → 给这个站点写 adapter:`opencli skills read opencli-adapter-author`(侦察 → 字段解码 →
42
42
  写 → verify);以后 adapter 失效:`opencli skills read opencli-autofix`。
43
43
 
44
- **④ 才轮到探** —— 用工具:`workbench_diy`(`action=probe, url=<那套系统的地址>, script=<一段回 JSON 的脚本>`;
44
+ **④ 才轮到探** —— 用工具:`workbench_diy_probe`(`url=<那套系统的地址>, script=<一段回 JSON 的脚本>`;
45
45
  页面里没有接口就给 `css=<选择器>`,走的是同一条固定 DOM 读,`{entries:[{text}]}` 直接回给你)。
46
46
  它用**用户浏览器里现有的登录**去读,并把"读不到"归一成阻断屏认的那几个码 —— 两拍(不在站点上就自己
47
47
  打开 `url` 再读一遍)、刚重启时的重试、平台码到阻断码的映射**都在工具里**,不用你记。
@@ -67,7 +67,7 @@
67
67
  [ADR-0036](../../../../docs/adr/0036-opencli-always-background.md))。
68
68
  - **页面里没有接口时(DOM 读)不要用 `eval` 去 `document.querySelectorAll`**:产物边界判据会拦
69
69
  (宿主产物里不许出现 `document.` / `window.` 字样),而且那样解析只能窝在字符串里、测不了。
70
- 探的时候 `workbench_diy`(`action=probe, url=…, css=<选择器>`)就是这个口子;
70
+ 探的时候 `workbench_diy_probe`(`url=…, css=<选择器>`)就是这个口子;
71
71
  固化时走 `browserFind`,它内部用的就是 `opencli browser <session> find --css '<选择器>' --limit N --text-max M`,
72
72
  回 `{ entries: [{ text, tag, nth }] }` —— **一行的单元格按换行拼在 `text` 里**(`美元\n668.3\n…`),
73
73
  所以"切格 + 按列序取值"是两个纯函数,能单测。很多公开站点一条 XHR 都不发,就该走这条。
@@ -119,4 +119,4 @@
119
119
 
120
120
  - 数据源写在**宿主半侧**,卡片只消费它的返回;
121
121
  - 只读路由出错时回 `{ ok: false, error: { code, message, hint? } }`(HTTP 仍是 200,外壳原样转发);
122
- - `?wbMock=1` 下三档夹具都要能出(正常 / 空 / 出错)—— 这是你自己验收"三态"的快捷方式。
122
+ - 验收时把「正常 / 空 / 出错」三条路都走一遍 —— 真机上造不出来的那几条,写清怎么才能看到(**评审夹具不是必做的**)。
@@ -101,7 +101,7 @@ key 固定 `idle|loading|ready|empty|unreadable`)+ `config[]` + `capabilities[
101
101
  `link` 这类**可选格**留空渲染成 `—`,不算空格。
102
102
 
103
103
  `design.json` 是**设计底稿**:只装人话。"这条能力在哪些工作区出现"**哪里都别写** ——
104
- 它既不在底稿、也不在清单:那是**用户**的事(他在 `workbench_panels` 里决定,见 [TOOLS.md](TOOLS.md) §12)。
104
+ 它既不在底稿、也不在清单:那是**用户**的事(他在 `workbench_panels_show` / `workbench_panels_hide` 里决定,见 [TOOLS.md](TOOLS.md) §12)。
105
105
  协议里那个 `scope`(`tools[].scope` / `card.scope`)**已经删掉**:用户口径是「模块开发者不能定义 scope,
106
106
  scope 永远是用户来决定的」(见 [ADR-0038 §2.4 / §6.16](../../../../docs/adr/0038-module-declaration-protocol.md))。
107
107
  `contracts` 只拿名字把底稿与清单对起来(键集合对账)。
@@ -0,0 +1,121 @@
1
+ # kit 速查:要做什么 → 用哪一件 → 最小写法
2
+
3
+ `dsh-wb-module check --only kit` 列的是**名字**,这一份给的是**用法**。两处都别看漏:
4
+ 名字那份是这台机器上**真有的**(别照抄别的模块),这一份是**怎么拼**。
5
+
6
+ **能拿 kit 的就别自己画。** 理由只有一条:主题、语言、悬停、禁用、焦点环、空态、被裁的处理
7
+ 都只维护一份。自己画的不会跟着换主题,也不会跟着我们改口径 —— 这就是"同一个东西长得不一样"的来源。
8
+
9
+ > 全部从 `const { … } = kit.components`(`@dsh-workbench/core/client`)拿。
10
+ > 例子里的 `h` = `createElement`(`import { createElement as h } from 'react'`)。
11
+
12
+ ## 1 卡片外壳
13
+
14
+ **`Card`** —— 一张卡的壳:标题 + 刷新钮 + 卡片体。面板上每一格都是它。
15
+
16
+ ```tsx
17
+ h(Card, {
18
+ t: props.t,
19
+ title: t('myunit.cardTitle'),
20
+ refreshing: resource.phase === 'loading',
21
+ onRefresh: props.onRefresh,
22
+ }, body)
23
+ ```
24
+
25
+ **`CardHead`** —— 你自己拼卡头时用它(`Card` 内部就是它):`t` / `title` / `meta` / `refreshing` /
26
+ `onRefresh` / `gear` / `onToggleGear`(右上那颗齿轮展开设置面)/ **`platformHref`**(跳整张卡对应的
27
+ 那个系统:只给这一颗就够,那颗 ↗ 立刻画出来)。
28
+
29
+ **`CardAnchor`** —— 外壳自己盖的锚点(`#wb-card-<unit>`)。**别自己写**,也别删。
30
+
31
+ **`CardBoundary`** —— 卡片级错误边界。一张卡炸了不许把整块面板带走;**别自己 try/catch 包渲染**。
32
+
33
+ ## 2 状态:这是下限最集中的地方
34
+
35
+ 先想清五态(`design.json` 的状态矩阵就是这五格):**待机 / 在读 / 就绪 / 就绪但空 / 读不到**。
36
+ 每一态都要有东西画,一个不落。
37
+
38
+ | 什么时候 | 用 |
39
+ |---|---|
40
+ | 有内容了 | `StateBlock`(标题 + `hint` 一行 + 正文) |
41
+ | 要他先选一个 / 正在读 / 读不到 | `CenteredState`(居中一屏) |
42
+ | 就绪但空 | `EmptyState`(它只是 `CenteredState` 的简写) |
43
+ | 在读、且你已经有大概形状 | `SkeletonRows`(骨架) |
44
+ | 一句说明 / 一句报错 | `Note`(`tone: 'problem'` 是报错) |
45
+
46
+ ```tsx
47
+ h(CenteredState, { icon: null, title: t('myunit.loading'), hint: t('myunit.loadingHint'), compact: true })
48
+ h(CenteredState, { icon: null, title: t('myunit.errorTitle'), detail: `${code}: ${message}`, hint: t('myunit.errorHint'), compact: true })
49
+ h(EmptyState, { text: t('myunit.empty'), icon: h(IconPanelScreen, { size: 20 }), compact: true })
50
+ h(StateBlock, { title: rows.title, hint: `3 条 · ${when}`, resolving: t('myunit.rows') }, table)
51
+ h(Note, { children: t('myunit.hint') })
52
+ ```
53
+
54
+ **读不到就说读不到**:`detail` 里放原始错误码与那句话,**绝不显示 0**(0 是"真的是 0",不是"没读到")。
55
+
56
+ ## 3 一行一行
57
+
58
+ **`Rows` + `Row`** —— 键值行(左边名字、右边值)。**别自己写 `ul`/`li` 或 div 行**:
59
+ 分隔线、行距、对齐、两端溢出都在这两件里。
60
+
61
+ ```tsx
62
+ h(Rows, null,
63
+ ...items.map((one) => h(Row, { key: one.name, label: one.name, value: `${size} · ${when}` })))
64
+ ```
65
+
66
+ **`ActivityBlock` / `ActivityLine` / `ActivityAction`** —— "它做过什么"那一块:
67
+ `ActivityLine` 有 `dot` / `text` / `at`(时间)/ `actions`;`ActivityAction` 是行尾那个动作
68
+ (`label` / `icon` / `href` 或 `onClick` / `failed`)。时间一律用它,别自己拼。
69
+
70
+ **`LongTaskRow`** —— 长任务那一行:`status`(四态)+ `headline` + `problem` + `links` + `steps` +
71
+ `raw`。跑得久的事(发布、扫描、迁移)都用它,别自己画进度。
72
+
73
+ **`ApprovalDetail` / `ApprovalPlan`** —— 审批屏上那两格(要用户点头的写操作)。
74
+
75
+ ## 4 表单
76
+
77
+ **`Field`** —— 标签 + 输入的排布(`label` / `hint`),输入框放里面。
78
+
79
+ ```tsx
80
+ h(Field, { label: t('myunit.mode'), hint: t('myunit.modeHint') },
81
+ h(TextField, { value, onChange: (event) => setMode(event.target.value) }))
82
+ ```
83
+
84
+ **`TextField`** —— 单行输入(`invalid` 标红;其余是原生 input 的 props)。
85
+ **`ChoiceGroup`** —— 单选 / 多选(`multi` 区分;`value` 或 `values` + `onChange`;选项 `{ value, label, hint?, disabled? }`)。
86
+ **`SelectField`** —— 下拉:默认是**常规下拉框**(跟 `TextField` 一套),无边框那种给 `variant: 'ghost'`;
87
+ 搜索、分组、禁用项、加载态、空态、页脚都是它的 prop。
88
+ **`SaveBar`** —— 设置面底下那条"取消 / 保存"(`busy` / `disabled` / `labels` / `onCancel` / `onSave`)。
89
+
90
+ **别手写 `<button>` / `<input>` / `<select>`**:dsh 有 `Button`(`variant` / `size`),
91
+ kit 有上面这几件;手写的会拿到浏览器默认样式,而且**判据会红**(`check:style`)。
92
+ `<textarea>` 例外(dsh 与 kit 都没有多行输入)——真要用就直接写,其余形态别开这个口子。
93
+
94
+ ## 5 别的
95
+
96
+ - **`Link`** —— **唯一**的外链写法:`h(Link, { href, children })`。自带新窗口 + `rel="noopener noreferrer"`
97
+ + 正文色 + hover 下划线 + 那颗小箭头。**别去掉箭头、别在文案里自带 `→`、别手写 `<a>`**(判据红)。
98
+ - **`SettingsSection`** —— 一屏设置里的一节(`label` / `title` / `action`)。
99
+ - **`Stack`** —— 竖排 + 统一间距(懒得分组时的默认容器)。
100
+ - **`BlockerState`** —— 阻断屏:要登录 / 没权限 / 能力没装好那一类(`blocker` / `t` / `onRetry` / `onOpenMarket`)。
101
+ - **`Tip`** —— Tooltip:图标钮与只有图的地方**必须**有它(或 `aria-label`)。
102
+ - **`MockStrip`** —— 夹具条(`?wbMock=1` 下给自己造那些真机上做不出来的状态)。
103
+ - **图标** —— `IconPanelScreen`(面板/卡片那类)、`IconModuleMark`(我们的模块类型标)、
104
+ `IconNpm` / `IconOpencli`(包类型);其余图形一律从 dsh 的 `Icon*Regular` 拿,**别自己画图标**。
105
+
106
+ ## 6 三条纪律
107
+
108
+ 1. **调用点只给数据与语义**:`h(Card, { title, refreshing, onRefresh }, …)`。别从外面塞外观类名
109
+ —— 只有布局槽例外(通栏、外边距、网格位置)。
110
+ 2. **字号与颜色走 kit 与令牌**(`--dsw-*` / `--wb-*`),不写字面值。**重点可以大**:
111
+ 一个"余额 / 总数"那种要突出的数字,用刻度里最大那档 `--dsw-font-xl-24`(24/32、600),
112
+ 数字加 `font-variant-numeric: tabular-nums`(见 [PANEL.md](PANEL.md) §6)。
113
+ 刻度外非要一个字面值(比如更大的标题数)时,在模块 `README.md` 里写一句为什么。
114
+ 3. **自己写 CSS 只写布局**(flex / grid / gap / 对齐)。要一个新的"长得像控件"的东西时,
115
+ 先问一句:kit 里是不是已经有了?
116
+
117
+ ## 7 kit 里没有、但你确定要标准控件
118
+
119
+ 见 [PANEL.md](PANEL.md) §9:可以引 dsh 的客户端原语,但**只在浏览器侧**、并在
120
+ `package.json` 的 `dsh.client.external` 里声明 `@deepseek-ai/dsh-client-ui-primitives`
121
+ (宿主侧一个字都不许碰 —— `check` 的源码边界判据会红)。能拿 kit 的时候仍然别自己画。
@@ -133,6 +133,16 @@ agent 那一侧也要能通过模块自己的能力读到同一份偏好。
133
133
  dsh 的 `Button`,或阻断屏里的 `action`。区别在"跳去哪"和"它是不是一个动作",不在好看不好看。
134
134
  - **标签 + 输入框的排布用 `Field`**,输入框用 `TextField` —— 内边距与排版是外壳给的默认,
135
135
  别为"能看"写模块自己的 CSS(见 §5.1)。
136
+ - **要做什么用哪件,照 [KIT.md](KIT.md) 抄**("要做什么 → 用哪件 → 最小写法"的表)。
137
+ 下面几条是口径,KIT.md 是写法。
138
+ - **字号与重点**:走那把刻度(`--dsw-font-*`:11 / 12 / 13 / 14 / 16 / 20 / 24),
139
+ **但不许"整张卡一样大"** —— 要有重点。最该大的那个数字(余额、总数、还剩多少)
140
+ 就用刻度里最大那档 `--dsw-font-xl-24`(24/32、600 重),它旁边的说明 12。
141
+ 这一档是**给重点留的、鼓励用**:卡里最大的字比正文大一倍,比全卡 12 号好读得多。
142
+ 刻度外非要一个字面值(28/32 那种)时,在模块 `README.md` 里写一句为什么,别静悄悄写。
143
+ 成列的数字加 `font-variant-numeric: tabular-nums`。
144
+ **别为了"统一"把重点压回正文大小**——那才是不合口径。
145
+ - **颜色与间距也走令牌**(`--dsw-*` / `--wb-*`);自己写 CSS 只写布局(flex / grid / gap / 对齐)。
136
146
 
137
147
  ## 7 状态矩阵怎么写
138
148
 
@@ -154,7 +164,7 @@ agent 那一侧也要能通过模块自己的能力读到同一份偏好。
154
164
  - **平台没给的那一格就不占位**(不留孤零零的字段名、不留空的一行)。
155
165
  - **读不到就说读不到**,并说清为什么(例:手上这份能力太旧);**绝不显示 0**。
156
166
  - 有确定答案就不打扰他(能按默认读出来就别弹设置面)。
157
- - 真机上造不出来的状态也要写 —— 造出来是评审夹具的事。
167
+ - 真机上造不出来的状态也要画 —— 它在实现时能不能造出来是另一回事(评审夹具,可选)。
158
168
 
159
169
  ### 7.1 图例就是把屏幕画成 ASCII
160
170
 
@@ -177,7 +187,7 @@ agent 那一侧也要能通过模块自己的能力读到同一份偏好。
177
187
  - **用真字**:图里的文案用界面上真实那句(例「没有指派给你的缺陷。」),别写"一句空态文案"。
178
188
  - **别拿图标代替画面**:给状态起个名字、配个 `⛔` 这类符号,用户还是分不清;一眼能认的是**形状**(整卡换掉 vs 多一行)与**位置**(行首的点、行尾的两颗)。
179
189
  - **画不成的才写词**:动作、agent 的下一步、时间性的"不变 / 自己变",写成词并说清意思。
180
- - **真机上造不出来的状态**:沟通时照样画(画的是设计意图);实现时用评审夹具把它造出来给人切。
190
+ - **真机上造不出来的状态**:沟通时照样画(画的是设计意图);实现时有 `?wbMock=1` 那一套就顺手造出来给人切 —— **不是必做**。
181
191
 
182
192
  ## 8 范例(真实产出的形状)
183
193
 
@@ -28,7 +28,7 @@
28
28
 
29
29
  一条结论 + 去哪读全(细节只有一份,住在 BUILD.md):
30
30
 
31
- 1. **装上去没反应** → 先问 `workbench_diy`(`action=status, unit=…`),别自己拼。[WIRING.md](WIRING.md) §3
31
+ 1. **装上去没反应** → 先问 `workbench_diy_status`(`unit=…`),别自己拼。[WIRING.md](WIRING.md) §3
32
32
  2. **改完没热换** → 宿主侧是秒级热换的;客户端侧刷新一下页面。[WIRING.md](WIRING.md) §4
33
33
  3. **"再装一遍"** → 日常迭代只重建、不重装。[WIRING.md](WIRING.md) §3
34
34
 
@@ -36,18 +36,24 @@
36
36
 
37
37
  他说"一片空白 / 一直转圈 / 数字不对"时,**先取证再改代码**,四步按顺序走;前一步有结论就停在那儿修:
38
38
 
39
- 1. **`workbench_diy`(`action=panel`)** —— 这个工作区面板上现在是什么样:哪几张卡、每张最后一次取数
39
+ 1. **`workbench_diy_panel`(``)** —— 这个工作区面板上现在是什么样:哪几张卡、每张最后一次取数
40
40
  是读到了还是读不到(带宿主回的码与原文)、有没有崩过(原因 + 次数)。有答案就别往下走。
41
- 2. **`workbench_diy`(`action=status, unit=…`)** —— 看"起得来:不是 —— …"那一行:模块起不来时
41
+ 1.5 **`workbench_diy_look`(`unit=…`)** —— 只截**这一张卡**(不动用户那一页)+ 它的盒子、卡头、可见文本;要看什么由你点:`props` 挑样式(想解释颜色/圆角就点名 `--dsw-…` 令牌)、`parts` 挑卡片里哪几块。图存进工坊、你自己也能看;配合 `workbench_diy_panel` 的「崩过 / 读到了」就够把"看起来不对"说成"哪一处不对"。
42
+ 2. **`workbench_diy_status`(`unit=…`)** —— 看"起得来:不是 —— …"那一行:模块起不来时
42
43
  原因就在那里(装配抛的错 / 声明被拒的原文)。这一行说"都在生效"才继续第 3 步。
43
44
  3. **自己把那条读跑一遍**:卡片画的就是模块自己那条宿主路由的返回值 ——
44
45
  `curl '<工作台地址>/workbench/api/<unit>/<route>'`(地址就是你起工作台那个,默认 `http://127.0.0.1:3081`)。
45
46
  接口回对了而卡片不对,问题在渲染那一侧;接口就回错,问题在宿主半侧(回头照着 [DATASOURCE.md](DATASOURCE.md) 重探一次)。
46
- 4. **最后才看用户那一页**:`opencli browser <会话> console`(**不带任何过滤** —— 第一次查用过滤会得到
47
- 假的"没有消息",本仓实测踩过)、`state`(这一页是什么、有哪些可点的)、`screenshot <路径>`(截一张自己看)。
47
+ 要的是**浏览器里那一次**的原文(带凭据的那种),用 `workbench_diy_probe`(`script` 里 `fetch` 自己那条读)。
48
+ 4. **最后才看用户那一页**:要这一页的**控制台**,走三拍 —— `workbench_diy_logs_watch`(装上带钩子的收听)→
49
+ 做那件你怀疑会报错的事 → `workbench_diy_logs_read`(最近 50 条,读完 `workbench_diy_logs_stop` 卸掉)。
50
+ 钩子有上限(每页 200 条、单条截 400 字、随刷新消失),不会把用户那一页吃胖。要在页面里跑一段自己的代码
51
+ (读接口 / 抄一个 DOM 值),用 `workbench_diy_probe`。**别用 `opencli browser <会话> console`** ——
52
+ 那条命令拿到的是个 stub(恒回空数组、还 exit 0),看上去像"没有报错"。
53
+ 要看这一页是什么、有哪些可点的,用 `state`;要一张图用 `screenshot <路径>`。
48
54
  宿主侧热换 / 装载的报错在**起工作台那个终端**里,不在浏览器里。
49
55
 
50
- 两条纪律:**别让用户截图或替你转述报错**(第 1 步就是干这个的);**截图与 console 只用来定位,不用来验收** ——
56
+ 两条纪律:**别让用户截图或替你转述报错**(第 1 步就是干这个的);**截图与 probe 只用来定位,不用来验收** ——
51
57
  验收仍是他对着三张表走「打开哪 → 看什么 → 点什么 → 应该看到什么」。
52
58
 
53
59
  面板运行态是**用户那一页自己报上来的**(页面关了就停在最后一次,带时间与新鲜度),所以第 1 步没有内容时,
@@ -14,7 +14,7 @@ description: 在工作台里凭需求造一个模块(面板上的一块内容
14
14
  这一步的产出是 ASCII 画面 + 三张表,不是文字。三张表当场落进模块里的 `design.json`
15
15
  (命令与形状:[DESIGN.md](DESIGN.md) §5)—— 只留在对话里等于没定。
16
16
  3. **先定数据从哪来** —— [DATASOURCE.md](DATASOURCE.md):干净机器上没有现成 skill 可抄,
17
- 用 `workbench_diy`(`action=probe, url=…, script=…`)复用他的登录去探 —— 两拍、重试、失败码归一
17
+ 用 `workbench_diy_probe`(`url=…, script=…`)复用他的登录去探 —— 两拍、重试、失败码归一
18
18
  都在那把工具里;探明后**固化成一条读**(`@dsh-workbench/shared/host` 的 `browserRead`)。顺手把三态写掉。
19
19
  4. **从模板改** —— [BUILD.md](BUILD.md) §1–§2:在工坊里 `dsh-wb-module init` 一个骨架,
20
20
  把 `design.json` 里的**三张表与数据源记录先改成你问出来的那份**(`design check` 会点名叫出还空着哪一格),
@@ -23,14 +23,14 @@ description: 在工作台里凭需求造一个模块(面板上的一块内容
23
23
  命令写成 `pnpm exec dsh-wb-module …`(工坊里 PATH 上也有同名入口,但 `pnpm exec` 到处都对)。
24
24
  5. **自己过门禁** —— `dsh-wb-module build` + `check` + `design check`;
25
25
  红了自己修([REVIEW.md](REVIEW.md) 有对照),不抛给他、也不贴报错给他看。
26
- 6. **让他看一眼** —— 先对着 ASCII 图确认,再用 `workbench_diy`(`action=install, unit=<单元 id>`)装上:
26
+ 6. **让他看一眼** —— 先对着 ASCII 图确认,再用 `workbench_diy_install`(`unit=<单元 id>`)装上:
27
27
  它只认工坊里造好的那个模块,装完顺手在**这个项目**的面板上打开(装的是这台机器上的东西,他会点头一次)。
28
28
  **这一跳永远只走一次**:之后只重建、不重装(宿主侧秒级热换;改了 client.js 刷新一下页面)。
29
29
  拿不准它现在走到哪一步(装了没 / 面板认得没 / 这个项目上开着没 / 产物齐不齐 / 源码比产物新没),
30
- 就问工具:`workbench_diy`(`action=status, unit=<单元 id>`)会把这几件事一次说清并给出下一步 ——
31
- **别自己拼,也别再装一遍**;已经装过只是想上板,用 `action=open`。
30
+ 就问工具:`workbench_diy_status`(`unit=<单元 id>`)会把这几件事一次说清并给出下一步 ——
31
+ **别自己拼,也别再装一遍**;已经装过只是想上板,用 `workbench_diy_show`。
32
32
  7. **验收** —— 逐条对着三张表走:「打开哪 → 看什么 → 点什么 → 应该看到什么」。
33
- 他说哪不对,**先取证再改**:`workbench_diy`(`action=panel`)一次看清面板上每张卡是读到了、读不到还是崩了
33
+ 他说哪不对,**先取证再改**:`workbench_diy_panel` 一次看清面板上每张卡是读到了、读不到还是崩了
34
34
  (四步取证见 [REVIEW.md](REVIEW.md));改完让他看**同一处**;不给他看 diff、日志、代码。
35
35
  逐条过他都说对了,再 `dsh-wb-module design accept` 把验收打勾(**没验完不许打**)。
36
36
 
@@ -48,12 +48,13 @@ description: 在工作台里凭需求造一个模块(面板上的一块内容
48
48
  | 步骤 1 / 2 / 3 各自问什么、什么时候算做完 | [STEPS.md](STEPS.md) |
49
49
  | **数据从哪来**(干净机器上怎么找到那一条读) | [DATASOURCE.md](DATASOURCE.md) |
50
50
  | 面板上该有什么、状态矩阵怎么写、界面习惯 | [PANEL.md](PANEL.md) |
51
+ | **要做什么 → 用哪件 kit → 最小写法**(照抄面) | [KIT.md](KIT.md) |
51
52
  | 能力怎么划、传什么拿回什么、哪几件要点头 | [TOOLS.md](TOOLS.md) |
52
53
  | agent 动完面板哪一格变、他正在干的时候看哪儿 | [LINKAGE.md](LINKAGE.md) |
53
54
  | 落点、文件形状、命令、两个面 | [BUILD.md](BUILD.md) |
54
55
  | 依赖、会被拦的写法、装、热换、发布 | [WIRING.md](WIRING.md) |
55
56
  | 门禁红了、装上去没反应、改完没热换 | [REVIEW.md](REVIEW.md) |
56
- | 用户说"卡片不对 / 一片空白 / 一直转圈"(四步取证) | [REVIEW.md](REVIEW.md) 那一节 · `workbench_diy`(`action=panel`) |
57
+ | 用户说"卡片不对 / 一片空白 / 一直转圈"(四步取证) | [REVIEW.md](REVIEW.md) 那一节 · `workbench_diy_panel`(``) |
57
58
  | 哪条纪律已经固化成工具、哪些还留给 agent | [TOOLING.md](TOOLING.md) |
58
59
 
59
60
  ## 找不到一件事的时候,按这个顺序找
@@ -14,9 +14,10 @@
14
14
  |---|---|
15
15
  | 十一条判据([REVIEW.md](REVIEW.md)) | `dsh-wb-module check`(`--only <判据>` 单跑;`--only kit` 列这份外壳的 kit 成员) |
16
16
  | 落点、文件形状、四个脚本([BUILD.md](BUILD.md) §1–§2) | `dsh-wb-module init --template diy`(骨架带一条真读盘的数据源)+ `scripts/smoke.mjs` |
17
- | 装、上板、不重装([WIRING.md](WIRING.md) §3) | `workbench_diy`(`install` / `open` / `status`)+ `workbench_panels` |
18
- | 它到底起得来没(装配抛的错 / 声明被拒的原文) | `workbench_diy`(`status` 的"起得来"那一行,见 [REVIEW.md](REVIEW.md) 四步取证的第二步) |
19
- | 面板上现在是什么样(每张卡读到没 / 崩没崩) | `workbench_diy`(`panel`:浏览器半侧上报,宿主按 unit 留最新一条;见 [ADR-0042](../../../../docs/adr/0042-panel-runtime-state-is-reported-to-the-host.md)) |
17
+ | 装、上板、不重装([WIRING.md](WIRING.md) §3) | `workbench_diy_install` / `workbench_diy_show` / `workbench_diy_status`(工坊那一跳自己上板,不再指向 `workbench_panels_show`) |
18
+ | 它到底起得来没(装配抛的错 / 声明被拒的原文) | `workbench_diy_status`("起得来"那一行,见 [REVIEW.md](REVIEW.md) 四步取证的第二步) |
19
+ | 这一页的控制台 / 网络怎么取证([REVIEW.md](REVIEW.md) 四步取证) | `workbench_diy_logs_watch` / `_read` / `_stop`(钩子有上限:每页 200 条、单条 400 字);`workbench_diy_net_watch` / `_read` / `_stop`(最多 2000 行 / 15 分钟后自己收工;`_read key=…` 取 body)。**别用 `opencli browser <会话> console`**:那条命令拿到的是 stub,恒回空数组。网络那一头工作台走的是**隔离 opencli**(自己的 `HOME`),它的缓存与你自己 shell 里 `~/.opencli` 那份不是同一个 —— 核验要用工具,别看错文件 |
20
+ | 面板上现在是什么样(每张卡读到没 / 崩没崩) | `workbench_diy_panel`(浏览器半侧上报,宿主按 unit 留最新一条;见 [ADR-0042](../../../../docs/adr/0042-panel-runtime-state-is-reported-to-the-host.md)) |
20
21
  | 空着的格就是还没定的事([DESIGN.md](DESIGN.md) §5) | `design.json` + `design check`(点名叫出空格)/ `render` / `accept` |
21
22
  | 发货前的清单([WIRING.md](WIRING.md) §5) | `dsh-wb-module publish`(门禁 + 打包清单 `files` 漏项,只 dry run) |
22
23
  | 干净机器上的第一脚([SKILL.md](SKILL.md) 第 4 步) | 启动器自愈骨架 + PATH 上的 `dsh-wb-module` |
@@ -29,7 +30,7 @@
29
30
  | DOM 读怎么读([DATASOURCE.md](DATASOURCE.md) 第 2 步) | `probe`(探)+ `browserFind`(固化)—— 切格 / 按列取值是两个纯函数 |
30
31
  | 链接只有一副样子([PANEL.md](PANEL.md) §6) | kit 的 `Link` 是唯一一份(**没有 `icon` 口子**),`check --only style` 拦裸 `<a>` |
31
32
  | 输入框不用自己配样式([PANEL.md](PANEL.md) §5.1) | `TextField` 的默认内边距 + kit 的 `Field`(标签 + 提示的排布) |
32
- | 这个单元在哪些工作区出现([TOOLS.md](TOOLS.md) §12) | **不是模块能声明的事** —— 用户用 `workbench_panels` 的板决定(卡片与全部工具同判);协议里的 `scope` 已删。`gate.deniedTools` 是可选的运行期收窄,门禁不再对它做声明对账 |
33
+ | 这个单元在哪些工作区出现([TOOLS.md](TOOLS.md) §12) | **不是模块能声明的事** —— 用户用 `workbench_panels_*` 的板决定(卡片与全部工具同判);协议里的 `scope` 已删。`gate.deniedTools` 是可选的运行期收窄,门禁不再对它做声明对账 |
33
34
 
34
35
  ## 留给 agent 的(判断,不该固化)
35
36
 
@@ -126,7 +126,7 @@ approvalDetail: async (call) => ({ zh: '会覆盖这 3 个模块的产物,不
126
126
  ## 8 外部供给:这个能力要靠什么才跑得起来
127
127
 
128
128
  模块要的东西机器上不一定有(opencli 插件、内网源上的 npm 库)。**清单里的 `env` 段就是说这件事的** ——
129
- 外壳的隔离运行时按它去装、去探,装机器的状态在 `workbench_modules list` 里逐条看得到
129
+ 外壳的隔离运行时按它去装、去探,状态在 `workbench_modules_list` 里逐条看得到
130
130
  (实测:`cmdb` 声明了插件 `cmdb`/`coding` 与库 `opencli-workflow`,那份列表里就按名字列着它们与版本)。
131
131
 
132
132
  ```ts
@@ -251,10 +251,10 @@ return {
251
251
 
252
252
  外壳自己就是这么用的(工坊里看 `node_modules/@dsh-workbench/core/…`,仓库里是
253
253
  `packages/workbench/src/modules/panels/index.ts`):某张卡在这个项目上没开 → 那张卡的工具全不出现;
254
- 工坊那把窄门工具 `workbench_diy` **只在工坊会话里**出现,别处一律不出现。
254
+ 工坊那七把(`workbench_diy_*`)**只在工坊会话里**出现,别处一律不出现。
255
255
 
256
256
  **"这条能力在哪些工作区出现"不用你声明 —— 那是用户的事。** 用户口径:「模块开发者不能定义 scope,
257
- scope 永远是用户来决定的」。他在 `workbench_panels`(agent 工具)里把它们放上/收起;一个单元在不在,
257
+ scope 永远是用户来决定的」。他在 `workbench_panels_show` / `workbench_panels_hide`(agent 工具)里把它们放上/收起;一个单元在不在,
258
258
  它的卡片与**全部**工具同判(外壳的 `resolveScope` 只看两份板:本工作区那份,否则机器级那份)。
259
259
  协议里那个 `scope`(`dsh.workbench.tools[].scope` / `card.scope`)**已经删掉**,写它就是写了一个
260
260
  不认识的键、`check --only protocol` 当场红(见 [ADR-0038 §2.4 / §6.16](../../../../docs/adr/0038-module-declaration-protocol.md))。
@@ -43,7 +43,7 @@ import 运行值会被**打进你的包**(第二份实例),而且浏览器
43
43
 
44
44
  ## 3 装上,看一眼
45
45
 
46
- 模块造好、门禁绿了,**装这一步你自己做** —— 用 `workbench_diy`(`action=install, unit=<单元 id>`):
46
+ 模块造好、门禁绿了,**装这一步你自己做** —— 用 `workbench_diy_install`(`unit=<单元 id>`):
47
47
 
48
48
  - 它只认**工坊里造好的**那个模块(按 unit 在工坊里查,不接任何路径)。
49
49
  - 装完顺手在**当前会话这个工作区**的面板上把它打开。
@@ -57,15 +57,15 @@ import 运行值会被**打进你的包**(第二份实例),而且浏览器
57
57
  改源码 + `dsh-wb-module build` 就行;同名重装本来就只会得到"要重启",而那一支连重载都不做。
58
58
  - **装 ≠ 上板**:面板默认是空的,得在面板配置里把它打开 —— 那一跳顺手做掉了。
59
59
  只有在"要重启才生效"或它还没登记进外壳时才会没打开,那时返回里会说清:
60
- 等它生效后调 `workbench_panels`(`action=show, id=<单元 id>`)。
61
- - **拿不准它现在走到哪一步就问 `action=status`**:装了没 / 面板认得没 / 这个项目上开着没 / 产物齐不齐 /
60
+ 等它生效后调 `workbench_diy_show`(`unit=<单元 id>`)—— 它只上板、不装,也不会多要一次点头。
61
+ - **拿不准它现在走到哪一步就问 `workbench_diy_status`**:装了没 / 面板认得没 / 这个项目上开着没 / 产物齐不齐 /
62
62
  源码是否比产物新,外加一句"下一步做什么"。这五件事别自己去 panels、profile、工坊目录三处拼 ——
63
63
  实测会拼错(把"要重启才注册"读成"没装上",或者源码改了没 build 却去重装一遍)。
64
- - **已经装过、只是没上板**:`action=open`(只上板,不装)。
64
+ - **已经装过、只是没上板**:`workbench_diy_show`(只上板,不装)。
65
65
 
66
- `workbench_diy` **只在工坊那个会话里**(工具清单按工作区收窄,别处连它都不在),
66
+ 工坊那七把 `workbench_diy_*` **只在工坊那个会话里**(工具清单按工作区收窄,别处连它都不在),
67
67
  落在别的项目里调用会被当场拒 —— 这就是"DIY 模式"在工具面上的样子。
68
- 普通项目里装别的东西是另一把工具:`workbench_modules`。
68
+ 普通项目里装别的东西是另一套工具:`workbench_modules_install` / `workbench_modules_uninstall` 等(见 TOOLS.md)。
69
69
 
70
70
  ## 4 改一行,看到它变
71
71
 
@@ -1,9 +0,0 @@
1
- import type { Translate } from '@dsh-workbench/shared';
2
- export interface WorkspaceSearchFieldProps {
3
- readonly t: Translate;
4
- readonly query: string;
5
- readonly onQuery: (query: string) => void;
6
- readonly onCursor: (step: 1 | -1) => void;
7
- readonly onSubmit: () => void;
8
- }
9
- export declare function WorkspaceSearchField({ t, query, onQuery, onCursor, onSubmit, }: WorkspaceSearchFieldProps): JSX.Element;