@dsh-workbench/shared 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 (89) hide show
  1. package/dist/api/dsh-client.d.ts +4 -1
  2. package/dist/components/Activity.css +1 -0
  3. package/dist/components/Card.css +4 -4
  4. package/dist/components/CardAnchor.d.ts +7 -0
  5. package/dist/components/CardAnchor.js +8 -0
  6. package/dist/components/CardAnchor.module.css +3 -0
  7. package/dist/components/CardHead.css +1 -5
  8. package/dist/components/ChoiceGroup.css +11 -2
  9. package/dist/components/Field.css +4 -0
  10. package/dist/components/MockStrip.css +6 -0
  11. package/dist/components/Note.css +4 -4
  12. package/dist/components/SelectField.css +156 -0
  13. package/dist/components/SelectField.d.ts +57 -0
  14. package/dist/components/SelectField.js +236 -0
  15. package/dist/components/SettingsSection.css +2 -2
  16. package/dist/components/StateDot.d.ts +5 -0
  17. package/dist/components/StateDot.js +10 -0
  18. package/dist/components/TextField.css +5 -0
  19. package/dist/components/icons.d.ts +1 -0
  20. package/dist/components/icons.js +13 -0
  21. package/dist/host/__tests__/fakes.d.ts +41 -0
  22. package/dist/host/__tests__/fakes.js +76 -0
  23. package/dist/host/__tests__/preflight-fixtures.d.ts +19 -0
  24. package/dist/host/__tests__/preflight-fixtures.js +47 -0
  25. package/dist/host/context-inject.d.ts +24 -0
  26. package/dist/host/context-inject.js +74 -0
  27. package/dist/host/events.d.ts +9 -0
  28. package/dist/host/events.js +70 -0
  29. package/dist/host/mount-units.d.ts +52 -0
  30. package/dist/host/mount-units.js +186 -0
  31. package/dist/host/preflight.d.ts +58 -0
  32. package/dist/host/preflight.js +233 -0
  33. package/dist/host/probe-cache.d.ts +6 -0
  34. package/dist/host/probe-cache.js +19 -0
  35. package/dist/host/probe-libs.d.ts +45 -0
  36. package/dist/host/probe-libs.js +61 -0
  37. package/dist/host/probe-units.d.ts +17 -0
  38. package/dist/host/probe-units.js +102 -0
  39. package/dist/host/probe-verdict.d.ts +7 -0
  40. package/dist/host/probe-verdict.js +143 -0
  41. package/dist/host/tool-approval.d.ts +2 -0
  42. package/dist/host/tool-approval.js +1 -1
  43. package/dist/host/tool-kit.js +2 -2
  44. package/dist/host/tool-scope.d.ts +32 -0
  45. package/dist/host/tool-scope.js +84 -0
  46. package/dist/host/unit-readiness.d.ts +7 -0
  47. package/dist/host/unit-readiness.js +5 -0
  48. package/dist/host/unit-registry.d.ts +9 -0
  49. package/dist/host/unit-registry.js +33 -0
  50. package/dist/host/version-range.d.ts +2 -0
  51. package/dist/host/version-range.js +73 -0
  52. package/dist/host.d.ts +1 -0
  53. package/dist/host.js +1 -0
  54. package/dist/i18n/en.js +1 -1
  55. package/dist/i18n/index.d.ts +1 -1
  56. package/dist/i18n/zh.d.ts +1 -1
  57. package/dist/i18n/zh.js +1 -1
  58. package/dist/index.d.ts +2 -0
  59. package/dist/index.js +2 -0
  60. package/dist/lib/react.d.ts +2 -0
  61. package/dist/lib/react.js +2 -0
  62. package/package.json +1 -1
  63. package/src/api/dsh-client.ts +5 -1
  64. package/src/components/Activity.css +1 -0
  65. package/src/components/Card.css +4 -4
  66. package/src/components/Card.test.tsx +46 -0
  67. package/src/components/CardAnchor.module.css +3 -0
  68. package/src/components/CardAnchor.test.tsx +36 -0
  69. package/src/components/CardAnchor.tsx +20 -0
  70. package/src/components/CardHead.css +1 -5
  71. package/src/components/ChoiceGroup.css +11 -2
  72. package/src/components/Field.css +4 -0
  73. package/src/components/MockStrip.css +6 -0
  74. package/src/components/Note.css +4 -4
  75. package/src/components/SelectField.css +156 -0
  76. package/src/components/SelectField.test.tsx +238 -0
  77. package/src/components/SelectField.tsx +486 -0
  78. package/src/components/SettingsSection.css +2 -2
  79. package/src/components/TextField.css +5 -0
  80. package/src/components/icons.tsx +19 -0
  81. package/src/host/tool-approval.ts +2 -1
  82. package/src/host/tool-kit.ts +2 -2
  83. package/src/host.ts +1 -0
  84. package/src/i18n/en.ts +1 -1
  85. package/src/i18n/zh.ts +1 -1
  86. package/src/index.ts +2 -0
  87. package/src/lib/react.ts +3 -0
  88. package/dist/lib/lint-probe.d.ts +0 -1
  89. package/dist/lib/lint-probe.js +0 -6
