History

Tighten schema, delete @executor/contracts, add @executor/convex package

- Change workspaceId from v.string() to v.id('workspaces') in 7 tables and cascade the type fix through all validators, helpers, and call sites - Make anonymousSessions.userId and workspaceToolCache.dtsStorageIds required with cleanup/backfill migrations for both dev and prod - Delete pushSubscriptions table - Remove legacy OpenAPI compat (schemaTypes/operationTypes), legacy GraphQL run spec, and credential provider fallback code - Inline @executor/contracts types into lib/types.ts and web types.ts, delete the contracts package entirely - Turn executor/convex/ into @executor/convex workspace package so the web app imports from @executor/convex/_generated/* instead of fragile relative paths

Rhys Sullivan committed 9 months ago

Commit 1005f831 parent 552bff744 files · +561 −796Browse files

Unified · Split

44 files
  1. bun.lock +4 −5
  2. executor/apps/web/next.config.ts +1 −1
  3. executor/apps/web/package.json +1 −1
  4. executor/apps/web/src/app/sign-in/route.ts +2 −2
  5. executor/apps/web/src/app/sign-up/route.ts +2 −2
  6. executor/apps/web/src/components/code-editor.tsx +0 −16
  7. executor/apps/web/src/components/members-view.tsx +2 −2
  8. executor/apps/web/src/components/tasks-view.tsx +2 −1
  9. executor/apps/web/src/hooks/use-workspace-tools.ts +2 −1
  10. executor/apps/web/src/lib/convex-api.ts +1 −1
  11. executor/apps/web/src/lib/session-context.tsx +1 −1
  12. executor/apps/web/src/lib/types.ts +124 −22
  13. executor/convex/_generated/api.d.ts +72 −45
  14. executor/convex/access-controls.test.ts +31 −35
  15. executor/convex/accounts.ts +7 −9
  16. executor/convex/credentialsNode.ts +1 −1
  17. executor/convex/database.test.ts +35 −9
  18. executor/convex/database.ts +16 −16
  19. executor/convex/executor-mcp.e2e.test.ts +3 −2
  20. executor/convex/executor.ts +5 −4
  21. executor/convex/executorNode.ts +4 −6
  22. executor/convex/http.ts +15 −8
  23. executor/convex/migrations.ts +20 −0
  24. executor/convex/package.json +7 −0
  25. executor/convex/schema.ts +10 −25
  26. executor/convex/workspaceAuthInternal.ts +2 −2
  27. executor/convex/workspaceToolCache.ts +9 −11
  28. executor/convex/workspaces.ts +6 −8
  29. executor/lib/credential-providers.test.ts +0 −9
  30. executor/lib/credential_providers.ts +1 −9
  31. executor/lib/functionBuilders.ts +2 −2
  32. executor/lib/identity.ts +3 −3
  33. executor/lib/mcp-server.test.ts +2 −0
  34. executor/lib/openapi-cache.test.ts +0 −188
  35. executor/lib/tool_sources.ts +1 −28
  36. executor/lib/typechecker.test.ts +0 −93
  37. executor/lib/typechecker.ts +0 −19
  38. executor/lib/types.ts +122 −24
  39. executor/lib/workspace-tool-cache.test.ts +37 −17
  40. executor/mcp-gateway.ts +7 −6
  41. executor/packages/contracts/package.json +0 −7
  42. executor/packages/contracts/src/index.ts +0 −143
  43. executor/packages/contracts/tsconfig.json +0 −11
  44. package.json +1 −1
bun.lock+4−5
@@ -9,7 +9,6 @@ · 8 unmodified lines
99 "@convex-dev/migrations": "^0.3.1",
1010 "@convex-dev/stripe": "^0.1.3",
1111 "@convex-dev/workos-authkit": "^0.1.6",
12− "@executor/contracts": "workspace:*",
1312 "@workos-inc/node": "^8.2.0",
1413 "convex": "^1.31.7",
1514 "convex-helpers": "^0.1.111",
@@ -91,7 +90,7 @@ · 75 unmodified lines
9190 "dependencies": {
9291 "@convex-dev/workos": "^0.0.1",
9392 "@elysiajs/eden": "^1.4.6",
94− "@executor/contracts": "workspace:*",
93+ "@executor/convex": "workspace:*",
9594 "@monaco-editor/react": "^4.7.0",
9695 "@streamdown/code": "^1.0.1",
9796 "@tanstack/react-query": "^5.80.7",
@@ -127,8 +126,8 @@ · 29 unmodified lines
127126 "typescript": "^5",
128127 },
129128 },
130− "executor/packages/contracts": {
131− "name": "@executor/contracts",
129+ "executor/convex": {
130+ "name": "@executor/convex",
132131 "version": "0.0.1",
133132 },
134133 },
@@ -399,7 +398,7 @@ · 264 unmodified lines
399398
400399 "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="],
401400
402− "@executor/contracts": ["@executor/contracts@workspace:executor/packages/contracts"],
401+ "@executor/convex": ["@executor/convex@workspace:executor/convex"],
403402
404403 "@executor/web": ["@executor/web@workspace:executor/apps/web"],
405404
executor/apps/web/next.config.ts+1−1
@@ -13,7 +13,7 @@ const appShellRewriteExclusions = [ · 12 unmodified lines
1313 ].join("|");
1414
1515 const nextConfig: NextConfig = {
16− transpilePackages: ["@executor/contracts"],
16+ transpilePackages: ["@executor/convex"],
1717 env: {
1818 // Map canonical env vars to NEXT_PUBLIC_ so they're available client-side.
1919 // This lets us keep a single root .env without NEXT_PUBLIC_ prefixes.
executor/apps/web/package.json+1−1
@@ -11,8 +11,8 @@ · 10 unmodified lines
1111 },
1212 "dependencies": {
1313 "@convex-dev/workos": "^0.0.1",
14+ "@executor/convex": "workspace:*",
1415 "@elysiajs/eden": "^1.4.6",
15− "@executor/contracts": "workspace:*",
1616 "@monaco-editor/react": "^4.7.0",
1717 "@streamdown/code": "^1.0.1",
1818 "@tanstack/react-query": "^5.80.7",
executor/apps/web/src/app/sign-in/route.ts+2−2
@@ -1,8 +1,8 @@
11 import type { NextRequest } from "next/server";
22 import { redirect } from "next/navigation";
33 import { ConvexHttpClient } from "convex/browser";
4−import { api } from "../../../../../convex/_generated/api";
5−import type { Id } from "../../../../../convex/_generated/dataModel";
4+import { api } from "@executor/convex/_generated/api";
5+import type { Id } from "@executor/convex/_generated/dataModel";
66
77 function getExternalOrigin(request: NextRequest) {
88 const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host");
executor/apps/web/src/app/sign-up/route.ts+2−2
@@ -1,8 +1,8 @@
11 import type { NextRequest } from "next/server";
22 import { redirect } from "next/navigation";
33 import { ConvexHttpClient } from "convex/browser";
4−import { api } from "../../../../../convex/_generated/api";
5−import type { Id } from "../../../../../convex/_generated/dataModel";
4+import { api } from "@executor/convex/_generated/api";
5+import type { Id } from "@executor/convex/_generated/dataModel";
66
77 function getExternalOrigin(request: NextRequest) {
88 const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host");
executor/apps/web/src/components/code-editor.tsx+0−16
@@ -131,17 +131,6 @@ type ToolOutput<Op> = · 130 unmodified lines
131131 function generateToolsDts(tools: ToolDescriptor[], dtsSources: Set<string>): string {
132132 const root = buildTree(tools);
133133
134− // Legacy compat: collect schemaTypes from tools that use the old format
135− const schemaTypeAliases = new Map<string, string>();
136− for (const tool of tools) {
137− if (!tool.schemaTypes) continue;
138− for (const [name, type] of Object.entries(tool.schemaTypes)) {
139− if (!schemaTypeAliases.has(name)) {
140− schemaTypeAliases.set(name, type);
141− }
142− }
143− }
144−
145134 const interfaces: string[] = [];
146135
147136 // Emit all namespace interfaces recursively
@@ -170,11 +159,6 @@ function generateToolsDts(tools: ToolDescriptor[], dtsSources: Set<string>): str · 22 unmodified lines
170159 */
171160 `;
172161
173− // Legacy schema type aliases
174− if (schemaTypeAliases.size > 0) {
175− dts += Array.from(schemaTypeAliases, ([name, type]) => `type ${name} = ${type};`).join("\n") + "\n\n";
176− }
177−
178162 // Note: .d.ts blocks from OpenAPI sources are fetched separately and registered
179163 // as a distinct Monaco extra lib (see dtsUrls effect). This keeps generateToolsDts
180164 // fast and avoids bundling multi-MB .d.ts content into the tool declarations string.
executor/apps/web/src/components/members-view.tsx+2−2
@@ -9,7 +9,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; · 8 unmodified lines
99 import { Input } from "@/components/ui/input";
1010 import { useSession } from "@/lib/session-context";
1111 import { convexApi } from "@/lib/convex-api";
12−import type { Id } from "../../../../convex/_generated/dataModel";
12+import type { Id } from "@executor/convex/_generated/dataModel";
1313
1414 type Role = "owner" | "admin" | "member" | "billing_admin";
1515 type MemberStatus = "active" | "pending" | "removed";
@@ -64,7 +64,7 @@ export function MembersView({ showHeader = true }: MembersViewProps) { · 48 unmodified lines
6464
6565 const memberItems: OrganizationMemberListItem[] = members?.items ?? [];
6666 const actorMembership = memberItems.find((member: { accountId?: string; role?: string }) =>
67− context?.accountId ? member.accountId === context.accountId : false,
67+ context ? member.accountId === context.accountId : false,
6868 );
6969 const actorRole = actorMembership?.role ?? null;
7070 const canManageMembers = actorRole === "owner" || actorRole === "admin";
executor/apps/web/src/components/tasks-view.tsx+2−1
@@ -34,6 +34,7 @@ import type { · 33 unmodified lines
3434 TaskEventRecord,
3535 TaskRecord,
3636 } from "@/lib/types";
37+import type { Id } from "@executor/convex/_generated/dataModel";
3738 import { toast } from "sonner";
3839 import { cn } from "@/lib/utils";
3940
@@ -210,7 +211,7 @@ function TaskDetail({ · 170 unmodified lines
210211 onClose,
211212 }: {
212213 task: TaskRecord;
213− workspaceId: string;
214+ workspaceId: Id<"workspaces">;
214215 sessionId?: string;
215216 onClose: () => void;
216217 }) {
executor/apps/web/src/hooks/use-workspace-tools.ts+2−1
@@ -4,9 +4,10 @@ import { useQuery as useTanstackQuery } from "@tanstack/react-query"; · 3 unmodified lines
44 import { useAction, useQuery as useConvexQuery } from "convex/react";
55 import { convexApi } from "@/lib/convex-api";
66 import type { OpenApiSourceQuality, ToolDescriptor } from "@/lib/types";
7+import type { Id } from "@executor/convex/_generated/dataModel";
78
89 interface WorkspaceContext {
9− workspaceId: string;
10+ workspaceId: Id<"workspaces">;
1011 actorId?: string;
1112 clientId?: string;
1213 sessionId?: string;
executor/apps/web/src/lib/convex-api.ts+1−1
@@ -1,3 +1,3 @@
1−import { api } from "../../../../convex/_generated/api";
1+import { api } from "@executor/convex/_generated/api";
22
33 export const convexApi = api;
executor/apps/web/src/lib/session-context.tsx+1−1
@@ -14,7 +14,7 @@ import { anonymousDemoEnabled, workosEnabled } from "@/lib/auth-capabilities"; · 13 unmodified lines
1414 import { useWorkosAuthLoading } from "@/lib/convex-provider";
1515 import { convexApi } from "@/lib/convex-api";
1616 import type { AnonymousContext } from "./types";
17−import type { Id } from "../../../../convex/_generated/dataModel";
17+import type { Id } from "@executor/convex/_generated/dataModel";
1818
1919 interface SessionState {
2020 context: AnonymousContext | null;
executor/apps/web/src/lib/types.ts+124−22
@@ -1,27 +1,129 @@
1−// Re-export shared types from contracts
2−export type {
3− TaskStatus,
4− ApprovalStatus,
5− PolicyDecision,
6− CredentialScope,
7− CredentialProvider,
8− ToolApprovalMode,
9− ToolSourceType,
10− TaskRecord,
11− ApprovalRecord,
12− PendingApprovalRecord,
13− TaskEventRecord,
14− AccessPolicyRecord,
15− CredentialRecord,
16− ToolSourceRecord,
17− ToolDescriptor,
18− OpenApiSourceQuality,
19− AnonymousContext,
20−} from "@executor/contracts";
1+import type { Id } from "@executor/convex/_generated/dataModel";
212
22−// ── Web-only types ────────────────────────────────────────────────────────────
3+// ── Shared types (inlined from @executor/contracts) ──────────────────────────
4+
5+export type TaskStatus = "queued" | "running" | "completed" | "failed" | "timed_out" | "denied";
6+export type ApprovalStatus = "pending" | "approved" | "denied";
7+export type PolicyDecision = "allow" | "require_approval" | "deny";
8+export type CredentialScope = "workspace" | "actor";
9+export type CredentialProvider = "managed" | "workos-vault";
10+export type ToolApprovalMode = "auto" | "required";
11+export type ToolSourceType = "mcp" | "openapi" | "graphql";
12+
13+export interface TaskRecord {
14+ id: string;
15+ code: string;
16+ runtimeId: string;
17+ status: TaskStatus;
18+ timeoutMs: number;
19+ metadata: Record<string, unknown>;
20+ workspaceId: string;
21+ actorId?: string;
22+ clientId?: string;
23+ createdAt: number;
24+ updatedAt: number;
25+ startedAt?: number;
26+ completedAt?: number;
27+ error?: string;
28+ stdout?: string;
29+ stderr?: string;
30+ exitCode?: number;
31+}
32+
33+export interface ApprovalRecord {
34+ id: string;
35+ taskId: string;
36+ toolPath: string;
37+ input: unknown;
38+ status: ApprovalStatus;
39+ reason?: string;
40+ reviewerId?: string;
41+ createdAt: number;
42+ resolvedAt?: number;
43+}
44+
45+export interface PendingApprovalRecord extends ApprovalRecord {
46+ task: Pick<TaskRecord, "id" | "status" | "runtimeId" | "timeoutMs" | "createdAt">;
47+}
48+
49+export interface TaskEventRecord {
50+ id: number;
51+ taskId: string;
52+ eventName: string;
53+ type: string;
54+ payload: unknown;
55+ createdAt: number;
56+}
57+
58+export interface AccessPolicyRecord {
59+ id: string;
60+ workspaceId: string;
61+ actorId?: string;
62+ clientId?: string;
63+ toolPathPattern: string;
64+ decision: PolicyDecision;
65+ priority: number;
66+ createdAt: number;
67+ updatedAt: number;
68+}
69+
70+export interface CredentialRecord {
71+ id: string;
72+ workspaceId: string;
73+ sourceKey: string;
74+ scope: CredentialScope;
75+ actorId?: string;
76+ provider: CredentialProvider;
77+ secretJson: Record<string, unknown>;
78+ createdAt: number;
79+ updatedAt: number;
80+}
81+
82+export interface ToolSourceRecord {
83+ id: string;
84+ workspaceId: string;
85+ name: string;
86+ type: ToolSourceType;
87+ config: Record<string, unknown>;
88+ enabled: boolean;
89+ createdAt: number;
90+ updatedAt: number;
91+}
92+
93+export interface ToolDescriptor {
94+ path: string;
95+ description: string;
96+ approval: ToolApprovalMode;
97+ source?: string;
98+ argsType?: string;
99+ returnsType?: string;
100+ operationId?: string;
101+}
102+
103+export interface OpenApiSourceQuality {
104+ sourceKey: string;
105+ toolCount: number;
106+ unknownArgsCount: number;
107+ unknownReturnsCount: number;
108+ partialUnknownArgsCount: number;
109+ partialUnknownReturnsCount: number;
110+ argsQuality: number;
111+ returnsQuality: number;
112+ overallQuality: number;
113+}
114+
115+export interface AnonymousContext {
116+ sessionId: string;
117+ workspaceId: Id<"workspaces">;
118+ actorId: string;
119+ clientId: string;
120+ accountId: string;
121+ userId: string;
122+ createdAt: number;
123+ lastSeenAt: number;
124+}
23125
24−import type { TaskStatus, ApprovalStatus, CredentialScope } from "@executor/contracts";
126+// ── Web-only types ────────────────────────────────────────────────────────────
25127
26128 export type ApprovalDecision = "approved" | "denied";
27129
executor/convex/_generated/api.d.ts+72−45
@@ -93,7 +93,7 @@ export declare const api: { · 92 unmodified lines
9393 secretJson: any;
9494 sessionId?: string;
9595 sourceKey: string;
96− workspaceId: string;
96+ workspaceId: Id<"workspaces">;
9797 },
9898 any
9999 >;
@@ -107,7 +107,7 @@ export declare const api: { · 7 unmodified lines
107107 id: string;
108108 prompt: string;
109109 requesterId: string;
110− workspaceId: string;
110+ workspaceId: Id<"workspaces">;
111111 },
112112 any
113113 >;
@@ -142,7 +142,7 @@ export declare const api: { · 28 unmodified lines
142142 runtimeId?: string;
143143 sessionId?: string;
144144 timeoutMs?: number;
145− workspaceId: string;
145+ workspaceId: Id<"workspaces">;
146146 },
147147 any
148148 >;
@@ -155,7 +155,7 @@ export declare const api: { · 6 unmodified lines
155155 reason?: string;
156156 reviewerId?: string;
157157 sessionId?: string;
158− workspaceId: string;
158+ workspaceId: Id<"workspaces">;
159159 },
160160 any
161161 >;
@@ -168,7 +168,7 @@ export declare const api: { · 6 unmodified lines
168168 actorId?: string;
169169 clientId?: string;
170170 sessionId?: string;
171− workspaceId: string;
171+ workspaceId: Id<"workspaces">;
172172 },
173173 any
174174 >;
@@ -179,7 +179,7 @@ export declare const api: { · 4 unmodified lines
179179 actorId?: string;
180180 clientId?: string;
181181 sessionId?: string;
182− workspaceId: string;
182+ workspaceId: Id<"workspaces">;
183183 },
184184 any
185185 >;
@@ -292,31 +292,31 @@ export declare const api: { · 106 unmodified lines
292292 deleteToolSource: FunctionReference<
293293 "mutation",
294294 "public",
295− { sessionId?: string; sourceId: string; workspaceId: string },
295+ { sessionId?: string; sourceId: string; workspaceId: Id<"workspaces"> },
296296 any
297297 >;
298298 getRequestContext: FunctionReference<
299299 "query",
300300 "public",
301− { sessionId?: string; workspaceId: string },
301+ { sessionId?: string; workspaceId: Id<"workspaces"> },
302302 any
303303 >;
304304 getTask: FunctionReference<
305305 "query",
306306 "public",
307− { sessionId?: string; taskId: string; workspaceId: string },
307+ { sessionId?: string; taskId: string; workspaceId: Id<"workspaces"> },
308308 any
309309 >;
310310 getTaskInWorkspace: FunctionReference<
311311 "query",
312312 "public",
313− { sessionId?: string; taskId: string; workspaceId: string },
313+ { sessionId?: string; taskId: string; workspaceId: Id<"workspaces"> },
314314 any
315315 >;
316316 listAccessPolicies: FunctionReference<
317317 "query",
318318 "public",
319− { sessionId?: string; workspaceId: string },
319+ { sessionId?: string; workspaceId: Id<"workspaces"> },
320320 any
321321 >;
322322 listApprovals: FunctionReference<
@@ -325,7 +325,7 @@ export declare const api: { · 2 unmodified lines
325325 {
326326 sessionId?: string;
327327 status?: "pending" | "approved" | "denied";
328− workspaceId: string;
328+ workspaceId: Id<"workspaces">;
329329 },
330330 any
331331 >;
@@ -333,32 +333,32 @@ export declare const api: { · 1 unmodified line
333333 listCredentials: FunctionReference<
334334 "query",
335335 "public",
336− { sessionId?: string; workspaceId: string },
336+ { sessionId?: string; workspaceId: Id<"workspaces"> },
337337 any
338338 >;
339339 listPendingApprovals: FunctionReference<
340340 "query",
341341 "public",
342− { sessionId?: string; workspaceId: string },
342+ { sessionId?: string; workspaceId: Id<"workspaces"> },
343343 any
344344 >;
345345 listRuntimeTargets: FunctionReference<"query", "public", {}, any>;
346346 listTaskEvents: FunctionReference<
347347 "query",
348348 "public",
349− { sessionId?: string; taskId: string; workspaceId: string },
349+ { sessionId?: string; taskId: string; workspaceId: Id<"workspaces"> },
350350 any
351351 >;
352352 listTasks: FunctionReference<
353353 "query",
354354 "public",
355− { sessionId?: string; workspaceId: string },
355+ { sessionId?: string; workspaceId: Id<"workspaces"> },
356356 any
357357 >;
358358 listToolSources: FunctionReference<
359359 "query",
360360 "public",
361− { sessionId?: string; workspaceId: string },
361+ { sessionId?: string; workspaceId: Id<"workspaces"> },
362362 any
363363 >;
364364 resolveCredential: FunctionReference<
@@ -369,7 +369,7 @@ export declare const api: { · 4 unmodified lines
369369 scope: "workspace" | "actor";
370370 sessionId?: string;
371371 sourceKey: string;
372− workspaceId: string;
372+ workspaceId: Id<"workspaces">;
373373 },
374374 any
375375 >;
@@ -384,7 +384,7 @@ export declare const api: { · 8 unmodified lines
384384 priority?: number;
385385 sessionId?: string;
386386 toolPathPattern: string;
387− workspaceId: string;
387+ workspaceId: Id<"workspaces">;
388388 },
389389 any
390390 >;
@@ -399,7 +399,7 @@ export declare const api: { · 8 unmodified lines
399399 secretJson: any;
400400 sessionId?: string;
401401 sourceKey: string;
402− workspaceId: string;
402+ workspaceId: Id<"workspaces">;
403403 },
404404 any
405405 >;
@@ -413,7 +413,7 @@ export declare const api: { · 7 unmodified lines
413413 name: string;
414414 sessionId?: string;
415415 type: "mcp" | "openapi" | "graphql";
416− workspaceId: string;
416+ workspaceId: Id<"workspaces">;
417417 },
418418 any
419419 >;
@@ -532,7 +532,7 @@ export declare const internal: { · 112 unmodified lines
532532 metadata?: any;
533533 runtimeId: string;
534534 timeoutMs?: number;
535− workspaceId: string;
535+ workspaceId: Id<"workspaces">;
536536 },
537537 any
538538 >;
@@ -545,7 +545,7 @@ export declare const internal: { · 6 unmodified lines
545545 deleteToolSource: FunctionReference<
546546 "mutation",
547547 "internal",
548− { sourceId: string; workspaceId: string },
548+ { sourceId: string; workspaceId: Id<"workspaces"> },
549549 any
550550 >;
551551 getApproval: FunctionReference<
@@ -557,39 +557,42 @@ export declare const internal: { · 5 unmodified lines
557557 getApprovalInWorkspace: FunctionReference<
558558 "query",
559559 "internal",
560− { approvalId: string; workspaceId: string },
560+ { approvalId: string; workspaceId: Id<"workspaces"> },
561561 any
562562 >;
563563 getTask: FunctionReference<"query", "internal", { taskId: string }, any>;
564564 getTaskInWorkspace: FunctionReference<
565565 "query",
566566 "internal",
567− { taskId: string; workspaceId: string },
567+ { taskId: string; workspaceId: Id<"workspaces"> },
568568 any
569569 >;
570570 listAccessPolicies: FunctionReference<
571571 "query",
572572 "internal",
573− { workspaceId: string },
573+ { workspaceId: Id<"workspaces"> },
574574 any
575575 >;
576576 listApprovals: FunctionReference<
577577 "query",
578578 "internal",
579− { status?: "pending" | "approved" | "denied"; workspaceId: string },
579+ {
580+ status?: "pending" | "approved" | "denied";
581+ workspaceId: Id<"workspaces">;
582+ },
580583 any
581584 >;
582585 listCredentialProviders: FunctionReference<"query", "internal", {}, any>;
583586 listCredentials: FunctionReference<
584587 "query",
585588 "internal",
586− { workspaceId: string },
589+ { workspaceId: Id<"workspaces"> },
587590 any
588591 >;
589592 listPendingApprovals: FunctionReference<
590593 "query",
591594 "internal",
592− { workspaceId: string },
595+ { workspaceId: Id<"workspaces"> },
593596 any
594597 >;
595598 listQueuedTaskIds: FunctionReference<
@@ -608,13 +611,13 @@ export declare const internal: { · 12 unmodified lines
608611 listTasks: FunctionReference<
609612 "query",
610613 "internal",
611− { workspaceId: string },
614+ { workspaceId: Id<"workspaces"> },
612615 any
613616 >;
614617 listToolSources: FunctionReference<
615618 "query",
616619 "internal",
617− { workspaceId: string },
620+ { workspaceId: Id<"workspaces"> },
618621 any
619622 >;
620623 markTaskFinished: FunctionReference<
@@ -654,7 +657,7 @@ export declare const internal: { · 33 unmodified lines
654657 actorId?: string;
655658 scope: "workspace" | "actor";
656659 sourceKey: string;
657− workspaceId: string;
660+ workspaceId: Id<"workspaces">;
658661 },
659662 any
660663 >;
@@ -668,7 +671,7 @@ export declare const internal: { · 7 unmodified lines
668671 id?: string;
669672 priority?: number;
670673 toolPathPattern: string;
671− workspaceId: string;
674+ workspaceId: Id<"workspaces">;
672675 },
673676 any
674677 >;
@@ -682,7 +685,7 @@ export declare const internal: { · 7 unmodified lines
682685 scope: "workspace" | "actor";
683686 secretJson: any;
684687 sourceKey: string;
685− workspaceId: string;
688+ workspaceId: Id<"workspaces">;
686689 },
687690 any
688691 >;
@@ -695,7 +698,7 @@ export declare const internal: { · 6 unmodified lines
695698 id?: string;
696699 name: string;
697700 type: "mcp" | "openapi" | "graphql";
698− workspaceId: string;
701+ workspaceId: Id<"workspaces">;
699702 },
700703 any
701704 >;
@@ -722,7 +725,7 @@ export declare const internal: { · 20 unmodified lines
722725 metadata?: any;
723726 runtimeId?: string;
724727 timeoutMs?: number;
725− workspaceId: string;
728+ workspaceId: Id<"workspaces">;
726729 },
727730 any
728731 >;
@@ -734,7 +737,7 @@ export declare const internal: { · 5 unmodified lines
734737 decision: "approved" | "denied";
735738 reason?: string;
736739 reviewerId?: string;
737− workspaceId: string;
740+ workspaceId: Id<"workspaces">;
738741 },
739742 any
740743 >;
@@ -749,13 +752,13 @@ export declare const internal: { · 8 unmodified lines
749752 listToolsInternal: FunctionReference<
750753 "action",
751754 "internal",
752− { actorId?: string; clientId?: string; workspaceId: string },
755+ { actorId?: string; clientId?: string; workspaceId: Id<"workspaces"> },
753756 any
754757 >;
755758 listToolsWithWarningsInternal: FunctionReference<
756759 "action",
757760 "internal",
758− { actorId?: string; clientId?: string; workspaceId: string },
761+ { actorId?: string; clientId?: string; workspaceId: Id<"workspaces"> },
759762 any
760763 >;
761764 runTask: FunctionReference<"action", "internal", { taskId: string }, any>;
@@ -814,6 +817,18 @@ export declare const internal: { · 52 unmodified lines
814817 >;
815818 };
816819 migrations: {
820+ backfillDtsStorageIds: FunctionReference<
821+ "mutation",
822+ "internal",
823+ {
824+ batchSize?: number;
825+ cursor?: string | null;
826+ dryRun?: boolean;
827+ fn?: string;
828+ next?: Array<string>;
829+ },
830+ any
831+ >;
817832 deleteAnonymousSessionsMissingAccountId: FunctionReference<
818833 "mutation",
819834 "internal",
@@ -826,6 +841,18 @@ export declare const internal: { · 6 unmodified lines
826841 },
827842 any
828843 >;
844+ deleteAnonymousSessionsMissingUserId: FunctionReference<
845+ "mutation",
846+ "internal",
847+ {
848+ batchSize?: number;
849+ cursor?: string | null;
850+ dryRun?: boolean;
851+ fn?: string;
852+ next?: Array<string>;
853+ },
854+ any
855+ >;
829856 deleteInvitesMissingProvider: FunctionReference<
830857 "mutation",
831858 "internal",
@@ -916,13 +943,13 @@ export declare const internal: { · 84 unmodified lines
916943 getWorkspaceAccessForRequest: FunctionReference<
917944 "query",
918945 "internal",
919− { sessionId?: string; workspaceId: string },
946+ { sessionId?: string; workspaceId: Id<"workspaces"> },
920947 any
921948 >;
922949 getWorkspaceAccessForWorkosSubject: FunctionReference<
923950 "query",
924951 "internal",
925− { subject: string; workspaceId: string },
952+ { subject: string; workspaceId: Id<"workspaces"> },
926953 any
927954 >;
928955 };
@@ -930,25 +957,25 @@ export declare const internal: { · 1 unmodified line
930957 getDtsStorageIds: FunctionReference<
931958 "query",
932959 "internal",
933− { workspaceId: string },
960+ { workspaceId: Id<"workspaces"> },
934961 any
935962 >;
936963 getEntry: FunctionReference<
937964 "query",
938965 "internal",
939− { signature: string; workspaceId: string },
966+ { signature: string; workspaceId: Id<"workspaces"> },
940967 any
941968 >;
942969 putEntry: FunctionReference<
943970 "mutation",
944971 "internal",
945972 {
946− dtsStorageIds?: Array<{ sourceKey: string; storageId: Id<"_storage"> }>;
973+ dtsStorageIds: Array<{ sourceKey: string; storageId: Id<"_storage"> }>;
947974 signature: string;
948975 sizeBytes: number;
949976 storageId: Id<"_storage">;
950977 toolCount: number;
951− workspaceId: string;
978+ workspaceId: Id<"workspaces">;
952979 },
953980 any
954981 >;
executor/convex/access-controls.test.ts+31−35
@@ -153,10 +153,6 @@ async function addWorkspaceMember( · 152 unmodified lines
153153 });
154154 }
155155
156−function asWorkspaceId(id: Id<"workspaces">): string {
157− return id as unknown as string;
158−}
159−
160156 // ---------------------------------------------------------------------------
161157 // Tests
162158 // ---------------------------------------------------------------------------
@@ -168,7 +164,7 @@ describe("authentication", () => { · 5 unmodified lines
168164
169165 await expect(
170166 t.query(api.workspace.listTasks, {
171− workspaceId: asWorkspaceId(owner.workspaceId),
167+ workspaceId: owner.workspaceId,
172168 }),
173169 ).rejects.toThrow("Must be signed in");
174170 });
@@ -179,7 +175,7 @@ describe("authentication", () => { · 4 unmodified lines
179175
180176 await expect(
181177 t.mutation(api.workspace.upsertAccessPolicy, {
182− workspaceId: asWorkspaceId(owner.workspaceId),
178+ workspaceId: owner.workspaceId,
183179 toolPathPattern: "*",
184180 decision: "allow",
185181 }),
@@ -203,7 +199,7 @@ describe("authentication", () => { · 17 unmodified lines
203199
204200 await expect(
205201 t.mutation(api.executor.createTask, {
206− workspaceId: asWorkspaceId(owner.workspaceId),
202+ workspaceId: owner.workspaceId,
207203 code: "console.log('hello')",
208204 }),
209205 ).rejects.toThrow("Must be signed in");
@@ -215,7 +211,7 @@ describe("authentication", () => { · 5 unmodified lines
215211 const authed = t.withIdentity({ subject: "user-ok" });
216212
217213 const tasks = await authed.query(api.workspace.listTasks, {
218− workspaceId: asWorkspaceId(owner.workspaceId),
214+ workspaceId: owner.workspaceId,
219215 });
220216
221217 expect(tasks).toEqual([]);
@@ -259,7 +255,7 @@ describe("workspace access controls", () => { · 37 unmodified lines
259255
260256 const authedMember = t.withIdentity({ subject: "ws-member" });
261257 const tasks = await authedMember.query(api.workspace.listTasks, {
262− workspaceId: asWorkspaceId(owner.workspaceId),
258+ workspaceId: owner.workspaceId,
263259 });
264260
265261 expect(tasks).toEqual([]);
@@ -278,7 +274,7 @@ describe("workspace access controls", () => { · 12 unmodified lines
278274
279275 const authedMember = t.withIdentity({ subject: "ws-member-org-fallback" });
280276 const tasks = await authedMember.query(api.workspace.listTasks, {
281− workspaceId: asWorkspaceId(owner.workspaceId),
277+ workspaceId: owner.workspaceId,
282278 });
283279
284280 expect(tasks).toEqual([]);
@@ -293,7 +289,7 @@ describe("workspace access controls", () => { · 8 unmodified lines
293289
294290 await expect(
295291 authedOutsider.query(api.workspace.listTasks, {
296− workspaceId: asWorkspaceId(owner.workspaceId),
292+ workspaceId: owner.workspaceId,
297293 }),
298294 ).rejects.toThrow("You are not a member of this workspace");
299295 });
@@ -319,7 +315,7 @@ describe("workspace access controls", () => { · 19 unmodified lines
319315
320316 await expect(
321317 authedRemoved.query(api.workspace.listTasks, {
322− workspaceId: asWorkspaceId(owner.workspaceId),
318+ workspaceId: owner.workspaceId,
323319 }),
324320 ).rejects.toThrow("You are not a member of this workspace");
325321 });
@@ -344,7 +340,7 @@ describe("workspace access controls", () => { · 18 unmodified lines
344340
345341 await expect(
346342 authedMember.mutation(api.workspace.upsertAccessPolicy, {
347− workspaceId: asWorkspaceId(owner.workspaceId),
343+ workspaceId: owner.workspaceId,
348344 toolPathPattern: "*",
349345 decision: "allow",
350346 }),
@@ -370,7 +366,7 @@ describe("workspace access controls", () => { · 19 unmodified lines
370366 const authedAdmin = t.withIdentity({ subject: "ws-policy-admin" });
371367
372368 const policy = await authedAdmin.mutation(api.workspace.upsertAccessPolicy, {
373− workspaceId: asWorkspaceId(owner.workspaceId),
369+ workspaceId: owner.workspaceId,
374370 toolPathPattern: "*.delete",
375371 decision: "require_approval",
376372 });
@@ -385,7 +381,7 @@ describe("workspace access controls", () => { · 8 unmodified lines
385381 const authedOwner = t.withIdentity({ subject: "ws-owner-policy" });
386382
387383 const policy = await authedOwner.mutation(api.workspace.upsertAccessPolicy, {
388− workspaceId: asWorkspaceId(owner.workspaceId),
384+ workspaceId: owner.workspaceId,
389385 toolPathPattern: "*",
390386 decision: "allow",
391387 });
@@ -413,7 +409,7 @@ describe("workspace access controls", () => { · 21 unmodified lines
413409
414410 await expect(
415411 authedMember.mutation(api.workspace.upsertCredential, {
416− workspaceId: asWorkspaceId(owner.workspaceId),
412+ workspaceId: owner.workspaceId,
417413 sourceKey: "openapi:github",
418414 scope: "workspace",
419415 secretJson: { token: "ghp_test" },
@@ -441,7 +437,7 @@ describe("workspace access controls", () => { · 21 unmodified lines
441437
442438 await expect(
443439 authedMember.mutation(api.workspace.upsertToolSource, {
444− workspaceId: asWorkspaceId(owner.workspaceId),
440+ workspaceId: owner.workspaceId,
445441 name: "my-tool",
446442 type: "mcp",
447443 config: { url: "https://example.com" },
@@ -469,7 +465,7 @@ describe("workspace access controls", () => { · 21 unmodified lines
469465
470466 await expect(
471467 authedMember.mutation(api.workspace.deleteToolSource, {
472− workspaceId: asWorkspaceId(owner.workspaceId),
468+ workspaceId: owner.workspaceId,
473469 sourceId: "src_nonexistent",
474470 }),
475471 ).rejects.toThrow("Only workspace admins can perform this action");
@@ -494,7 +490,7 @@ describe("workspace access controls", () => { · 18 unmodified lines
494490 const authedMember = t.withIdentity({ subject: "read-policy-member" });
495491
496492 const policies = await authedMember.query(api.workspace.listAccessPolicies, {
497− workspaceId: asWorkspaceId(owner.workspaceId),
493+ workspaceId: owner.workspaceId,
498494 });
499495
500496 expect(policies).toEqual([]);
@@ -519,7 +515,7 @@ describe("workspace access controls", () => { · 18 unmodified lines
519515 const authedMember = t.withIdentity({ subject: "read-ts-member" });
520516
521517 const sources = await authedMember.query(api.workspace.listToolSources, {
522− workspaceId: asWorkspaceId(owner.workspaceId),
518+ workspaceId: owner.workspaceId,
523519 });
524520
525521 expect(sources).toEqual([]);
@@ -695,7 +691,7 @@ describe("cross-workspace isolation", () => { · 169 unmodified lines
695691 // User A tries to read User B's workspace
696692 await expect(
697693 authedA.query(api.workspace.listTasks, {
698− workspaceId: asWorkspaceId(userB.workspaceId),
694+ workspaceId: userB.workspaceId,
699695 }),
700696 ).rejects.toThrow("You are not a member of this workspace");
701697 });
@@ -709,7 +705,7 @@ describe("cross-workspace isolation", () => { · 7 unmodified lines
709705
710706 await expect(
711707 authedA.mutation(api.executor.createTask, {
712− workspaceId: asWorkspaceId(userB.workspaceId),
708+ workspaceId: userB.workspaceId,
713709 code: "console.log('pwned')",
714710 }),
715711 ).rejects.toThrow("You are not a member of this workspace");
@@ -724,7 +720,7 @@ describe("cross-workspace isolation", () => { · 8 unmodified lines
724720
725721 await expect(
726722 authedA.mutation(api.workspace.upsertAccessPolicy, {
727− workspaceId: asWorkspaceId(userB.workspaceId),
723+ workspaceId: userB.workspaceId,
728724 toolPathPattern: "*",
729725 decision: "allow",
730726 }),
@@ -738,7 +734,7 @@ describe("cross-workspace isolation", () => { · 7 unmodified lines
738734
739735 await expect(
740736 authed.query(api.workspace.listTasks, {
741− workspaceId: "invalid_workspace_id",
737+ workspaceId: "invalid_workspace_id" as Id<"workspaces">,
742738 }),
743739 ).rejects.toThrow();
744740 });
@@ -1018,12 +1014,12 @@ describe("task creation with access controls", () => { · 273 unmodified lines
10181014 const authed = t.withIdentity({ subject: "task-creator" });
10191015
10201016 const result = await authed.mutation(api.executor.createTask, {
1021− workspaceId: asWorkspaceId(user.workspaceId),
1017+ workspaceId: user.workspaceId,
10221018 code: "console.log('hello')",
10231019 });
10241020
10251021 expect(result.task.status).toBe("queued");
1026− expect(result.task.workspaceId).toBe(asWorkspaceId(user.workspaceId));
1022+ expect(result.task.workspaceId).toBe(user.workspaceId);
10271023 });
10281024
10291025 test("task is scoped to workspace and visible to members", async () => {
@@ -1045,14 +1041,14 @@ describe("task creation with access controls", () => { · 15 unmodified lines
10451041 // Owner creates a task
10461042 const authedOwner = t.withIdentity({ subject: "task-scope-owner" });
10471043 await authedOwner.mutation(api.executor.createTask, {
1048− workspaceId: asWorkspaceId(owner.workspaceId),
1044+ workspaceId: owner.workspaceId,
10491045 code: "1 + 1",
10501046 });
10511047
10521048 // Member can see it
10531049 const authedMember = t.withIdentity({ subject: "task-scope-member" });
10541050 const tasks = await authedMember.query(api.workspace.listTasks, {
1055− workspaceId: asWorkspaceId(owner.workspaceId),
1051+ workspaceId: owner.workspaceId,
10561052 });
10571053
10581054 expect(tasks.length).toBe(1);
@@ -1067,7 +1063,7 @@ describe("task creation with access controls", () => { · 8 unmodified lines
10671063 // Owner creates a task
10681064 const authedOwner = t.withIdentity({ subject: "task-vis-owner" });
10691065 await authedOwner.mutation(api.executor.createTask, {
1070− workspaceId: asWorkspaceId(owner.workspaceId),
1066+ workspaceId: owner.workspaceId,
10711067 code: "secret code",
10721068 });
10731069
@@ -1076,7 +1072,7 @@ describe("task creation with access controls", () => { · 2 unmodified lines
10761072
10771073 await expect(
10781074 authedOutsider.query(api.workspace.listTasks, {
1079− workspaceId: asWorkspaceId(owner.workspaceId),
1075+ workspaceId: owner.workspaceId,
10801076 }),
10811077 ).rejects.toThrow("You are not a member of this workspace");
10821078 });
@@ -1090,7 +1086,7 @@ describe("credential security", () => { · 7 unmodified lines
10901086
10911087 // Create a credential via admin
10921088 await authed.mutation(api.workspace.upsertCredential, {
1093− workspaceId: asWorkspaceId(owner.workspaceId),
1089+ workspaceId: owner.workspaceId,
10941090 sourceKey: "openapi:stripe",
10951091 scope: "workspace",
10961092 secretJson: { token: "sk_live_super_secret" },
@@ -1098,7 +1094,7 @@ describe("credential security", () => { · 1 unmodified line
10981094
10991095 // List should redact
11001096 const credentials = await authed.query(api.workspace.listCredentials, {
1101− workspaceId: asWorkspaceId(owner.workspaceId),
1097+ workspaceId: owner.workspaceId,
11021098 });
11031099
11041100 expect(credentials.length).toBe(1);
@@ -1126,7 +1122,7 @@ describe("credential security", () => { · 21 unmodified lines
11261122
11271123 await expect(
11281124 authedMember.query(api.workspace.resolveCredential, {
1129− workspaceId: asWorkspaceId(owner.workspaceId),
1125+ workspaceId: owner.workspaceId,
11301126 sourceKey: "openapi:github",
11311127 scope: "workspace",
11321128 }),
@@ -1155,7 +1151,7 @@ describe("role hierarchy validation", () => { · 22 unmodified lines
11551151
11561152 // Admin can upsert access policies
11571153 const policy = await authed.mutation(api.workspace.upsertAccessPolicy, {
1158− workspaceId: asWorkspaceId(org.workspaceId),
1154+ workspaceId: org.workspaceId,
11591155 toolPathPattern: "admin.*",
11601156 decision: "deny",
11611157 });
@@ -1163,7 +1159,7 @@ describe("role hierarchy validation", () => { · 1 unmodified line
11631159
11641160 // Admin can upsert tool sources
11651161 const source = await authed.mutation(api.workspace.upsertToolSource, {
1166− workspaceId: asWorkspaceId(org.workspaceId),
1162+ workspaceId: org.workspaceId,
11671163 name: "admin-tool",
11681164 type: "mcp",
11691165 config: { url: "https://example.com" },
executor/convex/accounts.ts+7−9
@@ -11,11 +11,9 @@ async function deleteWorkspaceData( · 10 unmodified lines
1111 return false;
1212 }
1313
14− const workspaceKey = String(workspaceId);
15−
1614 const taskDocs = await ctx.db
1715 .query("tasks")
18− .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceKey))
16+ .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId))
1917 .collect();
2018 for (const task of taskDocs) {
2119 const taskEvents = await ctx.db
@@ -30,7 +28,7 @@ async function deleteWorkspaceData( · 8 unmodified lines
3028
3129 const approvalDocs = await ctx.db
3230 .query("approvals")
33− .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceKey))
31+ .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId))
3432 .collect();
3533 for (const approval of approvalDocs) {
3634 await ctx.db.delete(approval._id);
@@ -38,7 +36,7 @@ async function deleteWorkspaceData( · 1 unmodified line
3836
3937 const policyDocs = await ctx.db
4038 .query("accessPolicies")
41− .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceKey))
39+ .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId))
4240 .collect();
4341 for (const policy of policyDocs) {
4442 await ctx.db.delete(policy._id);
@@ -46,7 +44,7 @@ async function deleteWorkspaceData( · 1 unmodified line
4644
4745 const credentialDocs = await ctx.db
4846 .query("sourceCredentials")
49− .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceKey))
47+ .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId))
5048 .collect();
5149 for (const credential of credentialDocs) {
5250 await ctx.db.delete(credential._id);
@@ -54,7 +52,7 @@ async function deleteWorkspaceData( · 1 unmodified line
5452
5553 const toolSources = await ctx.db
5654 .query("toolSources")
57− .withIndex("by_workspace_updated", (q) => q.eq("workspaceId", workspaceKey))
55+ .withIndex("by_workspace_updated", (q) => q.eq("workspaceId", workspaceId))
5856 .collect();
5957 for (const source of toolSources) {
6058 await ctx.db.delete(source._id);
@@ -62,7 +60,7 @@ async function deleteWorkspaceData( · 1 unmodified line
6260
6361 const agentTasks = await ctx.db
6462 .query("agentTasks")
65− .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceKey))
63+ .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId))
6664 .collect();
6765 for (const agentTask of agentTasks) {
6866 await ctx.db.delete(agentTask._id);
@@ -70,7 +68,7 @@ async function deleteWorkspaceData( · 1 unmodified line
7068
7169 const cachedToolsets = await ctx.db
7270 .query("workspaceToolCache")
73− .withIndex("by_workspace", (q) => q.eq("workspaceId", workspaceKey))
71+ .withIndex("by_workspace", (q) => q.eq("workspaceId", workspaceId))
7472 .collect();
7573 for (const cacheEntry of cachedToolsets) {
7674 await ctx.storage.delete(cacheEntry.storageId).catch(() => {});
executor/convex/credentialsNode.ts+1−1
@@ -141,7 +141,7 @@ async function upsertVaultObject(args: { · 140 unmodified lines
141141 export const upsertCredential = action({
142142 args: {
143143 id: v.optional(v.string()),
144− workspaceId: v.string(),
144+ workspaceId: v.id("workspaces"),
145145 sessionId: v.optional(v.string()),
146146 sourceKey: v.string(),
147147 scope: credentialScopeValidator,
executor/convex/database.test.ts+35−9
@@ -1,6 +1,7 @@
11 import { expect, test } from "bun:test";
22 import { convexTest } from "convex-test";
33 import { internal } from "./_generated/api";
4+import type { Id } from "./_generated/dataModel";
45 import schema from "./schema";
56
67 function setup() {
@@ -10,14 +11,36 @@ function setup() { · 3 unmodified lines
1011 });
1112 }
1213
14+/** Insert a minimal workspace row and return its Id. */
15+async function seedWorkspace(t: ReturnType<typeof setup>, name = "test-ws"): Promise<Id<"workspaces">> {
16+ return await t.run(async (ctx) => {
17+ const orgId = await ctx.db.insert("organizations", {
18+ name: `${name}-org`,
19+ slug: `${name}-org`,
20+ status: "active",
21+ createdAt: Date.now(),
22+ updatedAt: Date.now(),
23+ });
24+ return await ctx.db.insert("workspaces", {
25+ name,
26+ slug: name,
27+ organizationId: orgId,
28+ plan: "free",
29+ createdAt: Date.now(),
30+ updatedAt: Date.now(),
31+ });
32+ });
33+}
34+
1335 test("task lifecycle supports queue, run, and complete", async () => {
1436 const t = setup();
37+ const wsId = await seedWorkspace(t, "ws_1");
1538
1639 const created = await t.mutation(internal.database.createTask, {
1740 id: "task_1",
1841 code: "console.log('hello')",
1942 runtimeId: "local-bun",
20− workspaceId: "ws_1",
43+ workspaceId: wsId,
2144 actorId: "actor_1",
2245 clientId: "web",
2346 });
@@ -49,12 +72,13 @@ test("task lifecycle supports queue, run, and complete", async () => { · 25 unmodified lines
4972
5073 test("approval lifecycle tracks pending and resolution", async () => {
5174 const t = setup();
75+ const wsId = await seedWorkspace(t, "ws_2");
5276
5377 await t.mutation(internal.database.createTask, {
5478 id: "task_2",
5579 code: "await tools.admin.delete_data({ id: 'x' })",
5680 runtimeId: "local-bun",
57− workspaceId: "ws_2",
81+ workspaceId: wsId,
5882 actorId: "actor_2",
5983 clientId: "web",
6084 });
@@ -67,7 +91,7 @@ test("approval lifecycle tracks pending and resolution", async () => { · 6 unmodified lines
6791 });
6892 expect(createdApproval.status).toBe("pending");
6993
70− const pending = await t.query(internal.database.listPendingApprovals, { workspaceId: "ws_2" });
94+ const pending = await t.query(internal.database.listPendingApprovals, { workspaceId: wsId });
7195 expect(pending.length).toBe(1);
7296 expect(pending[0]?.task.id).toBe("task_2");
7397
@@ -78,7 +102,7 @@ test("approval lifecycle tracks pending and resolution", async () => { · 4 unmodified lines
78102 });
79103 expect(resolved?.status).toBe("approved");
80104
81− const pendingAfter = await t.query(internal.database.listPendingApprovals, { workspaceId: "ws_2" });
105+ const pendingAfter = await t.query(internal.database.listPendingApprovals, { workspaceId: wsId });
82106 expect(pendingAfter).toEqual([]);
83107 });
84108
@@ -140,9 +164,10 @@ test("bootstrap honors MCP caller-provided session id", async () => { · 55 unmodified lines
140164
141165 test("credentials persist provider and resolve by scope", async () => {
142166 const t = setup();
167+ const wsId = await seedWorkspace(t, "ws_cred");
143168
144169 const workspaceCredential = await t.mutation(internal.database.upsertCredential, {
145− workspaceId: "ws_cred",
170+ workspaceId: wsId,
146171 sourceKey: "openapi:github",
147172 scope: "workspace",
148173 provider: "managed",
@@ -152,7 +177,7 @@ test("credentials persist provider and resolve by scope", async () => { · 3 unmodified lines
152177 expect(workspaceCredential.provider).toBe("managed");
153178
154179 const actorCredential = await t.mutation(internal.database.upsertCredential, {
155− workspaceId: "ws_cred",
180+ workspaceId: wsId,
156181 sourceKey: "openapi:github",
157182 scope: "actor",
158183 actorId: "actor_cred",
@@ -164,14 +189,14 @@ test("credentials persist provider and resolve by scope", async () => { · 5 unmodified lines
164189 expect(actorCredential.actorId).toBe("actor_cred");
165190
166191 const resolvedWorkspace = await t.query(internal.database.resolveCredential, {
167− workspaceId: "ws_cred",
192+ workspaceId: wsId,
168193 sourceKey: "openapi:github",
169194 scope: "workspace",
170195 });
171196 expect(resolvedWorkspace?.provider).toBe("managed");
172197
173198 const resolvedActor = await t.query(internal.database.resolveCredential, {
174− workspaceId: "ws_cred",
199+ workspaceId: wsId,
175200 sourceKey: "openapi:github",
176201 scope: "actor",
177202 actorId: "actor_cred",
@@ -181,9 +206,10 @@ test("credentials persist provider and resolve by scope", async () => { · 3 unmodified lines
181206
182207 test("upsertCredential defaults provider to managed", async () => {
183208 const t = setup();
209+ const wsId = await seedWorkspace(t, "ws_default_provider");
184210
185211 const credential = await t.mutation(internal.database.upsertCredential, {
186− workspaceId: "ws_default_provider",
212+ workspaceId: wsId,
187213 sourceKey: "openapi:stripe",
188214 scope: "workspace",
189215 secretJson: { token: "sk_test_123" },
executor/convex/database.ts+16−16
@@ -5,7 +5,7 @@ query, · 4 unmodified lines
55 type MutationCtx, type QueryCtx } from "./_generated/server";
66 import { internalMutation, internalQuery } from "./_generated/server";
77 import { ensureUniqueSlug } from "../lib/slug";
8−import type { TaskStatus } from "@executor/contracts";
8+import type { TaskStatus } from "../lib/types";
99
1010 const DEFAULT_TIMEOUT_MS = 300_000;
1111 type OrganizationRole = "owner" | "admin" | "member" | "billing_admin";
@@ -335,7 +335,7 @@ export const createTask = internalMutation({ · 323 unmodified lines
335335 runtimeId: v.string(),
336336 timeoutMs: v.optional(v.number()),
337337 metadata: v.optional(v.any()),
338− workspaceId: v.string(),
338+ workspaceId: v.id("workspaces"),
339339 actorId: v.string(),
340340 clientId: v.optional(v.string()),
341341 },
@@ -377,7 +377,7 @@ export const getTask = internalQuery({ · 35 unmodified lines
377377 });
378378
379379 export const listTasks = internalQuery({
380− args: { workspaceId: v.string() },
380+ args: { workspaceId: v.id("workspaces") },
381381 handler: async (ctx, args) => {
382382 const docs = await ctx.db
383383 .query("tasks")
@@ -415,7 +415,7 @@ export const listRuntimeTargets = internalQuery({ · 31 unmodified lines
415415 });
416416
417417 export const getTaskInWorkspace = internalQuery({
418− args: { taskId: v.string(), workspaceId: v.string() },
418+ args: { taskId: v.string(), workspaceId: v.id("workspaces") },
419419 handler: async (ctx, args) => {
420420 const doc = await getTaskDoc(ctx, args.taskId);
421421 if (!doc || doc.workspaceId !== args.workspaceId) {
@@ -523,7 +523,7 @@ export const getApproval = internalQuery({ · 101 unmodified lines
523523
524524 export const listApprovals = internalQuery({
525525 args: {
526− workspaceId: v.string(),
526+ workspaceId: v.id("workspaces"),
527527 status: v.optional(approvalStatusValidator),
528528 },
529529 handler: async (ctx, args) => {
@@ -549,7 +549,7 @@ export const listApprovals = internalQuery({ · 19 unmodified lines
549549 });
550550
551551 export const listPendingApprovals = internalQuery({
552− args: { workspaceId: v.string() },
552+ args: { workspaceId: v.id("workspaces") },
553553 handler: async (ctx, args) => {
554554 const docs = await ctx.db
555555 .query("approvals")
@@ -616,7 +616,7 @@ export const resolveApproval = internalMutation({ · 60 unmodified lines
616616 });
617617
618618 export const getApprovalInWorkspace = internalQuery({
619− args: { approvalId: v.string(), workspaceId: v.string() },
619+ args: { approvalId: v.string(), workspaceId: v.id("workspaces") },
620620 handler: async (ctx, args) => {
621621 const doc = await getApprovalDoc(ctx, args.approvalId);
622622 if (!doc || doc.workspaceId !== args.workspaceId) {
@@ -656,7 +656,7 @@ export const createAgentTask = mutation({ · 33 unmodified lines
656656 id: v.string(),
657657 prompt: v.string(),
658658 requesterId: v.string(),
659− workspaceId: v.string(),
659+ workspaceId: v.id("workspaces"),
660660 actorId: v.string(),
661661 },
662662 handler: async (ctx, args) => {
@@ -796,7 +796,7 @@ export const bootstrapAnonymousSession = internalMutation({ · 133 unmodified lines
796796 export const upsertAccessPolicy = internalMutation({
797797 args: {
798798 id: v.optional(v.string()),
799− workspaceId: v.string(),
799+ workspaceId: v.id("workspaces"),
800800 actorId: v.optional(v.string()),
801801 clientId: v.optional(v.string()),
802802 toolPathPattern: v.string(),
@@ -847,7 +847,7 @@ export const upsertAccessPolicy = internalMutation({ · 44 unmodified lines
847847 });
848848
849849 export const listAccessPolicies = internalQuery({
850− args: { workspaceId: v.string() },
850+ args: { workspaceId: v.id("workspaces") },
851851 handler: async (ctx, args) => {
852852 const docs = await ctx.db
853853 .query("accessPolicies")
@@ -868,7 +868,7 @@ export const listAccessPolicies = internalQuery({ · 14 unmodified lines
868868 export const upsertCredential = internalMutation({
869869 args: {
870870 id: v.optional(v.string()),
871− workspaceId: v.string(),
871+ workspaceId: v.id("workspaces"),
872872 sourceKey: v.string(),
873873 scope: credentialScopeValidator,
874874 actorId: v.optional(v.string()),
@@ -932,7 +932,7 @@ export const upsertCredential = internalMutation({ · 57 unmodified lines
932932 });
933933
934934 export const listCredentials = internalQuery({
935− args: { workspaceId: v.string() },
935+ args: { workspaceId: v.id("workspaces") },
936936 handler: async (ctx, args) => {
937937 const docs = await ctx.db
938938 .query("sourceCredentials")
@@ -963,7 +963,7 @@ export const listCredentialProviders = internalQuery({ · 24 unmodified lines
963963
964964 export const resolveCredential = internalQuery({
965965 args: {
966− workspaceId: v.string(),
966+ workspaceId: v.id("workspaces"),
967967 sourceKey: v.string(),
968968 scope: credentialScopeValidator,
969969 actorId: v.optional(v.string()),
@@ -1007,7 +1007,7 @@ export const resolveCredential = internalQuery({ · 37 unmodified lines
10071007 export const upsertToolSource = internalMutation({
10081008 args: {
10091009 id: v.optional(v.string()),
1010− workspaceId: v.string(),
1010+ workspaceId: v.id("workspaces"),
10111011 name: v.string(),
10121012 type: toolSourceTypeValidator,
10131013 config: v.any(),
@@ -1065,7 +1065,7 @@ export const upsertToolSource = internalMutation({ · 51 unmodified lines
10651065 });
10661066
10671067 export const listToolSources = internalQuery({
1068− args: { workspaceId: v.string() },
1068+ args: { workspaceId: v.id("workspaces") },
10691069 handler: async (ctx, args) => {
10701070 const docs = await ctx.db
10711071 .query("toolSources")
@@ -1077,7 +1077,7 @@ export const listToolSources = internalQuery({ · 5 unmodified lines
10771077 });
10781078
10791079 export const deleteToolSource = internalMutation({
1080− args: { workspaceId: v.string(), sourceId: v.string() },
1080+ args: { workspaceId: v.id("workspaces"), sourceId: v.string() },
10811081 handler: async (ctx, args) => {
10821082 const doc = await ctx.db
10831083 .query("toolSources")
executor/convex/executor-mcp.e2e.test.ts+3−2
@@ -4,6 +4,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; · 3 unmodified lines
44 import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
55 import { ElicitRequestSchema } from "@modelcontextprotocol/sdk/types.js";
66 import { internal } from "./_generated/api";
7+import type { Id } from "./_generated/dataModel";
78 import schema from "./schema";
89
910 function setup() {
@@ -46,7 +47,7 @@ function createMcpTransport( · 36 unmodified lines
4647 async function waitForTaskId(t: ReturnType<typeof setup>, workspaceId: string, timeoutMs = 10_000): Promise<string> {
4748 const start = Date.now();
4849 while (Date.now() - start < timeoutMs) {
49− const tasks = await t.query(internal.database.listTasks, { workspaceId });
50+ const tasks = await t.query(internal.database.listTasks, { workspaceId: workspaceId as Id<"workspaces"> });
5051 if (tasks.length > 0) {
5152 return tasks[0]!.id;
5253 }
@@ -64,7 +65,7 @@ async function waitForPendingApproval( · 11 unmodified lines
6465 ): Promise<string> {
6566 const start = Date.now();
6667 while (Date.now() - start < timeoutMs) {
67− const approvals = await t.query(internal.database.listPendingApprovals, { workspaceId });
68+ const approvals = await t.query(internal.database.listPendingApprovals, { workspaceId: workspaceId as Id<"workspaces"> });
6869 const approval = approvals.find((item: { toolPath: string; id: string }) => item.toolPath === toolPath);
6970 if (approval) {
7071 return approval.id;
executor/convex/executor.ts+5−4
@@ -1,5 +1,6 @@
11 import { v } from "convex/values";
22 import { internal } from "./_generated/api";
3+import type { Id } from "./_generated/dataModel";
34 import type { MutationCtx } from "./_generated/server";
45 import { internalMutation } from "./_generated/server";
56 import { workspaceMutation } from "../lib/functionBuilders";
@@ -30,7 +31,7 @@ async function createTaskRecord( · 24 unmodified lines
3031 timeoutMs?: number;
3132 runtimeId?: string;
3233 metadata?: unknown;
33− workspaceId: string;
34+ workspaceId: Id<"workspaces">;
3435 actorId: string;
3536 clientId?: string;
3637 },
@@ -92,7 +93,7 @@ async function createTaskRecord( · 55 unmodified lines
9293 async function resolveApprovalRecord(
9394 ctx: MutationCtx,
9495 args: {
95− workspaceId: string;
96+ workspaceId: Id<"workspaces">;
9697 approvalId: string;
9798 decision: "approved" | "denied";
9899 reviewerId?: string;
@@ -172,7 +173,7 @@ export const createTaskInternal = internalMutation({ · 73 unmodified lines
172173 timeoutMs: v.optional(v.number()),
173174 runtimeId: v.optional(v.string()),
174175 metadata: v.optional(v.any()),
175− workspaceId: v.string(),
176+ workspaceId: v.id("workspaces"),
176177 actorId: v.string(),
177178 clientId: v.optional(v.string()),
178179 },
@@ -204,7 +205,7 @@ export const resolveApproval = workspaceMutation({ · 25 unmodified lines
204205
205206 export const resolveApprovalInternal = internalMutation({
206207 args: {
207− workspaceId: v.string(),
208+ workspaceId: v.id("workspaces"),
208209 approvalId: v.string(),
209210 decision: v.union(v.literal("approved"), v.literal("denied")),
210211 reviewerId: v.optional(v.string()),
executor/convex/executorNode.ts+4−6
@@ -525,8 +525,6 @@ function toToolDescriptor(tool: ToolDefinition, approval: "auto" | "required"): · 524 unmodified lines
525525 operationId: tool.metadata?.operationId,
526526 // Note: sourceDts is NOT included — it's too large to send over the wire.
527527 // Monaco fetches .d.ts blobs separately via Convex storage URLs.
528− // Legacy compat
529− schemaTypes: tool.metadata?.schemaTypes,
530528 };
531529 }
532530
@@ -910,7 +908,7 @@ async function invokeTool(ctx: any, task: TaskRecord, call: ToolCallRequest): Pr · 377 unmodified lines
910908
911909 export const listTools = action({
912910 args: {
913− workspaceId: v.string(),
911+ workspaceId: v.id("workspaces"),
914912 actorId: v.optional(v.string()),
915913 clientId: v.optional(v.string()),
916914 sessionId: v.optional(v.string()),
@@ -939,7 +937,7 @@ export const listTools = action({ · 22 unmodified lines
939937
940938 export const listToolsWithWarnings = action({
941939 args: {
942− workspaceId: v.string(),
940+ workspaceId: v.id("workspaces"),
943941 actorId: v.optional(v.string()),
944942 clientId: v.optional(v.string()),
945943 sessionId: v.optional(v.string()),
@@ -976,7 +974,7 @@ export const listToolsWithWarnings = action({ · 30 unmodified lines
976974
977975 export const listToolsInternal = internalAction({
978976 args: {
979− workspaceId: v.string(),
977+ workspaceId: v.id("workspaces"),
980978 actorId: v.optional(v.string()),
981979 clientId: v.optional(v.string()),
982980 },
@@ -987,7 +985,7 @@ export const listToolsInternal = internalAction({ · 4 unmodified lines
987985
988986 export const listToolsWithWarningsInternal = internalAction({
989987 args: {
990− workspaceId: v.string(),
988+ workspaceId: v.id("workspaces"),
991989 actorId: v.optional(v.string()),
992990 clientId: v.optional(v.string()),
993991 },
executor/convex/http.ts+15−8
@@ -6,6 +6,7 @@ import { httpAction } from "./_generated/server"; · 5 unmodified lines
66 import { authKit } from "./auth";
77 import { handleMcpRequest, type McpWorkspaceContext } from "../lib/mcp_server";
88 import type { AnonymousContext, PendingApprovalRecord, TaskRecord, ToolDescriptor } from "../lib/types";
9+import type { Id } from "./_generated/dataModel";
910
1011 const http = httpRouter();
1112 const internalToken = process.env.EXECUTOR_INTERNAL_TOKEN ?? null;
@@ -161,7 +162,7 @@ const mcpHandler = httpAction(async (ctx, request) => { · 149 unmodified lines
161162 try {
162163 if (mcpAuthConfig.enabled && auth?.subject) {
163164 const access = await ctx.runQuery(internal.workspaceAuthInternal.getWorkspaceAccessForWorkosSubject, {
164− workspaceId: requestedContext.workspaceId,
165+ workspaceId: requestedContext.workspaceId as Id<"workspaces">,
165166 subject: auth.subject,
166167 });
167168
@@ -176,7 +177,7 @@ const mcpHandler = httpAction(async (ctx, request) => { · 8 unmodified lines
176177 }
177178
178179 const access = await ctx.runQuery(internal.workspaceAuthInternal.getWorkspaceAccessForRequest, {
179− workspaceId: requestedContext.workspaceId,
180+ workspaceId: requestedContext.workspaceId as Id<"workspaces">,
180181 sessionId: requestedContext.sessionId,
181182 });
182183
@@ -212,11 +213,14 @@ const mcpHandler = httpAction(async (ctx, request) => { · 29 unmodified lines
212213 actorId: string;
213214 clientId?: string;
214215 }) => {
215− return (await ctx.runMutation(internal.executor.createTaskInternal, input)) as { task: TaskRecord };
216+ return (await ctx.runMutation(internal.executor.createTaskInternal, {
217+ ...input,
218+ workspaceId: input.workspaceId as Id<"workspaces">,
219+ })) as { task: TaskRecord };
216220 },
217221 getTask: async (taskId: string, workspaceId?: string) => {
218222 if (workspaceId) {
219− return (await ctx.runQuery(internal.database.getTaskInWorkspace, { taskId, workspaceId })) as TaskRecord | null;
223+ return (await ctx.runQuery(internal.database.getTaskInWorkspace, { taskId, workspaceId: workspaceId as Id<"workspaces"> })) as TaskRecord | null;
220224 }
221225 return null;
222226 },
@@ -231,10 +235,10 @@ const mcpHandler = httpAction(async (ctx, request) => { · 8 unmodified lines
231235 return [];
232236 }
233237
234− return (await ctx.runAction(internal.executorNode.listToolsInternal, toolContext)) as ToolDescriptor[];
238+ return (await ctx.runAction(internal.executorNode.listToolsInternal, { ...toolContext, workspaceId: toolContext.workspaceId as Id<"workspaces"> })) as ToolDescriptor[];
235239 },
236240 listToolsForTypecheck: async (toolContext: { workspaceId: string; actorId?: string; clientId?: string }) => {
237− const result = await ctx.runAction(internal.executorNode.listToolsWithWarningsInternal, toolContext) as {
241+ const result = await ctx.runAction(internal.executorNode.listToolsWithWarningsInternal, { ...toolContext, workspaceId: toolContext.workspaceId as Id<"workspaces"> }) as {
238242 tools: ToolDescriptor[];
239243 dtsUrls?: Record<string, string>;
240244 };
@@ -245,7 +249,7 @@ const mcpHandler = httpAction(async (ctx, request) => { · 4 unmodified lines
245249 };
246250 },
247251 listPendingApprovals: async (workspaceId: string) => {
248− return (await ctx.runQuery(internal.database.listPendingApprovals, { workspaceId })) as PendingApprovalRecord[];
252+ return (await ctx.runQuery(internal.database.listPendingApprovals, { workspaceId: workspaceId as Id<"workspaces"> })) as PendingApprovalRecord[];
249253 },
250254 resolveApproval: async (input: {
251255 workspaceId: string;
@@ -254,7 +258,10 @@ const mcpHandler = httpAction(async (ctx, request) => { · 2 unmodified lines
254258 reviewerId?: string;
255259 reason?: string;
256260 }) => {
257− return await ctx.runMutation(internal.executor.resolveApprovalInternal, input);
261+ return await ctx.runMutation(internal.executor.resolveApprovalInternal, {
262+ ...input,
263+ workspaceId: input.workspaceId as Id<"workspaces">,
264+ });
258265 },
259266 };
260267
executor/convex/migrations.ts+20−0
@@ -92,4 +92,24 @@ export const deleteInvitesMissingProvider = migrations.define({ · 91 unmodified lines
9292 },
9393 });
9494
95+export const deleteAnonymousSessionsMissingUserId = migrations.define({
96+ table: "anonymousSessions",
97+ migrateOne: async (ctx, session) => {
98+ if (session.userId) {
99+ return;
100+ }
101+
102+ await ctx.db.delete(session._id);
103+ },
104+});
105+
106+export const backfillDtsStorageIds = migrations.define({
107+ table: "workspaceToolCache",
108+ migrateOne: async (_ctx, entry) => {
109+ if (entry.dtsStorageIds === undefined) {
110+ return { dtsStorageIds: [] };
111+ }
112+ },
113+});
114+
95115 export const run = migrations.runner();
executor/convex/package.json+7−0
@@ -0,0 +1,7 @@
1+{
2+ "name": "@executor/convex",
3+ "version": "0.0.1",
4+ "private": true,
5+ "main": "./_generated/api.js",
6+ "types": "./_generated/api.d.ts"
7+}
executor/convex/schema.ts+10−25
@@ -181,7 +181,7 @@ export default defineSchema({ · 180 unmodified lines
181181 taskId: v.string(),
182182 code: v.string(),
183183 runtimeId: v.string(),
184− workspaceId: v.string(),
184+ workspaceId: v.id("workspaces"),
185185 actorId: v.string(),
186186 clientId: v.string(),
187187 status: taskStatus,
@@ -203,7 +203,7 @@ export default defineSchema({ · 15 unmodified lines
203203 approvals: defineTable({
204204 approvalId: v.string(),
205205 taskId: v.string(),
206− workspaceId: v.string(),
206+ workspaceId: v.id("workspaces"),
207207 toolPath: v.string(),
208208 input: v.any(),
209209 status: approvalStatus,
@@ -217,21 +217,6 @@ export default defineSchema({ · 7 unmodified lines
217217 .index("by_workspace_created", ["workspaceId", "createdAt"])
218218 .index("by_workspace_status_created", ["workspaceId", "status", "createdAt"]),
219219
220− pushSubscriptions: defineTable({
221− workspaceId: v.string(),
222− accountId: v.string(),
223− endpoint: v.string(),
224− p256dh: v.string(),
225− auth: v.string(),
226− expirationTime: v.optional(v.number()),
227− userAgent: v.optional(v.string()),
228− createdAt: v.number(),
229− updatedAt: v.number(),
230− })
231− .index("by_workspace_created", ["workspaceId", "createdAt"])
232− .index("by_workspace_account", ["workspaceId", "accountId"])
233− .index("by_endpoint", ["endpoint"]),
234−
235220 taskEvents: defineTable({
236221 sequence: v.number(),
237222 taskId: v.string(),
@@ -245,7 +230,7 @@ export default defineSchema({ · 7 unmodified lines
245230
246231 accessPolicies: defineTable({
247232 policyId: v.string(),
248− workspaceId: v.string(),
233+ workspaceId: v.id("workspaces"),
249234 actorId: v.string(),
250235 clientId: v.string(),
251236 toolPathPattern: v.string(),
@@ -259,7 +244,7 @@ export default defineSchema({ · 7 unmodified lines
259244
260245 sourceCredentials: defineTable({
261246 credentialId: v.string(),
262− workspaceId: v.string(),
247+ workspaceId: v.id("workspaces"),
263248 sourceKey: v.string(),
264249 scope: credentialScope,
265250 actorId: v.string(),
@@ -274,7 +259,7 @@ export default defineSchema({ · 8 unmodified lines
274259
275260 toolSources: defineTable({
276261 sourceId: v.string(),
277− workspaceId: v.string(),
262+ workspaceId: v.id("workspaces"),
278263 name: v.string(),
279264 type: toolSourceType,
280265 config: v.any(),
@@ -291,7 +276,7 @@ export default defineSchema({ · 10 unmodified lines
291276 agentTaskId: v.string(),
292277 prompt: v.string(),
293278 requesterId: v.string(),
294− workspaceId: v.string(),
279+ workspaceId: v.id("workspaces"),
295280 actorId: v.string(),
296281 status: agentTaskStatus,
297282 resultText: v.optional(v.string()),
@@ -314,14 +299,14 @@ export default defineSchema({ · 16 unmodified lines
314299 .index("by_spec_url_version", ["specUrl", "version"]),
315300
316301 workspaceToolCache: defineTable({
317− workspaceId: v.string(),
302+ workspaceId: v.id("workspaces"),
318303 signature: v.string(),
319304 storageId: v.id("_storage"),
320305 /** Per-source .d.ts blobs stored separately (too large for action responses). */
321− dtsStorageIds: v.optional(v.array(v.object({
306+ dtsStorageIds: v.array(v.object({
322307 sourceKey: v.string(),
323308 storageId: v.id("_storage"),
324− }))),
309+ })),
325310 toolCount: v.number(),
326311 sizeBytes: v.number(),
327312 createdAt: v.number(),
@@ -334,7 +319,7 @@ export default defineSchema({ · 6 unmodified lines
334319 actorId: v.string(),
335320 clientId: v.string(),
336321 accountId: v.id("accounts"),
337− userId: v.optional(v.id("workspaceMembers")),
322+ userId: v.id("workspaceMembers"),
338323 createdAt: v.number(),
339324 lastSeenAt: v.number(),
340325 })
executor/convex/workspaceAuthInternal.ts+2−2
@@ -12,7 +12,7 @@ function actorIdForAccount(account: { _id: string; provider: string; providerAcc · 11 unmodified lines
1212
1313 export const getWorkspaceAccessForRequest = internalQuery({
1414 args: {
15− workspaceId: v.string(),
15+ workspaceId: v.id("workspaces"),
1616 sessionId: v.optional(v.string()),
1717 },
1818 handler: async (ctx, args) => {
@@ -36,7 +36,7 @@ export const getWorkspaceAccessForRequest = internalQuery({ · 17 unmodified lines
3636
3737 export const getWorkspaceAccessForWorkosSubject = internalQuery({
3838 args: {
39− workspaceId: v.string(),
39+ workspaceId: v.id("workspaces"),
4040 subject: v.string(),
4141 },
4242 handler: async (ctx, args) => {
executor/convex/workspaceToolCache.ts+9−11
@@ -7,7 +7,7 @@ import { internalMutation, internalQuery } from "./_generated/server"; · 6 unmodified lines
77 */
88 export const getEntry = internalQuery({
99 args: {
10− workspaceId: v.string(),
10+ workspaceId: v.id("workspaces"),
1111 signature: v.string(),
1212 },
1313 handler: async (ctx, args) => {
@@ -21,7 +21,7 @@ export const getEntry = internalQuery({ · 7 unmodified lines
2121
2222 return {
2323 storageId: entry.storageId,
24− dtsStorageIds: entry.dtsStorageIds ?? [],
24+ dtsStorageIds: entry.dtsStorageIds,
2525 toolCount: entry.toolCount,
2626 sizeBytes: entry.sizeBytes,
2727 createdAt: entry.createdAt,
@@ -35,13 +35,13 @@ export const getEntry = internalQuery({ · 7 unmodified lines
3535 */
3636 export const putEntry = internalMutation({
3737 args: {
38− workspaceId: v.string(),
38+ workspaceId: v.id("workspaces"),
3939 signature: v.string(),
4040 storageId: v.id("_storage"),
41− dtsStorageIds: v.optional(v.array(v.object({
41+ dtsStorageIds: v.array(v.object({
4242 sourceKey: v.string(),
4343 storageId: v.id("_storage"),
44− }))),
44+ })),
4545 toolCount: v.number(),
4646 sizeBytes: v.number(),
4747 },
@@ -55,10 +55,8 @@ export const putEntry = internalMutation({ · 7 unmodified lines
5555 // Delete old main snapshot blob
5656 await ctx.storage.delete(existing.storageId).catch(() => {});
5757 // Delete old .d.ts blobs
58− if (existing.dtsStorageIds) {
59− for (const entry of existing.dtsStorageIds) {
60− await ctx.storage.delete(entry.storageId).catch(() => {});
61− }
58+ for (const entry of existing.dtsStorageIds) {
59+ await ctx.storage.delete(entry.storageId).catch(() => {});
6260 }
6361 await ctx.db.delete(existing._id);
6462 }
@@ -80,7 +78,7 @@ export const putEntry = internalMutation({ · 15 unmodified lines
8078 */
8179 export const getDtsStorageIds = internalQuery({
8280 args: {
83− workspaceId: v.string(),
81+ workspaceId: v.id("workspaces"),
8482 },
8583 handler: async (ctx, args) => {
8684 const entry = await ctx.db
@@ -89,6 +87,6 @@ export const getDtsStorageIds = internalQuery({ · 2 unmodified lines
8987 .unique();
9088
9189 if (!entry) return [];
92− return entry.dtsStorageIds ?? [];
90+ return entry.dtsStorageIds;
9391 },
9492 });
executor/convex/workspaces.ts+6−8
@@ -20,8 +20,6 @@ async function workspaceHasActivity( · 19 unmodified lines
2020 ctx: Pick<QueryCtx, "db"> | Pick<MutationCtx, "db">,
2121 workspaceId: Id<"workspaces">,
2222 ): Promise<boolean> {
23− const workspaceIdString = String(workspaceId);
24−
2523 const [
2624 task,
2725 approval,
@@ -31,15 +29,15 @@ async function workspaceHasActivity( · 3 unmodified lines
3129 agentTask,
3230 member,
3331 ] = await Promise.all([
34− ctx.db.query("tasks").withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceIdString)).first(),
35− ctx.db.query("approvals").withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceIdString)).first(),
36− ctx.db.query("accessPolicies").withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceIdString)).first(),
32+ ctx.db.query("tasks").withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId)).first(),
33+ ctx.db.query("approvals").withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId)).first(),
34+ ctx.db.query("accessPolicies").withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId)).first(),
3735 ctx.db
3836 .query("sourceCredentials")
39− .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceIdString))
37+ .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId))
4038 .first(),
41− ctx.db.query("toolSources").withIndex("by_workspace_updated", (q) => q.eq("workspaceId", workspaceIdString)).first(),
42− ctx.db.query("agentTasks").withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceIdString)).first(),
39+ ctx.db.query("toolSources").withIndex("by_workspace_updated", (q) => q.eq("workspaceId", workspaceId)).first(),
40+ ctx.db.query("agentTasks").withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId)).first(),
4341 ctx.db.query("workspaceMembers").withIndex("by_workspace", (q) => q.eq("workspaceId", workspaceId)).first(),
4442 ]);
4543
executor/lib/credential-providers.test.ts+0−9
@@ -46,15 +46,6 @@ test("WorkOS Vault provider falls back to id key and raw token", async () => { · 45 unmodified lines
4646 expect(payload).toEqual({ token: "ghp_raw_token" });
4747 });
4848
49−test("WorkOS Vault provider supports legacy inline payload", async () => {
50− const payload = await resolveCredentialPayload({
51− provider: "workos-vault",
52− secretJson: { token: "legacy-token" },
53− });
54−
55− expect(payload).toEqual({ token: "legacy-token" });
56−});
57−
5849 test("WorkOS Vault provider gives actionable error on missing reference", async () => {
5950 await expect(
6051 resolveCredentialPayload({
executor/lib/credential_providers.ts+1−9
@@ -124,14 +124,6 @@ async function resolveWorkosVault( · 123 unmodified lines
124124 : "";
125125
126126 if (!objectId) {
127− // Backward compatibility: older rows may have stored credential payload directly
128− // under the encrypted provider before object references were persisted.
129− const { objectId: _objectId, id: _id, apiKey: _apiKey, ...legacyPayload } = config;
130− const normalized = asRecord(legacyPayload);
131− if (Object.keys(normalized).length > 0) {
132− return normalized;
133− }
134−
135127 throw new Error(
136128 "Encrypted credential is missing its secure reference. Re-save this credential in the dashboard.",
137129 );
@@ -151,7 +143,7 @@ export async function resolveCredentialPayload( · 13 unmodified lines
151143 record: Pick<CredentialRecord, "provider" | "secretJson">,
152144 options?: { readVaultObject?: VaultObjectReader },
153145 ): Promise<CredentialPayload | null> {
154− const provider = (record.provider ?? "managed") as CredentialProvider;
146+ const provider = record.provider;
155147 const resolver = providers[provider];
156148 if (!resolver) {
157149 throw new Error(`Unsupported credential provider: ${provider}`);
executor/lib/functionBuilders.ts+2−2
@@ -138,7 +138,7 @@ export const organizationMutation = customMutation(mutation, { · 137 unmodified lines
138138
139139 export const workspaceQuery = customQuery(query, {
140140 args: {
141− workspaceId: v.string(),
141+ workspaceId: v.id("workspaces"),
142142 sessionId: v.optional(v.string()),
143143 },
144144 input: async (ctx, args, options: WorkspaceAccessOptions = {}) => {
@@ -158,7 +158,7 @@ export const workspaceQuery = customQuery(query, { · 13 unmodified lines
158158
159159 export const workspaceMutation = customMutation(mutation, {
160160 args: {
161− workspaceId: v.string(),
161+ workspaceId: v.id("workspaces"),
162162 sessionId: v.optional(v.string()),
163163 },
164164 input: async (ctx, args, options: WorkspaceAccessOptions = {}) => {
executor/lib/identity.ts+3−3
@@ -100,10 +100,10 @@ export async function getWorkspaceMembership( · 99 unmodified lines
100100
101101 export async function requireWorkspaceAccessForAccount(
102102 ctx: MembershipCtx,
103− workspaceId: string,
103+ workspaceId: Id<"workspaces">,
104104 account: Doc<"accounts">,
105105 ): Promise<WorkspaceAccess> {
106− const workspace = await ctx.db.get(workspaceId as Id<"workspaces">);
106+ const workspace = await ctx.db.get(workspaceId);
107107 if (!workspace) {
108108 throw new Error("Workspace not found");
109109 }
@@ -140,7 +140,7 @@ export async function requireWorkspaceAccessForAccount( · 30 unmodified lines
140140
141141 export async function requireWorkspaceAccessForRequest(
142142 ctx: IdentityCtx,
143− workspaceId: string,
143+ workspaceId: Id<"workspaces">,
144144 sessionId?: string,
145145 ): Promise<WorkspaceAccess> {
146146 const account = await resolveAccountForRequest(ctx, sessionId);
executor/lib/mcp-server.test.ts+2−0
@@ -78,6 +78,8 @@ class FakeMcpService { · 77 unmodified lines
7878 workspaceId: `ws_${crypto.randomUUID()}`,
7979 actorId: `anon_${crypto.randomUUID()}`,
8080 clientId: "mcp",
81+ accountId: `account_${crypto.randomUUID()}`,
82+ userId: `user_${crypto.randomUUID()}`,
8183 createdAt: now,
8284 lastSeenAt: now,
8385 };
executor/lib/openapi-cache.test.ts+0−188
@@ -168,110 +168,7 @@ describe("prepareOpenApiSpec with large specs", () => { · 167 unmodified lines
168168 expect(json.length).toBeGreaterThan(100_000);
169169 });
170170
171− test("preserves operationTypes when spec is large", async () => {
172− const spec = makeLargeSpec(50);
173− const prepared = await prepareOpenApiSpec(spec, "type-test");
174171
175− // openapiTS should generate types for all operations
176− if (prepared.operationTypes) {
177− const opCount = Object.keys(prepared.operationTypes).length;
178− // Should have types for most operations (openapiTS generates from valid specs)
179− expect(opCount).toBeGreaterThan(0);
180−
181− // Spot-check a known operation
182− const getType = prepared.operationTypes["get_resource_0"];
183− if (getType) {
184− expect(getType.argsType).toBeDefined();
185− expect(getType.returnsType).toBeDefined();
186− }
187− }
188− });
189−
190− test("preserves schemaTypes when spec has shared schemas", async () => {
191− const specWithSchemas: Record<string, unknown> = {
192− openapi: "3.0.3",
193− info: { title: "Schema Test", version: "1.0.0" },
194− servers: [{ url: "https://api.example.com" }],
195− components: {
196− schemas: {
197− User: {
198− type: "object",
199− properties: {
200− id: { type: "string" },
201− email: { type: "string" },
202− name: { type: "string" },
203− role: { type: "string", enum: ["admin", "user", "viewer"] },
204− },
205− required: ["id", "email"],
206− },
207− UserList: {
208− type: "object",
209− properties: {
210− data: {
211− type: "array",
212− items: { $ref: "#/components/schemas/User" },
213− },
214− total: { type: "number" },
215− },
216− },
217− },
218− },
219− paths: {
220− "/users": {
221− get: {
222− operationId: "listUsers",
223− tags: ["users"],
224− summary: "List users",
225− responses: {
226− "200": {
227− description: "ok",
228− content: {
229− "application/json": {
230− schema: { $ref: "#/components/schemas/UserList" },
231− },
232− },
233− },
234− },
235− },
236− },
237− "/users/{id}": {
238− get: {
239− operationId: "getUser",
240− tags: ["users"],
241− parameters: [
242− {
243− name: "id",
244− in: "path",
245− required: true,
246− schema: { type: "string" },
247− },
248− ],
249− summary: "Get user",
250− responses: {
251− "200": {
252− description: "ok",
253− content: {
254− "application/json": {
255− schema: { $ref: "#/components/schemas/User" },
256− },
257− },
258− },
259− },
260− },
261− },
262− },
263− };
264−
265− const prepared = await prepareOpenApiSpec(specWithSchemas, "schema-test");
266−
267− // Should have generated schema types
268− if (prepared.schemaTypes) {
269− expect(Object.keys(prepared.schemaTypes).length).toBeGreaterThan(0);
270− }
271−
272− // No warnings
273− expect(prepared.warnings).toHaveLength(0);
274− });
275172 });
276173
277174 describe("buildOpenApiToolsFromPrepared", () => {
@@ -946,78 +843,7 @@ describe("buildOpenApiToolsFromPrepared", () => { · 668 unmodified lines
946843 expect(tool!.metadata?.returnsType).not.toBe("unknown");
947844 });
948845
949− test("schemaTypes only attached to first tool from a source", async () => {
950− const specWithSchemas: Record<string, unknown> = {
951− openapi: "3.0.3",
952− info: { title: "Schema placement test", version: "1.0.0" },
953− servers: [{ url: "https://api.example.com" }],
954− components: {
955− schemas: {
956− Widget: {
957− type: "object",
958− properties: {
959− id: { type: "string" },
960− name: { type: "string" },
961− },
962− },
963− },
964− },
965− paths: {
966− "/widgets": {
967− get: {
968− operationId: "listWidgets",
969− tags: ["widgets"],
970− responses: {
971− "200": {
972− description: "ok",
973− content: {
974− "application/json": {
975− schema: {
976− type: "array",
977− items: { $ref: "#/components/schemas/Widget" },
978− },
979− },
980− },
981− },
982− },
983− },
984− post: {
985− operationId: "createWidget",
986− tags: ["widgets"],
987− requestBody: {
988− content: {
989− "application/json": {
990− schema: { $ref: "#/components/schemas/Widget" },
991− },
992− },
993− },
994− responses: {
995− "201": { description: "created" },
996− },
997− },
998− },
999− },
1000− };
1001846
1002− const prepared = await prepareOpenApiSpec(specWithSchemas, "schema-placement");
1003− const tools = buildOpenApiToolsFromPrepared(
1004− {
1005− type: "openapi",
1006− name: "widgets",
1007− spec: specWithSchemas,
1008− baseUrl: "https://api.example.com",
1009− },
1010− prepared,
1011− );
1012−
1013− expect(tools.length).toBe(2);
1014−
1015− // Only the first tool should have schemaTypes
1016− const withSchemas = tools.filter(
1017− (t) => t.metadata?.schemaTypes && Object.keys(t.metadata.schemaTypes).length > 0,
1018− );
1019− expect(withSchemas.length).toBeLessThanOrEqual(1);
1020− });
1021847 });
1022848
1023849 describe("prepared spec serialization round-trip", () => {
@@ -1035,20 +861,6 @@ describe("prepared spec serialization round-trip", () => { · 11 unmodified lines
1035861 );
1036862 expect(restored.warnings).toEqual(prepared.warnings);
1037863
1038− if (prepared.operationTypes) {
1039− expect(restored.operationTypes).toBeDefined();
1040− expect(Object.keys(restored.operationTypes!).length).toBe(
1041− Object.keys(prepared.operationTypes).length,
1042− );
1043− }
1044−
1045− if (prepared.schemaTypes) {
1046− expect(restored.schemaTypes).toBeDefined();
1047− expect(Object.keys(restored.schemaTypes!).length).toBe(
1048− Object.keys(prepared.schemaTypes).length,
1049− );
1050− }
1051−
1052864 // Verify tools built from the restored spec match the original
1053865 const config = {
1054866 type: "openapi" as const,
executor/lib/tool_sources.ts+1−28
@@ -852,10 +852,6 @@ export interface PreparedOpenApiSpec { · 851 unmodified lines
852852 paths: Record<string, unknown>;
853853 /** Raw .d.ts from openapiTS — cached as-is, extracted lazily on read */
854854 dts?: string;
855− /** @deprecated Kept for cache compat — new entries use `dts` instead */
856− operationTypes?: Record<string, { argsType: string; returnsType: string }>;
857− /** @deprecated Kept for cache compat — new entries use `dts` instead */
858− schemaTypes?: Record<string, string>;
859855 warnings: string[];
860856 }
861857
@@ -941,7 +937,7 @@ export function buildOpenApiToolsFromPrepared( · 79 unmodified lines
941937 const paths = asRecord(prepared.paths);
942938 const tools: ToolDefinition[] = [];
943939
944− // The raw .d.ts is attached to the first tool only (same pattern as old schemaTypes).
940+ // The raw .d.ts is attached to the first tool only (one per source to avoid duplication).
945941 // The typechecker/Monaco use this directly via indexed access types.
946942 const sourceDts = prepared.dts
947943 ? prepared.dts.replace(/^export /gm, "") // strip 'export' so types are ambient
@@ -1739,7 +1735,6 @@ export interface SerializedTool { · 791 unmodified lines
17391735 * MCP: { kind: "mcp", url, transport?, queryParams?, toolName }
17401736 * GraphQL raw: { kind: "graphql_raw", endpoint, authHeaders }
17411737 * GraphQL field: { kind: "graphql_field", endpoint, operationName, operationType, queryTemplate, argNames?, authHeaders }
1742− * GraphQL (legacy compat): { kind: "graphql", endpoint, operationName, operationType, authHeaders }
17431738 * Builtin: { kind: "builtin" } — run comes from DEFAULT_TOOLS
17441739 */
17451740 runSpec:
@@ -1772,13 +1767,6 @@ export interface SerializedTool { · 26 unmodified lines
17721767 argNames?: string[];
17731768 authHeaders: Record<string, string>;
17741769 }
1775− | {
1776− kind: "graphql";
1777− endpoint: string;
1778− operationName: string;
1779− operationType: "query" | "mutation";
1780− authHeaders: Record<string, string>;
1781− }
17821770 | { kind: "builtin" };
17831771 }
17841772
@@ -1976,21 +1964,6 @@ export function rehydrateTools( · 191 unmodified lines
19761964 };
19771965 }
19781966
1979− if (st.runSpec.kind === "graphql") {
1980− // Legacy compat for older cached entries.
1981− const { endpoint, operationName, operationType, authHeaders } = st.runSpec;
1982− const queryTemplate = `${operationType} ${operationName}($input: JSON) { ${operationName}(input: $input) }`;
1983− return {
1984− ...base,
1985− run: async (input: unknown, context) => {
1986− const payload = asRecord(input);
1987− const variables = payload.variables ?? { input: payload };
1988− const envelope = await executeGraphql(endpoint, authHeaders, queryTemplate, variables, context);
1989− return selectGraphqlFieldEnvelope(envelope, operationName);
1990− },
1991− };
1992− }
1993−
19941967 return { ...base, run: async () => { throw new Error(`Unknown run spec kind for '${st.path}'`); } };
19951968 });
19961969 }
executor/lib/typechecker.test.ts+0−93
@@ -424,97 +424,4 @@ export interface operations { · 423 unmodified lines
424424 });
425425 });
426426
427−// ---------------------------------------------------------------------------
428−// schemaTypes integration — verifies that schema type aliases are emitted and
429−// correctly used by the typechecker
430−// ---------------------------------------------------------------------------
431−
432−describe("typecheckCode with schemaTypes", () => {
433− const CUSTOMER_SCHEMA_TOOL: ToolDescriptor = {
434− path: "stripe.customers.create",
435− description: "Create a customer",
436− approval: "auto",
437− argsType: "{ name: string; email: string }",
438− returnsType: "Customer",
439− // Only the first tool from a source carries schemaTypes
440− schemaTypes: {
441− Customer: "{ id: string; name: string; email: string; subscriptions: Subscription[] }",
442− Subscription: "{ id: string; status: string; plan: Plan }",
443− Plan: "{ id: string; amount: number; currency: string }",
444− },
445− };
446−
447− const CUSTOMER_GET_TOOL: ToolDescriptor = {
448− path: "stripe.customers.get",
449− description: "Get a customer",
450− approval: "auto",
451− argsType: "{ id: string }",
452− returnsType: "Customer",
453− // Subsequent tools do NOT carry schemaTypes (deduplication)
454− };
455−
456− const STRIPE_TOOLS = [CUSTOMER_SCHEMA_TOOL, CUSTOMER_GET_TOOL];
457−
458− test("schema types are emitted in declarations", () => {
459− const result = generateToolDeclarations(STRIPE_TOOLS);
460− expect(result).toContain("type Customer =");
461− expect(result).toContain("type Subscription =");
462− expect(result).toContain("type Plan =");
463− // Declarations should come before the tools declaration
464− const schemaIdx = result.indexOf("type Customer =");
465− const toolsIdx = result.indexOf("declare const tools:");
466− expect(schemaIdx).toBeLessThan(toolsIdx);
467− });
468−
469− test("code using schema return types typechecks correctly", () => {
470− const decl = generateToolDeclarations(STRIPE_TOOLS);
471− const result = typecheckCode(
472− `const customer = await tools.stripe.customers.create({ name: "John", email: "john@test.com" });
473− const id: string = customer.id;
474− const subs: Subscription[] = customer.subscriptions;
475− const plan: Plan = subs[0].plan;
476− return plan.amount;`,
477− decl,
478− );
479− expect(result.ok).toBe(true);
480− expect(result.errors).toHaveLength(0);
481− });
482−
483− test("wrong property access on schema type produces errors", () => {
484− const decl = generateToolDeclarations(STRIPE_TOOLS);
485− const result = typecheckCode(
486− `const customer = await tools.stripe.customers.get({ id: "cus_123" });
487− return customer.nonexistent_field;`,
488− decl,
489− );
490− expect(result.ok).toBe(false);
491− expect(result.errors.some((e) => e.includes("nonexistent_field"))).toBe(true);
492− });
493427
494− test("transitive schema types are available (Subscription, Plan)", () => {
495− const decl = generateToolDeclarations(STRIPE_TOOLS);
496− const result = typecheckCode(
497− `const customer = await tools.stripe.customers.get({ id: "cus_123" });
498− const sub = customer.subscriptions[0];
499− const status: string = sub.status;
500− const amount: number = sub.plan.amount;
501− const currency: string = sub.plan.currency;
502− return { status, amount, currency };`,
503− decl,
504− );
505− expect(result.ok).toBe(true);
506− expect(result.errors).toHaveLength(0);
507− });
508−
509− test("wrong type on schema property produces errors", () => {
510− const decl = generateToolDeclarations(STRIPE_TOOLS);
511− const result = typecheckCode(
512− `const customer = await tools.stripe.customers.get({ id: "cus_123" });
513− const amount: string = customer.subscriptions[0].plan.amount;`,
514− decl,
515− );
516− expect(result.ok).toBe(false);
517− // amount is number, assigning to string should fail
518− expect(result.errors.some((e) => e.includes("number") || e.includes("string"))).toBe(true);
519− });
520−});
executor/lib/typechecker.ts+0−19
@@ -104,18 +104,6 @@ export function generateToolDeclarations( · 103 unmodified lines
104104 options?: GenerateToolDeclarationOptions,
105105 ): string {
106106
107− // Legacy compat: collect schemaTypes from tools that use the old format
108− const allSchemas = new Map<string, string>();
109− for (const tool of tools) {
110− if (tool.schemaTypes) {
111− for (const [name, type] of Object.entries(tool.schemaTypes)) {
112− if (!allSchemas.has(name)) {
113− allSchemas.set(name, type);
114− }
115− }
116− }
117− }
118−
119107 // Build a nested tree from flat tool paths
120108 interface TreeNode {
121109 children: Map<string, TreeNode>;
@@ -172,13 +160,6 @@ export function generateToolDeclarations( · 50 unmodified lines
172160 // Assemble the full declarations block
173161 const parts: string[] = [];
174162
175− // Legacy schema type aliases (from old cache format)
176− if (allSchemas.size > 0) {
177− for (const [name, type] of allSchemas) {
178− parts.push(`type ${name} = ${type};`);
179− }
180− }
181−
182163 const sourceDtsBySource = options?.sourceDtsBySource ?? {};
183164 const dtsEntries = Object.entries(sourceDtsBySource)
184165 .filter(([, dts]) => typeof dts === "string" && dts.trim().length > 0)
executor/lib/types.ts+122−24
@@ -1,25 +1,125 @@
1−// Re-export shared types from contracts
2−export type {
3− TaskStatus,
4− ApprovalStatus,
5− PolicyDecision,
6− CredentialScope,
7− CredentialProvider,
8− ToolApprovalMode,
9− ToolSourceType,
10− TaskRecord,
11− ApprovalRecord,
12− PendingApprovalRecord,
13− TaskEventRecord,
14− AccessPolicyRecord,
15− CredentialRecord,
16− ToolSourceRecord,
17− ToolDescriptor,
18− OpenApiSourceQuality,
19− AnonymousContext,
20−} from "@executor/contracts";
21−
22−import type { TaskStatus, CredentialScope, ToolApprovalMode } from "@executor/contracts";
1+// ── Shared types (inlined from @executor/contracts) ──────────────────────────
2+
3+export type TaskStatus = "queued" | "running" | "completed" | "failed" | "timed_out" | "denied";
4+export type ApprovalStatus = "pending" | "approved" | "denied";
5+export type PolicyDecision = "allow" | "require_approval" | "deny";
6+export type CredentialScope = "workspace" | "actor";
7+export type CredentialProvider = "managed" | "workos-vault";
8+export type ToolApprovalMode = "auto" | "required";
9+export type ToolSourceType = "mcp" | "openapi" | "graphql";
10+
11+export interface TaskRecord {
12+ id: string;
13+ code: string;
14+ runtimeId: string;
15+ status: TaskStatus;
16+ timeoutMs: number;
17+ metadata: Record<string, unknown>;
18+ workspaceId: string;
19+ actorId?: string;
20+ clientId?: string;
21+ createdAt: number;
22+ updatedAt: number;
23+ startedAt?: number;
24+ completedAt?: number;
25+ error?: string;
26+ stdout?: string;
27+ stderr?: string;
28+ exitCode?: number;
29+}
30+
31+export interface ApprovalRecord {
32+ id: string;
33+ taskId: string;
34+ toolPath: string;
35+ input: unknown;
36+ status: ApprovalStatus;
37+ reason?: string;
38+ reviewerId?: string;
39+ createdAt: number;
40+ resolvedAt?: number;
41+}
42+
43+export interface PendingApprovalRecord extends ApprovalRecord {
44+ task: Pick<TaskRecord, "id" | "status" | "runtimeId" | "timeoutMs" | "createdAt">;
45+}
46+
47+export interface TaskEventRecord {
48+ id: number;
49+ taskId: string;
50+ eventName: string;
51+ type: string;
52+ payload: unknown;
53+ createdAt: number;
54+}
55+
56+export interface AccessPolicyRecord {
57+ id: string;
58+ workspaceId: string;
59+ actorId?: string;
60+ clientId?: string;
61+ toolPathPattern: string;
62+ decision: PolicyDecision;
63+ priority: number;
64+ createdAt: number;
65+ updatedAt: number;
66+}
67+
68+export interface CredentialRecord {
69+ id: string;
70+ workspaceId: string;
71+ sourceKey: string;
72+ scope: CredentialScope;
73+ actorId?: string;
74+ provider: CredentialProvider;
75+ secretJson: Record<string, unknown>;
76+ createdAt: number;
77+ updatedAt: number;
78+}
79+
80+export interface ToolSourceRecord {
81+ id: string;
82+ workspaceId: string;
83+ name: string;
84+ type: ToolSourceType;
85+ config: Record<string, unknown>;
86+ enabled: boolean;
87+ createdAt: number;
88+ updatedAt: number;
89+}
90+
91+export interface ToolDescriptor {
92+ path: string;
93+ description: string;
94+ approval: ToolApprovalMode;
95+ source?: string;
96+ argsType?: string;
97+ returnsType?: string;
98+ operationId?: string;
99+}
100+
101+export interface OpenApiSourceQuality {
102+ sourceKey: string;
103+ toolCount: number;
104+ unknownArgsCount: number;
105+ unknownReturnsCount: number;
106+ partialUnknownArgsCount: number;
107+ partialUnknownReturnsCount: number;
108+ argsQuality: number;
109+ returnsQuality: number;
110+ overallQuality: number;
111+}
112+
113+export interface AnonymousContext {
114+ sessionId: string;
115+ workspaceId: string;
116+ actorId: string;
117+ clientId: string;
118+ accountId: string;
119+ userId: string;
120+ createdAt: number;
121+ lastSeenAt: number;
122+}
23123
24124 // ── Server-only types ─────────────────────────────────────────────────────────
25125
@@ -120,8 +220,6 @@ export interface ToolTypeMetadata { · 94 unmodified lines
120220 * Only set on the FIRST tool per source to avoid duplication.
121221 */
122222 sourceDts?: string;
123− /** @deprecated Use sourceDts instead. Kept for cache compat. */
124− schemaTypes?: Record<string, string>;
125223 }
126224
127225 export interface ToolDefinition {
executor/lib/workspace-tool-cache.test.ts+37−17
@@ -5,6 +5,7 @@ · 4 unmodified lines
55 * round-trip with functional `run` methods.
66 */
77 import { test, expect, describe } from "bun:test";
8+import type { Id } from "../convex/_generated/dataModel";
89 import {
910 prepareOpenApiSpec,
1011 buildOpenApiToolsFromPrepared,
@@ -464,8 +465,7 @@ describe("serializeTools + rehydrateTools round-trip", () => { · 453 unmodified lines
464465 });
465466
466467 describe("workspace tool cache table", () => {
467− // convex-test based tests for the table operations
468− test("getEntry + putEntry round-trip", async () => {
468+ async function setupCacheTest() {
469469 const { convexTest } = await import("convex-test");
470470 const { internal } = await import("../convex/_generated/api");
471471 const schema = (await import("../convex/schema")).default;
@@ -475,9 +475,33 @@ describe("workspace tool cache table", () => { · 3 unmodified lines
475475 "./_generated/api.js": () => import("../convex/_generated/api.js"),
476476 });
477477
478+ const wsId = await t.run(async (ctx) => {
479+ const orgId = await ctx.db.insert("organizations", {
480+ name: "test-org",
481+ slug: "test-org",
482+ status: "active" as const,
483+ createdAt: Date.now(),
484+ updatedAt: Date.now(),
485+ });
486+ return await ctx.db.insert("workspaces", {
487+ name: "test-ws",
488+ slug: "test-ws",
489+ organizationId: orgId,
490+ plan: "free",
491+ createdAt: Date.now(),
492+ updatedAt: Date.now(),
493+ });
494+ });
495+
496+ return { t, internal, wsId };
497+ }
498+
499+ test("getEntry + putEntry round-trip", async () => {
500+ const { t, internal, wsId } = await setupCacheTest();
501+
478502 // Empty cache
479503 const miss = await t.query(internal.workspaceToolCache.getEntry, {
480− workspaceId: "ws_1",
504+ workspaceId: wsId,
481505 signature: "sig_1",
482506 });
483507 expect(miss).toBeNull();
@@ -489,16 +513,17 @@ describe("workspace tool cache table", () => { · 5 unmodified lines
489513 });
490514
491515 await t.mutation(internal.workspaceToolCache.putEntry, {
492− workspaceId: "ws_1",
516+ workspaceId: wsId,
493517 signature: "sig_1",
494518 storageId,
495519 toolCount: 0,
496520 sizeBytes: 27,
521+ dtsStorageIds: [],
497522 });
498523
499524 // Hit
500525 const hit = await t.query(internal.workspaceToolCache.getEntry, {
501− workspaceId: "ws_1",
526+ workspaceId: wsId,
502527 signature: "sig_1",
503528 });
504529 expect(hit).not.toBeNull();
@@ -506,32 +531,26 @@ describe("workspace tool cache table", () => { · 1 unmodified line
506531
507532 // Wrong signature = miss
508533 const wrongSig = await t.query(internal.workspaceToolCache.getEntry, {
509− workspaceId: "ws_1",
534+ workspaceId: wsId,
510535 signature: "sig_2",
511536 });
512537 expect(wrongSig).toBeNull();
513538 });
514539
515540 test("putEntry replaces old entry and deletes old blob", async () => {
516− const { convexTest } = await import("convex-test");
517− const { internal } = await import("../convex/_generated/api");
518− const schema = (await import("../convex/schema")).default;
519−
520− const t = convexTest(schema, {
521− "./workspaceToolCache.ts": () => import("../convex/workspaceToolCache"),
522− "./_generated/api.js": () => import("../convex/_generated/api.js"),
523− });
541+ const { t, internal, wsId } = await setupCacheTest();
524542
525543 const storageId1 = await t.run(async (ctx) => {
526544 return await ctx.storage.store(new Blob(["old"]));
527545 });
528546
529547 await t.mutation(internal.workspaceToolCache.putEntry, {
530− workspaceId: "ws_1",
548+ workspaceId: wsId,
531549 signature: "sig_1",
532550 storageId: storageId1,
533551 toolCount: 5,
534552 sizeBytes: 3,
553+ dtsStorageIds: [],
535554 });
536555
537556 const storageId2 = await t.run(async (ctx) => {
@@ -539,16 +558,17 @@ describe("workspace tool cache table", () => { · 1 unmodified line
539558 });
540559
541560 await t.mutation(internal.workspaceToolCache.putEntry, {
542− workspaceId: "ws_1",
561+ workspaceId: wsId,
543562 signature: "sig_2",
544563 storageId: storageId2,
545564 toolCount: 10,
546565 sizeBytes: 3,
566+ dtsStorageIds: [],
547567 });
548568
549569 // New entry
550570 const entry = await t.query(internal.workspaceToolCache.getEntry, {
551− workspaceId: "ws_1",
571+ workspaceId: wsId,
552572 signature: "sig_2",
553573 });
554574 expect(entry!.storageId).toBe(storageId2);
executor/mcp-gateway.ts+7−6
@@ -3,6 +3,7 @@ import { createRemoteJWKSet, jwtVerify } from "jose"; · 2 unmodified lines
33 import { api } from "./convex/_generated/api";
44 import { handleMcpRequest, type McpWorkspaceContext } from "./lib/mcp_server";
55 import type { AnonymousContext, PendingApprovalRecord, TaskRecord, ToolDescriptor } from "./lib/types";
6+import type { Id } from "./convex/_generated/dataModel";
67
78 const convexUrlFromEnv = Bun.env.CONVEX_URL;
89 if (!convexUrlFromEnv) {
@@ -126,7 +127,7 @@ function createService(context?: McpWorkspaceContext, bearerToken?: string) { · 117 unmodified lines
126127 clientId?: string;
127128 }) => {
128129 const created = await convex.mutation(api.executor.createTask, {
129− workspaceId: input.workspaceId,
130+ workspaceId: input.workspaceId as Id<"workspaces">,
130131 sessionId,
131132 code: input.code,
132133 timeoutMs: input.timeoutMs,
@@ -144,7 +145,7 @@ function createService(context?: McpWorkspaceContext, bearerToken?: string) { · 11 unmodified lines
144145 return null;
145146 }
146147 const task = await convex.query(api.workspace.getTaskInWorkspace, {
147− workspaceId: effectiveWorkspaceId,
148+ workspaceId: effectiveWorkspaceId as Id<"workspaces">,
148149 sessionId,
149150 taskId,
150151 });
@@ -165,7 +166,7 @@ function createService(context?: McpWorkspaceContext, bearerToken?: string) { · 14 unmodified lines
165166 return [];
166167 }
167168 const tools = await convex.action(api.executorNode.listTools, {
168− workspaceId: toolContext.workspaceId,
169+ workspaceId: toolContext.workspaceId as Id<"workspaces">,
169170 sessionId,
170171 actorId: toolContext.actorId,
171172 clientId: toolContext.clientId,
@@ -175,7 +176,7 @@ function createService(context?: McpWorkspaceContext, bearerToken?: string) { · 3 unmodified lines
175176
176177 listPendingApprovals: async (approvalWorkspaceId: string) => {
177178 const approvals = await convex.query(api.workspace.listPendingApprovals, {
178− workspaceId: approvalWorkspaceId,
179+ workspaceId: approvalWorkspaceId as Id<"workspaces">,
179180 sessionId,
180181 });
181182 return approvals as PendingApprovalRecord[];
@@ -189,7 +190,7 @@ function createService(context?: McpWorkspaceContext, bearerToken?: string) { · 7 unmodified lines
189190 reason?: string;
190191 }) => {
191192 return await convex.mutation(api.executor.resolveApproval, {
192− workspaceId: input.workspaceId,
193+ workspaceId: input.workspaceId as Id<"workspaces">,
193194 sessionId,
194195 approvalId: input.approvalId,
195196 decision: input.decision,
@@ -232,7 +233,7 @@ async function resolveContext( · 36 unmodified lines
232233 }
233234
234235 const requestContext = await convex.query(api.workspace.getRequestContext, {
235− workspaceId: requested.workspaceId,
236+ workspaceId: requested.workspaceId as Id<"workspaces">,
236237 sessionId: requested.sessionId,
237238 });
238239
executor/packages/contracts/package.json+0−7
@@ -1,7 +0,0 @@
1−{
2− "name": "@executor/contracts",
3− "version": "0.0.1",
4− "private": true,
5− "main": "src/index.ts",
6− "types": "src/index.ts"
7−}
executor/packages/contracts/src/index.ts+0−143
@@ -1,143 +0,0 @@
1−// ── Status enums ──────────────────────────────────────────────────────────────
2−
3−export type TaskStatus =
4− | "queued"
5− | "running"
6− | "completed"
7− | "failed"
8− | "timed_out"
9− | "denied";
10−
11−export type ApprovalStatus = "pending" | "approved" | "denied";
12−
13−export type PolicyDecision = "allow" | "require_approval" | "deny";
14−
15−export type CredentialScope = "workspace" | "actor";
16−
17−export type CredentialProvider = "managed" | "workos-vault";
18−
19−export type ToolApprovalMode = "auto" | "required";
20−
21−export type ToolSourceType = "mcp" | "openapi" | "graphql";
22−
23−// ── Records ───────────────────────────────────────────────────────────────────
24−
25−export interface TaskRecord {
26− id: string;
27− code: string;
28− runtimeId: string;
29− status: TaskStatus;
30− timeoutMs: number;
31− metadata: Record<string, unknown>;
32− workspaceId: string;
33− actorId?: string;
34− clientId?: string;
35− createdAt: number;
36− updatedAt: number;
37− startedAt?: number;
38− completedAt?: number;
39− error?: string;
40− stdout?: string;
41− stderr?: string;
42− exitCode?: number;
43−}
44−
45−export interface ApprovalRecord {
46− id: string;
47− taskId: string;
48− toolPath: string;
49− input: unknown;
50− status: ApprovalStatus;
51− reason?: string;
52− reviewerId?: string;
53− createdAt: number;
54− resolvedAt?: number;
55−}
56−
57−export interface PendingApprovalRecord extends ApprovalRecord {
58− task: Pick<TaskRecord, "id" | "status" | "runtimeId" | "timeoutMs" | "createdAt">;
59−}
60−
61−export interface TaskEventRecord {
62− id: number;
63− taskId: string;
64− eventName: string;
65− type: string;
66− payload: unknown;
67− createdAt: number;
68−}
69−
70−export interface AccessPolicyRecord {
71− id: string;
72− workspaceId: string;
73− actorId?: string;
74− clientId?: string;
75− toolPathPattern: string;
76− decision: PolicyDecision;
77− priority: number;
78− createdAt: number;
79− updatedAt: number;
80−}
81−
82−export interface CredentialRecord {
83− id: string;
84− workspaceId: string;
85− sourceKey: string;
86− scope: CredentialScope;
87− actorId?: string;
88− provider: CredentialProvider;
89− secretJson: Record<string, unknown>;
90− createdAt: number;
91− updatedAt: number;
92−}
93−
94−export interface ToolSourceRecord {
95− id: string;
96− workspaceId: string;
97− name: string;
98− type: ToolSourceType;
99− config: Record<string, unknown>;
100− enabled: boolean;
101− createdAt: number;
102− updatedAt: number;
103−}
104−
105−// ── Descriptors ───────────────────────────────────────────────────────────────
106−
107−export interface ToolDescriptor {
108− path: string;
109− description: string;
110− approval: ToolApprovalMode;
111− source?: string;
112− /** Lightweight TS type hint for args (for LLM prompt / discover tool). */
113− argsType?: string;
114− /** Lightweight TS type hint for return value (for LLM prompt / discover tool). */
115− returnsType?: string;
116− /** Raw operationId from the OpenAPI spec (used by Monaco for indexed access types). */
117− operationId?: string;
118− /** @deprecated Kept for cache compat. */
119− schemaTypes?: Record<string, string>;
120−}
121−
122−export interface OpenApiSourceQuality {
123− sourceKey: string;
124− toolCount: number;
125− unknownArgsCount: number;
126− unknownReturnsCount: number;
127− partialUnknownArgsCount: number;
128− partialUnknownReturnsCount: number;
129− argsQuality: number;
130− returnsQuality: number;
131− overallQuality: number;
132−}
133−
134−export interface AnonymousContext {
135− sessionId: string;
136− workspaceId: string;
137− actorId: string;
138− clientId: string;
139− accountId?: string;
140− userId?: string;
141− createdAt: number;
142− lastSeenAt: number;
143−}
executor/packages/contracts/tsconfig.json+0−11
@@ -1,11 +0,0 @@
1−{
2− "compilerOptions": {
3− "strict": true,
4− "moduleResolution": "bundler",
5− "target": "ESNext",
6− "module": "ESNext",
7− "noEmit": true,
8− "skipLibCheck": true
9− },
10− "include": ["src/**/*.ts"]
11−}
package.json+1−1
@@ -3,6 +3,7 @@ · 2 unmodified lines
33 "private": true,
44 "type": "module",
55 "workspaces": [
6+ "executor/convex",
67 "executor/apps/*",
78 "executor/packages/*",
89 "assistant",
@@ -32,7 +33,6 @@ · 23 unmodified lines
3233 "@convex-dev/migrations": "^0.3.1",
3334 "@convex-dev/stripe": "^0.1.3",
3435 "@convex-dev/workos-authkit": "^0.1.6",
35− "@executor/contracts": "workspace:*",
3636 "@workos-inc/node": "^8.2.0",
3737 "convex": "^1.31.7",
3838 "convex-helpers": "^0.1.111",

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