@dsh-workbench/shared 0.3.3 → 0.3.5

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 (193) hide show
  1. package/dist/api/blocker-table.d.ts +13 -0
  2. package/dist/api/blocker-table.js +13 -0
  3. package/dist/api/blocker.d.ts +6 -0
  4. package/dist/api/context-ref.d.ts +31 -0
  5. package/dist/api/context-ref.js +28 -0
  6. package/dist/api/dsh-client.d.ts +99 -0
  7. package/dist/api/dsh.d.ts +55 -3
  8. package/dist/api/facts.d.ts +42 -0
  9. package/dist/api/facts.js +33 -0
  10. package/dist/api/host-write.d.ts +19 -1
  11. package/dist/api/host-write.js +12 -0
  12. package/dist/api/long-task.d.ts +36 -0
  13. package/dist/api/long-task.js +27 -0
  14. package/dist/api/unit-board.d.ts +25 -0
  15. package/dist/api/unit-board.js +16 -0
  16. package/dist/api/unit-declaration.d.ts +36 -0
  17. package/dist/api/unit-declaration.js +24 -0
  18. package/dist/api/unit.d.ts +32 -0
  19. package/dist/api/unit.js +5 -0
  20. package/dist/api/workbench-client.d.ts +25 -0
  21. package/dist/api/workbench-client.js +13 -0
  22. package/dist/api/workbench-contract.d.ts +12 -0
  23. package/dist/components/Activity.d.ts +33 -0
  24. package/dist/components/Activity.js +24 -0
  25. package/dist/components/ApprovalDetail.d.ts +15 -1
  26. package/dist/components/ApprovalDetail.js +13 -1
  27. package/dist/components/ApprovalPlan.d.ts +14 -0
  28. package/dist/components/ApprovalPlan.js +8 -0
  29. package/dist/components/Card.d.ts +13 -0
  30. package/dist/components/Card.js +10 -0
  31. package/dist/components/CardAnchor.d.ts +14 -0
  32. package/dist/components/CardAnchor.js +11 -0
  33. package/dist/components/CardBoundary.d.ts +20 -0
  34. package/dist/components/CardBoundary.js +14 -0
  35. package/dist/components/CardHead.d.ts +13 -0
  36. package/dist/components/CardHead.js +10 -0
  37. package/dist/components/ChoiceGroup.d.ts +24 -0
  38. package/dist/components/ChoiceGroup.js +12 -0
  39. package/dist/components/Field.d.ts +10 -0
  40. package/dist/components/Field.js +7 -0
  41. package/dist/components/Form.css +6 -0
  42. package/dist/components/Form.d.ts +20 -0
  43. package/dist/components/Form.js +58 -0
  44. package/dist/components/FormActions.d.ts +14 -0
  45. package/dist/components/FormActions.js +5 -0
  46. package/dist/components/FormNodes.d.ts +103 -0
  47. package/dist/components/FormNodes.js +153 -0
  48. package/dist/components/FormRows.d.ts +20 -0
  49. package/dist/components/FormRows.js +14 -0
  50. package/dist/components/FormTypes.d.ts +59 -0
  51. package/dist/components/FormTypes.js +1 -0
  52. package/dist/components/Link.d.ts +12 -0
  53. package/dist/components/Link.js +9 -0
  54. package/dist/components/LongTaskRow.d.ts +19 -0
  55. package/dist/components/LongTaskRow.js +13 -0
  56. package/dist/components/Note.d.ts +11 -0
  57. package/dist/components/Note.js +8 -0
  58. package/dist/components/ResourceStates.d.ts +25 -0
  59. package/dist/components/ResourceStates.js +22 -0
  60. package/dist/components/Rows.d.ts +17 -0
  61. package/dist/components/Rows.js +11 -0
  62. package/dist/components/SaveBar.d.ts +12 -0
  63. package/dist/components/SaveBar.js +9 -0
  64. package/dist/components/SelectField.d.ts +26 -0
  65. package/dist/components/SelectField.js +22 -0
  66. package/dist/components/SelectFieldNodes.d.ts +24 -0
  67. package/dist/components/SelectFieldNodes.js +24 -0
  68. package/dist/components/SettingsSection.d.ts +11 -0
  69. package/dist/components/SettingsSection.js +8 -0
  70. package/dist/components/Spinner.d.ts +9 -0
  71. package/dist/components/Spinner.js +6 -0
  72. package/dist/components/Stack.d.ts +8 -0
  73. package/dist/components/Stack.js +5 -0
  74. package/dist/components/StateBlock.d.ts +13 -0
  75. package/dist/components/StateBlock.js +10 -0
  76. package/dist/components/TextField.d.ts +8 -0
  77. package/dist/components/TextField.js +5 -0
  78. package/dist/components/board-module.d.ts +30 -0
  79. package/dist/components/icons.d.ts +38 -0
  80. package/dist/components/icons.js +35 -0
  81. package/dist/config/constants.d.ts +45 -0
  82. package/dist/config/constants.js +45 -0
  83. package/dist/config/storage.d.ts +4 -0
  84. package/dist/config/storage.js +4 -0
  85. package/dist/host/browser-read.d.ts +1 -0
  86. package/dist/host/browser-read.js +1 -0
  87. package/dist/host/http.d.ts +3 -0
  88. package/dist/host/http.js +10 -1
  89. package/dist/host/tool-approval.d.ts +5 -0
  90. package/dist/host/tool-approval.js +11 -5
  91. package/dist/host/unit-face.d.ts +5 -1
  92. package/dist/host.d.ts +1 -0
  93. package/dist/host.js +1 -0
  94. package/dist/i18n/en.d.ts +1 -0
  95. package/dist/i18n/en.js +22 -8
  96. package/dist/i18n/index.d.ts +25 -9
  97. package/dist/i18n/index.js +1 -0
  98. package/dist/i18n/merge.d.ts +5 -0
  99. package/dist/i18n/merge.js +2 -0
  100. package/dist/i18n/zh.d.ts +23 -8
  101. package/dist/i18n/zh.js +22 -8
  102. package/dist/index.d.ts +7 -2
  103. package/dist/index.js +7 -2
  104. package/dist/lib/blocker-action.d.ts +22 -0
  105. package/dist/lib/blocker-action.js +13 -0
  106. package/dist/lib/board-fact.d.ts +27 -0
  107. package/dist/lib/board-fact.js +18 -0
  108. package/dist/lib/busy.d.ts +10 -0
  109. package/dist/lib/busy.js +7 -0
  110. package/dist/lib/diy-debug.d.ts +42 -2
  111. package/dist/lib/diy-debug.js +19 -1
  112. package/dist/lib/diy-record.d.ts +98 -0
  113. package/dist/lib/diy-record.js +96 -0
  114. package/dist/lib/form-problem.d.ts +10 -0
  115. package/dist/lib/form-problem.js +10 -0
  116. package/dist/lib/panel-state.d.ts +60 -0
  117. package/dist/lib/panel-state.js +30 -0
  118. package/dist/lib/react.d.ts +39 -0
  119. package/dist/lib/react.js +36 -0
  120. package/dist/lib/text.d.ts +22 -0
  121. package/dist/lib/text.js +22 -0
  122. package/dist/lib/ui.d.ts +50 -0
  123. package/dist/lib/ui.js +41 -0
  124. package/package.json +1 -1
  125. package/src/api/blocker-table.ts +13 -0
  126. package/src/api/blocker.ts +6 -0
  127. package/src/api/context-ref.ts +31 -0
  128. package/src/api/dsh-client.ts +99 -0
  129. package/src/api/dsh.ts +56 -1
  130. package/src/api/facts.ts +42 -0
  131. package/src/api/host-write.ts +20 -1
  132. package/src/api/long-task.ts +36 -0
  133. package/src/api/unit-board.ts +25 -0
  134. package/src/api/unit-declaration.ts +36 -0
  135. package/src/api/unit.ts +32 -0
  136. package/src/api/workbench-client.ts +25 -0
  137. package/src/api/workbench-contract.ts +12 -0
  138. package/src/components/Activity.tsx +33 -0
  139. package/src/components/ApprovalDetail.tsx +17 -1
  140. package/src/components/ApprovalPlan.tsx +14 -0
  141. package/src/components/Card.tsx +13 -0
  142. package/src/components/CardAnchor.tsx +14 -0
  143. package/src/components/CardBoundary.tsx +20 -0
  144. package/src/components/CardHead.tsx +13 -0
  145. package/src/components/ChoiceGroup.tsx +24 -0
  146. package/src/components/Field.tsx +10 -0
  147. package/src/components/Form.css +6 -0
  148. package/src/components/Form.test.tsx +475 -0
  149. package/src/components/Form.tsx +78 -0
  150. package/src/components/FormActions.tsx +14 -0
  151. package/src/components/FormNodes.tsx +193 -0
  152. package/src/components/FormRows.tsx +20 -0
  153. package/src/components/FormTypes.ts +65 -0
  154. package/src/components/FormWiring.test.tsx +88 -0
  155. package/src/components/Link.tsx +12 -0
  156. package/src/components/LongTaskRow.tsx +19 -0
  157. package/src/components/Note.tsx +11 -0
  158. package/src/components/ResourceStates.tsx +25 -0
  159. package/src/components/Rows.tsx +17 -0
  160. package/src/components/SaveBar.tsx +12 -0
  161. package/src/components/SelectField.tsx +26 -0
  162. package/src/components/SelectFieldNodes.tsx +24 -0
  163. package/src/components/SettingsSection.tsx +11 -0
  164. package/src/components/Spinner.tsx +9 -0
  165. package/src/components/Stack.tsx +8 -0
  166. package/src/components/StateBlock.tsx +13 -0
  167. package/src/components/TextField.tsx +8 -0
  168. package/src/components/board-module.ts +30 -0
  169. package/src/components/icons.tsx +38 -0
  170. package/src/config/constants.ts +48 -0
  171. package/src/config/storage.ts +4 -0
  172. package/src/host/browser-read.ts +1 -0
  173. package/src/host/http.ts +15 -1
  174. package/src/host/tool-approval.ts +18 -4
  175. package/src/host/unit-face.ts +6 -1
  176. package/src/host.ts +1 -0
  177. package/src/i18n/en.ts +22 -8
  178. package/src/i18n/index.ts +3 -0
  179. package/src/i18n/merge.ts +5 -0
  180. package/src/i18n/zh.ts +23 -8
  181. package/src/index.ts +7 -2
  182. package/src/lib/blocker-action.ts +22 -0
  183. package/src/lib/board-fact.ts +27 -0
  184. package/src/lib/busy.ts +10 -0
  185. package/src/lib/diy-debug.test.ts +2 -1
  186. package/src/lib/diy-debug.ts +44 -3
  187. package/src/lib/diy-record.test.ts +171 -0
  188. package/src/lib/diy-record.ts +185 -0
  189. package/src/lib/form-problem.ts +10 -0
  190. package/src/lib/panel-state.ts +60 -0
  191. package/src/lib/react.ts +39 -0
  192. package/src/lib/text.ts +22 -0
  193. package/src/lib/ui.ts +50 -0
