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
106 "lucide-react": "^0.563.0",106 "lucide-react": "^0.563.0",
107 "monaco-editor": "^0.55.1",107 "monaco-editor": "^0.55.1",
108 "next-themes": "^0.4.6",108 "next-themes": "^0.4.6",
109 "nuqs": "^2.8.8",
109 "radix-ui": "^1.4.3",110 "radix-ui": "^1.4.3",
110 "react": "19.2.3",111 "react": "19.2.3",
111 "react-dom": "19.2.3",112 "react-dom": "19.2.3",
@@ -2049,6 +2050,8 @@ · 1937 unmodified lines
2049 2050
2050 "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],2051 "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="],
2051 2052
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
2052 "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],2055 "object-assign": ["object-assign@4.1.1", "", {}, "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg=="],
2053 2056
2054 "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],2057 "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="],
@@ -2687,6 +2690,8 @@ · 632 unmodified lines
2687 2690
2688 "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],2691 "npm-run-path/path-key": ["path-key@4.0.0", "", {}, "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ=="],
2689 2692
2693 "nuqs/@standard-schema/spec": ["@standard-schema/spec@1.0.0", "", {}, "sha512-m2bOd0f2RT9k8QJx1JN85cZYyH1RqFBdlwtkSlf4tBDYLCiiZnv1fIIwacK6cqwXavOydf0NPToMQgpKq+dVlA=="],
2694
2690 "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],2695 "parse-entities/@types/unist": ["@types/unist@2.0.11", "", {}, "sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA=="],
2691 2696
2692 "parse-json/type-fest": ["type-fest@4.41.0", "", {}, "sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA=="],2697 "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
17 "@executor/ui": "workspace:*",17 "@executor/ui": "workspace:*",
18 "@monaco-editor/react": "^4.7.0",18 "@monaco-editor/react": "^4.7.0",
19 "@streamdown/code": "^1.0.1",19 "@streamdown/code": "^1.0.1",
20 "@tanstack/react-query": "^5.80.7",
20 "@tanstack/react-router": "^1.132.0",21 "@tanstack/react-router": "^1.132.0",
21 "@tanstack/react-start": "^1.132.0",22 "@tanstack/react-start": "^1.132.0",
22 "@tanstack/router-plugin": "^1.132.0",
23 "@tanstack/react-query": "^5.80.7",
24 "@tanstack/react-virtual": "^3.13.18",23 "@tanstack/react-virtual": "^3.13.18",
24 "@tanstack/router-plugin": "^1.132.0",
25 "@workos/authkit-tanstack-react-start": "^0.5.0",25 "@workos/authkit-tanstack-react-start": "^0.5.0",
26 "better-result": "^2.7.0",26 "better-result": "^2.7.0",
27 "class-variance-authority": "^0.7.1",27 "class-variance-authority": "^0.7.1",
@@ -32,6 +32,7 @@ · 4 unmodified lines
32 "lucide-react": "^0.563.0",32 "lucide-react": "^0.563.0",
33 "monaco-editor": "^0.55.1",33 "monaco-editor": "^0.55.1",
34 "next-themes": "^0.4.6",34 "next-themes": "^0.4.6",
35 "nuqs": "^2.8.8",
35 "radix-ui": "^1.4.3",36 "radix-ui": "^1.4.3",
36 "react": "19.2.3",37 "react": "19.2.3",
37 "react-dom": "19.2.3",38 "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
97 variant="ghost"97 variant="ghost"
98 size="sm"98 size="sm"
99 className="h-7 text-[11px]"99 className="h-7 text-[11px]"
100 onClick={() => navigate("/tools?tab=editor")}100 onClick={() => navigate("/tools/editor")}
101 >101 >
102 Open editor102 Open editor
103 </Button>103 </Button>
executor/apps/web/src/components/menubar/mvp-view.tsx+3−3
@@ -60,7 +60,7 @@ export function MenubarMvpView() { · 59 unmodified lines
60 </p>60 </p>
61 </div>61 </div>
62 <div className="flex items-center gap-2">62 <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")}>
64 Open Editor64 Open Editor
65 </Button>65 </Button>
66 <Button size="sm" variant="outline" className="h-8 text-xs" onClick={() => navigate("/approvals")}>66 <Button size="sm" variant="outline" className="h-8 text-xs" onClick={() => navigate("/approvals")}>
@@ -112,7 +112,7 @@ export function MenubarMvpView() { · 45 unmodified lines
112 <Sparkles className="h-4 w-4 text-primary" />112 <Sparkles className="h-4 w-4 text-primary" />
113 Recent AI tasks113 Recent AI tasks
114 </p>114 </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>
116 </div>116 </div>
117 117
118 {tasksLoading ? (118 {tasksLoading ? (
@@ -130,7 +130,7 @@ export function MenubarMvpView() { · 11 unmodified lines
130 key={task.id}130 key={task.id}
131 type="button"131 type="button"
132 className="flex w-full items-center justify-between rounded border border-border bg-background px-2.5 py-2 text-left hover:bg-accent"132 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")}
134 >134 >
135 <span className="min-w-0">135 <span className="min-w-0">
136 <span className="block truncate text-[11px] font-mono text-foreground">{task.id}</span>136 <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
51 loadingSources?: string[];51 loadingSources?: string[];
52 onLoadToolDetails?: (toolPaths: string[]) => Promise<Record<string, Pick<ToolDescriptor, "path" | "description" | "display" | "typing">>>;52 onLoadToolDetails?: (toolPaths: string[]) => Promise<Record<string, Pick<ToolDescriptor, "path" | "description" | "display" | "typing">>>;
53 warnings?: string[];53 warnings?: string[];
54 initialSource?: string | null;54 activeSource: string | null;
55 activeSource?: string | null;55 searchValue: string;
56 onActiveSourceChange?: (source: string | null) => void;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;
57 /** @deprecated No longer used — source sidebar is merged into the tool list panel. */65 /** @deprecated No longer used — source sidebar is merged into the tool list panel. */
58 showSourceSidebar?: boolean;66 showSourceSidebar?: boolean;
59 onSourceAdded?: (source: ToolSourceRecord) => void;67 onSourceAdded?: (source: ToolSourceRecord) => void;
@@ -79,9 +87,17 @@ export function ToolExplorer({ · 19 unmodified lines
79 loadingSources = [],87 loadingSources = [],
80 onLoadToolDetails,88 onLoadToolDetails,
81 warnings = [],89 warnings = [],
82 initialSource = null,
83 activeSource,90 activeSource,
84 onActiveSourceChange,91 searchValue,
92 filterApprovalValue,
93 focusedToolPathValue,
94 selectedToolPathsValue,
95 focusedSourceNameValue,
96 onSearchValueChange,
97 onFilterApprovalValueChange,
98 onFocusedToolPathChange,
99 onSelectedToolPathsChange,
100 onFocusedSourceNameChange,
85 onSourceAdded,101 onSourceAdded,
86 sourceDialogMeta,102 sourceDialogMeta,
87 sourceAuthProfiles,103 sourceAuthProfiles,
@@ -106,21 +122,45 @@ export function ToolExplorer({ · 18 unmodified lines
106 return description.length > 0 || hasInputHint || hasOutputHint || hasSchemas;122 return description.length > 0 || hasInputHint || hasOutputHint || hasSchemas;
107 }, []);123 }, []);
108 124
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");
114 const [toolDetailsByPath, setToolDetailsByPath] = useState<Record<string, Pick<ToolDescriptor, "path" | "description" | "display" | "typing">>>({});125 const [toolDetailsByPath, setToolDetailsByPath] = useState<Record<string, Pick<ToolDescriptor, "path" | "description" | "display" | "typing">>>({});
115 const [loadingDetailPaths, setLoadingDetailPaths] = useState<Set<string>>(new Set());126 const [loadingDetailPaths, setLoadingDetailPaths] = useState<Set<string>>(new Set());
116 const [focusedToolPath, setFocusedToolPath] = useState<string | null>(null);
117 const [focusedSourceName, setFocusedSourceName] = useState<string | null>(null);
118 // "new" = add source form, ToolSourceRecord = edit/view existing source, null = no source panel127 // "new" = add source form, ToolSourceRecord = edit/view existing source, null = no source panel
119 const [formSource, setFormSource] = useState<"new" | ToolSourceRecord | null>(null);128 const [formSource, setFormSource] = useState<"new" | ToolSourceRecord | null>(null);
120 const toolListRef = useRef<HTMLDivElement>(null);129 const toolListRef = useRef<HTMLDivElement>(null);
121 const toolListScrollContainerId = "tool-explorer-toollist-scroll";130 const toolListScrollContainerId = "tool-explorer-toollist-scroll";
122 const resolvedActiveSource =131 const resolvedActiveSource = activeSource;
123 activeSource === undefined ? internalActiveSource : 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]);
124 164
125 const hydratedTools = useMemo(() => {165 const hydratedTools = useMemo(() => {
126 if (Object.keys(toolDetailsByPath).length === 0) {166 if (Object.keys(toolDetailsByPath).length === 0) {
@@ -212,21 +252,30 @@ export function ToolExplorer({ · 85 unmodified lines
212 return map;252 return map;
213 }, [visibleSources]);253 }, [visibleSources]);
214 254
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
215 const toggleSelectTool = useCallback(266 const toggleSelectTool = useCallback(
216 (path: string, e: React.MouseEvent) => {267 (path: string, e: React.MouseEvent) => {
217 e.stopPropagation();268 e.stopPropagation();
218 e.preventDefault();269 e.preventDefault();
219 setSelectedKeys((prev) => {270 const next = new Set(selectedKeys);
220 const next = new Set(prev);271 if (next.has(path)) {
221 if (next.has(path)) {272 next.delete(path);
222 next.delete(path);273 } else {
223 } else {274 next.add(path);
224 next.add(path);275 }
225 }276 setSelectedKeys(next);
226 return next;
227 });
228 },277 },
229 [],278 [selectedKeys, setSelectedKeys],
230 );279 );
231 280
232 const clearSelection = useCallback(() => {281 const clearSelection = useCallback(() => {
@@ -280,14 +329,11 @@ export function ToolExplorer({ · 47 unmodified lines
280 setFocusedSourceName(null);329 setFocusedSourceName(null);
281 setFormSource(null);330 setFormSource(null);
282 void maybeLoadToolDetails(tool, true);331 void maybeLoadToolDetails(tool, true);
283 }, [maybeLoadToolDetails]);332 }, [maybeLoadToolDetails, setFocusedSourceName]);
284 333
285 const handleSourceClick = useCallback((sourceName: string) => {334 const handleSourceClick = useCallback((sourceName: string) => {
286 const source = sourceByName.get(sourceName);335 const source = sourceByName.get(sourceName);
287 setFocusedSourceName(sourceName);336 setSourceFocusState(sourceName, source ?? null);
288 setFocusedToolPath(null);
289 // Open the source form panel directly — no separate "view" mode
290 setFormSource(source ?? null);
291 // Ensure the source group is expanded337 // Ensure the source group is expanded
292 setExpandedKeys((prev) => {338 setExpandedKeys((prev) => {
293 const key = `source:${sourceName}`;339 const key = `source:${sourceName}`;
@@ -299,10 +345,9 @@ export function ToolExplorer({ · 5 unmodified lines
299 }, [sourceByName]);345 }, [sourceByName]);
300 346
301 const handleAddSource = useCallback(() => {347 const handleAddSource = useCallback(() => {
348 setSourceFocusState(null, null);
302 setFormSource("new");349 setFormSource("new");
303 setFocusedToolPath(null);350 }, [setFocusedSourceName, setSourceFocusState]);
304 setFocusedSourceName(null);
305 }, []);
306 351
307 const handleSourceFormClose = useCallback(() => {352 const handleSourceFormClose = useCallback(() => {
308 setFormSource(null);353 setFormSource(null);
@@ -311,10 +356,8 @@ export function ToolExplorer({ · 2 unmodified lines
311 const handleSourceFormAdded = useCallback((source: ToolSourceRecord) => {356 const handleSourceFormAdded = useCallback((source: ToolSourceRecord) => {
312 onSourceAdded?.(source);357 onSourceAdded?.(source);
313 // After adding, show the new source in the form358 // After adding, show the new source in the form
314 setFormSource(null);359 setSourceFocusState(source.name, source);
315 setFocusedSourceName(source.name);360 }, [onSourceAdded, setSourceFocusState]);
316 setFocusedToolPath(null);
317 }, [onSourceAdded]);
318 361
319 const focusedTool = useMemo(() => {362 const focusedTool = useMemo(() => {
320 if (!focusedToolPath) return null;363 if (!focusedToolPath) return null;
@@ -328,7 +371,7 @@ export function ToolExplorer({ · 7 unmodified lines
328 371
329 // Auto-focus first tool when tools arrive372 // Auto-focus first tool when tools arrive
330 useEffect(() => {373 useEffect(() => {
331 if (!focusedToolPath && searchedTools.length > 0) {374 if (!focusedToolPath && !focusedSourceName && searchedTools.length > 0) {
332 const first = searchedTools[0];375 const first = searchedTools[0];
333 setFocusedToolPath(first.path);376 setFocusedToolPath(first.path);
334 void maybeLoadToolDetails(first, true);377 void maybeLoadToolDetails(first, true);
@@ -336,6 +379,16 @@ export function ToolExplorer({ · 1 unmodified line
336 // eslint-disable-next-line react-hooks/exhaustive-deps379 // eslint-disable-next-line react-hooks/exhaustive-deps
337 }, [searchedTools.length > 0]);380 }, [searchedTools.length > 0]);
338 381
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
339 const loadingRows = useMemo(() => {392 const loadingRows = useMemo(() => {
340 if (search.length > 0) return [];393 if (search.length > 0) return [];
341 const visibleLoadingSources = loadingSourceSet.size === 0394 const visibleLoadingSources = loadingSourceSet.size === 0
executor/apps/web/src/components/tools/policies.tsx+97−91
@@ -53,9 +53,9 @@ import type { · 52 unmodified lines
53 ArgumentCondition,53 ArgumentCondition,
54 ArgumentConditionOperator,54 ArgumentConditionOperator,
55 ToolDescriptor,55 ToolDescriptor,
56 ToolRoleBindingRecord,56 ToolPolicyAssignmentRecord,
57 ToolRoleRuleRecord,57 ToolPolicyRuleRecord,
58 ToolRoleRecord,58 ToolPolicySetRecord,
59} from "@/lib/types";59} from "@/lib/types";
60import type { Id } from "@executor/database/convex/_generated/dataModel";60import type { Id } from "@executor/database/convex/_generated/dataModel";
61import { workspaceQueryArgs } from "@/lib/workspace/query-args";61import { workspaceQueryArgs } from "@/lib/workspace/query-args";
@@ -94,20 +94,20 @@ interface RoleFormState { · 32 unmodified lines
94}94}
95 95
96interface RoleRuleFormState {96interface RoleRuleFormState {
97 selectorType: ToolRoleRuleRecord["selectorType"];97 selectorType: ToolPolicyRuleRecord["selectorType"];
98 sourceKey: string;98 sourceKey: string;
99 resourcePattern: string;99 resourcePattern: string;
100 matchType: ToolRoleRuleRecord["matchType"];100 matchType: ToolPolicyRuleRecord["matchType"];
101 effect: ToolRoleRuleRecord["effect"];101 effect: ToolPolicyRuleRecord["effect"];
102 approvalMode: ToolRoleRuleRecord["approvalMode"];102 approvalMode: ToolPolicyRuleRecord["approvalMode"];
103 priority: string;103 priority: string;
104}104}
105 105
106interface RoleBindingFormState {106interface RoleBindingFormState {
107 scopeType: ToolRoleBindingRecord["scopeType"];107 scopeType: ToolPolicyAssignmentRecord["scopeType"];
108 targetAccountId: string;108 targetAccountId: string;
109 clientId: string;109 clientId: string;
110 status: ToolRoleBindingRecord["status"];110 status: ToolPolicyAssignmentRecord["status"];
111}111}
112 112
113interface OrganizationMemberListItem {113interface OrganizationMemberListItem {
@@ -343,7 +343,7 @@ function isDirectToolPolicy(policy: ToolPolicyRecord): boolean { · 229 unmodified lines
343 );343 );
344}344}
345 345
346function roleRulePattern(rule: ToolRoleRuleRecord): string {346function roleRulePattern(rule: ToolPolicyRuleRecord): string {
347 if (rule.selectorType === "all") return "*";347 if (rule.selectorType === "all") return "*";
348 if (rule.selectorType === "source") return rule.sourceKey ?? "";348 if (rule.selectorType === "source") return rule.sourceKey ?? "";
349 if (rule.selectorType === "namespace") return rule.namespacePattern ?? "";349 if (rule.selectorType === "namespace") return rule.namespacePattern ?? "";
@@ -837,7 +837,7 @@ function PolicyCard({ · 487 unmodified lines
837 </button>837 </button>
838 </TooltipTrigger>838 </TooltipTrigger>
839 <TooltipContent side="left" className="text-xs">839 <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)"}
841 </TooltipContent>841 </TooltipContent>
842 </Tooltip>842 </Tooltip>
843 </TooltipProvider>843 </TooltipProvider>
@@ -869,12 +869,12 @@ export function PoliciesPanel({ · 25 unmodified lines
869 869
870 const listArgs = workspaceQueryArgs(context);870 const listArgs = workspaceQueryArgs(context);
871 const policiesQuery = useQuery(convexApi.workspace.listToolPolicies, listArgs);871 const policiesQuery = useQuery(convexApi.workspace.listToolPolicies, listArgs);
872 const upsertToolRole = useMutation(convexApi.workspace.upsertToolRole);872 const upsertToolPolicySet = useMutation(convexApi.workspace.upsertToolPolicySet);
873 const upsertToolRoleRule = useMutation(convexApi.workspace.upsertToolRoleRule);873 const upsertToolPolicyRule = useMutation(convexApi.workspace.upsertToolPolicyRule);
874 const upsertToolRoleBinding = useMutation(convexApi.workspace.upsertToolRoleBinding);874 const upsertToolPolicyAssignment = useMutation(convexApi.workspace.upsertToolPolicyAssignment);
875 const deleteToolRole = useMutation(convexApi.workspace.deleteToolRole);875 const deleteToolPolicySet = useMutation(convexApi.workspace.deleteToolPolicySet);
876 const deleteToolRoleRule = useMutation(convexApi.workspace.deleteToolRoleRule);876 const deleteToolPolicyRule = useMutation(convexApi.workspace.deleteToolPolicyRule);
877 const deleteToolRoleBinding = useMutation(convexApi.workspace.deleteToolRoleBinding);877 const deleteToolPolicyAssignment = useMutation(convexApi.workspace.deleteToolPolicyAssignment);
878 878
879 const loading = Boolean(context) && policiesQuery === undefined;879 const loading = Boolean(context) && policiesQuery === undefined;
880 const policies = useMemo(() => (policiesQuery ?? []) as ToolPolicyRecord[], [policiesQuery]);880 const policies = useMemo(() => (policiesQuery ?? []) as ToolPolicyRecord[], [policiesQuery]);
@@ -902,10 +902,10 @@ export function PoliciesPanel({ · 21 unmodified lines
902 const canManageRoles = selfMembership?.role === "owner" || selfMembership?.role === "admin";902 const canManageRoles = selfMembership?.role === "owner" || selfMembership?.role === "admin";
903 903
904 const rolesQuery = useQuery(904 const rolesQuery = useQuery(
905 convexApi.workspace.listToolRoles,905 convexApi.workspace.listToolPolicySets,
906 context && canManageRoles ? listArgs : "skip",906 context && canManageRoles ? listArgs : "skip",
907 );907 );
908 const roleItems = useMemo(() => ((rolesQuery ?? []) as ToolRoleRecord[]), [rolesQuery]);908 const roleItems = useMemo(() => ((rolesQuery ?? []) as ToolPolicySetRecord[]), [rolesQuery]);
909 const activeRoleId = useMemo(() => {909 const activeRoleId = useMemo(() => {
910 if (!canManageRoles) {910 if (!canManageRoles) {
911 return null;911 return null;
@@ -919,13 +919,13 @@ export function PoliciesPanel({ · 7 unmodified lines
919 }, [canManageRoles, roleItems, selectedRoleId]);919 }, [canManageRoles, roleItems, selectedRoleId]);
920 920
921 const bindingsQuery = useQuery(921 const bindingsQuery = useQuery(
922 convexApi.workspace.listToolRoleBindings,922 convexApi.workspace.listToolPolicyAssignments,
923 context && canManageRoles ? listArgs : "skip",923 context && canManageRoles ? listArgs : "skip",
924 );924 );
925 const bindingItems = useMemo(() => ((bindingsQuery ?? []) as ToolRoleBindingRecord[]), [bindingsQuery]);925 const bindingItems = useMemo(() => ((bindingsQuery ?? []) as ToolPolicyAssignmentRecord[]), [bindingsQuery]);
926 926
927 const roleRulesQuery = useQuery(927 const roleRulesQuery = useQuery(
928 convexApi.workspace.listToolRoleRules,928 convexApi.workspace.listToolPolicyRules,
929 context && canManageRoles && activeRoleId929 context && canManageRoles && activeRoleId
930 ? {930 ? {
931 workspaceId: context.workspaceId,931 workspaceId: context.workspaceId,
@@ -935,7 +935,7 @@ export function PoliciesPanel({ · 3 unmodified lines
935 : "skip",935 : "skip",
936 );936 );
937 const selectedRoleRules = useMemo(937 const selectedRoleRules = useMemo(
938 () => ((roleRulesQuery ?? []) as ToolRoleRuleRecord[]),938 () => ((roleRulesQuery ?? []) as ToolPolicyRuleRecord[]),
939 [roleRulesQuery],939 [roleRulesQuery],
940 );940 );
941 941
@@ -1024,7 +1024,7 @@ export function PoliciesPanel({ · 82 unmodified lines
1024 : "organization" as const;1024 : "organization" as const;
1025 const targetAccountId = form.scope === "personal" ? context.accountId : undefined;1025 const targetAccountId = form.scope === "personal" ? context.accountId : undefined;
1026 1026
1027 await upsertToolRole({1027 await upsertToolPolicySet({
1028 workspaceId: context.workspaceId,1028 workspaceId: context.workspaceId,
1029 sessionId: context.sessionId,1029 sessionId: context.sessionId,
1030 id: roleId,1030 id: roleId,
@@ -1033,7 +1033,7 @@ export function PoliciesPanel({ · 2 unmodified lines
1033 });1033 });
1034 1034
1035 try {1035 try {
1036 await upsertToolRoleRule({1036 await upsertToolPolicyRule({
1037 workspaceId: context.workspaceId,1037 workspaceId: context.workspaceId,
1038 sessionId: context.sessionId,1038 sessionId: context.sessionId,
1039 id: ruleId,1039 id: ruleId,
@@ -1048,7 +1048,7 @@ export function PoliciesPanel({ · 8 unmodified lines
1048 priority,1048 priority,
1049 });1049 });
1050 1050
1051 await upsertToolRoleBinding({1051 await upsertToolPolicyAssignment({
1052 workspaceId: context.workspaceId,1052 workspaceId: context.workspaceId,
1053 sessionId: context.sessionId,1053 sessionId: context.sessionId,
1054 id: bindingId,1054 id: bindingId,
@@ -1060,7 +1060,7 @@ export function PoliciesPanel({ · 5 unmodified lines
1060 });1060 });
1061 } catch (error) {1061 } catch (error) {
1062 try {1062 try {
1063 await deleteToolRole({1063 await deleteToolPolicySet({
1064 workspaceId: context.workspaceId,1064 workspaceId: context.workspaceId,
1065 sessionId: context.sessionId,1065 sessionId: context.sessionId,
1066 roleId,1066 roleId,
@@ -1084,7 +1084,7 @@ export function PoliciesPanel({ · 17 unmodified lines
1084 const handleDelete = useCallback(async (policy: ToolPolicyRecord) => {1084 const handleDelete = useCallback(async (policy: ToolPolicyRecord) => {
1085 if (!context) return;1085 if (!context) return;
1086 if (!isDirectToolPolicy(policy)) {1086 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.");
1088 return;1088 return;
1089 }1089 }
1090 1090
@@ -1095,7 +1095,7 @@ export function PoliciesPanel({ · 4 unmodified lines
1095 1095
1096 setDeletingId(policy.id);1096 setDeletingId(policy.id);
1097 try {1097 try {
1098 await deleteToolRole({1098 await deleteToolPolicySet({
1099 workspaceId: context.workspaceId,1099 workspaceId: context.workspaceId,
1100 sessionId: context.sessionId,1100 sessionId: context.sessionId,
1101 roleId: policy.roleId,1101 roleId: policy.roleId,
@@ -1106,7 +1106,7 @@ export function PoliciesPanel({ · 4 unmodified lines
1106 } finally {1106 } finally {
1107 setDeletingId(null);1107 setDeletingId(null);
1108 }1108 }
1109 }, [context, deleteToolRole]);1109 }, [context, deleteToolPolicySet]);
1110 1110
1111 const handleCreateRole = useCallback(async () => {1111 const handleCreateRole = useCallback(async () => {
1112 if (!context) return;1112 if (!context) return;
@@ -1118,7 +1118,7 @@ export function PoliciesPanel({ · 5 unmodified lines
1118 1118
1119 setBusyRoleAction("create-role");1119 setBusyRoleAction("create-role");
1120 try {1120 try {
1121 const role = await upsertToolRole({1121 const role = await upsertToolPolicySet({
1122 workspaceId: context.workspaceId,1122 workspaceId: context.workspaceId,
1123 sessionId: context.sessionId,1123 sessionId: context.sessionId,
1124 name,1124 name,
@@ -1132,7 +1132,7 @@ export function PoliciesPanel({ · 7 unmodified lines
1132 } finally {1132 } finally {
1133 setBusyRoleAction(null);1133 setBusyRoleAction(null);
1134 }1134 }
1135 }, [context, roleForm, upsertToolRole]);1135 }, [context, roleForm, upsertToolPolicySet]);
1136 1136
1137 const handleCreateRoleRule = useCallback(async () => {1137 const handleCreateRoleRule = useCallback(async () => {
1138 if (!context || !activeRoleId) return;1138 if (!context || !activeRoleId) return;
@@ -1157,7 +1157,7 @@ export function PoliciesPanel({ · 18 unmodified lines
1157 1157
1158 setBusyRoleAction("create-rule");1158 setBusyRoleAction("create-rule");
1159 try {1159 try {
1160 await upsertToolRoleRule({1160 await upsertToolPolicyRule({
1161 workspaceId: context.workspaceId,1161 workspaceId: context.workspaceId,
1162 sessionId: context.sessionId,1162 sessionId: context.sessionId,
1163 roleId: activeRoleId,1163 roleId: activeRoleId,
@@ -1176,7 +1176,7 @@ export function PoliciesPanel({ · 12 unmodified lines
1176 } finally {1176 } finally {
1177 setBusyRoleAction(null);1177 setBusyRoleAction(null);
1178 }1178 }
1179 }, [activeRoleId, context, roleRuleForm, upsertToolRoleRule]);1179 }, [activeRoleId, context, roleRuleForm, upsertToolPolicyRule]);
1180 1180
1181 const handleCreateRoleBinding = useCallback(async () => {1181 const handleCreateRoleBinding = useCallback(async () => {
1182 if (!context || !activeRoleId) return;1182 if (!context || !activeRoleId) return;
@@ -1190,7 +1190,7 @@ export function PoliciesPanel({ · 7 unmodified lines
1190 1190
1191 setBusyRoleAction("create-binding");1191 setBusyRoleAction("create-binding");
1192 try {1192 try {
1193 await upsertToolRoleBinding({1193 await upsertToolPolicyAssignment({
1194 workspaceId: context.workspaceId,1194 workspaceId: context.workspaceId,
1195 sessionId: context.sessionId,1195 sessionId: context.sessionId,
1196 roleId: activeRoleId,1196 roleId: activeRoleId,
@@ -1208,14 +1208,14 @@ export function PoliciesPanel({ · 11 unmodified lines
1208 } finally {1208 } finally {
1209 setBusyRoleAction(null);1209 setBusyRoleAction(null);
1210 }1210 }
1211 }, [activeRoleId, context, roleBindingForm, upsertToolRoleBinding]);1211 }, [activeRoleId, context, roleBindingForm, upsertToolPolicyAssignment]);
1212 1212
1213 const handleDeleteRole = useCallback(async (roleId: string) => {1213 const handleDeleteRole = useCallback(async (roleId: string) => {
1214 if (!context) return;1214 if (!context) return;
1215 1215
1216 setBusyRoleAction(`delete-role:${roleId}`);1216 setBusyRoleAction(`delete-role:${roleId}`);
1217 try {1217 try {
1218 await deleteToolRole({1218 await deleteToolPolicySet({
1219 workspaceId: context.workspaceId,1219 workspaceId: context.workspaceId,
1220 sessionId: context.sessionId,1220 sessionId: context.sessionId,
1221 roleId,1221 roleId,
@@ -1229,14 +1229,14 @@ export function PoliciesPanel({ · 7 unmodified lines
1229 } finally {1229 } finally {
1230 setBusyRoleAction(null);1230 setBusyRoleAction(null);
1231 }1231 }
1232 }, [activeRoleId, context, deleteToolRole]);1232 }, [activeRoleId, context, deleteToolPolicySet]);
1233 1233
1234 const handleDeleteRule = useCallback(async (ruleId: string) => {1234 const handleDeleteRule = useCallback(async (ruleId: string) => {
1235 if (!context || !activeRoleId) return;1235 if (!context || !activeRoleId) return;
1236 1236
1237 setBusyRoleAction(`delete-rule:${ruleId}`);1237 setBusyRoleAction(`delete-rule:${ruleId}`);
1238 try {1238 try {
1239 await deleteToolRoleRule({1239 await deleteToolPolicyRule({
1240 workspaceId: context.workspaceId,1240 workspaceId: context.workspaceId,
1241 sessionId: context.sessionId,1241 sessionId: context.sessionId,
1242 roleId: activeRoleId,1242 roleId: activeRoleId,
@@ -1248,14 +1248,14 @@ export function PoliciesPanel({ · 5 unmodified lines
1248 } finally {1248 } finally {
1249 setBusyRoleAction(null);1249 setBusyRoleAction(null);
1250 }1250 }
1251 }, [activeRoleId, context, deleteToolRoleRule]);1251 }, [activeRoleId, context, deleteToolPolicyRule]);
1252 1252
1253 const handleDeleteBinding = useCallback(async (bindingId: string) => {1253 const handleDeleteBinding = useCallback(async (bindingId: string) => {
1254 if (!context) return;1254 if (!context) return;
1255 1255
1256 setBusyRoleAction(`delete-binding:${bindingId}`);1256 setBusyRoleAction(`delete-binding:${bindingId}`);
1257 try {1257 try {
1258 await deleteToolRoleBinding({1258 await deleteToolPolicyAssignment({
1259 workspaceId: context.workspaceId,1259 workspaceId: context.workspaceId,
1260 sessionId: context.sessionId,1260 sessionId: context.sessionId,
1261 bindingId,1261 bindingId,
@@ -1266,7 +1266,7 @@ export function PoliciesPanel({ · 4 unmodified lines
1266 } finally {1266 } finally {
1267 setBusyRoleAction(null);1267 setBusyRoleAction(null);
1268 }1268 }
1269 }, [context, deleteToolRoleBinding]);1269 }, [context, deleteToolPolicyAssignment]);
1270 1270
1271 // ── Group policies by decision for display ──1271 // ── Group policies by decision for display ──
1272 1272
@@ -1305,7 +1305,7 @@ export function PoliciesPanel({ · 32 unmodified lines
1305 disabled={!context}1305 disabled={!context}
1306 >1306 >
1307 <Plus className="h-3.5 w-3.5" />1307 <Plus className="h-3.5 w-3.5" />
1308 New Policy1308 Quick Policy
1309 </Button>1309 </Button>
1310 </div>1310 </div>
1311 1311
@@ -1375,28 +1375,31 @@ export function PoliciesPanel({ · 63 unmodified lines
1375 1375
1376 <Separator className="bg-border/40" />1376 <Separator className="bg-border/40" />
1377 1377
1378 <div className="space-y-3">1378 <details className="group rounded-lg border border-border/60 bg-card/40">
1379 <div className="flex items-center justify-between">1379 <summary className="flex cursor-pointer list-none items-center justify-between gap-3 px-3 py-2.5">
1380 <div>1380 <div>
1381 <h4 className="text-sm font-medium">Role Manager</h4>1381 <h4 className="text-sm font-medium">Policy Set Manager</h4>
1382 <p className="text-[11px] text-muted-foreground mt-1">1382 <p className="text-[11px] text-muted-foreground mt-0.5">
1383 Manage reusable tool roles, rule selectors, and scoped bindings.1383 Advanced: reusable policy sets with rules and assignments.
1384 </p>1384 </p>
1385 </div>1385 </div>
1386 </div>1386 <span className="text-[10px] text-muted-foreground group-open:hidden">Expand</span>
1387 1387 <span className="text-[10px] text-muted-foreground hidden group-open:inline">Collapse</span>
1388 {!canManageRoles ? (1388 </summary>
1389 <div className="rounded-lg border border-dashed border-border/50 p-4 text-xs text-muted-foreground">1389
1390 Organization owner/admin role required to manage tool roles.1390 <div className="px-3 pb-3">
1391 </div>1391 {!canManageRoles ? (
1392 ) : (1392 <div className="rounded-lg border border-dashed border-border/50 p-4 text-xs text-muted-foreground">
1393 <div className="grid grid-cols-1 gap-4 lg:grid-cols-[280px_1fr]">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]">
1394 <div className="space-y-3 rounded-lg border border-border/60 bg-card p-3">1397 <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>
1396 <Input1399 <Input
1397 value={roleForm.name}1400 value={roleForm.name}
1398 onChange={(event) => setRoleForm((state) => ({ ...state, name: event.target.value }))}1401 onChange={(event) => setRoleForm((state) => ({ ...state, name: event.target.value }))}
1399 placeholder="Role name"1402 placeholder="Policy set name"
1400 className="h-8 text-xs"1403 className="h-8 text-xs"
1401 />1404 />
1402 <Input1405 <Input
@@ -1411,14 +1414,14 @@ export function PoliciesPanel({ · 8 unmodified lines
1411 onClick={handleCreateRole}1414 onClick={handleCreateRole}
1412 disabled={busyRoleAction === "create-role"}1415 disabled={busyRoleAction === "create-role"}
1413 >1416 >
1414 {busyRoleAction === "create-role" ? "Creating..." : "Create role"}1417 {busyRoleAction === "create-role" ? "Creating..." : "Create policy set"}
1415 </Button>1418 </Button>
1416 1419
1417 <Separator className="bg-border/40" />1420 <Separator className="bg-border/40" />
1418 1421
1419 <div className="space-y-1.5 max-h-[320px] overflow-y-auto">1422 <div className="space-y-1.5 max-h-[320px] overflow-y-auto">
1420 {roleItems.length === 0 ? (1423 {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>
1422 ) : (1425 ) : (
1423 roleItems.map((role) => {1426 roleItems.map((role) => {
1424 const bindingCount = bindingItems.filter((binding) => binding.roleId === role.id).length;1427 const bindingCount = bindingItems.filter((binding) => binding.roleId === role.id).length;
@@ -1429,7 +1432,7 @@ export function PoliciesPanel({ · 4 unmodified lines
1429 onClick={() => setSelectedRoleId(role.id)}1432 onClick={() => setSelectedRoleId(role.id)}
1430 className={cn(1433 className={cn(
1431 "w-full rounded-md border px-2 py-1.5 text-left",1434 "w-full rounded-md border px-2 py-1.5 text-left",
1432 selectedRoleId === role.id1435 activeRoleId === role.id
1433 ? "border-primary/40 bg-primary/5"1436 ? "border-primary/40 bg-primary/5"
1434 : "border-border/40 hover:bg-muted/30",1437 : "border-border/40 hover:bg-muted/30",
1435 )}1438 )}
@@ -1437,7 +1440,7 @@ export function PoliciesPanel({ · 1 unmodified line
1437 <div className="flex items-center justify-between gap-2">1440 <div className="flex items-center justify-between gap-2">
1438 <span className="truncate text-xs font-medium">{role.name}</span>1441 <span className="truncate text-xs font-medium">{role.name}</span>
1439 <Badge variant="outline" className="text-[9px] px-1.5 py-0">1442 <Badge variant="outline" className="text-[9px] px-1.5 py-0">
1440 {bindingCount} bind1443 {bindingCount} assign
1441 </Badge>1444 </Badge>
1442 </div>1445 </div>
1443 {role.description ? (1446 {role.description ? (
@@ -1452,7 +1455,7 @@ export function PoliciesPanel({ · 8 unmodified lines
1452 1455
1453 <div className="space-y-4 rounded-lg border border-border/60 bg-card p-4">1456 <div className="space-y-4 rounded-lg border border-border/60 bg-card p-4">
1454 {!selectedRole ? (1457 {!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>
1456 ) : (1459 ) : (
1457 <>1460 <>
1458 <div className="flex items-start justify-between gap-3">1461 <div className="flex items-start justify-between gap-3">
@@ -1470,7 +1473,7 @@ export function PoliciesPanel({ · 11 unmodified lines
1470 onClick={() => handleDeleteRole(selectedRole.id)}1473 onClick={() => handleDeleteRole(selectedRole.id)}
1471 disabled={busyRoleAction === `delete-role:${selectedRole.id}`}1474 disabled={busyRoleAction === `delete-role:${selectedRole.id}`}
1472 >1475 >
1473 {busyRoleAction === `delete-role:${selectedRole.id}` ? "Deleting..." : "Delete role"}1476 {busyRoleAction === `delete-role:${selectedRole.id}` ? "Deleting..." : "Delete set"}
1474 </Button>1477 </Button>
1475 </div>1478 </div>
1476 1479
@@ -1520,7 +1523,7 @@ export function PoliciesPanel({ · 43 unmodified lines
1520 value={roleRuleForm.selectorType}1523 value={roleRuleForm.selectorType}
1521 onValueChange={(value) => setRoleRuleForm((state) => ({1524 onValueChange={(value) => setRoleRuleForm((state) => ({
1522 ...state,1525 ...state,
1523 selectorType: value as ToolRoleRuleRecord["selectorType"],1526 selectorType: value as ToolPolicyRuleRecord["selectorType"],
1524 }))}1527 }))}
1525 >1528 >
1526 <SelectTrigger className="h-8 text-xs">1529 <SelectTrigger className="h-8 text-xs">
@@ -1564,7 +1567,7 @@ export function PoliciesPanel({ · 37 unmodified lines
1564 value={roleRuleForm.effect}1567 value={roleRuleForm.effect}
1565 onValueChange={(value) => setRoleRuleForm((state) => ({1568 onValueChange={(value) => setRoleRuleForm((state) => ({
1566 ...state,1569 ...state,
1567 effect: value as ToolRoleRuleRecord["effect"],1570 effect: value as ToolPolicyRuleRecord["effect"],
1568 }))}1571 }))}
1569 >1572 >
1570 <SelectTrigger className="h-8 text-xs">1573 <SelectTrigger className="h-8 text-xs">
@@ -1580,7 +1583,7 @@ export function PoliciesPanel({ · 9 unmodified lines
1580 value={roleRuleForm.approvalMode}1583 value={roleRuleForm.approvalMode}
1581 onValueChange={(value) => setRoleRuleForm((state) => ({1584 onValueChange={(value) => setRoleRuleForm((state) => ({
1582 ...state,1585 ...state,
1583 approvalMode: value as ToolRoleRuleRecord["approvalMode"],1586 approvalMode: value as ToolPolicyRuleRecord["approvalMode"],
1584 }))}1587 }))}
1585 >1588 >
1586 <SelectTrigger className="h-8 text-xs">1589 <SelectTrigger className="h-8 text-xs">
@@ -1615,7 +1618,7 @@ export function PoliciesPanel({ · 28 unmodified lines
1615 1618
1616 <div className="space-y-2">1619 <div className="space-y-2">
1617 <div className="flex items-center justify-between">1620 <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>
1619 <span className="text-[10px] text-muted-foreground">{selectedRoleBindings.length}</span>1622 <span className="text-[10px] text-muted-foreground">{selectedRoleBindings.length}</span>
1620 </div>1623 </div>
1621 <div className="space-y-1.5 max-h-[200px] overflow-y-auto">1624 <div className="space-y-1.5 max-h-[200px] overflow-y-auto">
@@ -1657,7 +1660,7 @@ export function PoliciesPanel({ · 35 unmodified lines
1657 value={roleBindingForm.scopeType}1660 value={roleBindingForm.scopeType}
1658 onValueChange={(value) => setRoleBindingForm((state) => ({1661 onValueChange={(value) => setRoleBindingForm((state) => ({
1659 ...state,1662 ...state,
1660 scopeType: value as ToolRoleBindingRecord["scopeType"],1663 scopeType: value as ToolPolicyAssignmentRecord["scopeType"],
1661 }))}1664 }))}
1662 >1665 >
1663 <SelectTrigger className="h-8 text-xs">1666 <SelectTrigger className="h-8 text-xs">
@@ -1699,7 +1702,7 @@ export function PoliciesPanel({ · 35 unmodified lines
1699 value={roleBindingForm.status}1702 value={roleBindingForm.status}
1700 onValueChange={(value) => setRoleBindingForm((state) => ({1703 onValueChange={(value) => setRoleBindingForm((state) => ({
1701 ...state,1704 ...state,
1702 status: value as ToolRoleBindingRecord["status"],1705 status: value as ToolPolicyAssignmentRecord["status"],
1703 }))}1706 }))}
1704 >1707 >
1705 <SelectTrigger className="h-8 text-xs">1708 <SelectTrigger className="h-8 text-xs">
@@ -1717,7 +1720,7 @@ export function PoliciesPanel({ · 11 unmodified lines
1717 onClick={handleCreateRoleBinding}1720 onClick={handleCreateRoleBinding}
1718 disabled={busyRoleAction === "create-binding"}1721 disabled={busyRoleAction === "create-binding"}
1719 >1722 >
1720 {busyRoleAction === "create-binding" ? "Adding binding..." : "Add binding"}1723 {busyRoleAction === "create-binding" ? "Adding assignment..." : "Add assignment"}
1721 </Button>1724 </Button>
1722 </div>1725 </div>
1723 </div>1726 </div>
@@ -1725,8 +1728,9 @@ export function PoliciesPanel({ · 1 unmodified line
1725 )}1728 )}
1726 </div>1729 </div>
1727 </div>1730 </div>
1728 )}1731 )}
1729 </div>1732 </div>
1733 </details>
1730 1734
1731 {/* Create Policy Dialog */}1735 {/* Create Policy Dialog */}
1732 <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>1736 <Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
@@ -1924,26 +1928,28 @@ export function PoliciesPanel({ · 191 unmodified lines
1924 />1928 />
1925 </div>1929 </div>
1926 1930
1927 {/* Advanced filters */}1931 <details className="rounded-md border border-border/40 bg-muted/10 p-3">
1928 <div className="space-y-3 rounded-md border border-border/40 bg-muted/10 p-3">1932 <summary className="cursor-pointer text-xs text-muted-foreground">Optional filters</summary>
1929 <div className="space-y-1">1933 <div className="mt-3 space-y-3">
1930 <Label className="text-xs text-muted-foreground">Client ID</Label>1934 <div className="space-y-1">
1931 <Input1935 <Label className="text-xs text-muted-foreground">Client ID</Label>
1932 value={form.clientId}1936 <Input
1933 onChange={(event) => setForm((state) => ({ ...state, clientId: event.target.value }))}1937 value={form.clientId}
1934 placeholder="optional client identifier"1938 onChange={(event) => setForm((state) => ({ ...state, clientId: event.target.value }))}
1935 className="h-8 text-xs font-mono bg-background"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 }))}
1936 />1950 />
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>
1940 </div>1951 </div>
1941 1952 </details>
1942 <ArgumentConditionsEditor
1943 conditions={form.argumentConditions}
1944 onChange={(argumentConditions) => setForm((state) => ({ ...state, argumentConditions }))}
1945 />
1946 </div>
1947 </div>1953 </div>
1948 1954
1949 <DialogFooter>1955 <DialogFooter>
executor/apps/web/src/components/tools/view.tsx+154−35
@@ -1,6 +1,8 @@
1"use client";1"use client";
2 2
3import { useCallback, useMemo, useRef, useState } from "react";3import { useCallback, useEffect, useMemo, useRef, useState } from "react";
4import { parseAsNativeArrayOf, parseAsString, parseAsStringLiteral, useQueryStates } from "nuqs";
5import { useLocation, useNavigate } from "@/lib/router";
4import { Card, CardContent } from "@/components/ui/card";6import { Card, CardContent } from "@/components/ui/card";
5import { Skeleton } from "@/components/ui/skeleton";7import { Skeleton } from "@/components/ui/skeleton";
6import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";8import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
@@ -27,6 +29,7 @@ import { · 20 unmodified lines
27import { sourceLabel } from "@/lib/tool/source-utils";29import { sourceLabel } from "@/lib/tool/source-utils";
28import { workspaceQueryArgs } from "@/lib/workspace/query-args";30import { workspaceQueryArgs } from "@/lib/workspace/query-args";
29import type { SourceDialogMeta } from "@/components/tools/add/source-dialog";31import type { SourceDialogMeta } from "@/components/tools/add/source-dialog";
32import type { FilterApproval } from "@/components/tools/explorer-derived";
30 33
31// ── Optimistic source helpers ──34// ── Optimistic source helpers ──
32 35
@@ -80,34 +83,87 @@ function pruneStaleOps( · 47 unmodified lines
80 });83 });
81}84}
82 85
83type ToolsTab = "catalog" | "credentials" | "policies" | "editor";86type ToolsTab = "catalog" | "connections" | "policies" | "editor";
84const INVENTORY_REGENERATION_TOAST_ID = "tool-inventory-regeneration";87const INVENTORY_REGENERATION_TOAST_ID = "tool-inventory-regeneration";
85 88
86function parseInitialTab(tab?: string | null): ToolsTab {89function parseToolsTab(pathname: string): ToolsTab {
87 if (tab === "runner" || tab === "editor") {90 const segments = pathname.split("/").filter(Boolean);
88 return "editor";91 const last = segments.at(-1);
92
93 if (last === "catalog") {
94 return "catalog";
89 }95 }
90 if (tab === "catalog" || tab === "credentials") {96 if (last === "connections" || last === "credentials") {
91 return tab;97 return "connections";
92 }98 }
93 if (tab === "policies") {99 if (last === "policies") {
94 return "policies";100 return "policies";
95 }101 }
102 if (last === "editor" || last === "runner") {
103 return "editor";
104 }
105
96 return "catalog";106 return "catalog";
97}107}
98 108
109function normalizeCatalogApprovalFilter(value: string | null): FilterApproval {
110 if (value === "required" || value === "auto") {
111 return value;
112 }
113
114 return "all";
115}
116
117function 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
130function mapEmptyQueryValueToNull(value: string): string | null {
131 return value.length > 0 ? value : null;
132}
133
99// ── Tools View ──134// ── Tools View ──
100 135
101export function ToolsView({136export function ToolsView() {
102 initialSource,137 const navigate = useNavigate();
103 initialTab,138 const location = useLocation();
104}: {
105 initialSource?: string | null;
106 initialTab?: string | null;
107}) {
108 const { context, loading: sessionLoading } = useSession();139 const { context, loading: sessionLoading } = useSession();
109 const [selectedSource, setSelectedSource] = useState<string | null>(initialSource ?? null);140 const activeTab = useMemo(() => parseToolsTab(location.pathname), [location.pathname]);
110 const [activeTab, setActiveTab] = useState<ToolsTab>(parseInitialTab(initialTab));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 );
111 const [connectionDialogOpen, setConnectionDialogOpen] = useState(false);167 const [connectionDialogOpen, setConnectionDialogOpen] = useState(false);
112 const [connectionDialogEditing, setConnectionDialogEditing] = useState<CredentialRecord | null>(null);168 const [connectionDialogEditing, setConnectionDialogEditing] = useState<CredentialRecord | null>(null);
113 const [connectionDialogSourceKey, setConnectionDialogSourceKey] = useState<string | null>(null);169 const [connectionDialogSourceKey, setConnectionDialogSourceKey] = useState<string | null>(null);
@@ -316,6 +372,7 @@ export function ToolsView({ · 202 unmodified lines
316 372
317 const existingSourceNames = useMemo(() => new Set(sourceItems.map((source) => source.name)), [sourceItems]);373 const existingSourceNames = useMemo(() => new Set(sourceItems.map((source) => source.name)), [sourceItems]);
318 const warningsBySource = useMemo(() => warningsBySourceName(warnings), [warnings]);374 const warningsBySource = useMemo(() => warningsBySourceName(warnings), [warnings]);
375
319 const sourceDialogMeta = useMemo(() => {376 const sourceDialogMeta = useMemo(() => {
320 const bySource: Record<string, SourceDialogMeta> = {};377 const bySource: Record<string, SourceDialogMeta> = {};
321 for (const source of sourceItems) {378 for (const source of sourceItems) {
@@ -328,10 +385,10 @@ export function ToolsView({ · 6 unmodified lines
328 }385 }
329 return bySource;386 return bySource;
330 }, [sourceItems, sourceQuality, refreshingTools, warningsBySource]);387 }, [sourceItems, sourceQuality, refreshingTools, warningsBySource]);
331 const activeSource = selectedSource388 const activeSource = catalogSourceValue
332 && (sourceItems.some((source) => source.name === selectedSource) || toolSourceNames.has(selectedSource))389 && (sourceItems.some((source) => source.name === catalogSourceValue) || toolSourceNames.has(catalogSourceValue))
333 ? selectedSource390 ? catalogSourceValue
334 : null;391 : null;
335 392
336 const handleSourceAdded = useCallback((source: ToolSourceRecord) => {393 const handleSourceAdded = useCallback((source: ToolSourceRecord) => {
337 setOptimisticOps((ops) => [394 setOptimisticOps((ops) => [
@@ -340,13 +397,67 @@ export function ToolsView({ · 2 unmodified lines
340 ]);397 ]);
341 }, []);398 }, []);
342 399
400 const syncSourceToUrl = useCallback((sourceName: string) => {
401 void setCatalogQueryState({
402 source: sourceName,
403 }, {
404 history: "replace",
405 });
406 }, [setCatalogQueryState]);
407
343 const handleSourceDeleted = useCallback((sourceName: string) => {408 const handleSourceDeleted = useCallback((sourceName: string) => {
344 setOptimisticOps((ops) => [409 setOptimisticOps((ops) => [
345 ...ops,410 ...ops,
346 { kind: "remove", sourceName, removedAt: Date.now() },411 { kind: "remove", sourceName, removedAt: Date.now() },
347 ]);412 ]);
348 setSelectedSource((current) => (current === sourceName ? null : current));413 syncSourceToUrl(activeSource === sourceName ? "" : (activeSource ?? ""));
349 }, []);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]);
350 461
351 const handleRegenerateInventory = useCallback(async () => {462 const handleRegenerateInventory = useCallback(async () => {
352 if (!context || regenerationInFlight) {463 if (!context || regenerationInFlight) {
@@ -442,7 +553,7 @@ export function ToolsView({ · 89 unmodified lines
442 {loadingTools ? "..." : totalTools}553 {loadingTools ? "..." : totalTools}
443 </span>554 </span>
444 </TabsTrigger>555 </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">
446 Connections557 Connections
447 {credentials && (558 {credentials && (
448 <span className="ml-1.5 text-[10px] font-mono text-muted-foreground">559 <span className="ml-1.5 text-[10px] font-mono text-muted-foreground">
@@ -478,16 +589,24 @@ export function ToolsView({ · 29 unmodified lines
478 sourceHasMoreTools={sourceHasMoreTools}589 sourceHasMoreTools={sourceHasMoreTools}
479 sourceLoadingMoreTools={sourceLoadingMoreTools}590 sourceLoadingMoreTools={sourceLoadingMoreTools}
480 onLoadMoreToolsForSource={loadMoreToolsForSource}591 onLoadMoreToolsForSource={loadMoreToolsForSource}
481 loading={loadingTools}592 loading={loadingTools}
482 sourceDialogMeta={sourceDialogMeta}593 sourceDialogMeta={sourceDialogMeta}
483 sourceAuthProfiles={sourceAuthProfiles}594 sourceAuthProfiles={sourceAuthProfiles}
484 existingSourceNames={existingSourceNames}595 existingSourceNames={existingSourceNames}
485 onSourceDeleted={handleSourceDeleted}596 onSourceDeleted={handleSourceDeleted}
486 onLoadToolDetails={loadToolDetails}597 onLoadToolDetails={loadToolDetails}
487 warnings={warnings}598 warnings={warnings}
488 initialSource={initialSource}599 activeSource={activeSource}
489 activeSource={activeSource}600 searchValue={catalogSearchValue}
490 onActiveSourceChange={setSelectedSource}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}
491 onRegenerate={handleRegenerateInventory}610 onRegenerate={handleRegenerateInventory}
492 inventoryState={inventoryStatus?.state}611 inventoryState={inventoryStatus?.state}
493 inventoryError={inventoryStatus?.error}612 inventoryError={inventoryStatus?.error}
@@ -503,7 +622,7 @@ export function ToolsView({ · 9 unmodified lines
503 </Card>622 </Card>
504 </TabsContent>623 </TabsContent>
505 624
506 <TabsContent value="credentials" className="mt-4">625 <TabsContent value="connections" className="mt-4">
507 <CredentialsPanel626 <CredentialsPanel
508 sources={sourceItems}627 sources={sourceItems}
509 credentials={credentialItems}628 credentials={credentialItems}
executor/apps/web/src/lib/types.ts+6−6
@@ -1,9 +1,9 @@
1import type { Id } from "@executor/database/convex/_generated/dataModel";1import type { Id } from "@executor/database/convex/_generated/dataModel";
2import type {2import type {
3 ToolPolicyRecord as CoreToolPolicyRecord,3 ToolPolicyRecord as CoreToolPolicyRecord,
4 ToolRoleBindingRecord as CoreToolRoleBindingRecord,4 ToolPolicyAssignmentRecord as CoreToolPolicyAssignmentRecord,
5 ToolRoleRecord as CoreToolRoleRecord,5 ToolPolicySetRecord as CoreToolPolicySetRecord,
6 ToolRoleRuleRecord as CoreToolRoleRuleRecord,6 ToolPolicyRuleRecord as CoreToolPolicyRuleRecord,
7} from "@executor/core/types";7} from "@executor/core/types";
8 8
9// ── Shared types (inlined from @executor/contracts) ──────────────────────────9// ── Shared types (inlined from @executor/contracts) ──────────────────────────
@@ -85,9 +85,9 @@ export interface TaskEventRecord { · 75 unmodified lines
85}85}
86 86
87export type ToolPolicyRecord = CoreToolPolicyRecord;87export type ToolPolicyRecord = CoreToolPolicyRecord;
88export type ToolRoleRecord = CoreToolRoleRecord;88export type ToolPolicySetRecord = CoreToolPolicySetRecord;
89export type ToolRoleRuleRecord = CoreToolRoleRuleRecord;89export type ToolPolicyRuleRecord = CoreToolPolicyRuleRecord;
90export type ToolRoleBindingRecord = CoreToolRoleBindingRecord;90export type ToolPolicyAssignmentRecord = CoreToolPolicyAssignmentRecord;
91 91
92export interface CredentialRecord {92export interface CredentialRecord {
93 id: string;93 id: string;
executor/apps/web/src/routeTree.gen.ts+104−20
@@ -9,7 +9,6 @@ · 8 unmodified lines
9// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.9// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
10 10
11import { Route as rootRouteImport } from './routes/__root'11import { Route as rootRouteImport } from './routes/__root'
12import { Route as ToolsRouteImport } from './routes/tools'
13import { Route as TasksRouteImport } from './routes/tasks'12import { Route as TasksRouteImport } from './routes/tasks'
14import { Route as StaticAppShellRouteImport } from './routes/static-app-shell'13import { Route as StaticAppShellRouteImport } from './routes/static-app-shell'
15import { Route as SignUpRouteImport } from './routes/sign-up'14import { Route as SignUpRouteImport } from './routes/sign-up'
@@ -25,6 +24,11 @@ import { Route as CallbackRouteImport } from './routes/callback' · 9 unmodified lines
25import { Route as BillingRouteImport } from './routes/billing'24import { Route as BillingRouteImport } from './routes/billing'
26import { Route as ApprovalsRouteImport } from './routes/approvals'25import { Route as ApprovalsRouteImport } from './routes/approvals'
27import { Route as IndexRouteImport } from './routes/index'26import { Route as IndexRouteImport } from './routes/index'
27import { Route as ToolsIndexRouteImport } from './routes/tools/index'
28import { Route as ToolsPoliciesRouteImport } from './routes/tools/policies'
29import { Route as ToolsEditorRouteImport } from './routes/tools/editor'
30import { Route as ToolsConnectionsRouteImport } from './routes/tools/connections'
31import { Route as ToolsCatalogRouteImport } from './routes/tools/catalog'
28import { Route as McpOauthStartRouteImport } from './routes/mcp/oauth/start'32import { Route as McpOauthStartRouteImport } from './routes/mcp/oauth/start'
29import { Route as McpOauthResultRouteImport } from './routes/mcp/oauth/result'33import { Route as McpOauthResultRouteImport } from './routes/mcp/oauth/result'
30import { Route as McpOauthDetectRouteImport } from './routes/mcp/oauth/detect'34import { Route as McpOauthDetectRouteImport } from './routes/mcp/oauth/detect'
@@ -34,11 +38,6 @@ import { Route as ApiOpenapiInspectRouteImport } from './routes/api/openapi/insp · 3 unmodified lines
34import { Route as ApiAuthCallbackRouteImport } from './routes/api/auth/callback'38import { Route as ApiAuthCallbackRouteImport } from './routes/api/auth/callback'
35import { Route as ApiAuthAnonymousTokenRouteImport } from './routes/api/auth/anonymous-token'39import { Route as ApiAuthAnonymousTokenRouteImport } from './routes/api/auth/anonymous-token'
36 40
37const ToolsRoute = ToolsRouteImport.update({
38 id: '/tools',
39 path: '/tools',
40 getParentRoute: () => rootRouteImport,
41} as any)
42const TasksRoute = TasksRouteImport.update({41const TasksRoute = TasksRouteImport.update({
43 id: '/tasks',42 id: '/tasks',
44 path: '/tasks',43 path: '/tasks',
@@ -114,6 +113,31 @@ const IndexRoute = IndexRouteImport.update({ · 69 unmodified lines
114 path: '/',113 path: '/',
115 getParentRoute: () => rootRouteImport,114 getParentRoute: () => rootRouteImport,
116} as any)115} as any)
116const ToolsIndexRoute = ToolsIndexRouteImport.update({
117 id: '/tools/',
118 path: '/tools/',
119 getParentRoute: () => rootRouteImport,
120} as any)
121const ToolsPoliciesRoute = ToolsPoliciesRouteImport.update({
122 id: '/tools/policies',
123 path: '/tools/policies',
124 getParentRoute: () => rootRouteImport,
125} as any)
126const ToolsEditorRoute = ToolsEditorRouteImport.update({
127 id: '/tools/editor',
128 path: '/tools/editor',
129 getParentRoute: () => rootRouteImport,
130} as any)
131const ToolsConnectionsRoute = ToolsConnectionsRouteImport.update({
132 id: '/tools/connections',
133 path: '/tools/connections',
134 getParentRoute: () => rootRouteImport,
135} as any)
136const ToolsCatalogRoute = ToolsCatalogRouteImport.update({
137 id: '/tools/catalog',
138 path: '/tools/catalog',
139 getParentRoute: () => rootRouteImport,
140} as any)
117const McpOauthStartRoute = McpOauthStartRouteImport.update({141const McpOauthStartRoute = McpOauthStartRouteImport.update({
118 id: '/mcp/oauth/start',142 id: '/mcp/oauth/start',
119 path: '/mcp/oauth/start',143 path: '/mcp/oauth/start',
@@ -171,7 +195,11 @@ export interface FileRoutesByFullPath { · 51 unmodified lines
171 '/sign-up': typeof SignUpRoute195 '/sign-up': typeof SignUpRoute
172 '/static-app-shell': typeof StaticAppShellRoute196 '/static-app-shell': typeof StaticAppShellRoute
173 '/tasks': typeof TasksRoute197 '/tasks': typeof TasksRoute
174 '/tools': typeof ToolsRoute198 '/tools/catalog': typeof ToolsCatalogRoute
199 '/tools/connections': typeof ToolsConnectionsRoute
200 '/tools/editor': typeof ToolsEditorRoute
201 '/tools/policies': typeof ToolsPoliciesRoute
202 '/tools/': typeof ToolsIndexRoute
175 '/api/auth/anonymous-token': typeof ApiAuthAnonymousTokenRoute203 '/api/auth/anonymous-token': typeof ApiAuthAnonymousTokenRoute
176 '/api/auth/callback': typeof ApiAuthCallbackRoute204 '/api/auth/callback': typeof ApiAuthCallbackRoute
177 '/api/openapi/inspect': typeof ApiOpenapiInspectRoute205 '/api/openapi/inspect': typeof ApiOpenapiInspectRoute
@@ -197,7 +225,11 @@ export interface FileRoutesByTo { · 19 unmodified lines
197 '/sign-up': typeof SignUpRoute225 '/sign-up': typeof SignUpRoute
198 '/static-app-shell': typeof StaticAppShellRoute226 '/static-app-shell': typeof StaticAppShellRoute
199 '/tasks': typeof TasksRoute227 '/tasks': typeof TasksRoute
200 '/tools': typeof ToolsRoute228 '/tools/catalog': typeof ToolsCatalogRoute
229 '/tools/connections': typeof ToolsConnectionsRoute
230 '/tools/editor': typeof ToolsEditorRoute
231 '/tools/policies': typeof ToolsPoliciesRoute
232 '/tools': typeof ToolsIndexRoute
201 '/api/auth/anonymous-token': typeof ApiAuthAnonymousTokenRoute233 '/api/auth/anonymous-token': typeof ApiAuthAnonymousTokenRoute
202 '/api/auth/callback': typeof ApiAuthCallbackRoute234 '/api/auth/callback': typeof ApiAuthCallbackRoute
203 '/api/openapi/inspect': typeof ApiOpenapiInspectRoute235 '/api/openapi/inspect': typeof ApiOpenapiInspectRoute
@@ -224,7 +256,11 @@ export interface FileRoutesById { · 20 unmodified lines
224 '/sign-up': typeof SignUpRoute256 '/sign-up': typeof SignUpRoute
225 '/static-app-shell': typeof StaticAppShellRoute257 '/static-app-shell': typeof StaticAppShellRoute
226 '/tasks': typeof TasksRoute258 '/tasks': typeof TasksRoute
227 '/tools': typeof ToolsRoute259 '/tools/catalog': typeof ToolsCatalogRoute
260 '/tools/connections': typeof ToolsConnectionsRoute
261 '/tools/editor': typeof ToolsEditorRoute
262 '/tools/policies': typeof ToolsPoliciesRoute
263 '/tools/': typeof ToolsIndexRoute
228 '/api/auth/anonymous-token': typeof ApiAuthAnonymousTokenRoute264 '/api/auth/anonymous-token': typeof ApiAuthAnonymousTokenRoute
229 '/api/auth/callback': typeof ApiAuthCallbackRoute265 '/api/auth/callback': typeof ApiAuthCallbackRoute
230 '/api/openapi/inspect': typeof ApiOpenapiInspectRoute266 '/api/openapi/inspect': typeof ApiOpenapiInspectRoute
@@ -252,7 +288,11 @@ export interface FileRouteTypes { · 21 unmodified lines
252 | '/sign-up'288 | '/sign-up'
253 | '/static-app-shell'289 | '/static-app-shell'
254 | '/tasks'290 | '/tasks'
255 | '/tools'291 | '/tools/catalog'
292 | '/tools/connections'
293 | '/tools/editor'
294 | '/tools/policies'
295 | '/tools/'
256 | '/api/auth/anonymous-token'296 | '/api/auth/anonymous-token'
257 | '/api/auth/callback'297 | '/api/auth/callback'
258 | '/api/openapi/inspect'298 | '/api/openapi/inspect'
@@ -278,6 +318,10 @@ export interface FileRouteTypes { · 19 unmodified lines
278 | '/sign-up'318 | '/sign-up'
279 | '/static-app-shell'319 | '/static-app-shell'
280 | '/tasks'320 | '/tasks'
321 | '/tools/catalog'
322 | '/tools/connections'
323 | '/tools/editor'
324 | '/tools/policies'
281 | '/tools'325 | '/tools'
282 | '/api/auth/anonymous-token'326 | '/api/auth/anonymous-token'
283 | '/api/auth/callback'327 | '/api/auth/callback'
@@ -304,7 +348,11 @@ export interface FileRouteTypes { · 20 unmodified lines
304 | '/sign-up'348 | '/sign-up'
305 | '/static-app-shell'349 | '/static-app-shell'
306 | '/tasks'350 | '/tasks'
307 | '/tools'351 | '/tools/catalog'
352 | '/tools/connections'
353 | '/tools/editor'
354 | '/tools/policies'
355 | '/tools/'
308 | '/api/auth/anonymous-token'356 | '/api/auth/anonymous-token'
309 | '/api/auth/callback'357 | '/api/auth/callback'
310 | '/api/openapi/inspect'358 | '/api/openapi/inspect'
@@ -331,7 +379,11 @@ export interface RootRouteChildren { · 20 unmodified lines
331 SignUpRoute: typeof SignUpRoute379 SignUpRoute: typeof SignUpRoute
332 StaticAppShellRoute: typeof StaticAppShellRoute380 StaticAppShellRoute: typeof StaticAppShellRoute
333 TasksRoute: typeof TasksRoute381 TasksRoute: typeof TasksRoute
334 ToolsRoute: typeof ToolsRoute382 ToolsCatalogRoute: typeof ToolsCatalogRoute
383 ToolsConnectionsRoute: typeof ToolsConnectionsRoute
384 ToolsEditorRoute: typeof ToolsEditorRoute
385 ToolsPoliciesRoute: typeof ToolsPoliciesRoute
386 ToolsIndexRoute: typeof ToolsIndexRoute
335 ApiAuthAnonymousTokenRoute: typeof ApiAuthAnonymousTokenRoute387 ApiAuthAnonymousTokenRoute: typeof ApiAuthAnonymousTokenRoute
336 ApiAuthCallbackRoute: typeof ApiAuthCallbackRoute388 ApiAuthCallbackRoute: typeof ApiAuthCallbackRoute
337 ApiOpenapiInspectRoute: typeof ApiOpenapiInspectRoute389 ApiOpenapiInspectRoute: typeof ApiOpenapiInspectRoute
@@ -344,13 +396,6 @@ export interface RootRouteChildren { · 6 unmodified lines
344 396
345declare module '@tanstack/react-router' {397declare module '@tanstack/react-router' {
346 interface FileRoutesByPath {398 interface FileRoutesByPath {
347 '/tools': {
348 id: '/tools'
349 path: '/tools'
350 fullPath: '/tools'
351 preLoaderRoute: typeof ToolsRouteImport
352 parentRoute: typeof rootRouteImport
353 }
354 '/tasks': {399 '/tasks': {
355 id: '/tasks'400 id: '/tasks'
356 path: '/tasks'401 path: '/tasks'
@@ -456,6 +501,41 @@ declare module '@tanstack/react-router' { · 99 unmodified lines
456 preLoaderRoute: typeof IndexRouteImport501 preLoaderRoute: typeof IndexRouteImport
457 parentRoute: typeof rootRouteImport502 parentRoute: typeof rootRouteImport
458 }503 }
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 }
459 '/mcp/oauth/start': {539 '/mcp/oauth/start': {
460 id: '/mcp/oauth/start'540 id: '/mcp/oauth/start'
461 path: '/mcp/oauth/start'541 path: '/mcp/oauth/start'
@@ -531,7 +611,11 @@ const rootRouteChildren: RootRouteChildren = { · 69 unmodified lines
531 SignUpRoute: SignUpRoute,611 SignUpRoute: SignUpRoute,
532 StaticAppShellRoute: StaticAppShellRoute,612 StaticAppShellRoute: StaticAppShellRoute,
533 TasksRoute: TasksRoute,613 TasksRoute: TasksRoute,
534 ToolsRoute: ToolsRoute,614 ToolsCatalogRoute: ToolsCatalogRoute,
615 ToolsConnectionsRoute: ToolsConnectionsRoute,
616 ToolsEditorRoute: ToolsEditorRoute,
617 ToolsPoliciesRoute: ToolsPoliciesRoute,
618 ToolsIndexRoute: ToolsIndexRoute,
535 ApiAuthAnonymousTokenRoute: ApiAuthAnonymousTokenRoute,619 ApiAuthAnonymousTokenRoute: ApiAuthAnonymousTokenRoute,
536 ApiAuthCallbackRoute: ApiAuthCallbackRoute,620 ApiAuthCallbackRoute: ApiAuthCallbackRoute,
537 ApiOpenapiInspectRoute: ApiOpenapiInspectRoute,621 ApiOpenapiInspectRoute: ApiOpenapiInspectRoute,
executor/apps/web/src/routes/__root.tsx+20−17
@@ -5,6 +5,7 @@ import { AppConvexProvider } from "@/lib/convex-provider"; · 4 unmodified lines
5import { QueryProvider } from "@/lib/query-provider";5import { QueryProvider } from "@/lib/query-provider";
6import { SessionProvider } from "@/lib/session-context";6import { SessionProvider } from "@/lib/session-context";
7import { AppErrorBoundary } from "@/components/app-error-boundary";7import { AppErrorBoundary } from "@/components/app-error-boundary";
8import { NuqsAdapter } from "nuqs/adapters/tanstack-router";
8import { runtimeConfigFromEnv } from "@/lib/runtime-config";9import { runtimeConfigFromEnv } from "@/lib/runtime-config";
9import appCss from "../app/globals.css?url";10import appCss from "../app/globals.css?url";
10 11
@@ -55,23 +56,25 @@ function RootDocument({ children }: { children: React.ReactNode }) { · 44 unmodified lines
55 __html: `window.__EXECUTOR_RUNTIME_CONFIG__ = ${runtimeConfig};`,56 __html: `window.__EXECUTOR_RUNTIME_CONFIG__ = ${runtimeConfig};`,
56 }}57 }}
57 />58 />
58 <ThemeProvider59 <NuqsAdapter>
59 attribute="class"60 <ThemeProvider
60 defaultTheme="dark"61 attribute="class"
61 enableSystem62 defaultTheme="dark"
62 enableColorScheme63 enableSystem
63 >64 enableColorScheme
64 <AppErrorBoundary>65 >
65 <QueryProvider>66 <AppErrorBoundary>
66 <AppConvexProvider>67 <QueryProvider>
67 <SessionProvider>68 <AppConvexProvider>
68 {children}69 <SessionProvider>
69 </SessionProvider>70 {children}
70 </AppConvexProvider>71 </SessionProvider>
71 </QueryProvider>72 </AppConvexProvider>
72 </AppErrorBoundary>73 </QueryProvider>
73 <Toaster position="bottom-right" />74 </AppErrorBoundary>
74 </ThemeProvider>75 <Toaster position="bottom-right" />
76 </ThemeProvider>
77 </NuqsAdapter>
75 <Scripts />78 <Scripts />
76 </body>79 </body>
77 </html>80 </html>
executor/apps/web/src/routes/tools.tsx+0−22
@@ -1,22 +0,0 @@
1import { createFileRoute } from "@tanstack/react-router";
2import { AppShell } from "@/components/app-shell";
3import { ToolsView } from "@/components/tools/view";
4import { useSearchParams } from "@/lib/router";
5
6export const Route = createFileRoute("/tools")({
7 component: ToolsPage,
8});
9
10function 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 @@
1import { createFileRoute } from "@tanstack/react-router";
2import { AppShell } from "@/components/app-shell";
3import { ToolsView } from "@/components/tools/view";
4
5export const Route = createFileRoute("/tools/catalog")({
6 component: ToolsCatalogPage,
7});
8
9function 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 @@
1import { createFileRoute } from "@tanstack/react-router";
2import { AppShell } from "@/components/app-shell";
3import { ToolsView } from "@/components/tools/view";
4
5export const Route = createFileRoute("/tools/connections")({
6 component: ToolsConnectionsPage,
7});
8
9function 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 @@
1import { createFileRoute } from "@tanstack/react-router";
2import { AppShell } from "@/components/app-shell";
3import { ToolsView } from "@/components/tools/view";
4
5export const Route = createFileRoute("/tools/editor")({
6 component: ToolsEditorPage,
7});
8
9function 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 @@
1import { createFileRoute } from "@tanstack/react-router";
2import { AppShell } from "@/components/app-shell";
3import { Navigate, useLocation } from "@/lib/router";
4
5export const Route = createFileRoute("/tools/")({
6 component: ToolsLayout,
7});
8
9function 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 @@
1import { createFileRoute } from "@tanstack/react-router";
2import { AppShell } from "@/components/app-shell";
3import { ToolsView } from "@/components/tools/view";
4
5export const Route = createFileRoute("/tools/policies")({
6 component: ToolsPoliciesPage,
7});
8
9function 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
123 123
124export type ToolRoleSelectorType = "all" | "source" | "namespace" | "tool_path";124export type ToolRoleSelectorType = "all" | "source" | "namespace" | "tool_path";
125 125
126export interface ToolRoleRecord {126export interface ToolPolicySetRecord {
127 id: string;127 id: string;
128 organizationId: Id<"organizations">;128 organizationId: Id<"organizations">;
129 name: string;129 name: string;
@@ -133,7 +133,7 @@ export interface ToolRoleRecord { · 3 unmodified lines
133 updatedAt: number;133 updatedAt: number;
134}134}
135 135
136export interface ToolRoleRuleRecord {136export interface ToolPolicyRuleRecord {
137 id: string;137 id: string;
138 roleId: string;138 roleId: string;
139 organizationId: Id<"organizations">;139 organizationId: Id<"organizations">;
@@ -150,7 +150,7 @@ export interface ToolRoleRuleRecord { · 10 unmodified lines
150 updatedAt: number;150 updatedAt: number;
151}151}
152 152
153export interface ToolRoleBindingRecord {153export interface ToolPolicyAssignmentRecord {
154 id: string;154 id: string;
155 roleId: string;155 roleId: string;
156 organizationId: Id<"organizations">;156 organizationId: Id<"organizations">;
executor/packages/database/convex/_generated/api.d.ts+71−71
@@ -306,19 +306,13 @@ export declare const api: { · 305 unmodified lines
306 { accountId?: string; sessionId?: string },306 { accountId?: string; sessionId?: string },
307 any307 any
308 >;308 >;
309 deleteToolRole: FunctionReference<309 deleteToolPolicyAssignment: FunctionReference<
310 "mutation",
311 "public",
312 { roleId: string; sessionId?: string; workspaceId: Id<"workspaces"> },
313 any
314 >;
315 deleteToolRoleBinding: FunctionReference<
316 "mutation",310 "mutation",
317 "public",311 "public",
318 { bindingId: string; sessionId?: string; workspaceId: Id<"workspaces"> },312 { bindingId: string; sessionId?: string; workspaceId: Id<"workspaces"> },
319 any313 any
320 >;314 >;
321 deleteToolRoleRule: FunctionReference<315 deleteToolPolicyRule: FunctionReference<
322 "mutation",316 "mutation",
323 "public",317 "public",
324 {318 {
@@ -329,6 +323,12 @@ export declare const api: { · 4 unmodified lines
329 },323 },
330 any324 any
331 >;325 >;
326 deleteToolPolicySet: FunctionReference<
327 "mutation",
328 "public",
329 { roleId: string; sessionId?: string; workspaceId: Id<"workspaces"> },
330 any
331 >;
332 deleteToolSource: FunctionReference<332 deleteToolSource: FunctionReference<
333 "mutation",333 "mutation",
334 "public",334 "public",
@@ -381,19 +381,19 @@ export declare const api: { · 46 unmodified lines
381 { sessionId?: string; workspaceId: Id<"workspaces"> },381 { sessionId?: string; workspaceId: Id<"workspaces"> },
382 any382 any
383 >;383 >;
384 listToolRoleBindings: FunctionReference<384 listToolPolicyAssignments: FunctionReference<
385 "query",385 "query",
386 "public",386 "public",
387 { roleId?: string; sessionId?: string; workspaceId: Id<"workspaces"> },387 { roleId?: string; sessionId?: string; workspaceId: Id<"workspaces"> },
388 any388 any
389 >;389 >;
390 listToolRoleRules: FunctionReference<390 listToolPolicyRules: FunctionReference<
391 "query",391 "query",
392 "public",392 "public",
393 { roleId: string; sessionId?: string; workspaceId: Id<"workspaces"> },393 { roleId: string; sessionId?: string; workspaceId: Id<"workspaces"> },
394 any394 any
395 >;395 >;
396 listToolRoles: FunctionReference<396 listToolPolicySets: FunctionReference<
397 "query",397 "query",
398 "public",398 "public",
399 { sessionId?: string; workspaceId: Id<"workspaces"> },399 { sessionId?: string; workspaceId: Id<"workspaces"> },
@@ -439,19 +439,7 @@ export declare const api: { · 39 unmodified lines
439 },439 },
440 any440 any
441 >;441 >;
442 upsertToolRole: FunctionReference<442 upsertToolPolicyAssignment: 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<
455 "mutation",443 "mutation",
456 "public",444 "public",
457 {445 {
@@ -467,7 +455,7 @@ export declare const api: { · 9 unmodified lines
467 },455 },
468 any456 any
469 >;457 >;
470 upsertToolRoleRule: FunctionReference<458 upsertToolPolicyRule: FunctionReference<
471 "mutation",459 "mutation",
472 "public",460 "public",
473 {461 {
@@ -490,6 +478,18 @@ export declare const api: { · 16 unmodified lines
490 },478 },
491 any479 any
492 >;480 >;
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 >;
493 upsertToolSource: FunctionReference<493 upsertToolSource: FunctionReference<
494 "mutation",494 "mutation",
495 "public",495 "public",
@@ -742,22 +742,22 @@ export declare const internal: { · 246 unmodified lines
742 any742 any
743 >;743 >;
744 };744 };
745 deleteToolRole: FunctionReference<745 deleteToolPolicyAssignment: FunctionReference<
746 "mutation",746 "mutation",
747 "internal",747 "internal",
748 { roleId: string; workspaceId: Id<"workspaces"> },748 { bindingId: string; workspaceId: Id<"workspaces"> },
749 any749 any
750 >;750 >;
751 deleteToolRoleBinding: FunctionReference<751 deleteToolPolicyRule: FunctionReference<
752 "mutation",752 "mutation",
753 "internal",753 "internal",
754 { bindingId: string; workspaceId: Id<"workspaces"> },754 { roleId: string; ruleId: string; workspaceId: Id<"workspaces"> },
755 any755 any
756 >;756 >;
757 deleteToolRoleRule: FunctionReference<757 deleteToolPolicySet: FunctionReference<
758 "mutation",758 "mutation",
759 "internal",759 "internal",
760 { roleId: string; ruleId: string; workspaceId: Id<"workspaces"> },760 { roleId: string; workspaceId: Id<"workspaces"> },
761 any761 any
762 >;762 >;
763 deleteToolSource: FunctionReference<763 deleteToolSource: FunctionReference<
@@ -855,19 +855,19 @@ export declare const internal: { · 91 unmodified lines
855 { accountId?: Id<"accounts">; workspaceId: Id<"workspaces"> },855 { accountId?: Id<"accounts">; workspaceId: Id<"workspaces"> },
856 any856 any
857 >;857 >;
858 listToolRoleBindings: FunctionReference<858 listToolPolicyAssignments: FunctionReference<
859 "query",859 "query",
860 "internal",860 "internal",
861 { roleId?: string; workspaceId: Id<"workspaces"> },861 { roleId?: string; workspaceId: Id<"workspaces"> },
862 any862 any
863 >;863 >;
864 listToolRoleRules: FunctionReference<864 listToolPolicyRules: FunctionReference<
865 "query",865 "query",
866 "internal",866 "internal",
867 { roleId: string; workspaceId: Id<"workspaces"> },867 { roleId: string; workspaceId: Id<"workspaces"> },
868 any868 any
869 >;869 >;
870 listToolRoles: FunctionReference<870 listToolPolicySets: FunctionReference<
871 "query",871 "query",
872 "internal",872 "internal",
873 { workspaceId: Id<"workspaces"> },873 { workspaceId: Id<"workspaces"> },
@@ -897,22 +897,22 @@ export declare const internal: { · 23 unmodified lines
897 any897 any
898 >;898 >;
899 policies: {899 policies: {
900 deleteToolRole: FunctionReference<900 deleteToolPolicyAssignment: FunctionReference<
901 "mutation",901 "mutation",
902 "internal",902 "internal",
903 { roleId: string; workspaceId: Id<"workspaces"> },903 { bindingId: string; workspaceId: Id<"workspaces"> },
904 any904 any
905 >;905 >;
906 deleteToolRoleBinding: FunctionReference<906 deleteToolPolicyRule: FunctionReference<
907 "mutation",907 "mutation",
908 "internal",908 "internal",
909 { bindingId: string; workspaceId: Id<"workspaces"> },909 { roleId: string; ruleId: string; workspaceId: Id<"workspaces"> },
910 any910 any
911 >;911 >;
912 deleteToolRoleRule: FunctionReference<912 deleteToolPolicySet: FunctionReference<
913 "mutation",913 "mutation",
914 "internal",914 "internal",
915 { roleId: string; ruleId: string; workspaceId: Id<"workspaces"> },915 { roleId: string; workspaceId: Id<"workspaces"> },
916 any916 any
917 >;917 >;
918 listRuntimeTargets: FunctionReference<"query", "internal", {}, any>;918 listRuntimeTargets: FunctionReference<"query", "internal", {}, any>;
@@ -922,37 +922,25 @@ export declare const internal: { · 3 unmodified lines
922 { accountId?: Id<"accounts">; workspaceId: Id<"workspaces"> },922 { accountId?: Id<"accounts">; workspaceId: Id<"workspaces"> },
923 any923 any
924 >;924 >;
925 listToolRoleBindings: FunctionReference<925 listToolPolicyAssignments: FunctionReference<
926 "query",926 "query",
927 "internal",927 "internal",
928 { roleId?: string; workspaceId: Id<"workspaces"> },928 { roleId?: string; workspaceId: Id<"workspaces"> },
929 any929 any
930 >;930 >;
931 listToolRoleRules: FunctionReference<931 listToolPolicyRules: FunctionReference<
932 "query",932 "query",
933 "internal",933 "internal",
934 { roleId: string; workspaceId: Id<"workspaces"> },934 { roleId: string; workspaceId: Id<"workspaces"> },
935 any935 any
936 >;936 >;
937 listToolRoles: FunctionReference<937 listToolPolicySets: FunctionReference<
938 "query",938 "query",
939 "internal",939 "internal",
940 { workspaceId: Id<"workspaces"> },940 { workspaceId: Id<"workspaces"> },
941 any941 any
942 >;942 >;
943 upsertToolRole: FunctionReference<943 upsertToolPolicyAssignment: 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<
956 "mutation",944 "mutation",
957 "internal",945 "internal",
958 {946 {
@@ -967,7 +955,7 @@ export declare const internal: { · 8 unmodified lines
967 },955 },
968 any956 any
969 >;957 >;
970 upsertToolRoleRule: FunctionReference<958 upsertToolPolicyRule: FunctionReference<
971 "mutation",959 "mutation",
972 "internal",960 "internal",
973 {961 {
@@ -989,6 +977,18 @@ export declare const internal: { · 15 unmodified lines
989 },977 },
990 any978 any
991 >;979 >;
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 >;
992 };992 };
993 resolveApproval: FunctionReference<993 resolveApproval: FunctionReference<
994 "mutation",994 "mutation",
@@ -1186,19 +1186,7 @@ export declare const internal: { · 191 unmodified lines
1186 },1186 },
1187 any1187 any
1188 >;1188 >;
1189 upsertToolRole: FunctionReference<1189 upsertToolPolicyAssignment: 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<
1202 "mutation",1190 "mutation",
1203 "internal",1191 "internal",
1204 {1192 {
@@ -1213,7 +1201,7 @@ export declare const internal: { · 8 unmodified lines
1213 },1201 },
1214 any1202 any
1215 >;1203 >;
1216 upsertToolRoleRule: FunctionReference<1204 upsertToolPolicyRule: FunctionReference<
1217 "mutation",1205 "mutation",
1218 "internal",1206 "internal",
1219 {1207 {
@@ -1235,6 +1223,18 @@ export declare const internal: { · 15 unmodified lines
1235 },1223 },
1236 any1224 any
1237 >;1225 >;
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 >;
1238 upsertToolSource: FunctionReference<1238 upsertToolSource: FunctionReference<
1239 "mutation",1239 "mutation",
1240 "internal",1240 "internal",
executor/packages/database/convex/access-controls.test.ts+22−22
@@ -217,7 +217,7 @@ describe("authentication", () => { · 216 unmodified lines
217 const owner = await seedUser(t, { subject: "owner-2" });217 const owner = await seedUser(t, { subject: "owner-2" });
218 218
219 await expect(219 await expect(
220 t.mutation(api.workspace.upsertToolRole, {220 t.mutation(api.workspace.upsertToolPolicySet, {
221 workspaceId: owner.workspaceId,221 workspaceId: owner.workspaceId,
222 name: "anonymous-role",222 name: "anonymous-role",
223 }),223 }),
@@ -439,7 +439,7 @@ describe("workspace access controls", () => { · 215 unmodified lines
439 const authedMember = t.withIdentity({ subject: "ws-plain-member" });439 const authedMember = t.withIdentity({ subject: "ws-plain-member" });
440 440
441 await expect(441 await expect(
442 authedMember.mutation(api.workspace.upsertToolRole, {442 authedMember.mutation(api.workspace.upsertToolPolicySet, {
443 workspaceId: owner.workspaceId,443 workspaceId: owner.workspaceId,
444 name: "member-managed-role",444 name: "member-managed-role",
445 }),445 }),
@@ -464,13 +464,13 @@ describe("workspace access controls", () => { · 18 unmodified lines
464 464
465 const authedAdmin = t.withIdentity({ subject: "ws-policy-admin" });465 const authedAdmin = t.withIdentity({ subject: "ws-policy-admin" });
466 466
467 const role = await authedAdmin.mutation(api.workspace.upsertToolRole, {467 const role = await authedAdmin.mutation(api.workspace.upsertToolPolicySet, {
468 workspaceId: owner.workspaceId,468 workspaceId: owner.workspaceId,
469 name: "policy-admin-role",469 name: "policy-admin-role",
470 description: "workspace admin policy role",470 description: "workspace admin policy role",
471 });471 });
472 472
473 await authedAdmin.mutation(api.workspace.upsertToolRoleRule, {473 await authedAdmin.mutation(api.workspace.upsertToolPolicyRule, {
474 workspaceId: owner.workspaceId,474 workspaceId: owner.workspaceId,
475 roleId: role.id,475 roleId: role.id,
476 selectorType: "tool_path",476 selectorType: "tool_path",
@@ -479,7 +479,7 @@ describe("workspace access controls", () => { · 2 unmodified lines
479 approvalMode: "required",479 approvalMode: "required",
480 });480 });
481 481
482 await authedAdmin.mutation(api.workspace.upsertToolRoleBinding, {482 await authedAdmin.mutation(api.workspace.upsertToolPolicyAssignment, {
483 workspaceId: owner.workspaceId,483 workspaceId: owner.workspaceId,
484 roleId: role.id,484 roleId: role.id,
485 scopeType: "workspace",485 scopeType: "workspace",
@@ -503,13 +503,13 @@ describe("workspace access controls", () => { · 17 unmodified lines
503 const outsider = await seedUser(t, { subject: "policy-target-outsider" });503 const outsider = await seedUser(t, { subject: "policy-target-outsider" });
504 const authedOwner = t.withIdentity({ subject: "policy-target-owner" });504 const authedOwner = t.withIdentity({ subject: "policy-target-owner" });
505 505
506 const role = await authedOwner.mutation(api.workspace.upsertToolRole, {506 const role = await authedOwner.mutation(api.workspace.upsertToolPolicySet, {
507 workspaceId: owner.workspaceId,507 workspaceId: owner.workspaceId,
508 name: "policy-target-role",508 name: "policy-target-role",
509 });509 });
510 510
511 await expect(511 await expect(
512 authedOwner.mutation(api.workspace.upsertToolRoleBinding, {512 authedOwner.mutation(api.workspace.upsertToolPolicyAssignment, {
513 workspaceId: owner.workspaceId,513 workspaceId: owner.workspaceId,
514 roleId: role.id,514 roleId: role.id,
515 scopeType: "account",515 scopeType: "account",
@@ -524,12 +524,12 @@ describe("workspace access controls", () => { · 8 unmodified lines
524 const owner = await seedUser(t, { subject: "ws-owner-policy" });524 const owner = await seedUser(t, { subject: "ws-owner-policy" });
525 const authedOwner = t.withIdentity({ subject: "ws-owner-policy" });525 const authedOwner = t.withIdentity({ subject: "ws-owner-policy" });
526 526
527 const role = await authedOwner.mutation(api.workspace.upsertToolRole, {527 const role = await authedOwner.mutation(api.workspace.upsertToolPolicySet, {
528 workspaceId: owner.workspaceId,528 workspaceId: owner.workspaceId,
529 name: "owner-policy-role",529 name: "owner-policy-role",
530 });530 });
531 531
532 await authedOwner.mutation(api.workspace.upsertToolRoleRule, {532 await authedOwner.mutation(api.workspace.upsertToolPolicyRule, {
533 workspaceId: owner.workspaceId,533 workspaceId: owner.workspaceId,
534 roleId: role.id,534 roleId: role.id,
535 selectorType: "all",535 selectorType: "all",
@@ -537,7 +537,7 @@ describe("workspace access controls", () => { · 1 unmodified line
537 approvalMode: "required",537 approvalMode: "required",
538 });538 });
539 539
540 await authedOwner.mutation(api.workspace.upsertToolRoleBinding, {540 await authedOwner.mutation(api.workspace.upsertToolPolicyAssignment, {
541 workspaceId: owner.workspaceId,541 workspaceId: owner.workspaceId,
542 roleId: role.id,542 roleId: role.id,
543 scopeType: "workspace",543 scopeType: "workspace",
@@ -555,12 +555,12 @@ describe("workspace access controls", () => { · 11 unmodified lines
555 const owner = await seedUser(t, { subject: "ws-owner-policy-delete" });555 const owner = await seedUser(t, { subject: "ws-owner-policy-delete" });
556 const authedOwner = t.withIdentity({ subject: "ws-owner-policy-delete" });556 const authedOwner = t.withIdentity({ subject: "ws-owner-policy-delete" });
557 557
558 const role = await authedOwner.mutation(api.workspace.upsertToolRole, {558 const role = await authedOwner.mutation(api.workspace.upsertToolPolicySet, {
559 workspaceId: owner.workspaceId,559 workspaceId: owner.workspaceId,
560 name: "owner-policy-delete-role",560 name: "owner-policy-delete-role",
561 });561 });
562 562
563 await authedOwner.mutation(api.workspace.upsertToolRoleRule, {563 await authedOwner.mutation(api.workspace.upsertToolPolicyRule, {
564 workspaceId: owner.workspaceId,564 workspaceId: owner.workspaceId,
565 roleId: role.id,565 roleId: role.id,
566 selectorType: "tool_path",566 selectorType: "tool_path",
@@ -569,14 +569,14 @@ describe("workspace access controls", () => { · 2 unmodified lines
569 matchType: "exact",569 matchType: "exact",
570 });570 });
571 571
572 await authedOwner.mutation(api.workspace.upsertToolRoleBinding, {572 await authedOwner.mutation(api.workspace.upsertToolPolicyAssignment, {
573 workspaceId: owner.workspaceId,573 workspaceId: owner.workspaceId,
574 roleId: role.id,574 roleId: role.id,
575 scopeType: "workspace",575 scopeType: "workspace",
576 status: "active",576 status: "active",
577 });577 });
578 578
579 await authedOwner.mutation(api.workspace.deleteToolRole, {579 await authedOwner.mutation(api.workspace.deleteToolPolicySet, {
580 workspaceId: owner.workspaceId,580 workspaceId: owner.workspaceId,
581 roleId: role.id,581 roleId: role.id,
582 });582 });
@@ -754,7 +754,7 @@ describe("workspace access controls", () => { · 171 unmodified lines
754 const authedMember = t.withIdentity({ subject: "role-tool-member" });754 const authedMember = t.withIdentity({ subject: "role-tool-member" });
755 755
756 await expect(756 await expect(
757 authedMember.mutation(api.workspace.upsertToolRole, {757 authedMember.mutation(api.workspace.upsertToolPolicySet, {
758 workspaceId: owner.workspaceId,758 workspaceId: owner.workspaceId,
759 name: "limited",759 name: "limited",
760 }),760 }),
@@ -766,13 +766,13 @@ describe("workspace access controls", () => { · 5 unmodified lines
766 const owner = await seedUser(t, { subject: "role-source-owner" });766 const owner = await seedUser(t, { subject: "role-source-owner" });
767 const authedOwner = t.withIdentity({ subject: "role-source-owner" });767 const authedOwner = t.withIdentity({ subject: "role-source-owner" });
768 768
769 const role = await authedOwner.mutation(api.workspace.upsertToolRole, {769 const role = await authedOwner.mutation(api.workspace.upsertToolPolicySet, {
770 workspaceId: owner.workspaceId,770 workspaceId: owner.workspaceId,
771 name: "github-access",771 name: "github-access",
772 description: "Allows github source tools",772 description: "Allows github source tools",
773 });773 });
774 774
775 await authedOwner.mutation(api.workspace.upsertToolRoleRule, {775 await authedOwner.mutation(api.workspace.upsertToolPolicyRule, {
776 workspaceId: owner.workspaceId,776 workspaceId: owner.workspaceId,
777 roleId: role.id,777 roleId: role.id,
778 selectorType: "source",778 selectorType: "source",
@@ -781,7 +781,7 @@ describe("workspace access controls", () => { · 2 unmodified lines
781 approvalMode: "auto",781 approvalMode: "auto",
782 });782 });
783 783
784 await authedOwner.mutation(api.workspace.upsertToolRoleBinding, {784 await authedOwner.mutation(api.workspace.upsertToolPolicyAssignment, {
785 workspaceId: owner.workspaceId,785 workspaceId: owner.workspaceId,
786 roleId: role.id,786 roleId: role.id,
787 scopeType: "organization",787 scopeType: "organization",
@@ -1120,7 +1120,7 @@ describe("cross-workspace isolation", () => { · 332 unmodified lines
1120 const authedA = t.withIdentity({ subject: "policy-user-a" });1120 const authedA = t.withIdentity({ subject: "policy-user-a" });
1121 1121
1122 await expect(1122 await expect(
1123 authedA.mutation(api.workspace.upsertToolRole, {1123 authedA.mutation(api.workspace.upsertToolPolicySet, {
1124 workspaceId: userB.workspaceId,1124 workspaceId: userB.workspaceId,
1125 name: "cross-workspace-role",1125 name: "cross-workspace-role",
1126 }),1126 }),
@@ -1628,18 +1628,18 @@ describe("role hierarchy validation", () => { · 501 unmodified lines
1628 const authed = t.withIdentity({ subject: "admin-hier-admin" });1628 const authed = t.withIdentity({ subject: "admin-hier-admin" });
1629 1629
1630 // Admin can manage tool-policy roles/rules/bindings1630 // 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, {
1632 workspaceId: org.workspaceId,1632 workspaceId: org.workspaceId,
1633 name: "admin-hierarchy-role",1633 name: "admin-hierarchy-role",
1634 });1634 });
1635 await authed.mutation(api.workspace.upsertToolRoleRule, {1635 await authed.mutation(api.workspace.upsertToolPolicyRule, {
1636 workspaceId: org.workspaceId,1636 workspaceId: org.workspaceId,
1637 roleId: role.id,1637 roleId: role.id,
1638 selectorType: "tool_path",1638 selectorType: "tool_path",
1639 resourcePattern: "admin.*",1639 resourcePattern: "admin.*",
1640 effect: "deny",1640 effect: "deny",
1641 });1641 });
1642 await authed.mutation(api.workspace.upsertToolRoleBinding, {1642 await authed.mutation(api.workspace.upsertToolPolicyAssignment, {
1643 workspaceId: org.workspaceId,1643 workspaceId: org.workspaceId,
1644 roleId: role.id,1644 roleId: role.id,
1645 scopeType: "workspace",1645 scopeType: "workspace",
executor/packages/database/convex/database.ts+9−9
@@ -25,15 +25,15 @@ export { · 24 unmodified lines
25export { bootstrapAnonymousSession } from "./database/anonymous_session";25export { bootstrapAnonymousSession } from "./database/anonymous_session";
26export {26export {
27 listRuntimeTargets,27 listRuntimeTargets,
28 upsertToolRole,28 upsertToolPolicySet,
29 listToolRoles,29 listToolPolicySets,
30 deleteToolRole,30 deleteToolPolicySet,
31 upsertToolRoleRule,31 upsertToolPolicyRule,
32 listToolRoleRules,32 listToolPolicyRules,
33 deleteToolRoleRule,33 deleteToolPolicyRule,
34 upsertToolRoleBinding,34 upsertToolPolicyAssignment,
35 listToolRoleBindings,35 listToolPolicyAssignments,
36 deleteToolRoleBinding,36 deleteToolPolicyAssignment,
37 listToolPolicies,37 listToolPolicies,
38} from "./database/policies";38} from "./database/policies";
39export {39export {
executor/packages/database/convex/database/policies.ts+16−16
@@ -5,9 +5,9 @@ import type { MutationCtx, QueryCtx } from "../_generated/server"; · 4 unmodified lines
5import { listRuntimeTargets as listAvailableRuntimeTargets } from "../../../core/src/runtimes/runtime-catalog";5import { listRuntimeTargets as listAvailableRuntimeTargets } from "../../../core/src/runtimes/runtime-catalog";
6import type {6import type {
7 ToolPolicyRecord,7 ToolPolicyRecord,
8 ToolRoleBindingRecord,8 ToolPolicyAssignmentRecord,
9 ToolRoleRecord,9 ToolPolicySetRecord,
10 ToolRoleRuleRecord,10 ToolPolicyRuleRecord,
11 ToolRoleSelectorType,11 ToolRoleSelectorType,
12} from "../../../core/src/types";12} from "../../../core/src/types";
13import {13import {
@@ -83,7 +83,7 @@ function mapToolRole(doc: { · 69 unmodified lines
83 createdByAccountId?: Id<"accounts">;83 createdByAccountId?: Id<"accounts">;
84 createdAt: number;84 createdAt: number;
85 updatedAt: number;85 updatedAt: number;
86}): ToolRoleRecord {86}): ToolPolicySetRecord {
87 return {87 return {
88 id: doc.roleId,88 id: doc.roleId,
89 organizationId: doc.organizationId,89 organizationId: doc.organizationId,
@@ -110,7 +110,7 @@ function mapToolRoleRule(doc: { · 20 unmodified lines
110 priority: number;110 priority: number;
111 createdAt: number;111 createdAt: number;
112 updatedAt: number;112 updatedAt: number;
113}): ToolRoleRuleRecord {113}): ToolPolicyRuleRecord {
114 return {114 return {
115 id: doc.ruleId,115 id: doc.ruleId,
116 roleId: doc.roleId,116 roleId: doc.roleId,
@@ -141,7 +141,7 @@ function mapToolRoleBinding(doc: { · 24 unmodified lines
141 expiresAt?: number;141 expiresAt?: number;
142 createdAt: number;142 createdAt: number;
143 updatedAt: number;143 updatedAt: number;
144}): ToolRoleBindingRecord {144}): ToolPolicyAssignmentRecord {
145 return {145 return {
146 id: doc.bindingId,146 id: doc.bindingId,
147 roleId: doc.roleId,147 roleId: doc.roleId,
@@ -328,7 +328,7 @@ async function listEffectiveBindings( · 180 unmodified lines
328}328}
329 329
330function selectorInputToResource(330function selectorInputToResource(
331 rule: Pick<ToolRoleRuleRecord, "selectorType" | "sourceKey" | "namespacePattern" | "toolPathPattern">,331 rule: Pick<ToolPolicyRuleRecord, "selectorType" | "sourceKey" | "namespacePattern" | "toolPathPattern">,
332): { resourceType: PolicyResourceType; resourcePattern: string } {332): { resourceType: PolicyResourceType; resourcePattern: string } {
333 const resourceType = resourceTypeFromSelectorType(rule.selectorType);333 const resourceType = resourceTypeFromSelectorType(rule.selectorType);
334 const resourcePattern = resourcePatternFromRule(rule);334 const resourcePattern = resourcePatternFromRule(rule);
@@ -346,7 +346,7 @@ export const listRuntimeTargets = internalQuery({ · 11 unmodified lines
346 },346 },
347});347});
348 348
349export const upsertToolRole = internalMutation({349export const upsertToolPolicySet = internalMutation({
350 args: {350 args: {
351 workspaceId: v.id("workspaces"),351 workspaceId: v.id("workspaces"),
352 id: v.optional(v.string()),352 id: v.optional(v.string()),
@@ -410,7 +410,7 @@ export const upsertToolRole = internalMutation({ · 57 unmodified lines
410 },410 },
411});411});
412 412
413export const listToolRoles = internalQuery({413export const listToolPolicySets = internalQuery({
414 args: {414 args: {
415 workspaceId: v.id("workspaces"),415 workspaceId: v.id("workspaces"),
416 },416 },
@@ -426,7 +426,7 @@ export const listToolRoles = internalQuery({ · 9 unmodified lines
426 },426 },
427});427});
428 428
429export const deleteToolRole = internalMutation({429export const deleteToolPolicySet = internalMutation({
430 args: {430 args: {
431 workspaceId: v.id("workspaces"),431 workspaceId: v.id("workspaces"),
432 roleId: v.string(),432 roleId: v.string(),
@@ -459,7 +459,7 @@ export const deleteToolRole = internalMutation({ · 26 unmodified lines
459 },459 },
460});460});
461 461
462export const upsertToolRoleRule = internalMutation({462export const upsertToolPolicyRule = internalMutation({
463 args: {463 args: {
464 workspaceId: v.id("workspaces"),464 workspaceId: v.id("workspaces"),
465 roleId: v.string(),465 roleId: v.string(),
@@ -541,7 +541,7 @@ export const upsertToolRoleRule = internalMutation({ · 75 unmodified lines
541 },541 },
542});542});
543 543
544export const listToolRoleRules = internalQuery({544export const listToolPolicyRules = internalQuery({
545 args: {545 args: {
546 workspaceId: v.id("workspaces"),546 workspaceId: v.id("workspaces"),
547 roleId: v.string(),547 roleId: v.string(),
@@ -572,7 +572,7 @@ export const listToolRoleRules = internalQuery({ · 24 unmodified lines
572 },572 },
573});573});
574 574
575export const deleteToolRoleRule = internalMutation({575export const deleteToolPolicyRule = internalMutation({
576 args: {576 args: {
577 workspaceId: v.id("workspaces"),577 workspaceId: v.id("workspaces"),
578 roleId: v.string(),578 roleId: v.string(),
@@ -601,7 +601,7 @@ export const deleteToolRoleRule = internalMutation({ · 22 unmodified lines
601 },601 },
602});602});
603 603
604export const upsertToolRoleBinding = internalMutation({604export const upsertToolPolicyAssignment = internalMutation({
605 args: {605 args: {
606 workspaceId: v.id("workspaces"),606 workspaceId: v.id("workspaces"),
607 roleId: v.string(),607 roleId: v.string(),
@@ -680,7 +680,7 @@ export const upsertToolRoleBinding = internalMutation({ · 72 unmodified lines
680 },680 },
681});681});
682 682
683export const listToolRoleBindings = internalQuery({683export const listToolPolicyAssignments = internalQuery({
684 args: {684 args: {
685 workspaceId: v.id("workspaces"),685 workspaceId: v.id("workspaces"),
686 roleId: v.optional(v.string()),686 roleId: v.optional(v.string()),
@@ -705,7 +705,7 @@ export const listToolRoleBindings = internalQuery({ · 18 unmodified lines
705 },705 },
706});706});
707 707
708export const deleteToolRoleBinding = internalMutation({708export const deleteToolPolicyAssignment = internalMutation({
709 args: {709 args: {
710 workspaceId: v.id("workspaces"),710 workspaceId: v.id("workspaces"),
711 bindingId: v.string(),711 bindingId: v.string(),
executor/packages/database/convex/workspace.ts+18−18
@@ -136,7 +136,7 @@ export const listToolPolicies = workspaceQuery({ · 135 unmodified lines
136 },136 },
137});137});
138 138
139export const upsertToolRole = workspaceMutation({139export const upsertToolPolicySet = workspaceMutation({
140 method: "POST",140 method: "POST",
141 requireAdmin: true,141 requireAdmin: true,
142 args: {142 args: {
@@ -145,7 +145,7 @@ export const upsertToolRole = workspaceMutation({ · 2 unmodified lines
145 description: v.optional(v.string()),145 description: v.optional(v.string()),
146 },146 },
147 handler: async (ctx, args) => {147 handler: async (ctx, args) => {
148 return await ctx.runMutation(internal.database.upsertToolRole, {148 return await ctx.runMutation(internal.database.upsertToolPolicySet, {
149 ...args,149 ...args,
150 workspaceId: ctx.workspaceId,150 workspaceId: ctx.workspaceId,
151 createdByAccountId: ctx.account._id,151 createdByAccountId: ctx.account._id,
@@ -153,32 +153,32 @@ export const upsertToolRole = workspaceMutation({ · 1 unmodified line
153 },153 },
154});154});
155 155
156export const listToolRoles = workspaceQuery({156export const listToolPolicySets = workspaceQuery({
157 method: "GET",157 method: "GET",
158 requireAdmin: true,158 requireAdmin: true,
159 args: {},159 args: {},
160 handler: async (ctx) => {160 handler: async (ctx) => {
161 return await ctx.runQuery(internal.database.listToolRoles, {161 return await ctx.runQuery(internal.database.listToolPolicySets, {
162 workspaceId: ctx.workspaceId,162 workspaceId: ctx.workspaceId,
163 });163 });
164 },164 },
165});165});
166 166
167export const deleteToolRole = workspaceMutation({167export const deleteToolPolicySet = workspaceMutation({
168 method: "POST",168 method: "POST",
169 requireAdmin: true,169 requireAdmin: true,
170 args: {170 args: {
171 roleId: v.string(),171 roleId: v.string(),
172 },172 },
173 handler: async (ctx, args) => {173 handler: async (ctx, args) => {
174 return await ctx.runMutation(internal.database.deleteToolRole, {174 return await ctx.runMutation(internal.database.deleteToolPolicySet, {
175 workspaceId: ctx.workspaceId,175 workspaceId: ctx.workspaceId,
176 roleId: args.roleId,176 roleId: args.roleId,
177 });177 });
178 },178 },
179});179});
180 180
181export const upsertToolRoleRule = workspaceMutation({181export const upsertToolPolicyRule = workspaceMutation({
182 method: "POST",182 method: "POST",
183 requireAdmin: true,183 requireAdmin: true,
184 args: {184 args: {
@@ -198,28 +198,28 @@ export const upsertToolRoleRule = workspaceMutation({ · 13 unmodified lines
198 priority: v.optional(v.number()),198 priority: v.optional(v.number()),
199 },199 },
200 handler: async (ctx, args) => {200 handler: async (ctx, args) => {
201 return await ctx.runMutation(internal.database.upsertToolRoleRule, {201 return await ctx.runMutation(internal.database.upsertToolPolicyRule, {
202 ...args,202 ...args,
203 workspaceId: ctx.workspaceId,203 workspaceId: ctx.workspaceId,
204 });204 });
205 },205 },
206});206});
207 207
208export const listToolRoleRules = workspaceQuery({208export const listToolPolicyRules = workspaceQuery({
209 method: "GET",209 method: "GET",
210 requireAdmin: true,210 requireAdmin: true,
211 args: {211 args: {
212 roleId: v.string(),212 roleId: v.string(),
213 },213 },
214 handler: async (ctx, args) => {214 handler: async (ctx, args) => {
215 return await ctx.runQuery(internal.database.listToolRoleRules, {215 return await ctx.runQuery(internal.database.listToolPolicyRules, {
216 workspaceId: ctx.workspaceId,216 workspaceId: ctx.workspaceId,
217 roleId: args.roleId,217 roleId: args.roleId,
218 });218 });
219 },219 },
220});220});
221 221
222export const deleteToolRoleRule = workspaceMutation({222export const deleteToolPolicyRule = workspaceMutation({
223 method: "POST",223 method: "POST",
224 requireAdmin: true,224 requireAdmin: true,
225 args: {225 args: {
@@ -227,7 +227,7 @@ export const deleteToolRoleRule = workspaceMutation({ · 1 unmodified line
227 ruleId: v.string(),227 ruleId: v.string(),
228 },228 },
229 handler: async (ctx, args) => {229 handler: async (ctx, args) => {
230 return await ctx.runMutation(internal.database.deleteToolRoleRule, {230 return await ctx.runMutation(internal.database.deleteToolPolicyRule, {
231 workspaceId: ctx.workspaceId,231 workspaceId: ctx.workspaceId,
232 roleId: args.roleId,232 roleId: args.roleId,
233 ruleId: args.ruleId,233 ruleId: args.ruleId,
@@ -235,7 +235,7 @@ export const deleteToolRoleRule = workspaceMutation({ · 1 unmodified line
235 },235 },
236});236});
237 237
238export const upsertToolRoleBinding = workspaceMutation({238export const upsertToolPolicyAssignment = workspaceMutation({
239 method: "POST",239 method: "POST",
240 requireAdmin: true,240 requireAdmin: true,
241 args: {241 args: {
@@ -248,35 +248,35 @@ export const upsertToolRoleBinding = workspaceMutation({ · 6 unmodified lines
248 expiresAt: v.optional(v.number()),248 expiresAt: v.optional(v.number()),
249 },249 },
250 handler: async (ctx, args) => {250 handler: async (ctx, args) => {
251 return await ctx.runMutation(internal.database.upsertToolRoleBinding, {251 return await ctx.runMutation(internal.database.upsertToolPolicyAssignment, {
252 ...args,252 ...args,
253 workspaceId: ctx.workspaceId,253 workspaceId: ctx.workspaceId,
254 });254 });
255 },255 },
256});256});
257 257
258export const listToolRoleBindings = workspaceQuery({258export const listToolPolicyAssignments = workspaceQuery({
259 method: "GET",259 method: "GET",
260 requireAdmin: true,260 requireAdmin: true,
261 args: {261 args: {
262 roleId: v.optional(v.string()),262 roleId: v.optional(v.string()),
263 },263 },
264 handler: async (ctx, args) => {264 handler: async (ctx, args) => {
265 return await ctx.runQuery(internal.database.listToolRoleBindings, {265 return await ctx.runQuery(internal.database.listToolPolicyAssignments, {
266 workspaceId: ctx.workspaceId,266 workspaceId: ctx.workspaceId,
267 roleId: args.roleId,267 roleId: args.roleId,
268 });268 });
269 },269 },
270});270});
271 271
272export const deleteToolRoleBinding = workspaceMutation({272export const deleteToolPolicyAssignment = workspaceMutation({
273 method: "POST",273 method: "POST",
274 requireAdmin: true,274 requireAdmin: true,
275 args: {275 args: {
276 bindingId: v.string(),276 bindingId: v.string(),
277 },277 },
278 handler: async (ctx, args) => {278 handler: async (ctx, args) => {
279 return await ctx.runMutation(internal.database.deleteToolRoleBinding, {279 return await ctx.runMutation(internal.database.deleteToolPolicyAssignment, {
280 workspaceId: ctx.workspaceId,280 workspaceId: ctx.workspaceId,
281 bindingId: args.bindingId,281 bindingId: args.bindingId,
282 });282 });

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