History

Update tool management and routing structure

- Added `nuqs` package for enhanced query handling. - Introduced new routes for tools, including catalog, connections, policies, and editor. - Updated navigation links to direct to specific tool routes instead of generic paths. - Refactored tool-related components to accommodate new routing and state management. - Adjusted type definitions to reflect changes in tool policy management, replacing role-based terminology with policy-centric terms.

Rhys Sullivan committed 9 months ago

Commit a4a18771 parent cffff3322 files · +700 −373Browse files

Unified · Split

22 files
  1. bun.lock +5 −0
  2. executor/apps/web/package.json +3 −2
  3. executor/apps/web/src/components/approvals/approvals-view.tsx +1 −1
  4. executor/apps/web/src/components/menubar/mvp-view.tsx +3 −3
  5. executor/apps/web/src/components/tools/explorer.tsx +90 −37
  6. executor/apps/web/src/components/tools/policies.tsx +97 −91
  7. executor/apps/web/src/components/tools/view.tsx +154 −35
  8. executor/apps/web/src/lib/types.ts +6 −6
  9. executor/apps/web/src/routeTree.gen.ts +104 −20
  10. executor/apps/web/src/routes/__root.tsx +20 −17
  11. executor/apps/web/src/routes/tools.tsx +0 −22
  12. executor/apps/web/src/routes/tools/catalog.tsx +15 −0
  13. executor/apps/web/src/routes/tools/connections.tsx +15 −0
  14. executor/apps/web/src/routes/tools/editor.tsx +15 −0
  15. executor/apps/web/src/routes/tools/index.tsx +18 −0
  16. executor/apps/web/src/routes/tools/policies.tsx +15 −0
  17. executor/packages/core/src/types.ts +3 −3
  18. executor/packages/database/convex/_generated/api.d.ts +71 −71
  19. executor/packages/database/convex/access-controls.test.ts +22 −22
  20. executor/packages/database/convex/database.ts +9 −9
  21. executor/packages/database/convex/database/policies.ts +16 −16
  22. executor/packages/database/convex/workspace.ts +18 −18
