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 | 3 | } from "@executor/platform-sdk/schema"; |
| 4 | 4 | import { |
| 5 | 5 | EXECUTOR_SOURCES_ADD_HELP_LINES, |
| 6 | − RuntimeExecutionResolverService, | |
| 6 | + RuntimeSourceCatalogStoreService, | |
| 7 | 7 | createExecution, |
| 8 | 8 | getExecution, |
| 9 | 9 | resumeExecution, |
| 10 | 10 | type ExecutorRuntime, |
| 11 | 11 | } from "@executor/platform-sdk/runtime"; |
| 12 | −import { ExecutionIdSchema } from "@executor/platform-sdk/schema"; | |
| 13 | 12 | import * as Effect from "effect/Effect"; |
| 14 | 13 | import * as Cause from "effect/Cause"; |
| 15 | 14 | import * as Exit from "effect/Exit"; |
| @@ -121,19 +120,33 @@ const interactionModeForServer = (server: McpServer): "live_form" | "detach" => · 105 unmodified lines | ||
| 121 | 120 | supportsManagedElicitation(server) ? "live_form" : "detach"; |
| 122 | 121 | |
| 123 | 122 | type CatalogLike = { |
| 124 | − listNamespaces: (input: { limit: number }) => Effect.Effect< | |
| 125 | − ReadonlyArray<{ namespace: string; displayName?: string }>, | |
| 126 | − unknown | |
| 127 | − >; | |
| 123 | + projected: { | |
| 124 | + toolDescriptors: Record<string, { toolPath: readonly string[] }>; | |
| 125 | + }; | |
| 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 | 142 | "Execute TypeScript in sandbox; call tools via discovery workflow.", |
| 133 | − ...(namespaces.length > 0 | |
| 143 | + ...(sourceToolExamples.length > 0 | |
| 134 | 144 | ? [ |
| 135 | − "Available namespaces:", | |
| 136 | − ...namespaces.map((namespace) => `- ${namespace}`), | |
| 145 | + "Available source tool examples:", | |
| 146 | + ...sourceToolExamples.flatMap((source) => [ | |
| 147 | + `${source.sourceName}:`, | |
| 148 | + ...source.toolPaths.map((toolPath) => `- ${toolPath}`), | |
| 149 | + ]), | |
| 137 | 150 | ] |
| 138 | 151 | : []), |
| 139 | 152 | "Workflow:", |
| @@ -152,28 +165,29 @@ const loadExecuteDescription = (runtime: ExecutorRuntime): Promise<string> => · 12 unmodified lines | ||
| 152 | 165 | runControlPlane( |
| 153 | 166 | runtime, |
| 154 | 167 | Effect.gen(function* () { |
| 155 | − const resolveExecutionEnvironment = yield* RuntimeExecutionResolverService; | |
| 156 | − const environment = yield* resolveExecutionEnvironment({ | |
| 168 | + const sourceCatalogStore = yield* RuntimeSourceCatalogStoreService; | |
| 169 | + const catalogs = yield* sourceCatalogStore.loadWorkspaceSourceCatalogs({ | |
| 157 | 170 | scopeId: runtime.localInstallation.scopeId, |
| 158 | 171 | actorScopeId: runtime.localInstallation.actorScopeId, |
| 159 | − executionId: ExecutionIdSchema.make("exec_mcp_help"), | |
| 160 | 172 | }); |
| 161 | 173 | |
| 162 | − const catalog = environment.catalog as CatalogLike | undefined; | |
| 163 | − if (!catalog) { | |
| 174 | + const sourceToolExamples = (catalogs as ReadonlyArray<CatalogLike>) | |
| 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 | 187 | return defaultExecuteDescription; |
| 165 | 188 | } |
| 166 | 189 | |
| 167 | − const namespaces = yield* catalog.listNamespaces({ limit: 200 }).pipe( | |
| 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); | |
| 190 | + return buildExecuteWorkflowText(sourceToolExamples); | |
| 177 | 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 | 1 | import { |
| 2 | 2 | createToolCatalogFromEntries, |
| 3 | 3 | type ToolCatalog, |
| 4 | + type ToolNamespace, | |
| 4 | 5 | } from "@executor/codemode-core"; |
| 5 | 6 | import type { |
| 6 | 7 | ScopeId, |
| @@ -80,6 +81,49 @@ const hasSubstringMatch = (value: string, queryToken: string): boolean => { · 73 unmodified lines | ||
| 80 | 81 | const queryTokenWeight = (token: string): number => |
| 81 | 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 | 127 | export const loadWorkspaceCatalogTools = (input: { |
| 84 | 128 | scopeId: Source["scopeId"]; |
| 85 | 129 | actorScopeId: ScopeId; |
| @@ -291,9 +335,14 @@ export const createScopeSourceCatalog = (input: { · 205 unmodified lines | ||
| 291 | 335 | return { |
| 292 | 336 | listNamespaces: ({ limit }) => |
| 293 | 337 | provideRuntimeLocalScope( |
| 294 | − Effect.flatMap(createSharedCatalog(false), (catalog) => | |
| 295 | − catalog.listNamespaces({ limit }), | |
| 296 | − ), | |
| 338 | + provideWorkspaceStorage(Effect.map( | |
| 339 | + loadWorkspaceCatalogNamespaces({ | |
| 340 | + scopeId: input.scopeId, | |
| 341 | + actorScopeId: input.actorScopeId, | |
| 342 | + sourceCatalogStore: input.sourceCatalogStore, | |
| 343 | + }), | |
| 344 | + (namespaces) => namespaces.slice(0, limit), | |
| 345 | + )), | |
| 297 | 346 | input.runtimeLocalScope, |
| 298 | 347 | ), |
| 299 | 348 | |