History

Fix toolkit CI lint and formatting

Rhys Sullivan committed 4 months ago

Commit 2836ce21 parent c7f81af17 files · +120 −294Browse files

Unified · Split

17 files
  1. apps/cloud/src/mcp/mount.test.ts +2 −8
  2. apps/cloud/src/mcp/mount.ts +2 −8
  3. apps/host-selfhost/src/mcp/auth.ts +1 −2
  4. apps/local/executor.config.ts +1 −2
  5. apps/local/src/executor.ts +15 −33
  6. apps/local/src/main.ts +6 −11
  7. apps/local/src/mcp.ts +26 −73
  8. apps/local/src/serve.ts +13 −38
  9. apps/local/vite.config.ts +14 −39
  10. e2e/local/toolkits-mcp.test.ts +2 −2
  11. e2e/scenarios/toolkits-mcp.test.ts +2 −2
  12. e2e/selfhost/toolkits-ui.test.ts +8 −2
  13. packages/plugins/google/src/sdk/openapi-ownership-migration.test.ts +12 −54
  14. packages/plugins/google/src/sdk/openapi-ownership-migration.ts +2 −6
  15. packages/plugins/toolkits/CHANGELOG.md +1 −0
  16. packages/plugins/toolkits/src/page.tsx +6 −11
  17. packages/plugins/toolkits/src/server.ts +7 −3