@@ -0,0 +1,486 @@
1
+ import type { KeyboardEvent as ReactKeyboardEvent, ReactNode, RefObject } from 'react';
2
+ import type { MenuEntry, MenuItem } from '@deepseek-ai/dsh-client-ui-primitives';
3
+ import { h, useEffect, useMemo, useRef, useState } from '../lib/react.js';
4
+ import {
5
+ IconChevronDownOutlineRegular,
6
+ IconSearchOutlineRegular,
7
+ Menu,
8
+ StateDot,
9
+ TextShimmer,
10
+ } from '@deepseek-ai/dsh-client-ui-primitives';
11
+ import { Field } from './Field.js';
12
+ import { TextField } from './TextField.js';
13
+ import './SelectField.css';
14
+
15
+ export interface SelectFieldOption {
16
+ readonly value: string;
17
+ readonly label: ReactNode;
18
+ readonly searchText?: string | undefined;
19
+ readonly icon?: ReactNode | undefined;
20
+ readonly group?: string | undefined;
21
+ readonly disabled?: boolean | undefined;
22
+ }
23
+
24
+ export interface SelectFieldFallback {
25
+ readonly value: string;
26
+ readonly onChange: (next: string) => void;
27
+ }
28
+
29
+ export interface SelectFieldFooter {
30
+ readonly label: ReactNode;
31
+ readonly icon?: ReactNode | undefined;
32
+ }
33
+
34
+ export interface SelectFieldProps {
35
+ readonly value: string;
36
+ readonly options: readonly SelectFieldOption[];
37
+ readonly onChange: (value: string) => void;
38
+ readonly label?: ReactNode | undefined;
39
+ readonly hint?: ReactNode | undefined;
40
+ readonly icon?: ReactNode | undefined;
41
+ readonly renderValue?: ((current: SelectFieldOption | undefined) => ReactNode) | undefined;
42
+ readonly placeholder?: ReactNode | undefined;
43
+ readonly search?: boolean | undefined;
44
+ readonly searchLabel?: string | undefined;
45
+ readonly searchPlaceholder?: string | undefined;
46
+ readonly filter?: ((option: SelectFieldOption, query: string) => boolean) | undefined;
47
+ readonly emptyText?: string | ((query: string) => string) | undefined;
48
+ readonly open?: boolean | undefined;
49
+ readonly onOpenChange?: ((open: boolean) => void) | undefined;
50
+ readonly disabled?: boolean | undefined;
51
+ readonly loading?: boolean | undefined;
52
+ readonly loadingText?: string | undefined;
53
+ readonly fallback?: SelectFieldFallback | undefined;
54
+ readonly footer?: SelectFieldFooter | undefined;
55
+ readonly onFooter?: (() => void) | undefined;
56
+ readonly selection?: 'check' | 'fill' | undefined;
57
+ readonly variant?: 'ghost' | 'boxed' | undefined;
58
+ readonly ariaLabel?: string | undefined;
59
+ readonly title?: string | undefined;
60
+ readonly id?: string | undefined;
61
+ readonly name?: string | undefined;
62
+ readonly className?: string | undefined;
63
+ readonly menuClassName?: string | undefined;
64
+ }
65
+
66
+ const FOOTER_ID = '__wb-select-footer__';
67
+ const EMPTY_ID = '__wb-select-empty__';
68
+
69
+ function mergeClass(...parts: readonly (string | undefined)[]): string {
70
+ return parts.filter((one) => one !== undefined && one !== '').join(' ');
71
+ }
72
+
73
+ function itemOf(option: SelectFieldOption): MenuItem {
74
+ return {
75
+ id: option.value,
76
+ label: option.label,
77
+ ...(option.icon === undefined ? {} : { icon: option.icon }),
78
+ ...(option.disabled === true ? { disabled: true } : {}),
79
+ };
80
+ }
81
+
82
+ function triggerNode(args: {
83
+ readonly id: string | undefined;
84
+ readonly className: string;
85
+ readonly disabled: boolean;
86
+ readonly open: boolean;
87
+ readonly ariaLabel: string | undefined;
88
+ readonly title: string | undefined;
89
+ readonly icon: ReactNode | undefined;
90
+ readonly value: ReactNode;
91
+ readonly onOpen: () => void;
92
+ }): ReactNode {
93
+ return (
94
+ <button
95
+ type="button"
96
+ id={args.id}
97
+ className={args.className}
98
+ disabled={args.disabled}
99
+ aria-haspopup="listbox"
100
+ aria-expanded={args.open}
101
+ aria-label={args.ariaLabel}
102
+ title={args.title}
103
+ onClick={args.onOpen}
104
+ >
105
+ {args.icon === undefined ? null : <span className="wb-selectIcon">{args.icon}</span>}
106
+ <span className="wb-selectValue">{args.value}</span>
107
+ <span className="wb-selectChevron">
108
+ <IconChevronDownOutlineRegular size={14} />
109
+ </span>
110
+ </button>
111
+ );
112
+ }
113
+
114
+ function loadingNode(args: {
115
+ readonly id: string | undefined;
116
+ readonly className: string;
117
+ readonly ariaLabel: string | undefined;
118
+ readonly loadingText: string | undefined;
119
+ }): ReactNode {
120
+ return (
121
+ <button
122
+ type="button"
123
+ id={args.id}
124
+ className={`${args.className} wb-selectLoading`}
125
+ disabled
126
+ aria-busy="true"
127
+ aria-label={args.ariaLabel}
128
+ title={args.loadingText}
129
+ >
130
+ <span className="wb-selectValue">
131
+ <TextShimmer active>{args.loadingText ?? '…'}</TextShimmer>
132
+ </span>
133
+ <span className="wb-selectChevron">
134
+ <StateDot state="ongoing" size={14} />
135
+ </span>
136
+ </button>
137
+ );
138
+ }
139
+
140
+ function searchNode(args: {
141
+ readonly boxRef: RefObject<HTMLSpanElement | null>;
142
+ readonly className: string;
143
+ readonly id: string | undefined;
144
+ readonly name: string | undefined;
145
+ readonly ariaLabel: string | undefined;
146
+ readonly placeholder: string | undefined;
147
+ readonly query: string;
148
+ readonly onQuery: (next: string) => void;
149
+ readonly onKey: (event: ReactKeyboardEvent<HTMLInputElement>) => void;
150
+ }): ReactNode {
151
+ return (
152
+ <span ref={args.boxRef} className={args.className}>
153
+ <span className="wb-selectSearchIcon">
154
+ <IconSearchOutlineRegular size={14} />
155
+ </span>
156
+ <TextField
157
+ id={args.id}
158
+ name={args.name}
159
+ className="wb-selectSearchInput"
160
+ aria-label={args.ariaLabel}
161
+ placeholder={args.placeholder}
162
+ value={args.query}
163
+ onChange={(event) => args.onQuery(event.currentTarget.value)}
164
+ onKeyDown={(event) => {
165
+ if (event.nativeEvent.isComposing) return;
166
+ args.onKey(event);
167
+ }}
168
+ />
169
+ </span>
170
+ );
171
+ }
172
+
173
+ function menuNode(args: {
174
+ readonly open: boolean;
175
+ readonly listClassName: string;
176
+ readonly anchor: ReactNode;
177
+ readonly selection: 'check' | 'fill';
178
+ readonly selectedId: string | undefined;
179
+ readonly items: readonly MenuEntry[];
180
+ readonly footer: readonly MenuEntry[] | undefined;
181
+ readonly onPick: (id: string) => void;
182
+ readonly onCancel: () => void;
183
+ }): ReactNode {
184
+ return (
185
+ <Menu
186
+ open={args.open}
187
+ portal
188
+ className="wb-selectAnchor"
189
+ listClassName={args.listClassName}
190
+ anchor={args.anchor}
191
+ align="start"
192
+ side="bottom"
193
+ selection={args.selection}
194
+ selectedId={args.selectedId}
195
+ items={args.items}
196
+ footer={args.footer}
197
+ onSelect={args.onPick}
198
+ onClose={args.onCancel}
199
+ />
200
+ );
201
+ }
202
+
203
+ function framed(
204
+ label: ReactNode | undefined,
205
+ hint: ReactNode | undefined,
206
+ className: string | undefined,
207
+ control: ReactNode,
208
+ ): ReactNode {
209
+ const inner =
210
+ label === undefined
211
+ ? hint === undefined
212
+ ? control
213
+ : (
214
+ <span className="wb-select">
215
+ {control}
216
+ <span className="wb-selectHint">{hint}</span>
217
+ </span>
218
+ )
219
+ : (
220
+ <Field label={label} hint={hint}>
221
+ {control}
222
+ </Field>
223
+ );
224
+ return className === undefined ? inner : <div className={className}>{inner}</div>;
225
+ }
226
+
227
+ export function searchTextOf(option: SelectFieldOption): string {
228
+ if (option.searchText !== undefined) return option.searchText;
229
+ return typeof option.label === 'string' ? option.label : '';
230
+ }
231
+
232
+ export function defaultSelectFilter(option: SelectFieldOption, query: string): boolean {
233
+ const needle = query.trim().toLowerCase();
234
+ if (needle === '') return true;
235
+ return searchTextOf(option).toLowerCase().includes(needle);
236
+ }
237
+
238
+ export function filterSelectOptions(
239
+ options: readonly SelectFieldOption[],
240
+ query: string,
241
+ filter: (option: SelectFieldOption, query: string) => boolean = defaultSelectFilter,
242
+ ): readonly SelectFieldOption[] {
243
+ return options.filter((option) => filter(option, query));
244
+ }
245
+
246
+ export function selectCursorValue(
247
+ shown: readonly SelectFieldOption[],
248
+ cursor: number | null,
249
+ ): string | null {
250
+ if (cursor === null || shown.length === 0) return null;
251
+ return shown[Math.min(cursor, shown.length - 1)]?.value ?? null;
252
+ }
253
+
254
+ export function stepSelectCursor(
255
+ shown: readonly SelectFieldOption[],
256
+ cursor: number | null,
257
+ step: 1 | -1,
258
+ ): number | null {
259
+ if (shown.length === 0) return null;
260
+ if (cursor === null) return step === 1 ? 0 : shown.length - 1;
261
+ return (cursor + step + shown.length) % shown.length;
262
+ }
263
+
264
+ export function selectEntries(
265
+ shown: readonly SelectFieldOption[],
266
+ emptyText: string | undefined,
267
+ ): readonly MenuEntry[] {
268
+ if (shown.length === 0) {
269
+ return emptyText === undefined ? [] : [{ type: 'label', id: EMPTY_ID, text: emptyText }];
270
+ }
271
+ const out: MenuEntry[] = [];
272
+ let group: string | null = null;
273
+ for (const option of shown) {
274
+ if (option.group !== undefined && option.group !== group) {
275
+ out.push({ type: 'label', id: `__wb-select-group-${String(out.length)}`, text: option.group });
276
+ group = option.group;
277
+ }
278
+ out.push(itemOf(option));
279
+ }
280
+ return out;
281
+ }
282
+
283
+ function useSelectState(props: SelectFieldProps) {
284
+ const { options, filter = defaultSelectFilter, emptyText, open: openProp, onOpenChange } = props;
285
+ const [openState, setOpenState] = useState(false);
286
+ const [query, setQuery] = useState('');
287
+ const [cursor, setCursor] = useState<number | null>(null);
288
+ const boxRef = useRef<HTMLSpanElement | null>(null);
289
+ const triggerRef = useRef<HTMLButtonElement | null>(null);
290
+ const wasOpenRef = useRef(false);
291
+ const pickedRef = useRef(false);
292
+ const closedByUsRef = useRef(false);
293
+
294
+ const open = openProp ?? openState;
295
+ const setOpen = (next: boolean): void => {
296
+ if (openProp === undefined) setOpenState(next);
297
+ onOpenChange?.(next);
298
+ };
299
+
300
+ useEffect(() => {
301
+ if (!open || props.search === false) return;
302
+ boxRef.current?.querySelector('input')?.focus();
303
+ }, [open, props.search]);
304
+
305
+ useEffect(() => {
306
+ if (open) {
307
+ wasOpenRef.current = true;
308
+ return;
309
+ }
310
+ if (!wasOpenRef.current) return;
311
+ wasOpenRef.current = false;
312
+ const closedByUs = closedByUsRef.current;
313
+ const picked = pickedRef.current;
314
+ closedByUsRef.current = false;
315
+ pickedRef.current = false;
316
+ if (!closedByUs || picked) return;
317
+ triggerRef.current?.focus();
318
+ }, [open]);
319
+
320
+ const shown = useMemo(() => filterSelectOptions(options, query, filter), [options, query, filter]);
321
+ const close = (picked: boolean): void => {
322
+ closedByUsRef.current = true;
323
+ pickedRef.current = picked;
324
+ setOpen(false);
325
+ setQuery('');
326
+ setCursor(null);
327
+ };
328
+
329
+ return {
330
+ open,
331
+ setOpen,
332
+ query,
333
+ cursor,
334
+ setCursor,
335
+ setQuery,
336
+ shown,
337
+ close,
338
+ boxRef,
339
+ triggerRef,
340
+ empty: typeof emptyText === 'function' ? emptyText(query) : emptyText,
341
+ };
342
+ }
343
+
344
+ export function SelectField(props: SelectFieldProps) {
345
+ const {
346
+ value,
347
+ options,
348
+ onChange,
349
+ label,
350
+ hint,
351
+ icon,
352
+ renderValue,
353
+ placeholder,
354
+ search = true,
355
+ searchLabel,
356
+ searchPlaceholder,
357
+ footer,
358
+ onFooter,
359
+ disabled = false,
360
+ loading = false,
361
+ loadingText,
362
+ fallback,
363
+ selection = 'fill',
364
+ variant = 'boxed',
365
+ ariaLabel,
366
+ title,
367
+ id,
368
+ name,
369
+ className,
370
+ menuClassName,
371
+ } = props;
372
+ const state = useSelectState(props);
373
+ const current = options.find((option) => option.value === value);
374
+ const variantClass = variant === 'ghost' ? 'wb-selectGhost' : undefined;
375
+ const triggerClass = mergeClass('wb-selectTrigger', variantClass);
376
+
377
+ if (loading) {
378
+ return framed(
379
+ label,
380
+ hint,
381
+ className,
382
+ loadingNode({ id, className: triggerClass, ariaLabel, loadingText }),
383
+ );
384
+ }
385
+
386
+ if (fallback !== undefined && options.length === 0) {
387
+ return framed(
388
+ label,
389
+ hint,
390
+ className,
391
+ <TextField
392
+ id={id}
393
+ name={name}
394
+ className="wb-selectFallback"
395
+ aria-label={ariaLabel}
396
+ value={fallback.value}
397
+ onChange={(event) => fallback.onChange(event.currentTarget.value)}
398
+ />,
399
+ );
400
+ }
401
+
402
+ const trigger = triggerNode({
403
+ id,
404
+ className: triggerClass,
405
+ disabled,
406
+ open: state.open,
407
+ ariaLabel,
408
+ title,
409
+ icon,
410
+ value:
411
+ current === undefined ? (
412
+ <span className="wb-selectPlaceholder">{placeholder ?? '—'}</span>
413
+ ) : renderValue === undefined ? (
414
+ current.label
415
+ ) : (
416
+ renderValue(current)
417
+ ),
418
+ onOpen: () => state.setOpen(true),
419
+ });
420
+
421
+ const anchor =
422
+ state.open && search
423
+ ? searchNode({
424
+ boxRef: state.boxRef,
425
+ className: mergeClass('wb-selectSearch', variantClass),
426
+ id,
427
+ name,
428
+ ariaLabel: searchLabel,
429
+ placeholder: searchPlaceholder,
430
+ query: state.query,
431
+ onQuery: (next) => {
432
+ state.setQuery(next);
433
+ state.setCursor(null);
434
+ },
435
+ onKey: (event) => {
436
+ if (event.key === 'ArrowDown' || event.key === 'ArrowUp') {
437
+ event.stopPropagation();
438
+ event.preventDefault();
439
+ state.setCursor((at) =>
440
+ stepSelectCursor(state.shown, at, event.key === 'ArrowDown' ? 1 : -1),
441
+ );
442
+ return;
443
+ }
444
+ if (event.key !== 'Enter') return;
445
+ event.stopPropagation();
446
+ const pick = state.shown[Math.min(state.cursor ?? 0, state.shown.length - 1)];
447
+ if (pick === undefined) return;
448
+ state.close(true);
449
+ onChange(pick.value);
450
+ },
451
+ })
452
+ : trigger;
453
+
454
+ return framed(
455
+ label,
456
+ hint,
457
+ className,
458
+ menuNode({
459
+ open: state.open,
460
+ listClassName: mergeClass('wb-selectMenu', menuClassName),
461
+ anchor,
462
+ selection,
463
+ selectedId: selectCursorValue(state.shown, state.cursor) ?? undefined,
464
+ items: selectEntries(state.shown, state.empty),
465
+ footer:
466
+ footer === undefined
467
+ ? undefined
468
+ : [
469
+ {
470
+ id: FOOTER_ID,
471
+ label: footer.label,
472
+ ...(footer.icon === undefined ? {} : { icon: footer.icon }),
473
+ },
474
+ ],
475
+ onPick: (picked) => {
476
+ state.close(true);
477
+ if (picked === FOOTER_ID) {
478
+ onFooter?.();
479
+ return;
480
+ }
481
+ onChange(picked);
482
+ },
483
+ onCancel: () => state.close(false),
484
+ }),
485
+ );
486
+ }
@@ -3,7 +3,7 @@ section.wb-settings {
3
3
  display: flex;
4
4
  flex-direction: column;
5
5
  gap: var(--wb-len-12);
6
- padding: 0;
6
+ padding: var(--wb-len-12) var(--wb-len-16);
7
7
  border: 0;
8
8
  border-radius: var(--dsw-radius-md);
9
9
  background: var(--dsw-alias-settings-card-fill);
@@ -19,7 +19,7 @@ section.wb-settings {
19
19
  overflow: hidden;
20
20
  text-overflow: ellipsis;
21
21
  white-space: nowrap;
22
- font-size: var(--dsw-font-base-16-font-size);
22
+ font-size: var(--dsw-font-s-strong-14-font-size);
23
23
  line-height: var(--wb-line-24);
24
24
  font-weight: 500;
25
25
  color: var(--dsw-alias-label-primary);
@@ -13,6 +13,11 @@
13
13
  box-shadow: inset 0 0 0 0.5px var(--dsw-alias-state-business-primary);
14
14
  }
15
15
 
16
+ .wb-textField:disabled {
17
+ cursor: not-allowed;
18
+ opacity: 0.4;
19
+ }
20
+
16
21
  .wb-textField[aria-invalid='true'] {
17
22
  border-color: var(--dsw-alias-state-error-primary);
18
23
  }
@@ -105,6 +105,25 @@ export function IconModules({ size = 16 }: IconProps) {
105
105
  );
106
106
  }