bun.lock+5−0
@@ -106,6 +106,7 @@ · 105 unmodified lines
106106 "lucide-react": "^0.563.0",
107107 "monaco-editor": "^0.55.1",
108108 "next-themes": "^0.4.6",
109+ "nuqs": "^2.8.8",
109110 "radix-ui": "^1.4.3",
110111 "react": "19.2.3",
111112 "react-dom": "19.2.3",
@@ -2049,6 +2050,8 @@ · 1937 unmodified lines
20492050
20502051 "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
20512052
2053+ "nuqs": ["nuqs@2.8.8", "", { "dependencies": { "@standard-schema/spec": "1.0.0" }, "peerDependencies": { "@remix-run/react": ">=2", "@tanstack/react-router": "^1", "next": ">=14.2.0", "react": ">=18.2.0 || ^19.0.0-0", "react-router": "^5 || ^6 || ^7", "react-router-dom": "^5 || ^6 || ^7" }, "optionalPeers": ["@remix-run/react", "@tanstack/react-router", "next", "react-router", "react-router-dom"] }, "sha512-LF5sw9nWpHyPWzMMu9oho3r9C5DvkpmBIg4LQN78sexIzGaeRx8DWr0uy3YiFx5i2QGZN1Qqcb+OAtEVRa2bnA=="],
2054+
20522055 "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
20532056
20542057 "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
@@ -2687,6 +2690,8 @@ · 632 unmodified lines
26872690
26882691 "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
26892692
2693+ "nuqs/@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
2694+
26902695 "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
26912696
26922697 "parse-json/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],
executor/apps/web/package.json+3−2
@@ -17,11 +17,11 @@ · 16 unmodified lines
1717 "@executor/ui": "workspace:*",
1818 "@monaco-editor/react": "^4.7.0",
1919 "@streamdown/code": "^1.0.1",
20+ "@tanstack/react-query": "^5.80.7",
2021 "@tanstack/react-router": "^1.132.0",
2122 "@tanstack/react-start": "^1.132.0",
22− "@tanstack/router-plugin": "^1.132.0",
23− "@tanstack/react-query": "^5.80.7",
2423 "@tanstack/react-virtual": "^3.13.18",
24+ "@tanstack/router-plugin": "^1.132.0",
2525 "@workos/authkit-tanstack-react-start": "^0.5.0",
2626 "better-result": "^2.7.0",
2727 "class-variance-authority": "^0.7.1",
@@ -32,6 +32,7 @@ · 4 unmodified lines
3232 "lucide-react": "^0.563.0",
3333 "monaco-editor": "^0.55.1",
3434 "next-themes": "^0.4.6",
35+ "nuqs": "^2.8.8",
3536 "radix-ui": "^1.4.3",
3637 "react": "19.2.3",
3738 "react-dom": "19.2.3",
executor/apps/web/src/components/approvals/approvals-view.tsx+1−1
@@ -97,7 +97,7 @@ function ApprovalCard({ · 96 unmodified lines
9797 variant="ghost"
9898 size="sm"
9999 className="h-7 text-[11px]"
100− onClick={() => navigate("/tools?tab=editor")}
100+ onClick={() => navigate("/tools/editor")}
101101 >
102102 Open editor
103103 </Button>
executor/apps/web/src/components/menubar/mvp-view.tsx+3−3
@@ -60,7 +60,7 @@ export function MenubarMvpView() { · 59 unmodified lines
6060 </p>
6161 </div>
6262 <div className="flex items-center gap-2">
63− <Button size="sm" variant="outline" className="h-8 text-xs" onClick={() => navigate("/tools?tab=editor")}>
63+ <Button size="sm" variant="outline" className="h-8 text-xs" onClick={() => navigate("/tools/editor")}>
6464 Open Editor
6565 </Button>
6666 <Button size="sm" variant="outline" className="h-8 text-xs" onClick={() => navigate("/approvals")}>
@@ -112,7 +112,7 @@ export function MenubarMvpView() { · 45 unmodified lines
112112 <Sparkles className="h-4 w-4 text-primary" />
113113 Recent AI tasks
114114 </p>
115− <Button size="sm" variant="outline" className="h-8 text-xs" onClick={() => navigate("/tools?tab=editor")}>Open Editor</Button>
115+ <Button size="sm" variant="outline" className="h-8 text-xs" onClick={() => navigate("/tools/editor")}>Open Editor</Button>
116116 </div>
117117
118118 {tasksLoading ? (
@@ -130,7 +130,7 @@ export function MenubarMvpView() { · 11 unmodified lines
130130 key={task.id}
131131 type="button"
132132 className="flex w-full items-center justify-between rounded border border-border bg-background px-2.5 py-2 text-left hover:bg-accent"
133− onClick={() => navigate("/tools?tab=editor")}
133+ onClick={() => navigate("/tools/editor")}
134134 >
135135 <span className="min-w-0">
136136 <span className="block truncate text-[11px] font-mono text-foreground">{task.id}</span>
executor/apps/web/src/components/tools/explorer.tsx+90−37
@@ -51,9 +51,17 @@ interface ToolExplorerProps { · 50 unmodified lines
5151 loadingSources?: string[];
5252 onLoadToolDetails?: (toolPaths: string[]) => Promise<Record<string, Pick<ToolDescriptor, "path" | "description" | "display" | "typing">>>;
5353 warnings?: string[];
54− initialSource?: string | null;
55− activeSource?: string | null;
56− onActiveSourceChange?: (source: string | null) => void;
54+ activeSource: string | null;
55+ searchValue: string;
56+ filterApprovalValue: FilterApproval;
57+ focusedToolPathValue: string | null;
58+ selectedToolPathsValue: string[];
59+ focusedSourceNameValue: string | null;
60+ onSearchValueChange: (value: string) => void;
61+ onFilterApprovalValueChange: (filter: FilterApproval) => void;
62+ onFocusedToolPathChange: (toolPath: string | null) => void;
63+ onSelectedToolPathsChange: (toolPaths: string[]) => void;
64+ onFocusedSourceNameChange: (sourceName: string | null) => void;
5765 /** @deprecated No longer used — source sidebar is merged into the tool list panel. */
5866 showSourceSidebar?: boolean;
5967 onSourceAdded?: (source: ToolSourceRecord) => void;
@@ -79,9 +87,17 @@ export function ToolExplorer({ · 19 unmodified lines
7987 loadingSources = [],
8088 onLoadToolDetails,
8189 warnings = [],
82− initialSource = null,
8390 activeSource,
84− onActiveSourceChange,
91+ searchValue,
92+ filterApprovalValue,
93+ focusedToolPathValue,
94+ selectedToolPathsValue,
95+ focusedSourceNameValue,
96+ onSearchValueChange,
97+ onFilterApprovalValueChange,
98+ onFocusedToolPathChange,
99+ onSelectedToolPathsChange,
100+ onFocusedSourceNameChange,
85101 onSourceAdded,
86102 sourceDialogMeta,
87103 sourceAuthProfiles,
@@ -106,21 +122,45 @@ export function ToolExplorer({ · 18 unmodified lines
106122 return description.length > 0 || hasInputHint || hasOutputHint || hasSchemas;
107123 }, []);
108124
109− const [searchInput, setSearchInput] = useState("");
110− const search = useDeferredValue(searchInput);
111− const [internalActiveSource] = useState<string | null>(initialSource);
112− const [selectedKeys, setSelectedKeys] = useState<Set<string>>(new Set());
113− const [filterApproval, setFilterApproval] = useState<FilterApproval>("all");
114125 const [toolDetailsByPath, setToolDetailsByPath] = useState<Record<string, Pick<ToolDescriptor, "path" | "description" | "display" | "typing">>>({});
115126 const [loadingDetailPaths, setLoadingDetailPaths] = useState<Set<string>>(new Set());
116− const [focusedToolPath, setFocusedToolPath] = useState<string | null>(null);
117− const [focusedSourceName, setFocusedSourceName] = useState<string | null>(null);
118127 // "new" = add source form, ToolSourceRecord = edit/view existing source, null = no source panel
119128 const [formSource, setFormSource] = useState<"new" | ToolSourceRecord | null>(null);
120129 const toolListRef = useRef<HTMLDivElement>(null);
121130 const toolListScrollContainerId = "tool-explorer-toollist-scroll";
122− const resolvedActiveSource =
123− activeSource === undefined ? internalActiveSource : activeSource;
131+ const resolvedActiveSource = activeSource;
132+ const searchInput = searchValue;
133+ const filterApproval = filterApprovalValue;
134+ const focusedToolPath = focusedToolPathValue;
135+ const selectedKeys = useMemo(() => new Set(selectedToolPathsValue), [selectedToolPathsValue]);
136+ const focusedSourceName = focusedSourceNameValue;
137+ const search = useDeferredValue(searchInput);
138+
139+ const setSearchInput = useCallback((value: string) => {
140+ onSearchValueChange(value);
141+ }, [onSearchValueChange]);
142+
143+ const setFilterApproval = useCallback((nextFilter: FilterApproval) => {
144+ onFilterApprovalValueChange(nextFilter);
145+ }, [onFilterApprovalValueChange]);
146+
147+ const setFocusedToolPath = useCallback((toolPath: string | null) => {
148+ onFocusedToolPathChange(toolPath);
149+ }, [onFocusedToolPathChange]);
150+
151+ const setSelectedKeys = useCallback((next: Set<string>) => {
152+ onSelectedToolPathsChange(Array.from(next));
153+ }, [onSelectedToolPathsChange]);
154+
155+ const setFocusedSourceName = useCallback((sourceName: string | null) => {
156+ onFocusedSourceNameChange(sourceName);
157+ }, [onFocusedSourceNameChange]);
158+
159+ const setSourceFocusState = useCallback((sourceName: string | null, sourceRecord: ToolSourceRecord | null) => {
160+ setFocusedSourceName(sourceName);
161+ setFormSource(sourceRecord);
162+ setFocusedToolPath(null);
163+ }, [setFocusedSourceName, setFocusedToolPath]);
124164
125165 const hydratedTools = useMemo(() => {
126166 if (Object.keys(toolDetailsByPath).length === 0) {
@@ -212,21 +252,30 @@ export function ToolExplorer({ · 85 unmodified lines
212252 return map;
213253 }, [visibleSources]);
214254
255+ useEffect(() => {
256+ const resolvedSource = focusedSourceNameValue
257+ ? sourceByName.get(focusedSourceNameValue) ?? null
258+ : null;
259+
260+ setFormSource(resolvedSource);
261+ if (focusedSourceNameValue) {
262+ setFocusedToolPath(null);
263+ }
264+ }, [focusedSourceNameValue, sourceByName, setFocusedToolPath]);
265+
215266 const toggleSelectTool = useCallback(
216267 (path: string, e: React.MouseEvent) => {
217268 e.stopPropagation();
218269 e.preventDefault();
219− setSelectedKeys((prev) => {
220− const next = new Set(prev);
221− if (next.has(path)) {
222− next.delete(path);
223− } else {
224− next.add(path);
225− }
226− return next;
227− });
270+ const next = new Set(selectedKeys);
271+ if (next.has(path)) {
272+ next.delete(path);
273+ } else {
274+ next.add(path);
275+ }
276+ setSelectedKeys(next);
228277 },
229− [],
278+ [selectedKeys, setSelectedKeys],
230279 );
231280
232281 const clearSelection = useCallback(() => {
@@ -280,14 +329,11 @@ export function ToolExplorer({ · 47 unmodified lines
280329 setFocusedSourceName(null);
281330 setFormSource(null);
282331 void maybeLoadToolDetails(tool, true);
283− }, [maybeLoadToolDetails]);
332+ }, [maybeLoadToolDetails, setFocusedSourceName]);
284333
285334 const handleSourceClick = useCallback((sourceName: string) => {
286335 const source = sourceByName.get(sourceName);
287− setFocusedSourceName(sourceName);
288− setFocusedToolPath(null);
289− // Open the source form panel directly — no separate "view" mode
290− setFormSource(source ?? null);
336+ setSourceFocusState(sourceName, source ?? null);
291337 // Ensure the source group is expanded
292338 setExpandedKeys((prev) => {
293339 const key = `source:${sourceName}`;
@@ -299,10 +345,9 @@ export function ToolExplorer({ · 5 unmodified lines
299345 }, [sourceByName]);
300346
301347 const handleAddSource = useCallback(() => {
348+ setSourceFocusState(null, null);
302349 setFormSource("new");
303− setFocusedToolPath(null);
304− setFocusedSourceName(null);
305− }, []);
350+ }, [setFocusedSourceName, setSourceFocusState]);
306351
307352 const handleSourceFormClose = useCallback(() => {
308353 setFormSource(null);
@@ -311,10 +356,8 @@ export function ToolExplorer({ · 2 unmodified lines
311356 const handleSourceFormAdded = useCallback((source: ToolSourceRecord) => {
312357 onSourceAdded?.(source);
313358 // After adding, show the new source in the form
314− setFormSource(null);
315− setFocusedSourceName(source.name);
316− setFocusedToolPath(null);
317− }, [onSourceAdded]);
359+ setSourceFocusState(source.name, source);
360+ }, [onSourceAdded, setSourceFocusState]);
318361
319362 const focusedTool = useMemo(() => {
320363 if (!focusedToolPath) return null;
@@ -328,7 +371,7 @@ export function ToolExplorer({ · 7 unmodified lines
328371
329372 // Auto-focus first tool when tools arrive
330373 useEffect(() => {
331− if (!focusedToolPath && searchedTools.length > 0) {
374+ if (!focusedToolPath && !focusedSourceName && searchedTools.length > 0) {
332375 const first = searchedTools[0];
333376 setFocusedToolPath(first.path);
334377 void maybeLoadToolDetails(first, true);
@@ -336,6 +379,16 @@ export function ToolExplorer({ · 1 unmodified line
336379 // eslint-disable-next-line react-hooks/exhaustive-deps
337380 }, [searchedTools.length > 0]);
338381
382+ useEffect(() => {
383+ if (!focusedToolPath || searchedTools.length === 0) {
384+ return;
385+ }
386+
387+ if (!searchedTools.some((tool) => tool.path === focusedToolPath)) {
388+ setFocusedToolPath(null);
389+ }
390+ }, [focusedToolPath, searchedTools]);
391+
339392 const loadingRows = useMemo(() => {
340393 if (search.length > 0) return [];
341394 const visibleLoadingSources = loadingSourceSet.size === 0
executor/apps/web/src/components/tools/policies.tsx+97−91
@@ -53,9 +53,9 @@ import type { · 52 unmodified lines
5353 ArgumentCondition,
5454 ArgumentConditionOperator,
5555 ToolDescriptor,
56− ToolRoleBindingRecord,
57− ToolRoleRuleRecord,
58− ToolRoleRecord,
56+ ToolPolicyAssignmentRecord,
57+ ToolPolicyRuleRecord,
58+ ToolPolicySetRecord,
5959 } from "@/lib/types";
6060 import type { Id } from "@executor/database/convex/_generated/dataModel";
6161 import { workspaceQueryArgs } from "@/lib/workspace/query-args";
@@ -94,20 +94,20 @@ interface RoleFormState { · 32 unmodified lines
9494 }
9595
9696 interface RoleRuleFormState {
97− selectorType: ToolRoleRuleRecord["selectorType"];
97+ selectorType: ToolPolicyRuleRecord["selectorType"];
9898 sourceKey: string;
9999 resourcePattern: string;
100− matchType: ToolRoleRuleRecord["matchType"];
101− effect: ToolRoleRuleRecord["effect"];
102− approvalMode: ToolRoleRuleRecord["approvalMode"];
100+ matchType: ToolPolicyRuleRecord["matchType"];
101+ effect: ToolPolicyRuleRecord["effect"];
102+ approvalMode: ToolPolicyRuleRecord["approvalMode"];
103103 priority: string;
104104 }
105105
106106 interface RoleBindingFormState {
107− scopeType: ToolRoleBindingRecord["scopeType"];
107+ scopeType: ToolPolicyAssignmentRecord["scopeType"];
108108 targetAccountId: string;
109109 clientId: string;
110− status: ToolRoleBindingRecord["status"];
110+ status: ToolPolicyAssignmentRecord["status"];
111111 }
112112
113113 interface OrganizationMemberListItem {
@@ -343,7 +343,7 @@ function isDirectToolPolicy(policy: ToolPolicyRecord): boolean { · 229 unmodified lines
343343 );
344344 }
345345
346−function roleRulePattern(rule: ToolRoleRuleRecord): string {
346+function roleRulePattern(rule: ToolPolicyRuleRecord): string {
347347 if (rule.selectorType === "all") return "*";
348348 if (rule.selectorType === "source") return rule.sourceKey ?? "";
349349 if (rule.selectorType === "namespace") return rule.namespacePattern ?? "";
@@ -837,7 +837,7 @@ function PolicyCard({ · 487 unmodified lines
837837 </button>
838838 </TooltipTrigger>
839839 <TooltipContent side="left" className="text-xs">
840− {canDelete ? "Delete policy" : "Role-managed policy (delete from roles)"}
840+ {canDelete ? "Delete policy" : "Policy-set-managed policy (delete from policy sets)"}
841841 </TooltipContent>
842842 </Tooltip>
843843 </TooltipProvider>
@@ -869,12 +869,12 @@ export function PoliciesPanel({ · 25 unmodified lines
869869
870870 const listArgs = workspaceQueryArgs(context);
871871 const policiesQuery = useQuery(convexApi.workspace.listToolPolicies, listArgs);
872− const upsertToolRole = useMutation(convexApi.workspace.upsertToolRole);
873− const upsertToolRoleRule = useMutation(convexApi.workspace.upsertToolRoleRule);
874− const upsertToolRoleBinding = useMutation(convexApi.workspace.upsertToolRoleBinding);
875− const deleteToolRole = useMutation(convexApi.workspace.deleteToolRole);
876− const deleteToolRoleRule = useMutation(convexApi.workspace.deleteToolRoleRule);
877− const deleteToolRoleBinding = useMutation(convexApi.workspace.deleteToolRoleBinding);
872+ const upsertToolPolicySet = useMutation(convexApi.workspace.upsertToolPolicySet);
873+ const upsertToolPolicyRule = useMutation(convexApi.workspace.upsertToolPolicyRule);
874+ const upsertToolPolicyAssignment = useMutation(convexApi.workspace.upsertToolPolicyAssignment);
875+ const deleteToolPolicySet = useMutation(convexApi.workspace.deleteToolPolicySet);
876+ const deleteToolPolicyRule = useMutation(convexApi.workspace.deleteToolPolicyRule);
877+ const deleteToolPolicyAssignment = useMutation(convexApi.workspace.deleteToolPolicyAssignment);
878878
879879 const loading = Boolean(context) && policiesQuery === undefined;
880880 const policies = useMemo(() => (policiesQuery ?? []) as ToolPolicyRecord[], [policiesQuery]);
@@ -902,10 +902,10 @@ export function PoliciesPanel({ · 21 unmodified lines
902902 const canManageRoles = selfMembership?.role === "owner" || selfMembership?.role === "admin";
903903
904904 const rolesQuery = useQuery(
905− convexApi.workspace.listToolRoles,
905+ convexApi.workspace.listToolPolicySets,
906906 context && canManageRoles ? listArgs : "skip",
907907 );
908− const roleItems = useMemo(() => ((rolesQuery ?? []) as ToolRoleRecord[]), [rolesQuery]);
908+ const roleItems = useMemo(() => ((rolesQuery ?? []) as ToolPolicySetRecord[]), [rolesQuery]);
909909 const activeRoleId = useMemo(() => {
910910 if (!canManageRoles) {
911911 return null;
@@ -919,13 +919,13 @@ export function PoliciesPanel({ · 7 unmodified lines
919919 }, [canManageRoles, roleItems, selectedRoleId]);
920920
921921 const bindingsQuery = useQuery(
922− convexApi.workspace.listToolRoleBindings,
922+ convexApi.workspace.listToolPolicyAssignments,
923923 context && canManageRoles ? listArgs : "skip",
924924 );
925− const bindingItems = useMemo(() => ((bindingsQuery ?? []) as ToolRoleBindingRecord[]), [bindingsQuery]);
925+ const bindingItems = useMemo(() => ((bindingsQuery ?? []) as ToolPolicyAssignmentRecord[]), [bindingsQuery]);
926926
927927 const roleRulesQuery = useQuery(
928− convexApi.workspace.listToolRoleRules,
928+ convexApi.workspace.listToolPolicyRules,
929929 context && canManageRoles && activeRoleId
930930 ? {
931931 workspaceId: context.workspaceId,
@@ -935,7 +935,7 @@ export function PoliciesPanel({ · 3 unmodified lines
935935 : "skip",
936936 );
937937 const selectedRoleRules = useMemo(
938− () => ((roleRulesQuery ?? []) as ToolRoleRuleRecord[]),
938+ () => ((roleRulesQuery ?? []) as ToolPolicyRuleRecord[]),
939939 [roleRulesQuery],
940940 );
941941
@@ -1024,7 +1024,7 @@ export function PoliciesPanel({ · 82 unmodified lines
10241024 : "organization" as const;
10251025 const targetAccountId = form.scope === "personal" ? context.accountId : undefined;
10261026
1027− await upsertToolRole({
1027+ await upsertToolPolicySet({
10281028 workspaceId: context.workspaceId,
10291029 sessionId: context.sessionId,
10301030 id: roleId,
@@ -1033,7 +1033,7 @@ export function PoliciesPanel({ · 2 unmodified lines
10331033 });
10341034
10351035 try {
1036− await upsertToolRoleRule({
1036+ await upsertToolPolicyRule({
10371037 workspaceId: context.workspaceId,
10381038 sessionId: context.sessionId,
10391039 id: ruleId,
@@ -1048,7 +1048,7 @@ export function PoliciesPanel({ · 8 unmodified lines
10481048 priority,
10491049 });
10501050
1051− await upsertToolRoleBinding({
1051+ await upsertToolPolicyAssignment({
10521052 workspaceId: context.workspaceId,
10531053 sessionId: context.sessionId,
10541054 id: bindingId,
@@ -1060,7 +1060,7 @@ export function PoliciesPanel({ · 5 unmodified lines
10601060 });
10611061 } catch (error) {
10621062 try {
1063− await deleteToolRole({
1063+ await deleteToolPolicySet({
10641064 workspaceId: context.workspaceId,
10651065 sessionId: context.sessionId,
10661066 roleId,
@@ -1084,7 +1084,7 @@ export function PoliciesPanel({ · 17 unmodified lines
10841084 const handleDelete = useCallback(async (policy: ToolPolicyRecord) => {
10851085 if (!context) return;
10861086 if (!isDirectToolPolicy(policy)) {
1087− toast.error("This policy is managed by role bindings. Use role management to remove it.");
1087+ toast.error("This policy is managed by a policy set assignment. Use the Policy Set Manager to remove it.");
10881088 return;
10891089 }
10901090
@@ -1095,7 +1095,7 @@ export function PoliciesPanel({ · 4 unmodified lines
10951095
10961096 setDeletingId(policy.id);
10971097 try {
1098− await deleteToolRole({
1098+ await deleteToolPolicySet({
10991099 workspaceId: context.workspaceId,
11001100 sessionId: context.sessionId,
11011101 roleId: policy.roleId,
@@ -1106,7 +1106,7 @@ export function PoliciesPanel({ · 4 unmodified lines
11061106 } finally {
11071107 setDeletingId(null);
11081108 }
1109− }, [context, deleteToolRole]);
1109+ }, [context, deleteToolPolicySet]);
11101110
11111111 const handleCreateRole = useCallback(async () => {
11121112 if (!context) return;
@@ -1118,7 +1118,7 @@ export function PoliciesPanel({ · 5 unmodified lines
11181118
11191119 setBusyRoleAction("create-role");
11201120 try {
1121− const role = await upsertToolRole({
1121+ const role = await upsertToolPolicySet({
11221122 workspaceId: context.workspaceId,
11231123 sessionId: context.sessionId,
11241124 name,
@@ -1132,7 +1132,7 @@ export function PoliciesPanel({ · 7 unmodified lines
11321132 } finally {
11331133 setBusyRoleAction(null);
11341134 }
1135− }, [context, roleForm, upsertToolRole]);
1135+ }, [context, roleForm, upsertToolPolicySet]);
11361136
11371137 const handleCreateRoleRule = useCallback(async () => {
11381138 if (!context || !activeRoleId) return;
@@ -1157,7 +1157,7 @@ export function PoliciesPanel({ · 18 unmodified lines
11571157
11581158 setBusyRoleAction("create-rule");
11591159 try {
1160− await upsertToolRoleRule({
1160+ await upsertToolPolicyRule({
11611161 workspaceId: context.workspaceId,
11621162 sessionId: context.sessionId,
11631163 roleId: activeRoleId,
@@ -1176,7 +1176,7 @@ export function PoliciesPanel({ · 12 unmodified lines
11761176 } finally {
11771177 setBusyRoleAction(null);
11781178 }
1179− }, [activeRoleId, context, roleRuleForm, upsertToolRoleRule]);
1179+ }, [activeRoleId, context, roleRuleForm, upsertToolPolicyRule]);
11801180
11811181 const handleCreateRoleBinding = useCallback(async () => {
11821182 if (!context || !activeRoleId) return;
@@ -1190,7 +1190,7 @@ export function PoliciesPanel({ · 7 unmodified lines
11901190
11911191 setBusyRoleAction("create-binding");
11921192 try {
1193− await upsertToolRoleBinding({
1193+ await upsertToolPolicyAssignment({
11941194 workspaceId: context.workspaceId,
11951195 sessionId: context.sessionId,
11961196 roleId: activeRoleId,
@@ -1208,14 +1208,14 @@ export function PoliciesPanel({ · 11 unmodified lines
12081208 } finally {
12091209 setBusyRoleAction(null);
12101210 }
1211− }, [activeRoleId, context, roleBindingForm, upsertToolRoleBinding]);
1211+ }, [activeRoleId, context, roleBindingForm, upsertToolPolicyAssignment]);
12121212
12131213 const handleDeleteRole = useCallback(async (roleId: string) => {
12141214 if (!context) return;
12151215
12161216 setBusyRoleAction(`delete-role:${roleId}`);
12171217 try {
1218− await deleteToolRole({
1218+ await deleteToolPolicySet({
12191219 workspaceId: context.workspaceId,
12201220 sessionId: context.sessionId,
12211221 roleId,
@@ -1229,14 +1229,14 @@ export function PoliciesPanel({ · 7 unmodified lines
12291229 } finally {
12301230 setBusyRoleAction(null);
12311231 }
1232− }, [activeRoleId, context, deleteToolRole]);
1232+ }, [activeRoleId, context, deleteToolPolicySet]);
12331233
12341234 const handleDeleteRule = useCallback(async (ruleId: string) => {
12351235 if (!context || !activeRoleId) return;
12361236
12371237 setBusyRoleAction(`delete-rule:${ruleId}`);
12381238 try {
1239− await deleteToolRoleRule({
1239+ await deleteToolPolicyRule({
12401240 workspaceId: context.workspaceId,
12411241 sessionId: context.sessionId,
12421242 roleId: activeRoleId,
@@ -1248,14 +1248,14 @@ export function PoliciesPanel({ · 5 unmodified lines
12481248 } finally {
12491249 setBusyRoleAction(null);
12501250 }
1251− }, [activeRoleId, context, deleteToolRoleRule]);
1251+ }, [activeRoleId, context, deleteToolPolicyRule]);
12521252
12531253 const handleDeleteBinding = useCallback(async (bindingId: string) => {
12541254 if (!context) return;
12551255
12561256 setBusyRoleAction(`delete-binding:${bindingId}`);
12571257 try {
1258− await deleteToolRoleBinding({
1258+ await deleteToolPolicyAssignment({
12591259 workspaceId: context.workspaceId,
12601260 sessionId: context.sessionId,
12611261 bindingId,
@@ -1266,7 +1266,7 @@ export function PoliciesPanel({ · 4 unmodified lines
12661266 } finally {
12671267 setBusyRoleAction(null);
12681268 }
1269− }, [context, deleteToolRoleBinding]);
1269+ }, [context, deleteToolPolicyAssignment]);
12701270
12711271 // ── Group policies by decision for display ──
12721272
@@ -1305,7 +1305,7 @@ export function PoliciesPanel({ · 32 unmodified lines
13051305 disabled={!context}
13061306 >
13071307 <Plus className="h-3.5 w-3.5" />
1308− New Policy
1308+ Quick Policy
13091309 </Button>
13101310 </div>
13111311
@@ -1375,28 +1375,31 @@ export function PoliciesPanel({ · 63 unmodified lines
13751375
13761376 <Separator className="bg-border/40" />
13771377
1378− <div className="space-y-3">
1379− <div className="flex items-center justify-between">
1378+ <details className="group rounded-lg border border-border/60 bg-card/40">
1379+ <summary className="flex cursor-pointer list-none items-center justify-between gap-3 px-3 py-2.5">
13801380 <div>
1381− <h4 className="text-sm font-medium">Role Manager</h4>
1382− <p className="text-[11px] text-muted-foreground mt-1">
1383− Manage reusable tool roles, rule selectors, and scoped bindings.
1381+ <h4 className="text-sm font-medium">Policy Set Manager</h4>
1382+ <p className="text-[11px] text-muted-foreground mt-0.5">
1383+ Advanced: reusable policy sets with rules and assignments.
13841384 </p>
13851385 </div>
1386− </div>
1387−
1388− {!canManageRoles ? (
1389− <div className="rounded-lg border border-dashed border-border/50 p-4 text-xs text-muted-foreground">
1390− Organization owner/admin role required to manage tool roles.
1391− </div>
1392− ) : (
1393− <div className="grid grid-cols-1 gap-4 lg:grid-cols-[280px_1fr]">
1386+ <span className="text-[10px] text-muted-foreground group-open:hidden">Expand</span>
1387+ <span className="text-[10px] text-muted-foreground hidden group-open:inline">Collapse</span>
1388+ </summary>
1389+
1390+ <div className="px-3 pb-3">
1391+ {!canManageRoles ? (
1392+ <div className="rounded-lg border border-dashed border-border/50 p-4 text-xs text-muted-foreground">
1393+ Organization owner/admin role required to manage tool policy sets.
1394+ </div>
1395+ ) : (
1396+ <div className="grid grid-cols-1 gap-4 lg:grid-cols-[280px_1fr]">
13941397 <div className="space-y-3 rounded-lg border border-border/60 bg-card p-3">
1395− <Label className="text-xs text-muted-foreground">Create role</Label>
1398+ <Label className="text-xs text-muted-foreground">Create policy set</Label>
13961399 <Input
13971400 value={roleForm.name}
13981401 onChange={(event) => setRoleForm((state) => ({ ...state, name: event.target.value }))}
1399− placeholder="Role name"
1402+ placeholder="Policy set name"
14001403 className="h-8 text-xs"
14011404 />
14021405 <Input
@@ -1411,14 +1414,14 @@ export function PoliciesPanel({ · 8 unmodified lines
14111414 onClick={handleCreateRole}
14121415 disabled={busyRoleAction === "create-role"}
14131416 >
1414− {busyRoleAction === "create-role" ? "Creating..." : "Create role"}
1417+ {busyRoleAction === "create-role" ? "Creating..." : "Create policy set"}
14151418 </Button>
14161419
14171420 <Separator className="bg-border/40" />
14181421
14191422 <div className="space-y-1.5 max-h-[320px] overflow-y-auto">
14201423 {roleItems.length === 0 ? (
1421− <p className="text-[11px] text-muted-foreground">No roles yet.</p>
1424+ <p className="text-[11px] text-muted-foreground">No policy sets yet.</p>
14221425 ) : (
14231426 roleItems.map((role) => {
14241427 const bindingCount = bindingItems.filter((binding) => binding.roleId === role.id).length;
@@ -1429,7 +1432,7 @@ export function PoliciesPanel({ · 4 unmodified lines
14291432 onClick={() => setSelectedRoleId(role.id)}
14301433 className={cn(
14311434 "w-full rounded-md border px-2 py-1.5 text-left",
1432− selectedRoleId === role.id
1435+ activeRoleId === role.id
14331436 ? "border-primary/40 bg-primary/5"
14341437 : "border-border/40 hover:bg-muted/30",
14351438 )}
@@ -1437,7 +1440,7 @@ export function PoliciesPanel({ · 1 unmodified line
14371440 <div className="flex items-center justify-between gap-2">
14381441 <span className="truncate text-xs font-medium">{role.name}</span>
14391442 <Badge variant="outline" className="text-[9px] px-1.5 py-0">
1440− {bindingCount} bind
1443+ {bindingCount} assign
14411444 </Badge>
14421445 </div>
14431446 {role.description ? (
@@ -1452,7 +1455,7 @@ export function PoliciesPanel({ · 8 unmodified lines
14521455
14531456 <div className="space-y-4 rounded-lg border border-border/60 bg-card p-4">
14541457 {!selectedRole ? (
1455− <p className="text-xs text-muted-foreground">Select a role to manage its rules and bindings.</p>
1458+ <p className="text-xs text-muted-foreground">Select a policy set to manage its rules and assignments.</p>
14561459 ) : (
14571460 <>
14581461 <div className="flex items-start justify-between gap-3">
@@ -1470,7 +1473,7 @@ export function PoliciesPanel({ · 11 unmodified lines
14701473 onClick={() => handleDeleteRole(selectedRole.id)}
14711474 disabled={busyRoleAction === `delete-role:${selectedRole.id}`}
14721475 >
1473− {busyRoleAction === `delete-role:${selectedRole.id}` ? "Deleting..." : "Delete role"}
1476+ {busyRoleAction === `delete-role:${selectedRole.id}` ? "Deleting..." : "Delete set"}
14741477 </Button>
14751478 </div>
14761479
@@ -1520,7 +1523,7 @@ export function PoliciesPanel({ · 43 unmodified lines
15201523 value={roleRuleForm.selectorType}
15211524 onValueChange={(value) => setRoleRuleForm((state) => ({
15221525 ...state,
1523− selectorType: value as ToolRoleRuleRecord["selectorType"],
1526+ selectorType: value as ToolPolicyRuleRecord["selectorType"],
15241527 }))}
15251528 >
15261529 <SelectTrigger className="h-8 text-xs">
@@ -1564,7 +1567,7 @@ export function PoliciesPanel({ · 37 unmodified lines
15641567 value={roleRuleForm.effect}
15651568 onValueChange={(value) => setRoleRuleForm((state) => ({
15661569 ...state,
1567− effect: value as ToolRoleRuleRecord["effect"],
1570+ effect: value as ToolPolicyRuleRecord["effect"],
15681571 }))}
15691572 >
15701573 <SelectTrigger className="h-8 text-xs">
@@ -1580,7 +1583,7 @@ export function PoliciesPanel({ · 9 unmodified lines
15801583 value={roleRuleForm.approvalMode}
15811584 onValueChange={(value) => setRoleRuleForm((state) => ({
15821585 ...state,
1583− approvalMode: value as ToolRoleRuleRecord["approvalMode"],
1586+ approvalMode: value as ToolPolicyRuleRecord["approvalMode"],
15841587 }))}
15851588 >
15861589 <SelectTrigger className="h-8 text-xs">
@@ -1615,7 +1618,7 @@ export function PoliciesPanel({ · 28 unmodified lines
16151618
16161619 <div className="space-y-2">
16171620 <div className="flex items-center justify-between">
1618− <Label className="text-xs text-muted-foreground">Bindings</Label>
1621+ <Label className="text-xs text-muted-foreground">Assignments</Label>
16191622 <span className="text-[10px] text-muted-foreground">{selectedRoleBindings.length}</span>
16201623 </div>
16211624 <div className="space-y-1.5 max-h-[200px] overflow-y-auto">
@@ -1657,7 +1660,7 @@ export function PoliciesPanel({ · 35 unmodified lines
16571660 value={roleBindingForm.scopeType}
16581661 onValueChange={(value) => setRoleBindingForm((state) => ({
16591662 ...state,
1660− scopeType: value as ToolRoleBindingRecord["scopeType"],
1663+ scopeType: value as ToolPolicyAssignmentRecord["scopeType"],
16611664 }))}
16621665 >
16631666 <SelectTrigger className="h-8 text-xs">
@@ -1699,7 +1702,7 @@ export function PoliciesPanel({ · 35 unmodified lines
16991702 value={roleBindingForm.status}
17001703 onValueChange={(value) => setRoleBindingForm((state) => ({
17011704 ...state,
1702− status: value as ToolRoleBindingRecord["status"],
1705+ status: value as ToolPolicyAssignmentRecord["status"],
17031706 }))}
17041707 >
17051708 <SelectTrigger className="h-8 text-xs">
@@ -1717,7 +1720,7 @@ export function PoliciesPanel({ · 11 unmodified lines
17171720 onClick={handleCreateRoleBinding}
17181721 disabled={busyRoleAction === "create-binding"}
17191722 >
1720− {busyRoleAction === "create-binding" ? "Adding binding..." : "Add binding"}
1723+ {busyRoleAction === "create-binding" ? "Adding assignment..." : "Add assignment"}
17211724 </Button>
17221725 </div>
17231726 </div>
@@ -1725,8 +1728,9 @@ export function PoliciesPanel({ · 1 unmodified line
17251728 )}
17261729 </div>
17271730 </div>
1728− )}
1729− </div>
1731+ )}
1732+ </div>
1733+ </details>
17301734
17311735 {/* Create Policy Dialog */}
17321736 <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
@@ -1924,26 +1928,28 @@ export function PoliciesPanel({ · 191 unmodified lines
19241928 />
19251929 </div>
19261930
1927− {/* Advanced filters */}
1928− <div className="space-y-3 rounded-md border border-border/40 bg-muted/10 p-3">
1929− <div className="space-y-1">
1930− <Label className="text-xs text-muted-foreground">Client ID</Label>
1931− <Input
1932− value={form.clientId}
1933− onChange={(event) => setForm((state) => ({ ...state, clientId: event.target.value }))}
1934− placeholder="optional client identifier"
1935− className="h-8 text-xs font-mono bg-background"
1931+ <details className="rounded-md border border-border/40 bg-muted/10 p-3">
1932+ <summary className="cursor-pointer text-xs text-muted-foreground">Optional filters</summary>
1933+ <div className="mt-3 space-y-3">
1934+ <div className="space-y-1">
1935+ <Label className="text-xs text-muted-foreground">Client ID</Label>
1936+ <Input
1937+ value={form.clientId}
1938+ onChange={(event) => setForm((state) => ({ ...state, clientId: event.target.value }))}
1939+ placeholder="optional client identifier"
1940+ className="h-8 text-xs font-mono bg-background"
1941+ />
1942+ <p className="text-[10px] text-muted-foreground/60 leading-tight">
1943+ When set, this policy only applies to tool calls from this client ID.
1944+ </p>
1945+ </div>
1946+
1947+ <ArgumentConditionsEditor
1948+ conditions={form.argumentConditions}
1949+ onChange={(argumentConditions) => setForm((state) => ({ ...state, argumentConditions }))}
19361950 />
1937− <p className="text-[10px] text-muted-foreground/60 leading-tight">
1938− When set, this policy only applies to tool calls from this client ID.
1939− </p>
19401951 </div>
1941−
1942− <ArgumentConditionsEditor
1943− conditions={form.argumentConditions}
1944− onChange={(argumentConditions) => setForm((state) => ({ ...state, argumentConditions }))}
1945− />
1946− </div>
1952+ </details>
19471953 </div>
19481954
19491955 <DialogFooter>
executor/apps/web/src/components/tools/view.tsx+154−35
@@ -1,6 +1,8 @@
11 "use client";
22
3−import { useCallback, useMemo, useRef, useState } from "react";
3+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4+import { parseAsNativeArrayOf, parseAsString, parseAsStringLiteral, useQueryStates } from "nuqs";
5+import { useLocation, useNavigate } from "@/lib/router";
46 import { Card, CardContent } from "@/components/ui/card";
57 import { Skeleton } from "@/components/ui/skeleton";
68 import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -27,6 +29,7 @@ import { · 20 unmodified lines
2729 import { sourceLabel } from "@/lib/tool/source-utils";
2830 import { workspaceQueryArgs } from "@/lib/workspace/query-args";
2931 import type { SourceDialogMeta } from "@/components/tools/add/source-dialog";
32+import type { FilterApproval } from "@/components/tools/explorer-derived";
3033
3134 // ── Optimistic source helpers ──
3235
@@ -80,34 +83,87 @@ function pruneStaleOps( · 47 unmodified lines
8083 });
8184 }
8285
83−type ToolsTab = "catalog" | "credentials" | "policies" | "editor";
86+type ToolsTab = "catalog" | "connections" | "policies" | "editor";
8487 const INVENTORY_REGENERATION_TOAST_ID = "tool-inventory-regeneration";
8588
86−function parseInitialTab(tab?: string | null): ToolsTab {
87− if (tab === "runner" || tab === "editor") {
88− return "editor";
89+function parseToolsTab(pathname: string): ToolsTab {
90+ const segments = pathname.split("/").filter(Boolean);
91+ const last = segments.at(-1);
92+
93+ if (last === "catalog") {
94+ return "catalog";
8995 }
90− if (tab === "catalog" || tab === "credentials") {
91− return tab;
96+ if (last === "connections" || last === "credentials") {
97+ return "connections";
9298 }
93− if (tab === "policies") {
99+ if (last === "policies") {
94100 return "policies";
95101 }
102+ if (last === "editor" || last === "runner") {
103+ return "editor";
104+ }
105+
96106 return "catalog";
97107 }
98108
109+function normalizeCatalogApprovalFilter(value: string | null): FilterApproval {
110+ if (value === "required" || value === "auto") {
111+ return value;
112+ }
113+
114+ return "all";
115+}
116+
117+function toolsPathFromTab(tab: ToolsTab): string {
118+ if (tab === "catalog") {
119+ return "/tools/catalog";
120+ }
121+ if (tab === "connections") {
122+ return "/tools/connections";
123+ }
124+ if (tab === "policies") {
125+ return "/tools/policies";
126+ }
127+ return "/tools/editor";
128+}
129+
130+function mapEmptyQueryValueToNull(value: string): string | null {
131+ return value.length > 0 ? value : null;
132+}
133+
99134 // ── Tools View ──
100135
101−export function ToolsView({
102− initialSource,
103− initialTab,
104−}: {
105− initialSource?: string | null;
106− initialTab?: string | null;
107−}) {
136+export function ToolsView() {
137+ const navigate = useNavigate();
138+ const location = useLocation();
108139 const { context, loading: sessionLoading } = useSession();
109− const [selectedSource, setSelectedSource] = useState<string | null>(initialSource ?? null);
110− const [activeTab, setActiveTab] = useState<ToolsTab>(parseInitialTab(initialTab));
140+ const activeTab = useMemo(() => parseToolsTab(location.pathname), [location.pathname]);
141+ const [catalogQueryState, setCatalogQueryState] = useQueryStates({
142+ q: parseAsString.withDefault(""),
143+ approval: parseAsStringLiteral(["all", "required", "auto"]).withDefault("all"),
144+ tool: parseAsString.withDefault(""),
145+ selected: parseAsNativeArrayOf(parseAsString),
146+ source: parseAsString.withDefault(""),
147+ sourcePanel: parseAsString.withDefault(""),
148+ }, {
149+ history: "replace",
150+ });
151+
152+ const catalogSearchValue = catalogQueryState.q;
153+ const catalogFilterValue = useMemo(
154+ () => normalizeCatalogApprovalFilter(catalogQueryState.approval),
155+ [catalogQueryState.approval],
156+ );
157+ const catalogSourceValue = useMemo(() => mapEmptyQueryValueToNull(catalogQueryState.source), [catalogQueryState.source]);
158+ const catalogActiveToolPath = mapEmptyQueryValueToNull(catalogQueryState.tool);
159+ const catalogSelectedToolPaths = useMemo(
160+ () => catalogQueryState.selected ?? [],
161+ [catalogQueryState.selected],
162+ );
163+ const catalogFocusedSourceName = useMemo(
164+ () => mapEmptyQueryValueToNull(catalogQueryState.sourcePanel),
165+ [catalogQueryState.sourcePanel],
166+ );
111167 const [connectionDialogOpen, setConnectionDialogOpen] = useState(false);
112168 const [connectionDialogEditing, setConnectionDialogEditing] = useState<CredentialRecord | null>(null);
113169 const [connectionDialogSourceKey, setConnectionDialogSourceKey] = useState<string | null>(null);
@@ -316,6 +372,7 @@ export function ToolsView({ · 202 unmodified lines
316372
317373 const existingSourceNames = useMemo(() => new Set(sourceItems.map((source) => source.name)), [sourceItems]);
318374 const warningsBySource = useMemo(() => warningsBySourceName(warnings), [warnings]);
375+
319376 const sourceDialogMeta = useMemo(() => {
320377 const bySource: Record<string, SourceDialogMeta> = {};
321378 for (const source of sourceItems) {
@@ -328,10 +385,10 @@ export function ToolsView({ · 6 unmodified lines
328385 }
329386 return bySource;
330387 }, [sourceItems, sourceQuality, refreshingTools, warningsBySource]);
331− const activeSource = selectedSource
332− && (sourceItems.some((source) => source.name === selectedSource) || toolSourceNames.has(selectedSource))
333− ? selectedSource
334− : null;
388+ const activeSource = catalogSourceValue
389+ && (sourceItems.some((source) => source.name === catalogSourceValue) || toolSourceNames.has(catalogSourceValue))
390+ ? catalogSourceValue
391+ : null;
335392
336393 const handleSourceAdded = useCallback((source: ToolSourceRecord) => {
337394 setOptimisticOps((ops) => [
@@ -340,13 +397,67 @@ export function ToolsView({ · 2 unmodified lines
340397 ]);
341398 }, []);
342399
400+ const syncSourceToUrl = useCallback((sourceName: string) => {
401+ void setCatalogQueryState({
402+ source: sourceName,
403+ }, {
404+ history: "replace",
405+ });
406+ }, [setCatalogQueryState]);
407+
343408 const handleSourceDeleted = useCallback((sourceName: string) => {
344409 setOptimisticOps((ops) => [
345410 ...ops,
346411 { kind: "remove", sourceName, removedAt: Date.now() },
347412 ]);
348− setSelectedSource((current) => (current === sourceName ? null : current));
349− }, []);
413+ syncSourceToUrl(activeSource === sourceName ? "" : (activeSource ?? ""));
414+ }, [activeSource, syncSourceToUrl]);
415+
416+ const setCatalogSearch = useCallback((search: string) => {
417+ void setCatalogQueryState({
418+ q: search,
419+ }, {
420+ history: "replace",
421+ });
422+ }, [setCatalogQueryState]);
423+
424+ const setCatalogApprovalFilter = useCallback((filterApproval: FilterApproval) => {
425+ void setCatalogQueryState({
426+ approval: filterApproval,
427+ }, {
428+ history: "replace",
429+ });
430+ }, [setCatalogQueryState]);
431+
432+ const setCatalogActiveToolPath = useCallback((toolPath: string | null) => {
433+ void setCatalogQueryState({
434+ tool: toolPath ?? "",
435+ }, {
436+ history: "replace",
437+ });
438+ }, [setCatalogQueryState]);
439+
440+ const setCatalogSelectedToolPaths = useCallback((toolPaths: string[]) => {
441+ void setCatalogQueryState({
442+ selected: toolPaths,
443+ }, {
444+ history: "replace",
445+ });
446+ }, [setCatalogQueryState]);
447+
448+ const setCatalogFocusedSourceName = useCallback((sourceName: string | null) => {
449+ void setCatalogQueryState({
450+ sourcePanel: sourceName ?? "",
451+ }, {
452+ history: "replace",
453+ });
454+ }, [setCatalogQueryState]);
455+
456+ const setActiveTab = useCallback((nextTab: ToolsTab) => {
457+ const nextPath = toolsPathFromTab(nextTab);
458+ const qs = location.searchStr;
459+ navigate(qs ? `${nextPath}?${qs}` : nextPath);
460+ }, [location.searchStr, navigate]);
350461
351462 const handleRegenerateInventory = useCallback(async () => {
352463 if (!context || regenerationInFlight) {
@@ -442,7 +553,7 @@ export function ToolsView({ · 89 unmodified lines
442553 {loadingTools ? "..." : totalTools}
443554 </span>
444555 </TabsTrigger>
445− <TabsTrigger value="credentials" className="text-xs data-[state=active]:bg-background">
556+ <TabsTrigger value="connections" className="text-xs data-[state=active]:bg-background">
446557 Connections
447558 {credentials && (
448559 <span className="ml-1.5 text-[10px] font-mono text-muted-foreground">
@@ -478,16 +589,24 @@ export function ToolsView({ · 29 unmodified lines
478589 sourceHasMoreTools={sourceHasMoreTools}
479590 sourceLoadingMoreTools={sourceLoadingMoreTools}
480591 onLoadMoreToolsForSource={loadMoreToolsForSource}
481− loading={loadingTools}
482− sourceDialogMeta={sourceDialogMeta}
483− sourceAuthProfiles={sourceAuthProfiles}
484− existingSourceNames={existingSourceNames}
485− onSourceDeleted={handleSourceDeleted}
486− onLoadToolDetails={loadToolDetails}
487− warnings={warnings}
488− initialSource={initialSource}
489− activeSource={activeSource}
490− onActiveSourceChange={setSelectedSource}
592+ loading={loadingTools}
593+ sourceDialogMeta={sourceDialogMeta}
594+ sourceAuthProfiles={sourceAuthProfiles}
595+ existingSourceNames={existingSourceNames}
596+ onSourceDeleted={handleSourceDeleted}
597+ onLoadToolDetails={loadToolDetails}
598+ warnings={warnings}
599+ activeSource={activeSource}
600+ searchValue={catalogSearchValue}
601+ filterApprovalValue={catalogFilterValue}
602+ focusedToolPathValue={catalogActiveToolPath}
603+ selectedToolPathsValue={catalogSelectedToolPaths}
604+ focusedSourceNameValue={catalogFocusedSourceName}
605+ onSearchValueChange={setCatalogSearch}
606+ onFilterApprovalValueChange={setCatalogApprovalFilter}
607+ onFocusedToolPathChange={setCatalogActiveToolPath}
608+ onSelectedToolPathsChange={setCatalogSelectedToolPaths}
609+ onFocusedSourceNameChange={setCatalogFocusedSourceName}
491610 onRegenerate={handleRegenerateInventory}
492611 inventoryState={inventoryStatus?.state}
493612 inventoryError={inventoryStatus?.error}
@@ -503,7 +622,7 @@ export function ToolsView({ · 9 unmodified lines
503622 </Card>
504623 </TabsContent>
505624
506− <TabsContent value="credentials" className="mt-4">
625+ <TabsContent value="connections" className="mt-4">
507626 <CredentialsPanel
508627 sources={sourceItems}
509628 credentials={credentialItems}
executor/apps/web/src/lib/types.ts+6−6
@@ -1,9 +1,9 @@
11 import type { Id } from "@executor/database/convex/_generated/dataModel";
22 import type {
33 ToolPolicyRecord as CoreToolPolicyRecord,
4− ToolRoleBindingRecord as CoreToolRoleBindingRecord,
5− ToolRoleRecord as CoreToolRoleRecord,
6− ToolRoleRuleRecord as CoreToolRoleRuleRecord,
4+ ToolPolicyAssignmentRecord as CoreToolPolicyAssignmentRecord,
5+ ToolPolicySetRecord as CoreToolPolicySetRecord,
6+ ToolPolicyRuleRecord as CoreToolPolicyRuleRecord,
77 } from "@executor/core/types";
88
99 // ── Shared types (inlined from @executor/contracts) ──────────────────────────
@@ -85,9 +85,9 @@ export interface TaskEventRecord { · 75 unmodified lines
8585 }
8686
8787 export type ToolPolicyRecord = CoreToolPolicyRecord;
88−export type ToolRoleRecord = CoreToolRoleRecord;
89−export type ToolRoleRuleRecord = CoreToolRoleRuleRecord;
90−export type ToolRoleBindingRecord = CoreToolRoleBindingRecord;
88+export type ToolPolicySetRecord = CoreToolPolicySetRecord;
89+export type ToolPolicyRuleRecord = CoreToolPolicyRuleRecord;
90+export type ToolPolicyAssignmentRecord = CoreToolPolicyAssignmentRecord;
9191
9292 export interface CredentialRecord {
9393 id: string;
executor/apps/web/src/routeTree.gen.ts+104−20
@@ -9,7 +9,6 @@ · 8 unmodified lines
99 // Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
1010
1111 import { Route as rootRouteImport } from './routes/__root'
12−import { Route as ToolsRouteImport } from './routes/tools'
1312 import { Route as TasksRouteImport } from './routes/tasks'
1413 import { Route as StaticAppShellRouteImport } from './routes/static-app-shell'
1514 import { Route as SignUpRouteImport } from './routes/sign-up'
@@ -25,6 +24,11 @@ import { Route as CallbackRouteImport } from './routes/callback' · 9 unmodified lines
2524 import { Route as BillingRouteImport } from './routes/billing'
2625 import { Route as ApprovalsRouteImport } from './routes/approvals'
2726 import { Route as IndexRouteImport } from './routes/index'
27+import { Route as ToolsIndexRouteImport } from './routes/tools/index'
28+import { Route as ToolsPoliciesRouteImport } from './routes/tools/policies'
29+import { Route as ToolsEditorRouteImport } from './routes/tools/editor'
30+import { Route as ToolsConnectionsRouteImport } from './routes/tools/connections'
31+import { Route as ToolsCatalogRouteImport } from './routes/tools/catalog'
2832 import { Route as McpOauthStartRouteImport } from './routes/mcp/oauth/start'
2933 import { Route as McpOauthResultRouteImport } from './routes/mcp/oauth/result'
3034 import { Route as McpOauthDetectRouteImport } from './routes/mcp/oauth/detect'
@@ -34,11 +38,6 @@ import { Route as ApiOpenapiInspectRouteImport } from './routes/api/openapi/insp · 3 unmodified lines
3438 import { Route as ApiAuthCallbackRouteImport } from './routes/api/auth/callback'
3539 import { Route as ApiAuthAnonymousTokenRouteImport } from './routes/api/auth/anonymous-token'
3640
37−const ToolsRoute = ToolsRouteImport.update({
38− id: '/tools',
39− path: '/tools',
40− getParentRoute: () => rootRouteImport,
41−} as any)
4241 const TasksRoute = TasksRouteImport.update({
4342 id: '/tasks',
4443 path: '/tasks',
@@ -114,6 +113,31 @@ const IndexRoute = IndexRouteImport.update({ · 69 unmodified lines
114113 path: '/',
115114 getParentRoute: () => rootRouteImport,
116115 } as any)
116+const ToolsIndexRoute = ToolsIndexRouteImport.update({
117+ id: '/tools/',
118+ path: '/tools/',
119+ getParentRoute: () => rootRouteImport,
120+} as any)
121+const ToolsPoliciesRoute = ToolsPoliciesRouteImport.update({
122+ id: '/tools/policies',
123+ path: '/tools/policies',
124+ getParentRoute: () => rootRouteImport,
125+} as any)
126+const ToolsEditorRoute = ToolsEditorRouteImport.update({
127+ id: '/tools/editor',
128+ path: '/tools/editor',
129+ getParentRoute: () => rootRouteImport,
130+} as any)
131+const ToolsConnectionsRoute = ToolsConnectionsRouteImport.update({
132+ id: '/tools/connections',
133+ path: '/tools/connections',
134+ getParentRoute: () => rootRouteImport,
135+} as any)
136+const ToolsCatalogRoute = ToolsCatalogRouteImport.update({
137+ id: '/tools/catalog',
138+ path: '/tools/catalog',
139+ getParentRoute: () => rootRouteImport,
140+} as any)
117141 const McpOauthStartRoute = McpOauthStartRouteImport.update({
118142 id: '/mcp/oauth/start',
119143 path: '/mcp/oauth/start',
@@ -171,7 +195,11 @@ export interface FileRoutesByFullPath { · 51 unmodified lines
171195 '/sign-up': typeof SignUpRoute
172196 '/static-app-shell': typeof StaticAppShellRoute
173197 '/tasks': typeof TasksRoute
174− '/tools': typeof ToolsRoute
198+ '/tools/catalog': typeof ToolsCatalogRoute
199+ '/tools/connections': typeof ToolsConnectionsRoute
200+ '/tools/editor': typeof ToolsEditorRoute
201+ '/tools/policies': typeof ToolsPoliciesRoute
202+ '/tools/': typeof ToolsIndexRoute
175203 '/api/auth/anonymous-token': typeof ApiAuthAnonymousTokenRoute
176204 '/api/auth/callback': typeof ApiAuthCallbackRoute
177205 '/api/openapi/inspect': typeof ApiOpenapiInspectRoute
@@ -197,7 +225,11 @@ export interface FileRoutesByTo { · 19 unmodified lines
197225 '/sign-up': typeof SignUpRoute
198226 '/static-app-shell': typeof StaticAppShellRoute
199227 '/tasks': typeof TasksRoute
200− '/tools': typeof ToolsRoute
228+ '/tools/catalog': typeof ToolsCatalogRoute
229+ '/tools/connections': typeof ToolsConnectionsRoute
230+ '/tools/editor': typeof ToolsEditorRoute
231+ '/tools/policies': typeof ToolsPoliciesRoute
232+ '/tools': typeof ToolsIndexRoute
201233 '/api/auth/anonymous-token': typeof ApiAuthAnonymousTokenRoute
202234 '/api/auth/callback': typeof ApiAuthCallbackRoute
203235 '/api/openapi/inspect': typeof ApiOpenapiInspectRoute
@@ -224,7 +256,11 @@ export interface FileRoutesById { · 20 unmodified lines
224256 '/sign-up': typeof SignUpRoute
225257 '/static-app-shell': typeof StaticAppShellRoute
226258 '/tasks': typeof TasksRoute
227− '/tools': typeof ToolsRoute
259+ '/tools/catalog': typeof ToolsCatalogRoute
260+ '/tools/connections': typeof ToolsConnectionsRoute
261+ '/tools/editor': typeof ToolsEditorRoute
262+ '/tools/policies': typeof ToolsPoliciesRoute
263+ '/tools/': typeof ToolsIndexRoute
228264 '/api/auth/anonymous-token': typeof ApiAuthAnonymousTokenRoute
229265 '/api/auth/callback': typeof ApiAuthCallbackRoute
230266 '/api/openapi/inspect': typeof ApiOpenapiInspectRoute
@@ -252,7 +288,11 @@ export interface FileRouteTypes { · 21 unmodified lines
252288 | '/sign-up'
253289 | '/static-app-shell'
254290 | '/tasks'
255− | '/tools'
291+ | '/tools/catalog'
292+ | '/tools/connections'
293+ | '/tools/editor'
294+ | '/tools/policies'
295+ | '/tools/'
256296 | '/api/auth/anonymous-token'
257297 | '/api/auth/callback'
258298 | '/api/openapi/inspect'
@@ -278,6 +318,10 @@ export interface FileRouteTypes { · 19 unmodified lines
278318 | '/sign-up'
279319 | '/static-app-shell'
280320 | '/tasks'
321+ | '/tools/catalog'
322+ | '/tools/connections'
323+ | '/tools/editor'
324+ | '/tools/policies'
281325 | '/tools'
282326 | '/api/auth/anonymous-token'
283327 | '/api/auth/callback'
@@ -304,7 +348,11 @@ export interface FileRouteTypes { · 20 unmodified lines
304348 | '/sign-up'
305349 | '/static-app-shell'
306350 | '/tasks'
307− | '/tools'
351+ | '/tools/catalog'
352+ | '/tools/connections'
353+ | '/tools/editor'
354+ | '/tools/policies'
355+ | '/tools/'
308356 | '/api/auth/anonymous-token'
309357 | '/api/auth/callback'
310358 | '/api/openapi/inspect'
@@ -331,7 +379,11 @@ export interface RootRouteChildren { · 20 unmodified lines
331379 SignUpRoute: typeof SignUpRoute
332380 StaticAppShellRoute: typeof StaticAppShellRoute
333381 TasksRoute: typeof TasksRoute
334− ToolsRoute: typeof ToolsRoute
382+ ToolsCatalogRoute: typeof ToolsCatalogRoute
383+ ToolsConnectionsRoute: typeof ToolsConnectionsRoute
384+ ToolsEditorRoute: typeof ToolsEditorRoute
385+ ToolsPoliciesRoute: typeof ToolsPoliciesRoute
386+ ToolsIndexRoute: typeof ToolsIndexRoute
335387 ApiAuthAnonymousTokenRoute: typeof ApiAuthAnonymousTokenRoute
336388 ApiAuthCallbackRoute: typeof ApiAuthCallbackRoute
337389 ApiOpenapiInspectRoute: typeof ApiOpenapiInspectRoute
@@ -344,13 +396,6 @@ export interface RootRouteChildren { · 6 unmodified lines
344396
345397 declare module '@tanstack/react-router' {
346398 interface FileRoutesByPath {
347− '/tools': {
348− id: '/tools'
349− path: '/tools'
350− fullPath: '/tools'
351− preLoaderRoute: typeof ToolsRouteImport
352− parentRoute: typeof rootRouteImport
353− }
354399 '/tasks': {
355400 id: '/tasks'
356401 path: '/tasks'
@@ -456,6 +501,41 @@ declare module '@tanstack/react-router' { · 99 unmodified lines
456501 preLoaderRoute: typeof IndexRouteImport
457502 parentRoute: typeof rootRouteImport
458503 }
504+ '/tools/': {
505+ id: '/tools/'
506+ path: '/tools'
507+ fullPath: '/tools/'
508+ preLoaderRoute: typeof ToolsIndexRouteImport
509+ parentRoute: typeof rootRouteImport
510+ }
511+ '/tools/policies': {
512+ id: '/tools/policies'
513+ path: '/tools/policies'
514+ fullPath: '/tools/policies'
515+ preLoaderRoute: typeof ToolsPoliciesRouteImport
516+ parentRoute: typeof rootRouteImport
517+ }
518+ '/tools/editor': {
519+ id: '/tools/editor'
520+ path: '/tools/editor'
521+ fullPath: '/tools/editor'
522+ preLoaderRoute: typeof ToolsEditorRouteImport
523+ parentRoute: typeof rootRouteImport
524+ }
525+ '/tools/connections': {
526+ id: '/tools/connections'
527+ path: '/tools/connections'
528+ fullPath: '/tools/connections'
529+ preLoaderRoute: typeof ToolsConnectionsRouteImport
530+ parentRoute: typeof rootRouteImport
531+ }
532+ '/tools/catalog': {
533+ id: '/tools/catalog'
534+ path: '/tools/catalog'
535+ fullPath: '/tools/catalog'
536+ preLoaderRoute: typeof ToolsCatalogRouteImport
537+ parentRoute: typeof rootRouteImport
538+ }
459539 '/mcp/oauth/start': {
460540 id: '/mcp/oauth/start'
461541 path: '/mcp/oauth/start'
@@ -531,7 +611,11 @@ const rootRouteChildren: RootRouteChildren = { · 69 unmodified lines
531611 SignUpRoute: SignUpRoute,
532612 StaticAppShellRoute: StaticAppShellRoute,
533613 TasksRoute: TasksRoute,
534− ToolsRoute: ToolsRoute,
614+ ToolsCatalogRoute: ToolsCatalogRoute,
615+ ToolsConnectionsRoute: ToolsConnectionsRoute,
616+ ToolsEditorRoute: ToolsEditorRoute,
617+ ToolsPoliciesRoute: ToolsPoliciesRoute,
618+ ToolsIndexRoute: ToolsIndexRoute,
535619 ApiAuthAnonymousTokenRoute: ApiAuthAnonymousTokenRoute,
536620 ApiAuthCallbackRoute: ApiAuthCallbackRoute,
537621 ApiOpenapiInspectRoute: ApiOpenapiInspectRoute,
executor/apps/web/src/routes/__root.tsx+20−17
@@ -5,6 +5,7 @@ import { AppConvexProvider } from "@/lib/convex-provider"; · 4 unmodified lines
55 import { QueryProvider } from "@/lib/query-provider";
66 import { SessionProvider } from "@/lib/session-context";
77 import { AppErrorBoundary } from "@/components/app-error-boundary";
8+import { NuqsAdapter } from "nuqs/adapters/tanstack-router";
89 import { runtimeConfigFromEnv } from "@/lib/runtime-config";
910 import appCss from "../app/globals.css?url";
1011
@@ -55,23 +56,25 @@ function RootDocument({ children }: { children: React.ReactNode }) { · 44 unmodified lines
5556 __html: `window.__EXECUTOR_RUNTIME_CONFIG__ = ${runtimeConfig};`,
5657 }}
5758 />
58− <ThemeProvider
59− attribute="class"
60− defaultTheme="dark"
61− enableSystem
62− enableColorScheme
63− >
64− <AppErrorBoundary>
65− <QueryProvider>
66− <AppConvexProvider>
67− <SessionProvider>
68− {children}
69− </SessionProvider>
70− </AppConvexProvider>
71− </QueryProvider>
72− </AppErrorBoundary>
73− <Toaster position="bottom-right" />
74− </ThemeProvider>
59+ <NuqsAdapter>
60+ <ThemeProvider
61+ attribute="class"
62+ defaultTheme="dark"
63+ enableSystem
64+ enableColorScheme
65+ >
66+ <AppErrorBoundary>
67+ <QueryProvider>
68+ <AppConvexProvider>
69+ <SessionProvider>
70+ {children}
71+ </SessionProvider>
72+ </AppConvexProvider>
73+ </QueryProvider>
74+ </AppErrorBoundary>
75+ <Toaster position="bottom-right" />
76+ </ThemeProvider>
77+ </NuqsAdapter>
7578 <Scripts />
7679 </body>
7780 </html>
executor/apps/web/src/routes/tools.tsx+0−22
@@ -1,22 +0,0 @@
1−import { createFileRoute } from "@tanstack/react-router";
2−import { AppShell } from "@/components/app-shell";
3−import { ToolsView } from "@/components/tools/view";
4−import { useSearchParams } from "@/lib/router";
5−
6−export const Route = createFileRoute("/tools")({
7− component: ToolsPage,
8−});
9−
10−function ToolsPage() {
11− const [searchParams] = useSearchParams();
12− const source = searchParams.get("source");
13− const tab = searchParams.get("tab");
14−
15− return (
16− <AppShell>
17− <div className="h-full min-h-0">
18− <ToolsView key={`${tab ?? "catalog"}:${source ?? "all"}`} initialSource={source} initialTab={tab} />
19− </div>
20− </AppShell>
21− );
22−}
executor/apps/web/src/routes/tools/catalog.tsx+15−0
@@ -0,0 +1,15 @@
1+import { createFileRoute } from "@tanstack/react-router";
2+import { AppShell } from "@/components/app-shell";
3+import { ToolsView } from "@/components/tools/view";
4+
5+export const Route = createFileRoute("/tools/catalog")({
6+ component: ToolsCatalogPage,
7+});
8+
9+function ToolsCatalogPage() {
10+ return (
11+ <AppShell>
12+ <ToolsView />
13+ </AppShell>
14+ );
15+}
executor/apps/web/src/routes/tools/connections.tsx+15−0
@@ -0,0 +1,15 @@
1+import { createFileRoute } from "@tanstack/react-router";
2+import { AppShell } from "@/components/app-shell";
3+import { ToolsView } from "@/components/tools/view";
4+
5+export const Route = createFileRoute("/tools/connections")({
6+ component: ToolsConnectionsPage,
7+});
8+
9+function ToolsConnectionsPage() {
10+ return (
11+ <AppShell>
12+ <ToolsView />
13+ </AppShell>
14+ );
15+}
executor/apps/web/src/routes/tools/editor.tsx+15−0
@@ -0,0 +1,15 @@
1+import { createFileRoute } from "@tanstack/react-router";
2+import { AppShell } from "@/components/app-shell";
3+import { ToolsView } from "@/components/tools/view";
4+
5+export const Route = createFileRoute("/tools/editor")({
6+ component: ToolsEditorPage,
7+});
8+
9+function ToolsEditorPage() {
10+ return (
11+ <AppShell>
12+ <ToolsView />
13+ </AppShell>
14+ );
15+}
executor/apps/web/src/routes/tools/index.tsx+18−0
@@ -0,0 +1,18 @@
1+import { createFileRoute } from "@tanstack/react-router";
2+import { AppShell } from "@/components/app-shell";
3+import { Navigate, useLocation } from "@/lib/router";
4+
5+export const Route = createFileRoute("/tools/")({
6+ component: ToolsLayout,
7+});
8+
9+function ToolsLayout() {
10+ const location = useLocation();
11+ const search = location.searchStr ? `?${location.searchStr}` : "";
12+
13+ return (
14+ <AppShell>
15+ <Navigate to={`/tools/catalog${search}`} replace />
16+ </AppShell>
17+ );
18+}
executor/apps/web/src/routes/tools/policies.tsx+15−0
@@ -0,0 +1,15 @@
1+import { createFileRoute } from "@tanstack/react-router";
2+import { AppShell } from "@/components/app-shell";
3+import { ToolsView } from "@/components/tools/view";
4+
5+export const Route = createFileRoute("/tools/policies")({
6+ component: ToolsPoliciesPage,
7+});
8+
9+function ToolsPoliciesPage() {
10+ return (
11+ <AppShell>
12+ <ToolsView />
13+ </AppShell>
14+ );
15+}
executor/packages/core/src/types.ts+3−3
@@ -123,7 +123,7 @@ export interface ToolPolicyRecord { · 122 unmodified lines
123123
124124 export type ToolRoleSelectorType = "all" | "source" | "namespace" | "tool_path";
125125
126−export interface ToolRoleRecord {
126+export interface ToolPolicySetRecord {
127127 id: string;
128128 organizationId: Id<"organizations">;
129129 name: string;
@@ -133,7 +133,7 @@ export interface ToolRoleRecord { · 3 unmodified lines
133133 updatedAt: number;
134134 }
135135
136−export interface ToolRoleRuleRecord {
136+export interface ToolPolicyRuleRecord {
137137 id: string;
138138 roleId: string;
139139 organizationId: Id<"organizations">;
@@ -150,7 +150,7 @@ export interface ToolRoleRuleRecord { · 10 unmodified lines
150150 updatedAt: number;
151151 }
152152
153−export interface ToolRoleBindingRecord {
153+export interface ToolPolicyAssignmentRecord {
154154 id: string;
155155 roleId: string;
156156 organizationId: Id<"organizations">;
executor/packages/database/convex/_generated/api.d.ts+71−71
@@ -306,19 +306,13 @@ export declare const api: { · 305 unmodified lines
306306 { accountId?: string; sessionId?: string },
307307 any
308308 >;
309− deleteToolRole: FunctionReference<
310− "mutation",
311− "public",
312− { roleId: string; sessionId?: string; workspaceId: Id<"workspaces"> },
313− any
314− >;
315− deleteToolRoleBinding: FunctionReference<
309+ deleteToolPolicyAssignment: FunctionReference<
316310 "mutation",
317311 "public",
318312 { bindingId: string; sessionId?: string; workspaceId: Id<"workspaces"> },
319313 any
320314 >;
321− deleteToolRoleRule: FunctionReference<
315+ deleteToolPolicyRule: FunctionReference<
322316 "mutation",
323317 "public",
324318 {
@@ -329,6 +323,12 @@ export declare const api: { · 4 unmodified lines
329323 },
330324 any
331325 >;
326+ deleteToolPolicySet: FunctionReference<
327+ "mutation",
328+ "public",
329+ { roleId: string; sessionId?: string; workspaceId: Id<"workspaces"> },
330+ any
331+ >;
332332 deleteToolSource: FunctionReference<
333333 "mutation",
334334 "public",
@@ -381,19 +381,19 @@ export declare const api: { · 46 unmodified lines
381381 { sessionId?: string; workspaceId: Id<"workspaces"> },
382382 any
383383 >;
384− listToolRoleBindings: FunctionReference<
384+ listToolPolicyAssignments: FunctionReference<
385385 "query",
386386 "public",
387387 { roleId?: string; sessionId?: string; workspaceId: Id<"workspaces"> },
388388 any
389389 >;
390− listToolRoleRules: FunctionReference<
390+ listToolPolicyRules: FunctionReference<
391391 "query",
392392 "public",
393393 { roleId: string; sessionId?: string; workspaceId: Id<"workspaces"> },
394394 any
395395 >;
396− listToolRoles: FunctionReference<
396+ listToolPolicySets: FunctionReference<
397397 "query",
398398 "public",
399399 { sessionId?: string; workspaceId: Id<"workspaces"> },
@@ -439,19 +439,7 @@ export declare const api: { · 39 unmodified lines
439439 },
440440 any
441441 >;
442− upsertToolRole: FunctionReference<
443− "mutation",
444− "public",
445− {
446− description?: string;
447− id?: string;
448− name: string;
449− sessionId?: string;
450− workspaceId: Id<"workspaces">;
451− },
452− any
453− >;
454− upsertToolRoleBinding: FunctionReference<
442+ upsertToolPolicyAssignment: FunctionReference<
455443 "mutation",
456444 "public",
457445 {
@@ -467,7 +455,7 @@ export declare const api: { · 9 unmodified lines
467455 },
468456 any
469457 >;
470− upsertToolRoleRule: FunctionReference<
458+ upsertToolPolicyRule: FunctionReference<
471459 "mutation",
472460 "public",
473461 {
@@ -490,6 +478,18 @@ export declare const api: { · 16 unmodified lines
490478 },
491479 any
492480 >;
481+ upsertToolPolicySet: FunctionReference<
482+ "mutation",
483+ "public",
484+ {
485+ description?: string;
486+ id?: string;
487+ name: string;
488+ sessionId?: string;
489+ workspaceId: Id<"workspaces">;
490+ },
491+ any
492+ >;
493493 upsertToolSource: FunctionReference<
494494 "mutation",
495495 "public",
@@ -742,22 +742,22 @@ export declare const internal: { · 246 unmodified lines
742742 any
743743 >;
744744 };
745− deleteToolRole: FunctionReference<
745+ deleteToolPolicyAssignment: FunctionReference<
746746 "mutation",
747747 "internal",
748− { roleId: string; workspaceId: Id<"workspaces"> },
748+ { bindingId: string; workspaceId: Id<"workspaces"> },
749749 any
750750 >;
751− deleteToolRoleBinding: FunctionReference<
751+ deleteToolPolicyRule: FunctionReference<
752752 "mutation",
753753 "internal",
754− { bindingId: string; workspaceId: Id<"workspaces"> },
754+ { roleId: string; ruleId: string; workspaceId: Id<"workspaces"> },
755755 any
756756 >;
757− deleteToolRoleRule: FunctionReference<
757+ deleteToolPolicySet: FunctionReference<
758758 "mutation",
759759 "internal",
760− { roleId: string; ruleId: string; workspaceId: Id<"workspaces"> },
760+ { roleId: string; workspaceId: Id<"workspaces"> },
761761 any
762762 >;
763763 deleteToolSource: FunctionReference<
@@ -855,19 +855,19 @@ export declare const internal: { · 91 unmodified lines
855855 { accountId?: Id<"accounts">; workspaceId: Id<"workspaces"> },
856856 any
857857 >;
858− listToolRoleBindings: FunctionReference<
858+ listToolPolicyAssignments: FunctionReference<
859859 "query",
860860 "internal",
861861 { roleId?: string; workspaceId: Id<"workspaces"> },
862862 any
863863 >;
864− listToolRoleRules: FunctionReference<
864+ listToolPolicyRules: FunctionReference<
865865 "query",
866866 "internal",
867867 { roleId: string; workspaceId: Id<"workspaces"> },
868868 any
869869 >;
870− listToolRoles: FunctionReference<
870+ listToolPolicySets: FunctionReference<
871871 "query",
872872 "internal",
873873 { workspaceId: Id<"workspaces"> },
@@ -897,22 +897,22 @@ export declare const internal: { · 23 unmodified lines
897897 any
898898 >;
899899 policies: {
900− deleteToolRole: FunctionReference<
900+ deleteToolPolicyAssignment: FunctionReference<
901901 "mutation",
902902 "internal",
903− { roleId: string; workspaceId: Id<"workspaces"> },
903+ { bindingId: string; workspaceId: Id<"workspaces"> },
904904 any
905905 >;
906− deleteToolRoleBinding: FunctionReference<
906+ deleteToolPolicyRule: FunctionReference<
907907 "mutation",
908908 "internal",
909− { bindingId: string; workspaceId: Id<"workspaces"> },
909+ { roleId: string; ruleId: string; workspaceId: Id<"workspaces"> },
910910 any
911911 >;
912− deleteToolRoleRule: FunctionReference<
912+ deleteToolPolicySet: FunctionReference<
913913 "mutation",
914914 "internal",
915− { roleId: string; ruleId: string; workspaceId: Id<"workspaces"> },
915+ { roleId: string; workspaceId: Id<"workspaces"> },
916916 any
917917 >;
918918 listRuntimeTargets: FunctionReference<"query", "internal", {}, any>;
@@ -922,37 +922,25 @@ export declare const internal: { · 3 unmodified lines
922922 { accountId?: Id<"accounts">; workspaceId: Id<"workspaces"> },
923923 any
924924 >;
925− listToolRoleBindings: FunctionReference<
925+ listToolPolicyAssignments: FunctionReference<
926926 "query",
927927 "internal",
928928 { roleId?: string; workspaceId: Id<"workspaces"> },
929929 any
930930 >;
931− listToolRoleRules: FunctionReference<
931+ listToolPolicyRules: FunctionReference<
932932 "query",
933933 "internal",
934934 { roleId: string; workspaceId: Id<"workspaces"> },
935935 any
936936 >;
937− listToolRoles: FunctionReference<
937+ listToolPolicySets: FunctionReference<
938938 "query",
939939 "internal",
940940 { workspaceId: Id<"workspaces"> },
941941 any
942942 >;
943− upsertToolRole: FunctionReference<
944− "mutation",
945− "internal",
946− {
947− createdByAccountId?: Id<"accounts">;
948− description?: string;
949− id?: string;
950− name: string;
951− workspaceId: Id<"workspaces">;
952− },
953− any
954− >;
955− upsertToolRoleBinding: FunctionReference<
943+ upsertToolPolicyAssignment: FunctionReference<
956944 "mutation",
957945 "internal",
958946 {
@@ -967,7 +955,7 @@ export declare const internal: { · 8 unmodified lines
967955 },
968956 any
969957 >;
970− upsertToolRoleRule: FunctionReference<
958+ upsertToolPolicyRule: FunctionReference<
971959 "mutation",
972960 "internal",
973961 {
@@ -989,6 +977,18 @@ export declare const internal: { · 15 unmodified lines
989977 },
990978 any
991979 >;
980+ upsertToolPolicySet: FunctionReference<
981+ "mutation",
982+ "internal",
983+ {
984+ createdByAccountId?: Id<"accounts">;
985+ description?: string;
986+ id?: string;
987+ name: string;
988+ workspaceId: Id<"workspaces">;
989+ },
990+ any
991+ >;
992992 };
993993 resolveApproval: FunctionReference<
994994 "mutation",
@@ -1186,19 +1186,7 @@ export declare const internal: { · 191 unmodified lines
11861186 },
11871187 any
11881188 >;
1189− upsertToolRole: FunctionReference<
1190− "mutation",
1191− "internal",
1192− {
1193− createdByAccountId?: Id<"accounts">;
1194− description?: string;
1195− id?: string;
1196− name: string;
1197− workspaceId: Id<"workspaces">;
1198− },
1199− any
1200− >;
1201− upsertToolRoleBinding: FunctionReference<
1189+ upsertToolPolicyAssignment: FunctionReference<
12021190 "mutation",
12031191 "internal",
12041192 {
@@ -1213,7 +1201,7 @@ export declare const internal: { · 8 unmodified lines
12131201 },
12141202 any
12151203 >;
1216− upsertToolRoleRule: FunctionReference<
1204+ upsertToolPolicyRule: FunctionReference<
12171205 "mutation",
12181206 "internal",
12191207 {
@@ -1235,6 +1223,18 @@ export declare const internal: { · 15 unmodified lines
12351223 },
12361224 any
12371225 >;
1226+ upsertToolPolicySet: FunctionReference<
1227+ "mutation",
1228+ "internal",
1229+ {
1230+ createdByAccountId?: Id<"accounts">;
1231+ description?: string;
1232+ id?: string;
1233+ name: string;
1234+ workspaceId: Id<"workspaces">;
1235+ },
1236+ any
1237+ >;
12381238 upsertToolSource: FunctionReference<
12391239 "mutation",
12401240 "internal",
executor/packages/database/convex/access-controls.test.ts+22−22
@@ -217,7 +217,7 @@ describe("authentication", () => { · 216 unmodified lines
217217 const owner = await seedUser(t, { subject: "owner-2" });
218218
219219 await expect(
220− t.mutation(api.workspace.upsertToolRole, {
220+ t.mutation(api.workspace.upsertToolPolicySet, {
221221 workspaceId: owner.workspaceId,
222222 name: "anonymous-role",
223223 }),
@@ -439,7 +439,7 @@ describe("workspace access controls", () => { · 215 unmodified lines
439439 const authedMember = t.withIdentity({ subject: "ws-plain-member" });
440440
441441 await expect(
442− authedMember.mutation(api.workspace.upsertToolRole, {
442+ authedMember.mutation(api.workspace.upsertToolPolicySet, {
443443 workspaceId: owner.workspaceId,
444444 name: "member-managed-role",
445445 }),
@@ -464,13 +464,13 @@ describe("workspace access controls", () => { · 18 unmodified lines
464464
465465 const authedAdmin = t.withIdentity({ subject: "ws-policy-admin" });
466466
467− const role = await authedAdmin.mutation(api.workspace.upsertToolRole, {
467+ const role = await authedAdmin.mutation(api.workspace.upsertToolPolicySet, {
468468 workspaceId: owner.workspaceId,
469469 name: "policy-admin-role",
470470 description: "workspace admin policy role",
471471 });
472472
473− await authedAdmin.mutation(api.workspace.upsertToolRoleRule, {
473+ await authedAdmin.mutation(api.workspace.upsertToolPolicyRule, {
474474 workspaceId: owner.workspaceId,
475475 roleId: role.id,
476476 selectorType: "tool_path",
@@ -479,7 +479,7 @@ describe("workspace access controls", () => { · 2 unmodified lines
479479 approvalMode: "required",
480480 });
481481
482− await authedAdmin.mutation(api.workspace.upsertToolRoleBinding, {
482+ await authedAdmin.mutation(api.workspace.upsertToolPolicyAssignment, {
483483 workspaceId: owner.workspaceId,
484484 roleId: role.id,
485485 scopeType: "workspace",
@@ -503,13 +503,13 @@ describe("workspace access controls", () => { · 17 unmodified lines
503503 const outsider = await seedUser(t, { subject: "policy-target-outsider" });
504504 const authedOwner = t.withIdentity({ subject: "policy-target-owner" });
505505
506− const role = await authedOwner.mutation(api.workspace.upsertToolRole, {
506+ const role = await authedOwner.mutation(api.workspace.upsertToolPolicySet, {
507507 workspaceId: owner.workspaceId,
508508 name: "policy-target-role",
509509 });
510510
511511 await expect(
512− authedOwner.mutation(api.workspace.upsertToolRoleBinding, {
512+ authedOwner.mutation(api.workspace.upsertToolPolicyAssignment, {
513513 workspaceId: owner.workspaceId,
514514 roleId: role.id,
515515 scopeType: "account",
@@ -524,12 +524,12 @@ describe("workspace access controls", () => { · 8 unmodified lines
524524 const owner = await seedUser(t, { subject: "ws-owner-policy" });
525525 const authedOwner = t.withIdentity({ subject: "ws-owner-policy" });
526526
527− const role = await authedOwner.mutation(api.workspace.upsertToolRole, {
527+ const role = await authedOwner.mutation(api.workspace.upsertToolPolicySet, {
528528 workspaceId: owner.workspaceId,
529529 name: "owner-policy-role",
530530 });
531531
532− await authedOwner.mutation(api.workspace.upsertToolRoleRule, {
532+ await authedOwner.mutation(api.workspace.upsertToolPolicyRule, {
533533 workspaceId: owner.workspaceId,
534534 roleId: role.id,
535535 selectorType: "all",
@@ -537,7 +537,7 @@ describe("workspace access controls", () => { · 1 unmodified line
537537 approvalMode: "required",
538538 });
539539
540− await authedOwner.mutation(api.workspace.upsertToolRoleBinding, {
540+ await authedOwner.mutation(api.workspace.upsertToolPolicyAssignment, {
541541 workspaceId: owner.workspaceId,
542542 roleId: role.id,
543543 scopeType: "workspace",
@@ -555,12 +555,12 @@ describe("workspace access controls", () => { · 11 unmodified lines
555555 const owner = await seedUser(t, { subject: "ws-owner-policy-delete" });
556556 const authedOwner = t.withIdentity({ subject: "ws-owner-policy-delete" });
557557
558− const role = await authedOwner.mutation(api.workspace.upsertToolRole, {
558+ const role = await authedOwner.mutation(api.workspace.upsertToolPolicySet, {
559559 workspaceId: owner.workspaceId,
560560 name: "owner-policy-delete-role",
561561 });
562562
563− await authedOwner.mutation(api.workspace.upsertToolRoleRule, {
563+ await authedOwner.mutation(api.workspace.upsertToolPolicyRule, {
564564 workspaceId: owner.workspaceId,
565565 roleId: role.id,
566566 selectorType: "tool_path",
@@ -569,14 +569,14 @@ describe("workspace access controls", () => { · 2 unmodified lines
569569 matchType: "exact",
570570 });
571571
572− await authedOwner.mutation(api.workspace.upsertToolRoleBinding, {
572+ await authedOwner.mutation(api.workspace.upsertToolPolicyAssignment, {
573573 workspaceId: owner.workspaceId,
574574 roleId: role.id,
575575 scopeType: "workspace",
576576 status: "active",
577577 });
578578
579− await authedOwner.mutation(api.workspace.deleteToolRole, {
579+ await authedOwner.mutation(api.workspace.deleteToolPolicySet, {
580580 workspaceId: owner.workspaceId,
581581 roleId: role.id,
582582 });
@@ -754,7 +754,7 @@ describe("workspace access controls", () => { · 171 unmodified lines
754754 const authedMember = t.withIdentity({ subject: "role-tool-member" });
755755
756756 await expect(
757− authedMember.mutation(api.workspace.upsertToolRole, {
757+ authedMember.mutation(api.workspace.upsertToolPolicySet, {
758758 workspaceId: owner.workspaceId,
759759 name: "limited",
760760 }),
@@ -766,13 +766,13 @@ describe("workspace access controls", () => { · 5 unmodified lines
766766 const owner = await seedUser(t, { subject: "role-source-owner" });
767767 const authedOwner = t.withIdentity({ subject: "role-source-owner" });
768768
769− const role = await authedOwner.mutation(api.workspace.upsertToolRole, {
769+ const role = await authedOwner.mutation(api.workspace.upsertToolPolicySet, {
770770 workspaceId: owner.workspaceId,
771771 name: "github-access",
772772 description: "Allows github source tools",
773773 });
774774
775− await authedOwner.mutation(api.workspace.upsertToolRoleRule, {
775+ await authedOwner.mutation(api.workspace.upsertToolPolicyRule, {
776776 workspaceId: owner.workspaceId,
777777 roleId: role.id,
778778 selectorType: "source",
@@ -781,7 +781,7 @@ describe("workspace access controls", () => { · 2 unmodified lines
781781 approvalMode: "auto",
782782 });
783783
784− await authedOwner.mutation(api.workspace.upsertToolRoleBinding, {
784+ await authedOwner.mutation(api.workspace.upsertToolPolicyAssignment, {
785785 workspaceId: owner.workspaceId,
786786 roleId: role.id,
787787 scopeType: "organization",
@@ -1120,7 +1120,7 @@ describe("cross-workspace isolation", () => { · 332 unmodified lines
11201120 const authedA = t.withIdentity({ subject: "policy-user-a" });
11211121
11221122 await expect(
1123− authedA.mutation(api.workspace.upsertToolRole, {
1123+ authedA.mutation(api.workspace.upsertToolPolicySet, {
11241124 workspaceId: userB.workspaceId,
11251125 name: "cross-workspace-role",
11261126 }),
@@ -1628,18 +1628,18 @@ describe("role hierarchy validation", () => { · 501 unmodified lines
16281628 const authed = t.withIdentity({ subject: "admin-hier-admin" });
16291629
16301630 // Admin can manage tool-policy roles/rules/bindings
1631− const role = await authed.mutation(api.workspace.upsertToolRole, {
1631+ const role = await authed.mutation(api.workspace.upsertToolPolicySet, {
16321632 workspaceId: org.workspaceId,
16331633 name: "admin-hierarchy-role",
16341634 });
1635− await authed.mutation(api.workspace.upsertToolRoleRule, {
1635+ await authed.mutation(api.workspace.upsertToolPolicyRule, {
16361636 workspaceId: org.workspaceId,
16371637 roleId: role.id,
16381638 selectorType: "tool_path",
16391639 resourcePattern: "admin.*",
16401640 effect: "deny",
16411641 });
1642− await authed.mutation(api.workspace.upsertToolRoleBinding, {
1642+ await authed.mutation(api.workspace.upsertToolPolicyAssignment, {
16431643 workspaceId: org.workspaceId,
16441644 roleId: role.id,
16451645 scopeType: "workspace",
executor/packages/database/convex/database.ts+9−9
@@ -25,15 +25,15 @@ export { · 24 unmodified lines
2525 export { bootstrapAnonymousSession } from "./database/anonymous_session";
2626 export {
2727 listRuntimeTargets,
28− upsertToolRole,
29− listToolRoles,
30− deleteToolRole,
31− upsertToolRoleRule,
32− listToolRoleRules,
33− deleteToolRoleRule,
34− upsertToolRoleBinding,
35− listToolRoleBindings,
36− deleteToolRoleBinding,
28+ upsertToolPolicySet,
29+ listToolPolicySets,
30+ deleteToolPolicySet,
31+ upsertToolPolicyRule,
32+ listToolPolicyRules,
33+ deleteToolPolicyRule,
34+ upsertToolPolicyAssignment,
35+ listToolPolicyAssignments,
36+ deleteToolPolicyAssignment,
3737 listToolPolicies,
3838 } from "./database/policies";
3939 export {
executor/packages/database/convex/database/policies.ts+16−16
@@ -5,9 +5,9 @@ import type { MutationCtx, QueryCtx } from "../_generated/server"; · 4 unmodified lines
55 import { listRuntimeTargets as listAvailableRuntimeTargets } from "../../../core/src/runtimes/runtime-catalog";
66 import type {
77 ToolPolicyRecord,
8− ToolRoleBindingRecord,
9− ToolRoleRecord,
10− ToolRoleRuleRecord,
8+ ToolPolicyAssignmentRecord,
9+ ToolPolicySetRecord,
10+ ToolPolicyRuleRecord,
1111 ToolRoleSelectorType,
1212 } from "../../../core/src/types";
1313 import {
@@ -83,7 +83,7 @@ function mapToolRole(doc: { · 69 unmodified lines
8383 createdByAccountId?: Id<"accounts">;
8484 createdAt: number;
8585 updatedAt: number;
86−}): ToolRoleRecord {
86+}): ToolPolicySetRecord {
8787 return {
8888 id: doc.roleId,
8989 organizationId: doc.organizationId,
@@ -110,7 +110,7 @@ function mapToolRoleRule(doc: { · 20 unmodified lines
110110 priority: number;
111111 createdAt: number;
112112 updatedAt: number;
113−}): ToolRoleRuleRecord {
113+}): ToolPolicyRuleRecord {
114114 return {
115115 id: doc.ruleId,
116116 roleId: doc.roleId,
@@ -141,7 +141,7 @@ function mapToolRoleBinding(doc: { · 24 unmodified lines
141141 expiresAt?: number;
142142 createdAt: number;
143143 updatedAt: number;
144−}): ToolRoleBindingRecord {
144+}): ToolPolicyAssignmentRecord {
145145 return {
146146 id: doc.bindingId,
147147 roleId: doc.roleId,
@@ -328,7 +328,7 @@ async function listEffectiveBindings( · 180 unmodified lines
328328 }
329329
330330 function selectorInputToResource(
331− rule: Pick<ToolRoleRuleRecord, "selectorType" | "sourceKey" | "namespacePattern" | "toolPathPattern">,
331+ rule: Pick<ToolPolicyRuleRecord, "selectorType" | "sourceKey" | "namespacePattern" | "toolPathPattern">,
332332 ): { resourceType: PolicyResourceType; resourcePattern: string } {
333333 const resourceType = resourceTypeFromSelectorType(rule.selectorType);
334334 const resourcePattern = resourcePatternFromRule(rule);
@@ -346,7 +346,7 @@ export const listRuntimeTargets = internalQuery({ · 11 unmodified lines
346346 },
347347 });
348348
349−export const upsertToolRole = internalMutation({
349+export const upsertToolPolicySet = internalMutation({
350350 args: {
351351 workspaceId: v.id("workspaces"),
352352 id: v.optional(v.string()),
@@ -410,7 +410,7 @@ export const upsertToolRole = internalMutation({ · 57 unmodified lines
410410 },
411411 });
412412
413−export const listToolRoles = internalQuery({
413+export const listToolPolicySets = internalQuery({
414414 args: {
415415 workspaceId: v.id("workspaces"),
416416 },
@@ -426,7 +426,7 @@ export const listToolRoles = internalQuery({ · 9 unmodified lines
426426 },
427427 });
428428
429−export const deleteToolRole = internalMutation({
429+export const deleteToolPolicySet = internalMutation({
430430 args: {
431431 workspaceId: v.id("workspaces"),
432432 roleId: v.string(),
@@ -459,7 +459,7 @@ export const deleteToolRole = internalMutation({ · 26 unmodified lines
459459 },
460460 });
461461
462−export const upsertToolRoleRule = internalMutation({
462+export const upsertToolPolicyRule = internalMutation({
463463 args: {
464464 workspaceId: v.id("workspaces"),
465465 roleId: v.string(),
@@ -541,7 +541,7 @@ export const upsertToolRoleRule = internalMutation({ · 75 unmodified lines
541541 },
542542 });
543543
544−export const listToolRoleRules = internalQuery({
544+export const listToolPolicyRules = internalQuery({
545545 args: {
546546 workspaceId: v.id("workspaces"),
547547 roleId: v.string(),
@@ -572,7 +572,7 @@ export const listToolRoleRules = internalQuery({ · 24 unmodified lines
572572 },
573573 });
574574
575−export const deleteToolRoleRule = internalMutation({
575+export const deleteToolPolicyRule = internalMutation({
576576 args: {
577577 workspaceId: v.id("workspaces"),
578578 roleId: v.string(),
@@ -601,7 +601,7 @@ export const deleteToolRoleRule = internalMutation({ · 22 unmodified lines
601601 },
602602 });
603603
604−export const upsertToolRoleBinding = internalMutation({
604+export const upsertToolPolicyAssignment = internalMutation({
605605 args: {
606606 workspaceId: v.id("workspaces"),
607607 roleId: v.string(),
@@ -680,7 +680,7 @@ export const upsertToolRoleBinding = internalMutation({ · 72 unmodified lines
680680 },
681681 });
682682
683−export const listToolRoleBindings = internalQuery({
683+export const listToolPolicyAssignments = internalQuery({
684684 args: {
685685 workspaceId: v.id("workspaces"),
686686 roleId: v.optional(v.string()),
@@ -705,7 +705,7 @@ export const listToolRoleBindings = internalQuery({ · 18 unmodified lines
705705 },
706706 });
707707
708−export const deleteToolRoleBinding = internalMutation({
708+export const deleteToolPolicyAssignment = internalMutation({
709709 args: {
710710 workspaceId: v.id("workspaces"),
711711 bindingId: v.string(),
executor/packages/database/convex/workspace.ts+18−18
@@ -136,7 +136,7 @@ export const listToolPolicies = workspaceQuery({ · 135 unmodified lines
136136 },
137137 });
138138
139−export const upsertToolRole = workspaceMutation({
139+export const upsertToolPolicySet = workspaceMutation({
140140 method: "POST",
141141 requireAdmin: true,
142142 args: {
@@ -145,7 +145,7 @@ export const upsertToolRole = workspaceMutation({ · 2 unmodified lines
145145 description: v.optional(v.string()),
146146 },
147147 handler: async (ctx, args) => {
148− return await ctx.runMutation(internal.database.upsertToolRole, {
148+ return await ctx.runMutation(internal.database.upsertToolPolicySet, {
149149 ...args,
150150 workspaceId: ctx.workspaceId,
151151 createdByAccountId: ctx.account._id,
@@ -153,32 +153,32 @@ export const upsertToolRole = workspaceMutation({ · 1 unmodified line
153153 },
154154 });
155155
156−export const listToolRoles = workspaceQuery({
156+export const listToolPolicySets = workspaceQuery({
157157 method: "GET",
158158 requireAdmin: true,
159159 args: {},
160160 handler: async (ctx) => {
161− return await ctx.runQuery(internal.database.listToolRoles, {
161+ return await ctx.runQuery(internal.database.listToolPolicySets, {
162162 workspaceId: ctx.workspaceId,
163163 });
164164 },
165165 });
166166
167−export const deleteToolRole = workspaceMutation({
167+export const deleteToolPolicySet = workspaceMutation({
168168 method: "POST",
169169 requireAdmin: true,
170170 args: {
171171 roleId: v.string(),
172172 },
173173 handler: async (ctx, args) => {
174− return await ctx.runMutation(internal.database.deleteToolRole, {
174+ return await ctx.runMutation(internal.database.deleteToolPolicySet, {
175175 workspaceId: ctx.workspaceId,
176176 roleId: args.roleId,
177177 });
178178 },
179179 });
180180
181−export const upsertToolRoleRule = workspaceMutation({
181+export const upsertToolPolicyRule = workspaceMutation({
182182 method: "POST",
183183 requireAdmin: true,
184184 args: {
@@ -198,28 +198,28 @@ export const upsertToolRoleRule = workspaceMutation({ · 13 unmodified lines
198198 priority: v.optional(v.number()),
199199 },
200200 handler: async (ctx, args) => {
201− return await ctx.runMutation(internal.database.upsertToolRoleRule, {
201+ return await ctx.runMutation(internal.database.upsertToolPolicyRule, {
202202 ...args,
203203 workspaceId: ctx.workspaceId,
204204 });
205205 },
206206 });
207207
208−export const listToolRoleRules = workspaceQuery({
208+export const listToolPolicyRules = workspaceQuery({
209209 method: "GET",
210210 requireAdmin: true,
211211 args: {
212212 roleId: v.string(),
213213 },
214214 handler: async (ctx, args) => {
215− return await ctx.runQuery(internal.database.listToolRoleRules, {
215+ return await ctx.runQuery(internal.database.listToolPolicyRules, {
216216 workspaceId: ctx.workspaceId,
217217 roleId: args.roleId,
218218 });
219219 },
220220 });
221221
222−export const deleteToolRoleRule = workspaceMutation({
222+export const deleteToolPolicyRule = workspaceMutation({
223223 method: "POST",
224224 requireAdmin: true,
225225 args: {
@@ -227,7 +227,7 @@ export const deleteToolRoleRule = workspaceMutation({ · 1 unmodified line
227227 ruleId: v.string(),
228228 },
229229 handler: async (ctx, args) => {
230− return await ctx.runMutation(internal.database.deleteToolRoleRule, {
230+ return await ctx.runMutation(internal.database.deleteToolPolicyRule, {
231231 workspaceId: ctx.workspaceId,
232232 roleId: args.roleId,
233233 ruleId: args.ruleId,
@@ -235,7 +235,7 @@ export const deleteToolRoleRule = workspaceMutation({ · 1 unmodified line
235235 },
236236 });
237237
238−export const upsertToolRoleBinding = workspaceMutation({
238+export const upsertToolPolicyAssignment = workspaceMutation({
239239 method: "POST",
240240 requireAdmin: true,
241241 args: {
@@ -248,35 +248,35 @@ export const upsertToolRoleBinding = workspaceMutation({ · 6 unmodified lines
248248 expiresAt: v.optional(v.number()),
249249 },
250250 handler: async (ctx, args) => {
251− return await ctx.runMutation(internal.database.upsertToolRoleBinding, {
251+ return await ctx.runMutation(internal.database.upsertToolPolicyAssignment, {
252252 ...args,
253253 workspaceId: ctx.workspaceId,
254254 });
255255 },
256256 });
257257
258−export const listToolRoleBindings = workspaceQuery({
258+export const listToolPolicyAssignments = workspaceQuery({
259259 method: "GET",
260260 requireAdmin: true,
261261 args: {
262262 roleId: v.optional(v.string()),
263263 },
264264 handler: async (ctx, args) => {
265− return await ctx.runQuery(internal.database.listToolRoleBindings, {
265+ return await ctx.runQuery(internal.database.listToolPolicyAssignments, {
266266 workspaceId: ctx.workspaceId,
267267 roleId: args.roleId,
268268 });
269269 },
270270 });
271271
272−export const deleteToolRoleBinding = workspaceMutation({
272+export const deleteToolPolicyAssignment = workspaceMutation({
273273 method: "POST",
274274 requireAdmin: true,
275275 args: {
276276 bindingId: v.string(),
277277 },
278278 handler: async (ctx, args) => {
279− return await ctx.runMutation(internal.database.deleteToolRoleBinding, {
279+ return await ctx.runMutation(internal.database.deleteToolPolicyAssignment, {
280280 workspaceId: ctx.workspaceId,
281281 bindingId: args.bindingId,
282282 });

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t