- database.test.ts: Remove assertions on non-existent workspaceDocId field (mapAnonymousContext no longer returns it) - executor-mcp.e2e.test.ts: Use internal.executorNode.runTask instead of api.executorNode.runTask (runTask is an internalAction) - executor-mcp.e2e.test.ts: Cast input to Request for .url access - credentialsNode.ts: Narrow string|null in async closure, add explicit return type and typed variables - tsconfig.json: Exclude apps/ from root tsconfig (web app has its own tsconfig with path aliases) - convex.config.ts: Add TODO comment noting actionCache can be removed
Unified · Split
| @@ -7,6 +7,8 @@ const app = defineApp(); · 6 unmodified lines | |||
| 7 | 7 | ||
| 8 | app.use(workOSAuthKit); | 8 | app.use(workOSAuthKit); |
| 9 | app.use(stripe); | 9 | app.use(stripe); |
| 10 | // TODO: Remove actionCache component after confirming no deployments depend on it. | ||
| 11 | // All OpenAPI spec caching now uses Convex file storage (openApiSpecCache / workspaceToolCache tables). | ||
| 10 | app.use(actionCache); | 12 | app.use(actionCache); |
| 11 | 13 | ||
| 12 | export default app; | 14 | export default app; |
| @@ -115,9 +115,10 @@ async function upsertVaultObject(args: { · 114 unmodified lines | |||
| 115 | const value = JSON.stringify(args.payload); | 115 | const value = JSON.stringify(args.payload); |
| 116 | 116 | ||
| 117 | if (args.existingObjectId) { | 117 | if (args.existingObjectId) { |
| 118 | const objectId: string = args.existingObjectId; | ||
| 118 | const updated = await withVaultRetry(async () => { | 119 | const updated = await withVaultRetry(async () => { |
| 119 | return await workos.vault.updateObject({ | 120 | return await workos.vault.updateObject({ |
| 120 | id: args.existingObjectId, | 121 | id: objectId, |
| 121 | value, | 122 | value, |
| 122 | }); | 123 | }); |
| 123 | }); | 124 | }); |
| @@ -147,7 +148,7 @@ export const upsertCredential = action({ · 23 unmodified lines | |||
| 147 | provider: v.optional(credentialProviderValidator), | 148 | provider: v.optional(credentialProviderValidator), |
| 148 | secretJson: v.any(), | 149 | secretJson: v.any(), |
| 149 | }, | 150 | }, |
| 150 | handler: async (ctx, args) => { | 151 | handler: async (ctx, args): Promise<Record<string, unknown>> => { |
| 151 | const provider = args.provider ?? "managed"; | 152 | const provider = args.provider ?? "managed"; |
| 152 | const actorId = normalizedActorId(args.scope, args.actorId); | 153 | const actorId = normalizedActorId(args.scope, args.actorId); |
| 153 | const submittedSecret = asRecord(args.secretJson); | 154 | const submittedSecret = asRecord(args.secretJson); |
| @@ -166,13 +167,13 @@ export const upsertCredential = action({ · 12 unmodified lines | |||
| 166 | throw new Error("Encrypted storage value looks like a GitHub token. Paste the token in the token field."); | 167 | throw new Error("Encrypted storage value looks like a GitHub token. Paste the token in the token field."); |
| 167 | } | 168 | } |
| 168 | 169 | ||
| 169 | const existing = await ctx.runQuery(api.database.resolveCredential, { | 170 | const existing: Record<string, unknown> | null = await ctx.runQuery(api.database.resolveCredential, { |
| 170 | workspaceId: args.workspaceId, | 171 | workspaceId: args.workspaceId, |
| 171 | sourceKey: args.sourceKey, | 172 | sourceKey: args.sourceKey, |
| 172 | scope: args.scope, | 173 | scope: args.scope, |
| 173 | ...(args.scope === "actor" ? { actorId } : {}), | 174 | ...(args.scope === "actor" ? { actorId } : {}), |
| 174 | }); | 175 | }); |
| 175 | const existingObjectId = existing ? extractObjectId(asRecord(existing.secretJson)) : null; | 176 | const existingObjectId: string | null = existing ? extractObjectId(asRecord(existing.secretJson)) : null; |
| 176 | 177 | ||
| 177 | const objectId = (() => { | 178 | const objectId = (() => { |
| 178 | if (submittedObjectId) return submittedObjectId; | 179 | if (submittedObjectId) return submittedObjectId; |
| @@ -90,7 +90,6 @@ test("anonymous bootstrap links guest account membership", async () => { · 89 unmodified lines | |||
| 90 | expect(first.workspaceId).toContain("ws_"); | 90 | expect(first.workspaceId).toContain("ws_"); |
| 91 | expect(first.actorId).toContain("anon_"); | 91 | expect(first.actorId).toContain("anon_"); |
| 92 | expect(first.accountId).toBeDefined(); | 92 | expect(first.accountId).toBeDefined(); |
| 93 | expect(first.workspaceDocId).toBeDefined(); | ||
| 94 | expect(first.userId).toBeDefined(); | 93 | expect(first.userId).toBeDefined(); |
| 95 | 94 | ||
| 96 | const again = await t.mutation(api.database.bootstrapAnonymousSession, { | 95 | const again = await t.mutation(api.database.bootstrapAnonymousSession, { |
| @@ -99,7 +98,6 @@ test("anonymous bootstrap links guest account membership", async () => { · 2 unmodified lines | |||
| 99 | 98 | ||
| 100 | expect(again.sessionId).toBe(first.sessionId); | 99 | expect(again.sessionId).toBe(first.sessionId); |
| 101 | expect(again.accountId).toBe(first.accountId); | 100 | expect(again.accountId).toBe(first.accountId); |
| 102 | expect(again.workspaceDocId).toBe(first.workspaceDocId); | ||
| 103 | expect(again.userId).toBe(first.userId); | 101 | expect(again.userId).toBe(first.userId); |
| 104 | }); | 102 | }); |
| 105 | 103 | ||
| @@ -2,7 +2,7 @@ import { expect, test } from "bun:test"; · 1 unmodified line | |||
| 2 | import { convexTest } from "convex-test"; | 2 | import { convexTest } from "convex-test"; |
| 3 | import { Client } from "@modelcontextprotocol/sdk/client/index.js"; | 3 | import { Client } from "@modelcontextprotocol/sdk/client/index.js"; |
| 4 | import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; | 4 | import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; |
| 5 | import { api } from "./_generated/api"; | 5 | import { api, internal } from "./_generated/api"; |
| 6 | import schema from "./schema"; | 6 | import schema from "./schema"; |
| 7 | 7 | ||
| 8 | function setup() { | 8 | function setup() { |
| @@ -24,7 +24,7 @@ function createMcpTransport(t: ReturnType<typeof setup>, workspaceId: string, ac · 15 unmodified lines | |||
| 24 | 24 | ||
| 25 | return new StreamableHTTPClientTransport(url, { | 25 | return new StreamableHTTPClientTransport(url, { |
| 26 | fetch: async (input, init) => { | 26 | fetch: async (input, init) => { |
| 27 | const raw = typeof input === "string" ? input : input instanceof URL ? input.toString() : input.url; | 27 | const raw = typeof input === "string" ? input : input instanceof URL ? input.toString() : (input as Request).url; |
| 28 | const parsed = new URL(raw); | 28 | const parsed = new URL(raw); |
| 29 | const path = `${parsed.pathname}${parsed.search}${parsed.hash}`; | 29 | const path = `${parsed.pathname}${parsed.search}${parsed.hash}`; |
| 30 | return await t.fetch(path, init); | 30 | return await t.fetch(path, init); |
| @@ -82,7 +82,7 @@ test("MCP run_code survives delayed approval and completes", async () => { · 51 unmodified lines | |||
| 82 | }); | 82 | }); |
| 83 | 83 | ||
| 84 | const taskId = await waitForTaskId(t, session.workspaceId); | 84 | const taskId = await waitForTaskId(t, session.workspaceId); |
| 85 | const runTask = t.action(api.executorNode.runTask, { taskId }); | 85 | const runTask = t.action(internal.executorNode.runTask, { taskId }); |
| 86 | 86 | ||
| 87 | const approvalId = await waitForPendingApproval(t, session.workspaceId, "admin.send_announcement"); | 87 | const approvalId = await waitForPendingApproval(t, session.workspaceId, "admin.send_announcement"); |
| 88 | 88 | ||
| @@ -133,7 +133,7 @@ test("MCP run_code returns denied after approval denial", async () => { · 44 unmodified lines | |||
| 133 | }); | 133 | }); |
| 134 | 134 | ||
| 135 | const taskId = await waitForTaskId(t, session.workspaceId); | 135 | const taskId = await waitForTaskId(t, session.workspaceId); |
| 136 | const runTask = t.action(api.executorNode.runTask, { taskId }); | 136 | const runTask = t.action(internal.executorNode.runTask, { taskId }); |
| 137 | 137 | ||
| 138 | const approvalId = await waitForPendingApproval(t, session.workspaceId, "admin.delete_data"); | 138 | const approvalId = await waitForPendingApproval(t, session.workspaceId, "admin.delete_data"); |
| 139 | await t.mutation(api.executor.resolveApproval, { | 139 | await t.mutation(api.executor.resolveApproval, { |
| @@ -16,7 +16,6 @@ · 15 unmodified lines | |||
| 16 | "baseUrl": "." | 16 | "baseUrl": "." |
| 17 | }, | 17 | }, |
| 18 | "include": [ | 18 | "include": [ |
| 19 | "apps/**/*.ts", | ||
| 20 | "packages/**/*.ts", | 19 | "packages/**/*.ts", |
| 21 | "convex/**/*.ts" | 20 | "convex/**/*.ts" |
| 22 | ] | 21 | ] |