107
107
 
108
+ export function IconModuleMark({ size = 16 }: IconProps) {
109
+ return (
110
+ <Shell size={size} viewBox="0 0 24 24">
111
+ <defs>
112
+ <linearGradient id="workbench-module-mark" x1="0%" y1="0%" x2="100%" y2="100%">
113
+ <stop offset="0%" stopColor="#3b82f6" />
114
+ <stop offset="55%" stopColor="#8b5cf6" />
115
+ <stop offset="100%" stopColor="#14b8a6" />
116
+ </linearGradient>
117
+ </defs>
118
+ <rect width={24} height={24} rx={7} fill="url(#workbench-module-mark)" />
119
+ <rect x="4.6" y="4.6" width="6.2" height="6.2" rx="1.9" fill="#ffffff" />
120
+ <rect x="13.2" y="4.6" width="6.2" height="6.2" rx="1.9" fill="#ffffff" fillOpacity={0.78} />
121
+ <rect x="4.6" y="13.2" width="6.2" height="6.2" rx="1.9" fill="#ffffff" fillOpacity={0.78} />
122
+ <rect x="13.2" y="13.2" width="6.2" height="6.2" rx="1.9" fill="#ffffff" />
123
+ </Shell>
124
+ );
125
+ }
126
+
108
127
  export function IconPanelScreen({ size = 16 }: IconProps) {
109
128
  return (
110
129
  <svg
@@ -63,6 +63,7 @@ export interface ApprovalCall {
63
63
 
64
64
  export type ApprovalVerdict =
65
65
  | { readonly kind: 'read' }
66
+ | { readonly kind: 'allow' }
66
67
  | {
67
68
  readonly kind: 'ask';
68
69
  readonly reason: string;
@@ -116,7 +117,7 @@ export async function approvalFor(
116
117
  };
117
118
  }
118
119
 
119
- if (verdict?.kind === 'read') return undefined;
120
+ if (verdict?.kind === 'read' || verdict?.kind === 'allow') return undefined;
120
121
 
121
122
  if (verdict?.kind !== 'ask') {
122
123
  return {
@@ -39,12 +39,12 @@ export function commandHintFor(names: CommandHintNames): CommandHint {
39
39
 
40
40
  const advice =
41
41
  `这条命令(${command})在这份 ${names.plugin} 插件里可能不存在(插件版本旧就会缺):` +
42
- `用 workbench_modules(action=update, id=${names.suiteId})升级插件,或在面板的模块状态里点更新,再重试。`;
42
+ `用 workbench_modules_update(id=${names.suiteId})升级插件,或在面板的模块状态里点更新,再重试。`;
43
43
 
44
44
  throw new WorkbenchDataError(error.code, `${error.message}。${advice}`, {
45
45
  ...(error.detail === undefined ? {} : { detail: error.detail }),
46
46
  hint:
47
- `升级:workbench_modules(action=update, id=${names.suiteId})。` +
47
+ `升级:workbench_modules_update(id=${names.suiteId})。` +
48
48
  `终端确认:opencli ${names.plugin} ${command} --help${
49
49
  error.hint === undefined ? '' : `\n${error.hint}`
50
50
  }`,
package/src/host.ts CHANGED
@@ -18,6 +18,7 @@ export * from './host/http.js';
18
18
  export * from './host/long-task.js';
19
19
  export * from './host/opencli-runtime.js';
20
20
  export * from './host/opencli.js';
21
+ export * from './host/opencli-window.js';
21
22
  export * from './host/plugin-ledger.js';
22
23
  export * from './host/plugin-source.js';
23
24
  export * from './host/provision.js';
package/src/i18n/en.ts CHANGED
@@ -144,7 +144,7 @@ export const en: Record<keyof WorkbenchMessages, string> = {
144
144
  'panels.market.pluginKeptUnsure': 'Files kept for now: some modules on this machine do not say what they use, so it is unclear who needs it.',
145
145
  'panels.market.pluginKeptHeld': 'Files kept: this one is yours, and a module letting go will not remove it.',
146
146
  'panels.market.pluginKeptUnnamed': 'Files kept for now: this one cannot be identified as a plugin, so it is unclear who needs it.',
147
- 'panels.market.pluginBroken': 'This install is broken: it cannot be identified as a plugin. Uninstall it, or tell the agent to reinstall it by package name with workbench_modules (the install-bundle action).',
147
+ 'panels.market.pluginBroken': 'This install is broken: it cannot be identified as a plugin. Uninstall it, or tell the agent to reinstall it by package name with workbench_modules_install_bundle.',
148
148
  'panels.market.restartNeeded': 'The new version is installed — restart once to pick it up.',
149
149
  'panels.market.foot': 'After installing, enable it in the panel page of that project.',
150
150
  'panels.market.recheck': 'Check for updates',
package/src/i18n/zh.ts CHANGED
@@ -140,7 +140,7 @@ export const zh = {
140
140
  'panels.market.pluginKeptUnsure': '文件先留着:这台机器上还有模块看不出用了什么,说不准谁在用它。',
141
141
  'panels.market.pluginKeptHeld': '文件留着:你自己装的那一份,模块放手不动它。',
142
142
  'panels.market.pluginKeptUnnamed': '文件先留着:看不出这是哪个插件,说不准谁在用它。',
143
- 'panels.market.pluginBroken': '这份装坏了:看不出它是个插件。可以卸载,或者直接跟 agent 说:用 workbench_modules 的 install-bundle 按包名再装一次。',
143
+ 'panels.market.pluginBroken': '这份装坏了:看不出它是个插件。可以卸载,或者直接跟 agent 说:用 workbench_modules_install_bundle 按包名再装一次。',
144
144
  'panels.market.restartNeeded': '新版装好了,重启一次才生效。',
145
145
  'panels.market.foot': '装好后到那个项目的面板页勾选它,模块才会出现。',
146
146
  'panels.market.recheck': '重新检查更新',
package/src/index.ts CHANGED
@@ -15,6 +15,7 @@ export * from './components/Activity.js';
15
15
  export * from './components/ApprovalDetail.js';
16
16
  export * from './components/ApprovalPlan.js';
17
17
  export * from './components/Card.js';
18
+ export * from './components/CardAnchor.js';
18
19
  export * from './components/CardBoundary.js';
19
20
  export * from './components/CardHead.js';
20
21
  export * from './components/ChoiceGroup.js';
@@ -25,6 +26,7 @@ export * from './components/ResourceStates.js';
25
26
  export * from './components/Rows.js';
26
27
  export * from './components/SaveBar.js';
27
28
  export * from './components/SettingsSection.js';
29
+ export * from './components/SelectField.js';
28
30
  export * from './components/Stack.js';
29
31
  export * from './components/StateBlock.js';
30
32
  export * from './components/TextField.js';
package/src/lib/react.ts CHANGED
@@ -8,6 +8,9 @@ export const Fragment = react.Fragment;
8
8
 
9
9
  export const Component = react.Component;
10
10
 
11
+ export const createContext = react.createContext;
12
+ export const useContext = react.useContext;
13
+
11
14
  export const useState = react.useState;
12
15
  export const useEffect = react.useEffect;
13
16
  export const useLayoutEffect = react.useLayoutEffect;
@@ -1 +0,0 @@
1
- export declare function useLintProbe(): number;
@@ -1,6 +0,0 @@
1
- import { useRef } from './react';
2
- export function useLintProbe() {
3
- const probeRef = useRef(0);
4
- probeRef.current = 1;
5
- return probeRef.current;
6
- }