@abstractframework/ui-kit 0.1.2 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,7 @@ This package provides:
6
6
 
7
7
  - **Theme tokens** (CSS variables + theme classes): `ui-kit/src/theme.css`
8
8
  - **Theme + typography helpers**: `applyTheme(...)`, `applyTypography(...)`
9
- - **Common inputs**: `AfSelect`, `ThemeSelect`, `ProviderModelSelect`, etc.
9
+ - **Common inputs**: `AfSelect`, `ThemeSelect`, `ProviderModelSelect`, `ToolPolicyEditor`, etc.
10
10
  - **Icons**: `Icon` (used by `@abstractframework/panel-chat`)
11
11
 
12
12
  ## Install / peer dependencies
@@ -16,7 +16,7 @@ This is a React package with peer dependencies on `react@^18` and `react-dom@^18
16
16
  ## Install
17
17
 
18
18
  - Workspace: add a dependency on `@abstractframework/ui-kit`
19
- - npm (once published): `npm i @abstractframework/ui-kit`
19
+ - npm: `npm i @abstractframework/ui-kit`
20
20
 
21
21
  ## Usage
22
22
 
@@ -37,9 +37,15 @@ applyTheme("dark"); // sets a `theme-*` class on <html>
37
37
  Use UI components:
38
38
 
39
39
  ```tsx
