- 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
Unified · Split
| @@ -9,7 +9,6 @@ · 8 unmodified lines | |||
| 9 | "@convex-dev/migrations": "^0.3.1", | 9 | "@convex-dev/migrations": "^0.3.1", |
| 10 | "@convex-dev/stripe": "^0.1.3", | 10 | "@convex-dev/stripe": "^0.1.3", |
| 11 | "@convex-dev/workos-authkit": "^0.1.6", | 11 | "@convex-dev/workos-authkit": "^0.1.6", |
| 12 | "@executor/contracts": "workspace:*", | ||
| 13 | "@workos-inc/node": "^8.2.0", | 12 | "@workos-inc/node": "^8.2.0", |
| 14 | "convex": "^1.31.7", | 13 | "convex": "^1.31.7", |
| 15 | "convex-helpers": "^0.1.111", | 14 | "convex-helpers": "^0.1.111", |
| @@ -91,7 +90,7 @@ · 75 unmodified lines | |||
| 91 | "dependencies": { | 90 | "dependencies": { |
| 92 | "@convex-dev/workos": "^0.0.1", | 91 | "@convex-dev/workos": "^0.0.1", |
| 93 | "@elysiajs/eden": "^1.4.6", | 92 | "@elysiajs/eden": "^1.4.6", |
| 94 | "@executor/contracts": "workspace:*", | 93 | "@executor/convex": "workspace:*", |
| 95 | "@monaco-editor/react": "^4.7.0", | 94 | "@monaco-editor/react": "^4.7.0", |
| 96 | "@streamdown/code": "^1.0.1", | 95 | "@streamdown/code": "^1.0.1", |
| 97 | "@tanstack/react-query": "^5.80.7", | 96 | "@tanstack/react-query": "^5.80.7", |
| @@ -127,8 +126,8 @@ · 29 unmodified lines | |||
| 127 | "typescript": "^5", | 126 | "typescript": "^5", |
| 128 | }, | 127 | }, |
| 129 | }, | 128 | }, |
| 130 | "executor/packages/contracts": { | 129 | "executor/convex": { |
| 131 | "name": "@executor/contracts", | 130 | "name": "@executor/convex", |
| 132 | "version": "0.0.1", | 131 | "version": "0.0.1", |
| 133 | }, | 132 | }, |
| 134 | }, | 133 | }, |
| @@ -399,7 +398,7 @@ · 264 unmodified lines | |||
| 399 | 398 | ||
| 400 | "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], | 399 | "@eslint/plugin-kit": ["@eslint/plugin-kit@0.4.1", "", { "dependencies": { "@eslint/core": "^0.17.0", "levn": "^0.4.1" } }, "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA=="], |
| 401 | 400 | ||
| 402 | "@executor/contracts": ["@executor/contracts@workspace:executor/packages/contracts"], | 401 | "@executor/convex": ["@executor/convex@workspace:executor/convex"], |
| 403 | 402 | ||
| 404 | "@executor/web": ["@executor/web@workspace:executor/apps/web"], | 403 | "@executor/web": ["@executor/web@workspace:executor/apps/web"], |
| 405 | 404 | ||
| @@ -13,7 +13,7 @@ const appShellRewriteExclusions = [ · 12 unmodified lines | |||
| 13 | ].join("|"); | 13 | ].join("|"); |
| 14 | 14 | ||
| 15 | const nextConfig: NextConfig = { | 15 | const nextConfig: NextConfig = { |
| 16 | transpilePackages: ["@executor/contracts"], | 16 | transpilePackages: ["@executor/convex"], |
| 17 | env: { | 17 | env: { |
| 18 | // Map canonical env vars to NEXT_PUBLIC_ so they're available client-side. | 18 | // Map canonical env vars to NEXT_PUBLIC_ so they're available client-side. |
| 19 | // This lets us keep a single root .env without NEXT_PUBLIC_ prefixes. | 19 | // This lets us keep a single root .env without NEXT_PUBLIC_ prefixes. |
| @@ -11,8 +11,8 @@ · 10 unmodified lines | |||
| 11 | }, | 11 | }, |
| 12 | "dependencies": { | 12 | "dependencies": { |
| 13 | "@convex-dev/workos": "^0.0.1", | 13 | "@convex-dev/workos": "^0.0.1", |
| 14 | "@executor/convex": "workspace:*", | ||
| 14 | "@elysiajs/eden": "^1.4.6", | 15 | "@elysiajs/eden": "^1.4.6", |
| 15 | "@executor/contracts": "workspace:*", | ||
| 16 | "@monaco-editor/react": "^4.7.0", | 16 | "@monaco-editor/react": "^4.7.0", |
| 17 | "@streamdown/code": "^1.0.1", | 17 | "@streamdown/code": "^1.0.1", |
| 18 | "@tanstack/react-query": "^5.80.7", | 18 | "@tanstack/react-query": "^5.80.7", |
| @@ -1,8 +1,8 @@ | |||
| 1 | import type { NextRequest } from "next/server"; | 1 | import type { NextRequest } from "next/server"; |
| 2 | import { redirect } from "next/navigation"; | 2 | import { redirect } from "next/navigation"; |
| 3 | import { ConvexHttpClient } from "convex/browser"; | 3 | import { ConvexHttpClient } from "convex/browser"; |
| 4 | import { api } from "../../../../../convex/_generated/api"; | 4 | import { api } from "@executor/convex/_generated/api"; |
| 5 | import type { Id } from "../../../../../convex/_generated/dataModel"; | 5 | import type { Id } from "@executor/convex/_generated/dataModel"; |
| 6 | 6 | ||
| 7 | function getExternalOrigin(request: NextRequest) { | 7 | function getExternalOrigin(request: NextRequest) { |
| 8 | const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host"); | 8 | const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host"); |
| @@ -1,8 +1,8 @@ | |||
| 1 | import type { NextRequest } from "next/server"; | 1 | import type { NextRequest } from "next/server"; |
| 2 | import { redirect } from "next/navigation"; | 2 | import { redirect } from "next/navigation"; |
| 3 | import { ConvexHttpClient } from "convex/browser"; | 3 | import { ConvexHttpClient } from "convex/browser"; |
| 4 | import { api } from "../../../../../convex/_generated/api"; | 4 | import { api } from "@executor/convex/_generated/api"; |
| 5 | import type { Id } from "../../../../../convex/_generated/dataModel"; | 5 | import type { Id } from "@executor/convex/_generated/dataModel"; |
| 6 | 6 | ||
| 7 | function getExternalOrigin(request: NextRequest) { | 7 | function getExternalOrigin(request: NextRequest) { |
| 8 | const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host"); | 8 | const host = request.headers.get("x-forwarded-host") ?? request.headers.get("host"); |
| @@ -131,17 +131,6 @@ type ToolOutput<Op> = · 130 unmodified lines | |||
| 131 | function generateToolsDts(tools: ToolDescriptor[], dtsSources: Set<string>): string { | 131 | function generateToolsDts(tools: ToolDescriptor[], dtsSources: Set<string>): string { |
| 132 | const root = buildTree(tools); | 132 | const root = buildTree(tools); |
| 133 | 133 | ||
| 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 | |||
| 145 | const interfaces: string[] = []; | 134 | const interfaces: string[] = []; |
| 146 | 135 | ||
| 147 | // Emit all namespace interfaces recursively | 136 | // Emit all namespace interfaces recursively |
| @@ -170,11 +159,6 @@ function generateToolsDts(tools: ToolDescriptor[], dtsSources: Set<string>): str · 22 unmodified lines | |||
| 170 | */ | 159 | */ |
| 171 | `; | 160 | `; |
| 172 | 161 | ||
| 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 | |||
| 178 | // Note: .d.ts blocks from OpenAPI sources are fetched separately and registered | 162 | // Note: .d.ts blocks from OpenAPI sources are fetched separately and registered |
| 179 | // as a distinct Monaco extra lib (see dtsUrls effect). This keeps generateToolsDts | 163 | // as a distinct Monaco extra lib (see dtsUrls effect). This keeps generateToolsDts |
| 180 | // fast and avoids bundling multi-MB .d.ts content into the tool declarations string. | 164 | // fast and avoids bundling multi-MB .d.ts content into the tool declarations string. |
| @@ -9,7 +9,7 @@ import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; · 8 unmodified lines | |||
| 9 | import { Input } from "@/components/ui/input"; | 9 | import { Input } from "@/components/ui/input"; |
| 10 | import { useSession } from "@/lib/session-context"; | 10 | import { useSession } from "@/lib/session-context"; |
| 11 | import { convexApi } from "@/lib/convex-api"; | 11 | import { convexApi } from "@/lib/convex-api"; |
| 12 | import type { Id } from "../../../../convex/_generated/dataModel"; | 12 | import type { Id } from "@executor/convex/_generated/dataModel"; |
| 13 | 13 | ||
| 14 | type Role = "owner" | "admin" | "member" | "billing_admin"; | 14 | type Role = "owner" | "admin" | "member" | "billing_admin"; |
| 15 | type MemberStatus = "active" | "pending" | "removed"; | 15 | type MemberStatus = "active" | "pending" | "removed"; |
| @@ -64,7 +64,7 @@ export function MembersView({ showHeader = true }: MembersViewProps) { · 48 unmodified lines | |||
| 64 | 64 | ||
| 65 | const memberItems: OrganizationMemberListItem[] = members?.items ?? []; | 65 | const memberItems: OrganizationMemberListItem[] = members?.items ?? []; |
| 66 | const actorMembership = memberItems.find((member: { accountId?: string; role?: string }) => | 66 | const actorMembership = memberItems.find((member: { accountId?: string; role?: string }) => |
| 67 | context?.accountId ? member.accountId === context.accountId : false, | 67 | context ? member.accountId === context.accountId : false, |
| 68 | ); | 68 | ); |
| 69 | const actorRole = actorMembership?.role ?? null; | 69 | const actorRole = actorMembership?.role ?? null; |
| 70 | const canManageMembers = actorRole === "owner" || actorRole === "admin"; | 70 | const canManageMembers = actorRole === "owner" || actorRole === "admin"; |
| @@ -34,6 +34,7 @@ import type { · 33 unmodified lines | |||
| 34 | TaskEventRecord, | 34 | TaskEventRecord, |
| 35 | TaskRecord, | 35 | TaskRecord, |
| 36 | } from "@/lib/types"; | 36 | } from "@/lib/types"; |
| 37 | import type { Id } from "@executor/convex/_generated/dataModel"; | ||
| 37 | import { toast } from "sonner"; | 38 | import { toast } from "sonner"; |
| 38 | import { cn } from "@/lib/utils"; | 39 | import { cn } from "@/lib/utils"; |
| 39 | 40 | ||
| @@ -210,7 +211,7 @@ function TaskDetail({ · 170 unmodified lines | |||
| 210 | onClose, | 211 | onClose, |
| 211 | }: { | 212 | }: { |
| 212 | task: TaskRecord; | 213 | task: TaskRecord; |
| 213 | workspaceId: string; | 214 | workspaceId: Id<"workspaces">; |
| 214 | sessionId?: string; | 215 | sessionId?: string; |
| 215 | onClose: () => void; | 216 | onClose: () => void; |
| 216 | }) { | 217 | }) { |
| @@ -4,9 +4,10 @@ import { useQuery as useTanstackQuery } from "@tanstack/react-query"; · 3 unmodified lines | |||
| 4 | import { useAction, useQuery as useConvexQuery } from "convex/react"; | 4 | import { useAction, useQuery as useConvexQuery } from "convex/react"; |
| 5 | import { convexApi } from "@/lib/convex-api"; | 5 | import { convexApi } from "@/lib/convex-api"; |
| 6 | import type { OpenApiSourceQuality, ToolDescriptor } from "@/lib/types"; | 6 | import type { OpenApiSourceQuality, ToolDescriptor } from "@/lib/types"; |
| 7 | import type { Id } from "@executor/convex/_generated/dataModel"; | ||
| 7 | 8 | ||
| 8 | interface WorkspaceContext { | 9 | interface WorkspaceContext { |
| 9 | workspaceId: string; | 10 | workspaceId: Id<"workspaces">; |
| 10 | actorId?: string; | 11 | actorId?: string; |
| 11 | clientId?: string; | 12 | clientId?: string; |
| 12 | sessionId?: string; | 13 | sessionId?: string; |
| @@ -1,3 +1,3 @@ | |||
| 1 | import { api } from "../../../../convex/_generated/api"; | 1 | import { api } from "@executor/convex/_generated/api"; |
| 2 | 2 | ||
| 3 | export const convexApi = api; | 3 | export const convexApi = api; |
| @@ -14,7 +14,7 @@ import { anonymousDemoEnabled, workosEnabled } from "@/lib/auth-capabilities"; · 13 unmodified lines | |||
| 14 | import { useWorkosAuthLoading } from "@/lib/convex-provider"; | 14 | import { useWorkosAuthLoading } from "@/lib/convex-provider"; |
| 15 | import { convexApi } from "@/lib/convex-api"; | 15 | import { convexApi } from "@/lib/convex-api"; |
| 16 | import type { AnonymousContext } from "./types"; | 16 | import type { AnonymousContext } from "./types"; |
| 17 | import type { Id } from "../../../../convex/_generated/dataModel"; | 17 | import type { Id } from "@executor/convex/_generated/dataModel"; |
| 18 | 18 | ||
| 19 | interface SessionState { | 19 | interface SessionState { |
| 20 | context: AnonymousContext | null; | 20 | context: AnonymousContext | null; |
| @@ -1,27 +1,129 @@ | |||
| 1 | // Re-export shared types from contracts | 1 | import type { Id } from "@executor/convex/_generated/dataModel"; |
| 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 | 2 | ||
| 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 | } | ||
| 23 | 125 | ||
| 24 | import type { TaskStatus, ApprovalStatus, CredentialScope } from "@executor/contracts"; | 126 | // ── Web-only types ──────────────────────────────────────────────────────────── |
| 25 | 127 | ||
| 26 | export type ApprovalDecision = "approved" | "denied"; | 128 | export type ApprovalDecision = "approved" | "denied"; |
| 27 | 129 | ||
| @@ -93,7 +93,7 @@ export declare const api: { · 92 unmodified lines | |||
| 93 | secretJson: any; | 93 | secretJson: any; |
| 94 | sessionId?: string; | 94 | sessionId?: string; |
| 95 | sourceKey: string; | 95 | sourceKey: string; |
| 96 | workspaceId: string; | 96 | workspaceId: Id<"workspaces">; |
| 97 | }, | 97 | }, |
| 98 | any | 98 | any |
| 99 | >; | 99 | >; |
| @@ -107,7 +107,7 @@ export declare const api: { · 7 unmodified lines | |||
| 107 | id: string; | 107 | id: string; |
| 108 | prompt: string; | 108 | prompt: string; |
| 109 | requesterId: string; | 109 | requesterId: string; |
| 110 | workspaceId: string; | 110 | workspaceId: Id<"workspaces">; |
| 111 | }, | 111 | }, |
| 112 | any | 112 | any |
| 113 | >; | 113 | >; |
| @@ -142,7 +142,7 @@ export declare const api: { · 28 unmodified lines | |||
| 142 | runtimeId?: string; | 142 | runtimeId?: string; |
| 143 | sessionId?: string; | 143 | sessionId?: string; |
| 144 | timeoutMs?: number; | 144 | timeoutMs?: number; |
| 145 | workspaceId: string; | 145 | workspaceId: Id<"workspaces">; |
| 146 | }, | 146 | }, |
| 147 | any | 147 | any |
| 148 | >; | 148 | >; |
| @@ -155,7 +155,7 @@ export declare const api: { · 6 unmodified lines | |||
| 155 | reason?: string; | 155 | reason?: string; |
| 156 | reviewerId?: string; | 156 | reviewerId?: string; |
| 157 | sessionId?: string; | 157 | sessionId?: string; |
| 158 | workspaceId: string; | 158 | workspaceId: Id<"workspaces">; |
| 159 | }, | 159 | }, |
| 160 | any | 160 | any |
| 161 | >; | 161 | >; |
| @@ -168,7 +168,7 @@ export declare const api: { · 6 unmodified lines | |||
| 168 | actorId?: string; | 168 | actorId?: string; |
| 169 | clientId?: string; | 169 | clientId?: string; |
| 170 | sessionId?: string; | 170 | sessionId?: string; |
| 171 | workspaceId: string; | 171 | workspaceId: Id<"workspaces">; |
| 172 | }, | 172 | }, |
| 173 | any | 173 | any |
| 174 | >; | 174 | >; |
| @@ -179,7 +179,7 @@ export declare const api: { · 4 unmodified lines | |||
| 179 | actorId?: string; | 179 | actorId?: string; |
| 180 | clientId?: string; | 180 | clientId?: string; |
| 181 | sessionId?: string; | 181 | sessionId?: string; |
| 182 | workspaceId: string; | 182 | workspaceId: Id<"workspaces">; |
| 183 | }, | 183 | }, |
| 184 | any | 184 | any |
| 185 | >; | 185 | >; |
| @@ -292,31 +292,31 @@ export declare const api: { · 106 unmodified lines | |||
| 292 | deleteToolSource: FunctionReference< | 292 | deleteToolSource: FunctionReference< |
| 293 | "mutation", | 293 | "mutation", |
| 294 | "public", | 294 | "public", |
| 295 | { sessionId?: string; sourceId: string; workspaceId: string }, | 295 | { sessionId?: string; sourceId: string; workspaceId: Id<"workspaces"> }, |
| 296 | any | 296 | any |
| 297 | >; | 297 | >; |
| 298 | getRequestContext: FunctionReference< | 298 | getRequestContext: FunctionReference< |
| 299 | "query", | 299 | "query", |
| 300 | "public", | 300 | "public", |
| 301 | { sessionId?: string; workspaceId: string }, | 301 | { sessionId?: string; workspaceId: Id<"workspaces"> }, |
| 302 | any | 302 | any |
| 303 | >; | 303 | >; |
| 304 | getTask: FunctionReference< | 304 | getTask: FunctionReference< |
| 305 | "query", | 305 | "query", |
| 306 | "public", | 306 | "public", |
| 307 | { sessionId?: string; taskId: string; workspaceId: string }, | 307 | { sessionId?: string; taskId: string; workspaceId: Id<"workspaces"> }, |
| 308 | any | 308 | any |
| 309 | >; | 309 | >; |
| 310 | getTaskInWorkspace: FunctionReference< | 310 | getTaskInWorkspace: FunctionReference< |
| 311 | "query", | 311 | "query", |
| 312 | "public", | 312 | "public", |
| 313 | { sessionId?: string; taskId: string; workspaceId: string }, | 313 | { sessionId?: string; taskId: string; workspaceId: Id<"workspaces"> }, |
| 314 | any | 314 | any |
| 315 | >; | 315 | >; |
| 316 | listAccessPolicies: FunctionReference< | 316 | listAccessPolicies: FunctionReference< |
| 317 | "query", | 317 | "query", |
| 318 | "public", | 318 | "public", |
| 319 | { sessionId?: string; workspaceId: string }, | 319 | { sessionId?: string; workspaceId: Id<"workspaces"> }, |
| 320 | any | 320 | any |
| 321 | >; | 321 | >; |
| 322 | listApprovals: FunctionReference< | 322 | listApprovals: FunctionReference< |
| @@ -325,7 +325,7 @@ export declare const api: { · 2 unmodified lines | |||
| 325 | { | 325 | { |
| 326 | sessionId?: string; | 326 | sessionId?: string; |
| 327 | status?: "pending" | "approved" | "denied"; | 327 | status?: "pending" | "approved" | "denied"; |
| 328 | workspaceId: string; | 328 | workspaceId: Id<"workspaces">; |
| 329 | }, | 329 | }, |
| 330 | any | 330 | any |
| 331 | >; | 331 | >; |
| @@ -333,32 +333,32 @@ export declare const api: { · 1 unmodified line | |||
| 333 | listCredentials: FunctionReference< | 333 | listCredentials: FunctionReference< |
| 334 | "query", | 334 | "query", |
| 335 | "public", | 335 | "public", |
| 336 | { sessionId?: string; workspaceId: string }, | 336 | { sessionId?: string; workspaceId: Id<"workspaces"> }, |
| 337 | any | 337 | any |
| 338 | >; | 338 | >; |
| 339 | listPendingApprovals: FunctionReference< | 339 | listPendingApprovals: FunctionReference< |
| 340 | "query", | 340 | "query", |
| 341 | "public", | 341 | "public", |
| 342 | { sessionId?: string; workspaceId: string }, | 342 | { sessionId?: string; workspaceId: Id<"workspaces"> }, |
| 343 | any | 343 | any |
| 344 | >; | 344 | >; |
| 345 | listRuntimeTargets: FunctionReference<"query", "public", {}, any>; | 345 | listRuntimeTargets: FunctionReference<"query", "public", {}, any>; |
| 346 | listTaskEvents: FunctionReference< | 346 | listTaskEvents: FunctionReference< |
| 347 | "query", | 347 | "query", |
| 348 | "public", | 348 | "public", |
| 349 | { sessionId?: string; taskId: string; workspaceId: string }, | 349 | { sessionId?: string; taskId: string; workspaceId: Id<"workspaces"> }, |
| 350 | any | 350 | any |
| 351 | >; | 351 | >; |
| 352 | listTasks: FunctionReference< | 352 | listTasks: FunctionReference< |
| 353 | "query", | 353 | "query", |
| 354 | "public", | 354 | "public", |
| 355 | { sessionId?: string; workspaceId: string }, | 355 | { sessionId?: string; workspaceId: Id<"workspaces"> }, |
| 356 | any | 356 | any |
| 357 | >; | 357 | >; |
| 358 | listToolSources: FunctionReference< | 358 | listToolSources: FunctionReference< |
| 359 | "query", | 359 | "query", |
| 360 | "public", | 360 | "public", |
| 361 | { sessionId?: string; workspaceId: string }, | 361 | { sessionId?: string; workspaceId: Id<"workspaces"> }, |
| 362 | any | 362 | any |
| 363 | >; | 363 | >; |
| 364 | resolveCredential: FunctionReference< | 364 | resolveCredential: FunctionReference< |
| @@ -369,7 +369,7 @@ export declare const api: { · 4 unmodified lines | |||
| 369 | scope: "workspace" | "actor"; | 369 | scope: "workspace" | "actor"; |
| 370 | sessionId?: string; | 370 | sessionId?: string; |
| 371 | sourceKey: string; | 371 | sourceKey: string; |
| 372 | workspaceId: string; | 372 | workspaceId: Id<"workspaces">; |
| 373 | }, | 373 | }, |
| 374 | any | 374 | any |
| 375 | >; | 375 | >; |
| @@ -384,7 +384,7 @@ export declare const api: { · 8 unmodified lines | |||
| 384 | priority?: number; | 384 | priority?: number; |
| 385 | sessionId?: string; | 385 | sessionId?: string; |
| 386 | toolPathPattern: string; | 386 | toolPathPattern: string; |
| 387 | workspaceId: string; | 387 | workspaceId: Id<"workspaces">; |
| 388 | }, | 388 | }, |
| 389 | any | 389 | any |
| 390 | >; | 390 | >; |
| @@ -399,7 +399,7 @@ export declare const api: { · 8 unmodified lines | |||
| 399 | secretJson: any; | 399 | secretJson: any; |
| 400 | sessionId?: string; | 400 | sessionId?: string; |
| 401 | sourceKey: string; | 401 | sourceKey: string; |
| 402 | workspaceId: string; | 402 | workspaceId: Id<"workspaces">; |
| 403 | }, | 403 | }, |
| 404 | any | 404 | any |
| 405 | >; | 405 | >; |
| @@ -413,7 +413,7 @@ export declare const api: { · 7 unmodified lines | |||
| 413 | name: string; | 413 | name: string; |
| 414 | sessionId?: string; | 414 | sessionId?: string; |
| 415 | type: "mcp" | "openapi" | "graphql"; | 415 | type: "mcp" | "openapi" | "graphql"; |
| 416 | workspaceId: string; | 416 | workspaceId: Id<"workspaces">; |
| 417 | }, | 417 | }, |
| 418 | any | 418 | any |
| 419 | >; | 419 | >; |
| @@ -532,7 +532,7 @@ export declare const internal: { · 112 unmodified lines | |||
| 532 | metadata?: any; | 532 | metadata?: any; |
| 533 | runtimeId: string; | 533 | runtimeId: string; |
| 534 | timeoutMs?: number; | 534 | timeoutMs?: number; |
| 535 | workspaceId: string; | 535 | workspaceId: Id<"workspaces">; |
| 536 | }, | 536 | }, |
| 537 | any | 537 | any |
| 538 | >; | 538 | >; |
| @@ -545,7 +545,7 @@ export declare const internal: { · 6 unmodified lines | |||
| 545 | deleteToolSource: FunctionReference< | 545 | deleteToolSource: FunctionReference< |
| 546 | "mutation", | 546 | "mutation", |
| 547 | "internal", | 547 | "internal", |
| 548 | { sourceId: string; workspaceId: string }, | 548 | { sourceId: string; workspaceId: Id<"workspaces"> }, |
| 549 | any | 549 | any |
| 550 | >; | 550 | >; |
| 551 | getApproval: FunctionReference< | 551 | getApproval: FunctionReference< |
| @@ -557,39 +557,42 @@ export declare const internal: { · 5 unmodified lines | |||
| 557 | getApprovalInWorkspace: FunctionReference< | 557 | getApprovalInWorkspace: FunctionReference< |
| 558 | "query", | 558 | "query", |
| 559 | "internal", | 559 | "internal", |
| 560 | { approvalId: string; workspaceId: string }, | 560 | { approvalId: string; workspaceId: Id<"workspaces"> }, |
| 561 | any | 561 | any |
| 562 | >; | 562 | >; |
| 563 | getTask: FunctionReference<"query", "internal", { taskId: string }, any>; | 563 | getTask: FunctionReference<"query", "internal", { taskId: string }, any>; |
| 564 | getTaskInWorkspace: FunctionReference< | 564 | getTaskInWorkspace: FunctionReference< |
| 565 | "query", | 565 | "query", |
| 566 | "internal", | 566 | "internal", |
| 567 | { taskId: string; workspaceId: string }, | 567 | { taskId: string; workspaceId: Id<"workspaces"> }, |
| 568 | any | 568 | any |
| 569 | >; | 569 | >; |
| 570 | listAccessPolicies: FunctionReference< | 570 | listAccessPolicies: FunctionReference< |
| 571 | "query", | 571 | "query", |
| 572 | "internal", | 572 | "internal", |
| 573 | { workspaceId: string }, | 573 | { workspaceId: Id<"workspaces"> }, |
| 574 | any | 574 | any |
| 575 | >; | 575 | >; |
| 576 | listApprovals: FunctionReference< | 576 | listApprovals: FunctionReference< |
| 577 | "query", | 577 | "query", |
| 578 | "internal", | 578 | "internal", |
| 579 | { status?: "pending" | "approved" | "denied"; workspaceId: string }, | 579 | { |
| 580 | status?: "pending" | "approved" | "denied"; | ||
| 581 | workspaceId: Id<"workspaces">; | ||
| 582 | }, | ||
| 580 | any | 583 | any |
| 581 | >; | 584 | >; |
| 582 | listCredentialProviders: FunctionReference<"query", "internal", {}, any>; | 585 | listCredentialProviders: FunctionReference<"query", "internal", {}, any>; |
| 583 | listCredentials: FunctionReference< | 586 | listCredentials: FunctionReference< |
| 584 | "query", | 587 | "query", |
| 585 | "internal", | 588 | "internal", |
| 586 | { workspaceId: string }, | 589 | { workspaceId: Id<"workspaces"> }, |
| 587 | any | 590 | any |
| 588 | >; | 591 | >; |
| 589 | listPendingApprovals: FunctionReference< | 592 | listPendingApprovals: FunctionReference< |
| 590 | "query", | 593 | "query", |
| 591 | "internal", | 594 | "internal", |
| 592 | { workspaceId: string }, | 595 | { workspaceId: Id<"workspaces"> }, |
| 593 | any | 596 | any |
| 594 | >; | 597 | >; |
| 595 | listQueuedTaskIds: FunctionReference< | 598 | listQueuedTaskIds: FunctionReference< |
| @@ -608,13 +611,13 @@ export declare const internal: { · 12 unmodified lines | |||
| 608 | listTasks: FunctionReference< | 611 | listTasks: FunctionReference< |
| 609 | "query", | 612 | "query", |
| 610 | "internal", | 613 | "internal", |
| 611 | { workspaceId: string }, | 614 | { workspaceId: Id<"workspaces"> }, |
| 612 | any | 615 | any |
| 613 | >; | 616 | >; |
| 614 | listToolSources: FunctionReference< | 617 | listToolSources: FunctionReference< |
| 615 | "query", | 618 | "query", |
| 616 | "internal", | 619 | "internal", |
| 617 | { workspaceId: string }, | 620 | { workspaceId: Id<"workspaces"> }, |
| 618 | any | 621 | any |
| 619 | >; | 622 | >; |
| 620 | markTaskFinished: FunctionReference< | 623 | markTaskFinished: FunctionReference< |
| @@ -654,7 +657,7 @@ export declare const internal: { · 33 unmodified lines | |||
| 654 | actorId?: string; | 657 | actorId?: string; |
| 655 | scope: "workspace" | "actor"; | 658 | scope: "workspace" | "actor"; |
| 656 | sourceKey: string; | 659 | sourceKey: string; |
| 657 | workspaceId: string; | 660 | workspaceId: Id<"workspaces">; |
| 658 | }, | 661 | }, |
| 659 | any | 662 | any |
| 660 | >; | 663 | >; |
| @@ -668,7 +671,7 @@ export declare const internal: { · 7 unmodified lines | |||
| 668 | id?: string; | 671 | id?: string; |
| 669 | priority?: number; | 672 | priority?: number; |
| 670 | toolPathPattern: string; | 673 | toolPathPattern: string; |
| 671 | workspaceId: string; | 674 | workspaceId: Id<"workspaces">; |
| 672 | }, | 675 | }, |
| 673 | any | 676 | any |
| 674 | >; | 677 | >; |
| @@ -682,7 +685,7 @@ export declare const internal: { · 7 unmodified lines | |||
| 682 | scope: "workspace" | "actor"; | 685 | scope: "workspace" | "actor"; |
| 683 | secretJson: any; | 686 | secretJson: any; |
| 684 | sourceKey: string; | 687 | sourceKey: string; |
| 685 | workspaceId: string; | 688 | workspaceId: Id<"workspaces">; |
| 686 | }, | 689 | }, |
| 687 | any | 690 | any |
| 688 | >; | 691 | >; |
| @@ -695,7 +698,7 @@ export declare const internal: { · 6 unmodified lines | |||
| 695 | id?: string; | 698 | id?: string; |
| 696 | name: string; | 699 | name: string; |
| 697 | type: "mcp" | "openapi" | "graphql"; | 700 | type: "mcp" | "openapi" | "graphql"; |
| 698 | workspaceId: string; | 701 | workspaceId: Id<"workspaces">; |
| 699 | }, | 702 | }, |
| 700 | any | 703 | any |
| 701 | >; | 704 | >; |
| @@ -722,7 +725,7 @@ export declare const internal: { · 20 unmodified lines | |||
| 722 | metadata?: any; | 725 | metadata?: any; |
| 723 | runtimeId?: string; | 726 | runtimeId?: string; |
| 724 | timeoutMs?: number; | 727 | timeoutMs?: number; |
| 725 | workspaceId: string; | 728 | workspaceId: Id<"workspaces">; |
| 726 | }, | 729 | }, |
| 727 | any | 730 | any |
| 728 | >; | 731 | >; |
| @@ -734,7 +737,7 @@ export declare const internal: { · 5 unmodified lines | |||
| 734 | decision: "approved" | "denied"; | 737 | decision: "approved" | "denied"; |
| 735 | reason?: string; | 738 | reason?: string; |
| 736 | reviewerId?: string; | 739 | reviewerId?: string; |
| 737 | workspaceId: string; | 740 | workspaceId: Id<"workspaces">; |
| 738 | }, | 741 | }, |
| 739 | any | 742 | any |
| 740 | >; | 743 | >; |
| @@ -749,13 +752,13 @@ export declare const internal: { · 8 unmodified lines | |||
| 749 | listToolsInternal: FunctionReference< | 752 | listToolsInternal: FunctionReference< |
| 750 | "action", | 753 | "action", |
| 751 | "internal", | 754 | "internal", |
| 752 | { actorId?: string; clientId?: string; workspaceId: string }, | 755 | { actorId?: string; clientId?: string; workspaceId: Id<"workspaces"> }, |
| 753 | any | 756 | any |
| 754 | >; | 757 | >; |
| 755 | listToolsWithWarningsInternal: FunctionReference< | 758 | listToolsWithWarningsInternal: FunctionReference< |
| 756 | "action", | 759 | "action", |
| 757 | "internal", | 760 | "internal", |
| 758 | { actorId?: string; clientId?: string; workspaceId: string }, | 761 | { actorId?: string; clientId?: string; workspaceId: Id<"workspaces"> }, |
| 759 | any | 762 | any |
| 760 | >; | 763 | >; |
| 761 | runTask: FunctionReference<"action", "internal", { taskId: string }, any>; | 764 | runTask: FunctionReference<"action", "internal", { taskId: string }, any>; |
| @@ -814,6 +817,18 @@ export declare const internal: { · 52 unmodified lines | |||
| 814 | >; | 817 | >; |
| 815 | }; | 818 | }; |
| 816 | migrations: { | 819 | 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 | >; | ||
| 817 | deleteAnonymousSessionsMissingAccountId: FunctionReference< | 832 | deleteAnonymousSessionsMissingAccountId: FunctionReference< |
| 818 | "mutation", | 833 | "mutation", |
| 819 | "internal", | 834 | "internal", |
| @@ -826,6 +841,18 @@ export declare const internal: { · 6 unmodified lines | |||
| 826 | }, | 841 | }, |
| 827 | any | 842 | any |
| 828 | >; | 843 | >; |
| 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 | >; | ||
| 829 | deleteInvitesMissingProvider: FunctionReference< | 856 | deleteInvitesMissingProvider: FunctionReference< |
| 830 | "mutation", | 857 | "mutation", |
| 831 | "internal", | 858 | "internal", |
| @@ -916,13 +943,13 @@ export declare const internal: { · 84 unmodified lines | |||
| 916 | getWorkspaceAccessForRequest: FunctionReference< | 943 | getWorkspaceAccessForRequest: FunctionReference< |
| 917 | "query", | 944 | "query", |
| 918 | "internal", | 945 | "internal", |
| 919 | { sessionId?: string; workspaceId: string }, | 946 | { sessionId?: string; workspaceId: Id<"workspaces"> }, |
| 920 | any | 947 | any |
| 921 | >; | 948 | >; |
| 922 | getWorkspaceAccessForWorkosSubject: FunctionReference< | 949 | getWorkspaceAccessForWorkosSubject: FunctionReference< |
| 923 | "query", | 950 | "query", |
| 924 | "internal", | 951 | "internal", |
| 925 | { subject: string; workspaceId: string }, | 952 | { subject: string; workspaceId: Id<"workspaces"> }, |
| 926 | any | 953 | any |
| 927 | >; | 954 | >; |
| 928 | }; | 955 | }; |
| @@ -930,25 +957,25 @@ export declare const internal: { · 1 unmodified line | |||
| 930 | getDtsStorageIds: FunctionReference< | 957 | getDtsStorageIds: FunctionReference< |
| 931 | "query", | 958 | "query", |
| 932 | "internal", | 959 | "internal", |
| 933 | { workspaceId: string }, | 960 | { workspaceId: Id<"workspaces"> }, |
| 934 | any | 961 | any |
| 935 | >; | 962 | >; |
| 936 | getEntry: FunctionReference< | 963 | getEntry: FunctionReference< |
| 937 | "query", | 964 | "query", |
| 938 | "internal", | 965 | "internal", |
| 939 | { signature: string; workspaceId: string }, | 966 | { signature: string; workspaceId: Id<"workspaces"> }, |
| 940 | any | 967 | any |
| 941 | >; | 968 | >; |
| 942 | putEntry: FunctionReference< | 969 | putEntry: FunctionReference< |
| 943 | "mutation", | 970 | "mutation", |
| 944 | "internal", | 971 | "internal", |
| 945 | { | 972 | { |
| 946 | dtsStorageIds?: Array<{ sourceKey: string; storageId: Id<"_storage"> }>; | 973 | dtsStorageIds: Array<{ sourceKey: string; storageId: Id<"_storage"> }>; |
| 947 | signature: string; | 974 | signature: string; |
| 948 | sizeBytes: number; | 975 | sizeBytes: number; |
| 949 | storageId: Id<"_storage">; | 976 | storageId: Id<"_storage">; |
| 950 | toolCount: number; | 977 | toolCount: number; |
| 951 | workspaceId: string; | 978 | workspaceId: Id<"workspaces">; |
| 952 | }, | 979 | }, |
| 953 | any | 980 | any |
| 954 | >; | 981 | >; |
| @@ -153,10 +153,6 @@ async function addWorkspaceMember( · 152 unmodified lines | |||
| 153 | }); | 153 | }); |
| 154 | } | 154 | } |
| 155 | 155 | ||
| 156 | function asWorkspaceId(id: Id<"workspaces">): string { | ||
| 157 | return id as unknown as string; | ||
| 158 | } | ||
| 159 | |||
| 160 | // --------------------------------------------------------------------------- | 156 | // --------------------------------------------------------------------------- |
| 161 | // Tests | 157 | // Tests |
| 162 | // --------------------------------------------------------------------------- | 158 | // --------------------------------------------------------------------------- |
| @@ -168,7 +164,7 @@ describe("authentication", () => { · 5 unmodified lines | |||
| 168 | 164 | ||
| 169 | await expect( | 165 | await expect( |
| 170 | t.query(api.workspace.listTasks, { | 166 | t.query(api.workspace.listTasks, { |
| 171 | workspaceId: asWorkspaceId(owner.workspaceId), | 167 | workspaceId: owner.workspaceId, |
| 172 | }), | 168 | }), |
| 173 | ).rejects.toThrow("Must be signed in"); | 169 | ).rejects.toThrow("Must be signed in"); |
| 174 | }); | 170 | }); |
| @@ -179,7 +175,7 @@ describe("authentication", () => { · 4 unmodified lines | |||
| 179 | 175 | ||
| 180 | await expect( | 176 | await expect( |
| 181 | t.mutation(api.workspace.upsertAccessPolicy, { | 177 | t.mutation(api.workspace.upsertAccessPolicy, { |
| 182 | workspaceId: asWorkspaceId(owner.workspaceId), | 178 | workspaceId: owner.workspaceId, |
| 183 | toolPathPattern: "*", | 179 | toolPathPattern: "*", |
| 184 | decision: "allow", | 180 | decision: "allow", |
| 185 | }), | 181 | }), |
| @@ -203,7 +199,7 @@ describe("authentication", () => { · 17 unmodified lines | |||
| 203 | 199 | ||
| 204 | await expect( | 200 | await expect( |
| 205 | t.mutation(api.executor.createTask, { | 201 | t.mutation(api.executor.createTask, { |
| 206 | workspaceId: asWorkspaceId(owner.workspaceId), | 202 | workspaceId: owner.workspaceId, |
| 207 | code: "console.log('hello')", | 203 | code: "console.log('hello')", |
| 208 | }), | 204 | }), |
| 209 | ).rejects.toThrow("Must be signed in"); | 205 | ).rejects.toThrow("Must be signed in"); |
| @@ -215,7 +211,7 @@ describe("authentication", () => { · 5 unmodified lines | |||
| 215 | const authed = t.withIdentity({ subject: "user-ok" }); | 211 | const authed = t.withIdentity({ subject: "user-ok" }); |
| 216 | 212 | ||
| 217 | const tasks = await authed.query(api.workspace.listTasks, { | 213 | const tasks = await authed.query(api.workspace.listTasks, { |
| 218 | workspaceId: asWorkspaceId(owner.workspaceId), | 214 | workspaceId: owner.workspaceId, |
| 219 | }); | 215 | }); |
| 220 | 216 | ||
| 221 | expect(tasks).toEqual([]); | 217 | expect(tasks).toEqual([]); |
| @@ -259,7 +255,7 @@ describe("workspace access controls", () => { · 37 unmodified lines | |||
| 259 | 255 | ||
| 260 | const authedMember = t.withIdentity({ subject: "ws-member" }); | 256 | const authedMember = t.withIdentity({ subject: "ws-member" }); |
| 261 | const tasks = await authedMember.query(api.workspace.listTasks, { | 257 | const tasks = await authedMember.query(api.workspace.listTasks, { |
| 262 | workspaceId: asWorkspaceId(owner.workspaceId), | 258 | workspaceId: owner.workspaceId, |
| 263 | }); | 259 | }); |
| 264 | 260 | ||
| 265 | expect(tasks).toEqual([]); | 261 | expect(tasks).toEqual([]); |
| @@ -278,7 +274,7 @@ describe("workspace access controls", () => { · 12 unmodified lines | |||
| 278 | 274 | ||
| 279 | const authedMember = t.withIdentity({ subject: "ws-member-org-fallback" }); | 275 | const authedMember = t.withIdentity({ subject: "ws-member-org-fallback" }); |
| 280 | const tasks = await authedMember.query(api.workspace.listTasks, { | 276 | const tasks = await authedMember.query(api.workspace.listTasks, { |
| 281 | workspaceId: asWorkspaceId(owner.workspaceId), | 277 | workspaceId: owner.workspaceId, |
| 282 | }); | 278 | }); |
| 283 | 279 | ||
| 284 | expect(tasks).toEqual([]); | 280 | expect(tasks).toEqual([]); |
| @@ -293,7 +289,7 @@ describe("workspace access controls", () => { · 8 unmodified lines | |||
| 293 | 289 | ||
| 294 | await expect( | 290 | await expect( |
| 295 | authedOutsider.query(api.workspace.listTasks, { | 291 | authedOutsider.query(api.workspace.listTasks, { |
| 296 | workspaceId: asWorkspaceId(owner.workspaceId), | 292 | workspaceId: owner.workspaceId, |
| 297 | }), | 293 | }), |
| 298 | ).rejects.toThrow("You are not a member of this workspace"); | 294 | ).rejects.toThrow("You are not a member of this workspace"); |
| 299 | }); | 295 | }); |
| @@ -319,7 +315,7 @@ describe("workspace access controls", () => { · 19 unmodified lines | |||
| 319 | 315 | ||
| 320 | await expect( | 316 | await expect( |
| 321 | authedRemoved.query(api.workspace.listTasks, { | 317 | authedRemoved.query(api.workspace.listTasks, { |
| 322 | workspaceId: asWorkspaceId(owner.workspaceId), | 318 | workspaceId: owner.workspaceId, |
| 323 | }), | 319 | }), |
| 324 | ).rejects.toThrow("You are not a member of this workspace"); | 320 | ).rejects.toThrow("You are not a member of this workspace"); |
| 325 | }); | 321 | }); |
| @@ -344,7 +340,7 @@ describe("workspace access controls", () => { · 18 unmodified lines | |||
| 344 | 340 | ||
| 345 | await expect( | 341 | await expect( |
| 346 | authedMember.mutation(api.workspace.upsertAccessPolicy, { | 342 | authedMember.mutation(api.workspace.upsertAccessPolicy, { |
| 347 | workspaceId: asWorkspaceId(owner.workspaceId), | 343 | workspaceId: owner.workspaceId, |
| 348 | toolPathPattern: "*", | 344 | toolPathPattern: "*", |
| 349 | decision: "allow", | 345 | decision: "allow", |
| 350 | }), | 346 | }), |
| @@ -370,7 +366,7 @@ describe("workspace access controls", () => { · 19 unmodified lines | |||
| 370 | const authedAdmin = t.withIdentity({ subject: "ws-policy-admin" }); | 366 | const authedAdmin = t.withIdentity({ subject: "ws-policy-admin" }); |
| 371 | 367 | ||
| 372 | const policy = await authedAdmin.mutation(api.workspace.upsertAccessPolicy, { | 368 | const policy = await authedAdmin.mutation(api.workspace.upsertAccessPolicy, { |
| 373 | workspaceId: asWorkspaceId(owner.workspaceId), | 369 | workspaceId: owner.workspaceId, |
| 374 | toolPathPattern: "*.delete", | 370 | toolPathPattern: "*.delete", |
| 375 | decision: "require_approval", | 371 | decision: "require_approval", |
| 376 | }); | 372 | }); |
| @@ -385,7 +381,7 @@ describe("workspace access controls", () => { · 8 unmodified lines | |||
| 385 | const authedOwner = t.withIdentity({ subject: "ws-owner-policy" }); | 381 | const authedOwner = t.withIdentity({ subject: "ws-owner-policy" }); |
| 386 | 382 | ||
| 387 | const policy = await authedOwner.mutation(api.workspace.upsertAccessPolicy, { | 383 | const policy = await authedOwner.mutation(api.workspace.upsertAccessPolicy, { |
| 388 | workspaceId: asWorkspaceId(owner.workspaceId), | 384 | workspaceId: owner.workspaceId, |
| 389 | toolPathPattern: "*", | 385 | toolPathPattern: "*", |
| 390 | decision: "allow", | 386 | decision: "allow", |
| 391 | }); | 387 | }); |
| @@ -413,7 +409,7 @@ describe("workspace access controls", () => { · 21 unmodified lines | |||
| 413 | 409 | ||
| 414 | await expect( | 410 | await expect( |
| 415 | authedMember.mutation(api.workspace.upsertCredential, { | 411 | authedMember.mutation(api.workspace.upsertCredential, { |
| 416 | workspaceId: asWorkspaceId(owner.workspaceId), | 412 | workspaceId: owner.workspaceId, |
| 417 | sourceKey: "openapi:github", | 413 | sourceKey: "openapi:github", |
| 418 | scope: "workspace", | 414 | scope: "workspace", |
| 419 | secretJson: { token: "ghp_test" }, | 415 | secretJson: { token: "ghp_test" }, |
| @@ -441,7 +437,7 @@ describe("workspace access controls", () => { · 21 unmodified lines | |||
| 441 | 437 | ||
| 442 | await expect( | 438 | await expect( |
| 443 | authedMember.mutation(api.workspace.upsertToolSource, { | 439 | authedMember.mutation(api.workspace.upsertToolSource, { |
| 444 | workspaceId: asWorkspaceId(owner.workspaceId), | 440 | workspaceId: owner.workspaceId, |
| 445 | name: "my-tool", | 441 | name: "my-tool", |
| 446 | type: "mcp", | 442 | type: "mcp", |
| 447 | config: { url: "https://example.com" }, | 443 | config: { url: "https://example.com" }, |
| @@ -469,7 +465,7 @@ describe("workspace access controls", () => { · 21 unmodified lines | |||
| 469 | 465 | ||
| 470 | await expect( | 466 | await expect( |
| 471 | authedMember.mutation(api.workspace.deleteToolSource, { | 467 | authedMember.mutation(api.workspace.deleteToolSource, { |
| 472 | workspaceId: asWorkspaceId(owner.workspaceId), | 468 | workspaceId: owner.workspaceId, |
| 473 | sourceId: "src_nonexistent", | 469 | sourceId: "src_nonexistent", |
| 474 | }), | 470 | }), |
| 475 | ).rejects.toThrow("Only workspace admins can perform this action"); | 471 | ).rejects.toThrow("Only workspace admins can perform this action"); |
| @@ -494,7 +490,7 @@ describe("workspace access controls", () => { · 18 unmodified lines | |||
| 494 | const authedMember = t.withIdentity({ subject: "read-policy-member" }); | 490 | const authedMember = t.withIdentity({ subject: "read-policy-member" }); |
| 495 | 491 | ||
| 496 | const policies = await authedMember.query(api.workspace.listAccessPolicies, { | 492 | const policies = await authedMember.query(api.workspace.listAccessPolicies, { |
| 497 | workspaceId: asWorkspaceId(owner.workspaceId), | 493 | workspaceId: owner.workspaceId, |
| 498 | }); | 494 | }); |
| 499 | 495 | ||
| 500 | expect(policies).toEqual([]); | 496 | expect(policies).toEqual([]); |
| @@ -519,7 +515,7 @@ describe("workspace access controls", () => { · 18 unmodified lines | |||
| 519 | const authedMember = t.withIdentity({ subject: "read-ts-member" }); | 515 | const authedMember = t.withIdentity({ subject: "read-ts-member" }); |
| 520 | 516 | ||
| 521 | const sources = await authedMember.query(api.workspace.listToolSources, { | 517 | const sources = await authedMember.query(api.workspace.listToolSources, { |
| 522 | workspaceId: asWorkspaceId(owner.workspaceId), | 518 | workspaceId: owner.workspaceId, |
| 523 | }); | 519 | }); |
| 524 | 520 | ||
| 525 | expect(sources).toEqual([]); | 521 | expect(sources).toEqual([]); |
| @@ -695,7 +691,7 @@ describe("cross-workspace isolation", () => { · 169 unmodified lines | |||
| 695 | // User A tries to read User B's workspace | 691 | // User A tries to read User B's workspace |
| 696 | await expect( | 692 | await expect( |
| 697 | authedA.query(api.workspace.listTasks, { | 693 | authedA.query(api.workspace.listTasks, { |
| 698 | workspaceId: asWorkspaceId(userB.workspaceId), | 694 | workspaceId: userB.workspaceId, |
| 699 | }), | 695 | }), |
| 700 | ).rejects.toThrow("You are not a member of this workspace"); | 696 | ).rejects.toThrow("You are not a member of this workspace"); |
| 701 | }); | 697 | }); |
| @@ -709,7 +705,7 @@ describe("cross-workspace isolation", () => { · 7 unmodified lines | |||
| 709 | 705 | ||
| 710 | await expect( | 706 | await expect( |
| 711 | authedA.mutation(api.executor.createTask, { | 707 | authedA.mutation(api.executor.createTask, { |
| 712 | workspaceId: asWorkspaceId(userB.workspaceId), | 708 | workspaceId: userB.workspaceId, |
| 713 | code: "console.log('pwned')", | 709 | code: "console.log('pwned')", |
| 714 | }), | 710 | }), |
| 715 | ).rejects.toThrow("You are not a member of this workspace"); | 711 | ).rejects.toThrow("You are not a member of this workspace"); |
| @@ -724,7 +720,7 @@ describe("cross-workspace isolation", () => { · 8 unmodified lines | |||
| 724 | 720 | ||
| 725 | await expect( | 721 | await expect( |
| 726 | authedA.mutation(api.workspace.upsertAccessPolicy, { | 722 | authedA.mutation(api.workspace.upsertAccessPolicy, { |
| 727 | workspaceId: asWorkspaceId(userB.workspaceId), | 723 | workspaceId: userB.workspaceId, |
| 728 | toolPathPattern: "*", | 724 | toolPathPattern: "*", |
| 729 | decision: "allow", | 725 | decision: "allow", |
| 730 | }), | 726 | }), |
| @@ -738,7 +734,7 @@ describe("cross-workspace isolation", () => { · 7 unmodified lines | |||
| 738 | 734 | ||
| 739 | await expect( | 735 | await expect( |
| 740 | authed.query(api.workspace.listTasks, { | 736 | authed.query(api.workspace.listTasks, { |
| 741 | workspaceId: "invalid_workspace_id", | 737 | workspaceId: "invalid_workspace_id" as Id<"workspaces">, |
| 742 | }), | 738 | }), |
| 743 | ).rejects.toThrow(); | 739 | ).rejects.toThrow(); |
| 744 | }); | 740 | }); |
| @@ -1018,12 +1014,12 @@ describe("task creation with access controls", () => { · 273 unmodified lines | |||
| 1018 | const authed = t.withIdentity({ subject: "task-creator" }); | 1014 | const authed = t.withIdentity({ subject: "task-creator" }); |
| 1019 | 1015 | ||
| 1020 | const result = await authed.mutation(api.executor.createTask, { | 1016 | const result = await authed.mutation(api.executor.createTask, { |
| 1021 | workspaceId: asWorkspaceId(user.workspaceId), | 1017 | workspaceId: user.workspaceId, |
| 1022 | code: "console.log('hello')", | 1018 | code: "console.log('hello')", |
| 1023 | }); | 1019 | }); |
| 1024 | 1020 | ||
| 1025 | expect(result.task.status).toBe("queued"); | 1021 | expect(result.task.status).toBe("queued"); |
| 1026 | expect(result.task.workspaceId).toBe(asWorkspaceId(user.workspaceId)); | 1022 | expect(result.task.workspaceId).toBe(user.workspaceId); |
| 1027 | }); | 1023 | }); |
| 1028 | 1024 | ||
| 1029 | test("task is scoped to workspace and visible to members", async () => { | 1025 | test("task is scoped to workspace and visible to members", async () => { |
| @@ -1045,14 +1041,14 @@ describe("task creation with access controls", () => { · 15 unmodified lines | |||
| 1045 | // Owner creates a task | 1041 | // Owner creates a task |
| 1046 | const authedOwner = t.withIdentity({ subject: "task-scope-owner" }); | 1042 | const authedOwner = t.withIdentity({ subject: "task-scope-owner" }); |
| 1047 | await authedOwner.mutation(api.executor.createTask, { | 1043 | await authedOwner.mutation(api.executor.createTask, { |
| 1048 | workspaceId: asWorkspaceId(owner.workspaceId), | 1044 | workspaceId: owner.workspaceId, |
| 1049 | code: "1 + 1", | 1045 | code: "1 + 1", |
| 1050 | }); | 1046 | }); |
| 1051 | 1047 | ||
| 1052 | // Member can see it | 1048 | // Member can see it |
| 1053 | const authedMember = t.withIdentity({ subject: "task-scope-member" }); | 1049 | const authedMember = t.withIdentity({ subject: "task-scope-member" }); |
| 1054 | const tasks = await authedMember.query(api.workspace.listTasks, { | 1050 | const tasks = await authedMember.query(api.workspace.listTasks, { |
| 1055 | workspaceId: asWorkspaceId(owner.workspaceId), | 1051 | workspaceId: owner.workspaceId, |
| 1056 | }); | 1052 | }); |
| 1057 | 1053 | ||
| 1058 | expect(tasks.length).toBe(1); | 1054 | expect(tasks.length).toBe(1); |
| @@ -1067,7 +1063,7 @@ describe("task creation with access controls", () => { · 8 unmodified lines | |||
| 1067 | // Owner creates a task | 1063 | // Owner creates a task |
| 1068 | const authedOwner = t.withIdentity({ subject: "task-vis-owner" }); | 1064 | const authedOwner = t.withIdentity({ subject: "task-vis-owner" }); |
| 1069 | await authedOwner.mutation(api.executor.createTask, { | 1065 | await authedOwner.mutation(api.executor.createTask, { |
| 1070 | workspaceId: asWorkspaceId(owner.workspaceId), | 1066 | workspaceId: owner.workspaceId, |
| 1071 | code: "secret code", | 1067 | code: "secret code", |
| 1072 | }); | 1068 | }); |
| 1073 | 1069 | ||
| @@ -1076,7 +1072,7 @@ describe("task creation with access controls", () => { · 2 unmodified lines | |||
| 1076 | 1072 | ||
| 1077 | await expect( | 1073 | await expect( |
| 1078 | authedOutsider.query(api.workspace.listTasks, { | 1074 | authedOutsider.query(api.workspace.listTasks, { |
| 1079 | workspaceId: asWorkspaceId(owner.workspaceId), | 1075 | workspaceId: owner.workspaceId, |
| 1080 | }), | 1076 | }), |
| 1081 | ).rejects.toThrow("You are not a member of this workspace"); | 1077 | ).rejects.toThrow("You are not a member of this workspace"); |
| 1082 | }); | 1078 | }); |
| @@ -1090,7 +1086,7 @@ describe("credential security", () => { · 7 unmodified lines | |||
| 1090 | 1086 | ||
| 1091 | // Create a credential via admin | 1087 | // Create a credential via admin |
| 1092 | await authed.mutation(api.workspace.upsertCredential, { | 1088 | await authed.mutation(api.workspace.upsertCredential, { |
| 1093 | workspaceId: asWorkspaceId(owner.workspaceId), | 1089 | workspaceId: owner.workspaceId, |
| 1094 | sourceKey: "openapi:stripe", | 1090 | sourceKey: "openapi:stripe", |
| 1095 | scope: "workspace", | 1091 | scope: "workspace", |
| 1096 | secretJson: { token: "sk_live_super_secret" }, | 1092 | secretJson: { token: "sk_live_super_secret" }, |
| @@ -1098,7 +1094,7 @@ describe("credential security", () => { · 1 unmodified line | |||
| 1098 | 1094 | ||
| 1099 | // List should redact | 1095 | // List should redact |
| 1100 | const credentials = await authed.query(api.workspace.listCredentials, { | 1096 | const credentials = await authed.query(api.workspace.listCredentials, { |
| 1101 | workspaceId: asWorkspaceId(owner.workspaceId), | 1097 | workspaceId: owner.workspaceId, |
| 1102 | }); | 1098 | }); |
| 1103 | 1099 | ||
| 1104 | expect(credentials.length).toBe(1); | 1100 | expect(credentials.length).toBe(1); |
| @@ -1126,7 +1122,7 @@ describe("credential security", () => { · 21 unmodified lines | |||
| 1126 | 1122 | ||
| 1127 | await expect( | 1123 | await expect( |
| 1128 | authedMember.query(api.workspace.resolveCredential, { | 1124 | authedMember.query(api.workspace.resolveCredential, { |
| 1129 | workspaceId: asWorkspaceId(owner.workspaceId), | 1125 | workspaceId: owner.workspaceId, |
| 1130 | sourceKey: "openapi:github", | 1126 | sourceKey: "openapi:github", |
| 1131 | scope: "workspace", | 1127 | scope: "workspace", |
| 1132 | }), | 1128 | }), |
| @@ -1155,7 +1151,7 @@ describe("role hierarchy validation", () => { · 22 unmodified lines | |||
| 1155 | 1151 | ||
| 1156 | // Admin can upsert access policies | 1152 | // Admin can upsert access policies |
| 1157 | const policy = await authed.mutation(api.workspace.upsertAccessPolicy, { | 1153 | const policy = await authed.mutation(api.workspace.upsertAccessPolicy, { |
| 1158 | workspaceId: asWorkspaceId(org.workspaceId), | 1154 | workspaceId: org.workspaceId, |
| 1159 | toolPathPattern: "admin.*", | 1155 | toolPathPattern: "admin.*", |
| 1160 | decision: "deny", | 1156 | decision: "deny", |
| 1161 | }); | 1157 | }); |
| @@ -1163,7 +1159,7 @@ describe("role hierarchy validation", () => { · 1 unmodified line | |||
| 1163 | 1159 | ||
| 1164 | // Admin can upsert tool sources | 1160 | // Admin can upsert tool sources |
| 1165 | const source = await authed.mutation(api.workspace.upsertToolSource, { | 1161 | const source = await authed.mutation(api.workspace.upsertToolSource, { |
| 1166 | workspaceId: asWorkspaceId(org.workspaceId), | 1162 | workspaceId: org.workspaceId, |
| 1167 | name: "admin-tool", | 1163 | name: "admin-tool", |
| 1168 | type: "mcp", | 1164 | type: "mcp", |
| 1169 | config: { url: "https://example.com" }, | 1165 | config: { url: "https://example.com" }, |
| @@ -11,11 +11,9 @@ async function deleteWorkspaceData( · 10 unmodified lines | |||
| 11 | return false; | 11 | return false; |
| 12 | } | 12 | } |
| 13 | 13 | ||
| 14 | const workspaceKey = String(workspaceId); | ||
| 15 | |||
| 16 | const taskDocs = await ctx.db | 14 | const taskDocs = await ctx.db |
| 17 | .query("tasks") | 15 | .query("tasks") |
| 18 | .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceKey)) | 16 | .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId)) |
| 19 | .collect(); | 17 | .collect(); |
| 20 | for (const task of taskDocs) { | 18 | for (const task of taskDocs) { |
| 21 | const taskEvents = await ctx.db | 19 | const taskEvents = await ctx.db |
| @@ -30,7 +28,7 @@ async function deleteWorkspaceData( · 8 unmodified lines | |||
| 30 | 28 | ||
| 31 | const approvalDocs = await ctx.db | 29 | const approvalDocs = await ctx.db |
| 32 | .query("approvals") | 30 | .query("approvals") |
| 33 | .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceKey)) | 31 | .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId)) |
| 34 | .collect(); | 32 | .collect(); |
| 35 | for (const approval of approvalDocs) { | 33 | for (const approval of approvalDocs) { |
| 36 | await ctx.db.delete(approval._id); | 34 | await ctx.db.delete(approval._id); |
| @@ -38,7 +36,7 @@ async function deleteWorkspaceData( · 1 unmodified line | |||
| 38 | 36 | ||
| 39 | const policyDocs = await ctx.db | 37 | const policyDocs = await ctx.db |
| 40 | .query("accessPolicies") | 38 | .query("accessPolicies") |
| 41 | .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceKey)) | 39 | .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId)) |
| 42 | .collect(); | 40 | .collect(); |
| 43 | for (const policy of policyDocs) { | 41 | for (const policy of policyDocs) { |
| 44 | await ctx.db.delete(policy._id); | 42 | await ctx.db.delete(policy._id); |
| @@ -46,7 +44,7 @@ async function deleteWorkspaceData( · 1 unmodified line | |||
| 46 | 44 | ||
| 47 | const credentialDocs = await ctx.db | 45 | const credentialDocs = await ctx.db |
| 48 | .query("sourceCredentials") | 46 | .query("sourceCredentials") |
| 49 | .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceKey)) | 47 | .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId)) |
| 50 | .collect(); | 48 | .collect(); |
| 51 | for (const credential of credentialDocs) { | 49 | for (const credential of credentialDocs) { |
| 52 | await ctx.db.delete(credential._id); | 50 | await ctx.db.delete(credential._id); |
| @@ -54,7 +52,7 @@ async function deleteWorkspaceData( · 1 unmodified line | |||
| 54 | 52 | ||
| 55 | const toolSources = await ctx.db | 53 | const toolSources = await ctx.db |
| 56 | .query("toolSources") | 54 | .query("toolSources") |
| 57 | .withIndex("by_workspace_updated", (q) => q.eq("workspaceId", workspaceKey)) | 55 | .withIndex("by_workspace_updated", (q) => q.eq("workspaceId", workspaceId)) |
| 58 | .collect(); | 56 | .collect(); |
| 59 | for (const source of toolSources) { | 57 | for (const source of toolSources) { |
| 60 | await ctx.db.delete(source._id); | 58 | await ctx.db.delete(source._id); |
| @@ -62,7 +60,7 @@ async function deleteWorkspaceData( · 1 unmodified line | |||
| 62 | 60 | ||
| 63 | const agentTasks = await ctx.db | 61 | const agentTasks = await ctx.db |
| 64 | .query("agentTasks") | 62 | .query("agentTasks") |
| 65 | .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceKey)) | 63 | .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId)) |
| 66 | .collect(); | 64 | .collect(); |
| 67 | for (const agentTask of agentTasks) { | 65 | for (const agentTask of agentTasks) { |
| 68 | await ctx.db.delete(agentTask._id); | 66 | await ctx.db.delete(agentTask._id); |
| @@ -70,7 +68,7 @@ async function deleteWorkspaceData( · 1 unmodified line | |||
| 70 | 68 | ||
| 71 | const cachedToolsets = await ctx.db | 69 | const cachedToolsets = await ctx.db |
| 72 | .query("workspaceToolCache") | 70 | .query("workspaceToolCache") |
| 73 | .withIndex("by_workspace", (q) => q.eq("workspaceId", workspaceKey)) | 71 | .withIndex("by_workspace", (q) => q.eq("workspaceId", workspaceId)) |
| 74 | .collect(); | 72 | .collect(); |
| 75 | for (const cacheEntry of cachedToolsets) { | 73 | for (const cacheEntry of cachedToolsets) { |
| 76 | await ctx.storage.delete(cacheEntry.storageId).catch(() => {}); | 74 | await ctx.storage.delete(cacheEntry.storageId).catch(() => {}); |
| @@ -141,7 +141,7 @@ async function upsertVaultObject(args: { · 140 unmodified lines | |||
| 141 | export const upsertCredential = action({ | 141 | export const upsertCredential = action({ |
| 142 | args: { | 142 | args: { |
| 143 | id: v.optional(v.string()), | 143 | id: v.optional(v.string()), |
| 144 | workspaceId: v.string(), | 144 | workspaceId: v.id("workspaces"), |
| 145 | sessionId: v.optional(v.string()), | 145 | sessionId: v.optional(v.string()), |
| 146 | sourceKey: v.string(), | 146 | sourceKey: v.string(), |
| 147 | scope: credentialScopeValidator, | 147 | scope: credentialScopeValidator, |
| @@ -1,6 +1,7 @@ | |||
| 1 | import { expect, test } from "bun:test"; | 1 | import { expect, test } from "bun:test"; |
| 2 | import { convexTest } from "convex-test"; | 2 | import { convexTest } from "convex-test"; |
| 3 | import { internal } from "./_generated/api"; | 3 | import { internal } from "./_generated/api"; |
| 4 | import type { Id } from "./_generated/dataModel"; | ||
| 4 | import schema from "./schema"; | 5 | import schema from "./schema"; |
| 5 | 6 | ||
| 6 | function setup() { | 7 | function setup() { |
| @@ -10,14 +11,36 @@ function setup() { · 3 unmodified lines | |||
| 10 | }); | 11 | }); |
| 11 | } | 12 | } |
| 12 | 13 | ||
| 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 | |||
| 13 | test("task lifecycle supports queue, run, and complete", async () => { | 35 | test("task lifecycle supports queue, run, and complete", async () => { |
| 14 | const t = setup(); | 36 | const t = setup(); |
| 37 | const wsId = await seedWorkspace(t, "ws_1"); | ||
| 15 | 38 | ||
| 16 | const created = await t.mutation(internal.database.createTask, { | 39 | const created = await t.mutation(internal.database.createTask, { |
| 17 | id: "task_1", | 40 | id: "task_1", |
| 18 | code: "console.log('hello')", | 41 | code: "console.log('hello')", |
| 19 | runtimeId: "local-bun", | 42 | runtimeId: "local-bun", |
| 20 | workspaceId: "ws_1", | 43 | workspaceId: wsId, |
| 21 | actorId: "actor_1", | 44 | actorId: "actor_1", |
| 22 | clientId: "web", | 45 | clientId: "web", |
| 23 | }); | 46 | }); |
| @@ -49,12 +72,13 @@ test("task lifecycle supports queue, run, and complete", async () => { · 25 unmodified lines | |||
| 49 | 72 | ||
| 50 | test("approval lifecycle tracks pending and resolution", async () => { | 73 | test("approval lifecycle tracks pending and resolution", async () => { |
| 51 | const t = setup(); | 74 | const t = setup(); |
| 75 | const wsId = await seedWorkspace(t, "ws_2"); | ||
| 52 | 76 | ||
| 53 | await t.mutation(internal.database.createTask, { | 77 | await t.mutation(internal.database.createTask, { |
| 54 | id: "task_2", | 78 | id: "task_2", |
| 55 | code: "await tools.admin.delete_data({ id: 'x' })", | 79 | code: "await tools.admin.delete_data({ id: 'x' })", |
| 56 | runtimeId: "local-bun", | 80 | runtimeId: "local-bun", |
| 57 | workspaceId: "ws_2", | 81 | workspaceId: wsId, |
| 58 | actorId: "actor_2", | 82 | actorId: "actor_2", |
| 59 | clientId: "web", | 83 | clientId: "web", |
| 60 | }); | 84 | }); |
| @@ -67,7 +91,7 @@ test("approval lifecycle tracks pending and resolution", async () => { · 6 unmodified lines | |||
| 67 | }); | 91 | }); |
| 68 | expect(createdApproval.status).toBe("pending"); | 92 | expect(createdApproval.status).toBe("pending"); |
| 69 | 93 | ||
| 70 | const pending = await t.query(internal.database.listPendingApprovals, { workspaceId: "ws_2" }); | 94 | const pending = await t.query(internal.database.listPendingApprovals, { workspaceId: wsId }); |
| 71 | expect(pending.length).toBe(1); | 95 | expect(pending.length).toBe(1); |
| 72 | expect(pending[0]?.task.id).toBe("task_2"); | 96 | expect(pending[0]?.task.id).toBe("task_2"); |
| 73 | 97 | ||
| @@ -78,7 +102,7 @@ test("approval lifecycle tracks pending and resolution", async () => { · 4 unmodified lines | |||
| 78 | }); | 102 | }); |
| 79 | expect(resolved?.status).toBe("approved"); | 103 | expect(resolved?.status).toBe("approved"); |
| 80 | 104 | ||
| 81 | const pendingAfter = await t.query(internal.database.listPendingApprovals, { workspaceId: "ws_2" }); | 105 | const pendingAfter = await t.query(internal.database.listPendingApprovals, { workspaceId: wsId }); |
| 82 | expect(pendingAfter).toEqual([]); | 106 | expect(pendingAfter).toEqual([]); |
| 83 | }); | 107 | }); |
| 84 | 108 | ||
| @@ -140,9 +164,10 @@ test("bootstrap honors MCP caller-provided session id", async () => { · 55 unmodified lines | |||
| 140 | 164 | ||
| 141 | test("credentials persist provider and resolve by scope", async () => { | 165 | test("credentials persist provider and resolve by scope", async () => { |
| 142 | const t = setup(); | 166 | const t = setup(); |
| 167 | const wsId = await seedWorkspace(t, "ws_cred"); | ||
| 143 | 168 | ||
| 144 | const workspaceCredential = await t.mutation(internal.database.upsertCredential, { | 169 | const workspaceCredential = await t.mutation(internal.database.upsertCredential, { |
| 145 | workspaceId: "ws_cred", | 170 | workspaceId: wsId, |
| 146 | sourceKey: "openapi:github", | 171 | sourceKey: "openapi:github", |
| 147 | scope: "workspace", | 172 | scope: "workspace", |
| 148 | provider: "managed", | 173 | provider: "managed", |
| @@ -152,7 +177,7 @@ test("credentials persist provider and resolve by scope", async () => { · 3 unmodified lines | |||
| 152 | expect(workspaceCredential.provider).toBe("managed"); | 177 | expect(workspaceCredential.provider).toBe("managed"); |
| 153 | 178 | ||
| 154 | const actorCredential = await t.mutation(internal.database.upsertCredential, { | 179 | const actorCredential = await t.mutation(internal.database.upsertCredential, { |
| 155 | workspaceId: "ws_cred", | 180 | workspaceId: wsId, |
| 156 | sourceKey: "openapi:github", | 181 | sourceKey: "openapi:github", |
| 157 | scope: "actor", | 182 | scope: "actor", |
| 158 | actorId: "actor_cred", | 183 | actorId: "actor_cred", |
| @@ -164,14 +189,14 @@ test("credentials persist provider and resolve by scope", async () => { · 5 unmodified lines | |||
| 164 | expect(actorCredential.actorId).toBe("actor_cred"); | 189 | expect(actorCredential.actorId).toBe("actor_cred"); |
| 165 | 190 | ||
| 166 | const resolvedWorkspace = await t.query(internal.database.resolveCredential, { | 191 | const resolvedWorkspace = await t.query(internal.database.resolveCredential, { |
| 167 | workspaceId: "ws_cred", | 192 | workspaceId: wsId, |
| 168 | sourceKey: "openapi:github", | 193 | sourceKey: "openapi:github", |
| 169 | scope: "workspace", | 194 | scope: "workspace", |
| 170 | }); | 195 | }); |
| 171 | expect(resolvedWorkspace?.provider).toBe("managed"); | 196 | expect(resolvedWorkspace?.provider).toBe("managed"); |
| 172 | 197 | ||
| 173 | const resolvedActor = await t.query(internal.database.resolveCredential, { | 198 | const resolvedActor = await t.query(internal.database.resolveCredential, { |
| 174 | workspaceId: "ws_cred", | 199 | workspaceId: wsId, |
| 175 | sourceKey: "openapi:github", | 200 | sourceKey: "openapi:github", |
| 176 | scope: "actor", | 201 | scope: "actor", |
| 177 | actorId: "actor_cred", | 202 | actorId: "actor_cred", |
| @@ -181,9 +206,10 @@ test("credentials persist provider and resolve by scope", async () => { · 3 unmodified lines | |||
| 181 | 206 | ||
| 182 | test("upsertCredential defaults provider to managed", async () => { | 207 | test("upsertCredential defaults provider to managed", async () => { |
| 183 | const t = setup(); | 208 | const t = setup(); |
| 209 | const wsId = await seedWorkspace(t, "ws_default_provider"); | ||
| 184 | 210 | ||
| 185 | const credential = await t.mutation(internal.database.upsertCredential, { | 211 | const credential = await t.mutation(internal.database.upsertCredential, { |
| 186 | workspaceId: "ws_default_provider", | 212 | workspaceId: wsId, |
| 187 | sourceKey: "openapi:stripe", | 213 | sourceKey: "openapi:stripe", |
| 188 | scope: "workspace", | 214 | scope: "workspace", |
| 189 | secretJson: { token: "sk_test_123" }, | 215 | secretJson: { token: "sk_test_123" }, |
| @@ -5,7 +5,7 @@ query, · 4 unmodified lines | |||
| 5 | type MutationCtx, type QueryCtx } from "./_generated/server"; | 5 | type MutationCtx, type QueryCtx } from "./_generated/server"; |
| 6 | import { internalMutation, internalQuery } from "./_generated/server"; | 6 | import { internalMutation, internalQuery } from "./_generated/server"; |
| 7 | import { ensureUniqueSlug } from "../lib/slug"; | 7 | import { ensureUniqueSlug } from "../lib/slug"; |
| 8 | import type { TaskStatus } from "@executor/contracts"; | 8 | import type { TaskStatus } from "../lib/types"; |
| 9 | 9 | ||
| 10 | const DEFAULT_TIMEOUT_MS = 300_000; | 10 | const DEFAULT_TIMEOUT_MS = 300_000; |
| 11 | type OrganizationRole = "owner" | "admin" | "member" | "billing_admin"; | 11 | type OrganizationRole = "owner" | "admin" | "member" | "billing_admin"; |
| @@ -335,7 +335,7 @@ export const createTask = internalMutation({ · 323 unmodified lines | |||
| 335 | runtimeId: v.string(), | 335 | runtimeId: v.string(), |
| 336 | timeoutMs: v.optional(v.number()), | 336 | timeoutMs: v.optional(v.number()), |
| 337 | metadata: v.optional(v.any()), | 337 | metadata: v.optional(v.any()), |
| 338 | workspaceId: v.string(), | 338 | workspaceId: v.id("workspaces"), |
| 339 | actorId: v.string(), | 339 | actorId: v.string(), |
| 340 | clientId: v.optional(v.string()), | 340 | clientId: v.optional(v.string()), |
| 341 | }, | 341 | }, |
| @@ -377,7 +377,7 @@ export const getTask = internalQuery({ · 35 unmodified lines | |||
| 377 | }); | 377 | }); |
| 378 | 378 | ||
| 379 | export const listTasks = internalQuery({ | 379 | export const listTasks = internalQuery({ |
| 380 | args: { workspaceId: v.string() }, | 380 | args: { workspaceId: v.id("workspaces") }, |
| 381 | handler: async (ctx, args) => { | 381 | handler: async (ctx, args) => { |
| 382 | const docs = await ctx.db | 382 | const docs = await ctx.db |
| 383 | .query("tasks") | 383 | .query("tasks") |
| @@ -415,7 +415,7 @@ export const listRuntimeTargets = internalQuery({ · 31 unmodified lines | |||
| 415 | }); | 415 | }); |
| 416 | 416 | ||
| 417 | export const getTaskInWorkspace = internalQuery({ | 417 | export const getTaskInWorkspace = internalQuery({ |
| 418 | args: { taskId: v.string(), workspaceId: v.string() }, | 418 | args: { taskId: v.string(), workspaceId: v.id("workspaces") }, |
| 419 | handler: async (ctx, args) => { | 419 | handler: async (ctx, args) => { |
| 420 | const doc = await getTaskDoc(ctx, args.taskId); | 420 | const doc = await getTaskDoc(ctx, args.taskId); |
| 421 | if (!doc || doc.workspaceId !== args.workspaceId) { | 421 | if (!doc || doc.workspaceId !== args.workspaceId) { |
| @@ -523,7 +523,7 @@ export const getApproval = internalQuery({ · 101 unmodified lines | |||
| 523 | 523 | ||
| 524 | export const listApprovals = internalQuery({ | 524 | export const listApprovals = internalQuery({ |
| 525 | args: { | 525 | args: { |
| 526 | workspaceId: v.string(), | 526 | workspaceId: v.id("workspaces"), |
| 527 | status: v.optional(approvalStatusValidator), | 527 | status: v.optional(approvalStatusValidator), |
| 528 | }, | 528 | }, |
| 529 | handler: async (ctx, args) => { | 529 | handler: async (ctx, args) => { |
| @@ -549,7 +549,7 @@ export const listApprovals = internalQuery({ · 19 unmodified lines | |||
| 549 | }); | 549 | }); |
| 550 | 550 | ||
| 551 | export const listPendingApprovals = internalQuery({ | 551 | export const listPendingApprovals = internalQuery({ |
| 552 | args: { workspaceId: v.string() }, | 552 | args: { workspaceId: v.id("workspaces") }, |
| 553 | handler: async (ctx, args) => { | 553 | handler: async (ctx, args) => { |
| 554 | const docs = await ctx.db | 554 | const docs = await ctx.db |
| 555 | .query("approvals") | 555 | .query("approvals") |
| @@ -616,7 +616,7 @@ export const resolveApproval = internalMutation({ · 60 unmodified lines | |||
| 616 | }); | 616 | }); |
| 617 | 617 | ||
| 618 | export const getApprovalInWorkspace = internalQuery({ | 618 | export const getApprovalInWorkspace = internalQuery({ |
| 619 | args: { approvalId: v.string(), workspaceId: v.string() }, | 619 | args: { approvalId: v.string(), workspaceId: v.id("workspaces") }, |
| 620 | handler: async (ctx, args) => { | 620 | handler: async (ctx, args) => { |
| 621 | const doc = await getApprovalDoc(ctx, args.approvalId); | 621 | const doc = await getApprovalDoc(ctx, args.approvalId); |
| 622 | if (!doc || doc.workspaceId !== args.workspaceId) { | 622 | if (!doc || doc.workspaceId !== args.workspaceId) { |
| @@ -656,7 +656,7 @@ export const createAgentTask = mutation({ · 33 unmodified lines | |||
| 656 | id: v.string(), | 656 | id: v.string(), |
| 657 | prompt: v.string(), | 657 | prompt: v.string(), |
| 658 | requesterId: v.string(), | 658 | requesterId: v.string(), |
| 659 | workspaceId: v.string(), | 659 | workspaceId: v.id("workspaces"), |
| 660 | actorId: v.string(), | 660 | actorId: v.string(), |
| 661 | }, | 661 | }, |
| 662 | handler: async (ctx, args) => { | 662 | handler: async (ctx, args) => { |
| @@ -796,7 +796,7 @@ export const bootstrapAnonymousSession = internalMutation({ · 133 unmodified lines | |||
| 796 | export const upsertAccessPolicy = internalMutation({ | 796 | export const upsertAccessPolicy = internalMutation({ |
| 797 | args: { | 797 | args: { |
| 798 | id: v.optional(v.string()), | 798 | id: v.optional(v.string()), |
| 799 | workspaceId: v.string(), | 799 | workspaceId: v.id("workspaces"), |
| 800 | actorId: v.optional(v.string()), | 800 | actorId: v.optional(v.string()), |
| 801 | clientId: v.optional(v.string()), | 801 | clientId: v.optional(v.string()), |
| 802 | toolPathPattern: v.string(), | 802 | toolPathPattern: v.string(), |
| @@ -847,7 +847,7 @@ export const upsertAccessPolicy = internalMutation({ · 44 unmodified lines | |||
| 847 | }); | 847 | }); |
| 848 | 848 | ||
| 849 | export const listAccessPolicies = internalQuery({ | 849 | export const listAccessPolicies = internalQuery({ |
| 850 | args: { workspaceId: v.string() }, | 850 | args: { workspaceId: v.id("workspaces") }, |
| 851 | handler: async (ctx, args) => { | 851 | handler: async (ctx, args) => { |
| 852 | const docs = await ctx.db | 852 | const docs = await ctx.db |
| 853 | .query("accessPolicies") | 853 | .query("accessPolicies") |
| @@ -868,7 +868,7 @@ export const listAccessPolicies = internalQuery({ · 14 unmodified lines | |||
| 868 | export const upsertCredential = internalMutation({ | 868 | export const upsertCredential = internalMutation({ |
| 869 | args: { | 869 | args: { |
| 870 | id: v.optional(v.string()), | 870 | id: v.optional(v.string()), |
| 871 | workspaceId: v.string(), | 871 | workspaceId: v.id("workspaces"), |
| 872 | sourceKey: v.string(), | 872 | sourceKey: v.string(), |
| 873 | scope: credentialScopeValidator, | 873 | scope: credentialScopeValidator, |
| 874 | actorId: v.optional(v.string()), | 874 | actorId: v.optional(v.string()), |
| @@ -932,7 +932,7 @@ export const upsertCredential = internalMutation({ · 57 unmodified lines | |||
| 932 | }); | 932 | }); |
| 933 | 933 | ||
| 934 | export const listCredentials = internalQuery({ | 934 | export const listCredentials = internalQuery({ |
| 935 | args: { workspaceId: v.string() }, | 935 | args: { workspaceId: v.id("workspaces") }, |
| 936 | handler: async (ctx, args) => { | 936 | handler: async (ctx, args) => { |
| 937 | const docs = await ctx.db | 937 | const docs = await ctx.db |
| 938 | .query("sourceCredentials") | 938 | .query("sourceCredentials") |
| @@ -963,7 +963,7 @@ export const listCredentialProviders = internalQuery({ · 24 unmodified lines | |||
| 963 | 963 | ||
| 964 | export const resolveCredential = internalQuery({ | 964 | export const resolveCredential = internalQuery({ |
| 965 | args: { | 965 | args: { |
| 966 | workspaceId: v.string(), | 966 | workspaceId: v.id("workspaces"), |
| 967 | sourceKey: v.string(), | 967 | sourceKey: v.string(), |
| 968 | scope: credentialScopeValidator, | 968 | scope: credentialScopeValidator, |
| 969 | actorId: v.optional(v.string()), | 969 | actorId: v.optional(v.string()), |
| @@ -1007,7 +1007,7 @@ export const resolveCredential = internalQuery({ · 37 unmodified lines | |||
| 1007 | export const upsertToolSource = internalMutation({ | 1007 | export const upsertToolSource = internalMutation({ |
| 1008 | args: { | 1008 | args: { |
| 1009 | id: v.optional(v.string()), | 1009 | id: v.optional(v.string()), |
| 1010 | workspaceId: v.string(), | 1010 | workspaceId: v.id("workspaces"), |
| 1011 | name: v.string(), | 1011 | name: v.string(), |
| 1012 | type: toolSourceTypeValidator, | 1012 | type: toolSourceTypeValidator, |
| 1013 | config: v.any(), | 1013 | config: v.any(), |
| @@ -1065,7 +1065,7 @@ export const upsertToolSource = internalMutation({ · 51 unmodified lines | |||
| 1065 | }); | 1065 | }); |
| 1066 | 1066 | ||
| 1067 | export const listToolSources = internalQuery({ | 1067 | export const listToolSources = internalQuery({ |
| 1068 | args: { workspaceId: v.string() }, | 1068 | args: { workspaceId: v.id("workspaces") }, |
| 1069 | handler: async (ctx, args) => { | 1069 | handler: async (ctx, args) => { |
| 1070 | const docs = await ctx.db | 1070 | const docs = await ctx.db |
| 1071 | .query("toolSources") | 1071 | .query("toolSources") |
| @@ -1077,7 +1077,7 @@ export const listToolSources = internalQuery({ · 5 unmodified lines | |||
| 1077 | }); | 1077 | }); |
| 1078 | 1078 | ||
| 1079 | export const deleteToolSource = internalMutation({ | 1079 | export const deleteToolSource = internalMutation({ |
| 1080 | args: { workspaceId: v.string(), sourceId: v.string() }, | 1080 | args: { workspaceId: v.id("workspaces"), sourceId: v.string() }, |
| 1081 | handler: async (ctx, args) => { | 1081 | handler: async (ctx, args) => { |
| 1082 | const doc = await ctx.db | 1082 | const doc = await ctx.db |
| 1083 | .query("toolSources") | 1083 | .query("toolSources") |
| @@ -4,6 +4,7 @@ import { Client } from "@modelcontextprotocol/sdk/client/index.js"; · 3 unmodified lines | |||
| 4 | import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; | 4 | import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; |
| 5 | import { ElicitRequestSchema } from "@modelcontextprotocol/sdk/types.js"; | 5 | import { ElicitRequestSchema } from "@modelcontextprotocol/sdk/types.js"; |
| 6 | import { internal } from "./_generated/api"; | 6 | import { internal } from "./_generated/api"; |
| 7 | import type { Id } from "./_generated/dataModel"; | ||
| 7 | import schema from "./schema"; | 8 | import schema from "./schema"; |
| 8 | 9 | ||
| 9 | function setup() { | 10 | function setup() { |
| @@ -46,7 +47,7 @@ function createMcpTransport( · 36 unmodified lines | |||
| 46 | async function waitForTaskId(t: ReturnType<typeof setup>, workspaceId: string, timeoutMs = 10_000): Promise<string> { | 47 | async function waitForTaskId(t: ReturnType<typeof setup>, workspaceId: string, timeoutMs = 10_000): Promise<string> { |
| 47 | const start = Date.now(); | 48 | const start = Date.now(); |
| 48 | while (Date.now() - start < timeoutMs) { | 49 | 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"> }); |
| 50 | if (tasks.length > 0) { | 51 | if (tasks.length > 0) { |
| 51 | return tasks[0]!.id; | 52 | return tasks[0]!.id; |
| 52 | } | 53 | } |
| @@ -64,7 +65,7 @@ async function waitForPendingApproval( · 11 unmodified lines | |||
| 64 | ): Promise<string> { | 65 | ): Promise<string> { |
| 65 | const start = Date.now(); | 66 | const start = Date.now(); |
| 66 | while (Date.now() - start < timeoutMs) { | 67 | 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"> }); |
| 68 | const approval = approvals.find((item: { toolPath: string; id: string }) => item.toolPath === toolPath); | 69 | const approval = approvals.find((item: { toolPath: string; id: string }) => item.toolPath === toolPath); |
| 69 | if (approval) { | 70 | if (approval) { |
| 70 | return approval.id; | 71 | return approval.id; |
| @@ -1,5 +1,6 @@ | |||
| 1 | import { v } from "convex/values"; | 1 | import { v } from "convex/values"; |
| 2 | import { internal } from "./_generated/api"; | 2 | import { internal } from "./_generated/api"; |
| 3 | import type { Id } from "./_generated/dataModel"; | ||
| 3 | import type { MutationCtx } from "./_generated/server"; | 4 | import type { MutationCtx } from "./_generated/server"; |
| 4 | import { internalMutation } from "./_generated/server"; | 5 | import { internalMutation } from "./_generated/server"; |
| 5 | import { workspaceMutation } from "../lib/functionBuilders"; | 6 | import { workspaceMutation } from "../lib/functionBuilders"; |
| @@ -30,7 +31,7 @@ async function createTaskRecord( · 24 unmodified lines | |||
| 30 | timeoutMs?: number; | 31 | timeoutMs?: number; |
| 31 | runtimeId?: string; | 32 | runtimeId?: string; |
| 32 | metadata?: unknown; | 33 | metadata?: unknown; |
| 33 | workspaceId: string; | 34 | workspaceId: Id<"workspaces">; |
| 34 | actorId: string; | 35 | actorId: string; |
| 35 | clientId?: string; | 36 | clientId?: string; |
| 36 | }, | 37 | }, |
| @@ -92,7 +93,7 @@ async function createTaskRecord( · 55 unmodified lines | |||
| 92 | async function resolveApprovalRecord( | 93 | async function resolveApprovalRecord( |
| 93 | ctx: MutationCtx, | 94 | ctx: MutationCtx, |
| 94 | args: { | 95 | args: { |
| 95 | workspaceId: string; | 96 | workspaceId: Id<"workspaces">; |
| 96 | approvalId: string; | 97 | approvalId: string; |
| 97 | decision: "approved" | "denied"; | 98 | decision: "approved" | "denied"; |
| 98 | reviewerId?: string; | 99 | reviewerId?: string; |
| @@ -172,7 +173,7 @@ export const createTaskInternal = internalMutation({ · 73 unmodified lines | |||
| 172 | timeoutMs: v.optional(v.number()), | 173 | timeoutMs: v.optional(v.number()), |
| 173 | runtimeId: v.optional(v.string()), | 174 | runtimeId: v.optional(v.string()), |
| 174 | metadata: v.optional(v.any()), | 175 | metadata: v.optional(v.any()), |
| 175 | workspaceId: v.string(), | 176 | workspaceId: v.id("workspaces"), |
| 176 | actorId: v.string(), | 177 | actorId: v.string(), |
| 177 | clientId: v.optional(v.string()), | 178 | clientId: v.optional(v.string()), |
| 178 | }, | 179 | }, |
| @@ -204,7 +205,7 @@ export const resolveApproval = workspaceMutation({ · 25 unmodified lines | |||
| 204 | 205 | ||
| 205 | export const resolveApprovalInternal = internalMutation({ | 206 | export const resolveApprovalInternal = internalMutation({ |
| 206 | args: { | 207 | args: { |
| 207 | workspaceId: v.string(), | 208 | workspaceId: v.id("workspaces"), |
| 208 | approvalId: v.string(), | 209 | approvalId: v.string(), |
| 209 | decision: v.union(v.literal("approved"), v.literal("denied")), | 210 | decision: v.union(v.literal("approved"), v.literal("denied")), |
| 210 | reviewerId: v.optional(v.string()), | 211 | reviewerId: v.optional(v.string()), |
| @@ -525,8 +525,6 @@ function toToolDescriptor(tool: ToolDefinition, approval: "auto" | "required"): · 524 unmodified lines | |||
| 525 | operationId: tool.metadata?.operationId, | 525 | operationId: tool.metadata?.operationId, |
| 526 | // Note: sourceDts is NOT included — it's too large to send over the wire. | 526 | // Note: sourceDts is NOT included — it's too large to send over the wire. |
| 527 | // Monaco fetches .d.ts blobs separately via Convex storage URLs. | 527 | // Monaco fetches .d.ts blobs separately via Convex storage URLs. |
| 528 | // Legacy compat | ||
| 529 | schemaTypes: tool.metadata?.schemaTypes, | ||
| 530 | }; | 528 | }; |
| 531 | } | 529 | } |
| 532 | 530 | ||
| @@ -910,7 +908,7 @@ async function invokeTool(ctx: any, task: TaskRecord, call: ToolCallRequest): Pr · 377 unmodified lines | |||
| 910 | 908 | ||
| 911 | export const listTools = action({ | 909 | export const listTools = action({ |
| 912 | args: { | 910 | args: { |
| 913 | workspaceId: v.string(), | 911 | workspaceId: v.id("workspaces"), |
| 914 | actorId: v.optional(v.string()), | 912 | actorId: v.optional(v.string()), |
| 915 | clientId: v.optional(v.string()), | 913 | clientId: v.optional(v.string()), |
| 916 | sessionId: v.optional(v.string()), | 914 | sessionId: v.optional(v.string()), |
| @@ -939,7 +937,7 @@ export const listTools = action({ · 22 unmodified lines | |||
| 939 | 937 | ||
| 940 | export const listToolsWithWarnings = action({ | 938 | export const listToolsWithWarnings = action({ |
| 941 | args: { | 939 | args: { |
| 942 | workspaceId: v.string(), | 940 | workspaceId: v.id("workspaces"), |
| 943 | actorId: v.optional(v.string()), | 941 | actorId: v.optional(v.string()), |
| 944 | clientId: v.optional(v.string()), | 942 | clientId: v.optional(v.string()), |
| 945 | sessionId: v.optional(v.string()), | 943 | sessionId: v.optional(v.string()), |
| @@ -976,7 +974,7 @@ export const listToolsWithWarnings = action({ · 30 unmodified lines | |||
| 976 | 974 | ||
| 977 | export const listToolsInternal = internalAction({ | 975 | export const listToolsInternal = internalAction({ |
| 978 | args: { | 976 | args: { |
| 979 | workspaceId: v.string(), | 977 | workspaceId: v.id("workspaces"), |
| 980 | actorId: v.optional(v.string()), | 978 | actorId: v.optional(v.string()), |
| 981 | clientId: v.optional(v.string()), | 979 | clientId: v.optional(v.string()), |
| 982 | }, | 980 | }, |
| @@ -987,7 +985,7 @@ export const listToolsInternal = internalAction({ · 4 unmodified lines | |||
| 987 | 985 | ||
| 988 | export const listToolsWithWarningsInternal = internalAction({ | 986 | export const listToolsWithWarningsInternal = internalAction({ |
| 989 | args: { | 987 | args: { |
| 990 | workspaceId: v.string(), | 988 | workspaceId: v.id("workspaces"), |
| 991 | actorId: v.optional(v.string()), | 989 | actorId: v.optional(v.string()), |
| 992 | clientId: v.optional(v.string()), | 990 | clientId: v.optional(v.string()), |
| 993 | }, | 991 | }, |
| @@ -6,6 +6,7 @@ import { httpAction } from "./_generated/server"; · 5 unmodified lines | |||
| 6 | import { authKit } from "./auth"; | 6 | import { authKit } from "./auth"; |
| 7 | import { handleMcpRequest, type McpWorkspaceContext } from "../lib/mcp_server"; | 7 | import { handleMcpRequest, type McpWorkspaceContext } from "../lib/mcp_server"; |
| 8 | import type { AnonymousContext, PendingApprovalRecord, TaskRecord, ToolDescriptor } from "../lib/types"; | 8 | import type { AnonymousContext, PendingApprovalRecord, TaskRecord, ToolDescriptor } from "../lib/types"; |
| 9 | import type { Id } from "./_generated/dataModel"; | ||
| 9 | 10 | ||
| 10 | const http = httpRouter(); | 11 | const http = httpRouter(); |
| 11 | const internalToken = process.env.EXECUTOR_INTERNAL_TOKEN ?? null; | 12 | const internalToken = process.env.EXECUTOR_INTERNAL_TOKEN ?? null; |
| @@ -161,7 +162,7 @@ const mcpHandler = httpAction(async (ctx, request) => { · 149 unmodified lines | |||
| 161 | try { | 162 | try { |
| 162 | if (mcpAuthConfig.enabled && auth?.subject) { | 163 | if (mcpAuthConfig.enabled && auth?.subject) { |
| 163 | const access = await ctx.runQuery(internal.workspaceAuthInternal.getWorkspaceAccessForWorkosSubject, { | 164 | const access = await ctx.runQuery(internal.workspaceAuthInternal.getWorkspaceAccessForWorkosSubject, { |
| 164 | workspaceId: requestedContext.workspaceId, | 165 | workspaceId: requestedContext.workspaceId as Id<"workspaces">, |
| 165 | subject: auth.subject, | 166 | subject: auth.subject, |
| 166 | }); | 167 | }); |
| 167 | 168 | ||
| @@ -176,7 +177,7 @@ const mcpHandler = httpAction(async (ctx, request) => { · 8 unmodified lines | |||
| 176 | } | 177 | } |
| 177 | 178 | ||
| 178 | const access = await ctx.runQuery(internal.workspaceAuthInternal.getWorkspaceAccessForRequest, { | 179 | const access = await ctx.runQuery(internal.workspaceAuthInternal.getWorkspaceAccessForRequest, { |
| 179 | workspaceId: requestedContext.workspaceId, | 180 | workspaceId: requestedContext.workspaceId as Id<"workspaces">, |
| 180 | sessionId: requestedContext.sessionId, | 181 | sessionId: requestedContext.sessionId, |
| 181 | }); | 182 | }); |
| 182 | 183 | ||
| @@ -212,11 +213,14 @@ const mcpHandler = httpAction(async (ctx, request) => { · 29 unmodified lines | |||
| 212 | actorId: string; | 213 | actorId: string; |
| 213 | clientId?: string; | 214 | clientId?: string; |
| 214 | }) => { | 215 | }) => { |
| 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 }; | ||
| 216 | }, | 220 | }, |
| 217 | getTask: async (taskId: string, workspaceId?: string) => { | 221 | getTask: async (taskId: string, workspaceId?: string) => { |
| 218 | if (workspaceId) { | 222 | 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; |
| 220 | } | 224 | } |
| 221 | return null; | 225 | return null; |
| 222 | }, | 226 | }, |
| @@ -231,10 +235,10 @@ const mcpHandler = httpAction(async (ctx, request) => { · 8 unmodified lines | |||
| 231 | return []; | 235 | return []; |
| 232 | } | 236 | } |
| 233 | 237 | ||
| 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[]; |
| 235 | }, | 239 | }, |
| 236 | listToolsForTypecheck: async (toolContext: { workspaceId: string; actorId?: string; clientId?: string }) => { | 240 | 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 { |
| 238 | tools: ToolDescriptor[]; | 242 | tools: ToolDescriptor[]; |
| 239 | dtsUrls?: Record<string, string>; | 243 | dtsUrls?: Record<string, string>; |
| 240 | }; | 244 | }; |
| @@ -245,7 +249,7 @@ const mcpHandler = httpAction(async (ctx, request) => { · 4 unmodified lines | |||
| 245 | }; | 249 | }; |
| 246 | }, | 250 | }, |
| 247 | listPendingApprovals: async (workspaceId: string) => { | 251 | 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[]; |
| 249 | }, | 253 | }, |
| 250 | resolveApproval: async (input: { | 254 | resolveApproval: async (input: { |
| 251 | workspaceId: string; | 255 | workspaceId: string; |
| @@ -254,7 +258,10 @@ const mcpHandler = httpAction(async (ctx, request) => { · 2 unmodified lines | |||
| 254 | reviewerId?: string; | 258 | reviewerId?: string; |
| 255 | reason?: string; | 259 | reason?: string; |
| 256 | }) => { | 260 | }) => { |
| 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 | }); | ||
| 258 | }, | 265 | }, |
| 259 | }; | 266 | }; |
| 260 | 267 | ||
| @@ -92,4 +92,24 @@ export const deleteInvitesMissingProvider = migrations.define({ · 91 unmodified lines | |||
| 92 | }, | 92 | }, |
| 93 | }); | 93 | }); |
| 94 | 94 | ||
| 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 | |||
| 95 | export const run = migrations.runner(); | 115 | export const run = migrations.runner(); |
| @@ -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 | } | ||
| @@ -181,7 +181,7 @@ export default defineSchema({ · 180 unmodified lines | |||
| 181 | taskId: v.string(), | 181 | taskId: v.string(), |
| 182 | code: v.string(), | 182 | code: v.string(), |
| 183 | runtimeId: v.string(), | 183 | runtimeId: v.string(), |
| 184 | workspaceId: v.string(), | 184 | workspaceId: v.id("workspaces"), |
| 185 | actorId: v.string(), | 185 | actorId: v.string(), |
| 186 | clientId: v.string(), | 186 | clientId: v.string(), |
| 187 | status: taskStatus, | 187 | status: taskStatus, |
| @@ -203,7 +203,7 @@ export default defineSchema({ · 15 unmodified lines | |||
| 203 | approvals: defineTable({ | 203 | approvals: defineTable({ |
| 204 | approvalId: v.string(), | 204 | approvalId: v.string(), |
| 205 | taskId: v.string(), | 205 | taskId: v.string(), |
| 206 | workspaceId: v.string(), | 206 | workspaceId: v.id("workspaces"), |
| 207 | toolPath: v.string(), | 207 | toolPath: v.string(), |
| 208 | input: v.any(), | 208 | input: v.any(), |
| 209 | status: approvalStatus, | 209 | status: approvalStatus, |
| @@ -217,21 +217,6 @@ export default defineSchema({ · 7 unmodified lines | |||
| 217 | .index("by_workspace_created", ["workspaceId", "createdAt"]) | 217 | .index("by_workspace_created", ["workspaceId", "createdAt"]) |
| 218 | .index("by_workspace_status_created", ["workspaceId", "status", "createdAt"]), | 218 | .index("by_workspace_status_created", ["workspaceId", "status", "createdAt"]), |
| 219 | 219 | ||
| 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 | |||
| 235 | taskEvents: defineTable({ | 220 | taskEvents: defineTable({ |
| 236 | sequence: v.number(), | 221 | sequence: v.number(), |
| 237 | taskId: v.string(), | 222 | taskId: v.string(), |
| @@ -245,7 +230,7 @@ export default defineSchema({ · 7 unmodified lines | |||
| 245 | 230 | ||
| 246 | accessPolicies: defineTable({ | 231 | accessPolicies: defineTable({ |
| 247 | policyId: v.string(), | 232 | policyId: v.string(), |
| 248 | workspaceId: v.string(), | 233 | workspaceId: v.id("workspaces"), |
| 249 | actorId: v.string(), | 234 | actorId: v.string(), |
| 250 | clientId: v.string(), | 235 | clientId: v.string(), |
| 251 | toolPathPattern: v.string(), | 236 | toolPathPattern: v.string(), |
| @@ -259,7 +244,7 @@ export default defineSchema({ · 7 unmodified lines | |||
| 259 | 244 | ||
| 260 | sourceCredentials: defineTable({ | 245 | sourceCredentials: defineTable({ |
| 261 | credentialId: v.string(), | 246 | credentialId: v.string(), |
| 262 | workspaceId: v.string(), | 247 | workspaceId: v.id("workspaces"), |
| 263 | sourceKey: v.string(), | 248 | sourceKey: v.string(), |
| 264 | scope: credentialScope, | 249 | scope: credentialScope, |
| 265 | actorId: v.string(), | 250 | actorId: v.string(), |
| @@ -274,7 +259,7 @@ export default defineSchema({ · 8 unmodified lines | |||
| 274 | 259 | ||
| 275 | toolSources: defineTable({ | 260 | toolSources: defineTable({ |
| 276 | sourceId: v.string(), | 261 | sourceId: v.string(), |
| 277 | workspaceId: v.string(), | 262 | workspaceId: v.id("workspaces"), |
| 278 | name: v.string(), | 263 | name: v.string(), |
| 279 | type: toolSourceType, | 264 | type: toolSourceType, |
| 280 | config: v.any(), | 265 | config: v.any(), |
| @@ -291,7 +276,7 @@ export default defineSchema({ · 10 unmodified lines | |||
| 291 | agentTaskId: v.string(), | 276 | agentTaskId: v.string(), |
| 292 | prompt: v.string(), | 277 | prompt: v.string(), |
| 293 | requesterId: v.string(), | 278 | requesterId: v.string(), |
| 294 | workspaceId: v.string(), | 279 | workspaceId: v.id("workspaces"), |
| 295 | actorId: v.string(), | 280 | actorId: v.string(), |
| 296 | status: agentTaskStatus, | 281 | status: agentTaskStatus, |
| 297 | resultText: v.optional(v.string()), | 282 | resultText: v.optional(v.string()), |
| @@ -314,14 +299,14 @@ export default defineSchema({ · 16 unmodified lines | |||
| 314 | .index("by_spec_url_version", ["specUrl", "version"]), | 299 | .index("by_spec_url_version", ["specUrl", "version"]), |
| 315 | 300 | ||
| 316 | workspaceToolCache: defineTable({ | 301 | workspaceToolCache: defineTable({ |
| 317 | workspaceId: v.string(), | 302 | workspaceId: v.id("workspaces"), |
| 318 | signature: v.string(), | 303 | signature: v.string(), |
| 319 | storageId: v.id("_storage"), | 304 | storageId: v.id("_storage"), |
| 320 | /** Per-source .d.ts blobs stored separately (too large for action responses). */ | 305 | /** 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({ |
| 322 | sourceKey: v.string(), | 307 | sourceKey: v.string(), |
| 323 | storageId: v.id("_storage"), | 308 | storageId: v.id("_storage"), |
| 324 | }))), | 309 | })), |
| 325 | toolCount: v.number(), | 310 | toolCount: v.number(), |
| 326 | sizeBytes: v.number(), | 311 | sizeBytes: v.number(), |
| 327 | createdAt: v.number(), | 312 | createdAt: v.number(), |
| @@ -334,7 +319,7 @@ export default defineSchema({ · 6 unmodified lines | |||
| 334 | actorId: v.string(), | 319 | actorId: v.string(), |
| 335 | clientId: v.string(), | 320 | clientId: v.string(), |
| 336 | accountId: v.id("accounts"), | 321 | accountId: v.id("accounts"), |
| 337 | userId: v.optional(v.id("workspaceMembers")), | 322 | userId: v.id("workspaceMembers"), |
| 338 | createdAt: v.number(), | 323 | createdAt: v.number(), |
| 339 | lastSeenAt: v.number(), | 324 | lastSeenAt: v.number(), |
| 340 | }) | 325 | }) |
| @@ -12,7 +12,7 @@ function actorIdForAccount(account: { _id: string; provider: string; providerAcc · 11 unmodified lines | |||
| 12 | 12 | ||
| 13 | export const getWorkspaceAccessForRequest = internalQuery({ | 13 | export const getWorkspaceAccessForRequest = internalQuery({ |
| 14 | args: { | 14 | args: { |
| 15 | workspaceId: v.string(), | 15 | workspaceId: v.id("workspaces"), |
| 16 | sessionId: v.optional(v.string()), | 16 | sessionId: v.optional(v.string()), |
| 17 | }, | 17 | }, |
| 18 | handler: async (ctx, args) => { | 18 | handler: async (ctx, args) => { |
| @@ -36,7 +36,7 @@ export const getWorkspaceAccessForRequest = internalQuery({ · 17 unmodified lines | |||
| 36 | 36 | ||
| 37 | export const getWorkspaceAccessForWorkosSubject = internalQuery({ | 37 | export const getWorkspaceAccessForWorkosSubject = internalQuery({ |
| 38 | args: { | 38 | args: { |
| 39 | workspaceId: v.string(), | 39 | workspaceId: v.id("workspaces"), |
| 40 | subject: v.string(), | 40 | subject: v.string(), |
| 41 | }, | 41 | }, |
| 42 | handler: async (ctx, args) => { | 42 | handler: async (ctx, args) => { |
| @@ -7,7 +7,7 @@ import { internalMutation, internalQuery } from "./_generated/server"; · 6 unmodified lines | |||
| 7 | */ | 7 | */ |
| 8 | export const getEntry = internalQuery({ | 8 | export const getEntry = internalQuery({ |
| 9 | args: { | 9 | args: { |
| 10 | workspaceId: v.string(), | 10 | workspaceId: v.id("workspaces"), |
| 11 | signature: v.string(), | 11 | signature: v.string(), |
| 12 | }, | 12 | }, |
| 13 | handler: async (ctx, args) => { | 13 | handler: async (ctx, args) => { |
| @@ -21,7 +21,7 @@ export const getEntry = internalQuery({ · 7 unmodified lines | |||
| 21 | 21 | ||
| 22 | return { | 22 | return { |
| 23 | storageId: entry.storageId, | 23 | storageId: entry.storageId, |
| 24 | dtsStorageIds: entry.dtsStorageIds ?? [], | 24 | dtsStorageIds: entry.dtsStorageIds, |
| 25 | toolCount: entry.toolCount, | 25 | toolCount: entry.toolCount, |
| 26 | sizeBytes: entry.sizeBytes, | 26 | sizeBytes: entry.sizeBytes, |
| 27 | createdAt: entry.createdAt, | 27 | createdAt: entry.createdAt, |
| @@ -35,13 +35,13 @@ export const getEntry = internalQuery({ · 7 unmodified lines | |||
| 35 | */ | 35 | */ |
| 36 | export const putEntry = internalMutation({ | 36 | export const putEntry = internalMutation({ |
| 37 | args: { | 37 | args: { |
| 38 | workspaceId: v.string(), | 38 | workspaceId: v.id("workspaces"), |
| 39 | signature: v.string(), | 39 | signature: v.string(), |
| 40 | storageId: v.id("_storage"), | 40 | storageId: v.id("_storage"), |
| 41 | dtsStorageIds: v.optional(v.array(v.object({ | 41 | dtsStorageIds: v.array(v.object({ |
| 42 | sourceKey: v.string(), | 42 | sourceKey: v.string(), |
| 43 | storageId: v.id("_storage"), | 43 | storageId: v.id("_storage"), |
| 44 | }))), | 44 | })), |
| 45 | toolCount: v.number(), | 45 | toolCount: v.number(), |
| 46 | sizeBytes: v.number(), | 46 | sizeBytes: v.number(), |
| 47 | }, | 47 | }, |
| @@ -55,10 +55,8 @@ export const putEntry = internalMutation({ · 7 unmodified lines | |||
| 55 | // Delete old main snapshot blob | 55 | // Delete old main snapshot blob |
| 56 | await ctx.storage.delete(existing.storageId).catch(() => {}); | 56 | await ctx.storage.delete(existing.storageId).catch(() => {}); |
| 57 | // Delete old .d.ts blobs | 57 | // Delete old .d.ts blobs |
| 58 | if (existing.dtsStorageIds) { | 58 | for (const entry of existing.dtsStorageIds) { |
| 59 | for (const entry of existing.dtsStorageIds) { | 59 | await ctx.storage.delete(entry.storageId).catch(() => {}); |
| 60 | await ctx.storage.delete(entry.storageId).catch(() => {}); | ||
| 61 | } | ||
| 62 | } | 60 | } |
| 63 | await ctx.db.delete(existing._id); | 61 | await ctx.db.delete(existing._id); |
| 64 | } | 62 | } |
| @@ -80,7 +78,7 @@ export const putEntry = internalMutation({ · 15 unmodified lines | |||
| 80 | */ | 78 | */ |
| 81 | export const getDtsStorageIds = internalQuery({ | 79 | export const getDtsStorageIds = internalQuery({ |
| 82 | args: { | 80 | args: { |
| 83 | workspaceId: v.string(), | 81 | workspaceId: v.id("workspaces"), |
| 84 | }, | 82 | }, |
| 85 | handler: async (ctx, args) => { | 83 | handler: async (ctx, args) => { |
| 86 | const entry = await ctx.db | 84 | const entry = await ctx.db |
| @@ -89,6 +87,6 @@ export const getDtsStorageIds = internalQuery({ · 2 unmodified lines | |||
| 89 | .unique(); | 87 | .unique(); |
| 90 | 88 | ||
| 91 | if (!entry) return []; | 89 | if (!entry) return []; |
| 92 | return entry.dtsStorageIds ?? []; | 90 | return entry.dtsStorageIds; |
| 93 | }, | 91 | }, |
| 94 | }); | 92 | }); |
| @@ -20,8 +20,6 @@ async function workspaceHasActivity( · 19 unmodified lines | |||
| 20 | ctx: Pick<QueryCtx, "db"> | Pick<MutationCtx, "db">, | 20 | ctx: Pick<QueryCtx, "db"> | Pick<MutationCtx, "db">, |
| 21 | workspaceId: Id<"workspaces">, | 21 | workspaceId: Id<"workspaces">, |
| 22 | ): Promise<boolean> { | 22 | ): Promise<boolean> { |
| 23 | const workspaceIdString = String(workspaceId); | ||
| 24 | |||
| 25 | const [ | 23 | const [ |
| 26 | task, | 24 | task, |
| 27 | approval, | 25 | approval, |
| @@ -31,15 +29,15 @@ async function workspaceHasActivity( · 3 unmodified lines | |||
| 31 | agentTask, | 29 | agentTask, |
| 32 | member, | 30 | member, |
| 33 | ] = await Promise.all([ | 31 | ] = await Promise.all([ |
| 34 | ctx.db.query("tasks").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(), |
| 35 | ctx.db.query("approvals").withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceIdString)).first(), | 33 | ctx.db.query("approvals").withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId)).first(), |
| 36 | ctx.db.query("accessPolicies").withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceIdString)).first(), | 34 | ctx.db.query("accessPolicies").withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId)).first(), |
| 37 | ctx.db | 35 | ctx.db |
| 38 | .query("sourceCredentials") | 36 | .query("sourceCredentials") |
| 39 | .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceIdString)) | 37 | .withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId)) |
| 40 | .first(), | 38 | .first(), |
| 41 | ctx.db.query("toolSources").withIndex("by_workspace_updated", (q) => q.eq("workspaceId", workspaceIdString)).first(), | 39 | ctx.db.query("toolSources").withIndex("by_workspace_updated", (q) => q.eq("workspaceId", workspaceId)).first(), |
| 42 | ctx.db.query("agentTasks").withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceIdString)).first(), | 40 | ctx.db.query("agentTasks").withIndex("by_workspace_created", (q) => q.eq("workspaceId", workspaceId)).first(), |
| 43 | ctx.db.query("workspaceMembers").withIndex("by_workspace", (q) => q.eq("workspaceId", workspaceId)).first(), | 41 | ctx.db.query("workspaceMembers").withIndex("by_workspace", (q) => q.eq("workspaceId", workspaceId)).first(), |
| 44 | ]); | 42 | ]); |
| 45 | 43 | ||
| @@ -46,15 +46,6 @@ test("WorkOS Vault provider falls back to id key and raw token", async () => { · 45 unmodified lines | |||
| 46 | expect(payload).toEqual({ token: "ghp_raw_token" }); | 46 | expect(payload).toEqual({ token: "ghp_raw_token" }); |
| 47 | }); | 47 | }); |
| 48 | 48 | ||
| 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 | |||
| 58 | test("WorkOS Vault provider gives actionable error on missing reference", async () => { | 49 | test("WorkOS Vault provider gives actionable error on missing reference", async () => { |
| 59 | await expect( | 50 | await expect( |
| 60 | resolveCredentialPayload({ | 51 | resolveCredentialPayload({ |
| @@ -124,14 +124,6 @@ async function resolveWorkosVault( · 123 unmodified lines | |||
| 124 | : ""; | 124 | : ""; |
| 125 | 125 | ||
| 126 | if (!objectId) { | 126 | 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 | |||
| 135 | throw new Error( | 127 | throw new Error( |
| 136 | "Encrypted credential is missing its secure reference. Re-save this credential in the dashboard.", | 128 | "Encrypted credential is missing its secure reference. Re-save this credential in the dashboard.", |
| 137 | ); | 129 | ); |
| @@ -151,7 +143,7 @@ export async function resolveCredentialPayload( · 13 unmodified lines | |||
| 151 | record: Pick<CredentialRecord, "provider" | "secretJson">, | 143 | record: Pick<CredentialRecord, "provider" | "secretJson">, |
| 152 | options?: { readVaultObject?: VaultObjectReader }, | 144 | options?: { readVaultObject?: VaultObjectReader }, |
| 153 | ): Promise<CredentialPayload | null> { | 145 | ): Promise<CredentialPayload | null> { |
| 154 | const provider = (record.provider ?? "managed") as CredentialProvider; | 146 | const provider = record.provider; |
| 155 | const resolver = providers[provider]; | 147 | const resolver = providers[provider]; |
| 156 | if (!resolver) { | 148 | if (!resolver) { |
| 157 | throw new Error(`Unsupported credential provider: ${provider}`); | 149 | throw new Error(`Unsupported credential provider: ${provider}`); |
| @@ -138,7 +138,7 @@ export const organizationMutation = customMutation(mutation, { · 137 unmodified lines | |||
| 138 | 138 | ||
| 139 | export const workspaceQuery = customQuery(query, { | 139 | export const workspaceQuery = customQuery(query, { |
| 140 | args: { | 140 | args: { |
| 141 | workspaceId: v.string(), | 141 | workspaceId: v.id("workspaces"), |
| 142 | sessionId: v.optional(v.string()), | 142 | sessionId: v.optional(v.string()), |
| 143 | }, | 143 | }, |
| 144 | input: async (ctx, args, options: WorkspaceAccessOptions = {}) => { | 144 | input: async (ctx, args, options: WorkspaceAccessOptions = {}) => { |
| @@ -158,7 +158,7 @@ export const workspaceQuery = customQuery(query, { · 13 unmodified lines | |||
| 158 | 158 | ||
| 159 | export const workspaceMutation = customMutation(mutation, { | 159 | export const workspaceMutation = customMutation(mutation, { |
| 160 | args: { | 160 | args: { |
| 161 | workspaceId: v.string(), | 161 | workspaceId: v.id("workspaces"), |
| 162 | sessionId: v.optional(v.string()), | 162 | sessionId: v.optional(v.string()), |
| 163 | }, | 163 | }, |
| 164 | input: async (ctx, args, options: WorkspaceAccessOptions = {}) => { | 164 | input: async (ctx, args, options: WorkspaceAccessOptions = {}) => { |
| @@ -100,10 +100,10 @@ export async function getWorkspaceMembership( · 99 unmodified lines | |||
| 100 | 100 | ||
| 101 | export async function requireWorkspaceAccessForAccount( | 101 | export async function requireWorkspaceAccessForAccount( |
| 102 | ctx: MembershipCtx, | 102 | ctx: MembershipCtx, |
| 103 | workspaceId: string, | 103 | workspaceId: Id<"workspaces">, |
| 104 | account: Doc<"accounts">, | 104 | account: Doc<"accounts">, |
| 105 | ): Promise<WorkspaceAccess> { | 105 | ): Promise<WorkspaceAccess> { |
| 106 | const workspace = await ctx.db.get(workspaceId as Id<"workspaces">); | 106 | const workspace = await ctx.db.get(workspaceId); |
| 107 | if (!workspace) { | 107 | if (!workspace) { |
| 108 | throw new Error("Workspace not found"); | 108 | throw new Error("Workspace not found"); |
| 109 | } | 109 | } |
| @@ -140,7 +140,7 @@ export async function requireWorkspaceAccessForAccount( · 30 unmodified lines | |||
| 140 | 140 | ||
| 141 | export async function requireWorkspaceAccessForRequest( | 141 | export async function requireWorkspaceAccessForRequest( |
| 142 | ctx: IdentityCtx, | 142 | ctx: IdentityCtx, |
| 143 | workspaceId: string, | 143 | workspaceId: Id<"workspaces">, |
| 144 | sessionId?: string, | 144 | sessionId?: string, |
| 145 | ): Promise<WorkspaceAccess> { | 145 | ): Promise<WorkspaceAccess> { |
| 146 | const account = await resolveAccountForRequest(ctx, sessionId); | 146 | const account = await resolveAccountForRequest(ctx, sessionId); |
| @@ -78,6 +78,8 @@ class FakeMcpService { · 77 unmodified lines | |||
| 78 | workspaceId: `ws_${crypto.randomUUID()}`, | 78 | workspaceId: `ws_${crypto.randomUUID()}`, |
| 79 | actorId: `anon_${crypto.randomUUID()}`, | 79 | actorId: `anon_${crypto.randomUUID()}`, |
| 80 | clientId: "mcp", | 80 | clientId: "mcp", |
| 81 | accountId: `account_${crypto.randomUUID()}`, | ||
| 82 | userId: `user_${crypto.randomUUID()}`, | ||
| 81 | createdAt: now, | 83 | createdAt: now, |
| 82 | lastSeenAt: now, | 84 | lastSeenAt: now, |
| 83 | }; | 85 | }; |
| @@ -168,110 +168,7 @@ describe("prepareOpenApiSpec with large specs", () => { · 167 unmodified lines | |||
| 168 | expect(json.length).toBeGreaterThan(100_000); | 168 | expect(json.length).toBeGreaterThan(100_000); |
| 169 | }); | 169 | }); |
| 170 | 170 | ||
| 171 | test("preserves operationTypes when spec is large", async () => { | ||
| 172 | const spec = makeLargeSpec(50); | ||
| 173 | const prepared = await prepareOpenApiSpec(spec, "type-test"); | ||
| 174 | 171 | ||
| 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 | }); | ||
| 275 | }); | 172 | }); |
| 276 | 173 | ||
| 277 | describe("buildOpenApiToolsFromPrepared", () => { | 174 | describe("buildOpenApiToolsFromPrepared", () => { |
| @@ -946,78 +843,7 @@ describe("buildOpenApiToolsFromPrepared", () => { · 668 unmodified lines | |||
| 946 | expect(tool!.metadata?.returnsType).not.toBe("unknown"); | 843 | expect(tool!.metadata?.returnsType).not.toBe("unknown"); |
| 947 | }); | 844 | }); |
| 948 | 845 | ||
| 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 | }; | ||
| 1001 | 846 | ||
| 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 | }); | ||
| 1021 | }); | 847 | }); |
| 1022 | 848 | ||
| 1023 | describe("prepared spec serialization round-trip", () => { | 849 | describe("prepared spec serialization round-trip", () => { |
| @@ -1035,20 +861,6 @@ describe("prepared spec serialization round-trip", () => { · 11 unmodified lines | |||
| 1035 | ); | 861 | ); |
| 1036 | expect(restored.warnings).toEqual(prepared.warnings); | 862 | expect(restored.warnings).toEqual(prepared.warnings); |
| 1037 | 863 | ||
| 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 | |||
| 1052 | // Verify tools built from the restored spec match the original | 864 | // Verify tools built from the restored spec match the original |
| 1053 | const config = { | 865 | const config = { |
| 1054 | type: "openapi" as const, | 866 | type: "openapi" as const, |
| @@ -852,10 +852,6 @@ export interface PreparedOpenApiSpec { · 851 unmodified lines | |||
| 852 | paths: Record<string, unknown>; | 852 | paths: Record<string, unknown>; |
| 853 | /** Raw .d.ts from openapiTS — cached as-is, extracted lazily on read */ | 853 | /** Raw .d.ts from openapiTS — cached as-is, extracted lazily on read */ |
| 854 | dts?: string; | 854 | 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>; | ||
| 859 | warnings: string[]; | 855 | warnings: string[]; |
| 860 | } | 856 | } |
| 861 | 857 | ||
| @@ -941,7 +937,7 @@ export function buildOpenApiToolsFromPrepared( · 79 unmodified lines | |||
| 941 | const paths = asRecord(prepared.paths); | 937 | const paths = asRecord(prepared.paths); |
| 942 | const tools: ToolDefinition[] = []; | 938 | const tools: ToolDefinition[] = []; |
| 943 | 939 | ||
| 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). |
| 945 | // The typechecker/Monaco use this directly via indexed access types. | 941 | // The typechecker/Monaco use this directly via indexed access types. |
| 946 | const sourceDts = prepared.dts | 942 | const sourceDts = prepared.dts |
| 947 | ? prepared.dts.replace(/^export /gm, "") // strip 'export' so types are ambient | 943 | ? prepared.dts.replace(/^export /gm, "") // strip 'export' so types are ambient |
| @@ -1739,7 +1735,6 @@ export interface SerializedTool { · 791 unmodified lines | |||
| 1739 | * MCP: { kind: "mcp", url, transport?, queryParams?, toolName } | 1735 | * MCP: { kind: "mcp", url, transport?, queryParams?, toolName } |
| 1740 | * GraphQL raw: { kind: "graphql_raw", endpoint, authHeaders } | 1736 | * GraphQL raw: { kind: "graphql_raw", endpoint, authHeaders } |
| 1741 | * GraphQL field: { kind: "graphql_field", endpoint, operationName, operationType, queryTemplate, argNames?, authHeaders } | 1737 | * GraphQL field: { kind: "graphql_field", endpoint, operationName, operationType, queryTemplate, argNames?, authHeaders } |
| 1742 | * GraphQL (legacy compat): { kind: "graphql", endpoint, operationName, operationType, authHeaders } | ||
| 1743 | * Builtin: { kind: "builtin" } — run comes from DEFAULT_TOOLS | 1738 | * Builtin: { kind: "builtin" } — run comes from DEFAULT_TOOLS |
| 1744 | */ | 1739 | */ |
| 1745 | runSpec: | 1740 | runSpec: |
| @@ -1772,13 +1767,6 @@ export interface SerializedTool { · 26 unmodified lines | |||
| 1772 | argNames?: string[]; | 1767 | argNames?: string[]; |
| 1773 | authHeaders: Record<string, string>; | 1768 | authHeaders: Record<string, string>; |
| 1774 | } | 1769 | } |
| 1775 | | { | ||
| 1776 | kind: "graphql"; | ||
| 1777 | endpoint: string; | ||
| 1778 | operationName: string; | ||
| 1779 | operationType: "query" | "mutation"; | ||
| 1780 | authHeaders: Record<string, string>; | ||
| 1781 | } | ||
| 1782 | | { kind: "builtin" }; | 1770 | | { kind: "builtin" }; |
| 1783 | } | 1771 | } |
| 1784 | 1772 | ||
| @@ -1976,21 +1964,6 @@ export function rehydrateTools( · 191 unmodified lines | |||
| 1976 | }; | 1964 | }; |
| 1977 | } | 1965 | } |
| 1978 | 1966 | ||
| 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 | |||
| 1994 | return { ...base, run: async () => { throw new Error(`Unknown run spec kind for '${st.path}'`); } }; | 1967 | return { ...base, run: async () => { throw new Error(`Unknown run spec kind for '${st.path}'`); } }; |
| 1995 | }); | 1968 | }); |
| 1996 | } | 1969 | } |
| @@ -424,97 +424,4 @@ export interface operations { · 423 unmodified lines | |||
| 424 | }); | 424 | }); |
| 425 | }); | 425 | }); |
| 426 | 426 | ||
| 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 | }); | ||
| 493 | 427 | ||
| 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 | }); | ||
| @@ -104,18 +104,6 @@ export function generateToolDeclarations( · 103 unmodified lines | |||
| 104 | options?: GenerateToolDeclarationOptions, | 104 | options?: GenerateToolDeclarationOptions, |
| 105 | ): string { | 105 | ): string { |
| 106 | 106 | ||
| 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 | |||
| 119 | // Build a nested tree from flat tool paths | 107 | // Build a nested tree from flat tool paths |
| 120 | interface TreeNode { | 108 | interface TreeNode { |
| 121 | children: Map<string, TreeNode>; | 109 | children: Map<string, TreeNode>; |
| @@ -172,13 +160,6 @@ export function generateToolDeclarations( · 50 unmodified lines | |||
| 172 | // Assemble the full declarations block | 160 | // Assemble the full declarations block |
| 173 | const parts: string[] = []; | 161 | const parts: string[] = []; |
| 174 | 162 | ||
| 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 | |||
| 182 | const sourceDtsBySource = options?.sourceDtsBySource ?? {}; | 163 | const sourceDtsBySource = options?.sourceDtsBySource ?? {}; |
| 183 | const dtsEntries = Object.entries(sourceDtsBySource) | 164 | const dtsEntries = Object.entries(sourceDtsBySource) |
| 184 | .filter(([, dts]) => typeof dts === "string" && dts.trim().length > 0) | 165 | .filter(([, dts]) => typeof dts === "string" && dts.trim().length > 0) |
| @@ -1,25 +1,125 @@ | |||
| 1 | // Re-export shared types from contracts | 1 | // ── Shared types (inlined from @executor/contracts) ────────────────────────── |
| 2 | export type { | 2 | |
| 3 | TaskStatus, | 3 | export type TaskStatus = "queued" | "running" | "completed" | "failed" | "timed_out" | "denied"; |
| 4 | ApprovalStatus, | 4 | export type ApprovalStatus = "pending" | "approved" | "denied"; |
| 5 | PolicyDecision, | 5 | export type PolicyDecision = "allow" | "require_approval" | "deny"; |
| 6 | CredentialScope, | 6 | export type CredentialScope = "workspace" | "actor"; |
| 7 | CredentialProvider, | 7 | export type CredentialProvider = "managed" | "workos-vault"; |
| 8 | ToolApprovalMode, | 8 | export type ToolApprovalMode = "auto" | "required"; |
| 9 | ToolSourceType, | 9 | export type ToolSourceType = "mcp" | "openapi" | "graphql"; |
| 10 | TaskRecord, | 10 | |
| 11 | ApprovalRecord, | 11 | export interface TaskRecord { |
| 12 | PendingApprovalRecord, | 12 | id: string; |
| 13 | TaskEventRecord, | 13 | code: string; |
| 14 | AccessPolicyRecord, | 14 | runtimeId: string; |
| 15 | CredentialRecord, | 15 | status: TaskStatus; |
| 16 | ToolSourceRecord, | 16 | timeoutMs: number; |
| 17 | ToolDescriptor, | 17 | metadata: Record<string, unknown>; |
| 18 | OpenApiSourceQuality, | 18 | workspaceId: string; |
| 19 | AnonymousContext, | 19 | actorId?: string; |
| 20 | } from "@executor/contracts"; | 20 | clientId?: string; |
| 21 | 21 | createdAt: number; | |
| 22 | import type { TaskStatus, CredentialScope, ToolApprovalMode } from "@executor/contracts"; | 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 | } | ||
| 23 | 123 | ||
| 24 | // ── Server-only types ───────────────────────────────────────────────────────── | 124 | // ── Server-only types ───────────────────────────────────────────────────────── |
| 25 | 125 | ||
| @@ -120,8 +220,6 @@ export interface ToolTypeMetadata { · 94 unmodified lines | |||
| 120 | * Only set on the FIRST tool per source to avoid duplication. | 220 | * Only set on the FIRST tool per source to avoid duplication. |
| 121 | */ | 221 | */ |
| 122 | sourceDts?: string; | 222 | sourceDts?: string; |
| 123 | /** @deprecated Use sourceDts instead. Kept for cache compat. */ | ||
| 124 | schemaTypes?: Record<string, string>; | ||
| 125 | } | 223 | } |
| 126 | 224 | ||
| 127 | export interface ToolDefinition { | 225 | export interface ToolDefinition { |
| @@ -5,6 +5,7 @@ · 4 unmodified lines | |||
| 5 | * round-trip with functional `run` methods. | 5 | * round-trip with functional `run` methods. |
| 6 | */ | 6 | */ |
| 7 | import { test, expect, describe } from "bun:test"; | 7 | import { test, expect, describe } from "bun:test"; |
| 8 | import type { Id } from "../convex/_generated/dataModel"; | ||
| 8 | import { | 9 | import { |
| 9 | prepareOpenApiSpec, | 10 | prepareOpenApiSpec, |
| 10 | buildOpenApiToolsFromPrepared, | 11 | buildOpenApiToolsFromPrepared, |
| @@ -464,8 +465,7 @@ describe("serializeTools + rehydrateTools round-trip", () => { · 453 unmodified lines | |||
| 464 | }); | 465 | }); |
| 465 | 466 | ||
| 466 | describe("workspace tool cache table", () => { | 467 | describe("workspace tool cache table", () => { |
| 467 | // convex-test based tests for the table operations | 468 | async function setupCacheTest() { |
| 468 | test("getEntry + putEntry round-trip", async () => { | ||
| 469 | const { convexTest } = await import("convex-test"); | 469 | const { convexTest } = await import("convex-test"); |
| 470 | const { internal } = await import("../convex/_generated/api"); | 470 | const { internal } = await import("../convex/_generated/api"); |
| 471 | const schema = (await import("../convex/schema")).default; | 471 | const schema = (await import("../convex/schema")).default; |
| @@ -475,9 +475,33 @@ describe("workspace tool cache table", () => { · 3 unmodified lines | |||
| 475 | "./_generated/api.js": () => import("../convex/_generated/api.js"), | 475 | "./_generated/api.js": () => import("../convex/_generated/api.js"), |
| 476 | }); | 476 | }); |
| 477 | 477 | ||
| 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 | |||
| 478 | // Empty cache | 502 | // Empty cache |
| 479 | const miss = await t.query(internal.workspaceToolCache.getEntry, { | 503 | const miss = await t.query(internal.workspaceToolCache.getEntry, { |
| 480 | workspaceId: "ws_1", | 504 | workspaceId: wsId, |
| 481 | signature: "sig_1", | 505 | signature: "sig_1", |
| 482 | }); | 506 | }); |
| 483 | expect(miss).toBeNull(); | 507 | expect(miss).toBeNull(); |
| @@ -489,16 +513,17 @@ describe("workspace tool cache table", () => { · 5 unmodified lines | |||
| 489 | }); | 513 | }); |
| 490 | 514 | ||
| 491 | await t.mutation(internal.workspaceToolCache.putEntry, { | 515 | await t.mutation(internal.workspaceToolCache.putEntry, { |
| 492 | workspaceId: "ws_1", | 516 | workspaceId: wsId, |
| 493 | signature: "sig_1", | 517 | signature: "sig_1", |
| 494 | storageId, | 518 | storageId, |
| 495 | toolCount: 0, | 519 | toolCount: 0, |
| 496 | sizeBytes: 27, | 520 | sizeBytes: 27, |
| 521 | dtsStorageIds: [], | ||
| 497 | }); | 522 | }); |
| 498 | 523 | ||
| 499 | // Hit | 524 | // Hit |
| 500 | const hit = await t.query(internal.workspaceToolCache.getEntry, { | 525 | const hit = await t.query(internal.workspaceToolCache.getEntry, { |
| 501 | workspaceId: "ws_1", | 526 | workspaceId: wsId, |
| 502 | signature: "sig_1", | 527 | signature: "sig_1", |
| 503 | }); | 528 | }); |
| 504 | expect(hit).not.toBeNull(); | 529 | expect(hit).not.toBeNull(); |
| @@ -506,32 +531,26 @@ describe("workspace tool cache table", () => { · 1 unmodified line | |||
| 506 | 531 | ||
| 507 | // Wrong signature = miss | 532 | // Wrong signature = miss |
| 508 | const wrongSig = await t.query(internal.workspaceToolCache.getEntry, { | 533 | const wrongSig = await t.query(internal.workspaceToolCache.getEntry, { |
| 509 | workspaceId: "ws_1", | 534 | workspaceId: wsId, |
| 510 | signature: "sig_2", | 535 | signature: "sig_2", |
| 511 | }); | 536 | }); |
| 512 | expect(wrongSig).toBeNull(); | 537 | expect(wrongSig).toBeNull(); |
| 513 | }); | 538 | }); |
| 514 | 539 | ||
| 515 | test("putEntry replaces old entry and deletes old blob", async () => { | 540 | test("putEntry replaces old entry and deletes old blob", async () => { |
| 516 | const { convexTest } = await import("convex-test"); | 541 | const { t, internal, wsId } = await setupCacheTest(); |
| 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 | }); | ||
| 524 | 542 | ||
| 525 | const storageId1 = await t.run(async (ctx) => { | 543 | const storageId1 = await t.run(async (ctx) => { |
| 526 | return await ctx.storage.store(new Blob(["old"])); | 544 | return await ctx.storage.store(new Blob(["old"])); |
| 527 | }); | 545 | }); |
| 528 | 546 | ||
| 529 | await t.mutation(internal.workspaceToolCache.putEntry, { | 547 | await t.mutation(internal.workspaceToolCache.putEntry, { |
| 530 | workspaceId: "ws_1", | 548 | workspaceId: wsId, |
| 531 | signature: "sig_1", | 549 | signature: "sig_1", |
| 532 | storageId: storageId1, | 550 | storageId: storageId1, |
| 533 | toolCount: 5, | 551 | toolCount: 5, |
| 534 | sizeBytes: 3, | 552 | sizeBytes: 3, |
| 553 | dtsStorageIds: [], | ||
| 535 | }); | 554 | }); |
| 536 | 555 | ||
| 537 | const storageId2 = await t.run(async (ctx) => { | 556 | const storageId2 = await t.run(async (ctx) => { |
| @@ -539,16 +558,17 @@ describe("workspace tool cache table", () => { · 1 unmodified line | |||
| 539 | }); | 558 | }); |
| 540 | 559 | ||
| 541 | await t.mutation(internal.workspaceToolCache.putEntry, { | 560 | await t.mutation(internal.workspaceToolCache.putEntry, { |
| 542 | workspaceId: "ws_1", | 561 | workspaceId: wsId, |
| 543 | signature: "sig_2", | 562 | signature: "sig_2", |
| 544 | storageId: storageId2, | 563 | storageId: storageId2, |
| 545 | toolCount: 10, | 564 | toolCount: 10, |
| 546 | sizeBytes: 3, | 565 | sizeBytes: 3, |
| 566 | dtsStorageIds: [], | ||
| 547 | }); | 567 | }); |
| 548 | 568 | ||
| 549 | // New entry | 569 | // New entry |
| 550 | const entry = await t.query(internal.workspaceToolCache.getEntry, { | 570 | const entry = await t.query(internal.workspaceToolCache.getEntry, { |
| 551 | workspaceId: "ws_1", | 571 | workspaceId: wsId, |
| 552 | signature: "sig_2", | 572 | signature: "sig_2", |
| 553 | }); | 573 | }); |
| 554 | expect(entry!.storageId).toBe(storageId2); | 574 | expect(entry!.storageId).toBe(storageId2); |
| @@ -3,6 +3,7 @@ import { createRemoteJWKSet, jwtVerify } from "jose"; · 2 unmodified lines | |||
| 3 | import { api } from "./convex/_generated/api"; | 3 | import { api } from "./convex/_generated/api"; |
| 4 | import { handleMcpRequest, type McpWorkspaceContext } from "./lib/mcp_server"; | 4 | import { handleMcpRequest, type McpWorkspaceContext } from "./lib/mcp_server"; |
| 5 | import type { AnonymousContext, PendingApprovalRecord, TaskRecord, ToolDescriptor } from "./lib/types"; | 5 | import type { AnonymousContext, PendingApprovalRecord, TaskRecord, ToolDescriptor } from "./lib/types"; |
| 6 | import type { Id } from "./convex/_generated/dataModel"; | ||
| 6 | 7 | ||
| 7 | const convexUrlFromEnv = Bun.env.CONVEX_URL; | 8 | const convexUrlFromEnv = Bun.env.CONVEX_URL; |
| 8 | if (!convexUrlFromEnv) { | 9 | if (!convexUrlFromEnv) { |
| @@ -126,7 +127,7 @@ function createService(context?: McpWorkspaceContext, bearerToken?: string) { · 117 unmodified lines | |||
| 126 | clientId?: string; | 127 | clientId?: string; |
| 127 | }) => { | 128 | }) => { |
| 128 | const created = await convex.mutation(api.executor.createTask, { | 129 | const created = await convex.mutation(api.executor.createTask, { |
| 129 | workspaceId: input.workspaceId, | 130 | workspaceId: input.workspaceId as Id<"workspaces">, |
| 130 | sessionId, | 131 | sessionId, |
| 131 | code: input.code, | 132 | code: input.code, |
| 132 | timeoutMs: input.timeoutMs, | 133 | timeoutMs: input.timeoutMs, |
| @@ -144,7 +145,7 @@ function createService(context?: McpWorkspaceContext, bearerToken?: string) { · 11 unmodified lines | |||
| 144 | return null; | 145 | return null; |
| 145 | } | 146 | } |
| 146 | const task = await convex.query(api.workspace.getTaskInWorkspace, { | 147 | const task = await convex.query(api.workspace.getTaskInWorkspace, { |
| 147 | workspaceId: effectiveWorkspaceId, | 148 | workspaceId: effectiveWorkspaceId as Id<"workspaces">, |
| 148 | sessionId, | 149 | sessionId, |
| 149 | taskId, | 150 | taskId, |
| 150 | }); | 151 | }); |
| @@ -165,7 +166,7 @@ function createService(context?: McpWorkspaceContext, bearerToken?: string) { · 14 unmodified lines | |||
| 165 | return []; | 166 | return []; |
| 166 | } | 167 | } |
| 167 | const tools = await convex.action(api.executorNode.listTools, { | 168 | const tools = await convex.action(api.executorNode.listTools, { |
| 168 | workspaceId: toolContext.workspaceId, | 169 | workspaceId: toolContext.workspaceId as Id<"workspaces">, |
| 169 | sessionId, | 170 | sessionId, |
| 170 | actorId: toolContext.actorId, | 171 | actorId: toolContext.actorId, |
| 171 | clientId: toolContext.clientId, | 172 | clientId: toolContext.clientId, |
| @@ -175,7 +176,7 @@ function createService(context?: McpWorkspaceContext, bearerToken?: string) { · 3 unmodified lines | |||
| 175 | 176 | ||
| 176 | listPendingApprovals: async (approvalWorkspaceId: string) => { | 177 | listPendingApprovals: async (approvalWorkspaceId: string) => { |
| 177 | const approvals = await convex.query(api.workspace.listPendingApprovals, { | 178 | const approvals = await convex.query(api.workspace.listPendingApprovals, { |
| 178 | workspaceId: approvalWorkspaceId, | 179 | workspaceId: approvalWorkspaceId as Id<"workspaces">, |
| 179 | sessionId, | 180 | sessionId, |
| 180 | }); | 181 | }); |
| 181 | return approvals as PendingApprovalRecord[]; | 182 | return approvals as PendingApprovalRecord[]; |
| @@ -189,7 +190,7 @@ function createService(context?: McpWorkspaceContext, bearerToken?: string) { · 7 unmodified lines | |||
| 189 | reason?: string; | 190 | reason?: string; |
| 190 | }) => { | 191 | }) => { |
| 191 | return await convex.mutation(api.executor.resolveApproval, { | 192 | return await convex.mutation(api.executor.resolveApproval, { |
| 192 | workspaceId: input.workspaceId, | 193 | workspaceId: input.workspaceId as Id<"workspaces">, |
| 193 | sessionId, | 194 | sessionId, |
| 194 | approvalId: input.approvalId, | 195 | approvalId: input.approvalId, |
| 195 | decision: input.decision, | 196 | decision: input.decision, |
| @@ -232,7 +233,7 @@ async function resolveContext( · 36 unmodified lines | |||
| 232 | } | 233 | } |
| 233 | 234 | ||
| 234 | const requestContext = await convex.query(api.workspace.getRequestContext, { | 235 | const requestContext = await convex.query(api.workspace.getRequestContext, { |
| 235 | workspaceId: requested.workspaceId, | 236 | workspaceId: requested.workspaceId as Id<"workspaces">, |
| 236 | sessionId: requested.sessionId, | 237 | sessionId: requested.sessionId, |
| 237 | }); | 238 | }); |
| 238 | 239 | ||
| @@ -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 | } | ||
| @@ -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 | } | ||
| @@ -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 | } | ||
| @@ -3,6 +3,7 @@ · 2 unmodified lines | |||
| 3 | "private": true, | 3 | "private": true, |
| 4 | "type": "module", | 4 | "type": "module", |
| 5 | "workspaces": [ | 5 | "workspaces": [ |
| 6 | "executor/convex", | ||
| 6 | "executor/apps/*", | 7 | "executor/apps/*", |
| 7 | "executor/packages/*", | 8 | "executor/packages/*", |
| 8 | "assistant", | 9 | "assistant", |
| @@ -32,7 +33,6 @@ · 23 unmodified lines | |||
| 32 | "@convex-dev/migrations": "^0.3.1", | 33 | "@convex-dev/migrations": "^0.3.1", |
| 33 | "@convex-dev/stripe": "^0.1.3", | 34 | "@convex-dev/stripe": "^0.1.3", |
| 34 | "@convex-dev/workos-authkit": "^0.1.6", | 35 | "@convex-dev/workos-authkit": "^0.1.6", |
| 35 | "@executor/contracts": "workspace:*", | ||
| 36 | "@workos-inc/node": "^8.2.0", | 36 | "@workos-inc/node": "^8.2.0", |
| 37 | "convex": "^1.31.7", | 37 | "convex": "^1.31.7", |
| 38 | "convex-helpers": "^0.1.111", | 38 | "convex-helpers": "^0.1.111", |