apps/cloud/src/mcp/mount.test.ts+2−8
@@ -1,10 +1,6 @@
11 import { describe, expect, it } from "@effect/vitest";
22
3−import {
4− protectedResourceMetadataUrlFor,
5− resourceUrlFor,
6− toolkitSlugFromRequest,
7−} from "./auth";
3+import { protectedResourceMetadataUrlFor, resourceUrlFor, toolkitSlugFromRequest } from "./auth";
84 import { classifyMcpPath, prepareMcpOrgScope } from "./mount";
95
106 describe("cloud MCP toolkit route normalization", () => {
@@ -50,9 +46,7 @@ describe("cloud MCP toolkit route normalization", () => { · 39 unmodified lines
5046
5147 it("builds toolkit-specific resource and metadata URLs", () => {
5248 expect(resourceUrlFor(null, "deploy")).toBe("https://executor.sh/mcp/toolkits/deploy");
53− expect(resourceUrlFor("acme", "deploy")).toBe(
54− "https://executor.sh/acme/mcp/toolkits/deploy",
55− );
49+ expect(resourceUrlFor("acme", "deploy")).toBe("https://executor.sh/acme/mcp/toolkits/deploy");
5650 expect(protectedResourceMetadataUrlFor(null, "deploy")).toBe(
5751 "https://executor.sh/.well-known/oauth-protected-resource/mcp/toolkits/deploy",
5852 );
apps/cloud/src/mcp/mount.ts+2−8
@@ -78,11 +78,7 @@ const matchMcpSuffix = (segments: readonly string[]): MatchedMcpSuffix | undefin · 77 unmodified lines
7878 const organizationId = orgSelectorSegment(segments[0]);
7979 return organizationId ? { organizationId } : undefined;
8080 }
81− if (
82− segments.length === 4 &&
83− segments[1] === "mcp" &&
84− segments[2] === "toolkits"
85− ) {
81+ if (segments.length === 4 && segments[1] === "mcp" && segments[2] === "toolkits") {
8682 const organizationId = orgSelectorSegment(segments[0]);
8783 const toolkitSlug = segments[3];
8884 return organizationId && toolkitSlug ? { organizationId, toolkitSlug } : undefined;
@@ -114,9 +110,7 @@ export const classifyMcpPath = (pathname: string): McpRoute => { · 25 unmodified lines
114110 const prmPrefix = "/.well-known/oauth-protected-resource";
115111 if (pathname.startsWith(`${prmPrefix}/`)) {
116112 const matched = matchMcpSuffix(segments.slice(2));
117− return matched === undefined
118− ? null
119− : { kind: "oauth-protected-resource", ...matched };
113+ return matched === undefined ? null : { kind: "oauth-protected-resource", ...matched };
120114 }
121115
122116 // MCP transport: `/mcp` or `/<org>/mcp`.
apps/host-selfhost/src/mcp/auth.ts+1−2
@@ -46,8 +46,7 @@ import { BetterAuth } from "../auth/better-auth"; · 45 unmodified lines
4646 // ---------------------------------------------------------------------------
4747
4848 const PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource";
49−const TOOLKIT_PROTECTED_RESOURCE_METADATA_PATH =
50− `${PROTECTED_RESOURCE_METADATA_PATH}/mcp/toolkits/:toolkitSlug`;
49+const TOOLKIT_PROTECTED_RESOURCE_METADATA_PATH = `${PROTECTED_RESOURCE_METADATA_PATH}/mcp/toolkits/:toolkitSlug`;
5150 const AUTHORIZATION_SERVER_METADATA_PATH = "/.well-known/oauth-authorization-server";
5251 const TOOLKIT_MCP_SEGMENT = "/mcp/toolkits/";
5352
apps/local/executor.config.ts+1−2
@@ -37,8 +37,7 @@ export default defineExecutorConfig({ · 36 unmodified lines
3737 onepasswordHttpPlugin(),
3838 desktopSettingsPlugin({
3939 webBaseUrl:
40− process.env.EXECUTOR_WEB_BASE_URL ??
41− `http://localhost:${process.env.PORT ?? "4788"}`,
40+ process.env.EXECUTOR_WEB_BASE_URL ?? `http://localhost:${process.env.PORT ?? "4788"}`,
4241 }),
4342 ] as const,
4443 });
apps/local/src/executor.ts+15−33
@@ -49,8 +49,7 @@ const makeTenantId = (cwd: string): string => { · 48 unmodified lines
4949 return `${folder}-${hash}`;
5050 };
5151
52−const resolvePluginConfigPath = (scopeDir: string): string =>
53− join(scopeDir, "executor.jsonc");
52+const resolvePluginConfigPath = (scopeDir: string): string => join(scopeDir, "executor.jsonc");
5453
5554 // Plugins reach the host through two doors that compose:
5655 // - `executor.config.ts`'s static tuple
@@ -69,14 +68,11 @@ const loadLocalPlugins = (options: LocalExecutorOptions = {}) => · 12 unmodified lines
6968 activeToolkitSlug: options.activeToolkitSlug,
7069 });
7170 const dynamicPlugins =
72− (yield* Effect.promise(() =>
73− loadPluginsFromJsonc({ path: resolvePluginConfigPath(cwd) }),
74− )) ?? [];
71+ (yield* Effect.promise(() => loadPluginsFromJsonc({ path: resolvePluginConfigPath(cwd) }))) ??
72+ [];
7573
7674 const staticPackageNames = new Set(
77− staticPlugins
78− .map((plugin) => plugin.packageName)
79− .filter((name): name is string => !!name),
75+ staticPlugins.map((plugin) => plugin.packageName).filter((name): name is string => !!name),
8076 );
8177 const dedupedDynamic = dynamicPlugins.filter((plugin) => {
8278 if (plugin.packageName && staticPackageNames.has(plugin.packageName)) {
@@ -101,23 +97,18 @@ interface LocalExecutorBundle { · 18 unmodified lines
10197 readonly plugins: LocalPlugins;
10298 }
10399
104−class LocalExecutorTag extends Context.Service<
105− LocalExecutorTag,
106− LocalExecutorBundle
107−>()("@executor-js/local/Executor") {}
100+class LocalExecutorTag extends Context.Service<LocalExecutorTag, LocalExecutorBundle>()(
101+ "@executor-js/local/Executor",
102+) {}
108103
109104 export type LocalExecutor = LocalExecutorBundle["executor"];
110105
111−class LocalExecutorCreateError extends Data.TaggedError(
112− "LocalExecutorCreateError",
113−)<{
106+class LocalExecutorCreateError extends Data.TaggedError("LocalExecutorCreateError")<{
114107 readonly message: string;
115108 readonly cause: unknown;
116109 }> {}
117110
118−class LocalExecutorDisposeError extends Data.TaggedError(
119− "LocalExecutorDisposeError",
120−)<{
111+class LocalExecutorDisposeError extends Data.TaggedError("LocalExecutorDisposeError")<{
121112 readonly operation: "createHandle" | "disposeExecutor" | "disposeRuntime";
122113 readonly cause: unknown;
123114 }> {}
@@ -142,13 +133,10 @@ const handleOrNull = (promise: ReturnType<typeof createExecutorHandle>) => · 18 unmodified lines
142133 Effect.runPromise(
143134 Effect.tryPromise({
144135 try: () => promise,
145− catch: (cause) =>
146− new LocalExecutorDisposeError({ operation: "createHandle", cause }),
136+ catch: (cause) => new LocalExecutorDisposeError({ operation: "createHandle", cause }),
147137 }).pipe(
148138 Effect.catch(() =>
149− Effect.succeed<Awaited<ReturnType<typeof createExecutorHandle>> | null>(
150− null,
151− ),
139+ Effect.succeed<Awaited<ReturnType<typeof createExecutorHandle>> | null>(null),
152140 ),
153141 ),
154142 );
@@ -212,8 +200,7 @@ const createLocalExecutorLayer = (options: LocalExecutorOptions = {}) => { · 57 unmodified lines
212200 // resolution so a custom $PORT flows through. EXECUTOR_WEB_BASE_URL
213201 // overrides entirely for deployments where the UI is on a different host.
214202 const webBaseUrl =
215− process.env.EXECUTOR_WEB_BASE_URL ??
216− `http://localhost:${process.env.PORT ?? "4788"}`;
203+ process.env.EXECUTOR_WEB_BASE_URL ?? `http://localhost:${process.env.PORT ?? "4788"}`;
217204
218205 const executor = yield* createExecutor({
219206 tenant: Tenant.make(tenantId),
@@ -247,9 +234,7 @@ const createLocalExecutorLayer = (options: LocalExecutorOptions = {}) => { · 27 unmodified lines
247234 );
248235 };
249236
250−export const createExecutorHandle = async (
251− options: LocalExecutorOptions = {},
252−) => {
237+export const createExecutorHandle = async (options: LocalExecutorOptions = {}) => {
253238 const layer = createLocalExecutorLayer(options);
254239 const runtime = ManagedRuntime.make(layer);
255240 const bundle = await runtime.runPromise(LocalExecutorTag.asEffect());
@@ -275,17 +260,14 @@ const loadSharedHandle = () => { · 19 unmodified lines
275260 return sharedHandlePromise;
276261 };
277262
278−export const getExecutor = () =>
279− loadSharedHandle().then((handle) => handle.executor);
263+export const getExecutor = () => loadSharedHandle().then((handle) => handle.executor);
280264 export const getExecutorBundle = () => loadSharedHandle();
281265
282266 export const disposeExecutor = async (): Promise<void> => {
283267 const currentHandlePromise = sharedHandlePromise;
284268 sharedHandlePromise = null;
285269
286− const handle = currentHandlePromise
287− ? await handleOrNull(currentHandlePromise)
288− : null;
270+ const handle = currentHandlePromise ? await handleOrNull(currentHandlePromise) : null;
289271 if (handle) {
290272 await ignorePromiseFailure("disposeExecutor", () => handle.dispose());
291273 }
apps/local/src/main.ts+6−11
@@ -53,9 +53,7 @@ const closeServerHandlers = async (handlers: ServerHandlers): Promise<void> => { · 52 unmodified lines
5353 );
5454 };
5555
56−export const createServerHandlers = async (
57− token: string,
58−): Promise<ServerHandlers> => {
56+export const createServerHandlers = async (token: string): Promise<ServerHandlers> => {
5957 // The typed `/api` web-handler comes from `ExecutorApp.make` (./app.ts). The
6058 // boot bearer token is the authoritative `/api` gate (see `identity.ts`).
6159 const apiHandler: ServerHandlers["api"] = await makeLocalApiHandler(token);
@@ -90,18 +88,15 @@ export const createServerHandlers = async ( · 28 unmodified lines
9088 return { api: apiHandler, mcp };
9189 };
9290
93−export class ServerHandlersService extends Context.Service<
94− ServerHandlersService,
95− ServerHandlers
96−>()("@executor-js/local/ServerHandlersService") {}
91+export class ServerHandlersService extends Context.Service<ServerHandlersService, ServerHandlers>()(
92+ "@executor-js/local/ServerHandlersService",
93+) {}
9794
9895 // The handlers are built once per process and memoized. The boot token is
9996 // captured on the first call (serve.ts / the vite dev middleware both pass the
10097 // SAME token loaded from `auth.json`), so memoization on first-call is correct.
101−let serverHandlersRuntime: ManagedRuntime.ManagedRuntime<
102− ServerHandlersService,
103− never
104−> | null = null;
98+let serverHandlersRuntime: ManagedRuntime.ManagedRuntime<ServerHandlersService, never> | null =
99+ null;
105100
106101 const getServerHandlersRuntime = (
107102 token: string,
apps/local/src/mcp.ts+26−73
@@ -67,9 +67,7 @@ const formatBoundaryError = (error: unknown): unknown => { · 66 unmodified lines
6767 return error;
6868 };
6969
70−const ignoreClose = (
71− close: (() => Promise<void>) | undefined,
72−): Promise<void> =>
70+const ignoreClose = (close: (() => Promise<void>) | undefined): Promise<void> =>
7371 close
7472 ? Effect.runPromise(
7573 Effect.ignore(
@@ -81,10 +79,8 @@ const ignoreClose = ( · 5 unmodified lines
8179 )
8280 : Promise.resolve();
8381
84−const pausedRequestPattern =
85− /^\/api\/mcp-sessions\/([^/?#]+)\/executions\/([^/?#]+)$/;
86−const approvalRequestPattern =
87− /^\/api\/mcp-sessions\/([^/?#]+)\/executions\/([^/?#]+)\/resume$/;
82+const pausedRequestPattern = /^\/api\/mcp-sessions\/([^/?#]+)\/executions\/([^/?#]+)$/;
83+const approvalRequestPattern = /^\/api\/mcp-sessions\/([^/?#]+)\/executions\/([^/?#]+)\/resume$/;
8884
8985 const json = (value: unknown, status = 200): Response =>
9086 new Response(JSON.stringify(value), {
@@ -97,15 +93,10 @@ const readResumeResponse = (request: Request): Promise<ResumeResponse | null> => · 6 unmodified lines
9793 Effect.tryPromise({
9894 try: () => request.json(),
9995 catch: () => null,
100− }).pipe(
101− Effect.map((raw) => (raw === null ? null : decodeResumeResponse(raw))),
102− ),
96+ }).pipe(Effect.map((raw) => (raw === null ? null : decodeResumeResponse(raw)))),
10397 );
10498
105−const resumeApprovalResult = (
106− executionId: string,
107− response: ResumeResponse,
108−) => ({
99+const resumeApprovalResult = (executionId: string, response: ResumeResponse) => ({
109100 status: "completed",
110101 ...formatResumeAcknowledgement(executionId, response),
111102 isError: false,
@@ -121,23 +112,18 @@ const resourceFromRequest = (request: Request): McpResource | null => { · 9 unmodified lines
121112 return { kind: "toolkit", slug: decodeURIComponent(match[1]) };
122113 };
123114
124−const engineFromConfig = (
125− config: ExecutorMcpServerConfig,
126−): AnyExecutionEngine | null => ("engine" in config ? config.engine : null);
115+const engineFromConfig = (config: ExecutorMcpServerConfig): AnyExecutionEngine | null =>
116+ "engine" in config ? config.engine : null;
127117
128118 const normalizeHandlerConfig = (
129119 input: ExecutorMcpServerConfig | LocalMcpRequestHandlerConfig,
130−): LocalMcpRequestHandlerConfig =>
131− "defaultConfig" in input ? input : { defaultConfig: input };
120+): LocalMcpRequestHandlerConfig => ("defaultConfig" in input ? input : { defaultConfig: input });
132121
133122 export const createMcpRequestHandler = (
134123 input: ExecutorMcpServerConfig | LocalMcpRequestHandlerConfig,
135124 ): McpRequestHandler => {
136125 const handlerConfig = normalizeHandlerConfig(input);
137− const transports = new Map<
138− string,
139− WebStandardStreamableHTTPServerTransport
140− >();
126+ const transports = new Map<string, WebStandardStreamableHTTPServerTransport>();
141127 const servers = new Map<string, McpServer>();
142128 const resources = new Map<string, McpResource>();
143129 const sessionEngines = new Map<string, AnyExecutionEngine>();
@@ -151,29 +137,19 @@ export const createMcpRequestHandler = ( · 7 unmodified lines
151137 ): Promise<ReturnType<typeof formatPausedExecution> | null> =>
152138 (sessionEngines.get(sessionId) ?? defaultEngine)
153139 ? Effect.runPromise(
154− (sessionEngines.get(sessionId) ?? defaultEngine)!
155− .getPausedExecution(executionId)
156− .pipe(
157− Effect.map((paused) =>
158− paused ? formatPausedExecution(paused) : null,
159− ),
160− Effect.orElseSucceed(() => null),
161− ),
140+ (sessionEngines.get(sessionId) ?? defaultEngine)!.getPausedExecution(executionId).pipe(
141+ Effect.map((paused) => (paused ? formatPausedExecution(paused) : null)),
142+ Effect.orElseSucceed(() => null),
143+ ),
162144 )
163145 : Promise.resolve(null);
164146
165− const configForResource = async (
166− resource: McpResource,
167− ): Promise<LocalMcpServerConfig> => {
168− if (!handlerConfig.createConfigForResource)
169− return { config: handlerConfig.defaultConfig };
147+ const configForResource = async (resource: McpResource): Promise<LocalMcpServerConfig> => {
148+ if (!handlerConfig.createConfigForResource) return { config: handlerConfig.defaultConfig };
170149 return handlerConfig.createConfigForResource(resource);
171150 };
172151
173− const dispose = async (
174− id: string,
175− opts: { transport?: boolean; server?: boolean } = {},
176− ) => {
152+ const dispose = async (id: string, opts: { transport?: boolean; server?: boolean } = {}) => {
177153 const t = transports.get(id);
178154 const s = servers.get(id);
179155 const close = sessionClosers.get(id);
@@ -197,15 +173,8 @@ export const createMcpRequestHandler = ( · 17 unmodified lines
197173 const transport = transports.get(sessionId);
198174 if (!transport) return jsonError(404, -32001, "Session not found");
199175 const sessionResource = resources.get(sessionId);
200− if (
201− !sessionResource ||
202− mcpResourceKey(sessionResource) !== mcpResourceKey(resource)
203− ) {
204− return jsonError(
205− 403,
206− -32003,
207− "Session belongs to a different MCP resource",
208− );
176+ if (!sessionResource || mcpResourceKey(sessionResource) !== mcpResourceKey(resource)) {
177+ return jsonError(403, -32003, "Session belongs to a different MCP resource");
209178 }
210179 return transport.handleRequest(request);
211180 }
@@ -221,12 +190,9 @@ export const createMcpRequestHandler = ( · 9 unmodified lines
221190 transports.set(sid, transport);
222191 if (created) servers.set(sid, created);
223192 resources.set(sid, resource);
224− const engine = resourceConfig
225− ? engineFromConfig(resourceConfig.config)
226− : null;
193+ const engine = resourceConfig ? engineFromConfig(resourceConfig.config) : null;
227194 if (engine) sessionEngines.set(sid, engine);
228− if (resourceConfig?.close)
229− sessionClosers.set(sid, resourceConfig.close);
195+ if (resourceConfig?.close) sessionClosers.set(sid, resourceConfig.close);
230196 },
231197 onsessionclosed: (sid) => void dispose(sid, { server: true }),
232198 });
@@ -249,11 +215,7 @@ export const createMcpRequestHandler = ( · 16 unmodified lines
249215 ? {
250216 mode: "browser" as const,
251217 approvalUrl: (executionId) =>
252− approvalUrlForRequest(
253− request,
254− executionId,
255− createdSessionId,
256− ),
218+ approvalUrlForRequest(request, executionId, createdSessionId),
257219 }
258220 : { mode: elicitationMode },
259221 }),
@@ -283,13 +245,9 @@ export const createMcpRequestHandler = ( · 23 unmodified lines
283245 handlePausedRequest: async (request) => {
284246 const match = pausedRequestPattern.exec(new URL(request.url).pathname);
285247 if (!match) return json({ error: "Not found" }, 404);
286− if (request.method !== "GET")
287− return json({ error: "Method not allowed" }, 405);
248+ if (request.method !== "GET") return json({ error: "Method not allowed" }, 405);
288249
289− const paused = await pausedDetail(
290− decodeURIComponent(match[1]),
291− decodeURIComponent(match[2]),
292− );
250+ const paused = await pausedDetail(decodeURIComponent(match[1]), decodeURIComponent(match[2]));
293251 if (!paused) return json({ error: "Paused execution not found" }, 404);
294252 return json({ text: paused.text, structured: paused.structured });
295253 },
@@ -297,8 +255,7 @@ export const createMcpRequestHandler = ( · 1 unmodified line
297255 handleApprovalRequest: async (request) => {
298256 const match = approvalRequestPattern.exec(new URL(request.url).pathname);
299257 if (!match) return json({ error: "Not found" }, 404);
300− if (request.method !== "POST")
301− return json({ error: "Method not allowed" }, 405);
258+ if (request.method !== "POST") return json({ error: "Method not allowed" }, 405);
302259
303260 const sessionId = decodeURIComponent(match[1]);
304261 const executionId = decodeURIComponent(match[2]);
@@ -316,9 +273,7 @@ export const createMcpRequestHandler = ( · 11 unmodified lines
316273
317274 close: async () => {
318275 const ids = new Set([...transports.keys(), ...servers.keys()]);
319− await Promise.all(
320− [...ids].map((id) => dispose(id, { transport: true, server: true })),
321− );
276+ await Promise.all([...ids].map((id) => dispose(id, { transport: true, server: true })));
322277 },
323278 };
324279 };
@@ -327,9 +282,7 @@ export const createMcpRequestHandler = ( · 2 unmodified lines
327282 // Stdio transport
328283 // ---------------------------------------------------------------------------
329284
330−export const runMcpStdioServer = async (
331− config: ExecutorMcpServerConfig,
332−): Promise<void> => {
285+export const runMcpStdioServer = async (config: ExecutorMcpServerConfig): Promise<void> => {
333286 startIntegrationsRefresh();
334287
335288 const server = await Effect.runPromise(createExecutorMcpServer(config));
apps/local/src/serve.ts+13−38
@@ -35,10 +35,7 @@ const htmlResponse = (file: Bun.BunFile): Response => · 34 unmodified lines
3535 headers: { "content-type": "text/html", "cache-control": "no-store" },
3636 });
3737
38−function collectStaticRoutes(
39− dir: string,
40− prefix = "",
41−): Record<string, StaticHandler> {
38+function collectStaticRoutes(dir: string, prefix = ""): Record<string, StaticHandler> {
4239 const routes: Record<string, StaticHandler> = {};
4340 // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: filesystem route discovery is best-effort for optional built assets
4441 try {
@@ -67,9 +64,7 @@ function collectStaticRoutes( · 22 unmodified lines
6764 * Convert an embedded web UI map (path → bunfs path) into Bun.serve routes.
6865 * The embedded map is generated by the CLI build step using `with { type: "file" }`.
6966 */
70−function embeddedToStaticRoutes(
71− embedded: Record<string, string>,
72−): Record<string, StaticHandler> {
67+function embeddedToStaticRoutes(embedded: Record<string, string>): Record<string, StaticHandler> {
7368 const routes: Record<string, StaticHandler> = {};
7469 for (const [key, bunfsPath] of Object.entries(embedded)) {
7570 const file = Bun.file(bunfsPath);
@@ -165,9 +160,7 @@ async function startViteChild(): Promise<ViteChild> { · 89 unmodified lines
165160 }
166161 if (child.exitCode !== null) {
167162 // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: child process aborted before becoming ready
168− throw new Error(
169− `vite dev exited with code ${child.exitCode} before becoming ready`,
170− );
163+ throw new Error(`vite dev exited with code ${child.exitCode} before becoming ready`);
171164 }
172165 await Bun.sleep(150);
173166 }
@@ -256,8 +249,7 @@ const withCorsHeaders = ( · 82 unmodified lines
256249 if (!origin || !isAllowedOrigin(origin, allowedHosts)) return response;
257250 const headers = new Headers(response.headers);
258251 headers.set("access-control-allow-origin", origin);
259− for (const [key, value] of Object.entries(corsHeaders))
260− headers.set(key, value);
252+ for (const [key, value] of Object.entries(corsHeaders)) headers.set(key, value);
261253 headers.set(
262254 "access-control-allow-headers",
263255 req.headers.get("access-control-request-headers") ??
@@ -271,23 +263,17 @@ const withCorsHeaders = ( · 7 unmodified lines
271263 });
272264 };
273265
274−const corsPreflightResponse = (
275− req: Request,
276− allowedHosts: ReadonlySet<string>,
277−): Response =>
266+const corsPreflightResponse = (req: Request, allowedHosts: ReadonlySet<string>): Response =>
278267 withCorsHeaders(req, new Response(null, { status: 204 }), allowedHosts);
279268
280−export async function startServer(
281− opts: StartServerOptions = {},
282−): Promise<ServerInstance> {
269+export async function startServer(opts: StartServerOptions = {}): Promise<ServerInstance> {
283270 const port = opts.port ?? parseInt(process.env.PORT ?? "4788", 10);
284271 const hostname = opts.hostname ?? "127.0.0.1";
285272 // ONE credential, always present: an explicit override or the stable token
286273 // from auth.json (minted on first run). Auth is unconditionally on — loopback
287274 // is no longer a free pass, since Executor runs arbitrary code that any local
288275 // process could otherwise drive.
289− const authToken =
290− normalizeCredential(opts.authToken) ?? loadOrMintLocalAuthToken();
276+ const authToken = normalizeCredential(opts.authToken) ?? loadOrMintLocalAuthToken();
291277 const isAuthorized = makeIsAuthorized(authToken);
292278 // CORS-only origin allowlist (no Host gate — the bearer is the boundary).
293279 const corsAllowedHosts = new Set<string>([
@@ -317,26 +303,20 @@ export async function startServer( · 23 unmodified lines
317303
318304 const devMode = process.env.EXECUTOR_DEV === "1" && !opts.embeddedWebUI;
319305 if (devMode) {
320− console.log(
321− "[executor] EXECUTOR_DEV=1 — spawning vite dev child for live UI",
322− );
306+ console.log("[executor] EXECUTOR_DEV=1 — spawning vite dev child for live UI");
323307 viteChild = await startViteChild();
324308 // Diagnostic only — this is the internal vite port the daemon proxies to.
325309 // It must NOT read as a destination: the URL to open is the `Open:` line the
326310 // CLI prints (the daemon port, with ?_token). Hitting the vite port directly
327311 // skips that bootstrap and lands on the auth gate.
328− console.log(
329− `[executor] (internal) vite dev child at ${viteChild.url} — don't open this`,
330− );
312+ console.log(`[executor] (internal) vite dev child at ${viteChild.url} — don't open this`);
331313 serveIndex = () =>
332314 // Unused when viteChild is non-null; defined so the type checker
333315 // can keep `serveIndex` non-nullable.
334316 new Response("vite not ready", { status: 503 });
335317 } else if (opts.embeddedWebUI) {
336318 staticRoutes = embeddedToStaticRoutes(opts.embeddedWebUI);
337− const indexFile = Bun.file(
338− opts.embeddedWebUI["index.html"] ?? join(clientDir, "index.html"),
339− );
319+ const indexFile = Bun.file(opts.embeddedWebUI["index.html"] ?? join(clientDir, "index.html"));
340320 serveIndex = () => htmlResponse(indexFile);
341321 } else {
342322 staticRoutes = collectStaticRoutes(clientDir);
@@ -364,9 +344,7 @@ export async function startServer( · 21 unmodified lines
364344 // Unauthenticated liveness probe — carries no data, used by the CLI
365345 // reachability check (which therefore never forwards a credential).
366346 if (url.pathname === "/api/health" && req.method === "GET") {
367− return withCors(
368− new Response("ok", { headers: { "content-type": "text/plain" } }),
369− );
347+ return withCors(new Response("ok", { headers: { "content-type": "text/plain" } }));
370348 }
371349
372350 // OAuth provider callbacks are hit by the user's external browser and
@@ -374,8 +352,7 @@ export async function startServer( · 1 unmodified line
374352 // gate — see isUnauthenticatedOAuthCallbackPath. Everything else under
375353 // /api and /mcp requires the bearer.
376354 const skipAuth = isUnauthenticatedOAuthCallbackPath(url.pathname);
377− const isMcpPath =
378− url.pathname === "/mcp" || url.pathname.startsWith("/mcp/");
355+ const isMcpPath = url.pathname === "/mcp" || url.pathname.startsWith("/mcp/");
379356 const isGatedSurface = url.pathname.startsWith("/api") || isMcpPath;
380357
381358 if (isGatedSurface && !skipAuth && !isAuthorized(req)) {
@@ -457,7 +434,5 @@ export async function startServer( · 75 unmodified lines
457434
458435 if (import.meta.main) {
459436 const server = await startServer();
460− console.log(
461− `Executor listening on http://localhost:${server.port}/?_token=${server.authToken}`,
462− );
437+ console.log(`Executor listening on http://localhost:${server.port}/?_token=${server.authToken}`);
463438 }
apps/local/vite.config.ts+14−39
@@ -6,10 +6,7 @@ import { defineConfig, type Plugin } from "vite"; · 5 unmodified lines
66 import appPlugin from "@executor-js/app/vite";
77 import { loadOrMintLocalAuthToken } from "./src/auth";
88 import { consumeOAuthResult } from "./src/oauth-result-store";
9−import {
10− isUnauthenticatedOAuthCallbackPath,
11− makeIsAuthorized,
12−} from "./src/serve-shared";
9+import { isUnauthenticatedOAuthCallbackPath, makeIsAuthorized } from "./src/serve-shared";
1310
1411 // oxlint-disable-next-line executor/no-json-parse -- boundary: Vite config reads package metadata from package.json
1512 const rootPackage = JSON.parse(
@@ -26,9 +23,7 @@ const cliPackage = JSON.parse( · 10 unmodified lines
2623 ) as { version?: string };
2724
2825 const repositoryUrl =
29− typeof rootPackage.repository === "string"
30− ? rootPackage.repository
31− : rootPackage.repository?.url;
26+ typeof rootPackage.repository === "string" ? rootPackage.repository : rootPackage.repository?.url;
3227
3328 const EXECUTOR_VERSION = cliPackage.version ?? rootPackage.version;
3429 const EXECUTOR_GITHUB_URL = (
@@ -67,9 +62,7 @@ function executorApiPlugin(): Plugin { · 32 unmodified lines
6762 server.httpServer?.once("listening", () => {
6863 const address = server.httpServer?.address();
6964 const port =
70− typeof address === "object" && address
71− ? address.port
72− : server.config.server.port;
65+ typeof address === "object" && address ? address.port : server.config.server.port;
7366 server.config.logger.info(
7467 `\n Open with auth: http://127.0.0.1:${port}/?_token=${devToken}\n`,
7568 );
@@ -77,10 +70,7 @@ function executorApiPlugin(): Plugin { · 1 unmodified line
7770 }
7871
7972 server.watcher.on("change", (path) => {
80− if (
81− path.includes("/apps/local/src/") ||
82− path.endsWith("/executor.config.ts")
83− ) {
73+ if (path.includes("/apps/local/src/") || path.endsWith("/executor.config.ts")) {
8474 handlers = null;
8575 }
8676 });
@@ -100,8 +90,7 @@ function executorApiPlugin(): Plugin { · 13 unmodified lines
10090 // bootstrap, so the UI is unaffected; external MCP clients use the
10191 // daemon port.
10292 const authExempt =
103− pathOnly === "/api/health" ||
104− isUnauthenticatedOAuthCallbackPath(pathOnly);
93+ pathOnly === "/api/health" || isUnauthenticatedOAuthCallbackPath(pathOnly);
10594 if (!authExempt) {
10695 const presented = req.headers.authorization;
10796 const authValue = Array.isArray(presented) ? presented[0] : presented;
@@ -109,9 +98,7 @@ function executorApiPlugin(): Plugin { · 1 unmodified line
10998 "http://localhost/",
11099 authValue ? { headers: { authorization: authValue } } : undefined,
111100 );
112− if (
113− !makeIsAuthorized(devToken ?? loadOrMintLocalAuthToken())(probe)
114− ) {
101+ if (!makeIsAuthorized(devToken ?? loadOrMintLocalAuthToken())(probe)) {
115102 res.statusCode = 401;
116103 res.setHeader("www-authenticate", 'Bearer realm="executor"');
117104 res.end("Unauthorized");
@@ -123,16 +110,13 @@ function executorApiPlugin(): Plugin { · 5 unmodified lines
123110 try {
124111 if (!handlers) {
125112 const { getServerHandlers } = await import("./src/main");
126− handlers = await getServerHandlers(
127− devToken ?? loadOrMintLocalAuthToken(),
128− );
113+ handlers = await getServerHandlers(devToken ?? loadOrMintLocalAuthToken());
129114 }
130115
131116 const origin = `http://${req.headers.host ?? "localhost"}`;
132117 const headers = new Headers();
133118 for (const [key, value] of Object.entries(req.headers)) {
134− if (value)
135− headers.set(key, Array.isArray(value) ? value.join(", ") : value);
119+ if (value) headers.set(key, Array.isArray(value) ? value.join(", ") : value);
136120 }
137121
138122 const hasBody = req.method !== "GET" && req.method !== "HEAD";
@@ -158,21 +142,14 @@ function executorApiPlugin(): Plugin { · 19 unmodified lines
158142 : handlers.mcp.handleApprovalRequest;
159143 response = await handler(webRequest(rawUrl));
160144 } else {
161− const awaitMatch = /^\/api\/oauth\/await\/([^/?#]+)$/.exec(
162− pathOnly,
163− );
145+ const awaitMatch = /^\/api\/oauth\/await\/([^/?#]+)$/.exec(pathOnly);
164146 if (awaitMatch && req.method === "GET") {
165− response = new Response(
166− JSON.stringify(consumeOAuthResult(awaitMatch[1]!)),
167− {
168− headers: { "content-type": "application/json" },
169− },
170− );
147+ response = new Response(JSON.stringify(consumeOAuthResult(awaitMatch[1]!)), {
148+ headers: { "content-type": "application/json" },
149+ });
171150 } else {
172151 // Strip /api prefix for Effect handlers.
173− response = await handlers.api.handler(
174− webRequest(rawUrl.slice("/api".length) || "/"),
175− );
152+ response = await handlers.api.handler(webRequest(rawUrl.slice("/api".length) || "/"));
176153 }
177154 }
178155
@@ -211,9 +188,7 @@ export default defineConfig({ · 32 unmodified lines
211188 "import.meta.env.VITE_APP_VERSION": JSON.stringify(EXECUTOR_VERSION),
212189 "import.meta.env.VITE_GITHUB_URL": JSON.stringify(EXECUTOR_GITHUB_URL),
213190 "import.meta.env.VITE_EXECUTOR_DEV_CLI_CWD": JSON.stringify(REPO_ROOT),
214− "process.env.NODE_ENV": JSON.stringify(
215− process.env.NODE_ENV ?? "development",
216− ),
191+ "process.env.NODE_ENV": JSON.stringify(process.env.NODE_ENV ?? "development"),
217192 },
218193 resolve: {
219194 tsconfigPaths: true,
e2e/local/toolkits-mcp.test.ts+2−2
@@ -67,8 +67,8 @@ const pingSpec = (baseUrl: string): string => · 66 unmodified lines
6767 });
6868
6969 const closeServer = (server: Server): Promise<void> =>
70− new Promise((resolve, reject) => {
71− server.close((error) => (error ? reject(error) : resolve()));
70+ new Promise((resolve) => {
71+ server.close(() => resolve());
7272 });
7373
7474 const servePingApi = Effect.acquireRelease(
e2e/scenarios/toolkits-mcp.test.ts+2−2
@@ -56,8 +56,8 @@ const pingSpec = (baseUrl: string): string => · 55 unmodified lines
5656 });
5757
5858 const closeServer = (server: Server): Promise<void> =>
59− new Promise((resolve, reject) => {
60− server.close((error) => (error ? reject(error) : resolve()));
59+ new Promise((resolve) => {
60+ server.close(() => resolve());
6161 });
6262
6363 const servePingApi = Effect.acquireRelease(
e2e/selfhost/toolkits-ui.test.ts+8−2
@@ -156,13 +156,19 @@ scenario( · 155 unmodified lines
156156 });
157157
158158 await step("Remove the connection from the toolkit tools list", async () => {
159− await page.getByRole("button", { name: /^Remove connection / }).first().click();
159+ await page
160+ .getByRole("button", { name: /^Remove connection / })
161+ .first()
162+ .click();
160163 await page.getByText("No connections added").waitFor();
161164 await page.getByRole("button", { name: "Add connection to toolkit" }).click();
162165 const dialog = page.getByRole("dialog", { name: "Add connection" });
163166 await dialog.waitFor();
164167 await dialog.getByLabel("Search connections and tools").fill("policies.list");
165− await dialog.getByRole("button", { name: /^Add connection / }).first().click();
168+ await dialog
169+ .getByRole("button", { name: /^Add connection / })
170+ .first()
171+ .click();
166172 await dialog.waitFor({ state: "hidden" });
167173 const toolkitTools = page.getByRole("region", { name: "Toolkit tools" });
168174 await toolkitTools.getByLabel("Filter tools").fill("policies.list");
packages/plugins/google/src/sdk/openapi-ownership-migration.test.ts+12−54
@@ -49,17 +49,7 @@ const insertIntegrationRawConfig = ( · 48 unmodified lines
4949 sql: `INSERT INTO integration
5050 (row_id, tenant, slug, plugin_id, name, description, config, can_remove, can_refresh, created_at, updated_at)
5151 VALUES (?, ?, ?, ?, ?, ?, ?, 1, 1, ?, ?)`,
52− args: [
53− row.rowId,
54− row.tenant,
55− row.slug,
56− row.pluginId,
57− row.slug,
58− row.slug,
59− row.config,
60− now,
61− now,
62− ],
52+ args: [row.rowId, row.tenant, row.slug, row.pluginId, row.slug, row.slug, row.config, now, now],
6353 });
6454
6555 const insertBlob = (
@@ -121,14 +111,7 @@ const insertTool = ( · 55 unmodified lines
121111 sql: `INSERT INTO tool
122112 (tenant, owner, subject, integration, connection, plugin_id, name, description, input_schema, output_schema, annotations, created_at, updated_at, row_id)
123113 VALUES (?, 'org', '', ?, 'default', ?, 'items.list', 'List items', NULL, NULL, NULL, ?, ?, ?)`,
124− args: [
125− row.tenant,
126− row.integration,
127− row.pluginId,
128− now,
129− now,
130− `tool-${row.integration}`,
131− ],
114+ args: [row.tenant, row.integration, row.pluginId, now, now, `tool-${row.integration}`],
132115 });
133116
134117 const insertDefinition = (
@@ -143,21 +126,13 @@ const insertDefinition = ( · 8 unmodified lines
143126 sql: `INSERT INTO definition
144127 (tenant, owner, subject, integration, connection, plugin_id, name, schema, created_at, row_id)
145128 VALUES (?, 'org', '', ?, 'default', ?, 'Item', '{}', ?, ?)`,
146− args: [
147− row.tenant,
148− row.integration,
149− row.pluginId,
150− now,
151− `definition-${row.integration}`,
152− ],
129+ args: [row.tenant, row.integration, row.pluginId, now, `definition-${row.integration}`],
153130 });
154131
155132 describe("runSqliteGoogleOpenApiOwnershipMigration", () => {
156133 it.effect("skips malformed legacy integration config rows", () =>
157134 Effect.gen(function* () {
158− const db = yield* Effect.promise(() =>
159− createSqliteTestFumaDb({ tables: collectTables() }),
160− );
135+ const db = yield* Effect.promise(() => createSqliteTestFumaDb({ tables: collectTables() }));
161136
162137 yield* Effect.promise(() =>
163138 insertIntegrationRawConfig(db.client, {
@@ -169,16 +144,12 @@ describe("runSqliteGoogleOpenApiOwnershipMigration", () => { · 5 unmodified lines
169144 }),
170145 );
171146
172− expect(yield* runSqliteGoogleOpenApiOwnershipMigration(db.client)).toBe(
173− 0,
174− );
147+ expect(yield* runSqliteGoogleOpenApiOwnershipMigration(db.client)).toBe(0);
175148
176149 const integrations = yield* Effect.promise(() =>
177150 db.client.execute("SELECT slug, plugin_id, config FROM integration"),
178151 );
179− expect(integrations.rows).toEqual([
180− { slug: "broken", plugin_id: "openapi", config: "" },
181− ]);
152+ expect(integrations.rows).toEqual([{ slug: "broken", plugin_id: "openapi", config: "" }]);
182153
183154 yield* Effect.promise(() => db.close());
184155 }),
@@ -186,9 +157,7 @@ describe("runSqliteGoogleOpenApiOwnershipMigration", () => { · 1 unmodified line
186157
187158 it.effect("moves OpenAPI-owned Google bundle rows to the Google plugin", () =>
188159 Effect.gen(function* () {
189− const db = yield* Effect.promise(() =>
190− createSqliteTestFumaDb({ tables: collectTables() }),
191− );
160+ const db = yield* Effect.promise(() => createSqliteTestFumaDb({ tables: collectTables() }));
192161 const client = db.client;
193162
194163 yield* Effect.promise(() =>
@@ -199,9 +168,7 @@ describe("runSqliteGoogleOpenApiOwnershipMigration", () => { · 4 unmodified lines
199168 pluginId: "openapi",
200169 config: {
201170 specHash: "googlehash",
202− googleDiscoveryUrls: [
203− "https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest",
204− ],
171+ googleDiscoveryUrls: ["https://www.googleapis.com/discovery/v1/apis/calendar/v3/rest"],
205172 },
206173 }),
207174 );
@@ -277,14 +244,9 @@ describe("runSqliteGoogleOpenApiOwnershipMigration", () => { · 69 unmodified lines
277244 expect(googleBlob.rows).toEqual([{ value: "google spec" }]);
278245
279246 const openApiBlobs = yield* Effect.promise(() =>
280− client.execute(
281− "SELECT key FROM blob WHERE namespace = 'o:org_1/openapi' ORDER BY key",
282− ),
247+ client.execute("SELECT key FROM blob WHERE namespace = 'o:org_1/openapi' ORDER BY key"),
283248 );
284− expect(openApiBlobs.rows).toEqual([
285− { key: "spec/googlehash" },
286− { key: "spec/stripehash" },
287− ]);
249+ expect(openApiBlobs.rows).toEqual([{ key: "spec/googlehash" }, { key: "spec/stripehash" }]);
288250
289251 const storage = yield* Effect.promise(() =>
290252 client.execute(
@@ -297,9 +259,7 @@ describe("runSqliteGoogleOpenApiOwnershipMigration", () => { · 6 unmodified lines
297259 ]);
298260
299261 const tools = yield* Effect.promise(() =>
300− client.execute(
301− "SELECT integration, plugin_id FROM tool ORDER BY integration",
302− ),
262+ client.execute("SELECT integration, plugin_id FROM tool ORDER BY integration"),
303263 );
304264 expect(tools.rows).toEqual([
305265 { integration: "google", plugin_id: "google" },
@@ -307,9 +267,7 @@ describe("runSqliteGoogleOpenApiOwnershipMigration", () => { · 1 unmodified line
307267 ]);
308268
309269 const definitions = yield* Effect.promise(() =>
310− client.execute(
311− "SELECT integration, plugin_id FROM definition ORDER BY integration",
312− ),
270+ client.execute("SELECT integration, plugin_id FROM definition ORDER BY integration"),
313271 );
314272 expect(definitions.rows).toEqual([
315273 { integration: "google", plugin_id: "google" },
packages/plugins/google/src/sdk/openapi-ownership-migration.ts+2−6
@@ -1,8 +1,5 @@
11 import { Effect } from "effect";
2−import {
3− DataMigrationError,
4− type SqliteDataMigrationClient,
5−} from "@executor-js/sdk/core";
2+import { DataMigrationError, type SqliteDataMigrationClient } from "@executor-js/sdk/core";
63
74 const MIGRATION_NAME = "2026-06-20-google-openapi-ownership";
85 const googleOpenApiCandidate = (alias?: string): string => {
@@ -16,8 +13,7 @@ const execute = ( · 7 unmodified lines
1613 ) =>
1714 Effect.tryPromise({
1815 try: () => client.execute(stmt),
19− catch: (cause) =>
20− new DataMigrationError({ migration: MIGRATION_NAME, cause }),
16+ catch: (cause) => new DataMigrationError({ migration: MIGRATION_NAME, cause }),
2117 });
2218
2319 export const runSqliteGoogleOpenApiOwnershipMigration = (
packages/plugins/toolkits/CHANGELOG.md+1−0
@@ -0,0 +1 @@
1+# @executor-js/plugin-toolkits
packages/plugins/toolkits/src/page.tsx+6−11
@@ -530,11 +530,12 @@ function CreateToolkitDialog(props: { · 529 unmodified lines
530530 function AddToolkitCard(props: { owner: Owner; onClick: () => void }) {
531531 const scopeLabel = addToolkitScopeLabel(props.owner);
532532 return (
533− <button
533+ <Button
534534 type="button"
535+ variant="ghost"
535536 onClick={props.onClick}
536537 aria-label={`Add ${scopeLabel} toolkit`}
537− className="group flex min-h-36 min-w-0 self-start items-center justify-center rounded-md border border-dashed border-border/75 bg-card/40 text-muted-foreground transition-[border-color,background-color,box-shadow,color] hover:border-foreground/25 hover:bg-muted/20 hover:text-foreground hover:shadow-sm focus-visible:ring-[3px] focus-visible:ring-ring/30 focus-visible:outline-none"
538+ className="group flex h-auto min-h-36 min-w-0 self-start items-center justify-center rounded-md border border-dashed border-border/75 bg-card/40 p-0 text-muted-foreground transition-[border-color,background-color,box-shadow,color] hover:border-foreground/25 hover:bg-muted/20 hover:text-foreground hover:shadow-sm focus-visible:ring-[3px] focus-visible:ring-ring/30 focus-visible:outline-none"
538539 style={toolkitCardStyle}
539540 >
540541 <span
@@ -546,7 +547,7 @@ function AddToolkitCard(props: { owner: Owner; onClick: () => void }) { · 5 unmodified lines
546547 >
547548 <PlusIcon className="size-6" />
548549 </span>
549− </button>
550+ </Button>
550551 );
551552 }
552553
@@ -1047,11 +1048,7 @@ function ToolkitWorkspace(props: { · 494 unmodified lines
10471048 );
10481049 const exposedToolIds = useMemo(
10491050 () =>
1050− new Set(
1051− toolkitTools
1052− .filter((tool) => tool.policy.action !== "block")
1053− .map((tool) => tool.id),
1054− ),
1051+ new Set(toolkitTools.filter((tool) => tool.policy.action !== "block").map((tool) => tool.id)),
10551052 [toolkitTools],
10561053 );
10571054 const selectedTool = selectedToolId
@@ -1231,9 +1228,7 @@ export function ToolkitsPage(props: PluginPageProps) { · 173 unmodified lines
12311228
12321229 const toolkitRows = AsyncResult.isSuccess(toolkits) ? toolkits.value.toolkits : [];
12331230 const selectedToolkit =
1234− selectedToolkitSlug === null
1235− ? null
1236− : toolkitByRouteSlug(toolkitRows, selectedToolkitSlug);
1231+ selectedToolkitSlug === null ? null : toolkitByRouteSlug(toolkitRows, selectedToolkitSlug);
12371232
12381233 const toolRows = AsyncResult.isSuccess(tools) ? (tools.value as readonly ToolRow[]) : [];
12391234 const integrationRows = AsyncResult.isSuccess(integrations)
packages/plugins/toolkits/src/server.ts+7−3
@@ -249,9 +249,13 @@ const makeToolkitsExtension = (ctx: PluginCtx<ToolkitStorage>) => { · 248 unmodified lines
249249 const getEntry = (toolkitId: string) => storage.toolkits.get({ key: toolkitId });
250250
251251 const getBySlugEntry = (slug: string) =>
252− storage.toolkits.query({ where: { slug } }).pipe(
253− Effect.map((entries) => entries.find((entry) => entry.owner === "org") ?? entries[0] ?? null),
254− );
252+ storage.toolkits
253+ .query({ where: { slug } })
254+ .pipe(
255+ Effect.map(
256+ (entries) => entries.find((entry) => entry.owner === "org") ?? entries[0] ?? null,
257+ ),
258+ );
255259
256260 const requireToolkit = (toolkitId: string) =>
257261 getEntry(toolkitId).pipe(