Unified · Split
| @@ -0,0 +1,5 @@ | |||
| 1 | --- | ||
| 2 | "executor": patch | ||
| 3 | --- | ||
| 4 | |||
| 5 | Avoid generating source tool type previews when building namespace metadata, and cap MCP execute help examples to five tools per source. | ||
| @@ -3,13 +3,12 @@ import type { · 2 unmodified lines | |||
| 3 | } from "@executor/platform-sdk/schema"; | 3 | } from "@executor/platform-sdk/schema"; |
| 4 | import { | 4 | import { |
| 5 | EXECUTOR_SOURCES_ADD_HELP_LINES, | 5 | EXECUTOR_SOURCES_ADD_HELP_LINES, |
| 6 | RuntimeExecutionResolverService, | 6 | RuntimeSourceCatalogStoreService, |
| 7 | createExecution, | 7 | createExecution, |
| 8 | getExecution, | 8 | getExecution, |
| 9 | resumeExecution, | 9 | resumeExecution, |
| 10 | type ExecutorRuntime, | 10 | type ExecutorRuntime, |
| 11 | } from "@executor/platform-sdk/runtime"; | 11 | } from "@executor/platform-sdk/runtime"; |
| 12 | import { ExecutionIdSchema } from "@executor/platform-sdk/schema"; | ||
| 13 | import * as Effect from "effect/Effect"; | 12 | import * as Effect from "effect/Effect"; |
| 14 | import * as Cause from "effect/Cause"; | 13 | import * as Cause from "effect/Cause"; |
| 15 | import * as Exit from "effect/Exit"; | 14 | import * as Exit from "effect/Exit"; |
| @@ -121,19 +120,33 @@ const interactionModeForServer = (server: McpServer): "live_form" | "detach" => · 105 unmodified lines | |||
| 121 | supportsManagedElicitation(server) ? "live_form" : "detach"; | 120 | supportsManagedElicitation(server) ? "live_form" : "detach"; |
| 122 | 121 | ||
| 123 | type CatalogLike = { | 122 | type CatalogLike = { |
| 124 | listNamespaces: (input: { limit: number }) => Effect.Effect< | 123 | projected: { |
| 125 | ReadonlyArray<{ namespace: string; displayName?: string }>, | 124 | toolDescriptors: Record<string, { toolPath: readonly string[] }>; |
| 126 | unknown | 125 | }; |
| 127 | >; | 126 | source: { |
| 127 | name: string; | ||
| 128 | enabled: boolean; | ||
| 129 | status: string; | ||
| 130 | }; | ||
| 128 | }; | 131 | }; |
| 129 | 132 | ||
| 130 | const buildExecuteWorkflowText = (namespaces: readonly string[] = []): string => | 133 | const executeDescriptionToolsPerSource = 5; |
| 134 | |||
| 135 | const buildExecuteWorkflowText = ( | ||
| 136 | sourceToolExamples: ReadonlyArray<{ | ||
| 137 | sourceName: string; | ||
| 138 | toolPaths: readonly string[]; | ||
| 139 | }> = [], | ||
| 140 | ): string => | ||
| 131 | [ | 141 | [ |
| 132 | "Execute TypeScript in sandbox; call tools via discovery workflow.", | 142 | "Execute TypeScript in sandbox; call tools via discovery workflow.", |
| 133 | ...(namespaces.length > 0 | 143 | ...(sourceToolExamples.length > 0 |
| 134 | ? [ | 144 | ? [ |
| 135 | "Available namespaces:", | 145 | "Available source tool examples:", |
| 136 | ...namespaces.map((namespace) => `- ${namespace}`), | 146 | ...sourceToolExamples.flatMap((source) => [ |
| 147 | `${source.sourceName}:`, | ||
| 148 | ...source.toolPaths.map((toolPath) => `- ${toolPath}`), | ||
| 149 | ]), | ||
| 137 | ] | 150 | ] |
| 138 | : []), | 151 | : []), |
| 139 | "Workflow:", | 152 | "Workflow:", |
| @@ -152,28 +165,29 @@ const loadExecuteDescription = (runtime: ExecutorRuntime): Promise<string> => · 12 unmodified lines | |||
| 152 | runControlPlane( | 165 | runControlPlane( |
| 153 | runtime, | 166 | runtime, |
| 154 | Effect.gen(function* () { | 167 | Effect.gen(function* () { |
| 155 | const resolveExecutionEnvironment = yield* RuntimeExecutionResolverService; | 168 | const sourceCatalogStore = yield* RuntimeSourceCatalogStoreService; |
| 156 | const environment = yield* resolveExecutionEnvironment({ | 169 | const catalogs = yield* sourceCatalogStore.loadWorkspaceSourceCatalogs({ |
| 157 | scopeId: runtime.localInstallation.scopeId, | 170 | scopeId: runtime.localInstallation.scopeId, |
| 158 | actorScopeId: runtime.localInstallation.actorScopeId, | 171 | actorScopeId: runtime.localInstallation.actorScopeId, |
| 159 | executionId: ExecutionIdSchema.make("exec_mcp_help"), | ||
| 160 | }); | 172 | }); |
| 161 | 173 | ||
| 162 | const catalog = environment.catalog as CatalogLike | undefined; | 174 | const sourceToolExamples = (catalogs as ReadonlyArray<CatalogLike>) |
| 163 | if (!catalog) { | 175 | .filter((catalog) => catalog.source.enabled && catalog.source.status === "connected") |
| 176 | .map((catalog) => ({ | ||
| 177 | sourceName: catalog.source.name, | ||
| 178 | toolPaths: Object.values(catalog.projected.toolDescriptors) | ||
| 179 | .map((descriptor) => descriptor.toolPath.join(".")) | ||
| 180 | .filter((toolPath) => toolPath.length > 0) | ||
| 181 | .sort((left, right) => left.localeCompare(right)) | ||
| 182 | .slice(0, executeDescriptionToolsPerSource), | ||
| 183 | })) | ||
| 184 | .filter((catalog) => catalog.toolPaths.length > 0); | ||
| 185 | |||
| 186 | if (sourceToolExamples.length === 0) { | ||
| 164 | return defaultExecuteDescription; | 187 | return defaultExecuteDescription; |
| 165 | } | 188 | } |
| 166 | 189 | ||
| 167 | const namespaces = yield* catalog.listNamespaces({ limit: 200 }).pipe( | 190 | return buildExecuteWorkflowText(sourceToolExamples); |
| 168 | Effect.map((items) => | ||
| 169 | items.length > 0 | ||
| 170 | ? items.map((item) => item.displayName ?? item.namespace) | ||
| 171 | : ["none discovered yet"], | ||
| 172 | ), | ||
| 173 | Effect.catchAll(() => Effect.succeed(["none discovered yet"])), | ||
| 174 | ); | ||
| 175 | |||
| 176 | return buildExecuteWorkflowText(namespaces); | ||
| 177 | }).pipe(Effect.catchAll(() => Effect.succeed(defaultExecuteDescription))), | 191 | }).pipe(Effect.catchAll(() => Effect.succeed(defaultExecuteDescription))), |
| 178 | ); | 192 | ); |
| 179 | 193 | ||
| @@ -0,0 +1,127 @@ | |||
| 1 | import { | ||
| 2 | describe, | ||
| 3 | expect, | ||
| 4 | it, | ||
| 5 | } from "@effect/vitest"; | ||
| 6 | import * as Effect from "effect/Effect"; | ||
| 7 | |||
| 8 | import { | ||
| 9 | createScopeSourceCatalog, | ||
| 10 | } from "./source-catalog"; | ||
| 11 | |||
| 12 | const makeLoadedCatalog = (input: { | ||
| 13 | sourceId: string; | ||
| 14 | toolPaths: ReadonlyArray<string>; | ||
| 15 | enabled?: boolean; | ||
| 16 | status?: "connected" | "error"; | ||
| 17 | }) => ({ | ||
| 18 | source: { | ||
| 19 | id: input.sourceId, | ||
| 20 | scopeId: "ws_test", | ||
| 21 | name: input.sourceId, | ||
| 22 | kind: "openapi", | ||
| 23 | endpoint: `https://example.test/${input.sourceId}`, | ||
| 24 | status: input.status ?? "connected", | ||
| 25 | enabled: input.enabled ?? true, | ||
| 26 | namespace: null, | ||
| 27 | bindingVersion: 1, | ||
| 28 | binding: {}, | ||
| 29 | importAuthPolicy: "reuse_runtime", | ||
| 30 | importAuth: { | ||
| 31 | kind: "none", | ||
| 32 | }, | ||
| 33 | auth: { | ||
| 34 | kind: "none", | ||
| 35 | }, | ||
| 36 | sourceHash: null, | ||
| 37 | lastError: null, | ||
| 38 | createdAt: 0, | ||
| 39 | updatedAt: 0, | ||
| 40 | }, | ||
| 41 | projected: { | ||
| 42 | toolDescriptors: Object.fromEntries( | ||
| 43 | input.toolPaths.map((path, index) => [ | ||
| 44 | `cap_${index}`, | ||
| 45 | { | ||
| 46 | toolPath: path.split("."), | ||
| 47 | }, | ||
| 48 | ]), | ||
| 49 | ), | ||
| 50 | }, | ||
| 51 | }) as any; | ||
| 52 | |||
| 53 | const noopScopeConfigStore = { | ||
| 54 | load: () => Effect.die("unexpected config load"), | ||
| 55 | writeProject: () => Effect.void, | ||
| 56 | resolveRelativePath: (path: string) => Effect.succeed(path), | ||
| 57 | } as any; | ||
| 58 | |||
| 59 | const noopScopeStateStore = { | ||
| 60 | load: () => Effect.die("unexpected scope state load"), | ||
| 61 | write: () => Effect.void, | ||
| 62 | } as any; | ||
| 63 | |||
| 64 | const noopSourceArtifactStore = { | ||
| 65 | build: () => Effect.die("unexpected source artifact build"), | ||
| 66 | read: () => Effect.die("unexpected source artifact read"), | ||
| 67 | write: () => Effect.void, | ||
| 68 | remove: () => Effect.void, | ||
| 69 | } as any; | ||
| 70 | |||
| 71 | describe("scope source catalog", () => { | ||
| 72 | it.effect("lists namespaces from projected tool paths without loading the tool index", () => | ||
| 73 | Effect.gen(function* () { | ||
| 74 | const catalog = createScopeSourceCatalog({ | ||
| 75 | scopeId: "ws_test" as any, | ||
| 76 | actorScopeId: "acc_test" as any, | ||
| 77 | sourceCatalogStore: { | ||
| 78 | loadWorkspaceSourceCatalogs: () => Effect.succeed([ | ||
| 79 | makeLoadedCatalog({ | ||
| 80 | sourceId: "github", | ||
| 81 | toolPaths: [ | ||
| 82 | "github.repos.get", | ||
| 83 | "github.repos.list", | ||
| 84 | "github.issues.get", | ||
| 85 | ], | ||
| 86 | }), | ||
| 87 | makeLoadedCatalog({ | ||
| 88 | sourceId: "linear", | ||
| 89 | toolPaths: [ | ||
| 90 | "linear.issue.create", | ||
| 91 | ], | ||
| 92 | }), | ||
| 93 | makeLoadedCatalog({ | ||
| 94 | sourceId: "disabled", | ||
| 95 | toolPaths: [ | ||
| 96 | "disabled.admin.delete", | ||
| 97 | ], | ||
| 98 | enabled: false, | ||
| 99 | }), | ||
| 100 | ]), | ||
| 101 | loadWorkspaceSourceCatalogToolIndex: () => Effect.die("unexpected tool index load"), | ||
| 102 | loadWorkspaceSourceCatalogToolByPath: () => Effect.die("unexpected tool lookup"), | ||
| 103 | } as any, | ||
| 104 | scopeConfigStore: noopScopeConfigStore, | ||
| 105 | scopeStateStore: noopScopeStateStore, | ||
| 106 | sourceArtifactStore: noopSourceArtifactStore, | ||
| 107 | runtimeLocalScope: null, | ||
| 108 | }); | ||
| 109 | |||
| 110 | const namespaces = yield* catalog.listNamespaces({ limit: 10 }); | ||
| 111 | |||
| 112 | expect(namespaces).toEqual([ | ||
| 113 | { | ||
| 114 | namespace: "github.issues", | ||
| 115 | toolCount: 1, | ||
| 116 | }, | ||
| 117 | { | ||
| 118 | namespace: "github.repos", | ||
| 119 | toolCount: 2, | ||
| 120 | }, | ||
| 121 | { | ||
| 122 | namespace: "linear.issue", | ||
| 123 | toolCount: 1, | ||
| 124 | }, | ||
| 125 | ]); | ||
| 126 | })); | ||
| 127 | }); | ||
| @@ -1,6 +1,7 @@ | |||
| 1 | import { | 1 | import { |
| 2 | createToolCatalogFromEntries, | 2 | createToolCatalogFromEntries, |
| 3 | type ToolCatalog, | 3 | type ToolCatalog, |
| 4 | type ToolNamespace, | ||
| 4 | } from "@executor/codemode-core"; | 5 | } from "@executor/codemode-core"; |
| 5 | import type { | 6 | import type { |
| 6 | ScopeId, | 7 | ScopeId, |
| @@ -80,6 +81,49 @@ const hasSubstringMatch = (value: string, queryToken: string): boolean => { · 73 unmodified lines | |||
| 80 | const queryTokenWeight = (token: string): number => | 81 | const queryTokenWeight = (token: string): number => |
| 81 | LOW_SIGNAL_QUERY_TOKENS.has(token) ? 0.25 : 1; | 82 | LOW_SIGNAL_QUERY_TOKENS.has(token) ? 0.25 : 1; |
| 82 | 83 | ||
| 84 | const namespaceFromPath = (path: string): string => { | ||
| 85 | const [first, second] = path.split("."); | ||
| 86 | return second ? `${first}.${second}` : first; | ||
| 87 | }; | ||
| 88 | |||
| 89 | const sortNamespaces = (namespaces: Iterable<ToolNamespace>): ToolNamespace[] => | ||
| 90 | [...namespaces].sort((left, right) => left.namespace.localeCompare(right.namespace)); | ||
| 91 | |||
| 92 | const loadWorkspaceCatalogNamespaces = (input: { | ||
| 93 | scopeId: Source["scopeId"]; | ||
| 94 | actorScopeId: ScopeId; | ||
| 95 | sourceCatalogStore: Effect.Effect.Success<typeof RuntimeSourceCatalogStoreService>; | ||
| 96 | }): Effect.Effect< | ||
| 97 | readonly ToolNamespace[], | ||
| 98 | Error, | ||
| 99 | ScopeStorageServices | ||
| 100 | > => | ||
| 101 | Effect.gen(function* () { | ||
| 102 | const catalogs = yield* input.sourceCatalogStore.loadWorkspaceSourceCatalogs({ | ||
| 103 | scopeId: input.scopeId, | ||
| 104 | actorScopeId: input.actorScopeId, | ||
| 105 | }); | ||
| 106 | |||
| 107 | const namespaces = new Map<string, ToolNamespace>(); | ||
| 108 | |||
| 109 | for (const catalog of catalogs) { | ||
| 110 | if (!catalog.source.enabled || catalog.source.status !== "connected") { | ||
| 111 | continue; | ||
| 112 | } | ||
| 113 | |||
| 114 | for (const descriptor of Object.values(catalog.projected.toolDescriptors)) { | ||
| 115 | const namespace = namespaceFromPath(descriptor.toolPath.join(".")); | ||
| 116 | const existing = namespaces.get(namespace); | ||
| 117 | namespaces.set(namespace, { | ||
| 118 | namespace, | ||
| 119 | toolCount: (existing?.toolCount ?? 0) + 1, | ||
| 120 | }); | ||
| 121 | } | ||
| 122 | } | ||
| 123 | |||
| 124 | return sortNamespaces(namespaces.values()); | ||
| 125 | }); | ||
| 126 | |||
| 83 | export const loadWorkspaceCatalogTools = (input: { | 127 | export const loadWorkspaceCatalogTools = (input: { |
| 84 | scopeId: Source["scopeId"]; | 128 | scopeId: Source["scopeId"]; |
| 85 | actorScopeId: ScopeId; | 129 | actorScopeId: ScopeId; |
| @@ -291,9 +335,14 @@ export const createScopeSourceCatalog = (input: { · 205 unmodified lines | |||
| 291 | return { | 335 | return { |
| 292 | listNamespaces: ({ limit }) => | 336 | listNamespaces: ({ limit }) => |
| 293 | provideRuntimeLocalScope( | 337 | provideRuntimeLocalScope( |
| 294 | Effect.flatMap(createSharedCatalog(false), (catalog) => | 338 | provideWorkspaceStorage(Effect.map( |
| 295 | catalog.listNamespaces({ limit }), | 339 | loadWorkspaceCatalogNamespaces({ |
| 296 | ), | 340 | scopeId: input.scopeId, |
| 341 | actorScopeId: input.actorScopeId, | ||
| 342 | sourceCatalogStore: input.sourceCatalogStore, | ||
| 343 | }), | ||
| 344 | (namespaces) => namespaces.slice(0, limit), | ||
| 345 | )), | ||
| 297 | input.runtimeLocalScope, | 346 | input.runtimeLocalScope, |
| 298 | ), | 347 | ), |
| 299 | 348 | ||