@@ -7,6 +7,7 @@ import { Field } from './Field.js';
7
7
  import { TextField } from './TextField.js';
8
8
  import './SelectField.css';
9
9
 
10
+ /** 一个候选项:值、人看的标签,可带搜索词、图标、分组与禁用标记。 */
10
11
  export interface SelectFieldOption {
11
12
  readonly value: string;
12
13
  readonly label: ReactNode;
@@ -16,16 +17,19 @@ export interface SelectFieldOption {
16
17
  readonly disabled?: boolean | undefined;
17
18
  }
18
19
 
20
+ /** 名单读不到时的退路:值 + 变更回调,`SelectField` 会由下拉改成手填输入框。 */
19
21
  export interface SelectFieldFallback {
20
22
  readonly value: string;
21
23
  readonly onChange: (next: string) => void;
22
24
  }
23
25
 
26
+ /** 列表底部固定那一条(如"添加工作区"):文案 + 可选图标。 */
24
27
  export interface SelectFieldFooter {
25
28
  readonly label: ReactNode;
26
29
  readonly icon?: ReactNode | undefined;
27
30
  }
28
31
 
32
+ /** `SelectField` 的契约:值、候选、变更回调,外加搜索 / 分组 / 加载 / 空态 / 页脚那一整套开关。 */
29
33
  export interface SelectFieldProps {
30
34
  readonly value: string;
31
35
  readonly options: readonly SelectFieldOption[];
@@ -205,17 +209,20 @@ function framed(
205
209
  return className === undefined ? inner : <div className={className}>{inner}</div>;
206
210
  }
207
211
 
212
+ /** 算候选项用来匹配搜索的文本:有 `searchText` 就用它,否则取字符串标签;标签不是字符串又没给它是空串(这项搜不到)。 */
208
213
  export function searchTextOf(option: SelectFieldOption): string {
209
214
  if (option.searchText !== undefined) return option.searchText;
210
215
  return typeof option.label === 'string' ? option.label : '';
211
216
  }
212
217
 
218
+ /** 默认匹配:查询去空白折成小写后看它是不是 `searchTextOf` 的子串;查询为空或纯空白时一律命中(留在列表里)。 */
213
219
  export function defaultSelectFilter(option: SelectFieldOption, query: string): boolean {
214
220
  const needle = query.trim().toLowerCase();
215
221
  if (needle === '') return true;
216
222
  return searchTextOf(option).toLowerCase().includes(needle);
217
223
  }
218
224
 
225
+ /** 按查询过滤候选 —— 组件与模块自己算列表都用它,好让同一份口径管两处:`filter` 不给就用 `defaultSelectFilter`,顺序不变。 */
219
226
  export function filterSelectOptions(
220
227
  options: readonly SelectFieldOption[],
221
228
  query: string,
@@ -224,6 +231,7 @@ export function filterSelectOptions(
224
231
  return options.filter((option) => filter(option, query));
225
232
  }
226
233
 
234
+ /** 把键盘游标换算成当前该高亮那项的 value:游标 `null`(还没动过键盘)或列表为空时没有高亮;太大的游标收到最后一项,负游标取不到项也回 `null`。 */
227
235
  export function selectCursorValue(
228
236
  shown: readonly SelectFieldOption[],
229
237
  cursor: number | null,
@@ -232,6 +240,7 @@ export function selectCursorValue(
232
240
  return shown[Math.min(cursor, shown.length - 1)]?.value ?? null;
233
241
  }
234
242
 
243
+ /** 按上下键移动游标(两头环绕):还没选过时向下取第一项、向上取最后一项;列表为空回 `null`(一步都动不了)。`step` 1 向下、-1 向上。 */
235
244
  export function stepSelectCursor(
236
245
  shown: readonly SelectFieldOption[],
237
246
  cursor: number | null,
@@ -242,6 +251,7 @@ export function stepSelectCursor(
242
251
  return (cursor + step + shown.length) % shown.length;
243
252
  }
244
253
 
254
+ /** 把候选排成 `Menu` 要的条目表:按 `group` 插分组标题;一项都没有时退回 `emptyText` 那一行(不给就是空表)。`revealId` 是开列表时要滚进可视区的那个 value。 */
245
255
  export function selectEntries(
246
256
  shown: readonly SelectFieldOption[],
247
257
  emptyText: string | undefined,
@@ -323,6 +333,22 @@ function useSelectState(props: SelectFieldProps) {
323
333
  };
324
334
  }
325
335
 
336
+ /**
337
+ * 下拉选择:默认是跟 `TextField` 一套的常规下拉框(不是原生 `<select>`),无边框那种给 `variant: 'ghost'`。
338
+ * `open` 不给就内部自己管(两种接法下 `onOpenChange` 都会通知),`loading` 把触发器换成转圈 + `loadingText`(默认 `…`),
339
+ * `selection` 选选中行标记(`fill` 整行底色 / `check` 行尾勾),`footer` 是列表底部那一条(点它走 `onFooter`,不改当前值)。
340
+ * @param props.value - 当前值;在 `options` 里找不到就按"没选"画 `placeholder`。
341
+ * @param props.options - 全量候选;空数组且给了 `fallback` 时改画手填输入框。
342
+ * @param props.onChange - 选中一项后的回调,回的是那一项的 `value`。
343
+ * @param props.label - 没进 `FormRow` 时的标签兜底;进了那一行就别再给它和 `hint`,否则会多一个标签。
344
+ * @param props.renderValue - 自己渲染触发器上那份当前值;不给就用候选项的 `label`。
345
+ * @param props.placeholder - 没选中时那句话;不给是 `—`。
346
+ * @param props.search - 是否可搜(默认 true);`false` 时不画搜索框、也不自动聚焦。
347
+ * @param props.filter - 自定义匹配;不给用 `defaultSelectFilter`(空白查询一律命中)。
348
+ * @param props.emptyText - 无匹配时的文案,或按查询生成文案的函数(能说"没有匹配 X 的项")。
349
+ * @param props.fallback - 退路:`options` 为空时改成手填(`value` + `onChange`)。
350
+ * @returns 触发器,以及打开时的搜索框与列表。
351
+ */
326
352
  export function SelectField(props: SelectFieldProps) {
327
353
  const {
328
354
  value,
@@ -6,6 +6,21 @@ import {
6
6
  } from '@deepseek-ai/dsh-client-ui-primitives';
7
7
  import { Spinner } from './Spinner.js';
8
8
 
9
+ /**
10
+ * 下拉触发器那颗按钮:图标 + 当前值 + 箭头,交给 `Menu` 当 anchor;宽窄两档共用一个形状。
11
+ * @param args.id - 触发器 id。
12
+ * @param args.className - 触发器类名(已含 variant 与 iconOnly 的档)。
13
+ * @param args.disabled - 是否禁用。
14
+ * @param args.invalid - 置真写 `aria-invalid`。
15
+ * @param args.open - 列表是否开着,写进 `aria-expanded`。
16
+ * @param args.ariaLabel - 读屏名。
17
+ * @param args.title - 原生提示。
18
+ * @param args.icon - 值左边那枚图标;不给不占位。
19
+ * @param args.value - 已渲染好的当前值节点(文本或占位)。
20
+ * @param args.iconOnly - 只画图标(窄栏那颗切换钮),值与箭头都不画。
21
+ * @param args.onOpen - 点它做什么,通常是把列表打开。
22
+ * @returns 一颗 `<button>`。
23
+ */
9
24
  export function selectTriggerNode(args: {
10
25
  readonly id: string | undefined;
11
26
  readonly className: string;
@@ -47,6 +62,15 @@ export function selectTriggerNode(args: {
47
62
  );
48
63
  }
49
64
 
65
+ /**
66
+ * 名单还没读到时的触发器:禁用 + `aria-busy` 的按钮,`SelectField` 在 `loading` 那档画它。
67
+ * @param args.id - 触发器 id。
68
+ * @param args.className - 触发器类名;它后面会接上 loading 那档的类。
69
+ * @param args.invalid - 置真写 `aria-invalid`。
70
+ * @param args.ariaLabel - 读屏名。
71
+ * @param args.loadingText - 占位文案;不给就是 `…`。
72
+ * @returns 一颗禁用且 `aria-busy` 的按钮。
73
+ */
50
74
  export function selectLoadingNode(args: {
51
75
  readonly id: string | undefined;
52
76
  readonly className: string;
@@ -2,6 +2,9 @@ import type { ReactNode } from '../lib/react.js';
2
2
  import { h } from '../lib/react.js';
3
3
  import './SettingsSection.css';
4
4
 
5
+ /**
6
+ * `SettingsSection` 的契约:一节的读屏名、标题、右上动作与内容。
7
+ */
5
8
  export interface SettingsSectionProps {
6
9
  readonly label: string;
7
10
  readonly title: ReactNode;
@@ -9,6 +12,14 @@ export interface SettingsSectionProps {
9
12
  readonly children?: ReactNode | undefined;
10
13
  }
11
14
 
15
+ /**
16
+ * 设置面里的一节:标题 + 右上那颗动作 + 一节内容,把一屏设置分组。
17
+ * @param props.label - 只给读屏的组名(`aria-label`),不是标题。
18
+ * @param props.title - 人看的标题。
19
+ * @param props.action - 标题右侧的动作(如"添加");不给不占位。
20
+ * @param props.children - 这一节的内容。
21
+ * @returns 一个 `role="group"` 的 section。
22
+ */
12
23
  export function SettingsSection({ label, title, action, children }: SettingsSectionProps) {
13
24
  return (
14
25
  <section className="wb-settings" role="group" aria-label={label}>
@@ -2,11 +2,20 @@ import { StateDot } from '@deepseek-ai/dsh-client-ui-primitives';
2
2
  import { h } from '../lib/react.js';
3
3
  import './Spinner.css';
4
4
 
5
+ /**
6
+ * `Spinner` 的契约:直径像素与可选的读屏标签。
7
+ */
5
8
  export interface SpinnerProps {
6
9
  readonly size?: number;
7
10
  readonly label?: string | undefined;
8
11
  }
9
12
 
13
+ /**
14
+ * 转圈:加载态一律用它(`FormActions` 与 `ChoiceGroup` 内部也是它),别自己写动画。
15
+ * @param props.size - 直径像素(默认 14,与 dsh 紧凑控件同高)。
16
+ * @param props.label - 给了才读屏可见(`role="status"`);不给整颗 `aria-hidden`,此时旁边必须有文字。
17
+ * @returns 一枚 `StateDot state="ongoing"`,自身不带文字。
18
+ */
10
19
  export function Spinner({ size = 14, label }: SpinnerProps) {
11
20
  return (
12
21
  <span
@@ -2,10 +2,18 @@ import type { ReactNode } from '../lib/react.js';
2
2
  import { h } from '../lib/react.js';
3
3
  import './Stack.css';
4
4
 
5
+ /**
6
+ * `Stack` 的契约:只带一个竖排内容槽。
7
+ */
5
8
  export interface StackProps {
6
9
  readonly children?: ReactNode | undefined;
7
10
  }
8
11
 
12
+ /**
13
+ * 竖排容器:把相邻内容按统一间距摞起来,懒得自己分组时的默认外包装。
14
+ * @param props.children - 要竖排的内容;不给就渲染一个空容器。
15
+ * @returns 竖排的 div。
16
+ */
9
17
  export function Stack({ children }: StackProps) {
10
18
  return <div className="wb-stack">{children}</div>;
11
19
  }
@@ -2,6 +2,9 @@ import type { ReactNode } from '../lib/react.js';
2
2
  import { h } from '../lib/react.js';
3
3
  import './StateBlock.css';
4
4
 
5
+ /**
6
+ * `StateBlock` 的契约:标题、状态点、提示、正文与行尾动作。
7
+ */
5
8
  export interface StateBlockProps {
6
9
  readonly title?: ReactNode | undefined;
7
10
  readonly dot?: ReactNode | undefined;
@@ -11,6 +14,16 @@ export interface StateBlockProps {
11
14
  readonly children?: ReactNode | undefined;
12
15
  }
13
16
 
17
+ /**
18
+ * 有内容那一段:一个标题 + 一行提示 + 正文,卡里已经读到东西时用它开头。
19
+ * @param props.title - 标题那行;它与 `dot` 都不给时整行不画。
20
+ * @param props.dot - 标题前那枚状态点。
21
+ * @param props.hint - 标题下那行小字(如"3 条 · 刚刚");不给不画。
22
+ * @param props.resolving - "正在读"那行,读数期间挂上去;不给不画。
23
+ * @param props.actions - 底部动作区;给了才画那一条。
24
+ * @param props.children - 正文。
25
+ * @returns 一个 div。
26
+ */
14
27
  export function StateBlock({ title, dot, hint, actions, resolving, children }: StateBlockProps) {
15
28
  return (
16
29
  <div className="wb-stateBlock">
@@ -2,10 +2,18 @@ import type { InputHTMLAttributes } from 'react';
2
2
  import './TextField.css';
3
3
  import { h } from '../lib/react.js';
4
4
 
5
+ /**
6
+ * `TextField` 的契约:原生 input 属性,外加一个标红开关。
7
+ */
5
8
  export interface TextFieldProps extends InputHTMLAttributes<HTMLInputElement> {
6
9
  readonly invalid?: boolean;
7
10
  }
8
11
 
12
+ /**
13
+ * 单行文本输入:设置面与筛选框都用它,`invalid` 之外的属性全部透传给原生 `<input>`。
14
+ * @param props.invalid - 置真标红并写 `aria-invalid`(默认 false);错在哪一句归 `FormRow.problem`。
15
+ * @returns 输入框元素;`className` 是追加在基础类名之后,而不是替换它。
16
+ */
9
17
  export function TextField({ invalid = false, className, ...rest }: TextFieldProps) {
10
18
  return (
11
19
  <input
@@ -4,10 +4,16 @@ import type { UnitManifest } from '../api/unit.js';
4
4
  import type { CapabilityPayload } from '../api/workbench-contract.js';
5
5
  import type { Dictionaries } from '../i18n/index.js';
6
6
 
7
+ /**
8
+ * 面板要跳去的一格:只用 `kind` 说去哪,具体负载由各 unit 自己约定。
9
+ */
7
10
  export interface PanelStep {
8
11
  readonly kind: string;
9
12
  }
10
13
 
14
+ /**
15
+ * 交给面板渲染的宿主世界:工作区与会话快照、装着的 unit、面板开关、能力表与当前选中。
16
+ */
11
17
  export interface SuiteWorld {
12
18
  readonly workspaces: WorkspaceSnapshot;
13
19
  readonly sessions: SessionsSnapshot;
@@ -20,6 +26,9 @@ export interface SuiteWorld {
20
26
  };
21
27
  }
22
28
 
29
+ /**
30
+ * 面板视图的统一 prop:一份世界快照 + 刷新令牌 + 四个通知回调 + 翻译函数。
31
+ */
23
32
  export interface BoardModuleProps {
24
33
  readonly world: SuiteWorld;
25
34
  readonly refreshToken: number;
@@ -30,16 +39,25 @@ export interface BoardModuleProps {
30
39
  readonly t: Translate;
31
40
  }
32
41
 
42
+ /**
43
+ * 一张卡报给宿主的摘要:此刻在不在刷新、上次读到是哪个时刻。
44
+ */
33
45
  export interface ModuleSummary {
34
46
  readonly refreshing: boolean;
35
47
  readonly at: number | null;
36
48
  }
37
49
 
50
+ /**
51
+ * unit 的一个子步骤:标题文案键 + 渲染这件视图的组件。
52
+ */
38
53
  export interface UnitStep {
39
54
  readonly titleKey: string;
40
55
  readonly View: (props: BoardModuleProps) => ReactNode;
41
56
  }
42
57
 
58
+ /**
59
+ * 一个面板单元的注册面:id、标题键、排序、视图,可带一组子步骤。
60
+ */
43
61
  export interface BoardModule {
44
62
  readonly id: string;
45
63
  readonly titleKey: string;
@@ -48,6 +66,9 @@ export interface BoardModule {
48
66
  readonly steps?: Readonly<Record<string, UnitStep>>;
49
67
  }
50
68
 
69
+ /**
70
+ * 模块在浏览器侧交出来的那份面:卡片、子步骤、自己的字典,以及审批渲染。
71
+ */
51
72
  export interface UnitClientFace {
52
73
  readonly unit: string;
53
74
  readonly card?: (props: BoardModuleProps) => ReactNode;
@@ -61,15 +82,24 @@ export interface UnitClientFace {
61
82
  | undefined;
62
83
  }
63
84
 
85
+ /**
86
+ * 带着自己 manifest 的模块面(外壳按 unit 认领时用)。
87
+ */
64
88
  export interface UnitOwnedClientFace extends UnitClientFace {
65
89
  readonly manifest: UnitManifest;
66
90
  }
67
91
 
92
+ /**
93
+ * 一次审批要看的输入:工具名,与它这次收到的参数。
94
+ */
68
95
  export interface ApprovalInput {
69
96
  readonly tool: string;
70
97
  readonly args: Readonly<Record<string, unknown>>;
71
98
  }
72
99
 
100
+ /**
101
+ * 模块注册进外壳的那个口:注册一份面(拿回注销函数)、列出已装 unit。
102
+ */
73
103
  export interface UnitClientApi {
74
104
  registerUnit(face: UnitClientFace): () => void;
75
105
  manifests(): readonly UnitManifest[];
@@ -1,5 +1,8 @@
1
1
  import { h, type ReactNode } from '../lib/react.js';
2
2
 
3
+ /**
4
+ * 自绘图标的契约:只给边长像素。
5
+ */
3
6
  export interface IconProps {
4
7
  size?: number;
5
8
  }
@@ -31,6 +34,11 @@ function Shell({
31
34
  );
32
35
  }
33
36
 
37
+ /**
38
+ * 会话图标:一枚对话气泡,dsh 的图标表里没有,留给我们自己用;默认 16px。
39
+ * @param props.size - 边长像素,同时给 `width` 与 `height`。
40
+ * @returns 一枚 `aria-hidden` 的 svg。
41
+ */
34
42
  export function IconConversation({ size = 16 }: IconProps) {
35
43
  return (
36
44
  <Shell size={size} strokeWidth={1.3}>
@@ -43,6 +51,11 @@ export function IconConversation({ size = 16 }: IconProps) {
43
51
  );
44
52
  }
45
53
 
54
+ /**
55
+ * 品牌标:侧栏顶上那枚工作台方块标;默认 24px(它是品牌物,别拿它当通用图标)。
56
+ * @param props.size - 边长像素,同时给 `width` 与 `height`。
57
+ * @returns 一枚 `aria-hidden` 的 svg。
58
+ */
46
59
  export function IconBrandMark({ size = 24 }: IconProps) {
47
60
  return (
48
61
  <Shell size={size} strokeWidth={1.6} viewBox="0 0 24 24">
@@ -56,6 +69,11 @@ export function IconBrandMark({ size = 24 }: IconProps) {
56
69
  );
57
70
  }
58
71
 
72
+ /**
73
+ * opencli 的标:包类型那一格用它;颜色写死的品牌物,不跟主题走。默认 16px。
74
+ * @param props.size - 边长像素,同时给 `width` 与 `height`。
75
+ * @returns 一枚 `aria-hidden` 的 svg。
76
+ */
59
77
  export function IconOpencli({ size = 16 }: IconProps) {
60
78
  return (
61
79
  <Shell size={size} viewBox="0 0 64 64">
@@ -83,6 +101,11 @@ export function IconOpencli({ size = 16 }: IconProps) {
83
101
  );
84
102
  }
85
103
 
104
+ /**
105
+ * npm 的标:包类型那一格用它;保留品牌红,不跟主题走。默认 16px。
106
+ * @param props.size - 边长像素,同时给 `width` 与 `height`。
107
+ * @returns 一枚 `aria-hidden` 的 svg。
108
+ */
86
109
  export function IconNpm({ size = 16 }: IconProps) {
87
110
  return (
88
111
  <Shell size={size} viewBox="0 0 1024 1024">
@@ -94,6 +117,11 @@ export function IconNpm({ size = 16 }: IconProps) {
94
117
  );
95
118
  }
96
119
 
120
+ /**
121
+ * 四宫格:"模块"这个概念的通用单色图标(侧栏、面板那颗钮、市场节的标题);默认 16px。
122
+ * @param props.size - 边长像素,同时给 `width` 与 `height`。
123
+ * @returns 一枚 `aria-hidden` 的 svg。
124
+ */
97
125
  export function IconModules({ size = 16 }: IconProps) {
98
126
  return (
99
127
  <svg width={size} height={size} viewBox="0 0 16 16" fill="none" aria-hidden="true">
@@ -105,6 +133,11 @@ export function IconModules({ size = 16 }: IconProps) {
105
133
  );
106
134
  }
107
135
 
136
+ /**
137
+ * 模块类型标:渐变方块 + 白四宫格,用在"这是个模块"的行首;品牌物,颜色不跟主题走。默认 16px。
138
+ * @param props.size - 边长像素,同时给 `width` 与 `height`。
139
+ * @returns 一枚 `aria-hidden` 的 svg。
140
+ */
108
141
  export function IconModuleMark({ size = 16 }: IconProps) {
109
142
  return (
110
143
  <Shell size={size} viewBox="0 0 24 24">
@@ -124,6 +157,11 @@ export function IconModuleMark({ size = 16 }: IconProps) {
124
157
  );
125
158
  }
126
159
 
160
+ /**
161
+ * 面板 / 卡片那类的图标:整屏与柱状那种形状,给"面板"这一档用;默认 16px。
162
+ * @param props.size - 边长像素,同时给 `width` 与 `height`。
163
+ * @returns 一枚 `aria-hidden` 的 svg。
164
+ */
127
165
  export function IconPanelScreen({ size = 16 }: IconProps) {
128
166
  return (
129
167
  <svg
@@ -1,74 +1,122 @@
1
+ /** 外壳插件的包名:浏览器模块表与宿主按它注册、认领这枚插件。 */
1
2
  export const PLUGIN_ID = "@dsh-workbench/core";
2
3
 
4
+ /** 与外壳同批发布的兄弟包名:`release.ts` 用它判一版三包是否都已在源上。 */
3
5
  export const RELEASE_SIBLINGS = [
4
6
  "@dsh-workbench/shared",
5
7
  "@dsh-workbench/module-tools",
6
8
  ] as const;
7
9
 
10
+ /** 外壳宿主 HTTP API 的根前缀;prefs / actions / provision 等路由都挂在它下面。 */
8
11
  export const API_PREFIX = "/workbench/api";
9
12
 
13
+ /** 模块偏好读写的路由前缀,挂在外壳 API 下:走写通道,拒写回 write-refused。 */
10
14
  export const PREFS_PREFIX = `${API_PREFIX}/prefs`;
11
15
 
16
+ /** 事实(facts)SSE 事件流的端点路径;前端 `EventSource` 按它带 `since` 游标续传。 */
12
17
  export const EVENTS_PATH = "/workbench/events";
18
+ /** 写动作的路由前缀,挂在外壳 API 下:模块的写通道 POST 到这里。 */
13
19
  export const ACTIONS_PREFIX = `${API_PREFIX}/actions`;
14
20
 
21
+ /** 机器供给(provision)的路由前缀:只收 POST,装机时用它铺一份运行环境。 */
15
22
  export const PROVISION_PREFIX = `${API_PREFIX}/provision`;
16
23
 
24
+ /** 面板运行态上报的路径段:崩溃与卡片收尾分别 POST 到 `panel-state/crash`、`panel-state/cards`。 */
17
25
  export const PANEL_STATE_PATH = "panel-state";
18
26
 
27
+ /** DIY 调试动作的路径段;宿主按它登记 `/workbench/api/diy-debug` 前缀。 */
19
28
  export const DIY_DEBUG_PATH = "diy-debug";
29
+ /** 调试动作的子路径(`diy-debug/act`);仓内暂无调用点,动作现走 `DIY_DEBUG_SERVICE`。 */
20
30
  export const DIY_DEBUG_ACT_PATH = `${DIY_DEBUG_PATH}/act`;
21
31
 
32
+ /** DIY 录制交接的路径段;宿主按它登记 `/workbench/api/diy-record` 前缀。 */
33
+ export const DIY_RECORD_PATH = "diy-record";
34
+ /** 录制动作的子路径(`diy-record/act`);仓内暂无调用点,录制现走 `DIY_RECORD_SERVICE`。 */
35
+ export const DIY_RECORD_ACT_PATH = `${DIY_RECORD_PATH}/act`;
36
+
37
+ /** 面板运行态的路由前缀,挂在外壳 API 下:上报口用它,拒报回 report-refused。 */
22
38
  export const PANEL_STATE_PREFIX = `${API_PREFIX}/${PANEL_STATE_PATH}`;
23
39
 
40
+ /** 钉住的 opencli 插件版本:`opencli-runtime` 按它校验、取那一份。 */
24
41
  export const OPENCLI_PIN = "1.8.8";
25
42
 
43
+ /** 缺陷看板套件的 dsh 服务名(`workbenchCodingIssues`):套件照它登记自己那份服务。 */
26
44
  export const CODING_ISSUES_SERVICE = "workbenchCodingIssues";
27
45
 
46
+ /** 面板套件的 dsh 服务名(`workbenchPanels`):宿主经它取套件公布的清单与订阅。 */
28
47
  export const PANELS_SERVICE = "workbenchPanels";
29
48
 
49
+ /** 单元花名册服务的 dsh 服务名(`workbenchUnits`):模块经它认领自己的单元。 */
30
50
  export const UNITS_SERVICE = "workbenchUnits";
31
51
 
52
+ /** 面板运行态服务的 dsh 服务名(`workbenchPanelState`):宿主注册,读口按它取。 */
32
53
  export const PANEL_STATE_SERVICE = "workbenchPanelState";
33
54
 
55
+ /** DIY 调试约定服务的 dsh 服务名(`workbenchDiyDebug`):调试动作走它,不走 HTTP。 */
34
56
  export const DIY_DEBUG_SERVICE = "workbenchDiyDebug";
35
57
 
58
+ /** DIY 录制交接服务的 dsh 服务名(`workbenchDiyRecord`):`diy-acts.ts` 经它收发录制三拍。 */
59
+ export const DIY_RECORD_SERVICE = "workbenchDiyRecord";
60
+
61
+ /** 会话投影服务的 dsh 服务名(`sessionProjections`):宿主经 `stateOf(session, 'agentPreset')` 读那一档。 */
62
+ export const SESSION_PROJECTIONS_SERVICE = "sessionProjections";
63
+
64
+ /** 面板清单一次取回的行数上限(条):外部模块拿它当 opencli `--pageSize`。 */
36
65
  export const PANEL_LIST_LIMIT = 50;
37
66
 
67
+ /** 单次 opencli 调用的默认超时(毫秒);调用点没传 `timeoutMs` 时按它算。 */
38
68
  export const OPENCLI_TIMEOUT_MS = 120_000;
39
69
 
70
+ /** 同时最多几条 opencli 在跑(条):`opencli.ts` 超出就排队。 */
40
71
  export const OPENCLI_MAX_CONCURRENT = 4;
41
72
 
73
+ /** 启动预热站点那一跳的超时(毫秒);超时按未热对待,不挡面板。 */
42
74
  export const WARM_TIMEOUT_MS = 20_000;
43
75
 
76
+ /** 装 / 卸模块包的超时(毫秒):pnpm 与取包内容都按它算。 */
44
77
  export const INSTALL_TIMEOUT_MS = 300_000;
45
78
 
79
+ /** 查 registry 版本那一次请求的超时(毫秒)。 */
46
80
  export const VERSION_CHECK_TIMEOUT_MS = 10_000;
47
81
 
82
+ /** 版本查询结果的缓存有效期(毫秒):期内不发第二次请求。 */
48
83
  export const VERSION_CHECK_TTL_MS = 600_000;
49
84
 
85
+ /** 单元探测结果的缓存有效期(毫秒)。 */
50
86
  export const PROBE_TTL_MS = 30_000;
51
87
 
88
+ /** 加载态至少显示多久(毫秒):一瞬完成的读写也得让用户看见那一下。 */
52
89
  export const MIN_BUSY_MS = 500;
53
90
 
91
+ /** 工具读出来的值交给面板后的保留时长(毫秒):过期就不再算这一拍。 */
54
92
  export const HANDOFF_TTL_MS = 15_000;
55
93
 
94
+ /** 跑本地 git 命令的超时(毫秒);短于对外的 `OPENCLI_TIMEOUT_MS`。 */
56
95
  export const GIT_TIMEOUT_MS = 10_000;
57
96
 
97
+ /** 面板轮询事实的间隔(毫秒):SSE 断了走这条兜底轮询。 */
58
98
  export const PANEL_POLL_MS = 2000;
59
99
 
100
+ /** 没起中文名的工作区默认标题:左栏按它分组、显示。 */
60
101
  export const DEFAULT_WORKSPACE_TITLE = "default-workspace";
61
102
 
103
+ /** 记住当前项目的 localStorage 键。 */
62
104
  export const STORAGE_KEY_PROJECT = "dsh-workbench.project";
63
105
 
106
+ /** 记住右栏展开与否的 localStorage 键。 */
64
107
  export const STORAGE_KEY_RIGHTBAR = "dsh-workbench.rightbar.expanded";
65
108
 
109
+ /** 记住右栏当前列的 localStorage 键。 */
66
110
  export const STORAGE_KEY_RIGHTBAR_COLUMN = "dsh-workbench.rightbar.column";
67
111
 
112
+ /** 记住右栏宽度(像素数字的字符串)的 localStorage 键。 */
68
113
  export const STORAGE_KEY_RIGHTBAR_WIDTH = "dsh-workbench.rightbar.width";
69
114
 
115
+ /** 右栏宽度达到或超过多少像素就按全屏对待(阈值,像素)。 */
70
116
  export const RIGHTBAR_FULLSCREEN_WIDTH = 768;
71
117
 
118
+ /** dsh i18n 的命名空间:词表都注册在它下面,`ctx.locale.register` / `bind` 按它取。 */
72
119
  export const LOCALE_NS = "workbench";
73
120
 
121
+ /** 外壳支持的语言标识,顺序即回退顺序:首项是基准,其余词表必须与它逐键同构。 */
74
122
  export const LOCALES = ["zh", "en"] as const;
@@ -1,3 +1,4 @@
1
+ /** 读 localStorage 里的一段文本;没有这个键、或存储不可用(隐私模式)时返回 null。 */
1
2
  export function readText(key: string): string | null {
2
3
  try {
3
4
  return window.localStorage.getItem(key);
@@ -6,6 +7,7 @@ export function readText(key: string): string | null {
6
7
  }
7
8
  }
8
9
 
10
+ /** 写一段文本进 localStorage;`value` 为 null 时删掉这个键,写不进去就静默作罢。 */
9
11
  export function writeText(key: string, value: string | null): void {
10
12
  try {
11
13
  if (value === null) window.localStorage.removeItem(key);
@@ -14,12 +16,14 @@ export function writeText(key: string, value: string | null): void {
14
16
  }
15
17
  }
16
18
 
19
+ /** 读一个布尔开关:键不存在时返回 `fallback`,只有存进去的 `'true'` 算真。 */
17
20
  export function readFlag(key: string, fallback: boolean): boolean {
18
21
  const raw = readText(key);
19
22
  if (raw === null) return fallback;
20
23
  return raw === 'true';
21
24
  }
22
25
 
26
+ /** 把一个布尔开关写进 localStorage(存 `'true'` / `'false'`);失败静默。 */
23
27
  export function writeFlag(key: string, value: boolean): void {
24
28
  writeText(key, value ? 'true' : 'false');
25
29
  }
@@ -5,6 +5,7 @@ import { runShell } from './shell.js';
5
5
 
6
6
  export const BROWSER_SCREEN_SESSION = 'workbench';
7
7
  export const BROWSER_READ_SESSION = 'workbench-read';
8
+ export const BROWSER_RECORD_SESSION = 'workbench-rec';
8
9
 
9
10
  const DEFAULT_TIMEOUT_MS = 25000;
10
11
  const DEFAULT_RETRY_MS = 1200;
package/src/host/http.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import type { DshHostContext, DshRequest, DshResponse } from '../api/dsh.js';
2
2
  import { unitFactScope } from '../api/facts.js';
3
3
  import { parseCardsReport, parseCrashReport, type PanelStateStore } from '../lib/panel-state.js';
4
- import { ACTIONS_PREFIX, API_PREFIX, DIY_DEBUG_PATH, PANEL_STATE_PREFIX, PLUGIN_ID, PREFS_PREFIX, PROVISION_PREFIX } from '../config/constants.js';
4
+ import { ACTIONS_PREFIX, API_PREFIX, DIY_DEBUG_PATH, DIY_RECORD_PATH, PANEL_STATE_PREFIX, PLUGIN_ID, PREFS_PREFIX, PROVISION_PREFIX } from '../config/constants.js';
5
5
  import {
6
6
  parseProvisionBody,
7
7
  type ProvisionHandler,
@@ -340,3 +340,17 @@ export function registerDiyDebugAct(
340
340
  wrote: () => log?.bump(['diy-debug']),
341
341
  });
342
342
  }
343
+
344
+ export function registerDiyRecordAct(
345
+ ctx: DshHostContext,
346
+ resource: PostResource,
347
+ log?: { bump(facts: readonly string[]): unknown } | undefined,
348
+ ): () => void {
349
+ return registerPost(ctx, {
350
+ prefix: `${API_PREFIX}/${DIY_RECORD_PATH}`,
351
+ what: '工作台 DIY 录制交接动作',
352
+ refusedCode: 'action-refused',
353
+ resources: [resource],
354
+ wrote: () => log?.bump(['diy-record']),
355
+ });
356
+ }
@@ -20,6 +20,15 @@ export const APPROVAL_NOTE_ARG = 'approval_note';
20
20
 
21
21
  export const APPROVAL_NOTE_LIMIT = 40;
22
22
 
23
+ export const APPROVAL_DETAIL_LIMIT = 20;
24
+
25
+ export const APPROVAL_QUESTION_LIMIT = 40;
26
+
27
+ export const APPROVAL_SEPARATOR_LIMIT = 8;
28
+
29
+ export const APPROVAL_HEADLINE_LIMIT =
30
+ APPROVAL_DETAIL_LIMIT + APPROVAL_QUESTION_LIMIT + APPROVAL_NOTE_LIMIT + APPROVAL_SEPARATOR_LIMIT;
31
+
23
32
  const NOTE_SCHEMA: Readonly<Record<string, unknown>> = {
24
33
  type: 'string',
25
34
  description: '一句话说明你这次想拿它干什么(要点头时显示在审批屏上;40 字以内)。',
@@ -45,12 +54,16 @@ export function withApprovalNote(
45
54
  };
46
55
  }
47
56
 
57
+ function oneLine(text: string): string {
58
+ return text.replace(/\s+/g, ' ').trim();
59
+ }
60
+
48
61
  function approvalNoteOf(call: ApprovalCall): string | undefined {
49
62
  const raw = call.arguments;
50
63
  const value =
51
64
  typeof raw === 'object' && raw !== null ? (raw as Record<string, unknown>)[APPROVAL_NOTE_ARG] : undefined;
52
65
  if (typeof value !== 'string') return undefined;
53
- const line = value.replace(/\s+/g, ' ').trim();
66
+ const line = oneLine(value);
54
67
  if (line === '') return undefined;
55
68
  return line.length > APPROVAL_NOTE_LIMIT ? `${line.slice(0, APPROVAL_NOTE_LIMIT)}…` : line;
56
69
  }
@@ -76,6 +89,7 @@ export type ApprovalPolicy = (call: ApprovalCall) => ApprovalVerdict | undefined
76
89
  export interface ApprovalContext {
77
90
  readonly writes?: boolean | undefined;
78
91
  readonly policy?: ApprovalPolicy | undefined;
92
+ readonly declaredNote?: boolean | undefined;
79
93
  readonly detailOf?:
80
94
  | ((call: ApprovalCall) => ApprovalFacts | undefined | Promise<ApprovalFacts | undefined>)
81
95
  | undefined;
@@ -97,8 +111,8 @@ function compose(
97
111
  en: `${detail.en} — ${verdict.question.en}`,
98
112
  zh: `${detail.zh} —— ${verdict.question.zh}`,
99
113
  };
100
- if (note === undefined) return base;
101
- return { en: `${base.en} — ${note}`, zh: `${base.zh} —— ${note}` };
114
+ const line = note === undefined ? base : { en: `${base.en} — ${note}`, zh: `${base.zh} —— ${note}` };
115
+ return { en: oneLine(line.en), zh: oneLine(line.zh) };
102
116
  }
103
117
 
104
118
  export async function approvalFor(
@@ -139,6 +153,6 @@ export async function approvalFor(
139
153
  return {
140
154
  kind: 'ask',
141
155
  reason: verdict.reason,
142
- displayReason: compose(facts, verdict, approvalNoteOf(call)),
156
+ displayReason: compose(facts, verdict, context.declaredNote === true ? approvalNoteOf(call) : undefined),
143
157
  };
144
158
  }