40
- import { ThemeSelect, Icon } from "@abstractframework/ui-kit";
40
+ import { ThemeSelect, Icon, ToolPolicyEditor } from "@abstractframework/ui-kit";
41
41
  ```
42
42
 
43
+ ### Tool policy editor
44
+
45
+ `ToolPolicyEditor` renders the shared allowlist + approve/ask picker for gateway tools. It intentionally **does not** include a deny mode; tools are denied by removing them from the allowlist. Pass `toolMode` (and optional `toolModeLabel`/`toolModeDetail`) to surface the gateway tool execution mode in a prominent banner.
46
+
47
+ The default approve/ask classification is exposed as `TOOL_POLICY_DEFAULTS` (mirrors the AbstractRuntime `ToolApprovalPolicy` defaults).
48
+
43
49
  ## Exported API
44
50
 
45
51
  See `ui-kit/src/index.ts` for the authoritative export list.
@@ -3,6 +3,8 @@ export type AfSelectOption = {
3
3
  value: string;
4
4
  label: string;
5
5
  group?: string;
6
+ disabled?: boolean;
7
+ reason?: string;
6
8
  };
7
9
  export type AfSelectProps = {
8
10
  value: string;
@@ -18,6 +20,10 @@ export type AfSelectProps = {
18
20
  variant?: "pin" | "panel";
19
21
  className?: string;
20
22
  triggerClassName?: string;
23
+ customOptionLabel?: (value: string) => string;
24
+ validateCustomValue?: (value: string, context: {
25
+ options: AfSelectOption[];
26
+ }) => string | null | undefined;
21
27
  onChange: (value: string) => void;
22
28
  renderOption?: (opt: AfSelectOption, state: {
23
29
  selected: boolean;
@@ -25,6 +31,6 @@ export type AfSelectProps = {
25
31
  }) => React.ReactNode;
26
32
  renderValue?: (opt: AfSelectOption | null, value: string) => React.ReactNode;
27
33
  };
28
- export declare function AfSelect({ value, options, placeholder, disabled, loading, searchable, searchPlaceholder, allowCustom, clearable, minPopoverWidth, variant, className, triggerClassName, onChange, renderOption, renderValue, }: AfSelectProps): React.ReactElement;
34
+ export declare function AfSelect({ value, options, placeholder, disabled, loading, searchable, searchPlaceholder, allowCustom, clearable, minPopoverWidth, variant, className, triggerClassName, customOptionLabel, validateCustomValue, onChange, renderOption, renderValue, }: AfSelectProps): React.ReactElement;
29
35
  export default AfSelect;
30
36
  //# sourceMappingURL=af_select.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"af_select.d.ts","sourceRoot":"","sources":["../src/af_select.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAOjF,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,OAAO,CAAA;KAAE,KAAK,KAAK,CAAC,SAAS,CAAC;IAC5G,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CAC9E,CAAC;AAEF,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,OAAO,EACP,WAAuB,EACvB,QAAgB,EAChB,OAAe,EACf,UAAiB,EACjB,iBAA6B,EAC7B,WAAmB,EACnB,SAAiB,EACjB,eAAqB,EACrB,OAAiB,EACjB,SAAS,EACT,gBAAgB,EAChB,QAAQ,EACR,YAAY,EACZ,WAAW,GACZ,EAAE,aAAa,GAAG,KAAK,CAAC,YAAY,CAmQpC;AAED,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"af_select.d.ts","sourceRoot":"","sources":["../src/af_select.tsx"],"names":[],"mappings":"AAAA,OAAO,KAA4D,MAAM,OAAO,CAAC;AAOjF,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,cAAc,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAC9C,mBAAmB,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE;QAAE,OAAO,EAAE,cAAc,EAAE,CAAA;KAAE,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAC3G,QAAQ,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAElC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE;QAAE,QAAQ,EAAE,OAAO,CAAC;QAAC,WAAW,EAAE,OAAO,CAAA;KAAE,KAAK,KAAK,CAAC,SAAS,CAAC;IAC5G,WAAW,CAAC,EAAE,CAAC,GAAG,EAAE,cAAc,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,KAAK,KAAK,CAAC,SAAS,CAAC;CAC9E,CAAC;AAEF,wBAAgB,QAAQ,CAAC,EACvB,KAAK,EACL,OAAO,EACP,WAAuB,EACvB,QAAgB,EAChB,OAAe,EACf,UAAiB,EACjB,iBAA6B,EAC7B,WAAmB,EACnB,SAAiB,EACjB,eAAqB,EACrB,OAAiB,EACjB,SAAS,EACT,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,QAAQ,EACR,YAAY,EACZ,WAAW,GACZ,EAAE,aAAa,GAAG,KAAK,CAAC,YAAY,CA4RpC;AAED,eAAe,QAAQ,CAAC"}
package/dist/af_select.js CHANGED
@@ -4,7 +4,7 @@ import { createPortal } from "react-dom";
4
4
  function cx(...parts) {
5
5
  return parts.filter(Boolean).join(" ");
6
6
  }
7
- export function AfSelect({ value, options, placeholder = "Select…", disabled = false, loading = false, searchable = true, searchPlaceholder = "Search…", allowCustom = false, clearable = false, minPopoverWidth = 240, variant = "panel", className, triggerClassName, onChange, renderOption, renderValue, }) {
7
+ export function AfSelect({ value, options, placeholder = "Select…", disabled = false, loading = false, searchable = true, searchPlaceholder = "Search…", allowCustom = false, clearable = false, minPopoverWidth = 240, variant = "panel", className, triggerClassName, customOptionLabel, validateCustomValue, onChange, renderOption, renderValue, }) {
8
8
  const triggerRef = useRef(null);
9
9
  const popoverRef = useRef(null);
10
10
  const searchRef = useRef(null);
@@ -35,8 +35,14 @@ export function AfSelect({ value, options, placeholder = "Select…", disabled =
35
35
  const exists = options.some((o) => o.value === q);
36
36
  if (exists)
37
37
  return null;
38
- return { value: q, label: `Use "${q}"` };
39
- }, [allowCustom, options, search]);
38
+ const reason = validateCustomValue?.(q, { options }) || "";
39
+ return {
40
+ value: q,
41
+ label: customOptionLabel ? customOptionLabel(q) : `Use "${q}"`,
42
+ disabled: Boolean(reason),
43
+ reason,
44
+ };
45
+ }, [allowCustom, customOptionLabel, options, search, validateCustomValue]);
40
46
  const visibleOptions = useMemo(() => {
41
47
  if (!customOption)
42
48
  return filtered;
@@ -98,6 +104,9 @@ export function AfSelect({ value, options, placeholder = "Select…", disabled =
98
104
  }
99
105
  }, [open, searchable, value, visibleOptions]);
100
106
  const pick = (v) => {
107
+ const option = visibleOptions.find((o) => o.value === v);
108
+ if (option?.disabled)
109
+ return;
101
110
  onChange(v);
102
111
  close();
103
112
  };
@@ -138,6 +147,7 @@ export function AfSelect({ value, options, placeholder = "Select…", disabled =
138
147
  };
139
148
  const showValue = Boolean(String(value || "").trim());
140
149
  const triggerText = showValue ? selectedLabel : placeholder;
150
+ const customError = customOption?.disabled ? customOption.reason || "Unavailable" : "";
141
151
  const defaultValueNode = (_jsx("span", { className: cx("af-select-value", !showValue && "af-select-value--placeholder"), children: loading ? "Loading…" : triggerText }));
142
152
  const valueNode = renderValue ? renderValue(selectedOpt, String(value || "")) : defaultValueNode;
143
153
  return (_jsxs("span", { className: cx("af-select", variant === "pin" ? "af-select--pin" : "af-select--panel", className), children: [_jsxs("button", { ref: triggerRef, type: "button", className: cx("af-select-trigger", triggerClassName), disabled: disabled, onMouseDown: (e) => e.stopPropagation(), onClick: (e) => {
@@ -154,16 +164,16 @@ export function AfSelect({ value, options, placeholder = "Select…", disabled =
154
164
  onChange("");
155
165
  close();
156
166
  }, title: "Clear", children: "\u00D7" })) : null, _jsx("span", { className: cx("af-select-caret", open && "af-select-caret--open"), children: "\u25BE" })] }), open
157
- ? createPortal(_jsxs("div", { ref: popoverRef, className: cx("af-select-popover", placement === "top" && "af-select-popover--top"), style: { position: "fixed", left: `${pos.left}px`, top: `${pos.top}px`, width: `${pos.width}px` }, onMouseDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation(), onKeyDown: onPopoverKeyDown, role: "listbox", tabIndex: -1, children: [searchable ? (_jsx("div", { className: "af-select-search", children: _jsx("input", { ref: searchRef, className: "af-select-search-input", value: search, onChange: (e) => setSearch(e.target.value), placeholder: searchPlaceholder, onKeyDown: onPopoverKeyDown }) })) : null, _jsx("div", { className: "af-select-options", children: visibleOptions.length === 0 ? (_jsx("div", { className: "af-select-empty", children: "No results" })) : (visibleOptions.map((o, i) => {
167
+ ? createPortal(_jsxs("div", { ref: popoverRef, className: cx("af-select-popover", placement === "top" && "af-select-popover--top"), style: { position: "fixed", left: `${pos.left}px`, top: `${pos.top}px`, width: `${pos.width}px` }, onMouseDown: (e) => e.stopPropagation(), onClick: (e) => e.stopPropagation(), onWheel: (e) => e.stopPropagation(), onKeyDown: onPopoverKeyDown, role: "listbox", tabIndex: -1, children: [searchable ? (_jsxs("div", { className: "af-select-search", children: [_jsx("input", { ref: searchRef, className: "af-select-search-input", value: search, onChange: (e) => setSearch(e.target.value), placeholder: searchPlaceholder, onKeyDown: onPopoverKeyDown, "aria-invalid": Boolean(customError), "aria-describedby": customError ? "af-select-custom-error" : undefined }), customError ? (_jsx("div", { id: "af-select-custom-error", className: "af-select-custom-error", role: "alert", children: customError })) : null] })) : null, _jsx("div", { className: "af-select-options", children: loading && visibleOptions.length === 0 ? (_jsx("div", { className: "af-select-empty", children: "Loading\u2026" })) : visibleOptions.length === 0 ? (_jsx("div", { className: "af-select-empty", children: "No results" })) : (visibleOptions.map((o, i) => {
158
168
  const isSelected = o.value === value;
159
169
  const isHighlighted = i === highlightIdx;
160
170
  const group = String(o.group || "").trim();
161
171
  const prevGroup = i > 0 ? String(visibleOptions[i - 1]?.group || "").trim() : "";
162
172
  const showGroup = Boolean(group) && group !== prevGroup;
163
- return (_jsxs(React.Fragment, { children: [showGroup ? _jsx("div", { className: "af-select-group", children: group }) : null, _jsx("div", { className: cx("af-select-option", isSelected && "af-select-option--selected", isHighlighted && "af-select-option--highlighted"), onMouseEnter: () => setHighlightIdx(i), onMouseDown: (e) => {
173
+ return (_jsxs(React.Fragment, { children: [showGroup ? _jsx("div", { className: "af-select-group", children: group }) : null, _jsx("div", { className: cx("af-select-option", isSelected && "af-select-option--selected", isHighlighted && "af-select-option--highlighted", o.disabled && "af-select-option--disabled"), title: o.disabled ? o.reason || "Unavailable" : undefined, onMouseEnter: () => setHighlightIdx(i), onMouseDown: (e) => {
164
174
  e.preventDefault();
165
175
  e.stopPropagation();
166
- }, onClick: () => pick(o.value), children: renderOption ? (renderOption(o, { selected: isSelected, highlighted: isHighlighted })) : (_jsxs(_Fragment, { children: [_jsx("span", { className: "af-select-option-label", children: o.label }), isSelected ? _jsx("span", { className: "af-select-check", children: "\u2713" }) : null] })) })] }, o.value));
176
+ }, onClick: () => pick(o.value), "aria-disabled": o.disabled ? true : undefined, children: renderOption ? (renderOption(o, { selected: isSelected, highlighted: isHighlighted })) : (_jsxs(_Fragment, { children: [_jsxs("span", { className: "af-select-option-label", children: [_jsx("span", { children: o.label }), o.reason ? _jsx("small", { className: "af-select-option-reason", children: o.reason }) : null] }), isSelected ? _jsx("span", { className: "af-select-check", children: "\u2713" }) : null] })) })] }, o.value));
167
177
  })) })] }), document.body)
168
178
  : null] }));
169
179
  }
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
- export { THEMES, THEME_SPECS, applyTheme, getThemeSpec, themeClassName, type ThemeOption, type ThemeSpec } from "./theme";
2
- export { FONT_SCALES, HEADER_DENSITIES, applyTypography, getFontScaleSpec, getHeaderDensitySpec, type FontScaleOption, type HeaderDensityOption } from "./typography";
3
- export { AfSelect, type AfSelectProps, type AfSelectOption } from "./af_select";
4
- export { ProviderModelSelect, type ProviderModelSelectProps, type ProviderOption } from "./provider_model_select";
5
- export { ThemeSelect, type ThemeSelectProps } from "./theme_select";
6
- export { FontScaleSelect, HeaderDensitySelect, type FontScaleSelectProps, type HeaderDensitySelectProps } from "./typography_select";
7
- export { Icon, type IconName } from "./icon";
1
+ export { THEMES, THEME_SPECS, applyTheme, getThemeSpec, themeClassName, type ThemeOption, type ThemeSpec } from "./theme.js";
2
+ export { FONT_SCALES, HEADER_DENSITIES, applyTypography, getFontScaleSpec, getHeaderDensitySpec, type FontScaleOption, type HeaderDensityOption } from "./typography.js";
3
+ export { AfSelect, type AfSelectProps, type AfSelectOption } from "./af_select.js";
4
+ export { ProviderModelSelect, type ProviderModelSelectProps, type ProviderOption } from "./provider_model_select.js";
5
+ export { ThemeSelect, type ThemeSelectProps } from "./theme_select.js";
6
+ export { FontScaleSelect, HeaderDensitySelect, type FontScaleSelectProps, type HeaderDensitySelectProps } from "./typography_select.js";
7
+ export { Icon, type IconName } from "./icon.js";
8
+ export { ToolPolicyEditor, TOOL_POLICY_DEFAULTS, type ToolPolicyEditorProps, type ToolPolicySelection, type ToolPolicyDefaults, type ToolSpec, type ToolApprovalMode, } from "./tool_policy_editor.js";
8
9
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAC1H,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,KAAK,eAAe,EAAE,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACtK,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAChF,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,KAAK,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAClH,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACpE,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,KAAK,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AACrI,OAAO,EAAE,IAAI,EAAE,KAAK,QAAQ,EAAE,MAAM,QAAQ,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAC7H,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,KAAK,eAAe,EAAE,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACzK,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAC;AACnF,OAAO,EAAE,mBAAmB,EAAE,KAAK,wBAAwB,EAAE,KAAK,cAAc,EAAE,MAAM,4BAA4B,CAAC;AACrH,OAAO,EAAE,WAAW,EAAE,KAAK,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,mBAAmB,EAAE,KAAK,oBAAoB,EAAE,KAAK,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AACxI,OAAO,EAAE,IAAI,EAAE,KAAK,QAAQ,EAAE,MAAM,WAAW,CAAC;AAChD,OAAO,EACL,gBAAgB,EAChB,oBAAoB,EACpB,KAAK,qBAAqB,EAC1B,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,QAAQ,EACb,KAAK,gBAAgB,GACtB,MAAM,yBAAyB,CAAC"}
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
- export { THEMES, THEME_SPECS, applyTheme, getThemeSpec, themeClassName } from "./theme";
2
- export { FONT_SCALES, HEADER_DENSITIES, applyTypography, getFontScaleSpec, getHeaderDensitySpec } from "./typography";
3
- export { AfSelect } from "./af_select";
4
- export { ProviderModelSelect } from "./provider_model_select";
5
- export { ThemeSelect } from "./theme_select";
6
- export { FontScaleSelect, HeaderDensitySelect } from "./typography_select";
7
- export { Icon } from "./icon";
1
+ export { THEMES, THEME_SPECS, applyTheme, getThemeSpec, themeClassName } from "./theme.js";
2
+ export { FONT_SCALES, HEADER_DENSITIES, applyTypography, getFontScaleSpec, getHeaderDensitySpec } from "./typography.js";
3
+ export { AfSelect } from "./af_select.js";
4
+ export { ProviderModelSelect } from "./provider_model_select.js";
5
+ export { ThemeSelect } from "./theme_select.js";
6
+ export { FontScaleSelect, HeaderDensitySelect } from "./typography_select.js";
7
+ export { Icon } from "./icon.js";
8
+ export { ToolPolicyEditor, TOOL_POLICY_DEFAULTS, } from "./tool_policy_editor.js";
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
- import { AfSelect } from "./af_select";
2
+ import { AfSelect } from "./af_select.js";
3
3
  function normalize_provider_options(items) {
4
4
  const seen = new Set();
5
5
  const out = [];
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { type ThemeSpec } from "./theme";
2
+ import { type ThemeSpec } from "./theme.js";
3
3
  export type ThemeSelectProps = {
4
4
  value: string;
5
5
  onChange: (theme_id: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"theme_select.d.ts","sourceRoot":"","sources":["../src/theme_select.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,SAAS,CAAC;AAEtD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAMF,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,KAAK,CAAC,YAAY,CAqEvE;AAED,eAAe,WAAW,CAAC"}
1
+ {"version":3,"file":"theme_select.d.ts","sourceRoot":"","sources":["../src/theme_select.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAEzD,MAAM,MAAM,gBAAgB,GAAG;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,MAAM,CAAC,EAAE,SAAS,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,YAAY,CAAC,EAAE,OAAO,CAAC;CACxB,CAAC;AAMF,wBAAgB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,KAAK,CAAC,YAAY,CAqEvE;AAED,eAAe,WAAW,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import { useMemo } from "react";
3
- import { AfSelect } from "./af_select";
4
- import { THEME_SPECS } from "./theme";
3
+ import { AfSelect } from "./af_select.js";
4
+ import { THEME_SPECS } from "./theme.js";
5
5
  function theme_group_label(group) {
6
6
  return group === "light" ? "Light" : "Dark";
7
7
  }
@@ -0,0 +1,35 @@
1
+ import React from "react";
2
+ export type ToolSpec = {
3
+ name: string;
4
+ description?: string;
5
+ toolset?: string;
6
+ when_to_use?: string;
7
+ };
8
+ export type ToolApprovalMode = "approve" | "ask";
9
+ export type ToolPolicyDefaults = {
10
+ autoApprove: string[];
11
+ requireApproval: string[];
12
+ };
13
+ export type ToolPolicySelection = {
14
+ mode: "all" | "custom";
15
+ selected: string[];
16
+ approval: Record<string, ToolApprovalMode>;
17
+ };
18
+ export type ToolPolicyEditorProps = {
19
+ tools: ToolSpec[];
20
+ value: ToolPolicySelection;
21
+ onChange: (next: ToolPolicySelection) => void;
22
+ defaults?: ToolPolicyDefaults;
23
+ disabled?: boolean;
24
+ title?: string;
25
+ subtitle?: string;
26
+ note?: string;
27
+ toolMode?: string;
28
+ toolModeLabel?: string;
29
+ toolModeDetail?: string;
30
+ className?: string;
31
+ };
32
+ export declare const TOOL_POLICY_DEFAULTS: ToolPolicyDefaults;
33
+ export declare function ToolPolicyEditor(props: ToolPolicyEditorProps): React.ReactElement;
34
+ export default ToolPolicyEditor;
35
+ //# sourceMappingURL=tool_policy_editor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool_policy_editor.d.ts","sourceRoot":"","sources":["../src/tool_policy_editor.tsx"],"names":[],"mappings":"AAGA,OAAO,KAA4B,MAAM,OAAO,CAAC;AAEjD,MAAM,MAAM,QAAQ,GAAG;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,KAAK,CAAC;AAEjD,MAAM,MAAM,kBAAkB,GAAG;IAC/B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,mBAAmB,GAAG;IAChC,IAAI,EAAE,KAAK,GAAG,QAAQ,CAAC;IACvB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAC5C,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,KAAK,EAAE,mBAAmB,CAAC;IAC3B,QAAQ,EAAE,CAAC,IAAI,EAAE,mBAAmB,KAAK,IAAI,CAAC;IAC9C,QAAQ,CAAC,EAAE,kBAAkB,CAAC;IAC9B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAGF,eAAO,MAAM,oBAAoB,EAAE,kBAwBlC,CAAC;AAgEF,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,qBAAqB,GAAG,KAAK,CAAC,YAAY,CA4KjF;AAED,eAAe,gBAAgB,CAAC"}
@@ -0,0 +1,161 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /*
3
+ * ToolPolicyEditor: shared allowlist + approval selector for thin clients.
4
+ */
5
+ import { useMemo, useState } from "react";
6
+ // Mirrors AbstractRuntime ToolApprovalPolicy defaults (keep in sync).
7
+ export const TOOL_POLICY_DEFAULTS = {
8
+ autoApprove: [
9
+ "list_files",
10
+ "skim_folders",
11
+ "analyze_code",
12
+ "read_file",
13
+ "skim_files",
14
+ "search_files",
15
+ "open_attachment",
16
+ "web_search",
17
+ "skim_websearch",
18
+ "skim_url",
19
+ "fetch_url",
20
+ "list_email_accounts",
21
+ "list_emails",
22
+ "read_email",
23
+ "list_whatsapp_messages",
24
+ "read_whatsapp_message",
25
+ "send_email",
26
+ "send_whatsapp_message",
27
+ "send_telegram_message",
28
+ "send_telegram_artifact",
29
+ ],
30
+ requireApproval: ["write_file", "edit_file", "execute_command"],
31
+ };
32
+ function normalize_tools(items) {
33
+ const seen = new Set();
34
+ const out = [];
35
+ for (const it of items || []) {
36
+ if (!it)
37
+ continue;
38
+ const name = String(it.name || "").trim();
39
+ if (!name || seen.has(name))
40
+ continue;
41
+ seen.add(name);
42
+ out.push({
43
+ name,
44
+ description: typeof it.description === "string" ? it.description : undefined,
45
+ toolset: typeof it.toolset === "string" ? it.toolset : undefined,
46
+ when_to_use: typeof it.when_to_use === "string" ? it.when_to_use : undefined,
47
+ });
48
+ }
49
+ out.sort((a, b) => {
50
+ const a_set = String(a.toolset || "");
51
+ const b_set = String(b.toolset || "");
52
+ if (a_set !== b_set)
53
+ return a_set.localeCompare(b_set);
54
+ return a.name.localeCompare(b.name);
55
+ });
56
+ return out;
57
+ }
58
+ function default_mode_for(name, defaults) {
59
+ if (defaults.requireApproval.includes(name))
60
+ return "ask";
61
+ if (defaults.autoApprove.includes(name))
62
+ return "approve";
63
+ return "ask";
64
+ }
65
+ function describe_tool_mode(raw, override_label, override_detail) {
66
+ const mode = String(raw || "").trim().toLowerCase();
67
+ if (!mode && !override_label && !override_detail)
68
+ return null;
69
+ let info;
70
+ if (mode === "approval" || mode === "local_approval" || mode === "local-approval") {
71
+ info = { label: "APPROVAL", detail: "Safe tools auto-run; mutating tools ask for approval.", tone: "safe" };
72
+ }
73
+ else if (mode === "passthrough") {
74
+ info = { label: "PASSTHROUGH", detail: "All tools require approval before execution.", tone: "warn" };
75
+ }
76
+ else if (mode === "delegated" || mode === "delegate" || mode === "job") {
77
+ info = { label: "DELEGATED", detail: "Tool calls wait for external executors.", tone: "info" };
78
+ }
79
+ else if (mode === "local" || mode === "local_all" || mode === "local-all") {
80
+ info = { label: "LOCAL", detail: "All tools run locally; client policy may still require approval.", tone: "danger" };
81
+ }
82
+ else {
83
+ info = { label: "UNKNOWN", detail: "#FALLBACK: gateway tool mode not reported.", tone: "warn" };
84
+ }
85
+ if (override_label)
86
+ info = { ...info, label: override_label };
87
+ if (override_detail)
88
+ info = { ...info, detail: override_detail };
89
+ return info;
90
+ }
91
+ export function ToolPolicyEditor(props) {
92
+ const defaults = props.defaults || TOOL_POLICY_DEFAULTS;
93
+ const disabled = props.disabled === true;
94
+ const title = props.title || "Tools";
95
+ const subtitle = props.subtitle ||
96
+ "Default is all tools. Safe/read-only tools auto-approve; mutating tools ask for approval.";
97
+ const note = String(props.note || "").trim();
98
+ const tool_mode = useMemo(() => describe_tool_mode(props.toolMode, props.toolModeLabel, props.toolModeDetail), [props.toolMode, props.toolModeLabel, props.toolModeDetail]);
99
+ const tools = useMemo(() => normalize_tools(props.tools), [props.tools]);
100
+ const all_names = useMemo(() => tools.map((t) => t.name), [tools]);
101
+ const all_names_set = useMemo(() => new Set(all_names), [all_names]);
102
+ const [filter, setFilter] = useState("");
103
+ const selected = useMemo(() => {
104
+ const raw = Array.isArray(props.value?.selected) ? props.value.selected : [];
105
+ return raw.map((n) => String(n || "").trim()).filter((n) => n && all_names_set.has(n));
106
+ }, [props.value?.selected, all_names_set]);
107
+ const mode = props.value?.mode === "custom" ? "custom" : "all";
108
+ const filtered = useMemo(() => {
109
+ const q = filter.trim().toLowerCase();
110
+ if (!q)
111
+ return tools;
112
+ return tools.filter((t) => {
113
+ const name = t.name.toLowerCase();
114
+ const desc = (t.description || "").toLowerCase();
115
+ return name.includes(q) || desc.includes(q);
116
+ });
117
+ }, [tools, filter]);
118
+ const effective_selected = mode === "all" ? new Set(all_names) : new Set(selected);
119
+ const on_change = (next) => {
120
+ props.onChange(next);
121
+ };
122
+ const toggle_mode = (next) => {
123
+ if (disabled)
124
+ return;
125
+ on_change({ ...props.value, mode: next, selected });
126
+ };
127
+ const toggle_tool = (name, enabled) => {
128
+ if (disabled)
129
+ return;
130
+ const set = new Set(selected);
131
+ if (enabled)
132
+ set.add(name);
133
+ else
134
+ set.delete(name);
135
+ on_change({ ...props.value, selected: Array.from(set), mode: "custom" });
136
+ };
137
+ const set_approval = (name, mode_value) => {
138
+ if (disabled)
139
+ return;
140
+ const next = { ...(props.value?.approval || {}) };
141
+ next[name] = mode_value;
142
+ on_change({ ...props.value, approval: next });
143
+ };
144
+ const select_all = () => {
145
+ if (disabled)
146
+ return;
147
+ on_change({ ...props.value, selected: Array.from(all_names), mode: "custom" });
148
+ };
149
+ const select_none = () => {
150
+ if (disabled)
151
+ return;
152
+ on_change({ ...props.value, selected: [], mode: "custom" });
153
+ };
154
+ const selected_count = effective_selected.size;
155
+ return (_jsxs("div", { className: `af-tool-policy ${props.className || ""}`.trim(), children: [_jsxs("div", { className: "af-tool-policy__header", children: [_jsx("div", { className: "af-tool-policy__title", children: title }), _jsx("div", { className: "af-tool-policy__subtitle", children: subtitle }), tool_mode ? (_jsxs("div", { className: `af-tool-policy__mode is-${tool_mode.tone}`, children: [_jsx("div", { className: "af-tool-policy__mode-badge", children: "Tool mode" }), _jsx("div", { className: "af-tool-policy__mode-value", children: tool_mode.label }), _jsx("div", { className: "af-tool-policy__mode-detail", children: tool_mode.detail })] })) : null, note ? _jsx("div", { className: "af-tool-policy__note", children: note }) : null] }), _jsxs("div", { className: "af-tool-policy__controls", children: [_jsxs("div", { className: "af-tool-policy__segmented", role: "tablist", "aria-label": "Tool allowlist mode", children: [_jsx("button", { type: "button", className: `af-tool-policy__seg-btn ${mode === "all" ? "is-active" : ""}`.trim(), onClick: () => toggle_mode("all"), disabled: disabled, children: "All tools" }), _jsx("button", { type: "button", className: `af-tool-policy__seg-btn ${mode === "custom" ? "is-active" : ""}`.trim(), onClick: () => toggle_mode("custom"), disabled: disabled, children: "Custom allowlist" })] }), _jsxs("div", { className: "af-tool-policy__count", children: [selected_count, " / ", all_names.length, " enabled"] }), _jsxs("div", { className: "af-tool-policy__bulk", children: [_jsx("button", { type: "button", onClick: select_all, disabled: disabled || mode !== "custom", children: "Select all" }), _jsx("button", { type: "button", onClick: select_none, disabled: disabled || mode !== "custom", children: "Select none" })] })] }), _jsx("div", { className: "af-tool-policy__filter", children: _jsx("input", { type: "text", placeholder: "Filter tools...", value: filter, onChange: (e) => setFilter(e.target.value), disabled: disabled }) }), _jsxs("div", { className: "af-tool-policy__list", children: [filtered.map((tool) => {
156
+ const is_checked = effective_selected.has(tool.name);
157
+ const approval = props.value?.approval?.[tool.name] || default_mode_for(tool.name, defaults);
158
+ return (_jsxs("div", { className: `af-tool-row ${is_checked ? "is-enabled" : ""}`.trim(), children: [_jsx("label", { className: "af-tool-row__check", children: _jsx("input", { type: "checkbox", checked: is_checked, disabled: disabled || mode !== "custom", onChange: (e) => toggle_tool(tool.name, e.target.checked) }) }), _jsxs("div", { className: "af-tool-row__meta", children: [_jsxs("div", { className: "af-tool-row__title", children: [_jsx("span", { className: "af-tool-row__name", children: tool.name }), tool.toolset ? _jsx("span", { className: "af-tool-row__badge", children: tool.toolset }) : null] }), tool.description ? _jsx("div", { className: "af-tool-row__desc", children: tool.description }) : null, tool.when_to_use ? _jsx("div", { className: "af-tool-row__hint", children: tool.when_to_use }) : null] }), _jsx("div", { className: "af-tool-row__approval", children: _jsxs("select", { value: approval, disabled: disabled || !is_checked, onChange: (e) => set_approval(tool.name, e.target.value), children: [_jsx("option", { value: "approve", children: "Approve" }), _jsx("option", { value: "ask", children: "Ask" })] }) })] }, tool.name));
159
+ }), !filtered.length ? _jsx("div", { className: "af-tool-policy__empty", children: "No tools match the filter." }) : null] })] }));
160
+ }
161
+ export default ToolPolicyEditor;
@@ -1,5 +1,5 @@
1
1
  import React from "react";
2
- import { type FontScaleOption, type HeaderDensityOption } from "./typography";
2
+ import { type FontScaleOption, type HeaderDensityOption } from "./typography.js";
3
3
  export type FontScaleSelectProps = {
4
4
  value: string;
5
5
  onChange: (font_scale_id: string) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"typography_select.d.ts","sourceRoot":"","sources":["../src/typography_select.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAiC,KAAK,eAAe,EAAE,KAAK,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAE7G,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,KAAK,CAAC,YAAY,CAuB/E;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,SAAS,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,GAAG,KAAK,CAAC,YAAY,CAuBvF;AAED,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"typography_select.d.ts","sourceRoot":"","sources":["../src/typography_select.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAkB,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAiC,KAAK,eAAe,EAAE,KAAK,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEhH,MAAM,MAAM,oBAAoB,GAAG;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,IAAI,CAAC;IAC1C,MAAM,CAAC,EAAE,eAAe,EAAE,CAAC;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB,GAAG,KAAK,CAAC,YAAY,CAuB/E;AAED,MAAM,MAAM,wBAAwB,GAAG;IACrC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,iBAAiB,EAAE,MAAM,KAAK,IAAI,CAAC;IAC9C,SAAS,CAAC,EAAE,mBAAmB,EAAE,CAAC;IAClC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,GAAG,KAAK,CAAC;IAC1B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB,CAAC;AAEF,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,GAAG,KAAK,CAAC,YAAY,CAuBvF;AAED,eAAe,eAAe,CAAC"}
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { useMemo } from "react";
3
- import { AfSelect } from "./af_select";
4
- import { FONT_SCALES, HEADER_DENSITIES } from "./typography";
3
+ import { AfSelect } from "./af_select.js";
4
+ import { FONT_SCALES, HEADER_DENSITIES } from "./typography.js";
5
5
  export function FontScaleSelect(props) {
6
6
  const scales = props.scales && props.scales.length ? props.scales : FONT_SCALES;
7
7
  const variant = props.variant || "panel";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@abstractframework/ui-kit",
3
- "version": "0.1.2",
3
+ "version": "0.1.6",
4
4
  "description": "Shared UI tokens + themes for AbstractFramework thin clients (AbstractFlow, AbstractObserver, etc.).",
5
5
  "type": "module",
6
6
  "author": "Laurent-Philippe Albou",
@@ -44,10 +44,10 @@
44
44
  "react-dom": "^18.0.0"
45
45
  },
46
46
  "devDependencies": {
47
- "react": "^18.0.0",
48
- "react-dom": "^18.0.0",
49
47
  "@types/react": "^18.2.48",
50
48
  "@types/react-dom": "^18.2.18",
49
+ "react": "^18.0.0",
50
+ "react-dom": "^18.0.0",
51
51
  "typescript": "^5.3.3"
52
52
  },
53
53
  "sideEffects": [
package/src/theme.css CHANGED
@@ -201,6 +201,255 @@
201
201
  --border-accent: rgba(136, 192, 208, 0.55);
202
202
  }
203
203
 
204
+ /* Tool policy editor (allowlist + approve/ask) */
205
+ .af-tool-policy {
206
+ display: flex;
207
+ flex-direction: column;
208
+ gap: 12px;
209
+ padding: 16px;
210
+ border-radius: var(--radius-lg);
211
+ border: 1px solid var(--ui-border-1);
212
+ background: var(--ui-surface-1);
213
+ box-shadow: var(--ui-shadow-1);
214
+ color: var(--text-primary);
215
+ font-family: var(--font-sans);
216
+ }
217
+
218
+ .af-tool-policy__header {
219
+ display: flex;
220
+ flex-direction: column;
221
+ gap: 6px;
222
+ }
223
+
224
+ .af-tool-policy__title {
225
+ font-size: var(--font-size-xs);
226
+ font-weight: 700;
227
+ letter-spacing: 0.12em;
228
+ color: var(--accent);
229
+ text-transform: uppercase;
230
+ }
231
+
232
+ .af-tool-policy__subtitle {
233
+ font-size: var(--font-size-sm);
234
+ color: var(--text-secondary);
235
+ }
236
+
237
+ .af-tool-policy__mode {
238
+ display: grid;
239
+ grid-template-columns: auto auto 1fr;
240
+ gap: 6px 10px;
241
+ align-items: center;
242
+ padding: 8px 10px;
243
+ border-radius: var(--radius-md);
244
+ border: 1px solid var(--ui-border-1);
245
+ background: var(--ui-overlay-bg);
246
+ font-size: var(--font-size-xs);
247
+ font-weight: 600;
248
+ color: var(--text-primary);
249
+ }
250
+
251
+ .af-tool-policy__mode-badge {
252
+ text-transform: uppercase;
253
+ letter-spacing: 0.08em;
254
+ font-size: var(--font-size-xxs);
255
+ color: var(--text-muted);
256
+ }
257
+
258
+ .af-tool-policy__mode-value {
259
+ font-weight: 800;
260
+ }
261
+
262
+ .af-tool-policy__mode-detail {
263
+ grid-column: 1 / -1;
264
+ font-size: var(--font-size-xs);
265
+ font-weight: 500;
266
+ color: var(--text-secondary);
267
+ }
268
+
269
+ .af-tool-policy__mode.is-safe {
270
+ border-color: color-mix(in srgb, var(--accent) 50%, transparent);
271
+ }
272
+
273
+ .af-tool-policy__mode.is-warn {
274
+ border-color: color-mix(in srgb, #f59e0b 50%, transparent);
275
+ background: color-mix(in srgb, #f59e0b 12%, var(--ui-overlay-bg));
276
+ }
277
+
278
+ .af-tool-policy__mode.is-danger {
279
+ border-color: color-mix(in srgb, #ef4444 55%, transparent);
280
+ background: color-mix(in srgb, #ef4444 14%, var(--ui-overlay-bg));
281
+ }
282
+
283
+ .af-tool-policy__mode.is-info {
284
+ border-color: color-mix(in srgb, #38bdf8 50%, transparent);
285
+ background: color-mix(in srgb, #38bdf8 12%, var(--ui-overlay-bg));
286
+ }
287
+
288
+ .af-tool-policy__note {
289
+ font-size: var(--font-size-xs);
290
+ color: var(--text-muted);
291
+ }
292
+
293
+ .af-tool-policy__controls {
294
+ display: grid;
295
+ grid-template-columns: minmax(180px, 1fr) auto auto;
296
+ gap: 10px;
297
+ align-items: center;
298
+ }
299
+
300
+ .af-tool-policy__segmented {
301
+ display: inline-flex;
302
+ background: var(--ui-overlay-bg);
303
+ border: 1px solid var(--ui-overlay-border);
304
+ border-radius: var(--radius-lg);
305
+ padding: 2px;
306
+ gap: 2px;
307
+ }
308
+
309
+ .af-tool-policy__seg-btn {
310
+ border: none;
311
+ background: transparent;
312
+ color: var(--text-secondary);
313
+ padding: 6px 12px;
314
+ font-size: var(--font-size-sm);
315
+ font-weight: 600;
316
+ border-radius: var(--radius-md);
317
+ cursor: pointer;
318
+ }
319
+
320
+ .af-tool-policy__seg-btn.is-active {
321
+ background: var(--ui-surface-2);
322
+ color: var(--text-primary);
323
+ }
324
+
325
+ .af-tool-policy__seg-btn:disabled {
326
+ cursor: not-allowed;
327
+ opacity: 0.6;
328
+ }
329
+
330
+ .af-tool-policy__count {
331
+ padding: 6px 12px;
332
+ border-radius: var(--radius-lg);
333
+ border: 1px solid var(--ui-border-1);
334
+ background: var(--ui-overlay-bg);
335
+ color: var(--text-secondary);
336
+ font-size: var(--font-size-xs);
337
+ font-weight: 600;
338
+ text-align: center;
339
+ }
340
+
341
+ .af-tool-policy__bulk {
342
+ display: inline-flex;
343
+ gap: 6px;
344
+ justify-self: end;
345
+ }
346
+
347
+ .af-tool-policy__bulk button {
348
+ border: 1px solid var(--ui-border-1);
349
+ background: var(--ui-overlay-bg);
350
+ color: var(--text-secondary);
351
+ border-radius: var(--radius-md);
352
+ padding: 6px 10px;
353
+ font-size: var(--font-size-xs);
354
+ font-weight: 600;
355
+ cursor: pointer;
356
+ }
357
+
358
+ .af-tool-policy__bulk button:disabled {
359
+ cursor: not-allowed;
360
+ opacity: 0.5;
361
+ }
362
+
363
+ .af-tool-policy__filter input {
364
+ width: 100%;
365
+ border-radius: var(--radius-md);
366
+ border: 1px solid var(--ui-border-1);
367
+ background: var(--ui-overlay-bg);
368
+ color: var(--text-primary);
369
+ padding: 8px 10px;
370
+ font-size: var(--font-size-sm);
371
+ }
372
+
373
+ .af-tool-policy__list {
374
+ display: flex;
375
+ flex-direction: column;
376
+ gap: 10px;
377
+ max-height: 420px;
378
+ overflow: auto;
379
+ padding-right: 4px;
380
+ }
381
+
382
+ .af-tool-row {
383
+ display: grid;
384
+ grid-template-columns: 24px 1fr auto;
385
+ gap: 10px;
386
+ align-items: flex-start;
387
+ padding: 10px 12px;
388
+ border-radius: var(--radius-md);
389
+ border: 1px solid var(--ui-border-1);
390
+ background: var(--ui-surface-2);
391
+ }
392
+
393
+ .af-tool-row.is-enabled {
394
+ border-color: var(--accent-border);
395
+ }
396
+
397
+ .af-tool-row__check input {
398
+ width: 16px;
399
+ height: 16px;
400
+ }
401
+
402
+ .af-tool-row__title {
403
+ display: flex;
404
+ align-items: center;
405
+ gap: 8px;
406
+ }
407
+
408
+ .af-tool-row__name {
409
+ font-weight: 700;
410
+ font-size: var(--font-size-sm);
411
+ color: var(--text-primary);
412
+ }
413
+
414
+ .af-tool-row__badge {
415
+ padding: 2px 8px;
416
+ border-radius: var(--radius-md);
417
+ border: 1px solid var(--ui-chip-border);
418
+ background: var(--ui-chip-bg);
419
+ color: var(--ui-chip-text);
420
+ font-size: var(--font-size-xxs);
421
+ text-transform: uppercase;
422
+ letter-spacing: 0.08em;
423
+ }
424
+
425
+ .af-tool-row__desc {
426
+ font-size: var(--font-size-xs);
427
+ color: var(--text-secondary);
428
+ margin-top: 4px;
429
+ }
430
+
431
+ .af-tool-row__hint {
432
+ font-size: var(--font-size-xxs);
433
+ color: var(--text-muted);
434
+ margin-top: 4px;
435
+ }
436
+
437
+ .af-tool-row__approval select {
438
+ border-radius: var(--radius-md);
439
+ border: 1px solid var(--ui-border-1);
440
+ background: var(--ui-overlay-bg);
441
+ color: var(--text-primary);
442
+ padding: 6px 8px;
443
+ font-size: var(--font-size-xs);
444
+ }
445
+
446
+ .af-tool-policy__empty {
447
+ padding: 18px;
448
+ text-align: center;
449
+ color: var(--text-muted);
450
+ font-size: var(--font-size-sm);
451
+ }
452
+
204
453
  :root.theme-dracula {
205
454
  --bg-primary: #282a36;
206
455
  --bg-secondary: #343746;
@@ -885,6 +1134,13 @@
885
1134
  font-size: var(--font-size-base);
886
1135
  }
887
1136
 
1137
+ .af-select-custom-error {
1138
+ margin-top: 6px;
1139
+ color: var(--accent);
1140
+ font-size: var(--font-size-xs);
1141
+ line-height: 1.35;
1142
+ }
1143
+
888
1144
  .af-select-options {
889
1145
  max-height: 340px;
890
1146
  overflow: auto;
@@ -926,13 +1182,37 @@
926
1182
  background: var(--ui-surface-2, rgba(255, 255, 255, 0.06));
927
1183
  }
928
1184
 
1185
+ .af-select-option--disabled {
1186
+ cursor: not-allowed;
1187
+ color: var(--text-muted);
1188
+ opacity: 0.58;
1189
+ }
1190
+
1191
+ .af-select-option--disabled.af-select-option--highlighted {
1192
+ background: var(--ui-surface-1, rgba(255, 255, 255, 0.04));
1193
+ }
1194
+
929
1195
  .af-select-option-label {
1196
+ display: flex;
1197
+ flex-direction: column;
1198
+ gap: 2px;
930
1199
  flex: 1;
1200
+ min-width: 0;
1201
+ overflow: hidden;
1202
+ }
1203
+
1204
+ .af-select-option-label > span {
931
1205
  overflow: hidden;
932
1206
  text-overflow: ellipsis;
933
1207
  white-space: nowrap;
934
1208
  }
935
1209
 
1210
+ .af-select-option-reason {
1211
+ color: var(--text-muted);
1212
+ font-size: var(--font-size-xs);
1213
+ line-height: 1.25;
1214
+ }
1215
+
936
1216
  .af-select-check {
937
1217
  color: var(--accent);
938
1218
  font-weight: 700;