History

Show resolved permission level on every tool in source views

- Add interaction field to SourceInspectionToolListItem so the API returns each tool's intrinsic interaction mode (auto/required) - Resolve actual permission level for every tool (no more "unknown" or "default") using the tool's interaction mode as fallback when no policy matches - Migrate OpenAPI and GraphQL plugins from renderDetail override to renderHeaderMeta/renderSchemaExtras props so policies and permission controls render in all plugin detail views - Fix Vite HMR for workspace packages: the reloadOnPluginChange handler was matching any path containing /plugins/ (including the react client's src/plugins/ subfolder), changed to only match the top-level plugins/ workspace directory - Enable polling in Vite watcher for reliable WSL2 file change detection

Rhys Sullivan committed 7 months ago

Commit 5ddc2861 parent 500303a8 files · +55 −37Browse files

Unified · Split

8 files
  1. apps/web/vite.config.ts +5 −2
  2. packages/clients/react/src/plugins/components/source-tool-explorer.tsx +17 −1
  3. packages/clients/react/src/plugins/components/source-tool-workbench.tsx +7 −3
  4. packages/clients/react/src/plugins/components/tool-permission-badge.tsx +19 −16
  5. packages/platform/sdk/src/schema/models/source-inspection.ts +1 −0
  6. packages/platform/sdk/src/sources/inspection.ts +3 −0
  7. plugins/graphql/react/components.tsx +1 −7
  8. plugins/openapi/react/components.tsx +2 −8
apps/web/vite.config.ts+5−2
@@ -34,12 +34,12 @@ const workspaceExecutorPackages = Object.keys(webPackage.dependencies ?? {}).fil · 33 unmodified lines
3434 (name) => name.startsWith("@executor/"),
3535 );
3636
37−const pluginWorkspaceSegment = `${path.sep}plugins${path.sep}`;
37+const pluginsWorkspaceRoot = path.resolve(import.meta.dirname, "../../plugins") + path.sep;
3838
3939 const reloadOnPluginChange = () => ({
4040 name: "executor-reload-on-plugin-change",
4141 handleHotUpdate({ file, server }: { file: string; server: { hot: { send: (payload: { type: "full-reload" }) => void } } }) {
42− if (!file.includes(pluginWorkspaceSegment)) {
42+ if (!file.startsWith(pluginsWorkspaceRoot)) {
4343 return;
4444 }
4545
@@ -91,6 +91,9 @@ export default defineConfig({ · 45 unmodified lines
9191 ignored: [
9292 "!**/node_modules/@executor/**",
9393 ],
94+ // WSL2 inotify doesn't reliably detect changes through symlinked workspace packages
95+ usePolling: true,
96+ interval: 500,
9497 },
9598 },
9699 });
packages/clients/react/src/plugins/components/source-tool-explorer.tsx+17−1
@@ -13,6 +13,7 @@ import { resolveToolPermission, type ToolPermissionLevel } from "./tool-permissi · 12 unmodified lines
1313 import { Badge } from "./ui/badge";
1414 import { LoadableBlock } from "./loadable";
1515 import {
16+ SourceToolDetailPanel,
1617 SourceToolModelWorkbench,
1718 type ToolPermissionChangeRequest,
1819 } from "./source-tool-workbench";
@@ -32,6 +33,8 @@ export const SourceToolExplorer = (props: { · 13 unmodified lines
3233 actions?: ReactNode;
3334 summary?: ReactNode;
3435 policies?: ReadonlyArray<LocalScopePolicy>;
36+ renderHeaderMeta?: (detail: SourceInspectionToolDetail) => ReactNode;
37+ renderSchemaExtras?: (detail: SourceInspectionToolDetail) => ReactNode;
3538 renderDetail?: (detail: SourceInspectionToolDetail) => ReactNode;
3639 }) => {
3740 const inspection = useSourceInspection(props.sourceId);
@@ -171,7 +174,20 @@ export const SourceToolExplorer = (props: { · 133 unmodified lines
171174 });
172175 }}
173176 sourceId={props.sourceId}
174− renderDetail={props.renderDetail}
177+ renderDetail={props.renderDetail ?? (
178+ (props.renderHeaderMeta || props.renderSchemaExtras)
179+ ? (toolDetail) => (
180+ <SourceToolDetailPanel
181+ detail={toolDetail}
182+ policies={policies}
183+ onSetPermission={handleSetPermission}
184+ permissionPending={permissionPending}
185+ renderHeaderMeta={props.renderHeaderMeta}
186+ renderSchemaExtras={props.renderSchemaExtras}
187+ />
188+ )
189+ : undefined
190+ )}
175191 />
176192 </div>
177193 </div>
packages/clients/react/src/plugins/components/source-tool-workbench.tsx+7−3
@@ -455,8 +455,12 @@ const SourceToolListItem = (props: { · 454 unmodified lines
455455 {highlightMatch(label, props.search)}
456456 </span>
457457 {props.tool.method && <MethodBadge method={props.tool.method} />}
458− {props.policies && props.policies.length > 0 && (
459− <ToolPermissionDot toolPath={props.tool.path} policies={props.policies} />
458+ {props.policies && (
459+ <ToolPermissionDot
460+ toolPath={props.tool.path}
461+ policies={props.policies}
462+ interaction={props.tool.interaction}
463+ />
460464 )}
461465 </Button>
462466 );
@@ -594,7 +598,7 @@ export const SourceToolDetailPanel = (props: SourceToolDetailPanelProps) => { · 131 unmodified lines
594598 {props.detail.summary.method && (
595599 <MethodBadge method={props.detail.summary.method} />
596600 )}
597− {props.policies && props.policies.length > 0 && (
601+ {props.policies && (
598602 <ToolPermissionBadge
599603 toolPath={props.detail.summary.path}
600604 policies={props.policies}
packages/clients/react/src/plugins/components/tool-permission-badge.tsx+19−16
@@ -1,12 +1,10 @@
11 import type {
22 LocalScopePolicy,
3− LocalScopePolicyApprovalMode,
4− LocalScopePolicyEffect,
53 } from "@executor/platform-sdk/schema";
64
75 import { cn } from "../lib/cn";
86
9−export type ToolPermissionLevel = "auto-run" | "requires-approval" | "denied" | "unknown";
7+export type ToolPermissionLevel = "auto-run" | "requires-approval" | "denied";
108
119 const matchesGlob = (pattern: string, value: string): boolean => {
1210 const escaped = pattern
@@ -21,6 +19,7 @@ const policySpecificity = (policy: LocalScopePolicy): number => · 8 unmodified lines
2119 export const resolveToolPermission = (
2220 toolPath: string,
2321 policies: ReadonlyArray<LocalScopePolicy>,
22+ toolInteraction?: "auto" | "required",
2423 ): {
2524 level: ToolPermissionLevel;
2625 matchedPolicy: LocalScopePolicy | null;
@@ -38,7 +37,10 @@ export const resolveToolPermission = ( · 11 unmodified lines
3837
3938 const matched = matching[0];
4039 if (!matched) {
41− return { level: "unknown", matchedPolicy: null };
40+ // No explicit policy — resolve from the tool's intrinsic interaction mode
41+ const defaultLevel: ToolPermissionLevel =
42+ toolInteraction === "auto" ? "auto-run" : "requires-approval";
43+ return { level: defaultLevel, matchedPolicy: null };
4244 }
4345
4446 if (matched.effect === "deny") {
@@ -71,42 +73,43 @@ const permissionStyles: Record< · 26 unmodified lines
7173 dotClass: "bg-red-500",
7274 textClass: "text-red-600 dark:text-red-400",
7375 },
74− unknown: {
75− label: "",
76− dotClass: "bg-muted-foreground/30",
77− textClass: "text-muted-foreground/50",
78− },
7976 };
8077
8178 export const ToolPermissionDot = (props: {
8279 toolPath: string;
8380 policies: ReadonlyArray<LocalScopePolicy>;
81+ interaction?: "auto" | "required";
8482 className?: string;
8583 }) => {
86− const { level } = resolveToolPermission(props.toolPath, props.policies);
87− if (level === "unknown") return null;
88−
84+ const { level } = resolveToolPermission(props.toolPath, props.policies, props.interaction);
8985 const style = permissionStyles[level];
9086
9187 return (
9288 <span
93− className={cn("size-1.5 shrink-0 rounded-full", style.dotClass, props.className)}
89+ className={cn(
90+ "inline-flex shrink-0 items-center gap-1 rounded-full px-1.5 py-px text-[9px] font-medium leading-none",
91+ style.textClass,
92+ props.className,
93+ )}
9494 title={style.label}
95− />
95+ >
96+ <span className={cn("size-1.5 rounded-full", style.dotClass)} />
97+ {style.label}
98+ </span>
9699 );
97100 };
98101
99102 export const ToolPermissionBadge = (props: {
100103 toolPath: string;
101104 policies: ReadonlyArray<LocalScopePolicy>;
105+ interaction?: "auto" | "required";
102106 className?: string;
103107 }) => {
104108 const { level, matchedPolicy } = resolveToolPermission(
105109 props.toolPath,
106110 props.policies,
111+ props.interaction,
107112 );
108− if (level === "unknown") return null;
109−
110113 const style = permissionStyles[level];
111114
112115 return (
packages/platform/sdk/src/schema/models/source-inspection.ts+1−0
@@ -60,6 +60,7 @@ export const SourceInspectionSectionSchema = Schema.Union( · 59 unmodified lines
6060 export const SourceInspectionToolListItemSchema = Schema.Struct({
6161 path: Schema.String,
6262 method: Schema.NullOr(Schema.String),
63+ interaction: Schema.optional(Schema.Literal("auto", "required")),
6364 inputTypePreview: Schema.optional(Schema.String),
6465 outputTypePreview: Schema.optional(Schema.String),
6566 });
packages/platform/sdk/src/sources/inspection.ts+3−0
@@ -117,6 +117,9 @@ const inspectionToolListItemFromTool = ( · 116 unmodified lines
117117 ): SourceInspectionToolListItem => ({
118118 path: tool.path,
119119 method: executableDetails(tool).method,
120+ ...(tool.descriptor.interaction
121+ ? { interaction: tool.descriptor.interaction }
122+ : {}),
120123 ...(tool.descriptor.contract?.inputTypePreview
121124 ? { inputTypePreview: tool.descriptor.contract.inputTypePreview }
122125 : {}),
plugins/graphql/react/components.tsx+1−7
@@ -22,7 +22,6 @@ import { · 21 unmodified lines
2222 Input,
2323 Label,
2424 Select,
25− SourceToolDetailPanel,
2625 SourceToolExplorer,
2726 Textarea,
2827 parseSourceToolExplorerSearch,
@@ -715,12 +714,7 @@ function GraphqlDetailExplorer(props: { · 686 unmodified lines
715714 )}
716715 </>
717716 )}
718− renderDetail={(detail) => (
719− <SourceToolDetailPanel
720− detail={detail}
721− renderHeaderMeta={renderGraphqlToolHeaderMeta}
722− />
723− )}
717+ renderHeaderMeta={renderGraphqlToolHeaderMeta}
724718 />
725719 );
726720 }
plugins/openapi/react/components.tsx+2−8
@@ -20,7 +20,6 @@ import { · 19 unmodified lines
2020 Input,
2121 Label,
2222 Select,
23− SourceToolDetailPanel,
2423 SourceToolExplorer,
2524 parseSourceToolExplorerSearch,
2625 type SourceToolExplorerSearch,
@@ -861,13 +860,8 @@ function OpenApiSourceDetailPageReady(props: { · 834 unmodified lines
861860 )}
862861 </>
863862 )}
864− renderDetail={(detail) => (
865− <SourceToolDetailPanel
866− detail={detail}
867− renderHeaderMeta={renderOpenApiToolHeaderMeta}
868− renderSchemaExtras={renderOpenApiToolSchemaExtras}
869− />
870− )}
863+ renderHeaderMeta={renderOpenApiToolHeaderMeta}
864+ renderSchemaExtras={renderOpenApiToolSchemaExtras}
871865 />
872866 );
873867 }