History

rework naming

Rhys Sullivan committed 7 months ago

Commit da613211 parent f0c45e713 files · +310 −304Browse files

Unified · Split

13 files
  1. examples/sqlite-sdk-consumer/src/sqlite-backend.ts +90 −87
  2. packages/platform/sdk-file/src/effect.ts +1 −1
  3. packages/platform/sdk-file/src/index.ts +41 −41
  4. packages/platform/sdk/src/backend.ts +61 −51
  5. packages/platform/sdk/src/executor.test.ts +14 −25
  6. packages/platform/sdk/src/runtime/execution/mcp-resume.test.ts +1 −1
  7. packages/platform/sdk/src/runtime/executor-runtime.test.ts +46 −30
  8. packages/platform/sdk/src/runtime/sources/source-adapters/__fixtures__/mcp-stdio-server.mjs +26 −0
  9. packages/platform/sdk/src/runtime/sources/source-adapters/mcp.test.ts +20 −38
  10. packages/platform/sdk/src/runtime/sources/source-discovery.test.ts +1 −1
  11. packages/platform/sdk/src/runtime/sources/source-inspection.test.ts +1 −1
  12. packages/sources/mcp/src/adapter.ts +3 −1
  13. packages/sources/mcp/src/connection.ts +5 −27
examples/sqlite-sdk-consumer/src/sqlite-backend.ts+90−87
@@ -14,10 +14,10 @@ import { · 13 unmodified lines
14import {14import {
15 createExecutorBackend,15 createExecutorBackend,
16 type ExecutorBackend,16 type ExecutorBackend,
17 type ExecutorBackendServices,17 type ExecutorBackendRepositories,
18 type ExecutorScopeConfigBackend,18 type ExecutorWorkspaceConfigRepository,
19 type ExecutorScopeStateBackend,19 type ExecutorWorkspaceStateRepository,
20 type ExecutorSourceArtifactBackend,20 type ExecutorWorkspaceSourceArtifactRepository,
21} from "@executor/platform-sdk";21} from "@executor/platform-sdk";
22import type {22import type {
23 AuthArtifact,23 AuthArtifact,
@@ -53,10 +53,13 @@ export type CreateSqliteExecutorBackendOptions = { · 29 unmodified lines
53 actorScopeId?: string;53 actorScopeId?: string;
54};54};
55 55
56type ScopeConfig = Awaited<ReturnType<ExecutorScopeConfigBackend["load"]>>;56type ScopeConfig = Awaited<ReturnType<ExecutorWorkspaceConfigRepository["load"]>>;
57type ScopeState = Awaited<ReturnType<ExecutorScopeStateBackend["load"]>>;57type ScopeState = Awaited<ReturnType<ExecutorWorkspaceStateRepository["load"]>>;
58type SourceArtifact = ReturnType<ExecutorSourceArtifactBackend["build"]>;58type SourceArtifact =
59type SourceArtifactBuildInput = Parameters<ExecutorSourceArtifactBackend["build"]>[0];59 ReturnType<ExecutorWorkspaceSourceArtifactRepository["build"]>;
60type SourceArtifactBuildInput = Parameters<
61 ExecutorWorkspaceSourceArtifactRepository["build"]
62>[0];
60 63
61type SecretMaterialSummary = {64type SecretMaterialSummary = {
62 id: string;65 id: string;
@@ -924,7 +927,7 @@ export const createSqliteExecutorBackend = ( · 861 unmodified lines
924 : (options.databasePath ?? ":memory:");927 : (options.databasePath ?? ":memory:");
925 928
926 return createExecutorBackend({929 return createExecutorBackend({
927 loadServices: () => {930 loadRepositories: () => {
928 const store = openSqliteStore(databasePath);931 const store = openSqliteStore(databasePath);
929 const { auth, secrets, executions } = createStorageDomains(store);932 const { auth, secrets, executions } = createStorageDomains(store);
930 933
@@ -937,9 +940,21 @@ export const createSqliteExecutorBackend = ( · 6 unmodified lines
937 databasePath,940 databasePath,
938 },941 },
939 },942 },
940 storage: {943 installation: {
941 installation: {944 load: () => {
942 load: () => {945 const row = store.db.select().from(installations).where(eq(installations.key, "active")).get();
946 return row
947 ? {
948 scopeId: row.scopeId as LocalInstallation["scopeId"],
949 actorScopeId: row.actorScopeId as LocalInstallation["actorScopeId"],
950 resolutionScopeIds: parseJson<LocalInstallation["resolutionScopeIds"]>(
951 row.resolutionScopeIdsJson,
952 ),
953 }
954 : createInstallation(options);
955 },
956 getOrProvision: () => {
957 const installation = (() => {
943 const row = store.db.select().from(installations).where(eq(installations.key, "active")).get();958 const row = store.db.select().from(installations).where(eq(installations.key, "active")).get();
944 return row959 return row
945 ? {960 ? {
@@ -950,37 +965,25 @@ export const createSqliteExecutorBackend = ( · 4 unmodified lines
950 ),965 ),
951 }966 }
952 : createInstallation(options);967 : createInstallation(options);
953 },968 })();
954 getOrProvision: () => {969 store.db.insert(installations).values({
955 const installation = (() => {970 key: "active",
956 const row = store.db.select().from(installations).where(eq(installations.key, "active")).get();971 scopeId: installation.scopeId,
957 return row972 actorScopeId: installation.actorScopeId,
958 ? {973 resolutionScopeIdsJson: JSON.stringify(installation.resolutionScopeIds),
959 scopeId: row.scopeId as LocalInstallation["scopeId"],974 }).onConflictDoUpdate({
960 actorScopeId: row.actorScopeId as LocalInstallation["actorScopeId"],975 target: installations.key,
961 resolutionScopeIds: parseJson<LocalInstallation["resolutionScopeIds"]>(976 set: {
962 row.resolutionScopeIdsJson,
963 ),
964 }
965 : createInstallation(options);
966 })();
967 store.db.insert(installations).values({
968 key: "active",
969 scopeId: installation.scopeId,977 scopeId: installation.scopeId,
970 actorScopeId: installation.actorScopeId,978 actorScopeId: installation.actorScopeId,
971 resolutionScopeIdsJson: JSON.stringify(installation.resolutionScopeIds),979 resolutionScopeIdsJson: JSON.stringify(installation.resolutionScopeIds),
972 }).onConflictDoUpdate({980 },
973 target: installations.key,981 }).run();
974 set: {982 return installation;
975 scopeId: installation.scopeId,
976 actorScopeId: installation.actorScopeId,
977 resolutionScopeIdsJson: JSON.stringify(installation.resolutionScopeIds),
978 },
979 }).run();
980 return installation;
981 },
982 },983 },
983 scopeConfig: {984 },
985 workspace: {
986 config: {
984 load: () => {987 load: () => {
985 const row = store.db.select().from(scopeConfigs).where(eq(scopeConfigs.key, "project")).get();988 const row = store.db.select().from(scopeConfigs).where(eq(scopeConfigs.key, "project")).get();
986 const projectConfig = row989 const projectConfig = row
@@ -1003,7 +1006,7 @@ export const createSqliteExecutorBackend = ( · 16 unmodified lines
1003 },1006 },
1004 resolveRelativePath: ({ path, scopeRoot }) => resolvePath(scopeRoot, path),1007 resolveRelativePath: ({ path, scopeRoot }) => resolvePath(scopeRoot, path),
1005 },1008 },
1006 scopeState: {1009 state: {
1007 load: () => {1010 load: () => {
1008 const row = store.db.select().from(scopeStates).where(eq(scopeStates.key, "active")).get();1011 const row = store.db.select().from(scopeStates).where(eq(scopeStates.key, "active")).get();
1009 return row ? parseJson<ScopeState>(row.stateJson) : defaultScopeState();1012 return row ? parseJson<ScopeState>(row.stateJson) : defaultScopeState();
@@ -1039,55 +1042,52 @@ export const createSqliteExecutorBackend = ( · 29 unmodified lines
1039 store.db.delete(sourceArtifacts).where(eq(sourceArtifacts.sourceId, sourceId)).run();1042 store.db.delete(sourceArtifacts).where(eq(sourceArtifacts.sourceId, sourceId)).run();
1040 },1043 },
1041 },1044 },
1042 auth,1045 sourceAuth: auth,
1043 secrets: {1046 },
1044 ...secrets,1047 secrets: {
1045 resolve: ({ ref }) => {1048 ...secrets,
1046 const material = secrets.getById(1049 resolve: ({ ref }) => {
1047 ref.handle as SecretMaterial["id"],1050 const material = secrets.getById(
1048 );1051 ref.handle as SecretMaterial["id"],
1049 if (!material || material.value === null) {1052 );
1050 throw new Error(`Missing secret material ${ref.handle}`);1053 if (!material || material.value === null) {
1051 }1054 throw new Error(`Missing secret material ${ref.handle}`);
1052 return material.value;1055 }
1053 },1056 return material.value;
1054 store: ({ purpose, value, name, providerId }) => {
1055 const now = Date.now();
1056 const id = SecretMaterialIdSchema.make(`secret_${randomUUID()}`);
1057 const material: SecretMaterial = {
1058 id,
1059 providerId: providerId ?? SQLITE_SECRET_PROVIDER_ID,
1060 handle: id,
1061 name: name ?? null,
1062 purpose,
1063 value,
1064 createdAt: now,
1065 updatedAt: now,
1066 };
1067 secrets.upsert(material);
1068 return {
1069 providerId: material.providerId,
1070 handle: material.handle,
1071 } satisfies SecretRef;
1072 },
1073 delete: (ref) =>
1074 secrets.removeById(ref.handle as SecretMaterial["id"]),
1075 update: ({ ref, name, value }) => {
1076 const updated = secrets.updateById(
1077 ref.handle as SecretMaterial["id"],
1078 { name, value },
1079 );
1080 if (!updated) {
1081 throw new Error(`Missing secret material ${ref.handle}`);
1082 }
1083 return updated;
1084 },
1085 },1057 },
1086 executions,1058 store: ({ purpose, value, name, providerId }) => {
1087 close: async () => {1059 const now = Date.now();
1088 store.close();1060 const id = SecretMaterialIdSchema.make(`secret_${randomUUID()}`);
1061 const material: SecretMaterial = {
1062 id,
1063 providerId: providerId ?? SQLITE_SECRET_PROVIDER_ID,
1064 handle: id,
1065 name: name ?? null,
1066 purpose,
1067 value,
1068 createdAt: now,
1069 updatedAt: now,
1070 };
1071 secrets.upsert(material);
1072 return {
1073 providerId: material.providerId,
1074 handle: material.handle,
1075 } satisfies SecretRef;
1076 },
1077 delete: (ref) =>
1078 secrets.removeById(ref.handle as SecretMaterial["id"]),
1079 update: ({ ref, name, value }) => {
1080 const updated = secrets.updateById(
1081 ref.handle as SecretMaterial["id"],
1082 { name, value },
1083 );
1084 if (!updated) {
1085 throw new Error(`Missing secret material ${ref.handle}`);
1086 }
1087 return updated;
1089 },1088 },
1090 },1089 },
1090 executions,
1091 instanceConfig: {1091 instanceConfig: {
1092 resolve: () => ({1092 resolve: () => ({
1093 platform: "sqlite-sdk-example",1093 platform: "sqlite-sdk-example",
@@ -1101,7 +1101,10 @@ export const createSqliteExecutorBackend = ( · 7 unmodified lines
1101 defaultSecretStoreProvider: SQLITE_SECRET_PROVIDER_ID,1101 defaultSecretStoreProvider: SQLITE_SECRET_PROVIDER_ID,
1102 }),1102 }),
1103 },1103 },
1104 } satisfies ExecutorBackendServices;1104 close: async () => {
1105 store.close();
1106 },
1107 } satisfies ExecutorBackendRepositories;
1105 },1108 },
1106 });1109 });
1107};1110};
packages/platform/sdk-file/src/effect.ts+1−1
@@ -2,7 +2,7 @@ export type { CreateLocalExecutorBackendOptions } from "./index"; · 1 unmodified line
2export {2export {
3 buildLocalSourceArtifact,3 buildLocalSourceArtifact,
4 createLocalExecutorEffect,4 createLocalExecutorEffect,
5 createLocalExecutorServicesEffect,5 createLocalExecutorRepositoriesEffect,
6 deriveLocalInstallation,6 deriveLocalInstallation,
7 getOrProvisionLocalInstallation,7 getOrProvisionLocalInstallation,
8 loadLocalExecutorConfig,8 loadLocalExecutorConfig,
packages/platform/sdk-file/src/index.ts+41−41
@@ -5,10 +5,10 @@ import { · 4 unmodified lines
5 createExecutorBackend,5 createExecutorBackend,
6 type Executor,6 type Executor,
7 type ExecutorBackend,7 type ExecutorBackend,
8 type ExecutorBackendServices,8 type ExecutorBackendRepositories,
9 type ExecutorLocalToolBackend,
10 type ExecutorSourceTypeDeclarationsBackend,
11 type ExecutorScopeDescriptor,9 type ExecutorScopeDescriptor,
10 type ExecutorWorkspaceLocalToolRepository,
11 type ExecutorWorkspaceSourceTypeDeclarationsRepository,
12} from "@executor/platform-sdk";12} from "@executor/platform-sdk";
13import {13import {
14 createExecutorEffect,14 createExecutorEffect,
@@ -176,14 +176,14 @@ const createBoundSourceArtifactStore = ( · 161 unmodified lines
176const createBoundLocalToolRuntimeLoader = (176const createBoundLocalToolRuntimeLoader = (
177 context: ResolvedLocalWorkspaceContext,177 context: ResolvedLocalWorkspaceContext,
178 fileSystem: FileSystem.FileSystem,178 fileSystem: FileSystem.FileSystem,
179): ExecutorLocalToolBackend => ({179): ExecutorWorkspaceLocalToolRepository => ({
180 load: () =>180 load: () =>
181 bindFileSystem(fileSystem, loadLocalToolRuntime(context)),181 bindFileSystem(fileSystem, loadLocalToolRuntime(context)),
182});182});
183 183
184const createBoundSourceTypeDeclarationsRefresher = (184const createBoundSourceTypeDeclarationsRefresher = (
185 context: ResolvedLocalWorkspaceContext,185 context: ResolvedLocalWorkspaceContext,
186): ExecutorSourceTypeDeclarationsBackend => ({186): ExecutorWorkspaceSourceTypeDeclarationsRepository => ({
187 refreshWorkspaceInBackground: ({ entries }) =>187 refreshWorkspaceInBackground: ({ entries }) =>
188 Effect.sync(() => {188 Effect.sync(() => {
189 refreshWorkspaceSourceTypeDeclarationsInBackground({189 refreshWorkspaceSourceTypeDeclarationsInBackground({
@@ -217,10 +217,10 @@ const toExecutorScopeContext = ( · 27 unmodified lines
217 },217 },
218});218});
219 219
220export const createLocalExecutorServicesEffect = (220export const createLocalExecutorRepositoriesEffect = (
221 options: CreateLocalExecutorBackendOptions = {},221 options: CreateLocalExecutorBackendOptions = {},
222 runtimeOptions: ExecutorRuntimeOptions = {},222 runtimeOptions: ExecutorRuntimeOptions = {},
223): Effect.Effect<ExecutorBackendServices, Error> =>223): Effect.Effect<ExecutorBackendRepositories, Error> =>
224 Effect.gen(function* () {224 Effect.gen(function* () {
225 const fileSystem = yield* FileSystem.FileSystem;225 const fileSystem = yield* FileSystem.FileSystem;
226 const workspaceContext = yield* bindFileSystem(226 const workspaceContext = yield* bindFileSystem(
@@ -252,10 +252,10 @@ export const createLocalExecutorServicesEffect = ( · 25 unmodified lines
252 252
253 return {253 return {
254 scope: toExecutorScopeContext(workspaceContext),254 scope: toExecutorScopeContext(workspaceContext),
255 storage: {255 installation: createBoundInstallationStore(workspaceContext),
256 installation: createBoundInstallationStore(workspaceContext),256 workspace: {
257 scopeConfig: workspaceConfigStore,257 config: workspaceConfigStore,
258 scopeState: createBoundWorkspaceStateStore(258 state: createBoundWorkspaceStateStore(
259 workspaceContext,259 workspaceContext,
260 fileSystem,260 fileSystem,
261 ),261 ),
@@ -263,7 +263,7 @@ export const createLocalExecutorServicesEffect = ( · 1 unmodified line
263 workspaceContext,263 workspaceContext,
264 fileSystem,264 fileSystem,
265 ),265 ),
266 auth: {266 sourceAuth: {
267 artifacts: executorStateStorage.executorState.authArtifacts,267 artifacts: executorStateStorage.executorState.authArtifacts,
268 leases: executorStateStorage.executorState.authLeases,268 leases: executorStateStorage.executorState.authLeases,
269 sourceOauthClients:269 sourceOauthClients:
@@ -275,45 +275,45 @@ export const createLocalExecutorServicesEffect = ( · 5 unmodified lines
275 sourceSessions:275 sourceSessions:
276 executorStateStorage.executorState.sourceAuthSessions,276 executorStateStorage.executorState.sourceAuthSessions,
277 },277 },
278 secrets: {278 localTools: createBoundLocalToolRuntimeLoader(
279 ...executorStateStorage.executorState.secretMaterials,279 workspaceContext,
280 resolve: resolveSecretMaterial,280 fileSystem,
281 store: createDefaultSecretMaterialStorer({281 ),
282 executorState: executorStateStorage.executorState,282 sourceTypeDeclarations:
283 }),283 createBoundSourceTypeDeclarationsRefresher(workspaceContext),
284 delete: createDefaultSecretMaterialDeleter({284 },
285 executorState: executorStateStorage.executorState,285 secrets: {
286 }),286 ...executorStateStorage.executorState.secretMaterials,
287 update: createDefaultSecretMaterialUpdater({287 resolve: resolveSecretMaterial,
288 executorState: executorStateStorage.executorState,288 store: createDefaultSecretMaterialStorer({
289 }),289 executorState: executorStateStorage.executorState,
290 },290 }),
291 executions: {291 delete: createDefaultSecretMaterialDeleter({
292 runs: executorStateStorage.executorState.executions,292 executorState: executorStateStorage.executorState,
293 interactions:293 }),
294 executorStateStorage.executorState.executionInteractions,294 update: createDefaultSecretMaterialUpdater({
295 steps: executorStateStorage.executorState.executionSteps,295 executorState: executorStateStorage.executorState,
296 },296 }),
297 close: executorStateStorage.close,297 },
298 executions: {
299 runs: executorStateStorage.executorState.executions,
300 interactions:
301 executorStateStorage.executorState.executionInteractions,
302 steps: executorStateStorage.executorState.executionSteps,
298 },303 },
299 localTools: createBoundLocalToolRuntimeLoader(
300 workspaceContext,
301 fileSystem,
302 ),
303 sourceTypeDeclarations:
304 createBoundSourceTypeDeclarationsRefresher(workspaceContext),
305 instanceConfig: {304 instanceConfig: {
306 resolve: createLocalInstanceConfigResolver(),305 resolve: createLocalInstanceConfigResolver(),
307 },306 },
308 } satisfies ExecutorBackendServices;307 close: executorStateStorage.close,
308 } satisfies ExecutorBackendRepositories;
309 }).pipe(Effect.provide(NodeFileSystem.layer));309 }).pipe(Effect.provide(NodeFileSystem.layer));
310 310
311export const createLocalExecutorBackend = (311export const createLocalExecutorBackend = (
312 options: CreateLocalExecutorBackendOptions = {},312 options: CreateLocalExecutorBackendOptions = {},
313): ExecutorBackend =>313): ExecutorBackend =>
314 createExecutorBackend({314 createExecutorBackend({
315 loadServices: (runtimeOptions: ExecutorRuntimeOptions) =>315 loadRepositories: (runtimeOptions: ExecutorRuntimeOptions) =>
316 createLocalExecutorServicesEffect(options, runtimeOptions),316 createLocalExecutorRepositoriesEffect(options, runtimeOptions),
317 });317 });
318 318
319export const createLocalExecutorRuntime = (319export const createLocalExecutorRuntime = (
packages/platform/sdk/src/backend.ts+61−51
@@ -47,15 +47,19 @@ type PublicizeObject<T> = { · 46 unmodified lines
47 : T[Key];47 : T[Key];
48};48};
49 49
50export type ExecutorInstallationBackend = PublicizeObject<BoundInstallationStore>;50export type ExecutorInstallationRepository = PublicizeObject<BoundInstallationStore>;
51export type ExecutorScopeConfigBackend = PublicizeObject<BoundScopeConfigStore>;51export type ExecutorWorkspaceConfigRepository = PublicizeObject<BoundScopeConfigStore>;
52export type ExecutorScopeStateBackend = PublicizeObject<BoundScopeStateStore>;52export type ExecutorWorkspaceStateRepository = PublicizeObject<BoundScopeStateStore>;
53export type ExecutorSourceArtifactBackend = PublicizeObject<BoundSourceArtifactStore>;53export type ExecutorWorkspaceSourceArtifactRepository = PublicizeObject<
54export type ExecutorLocalToolBackend = PublicizeObject<BoundLocalToolRuntimeLoader>;54 BoundSourceArtifactStore
55export type ExecutorSourceTypeDeclarationsBackend = PublicizeObject<55>;
56export type ExecutorWorkspaceLocalToolRepository = PublicizeObject<
57 BoundLocalToolRuntimeLoader
58>;
59export type ExecutorWorkspaceSourceTypeDeclarationsRepository = PublicizeObject<
56 BoundSourceTypeDeclarationsRefresher60 BoundSourceTypeDeclarationsRefresher
57>;61>;
58export type ExecutorAuthBackend = {62export type ExecutorWorkspaceSourceAuthRepository = {
59 artifacts: PublicizeObject<RuntimeAuthStorageServices["artifacts"]>;63 artifacts: PublicizeObject<RuntimeAuthStorageServices["artifacts"]>;
60 leases: PublicizeObject<RuntimeAuthStorageServices["leases"]>;64 leases: PublicizeObject<RuntimeAuthStorageServices["leases"]>;
61 sourceOauthClients: PublicizeObject<RuntimeAuthStorageServices["sourceOauthClients"]>;65 sourceOauthClients: PublicizeObject<RuntimeAuthStorageServices["sourceOauthClients"]>;
@@ -63,31 +67,33 @@ export type ExecutorAuthBackend = { · 1 unmodified line
63 providerGrants: PublicizeObject<RuntimeAuthStorageServices["providerGrants"]>;67 providerGrants: PublicizeObject<RuntimeAuthStorageServices["providerGrants"]>;
64 sourceSessions: PublicizeObject<RuntimeAuthStorageServices["sourceSessions"]>;68 sourceSessions: PublicizeObject<RuntimeAuthStorageServices["sourceSessions"]>;
65};69};
66export type ExecutorSecretsBackend = PublicizeObject<RuntimeSecretsStorageServices>;70export type ExecutorSecretRepository = PublicizeObject<RuntimeSecretsStorageServices>;
67export type ExecutorExecutionsBackend = {71export type ExecutorExecutionRepository = {
68 runs: PublicizeObject<RuntimeExecutionStorageServices["runs"]>;72 runs: PublicizeObject<RuntimeExecutionStorageServices["runs"]>;
69 interactions: PublicizeObject<RuntimeExecutionStorageServices["interactions"]>;73 interactions: PublicizeObject<RuntimeExecutionStorageServices["interactions"]>;
70 steps: PublicizeObject<RuntimeExecutionStorageServices["steps"]>;74 steps: PublicizeObject<RuntimeExecutionStorageServices["steps"]>;
71};75};
72export type ExecutorInstanceConfigBackend = PublicizeObject<RuntimeInstanceConfigService>;76export type ExecutorInstanceConfigRepository = PublicizeObject<
77 RuntimeInstanceConfigService
78>;
73 79
74export type ExecutorStorageBackend = {80export type ExecutorWorkspaceRepository = {
75 installation: ExecutorInstallationBackend;81 config: ExecutorWorkspaceConfigRepository;
76 scopeConfig: ExecutorScopeConfigBackend;82 state: ExecutorWorkspaceStateRepository;
77 scopeState: ExecutorScopeStateBackend;83 sourceArtifacts: ExecutorWorkspaceSourceArtifactRepository;
78 sourceArtifacts: ExecutorSourceArtifactBackend;84 sourceAuth: ExecutorWorkspaceSourceAuthRepository;
79 auth: ExecutorAuthBackend;85 localTools?: ExecutorWorkspaceLocalToolRepository;
80 secrets: ExecutorSecretsBackend;86 sourceTypeDeclarations?: ExecutorWorkspaceSourceTypeDeclarationsRepository;
81 executions: ExecutorExecutionsBackend;
82 close?: () => Promise<void>;
83};87};
84 88
85export type ExecutorBackendServices = {89export type ExecutorBackendRepositories = {
86 scope: ExecutorScopeDescriptor;90 scope: ExecutorScopeDescriptor;
87 storage: ExecutorStorageBackend;91 installation: ExecutorInstallationRepository;
88 instanceConfig: ExecutorInstanceConfigBackend;92 workspace: ExecutorWorkspaceRepository;
89 localTools?: ExecutorLocalToolBackend;93 secrets: ExecutorSecretRepository;
90 sourceTypeDeclarations?: ExecutorSourceTypeDeclarationsBackend;94 executions: ExecutorExecutionRepository;
95 instanceConfig: ExecutorInstanceConfigRepository;
96 close?: () => Promise<void>;
91};97};
92 98
93const toError = (cause: unknown): Error =>99const toError = (cause: unknown): Error =>
@@ -118,14 +124,14 @@ const toOptionEffect = <T>( · 24 unmodified lines
118 );124 );
119 125
120const toInstallationBackend = (126const toInstallationBackend = (
121 input: ExecutorInstallationBackend,127 input: ExecutorInstallationRepository,
122): BoundInstallationStore => ({128): BoundInstallationStore => ({
123 load: () => toEffect(input.load()),129 load: () => toEffect(input.load()),
124 getOrProvision: () => toEffect(input.getOrProvision()),130 getOrProvision: () => toEffect(input.getOrProvision()),
125});131});
126 132
127const toScopeConfigBackend = (133const toScopeConfigBackend = (
128 input: ExecutorScopeConfigBackend,134 input: ExecutorWorkspaceConfigRepository,
129): BoundScopeConfigStore => ({135): BoundScopeConfigStore => ({
130 load: () => toEffect(input.load()),136 load: () => toEffect(input.load()),
131 writeProject: (config) => toEffect(input.writeProject(config)),137 writeProject: (config) => toEffect(input.writeProject(config)),
@@ -133,14 +139,14 @@ const toScopeConfigBackend = ( · 1 unmodified line
133});139});
134 140
135const toScopeStateBackend = (141const toScopeStateBackend = (
136 input: ExecutorScopeStateBackend,142 input: ExecutorWorkspaceStateRepository,
137): BoundScopeStateStore => ({143): BoundScopeStateStore => ({
138 load: () => toEffect(input.load()),144 load: () => toEffect(input.load()),
139 write: (state) => toEffect(input.write(state)),145 write: (state) => toEffect(input.write(state)),
140});146});
141 147
142const toSourceArtifactBackend = (148const toSourceArtifactBackend = (
143 input: ExecutorSourceArtifactBackend,149 input: ExecutorWorkspaceSourceArtifactRepository,
144): BoundSourceArtifactStore => ({150): BoundSourceArtifactStore => ({
145 build: input.build,151 build: input.build,
146 read: (sourceId) => toEffect(input.read(sourceId)),152 read: (sourceId) => toEffect(input.read(sourceId)),
@@ -149,19 +155,19 @@ const toSourceArtifactBackend = ( · 2 unmodified lines
149});155});
150 156
151const toInstanceConfigBackend = (157const toInstanceConfigBackend = (
152 input: ExecutorInstanceConfigBackend,158 input: ExecutorInstanceConfigRepository,
153): RuntimeInstanceConfigService => ({159): RuntimeInstanceConfigService => ({
154 resolve: () => toEffect(input.resolve()),160 resolve: () => toEffect(input.resolve()),
155});161});
156 162
157const toLocalToolBackend = (163const toLocalToolBackend = (
158 input: ExecutorLocalToolBackend,164 input: ExecutorWorkspaceLocalToolRepository,
159): BoundLocalToolRuntimeLoader => ({165): BoundLocalToolRuntimeLoader => ({
160 load: () => toEffect(input.load()),166 load: () => toEffect(input.load()),
161});167});
162 168
163const toSourceTypeDeclarationsBackend = (169const toSourceTypeDeclarationsBackend = (
164 input: ExecutorSourceTypeDeclarationsBackend,170 input: ExecutorWorkspaceSourceTypeDeclarationsRepository,
165): BoundSourceTypeDeclarationsRefresher => ({171): BoundSourceTypeDeclarationsRefresher => ({
166 refreshWorkspaceInBackground: (payload) =>172 refreshWorkspaceInBackground: (payload) =>
167 toEffect(input.refreshWorkspaceInBackground(payload)).pipe(Effect.orDie),173 toEffect(input.refreshWorkspaceInBackground(payload)).pipe(Effect.orDie),
@@ -170,7 +176,7 @@ const toSourceTypeDeclarationsBackend = ( · 2 unmodified lines
170});176});
171 177
172const toAuthBackend = (178const toAuthBackend = (
173 input: ExecutorAuthBackend,179 input: ExecutorWorkspaceSourceAuthRepository,
174): RuntimeAuthStorageServices => ({180): RuntimeAuthStorageServices => ({
175 artifacts: {181 artifacts: {
176 listByScopeId: (scopeId) => toEffect(input.artifacts.listByScopeId(scopeId)),182 listByScopeId: (scopeId) => toEffect(input.artifacts.listByScopeId(scopeId)),
@@ -230,7 +236,7 @@ const toAuthBackend = ( · 53 unmodified lines
230});236});
231 237
232const toSecretsBackend = (238const toSecretsBackend = (
233 input: ExecutorSecretsBackend,239 input: ExecutorSecretRepository,
234): RuntimeSecretsStorageServices => ({240): RuntimeSecretsStorageServices => ({
235 getById: (id) => toOptionEffect(input.getById(id)),241 getById: (id) => toOptionEffect(input.getById(id)),
236 listAll: () => toEffect(input.listAll()),242 listAll: () => toEffect(input.listAll()),
@@ -244,7 +250,7 @@ const toSecretsBackend = ( · 7 unmodified lines
244});250});
245 251
246const toExecutionsBackend = (252const toExecutionsBackend = (
247 input: ExecutorExecutionsBackend,253 input: ExecutorExecutionRepository,
248): RuntimeExecutionStorageServices => ({254): RuntimeExecutionStorageServices => ({
249 runs: {255 runs: {
250 getById: (executionId) => toOptionEffect(input.runs.getById(executionId)),256 getById: (executionId) => toOptionEffect(input.runs.getById(executionId)),
@@ -285,33 +291,37 @@ const toExecutionsBackend = ( · 34 unmodified lines
285});291});
286 292
287export const createExecutorBackend = (input: {293export const createExecutorBackend = (input: {
288 loadServices: (294 loadRepositories: (
289 options: ExecutorRuntimeOptions,295 options: ExecutorRuntimeOptions,
290 ) => MaybeEffect<ExecutorBackendServices>;296 ) => MaybeEffect<ExecutorBackendRepositories>;
291}): ExecutorBackend => ({297}): ExecutorBackend => ({
292 createRuntime: (options) =>298 createRuntime: (options) =>
293 Effect.flatMap(toEffect(input.loadServices(options)), (services) =>299 Effect.flatMap(toEffect(input.loadRepositories(options)), (repositories) =>
294 createExecutorRuntimeFromServices({300 createExecutorRuntimeFromServices({
295 ...options,301 ...options,
296 services: {302 services: {
297 scope: services.scope,303 scope: repositories.scope,
298 storage: {304 storage: {
299 installation: toInstallationBackend(services.storage.installation),305 installation: toInstallationBackend(repositories.installation),
300 scopeConfig: toScopeConfigBackend(services.storage.scopeConfig),306 scopeConfig: toScopeConfigBackend(repositories.workspace.config),
301 scopeState: toScopeStateBackend(services.storage.scopeState),307 scopeState: toScopeStateBackend(repositories.workspace.state),
302 sourceArtifacts: toSourceArtifactBackend(services.storage.sourceArtifacts),308 sourceArtifacts: toSourceArtifactBackend(
303 auth: toAuthBackend(services.storage.auth),309 repositories.workspace.sourceArtifacts,
304 secrets: toSecretsBackend(services.storage.secrets),310 ),
305 executions: toExecutionsBackend(services.storage.executions),311 auth: toAuthBackend(repositories.workspace.sourceAuth),
306 close: services.storage.close,312 secrets: toSecretsBackend(repositories.secrets),
313 executions: toExecutionsBackend(repositories.executions),
314 close: repositories.close,
307 } satisfies RuntimeStorageServices,315 } satisfies RuntimeStorageServices,
308 localToolRuntimeLoader: services.localTools316 localToolRuntimeLoader: repositories.workspace.localTools
309 ? toLocalToolBackend(services.localTools)317 ? toLocalToolBackend(repositories.workspace.localTools)
310 : undefined,318 : undefined,
311 sourceTypeDeclarationsRefresher: services.sourceTypeDeclarations319 sourceTypeDeclarationsRefresher: repositories.workspace.sourceTypeDeclarations
312 ? toSourceTypeDeclarationsBackend(services.sourceTypeDeclarations)320 ? toSourceTypeDeclarationsBackend(
321 repositories.workspace.sourceTypeDeclarations,
322 )
313 : undefined,323 : undefined,
314 instanceConfig: toInstanceConfigBackend(services.instanceConfig),324 instanceConfig: toInstanceConfigBackend(repositories.instanceConfig),
315 },325 },
316 }),326 }),
317 ),327 ),
packages/platform/sdk/src/executor.test.ts+14−25
@@ -40,7 +40,7 @@ import { · 39 unmodified lines
40 ScopeIdSchema,40 ScopeIdSchema,
41} from "./schema";41} from "./schema";
42import {42import {
43 createLocalExecutorServicesEffect,43 createLocalExecutorRepositoriesEffect,
44} from "../../sdk-file/src/effect";44} from "../../sdk-file/src/effect";
45import {45import {
46 createExecutorApiLayer,46 createExecutorApiLayer,
@@ -50,7 +50,7 @@ import { · 3 unmodified lines
50} from "../../api/src/index";50} from "../../api/src/index";
51 51
52type LocalExecutorServices = Effect.Effect.Success<52type LocalExecutorServices = Effect.Effect.Success<
53 ReturnType<typeof createLocalExecutorServicesEffect>53 ReturnType<typeof createLocalExecutorRepositoriesEffect>
54>;54>;
55 55
56const createClientLayer = (executor: Executor) =>56const createClientLayer = (executor: Executor) =>
@@ -168,7 +168,7 @@ const createScopedExecutor = (input: { · 111 unmodified lines
168}) =>168}) =>
169 createExecutorEffect({169 createExecutorEffect({
170 backend: createExecutorBackend({170 backend: createExecutorBackend({
171 loadServices: () =>171 loadRepositories: () =>
172 Effect.succeed({172 Effect.succeed({
173 ...input.services,173 ...input.services,
174 scope: {174 scope: {
@@ -176,12 +176,9 @@ const createScopedExecutor = (input: { · 1 unmodified line
176 actorScopeId: input.installation.actorScopeId,176 actorScopeId: input.installation.actorScopeId,
177 resolutionScopeIds: input.installation.resolutionScopeIds,177 resolutionScopeIds: input.installation.resolutionScopeIds,
178 },178 },
179 storage: {179 installation: {
180 ...input.services.storage,180 load: () => Effect.succeed(input.installation),
181 installation: {181 getOrProvision: () => Effect.succeed(input.installation),
182 load: () => Effect.succeed(input.installation),
183 getOrProvision: () => Effect.succeed(input.installation),
184 },
185 },182 },
186 }),183 }),
187 }),184 }),
@@ -200,21 +197,21 @@ const makeSharedScopeExecutors = Effect.acquireRelease( · 12 unmodified lines
200 prefix: "executor-sdk-scope-b-",197 prefix: "executor-sdk-scope-b-",
201 });198 });
202 199
203 const servicesA = yield* createLocalExecutorServicesEffect({200 const servicesA = yield* createLocalExecutorRepositoriesEffect({
204 workspaceRoot: workspaceRootA,201 workspaceRoot: workspaceRootA,
205 homeConfigPath: join(workspaceRootA, ".executor-home.jsonc"),202 homeConfigPath: join(workspaceRootA, ".executor-home.jsonc"),
206 homeStateDirectory: join(workspaceRootA, ".executor-home-state"),203 homeStateDirectory: join(workspaceRootA, ".executor-home-state"),
207 });204 });
208 const servicesB = yield* createLocalExecutorServicesEffect({205 const servicesB = yield* createLocalExecutorRepositoriesEffect({
209 workspaceRoot: workspaceRootB,206 workspaceRoot: workspaceRootB,
210 homeConfigPath: join(workspaceRootB, ".executor-home.jsonc"),207 homeConfigPath: join(workspaceRootB, ".executor-home.jsonc"),
211 homeStateDirectory: join(workspaceRootB, ".executor-home-state"),208 homeStateDirectory: join(workspaceRootB, ".executor-home-state"),
212 });209 });
213 210
214 const baseInstallationA =211 const baseInstallationA =
215 yield* servicesA.storage.installation.getOrProvision();212 yield* servicesA.installation.getOrProvision();
216 const baseInstallationB =213 const baseInstallationB =
217 yield* servicesB.storage.installation.getOrProvision();214 yield* servicesB.installation.getOrProvision();
218 215
219 const sharedStorageClose = (() => {216 const sharedStorageClose = (() => {
220 let closed = false;217 let closed = false;
@@ -223,14 +220,12 @@ const makeSharedScopeExecutors = Effect.acquireRelease( · 2 unmodified lines
223 return;220 return;
224 }221 }
225 closed = true;222 closed = true;
226 await servicesA.storage.close?.();223 await servicesA.close?.();
227 await servicesB.storage.close?.();224 await servicesB.close?.();
228 };225 };
229 })();226 })();
230 227
231 const sharedStorage = {228 const sharedStorage = {
232 executorState: servicesA.storage.executorState,
233 secretMaterial: servicesA.storage.secretMaterial,
234 close: sharedStorageClose,229 close: sharedStorageClose,
235 };230 };
236 231
@@ -256,20 +251,14 @@ const makeSharedScopeExecutors = Effect.acquireRelease( · 19 unmodified lines
256 const executorA = yield* createScopedExecutor({251 const executorA = yield* createScopedExecutor({
257 services: {252 services: {
258 ...servicesA,253 ...servicesA,
259 storage: {254 ...sharedStorage,
260 ...servicesA.storage,
261 ...sharedStorage,
262 },
263 },255 },
264 installation: installationA,256 installation: installationA,
265 });257 });
266 const executorB = yield* createScopedExecutor({258 const executorB = yield* createScopedExecutor({
267 services: {259 services: {
268 ...servicesB,260 ...servicesB,
269 storage: {261 ...sharedStorage,
270 ...servicesB.storage,
271 ...sharedStorage,
272 },
273 },262 },
274 installation: installationB,263 installation: installationB,
275 });264 });
packages/platform/sdk/src/runtime/execution/mcp-resume.test.ts+1−1
@@ -369,7 +369,7 @@ describe("execution-mcp-resume", () => { · 368 unmodified lines
369 expect(created.pendingInteraction.payloadJson).toContain("Approve gated echo");369 expect(created.pendingInteraction.payloadJson).toContain("Approve gated echo");
370 }370 }
371 371
372 const pendingInteraction = yield* runtime.storage.executorState.executionInteractions.getPendingByExecutionId(372 const pendingInteraction = yield* runtime.storage.executions.interactions.getPendingByExecutionId(
373 created.execution.id,373 created.execution.id,
374 );374 );
375 375
packages/platform/sdk/src/runtime/executor-runtime.test.ts+46−30
@@ -59,6 +59,7 @@ import { · 58 unmodified lines
59 59
60import {60import {
61 type ExecutorRuntime,61 type ExecutorRuntime,
62 type ExecutorStateStoreShape,
62 LiveExecutionManagerService,63 LiveExecutionManagerService,
63 provideExecutorRuntime,64 provideExecutorRuntime,
64 RuntimeExecutionResolverService,65 RuntimeExecutionResolverService,
@@ -346,7 +347,7 @@ const upsertLocalSecret = ( · 281 unmodified lines
346 value: string;347 value: string;
347 },348 },
348) =>349) =>
349 runtime.storage.executorState.secretMaterials.upsert({350 executorStateForRuntime(runtime).secretMaterials.upsert({
350 id: SecretMaterialIdSchema.make(input.id),351 id: SecretMaterialIdSchema.make(input.id),
351 providerId: "local",352 providerId: "local",
352 handle: input.id,353 handle: input.id,
@@ -357,6 +358,21 @@ const upsertLocalSecret = ( · 4 unmodified lines
357 updatedAt: Date.now(),358 updatedAt: Date.now(),
358 });359 });
359 360
361const executorStateForRuntime = (
362 runtime: ExecutorRuntime,
363): ExecutorStateStoreShape => ({
364 authArtifacts: runtime.storage.auth.artifacts,
365 authLeases: runtime.storage.auth.leases,
366 sourceOauthClients: runtime.storage.auth.sourceOauthClients,
367 scopeOauthClients: runtime.storage.auth.scopeOauthClients,
368 providerAuthGrants: runtime.storage.auth.providerGrants,
369 sourceAuthSessions: runtime.storage.auth.sourceSessions,
370 secretMaterials: runtime.storage.secrets,
371 executions: runtime.storage.executions.runs,
372 executionInteractions: runtime.storage.executions.interactions,
373 executionSteps: runtime.storage.executions.steps,
374});
375
360const createPersistedGoogleSource = (input: {376const createPersistedGoogleSource = (input: {
361 runtime: ExecutorRuntime;377 runtime: ExecutorRuntime;
362 name: string;378 name: string;
@@ -403,7 +419,7 @@ const createPersistedGoogleSource = (input: { · 40 unmodified lines
403 now: Date.now(),419 now: Date.now(),
404 });420 });
405 421
406 return yield* persistSource(input.runtime.storage.executorState, source, {422 return yield* persistSource(executorStateForRuntime(input.runtime), source, {
407 actorScopeId: installation.actorScopeId,423 actorScopeId: installation.actorScopeId,
408 }).pipe((effect) => provideExecutorRuntime(effect, input.runtime));424 }).pipe((effect) => provideExecutorRuntime(effect, input.runtime));
409 });425 });
@@ -563,7 +579,7 @@ describe("executor-runtime", () => { · 153 unmodified lines
563 expect(updated.name).toBe("GitHub PAT Updated");579 expect(updated.name).toBe("GitHub PAT Updated");
564 580
565 const storedSecret =581 const storedSecret =
566 yield* runtime.storage.executorState.secretMaterials.getById(582 yield* executorStateForRuntime(runtime).secretMaterials.getById(
567 SecretMaterialIdSchema.make(created.id),583 SecretMaterialIdSchema.make(created.id),
568 );584 );
569 assertTrue(Option.isSome(storedSecret));585 assertTrue(Option.isSome(storedSecret));
@@ -798,7 +814,7 @@ describe("executor-runtime", () => { · 228 unmodified lines
798 );814 );
799 const now = Date.now();815 const now = Date.now();
800 816
801 yield* runtime.storage.executorState.executions.insert({817 yield* executorStateForRuntime(runtime).executions.insert({
802 id: executionId,818 id: executionId,
803 scopeId: installation.scopeId,819 scopeId: installation.scopeId,
804 createdByScopeId: installation.actorScopeId,820 createdByScopeId: installation.actorScopeId,
@@ -833,7 +849,7 @@ describe("executor-runtime", () => { · 28 unmodified lines
833 },849 },
834 now,850 now,
835 }).pipe(Effect.orDie);851 }).pipe(Effect.orDie);
836 yield* persistSource(runtime.storage.executorState, localSource, {852 yield* persistSource(executorStateForRuntime(runtime), localSource, {
837 actorScopeId: installation.actorScopeId,853 actorScopeId: installation.actorScopeId,
838 }).pipe(854 }).pipe(
839 (effect) => provideExecutorRuntime(effect, runtime),855 (effect) => provideExecutorRuntime(effect, runtime),
@@ -843,7 +859,7 @@ describe("executor-runtime", () => { · 3 unmodified lines
843 const interactionFiber = yield* Effect.gen(function* () {859 const interactionFiber = yield* Effect.gen(function* () {
844 const liveExecutionManager = yield* LiveExecutionManagerService;860 const liveExecutionManager = yield* LiveExecutionManagerService;
845 const onElicitation = liveExecutionManager.createOnElicitation({861 const onElicitation = liveExecutionManager.createOnElicitation({
846 executorState: runtime.storage.executorState,862 executorState: executorStateForRuntime(runtime),
847 executionId,863 executionId,
848 });864 });
849 865
@@ -874,7 +890,7 @@ describe("executor-runtime", () => { · 24 unmodified lines
874 const waitForPendingInteraction = (890 const waitForPendingInteraction = (
875 remaining: number,891 remaining: number,
876 ): Effect.Effect<Option.Option<ExecutionInteraction>, Error> =>892 ): Effect.Effect<Option.Option<ExecutionInteraction>, Error> =>
877 runtime.storage.executorState.executionInteractions893 executorStateForRuntime(runtime).executionInteractions
878 .getPendingByExecutionId(executionId)894 .getPendingByExecutionId(executionId)
879 .pipe(895 .pipe(
880 Effect.flatMap((pendingInteraction) =>896 Effect.flatMap((pendingInteraction) =>
@@ -947,7 +963,7 @@ describe("executor-runtime", () => { · 66 unmodified lines
947 : "",963 : "",
948 );964 );
949 const storedSecret =965 const storedSecret =
950 yield* runtime.storage.executorState.secretMaterials.getById(966 yield* executorStateForRuntime(runtime).secretMaterials.getById(
951 tokenSecretMaterialId,967 tokenSecretMaterialId,
952 );968 );
953 assertTrue(Option.isSome(storedSecret));969 assertTrue(Option.isSome(storedSecret));
@@ -963,7 +979,7 @@ describe("executor-runtime", () => { · 9 unmodified lines
963 );979 );
964 980
965 const storedInteraction =981 const storedInteraction =
966 yield* runtime.storage.executorState.executionInteractions.getById(982 yield* executorStateForRuntime(runtime).executionInteractions.getById(
967 pendingInteraction.value.id,983 pendingInteraction.value.id,
968 );984 );
969 assertTrue(Option.isSome(storedInteraction));985 assertTrue(Option.isSome(storedInteraction));
@@ -1315,7 +1331,7 @@ describe("executor-runtime", () => { · 345 unmodified lines
1315 },1331 },
1316 now,1332 now,
1317 }).pipe(Effect.orDie);1333 }).pipe(Effect.orDie);
1318 yield* persistSource(runtime.storage.executorState, localSource, {1334 yield* persistSource(executorStateForRuntime(runtime), localSource, {
1319 actorScopeId: installation.actorScopeId,1335 actorScopeId: installation.actorScopeId,
1320 }).pipe(1336 }).pipe(
1321 (effect) => provideExecutorRuntime(effect, runtime),1337 (effect) => provideExecutorRuntime(effect, runtime),
@@ -1373,7 +1389,7 @@ describe("executor-runtime", () => { · 51 unmodified lines
1373 },1389 },
1374 now,1390 now,
1375 }).pipe(Effect.orDie);1391 }).pipe(Effect.orDie);
1376 yield* persistSource(runtime.storage.executorState, localSource, {1392 yield* persistSource(executorStateForRuntime(runtime), localSource, {
1377 actorScopeId: installation.actorScopeId,1393 actorScopeId: installation.actorScopeId,
1378 }).pipe(1394 }).pipe(
1379 (effect) => provideExecutorRuntime(effect, runtime),1395 (effect) => provideExecutorRuntime(effect, runtime),
@@ -1415,7 +1431,7 @@ describe("executor-runtime", () => { · 35 unmodified lines
1415 );1431 );
1416 const now = Date.now();1432 const now = Date.now();
1417 1433
1418 yield* runtime.storage.executorState.executions.insert({1434 yield* executorStateForRuntime(runtime).executions.insert({
1419 id: executionId,1435 id: executionId,
1420 scopeId: installation.scopeId,1436 scopeId: installation.scopeId,
1421 createdByScopeId: installation.actorScopeId,1437 createdByScopeId: installation.actorScopeId,
@@ -1450,7 +1466,7 @@ describe("executor-runtime", () => { · 28 unmodified lines
1450 },1466 },
1451 now,1467 now,
1452 }).pipe(Effect.orDie);1468 }).pipe(Effect.orDie);
1453 yield* persistSource(runtime.storage.executorState, localSource, {1469 yield* persistSource(executorStateForRuntime(runtime), localSource, {
1454 actorScopeId: installation.actorScopeId,1470 actorScopeId: installation.actorScopeId,
1455 }).pipe(1471 }).pipe(
1456 (effect) => provideExecutorRuntime(effect, runtime),1472 (effect) => provideExecutorRuntime(effect, runtime),
@@ -1460,7 +1476,7 @@ describe("executor-runtime", () => { · 3 unmodified lines
1460 const interactionFiber = yield* Effect.gen(function* () {1476 const interactionFiber = yield* Effect.gen(function* () {
1461 const liveExecutionManager = yield* LiveExecutionManagerService;1477 const liveExecutionManager = yield* LiveExecutionManagerService;
1462 const onElicitation = liveExecutionManager.createOnElicitation({1478 const onElicitation = liveExecutionManager.createOnElicitation({
1463 executorState: runtime.storage.executorState,1479 executorState: executorStateForRuntime(runtime),
1464 executionId,1480 executionId,
1465 });1481 });
1466 1482
@@ -1491,7 +1507,7 @@ describe("executor-runtime", () => { · 24 unmodified lines
1491 const waitForPendingInteraction = (1507 const waitForPendingInteraction = (
1492 remaining: number,1508 remaining: number,
1493 ): Effect.Effect<Option.Option<ExecutionInteraction>, Error> =>1509 ): Effect.Effect<Option.Option<ExecutionInteraction>, Error> =>
1494 runtime.storage.executorState.executionInteractions1510 executorStateForRuntime(runtime).executionInteractions
1495 .getPendingByExecutionId(executionId)1511 .getPendingByExecutionId(executionId)
1496 .pipe(1512 .pipe(
1497 Effect.flatMap((pendingInteraction) =>1513 Effect.flatMap((pendingInteraction) =>
@@ -1522,7 +1538,7 @@ describe("executor-runtime", () => { · 24 unmodified lines
1522 });1538 });
1523 1539
1524 const storedInteraction =1540 const storedInteraction =
1525 yield* runtime.storage.executorState.executionInteractions.getById(1541 yield* executorStateForRuntime(runtime).executionInteractions.getById(
1526 pendingInteraction.value.id,1542 pendingInteraction.value.id,
1527 );1543 );
1528 assertTrue(Option.isSome(storedInteraction));1544 assertTrue(Option.isSome(storedInteraction));
@@ -1615,7 +1631,7 @@ describe("executor-runtime", () => { · 86 unmodified lines
1615 const grantId = ProviderAuthGrantIdSchema.make(1631 const grantId = ProviderAuthGrantIdSchema.make(
1616 `provider_grant_${crypto.randomUUID()}`,1632 `provider_grant_${crypto.randomUUID()}`,
1617 );1633 );
1618 yield* runtime.storage.executorState.providerAuthGrants.upsert({1634 yield* executorStateForRuntime(runtime).providerAuthGrants.upsert({
1619 id: grantId,1635 id: grantId,
1620 scopeId: installation.scopeId,1636 scopeId: installation.scopeId,
1621 actorScopeId: installation.actorScopeId,1637 actorScopeId: installation.actorScopeId,
@@ -1677,7 +1693,7 @@ describe("executor-runtime", () => { · 55 unmodified lines
1677 });1693 });
1678 1694
1679 const storedGrant =1695 const storedGrant =
1680 yield* runtime.storage.executorState.providerAuthGrants.getById(grantId);1696 yield* executorStateForRuntime(runtime).providerAuthGrants.getById(grantId);
1681 assertTrue(Option.isSome(storedGrant));1697 assertTrue(Option.isSome(storedGrant));
1682 expect(storedGrant.value.orphanedAt).toBeNull();1698 expect(storedGrant.value.orphanedAt).toBeNull();
1683 expect(googleServer.discoveryAuthorizations).toContain(1699 expect(googleServer.discoveryAuthorizations).toContain(
@@ -1791,7 +1807,7 @@ describe("executor-runtime", () => { · 107 unmodified lines
1791 const grantId = ProviderAuthGrantIdSchema.make(1807 const grantId = ProviderAuthGrantIdSchema.make(
1792 `provider_grant_${crypto.randomUUID()}`,1808 `provider_grant_${crypto.randomUUID()}`,
1793 );1809 );
1794 yield* runtime.storage.executorState.providerAuthGrants.upsert({1810 yield* executorStateForRuntime(runtime).providerAuthGrants.upsert({
1795 id: grantId,1811 id: grantId,
1796 scopeId: installation.scopeId,1812 scopeId: installation.scopeId,
1797 actorScopeId: installation.actorScopeId,1813 actorScopeId: installation.actorScopeId,
@@ -1870,7 +1886,7 @@ describe("executor-runtime", () => { · 72 unmodified lines
1870 ).toBe(true);1886 ).toBe(true);
1871 1887
1872 const storedGrant =1888 const storedGrant =
1873 yield* runtime.storage.executorState.providerAuthGrants.getById(grantId);1889 yield* executorStateForRuntime(runtime).providerAuthGrants.getById(grantId);
1874 assertTrue(Option.isSome(storedGrant));1890 assertTrue(Option.isSome(storedGrant));
1875 expect(storedGrant.value.orphanedAt).toBeNull();1891 expect(storedGrant.value.orphanedAt).toBeNull();
1876 expect(googleServer.tokenRequests.length).toBeGreaterThanOrEqual(1);1892 expect(googleServer.tokenRequests.length).toBeGreaterThanOrEqual(1);
@@ -2001,7 +2017,7 @@ describe("executor-runtime", () => { · 124 unmodified lines
2001 const grantId = ProviderAuthGrantIdSchema.make(2017 const grantId = ProviderAuthGrantIdSchema.make(
2002 `provider_grant_${crypto.randomUUID()}`,2018 `provider_grant_${crypto.randomUUID()}`,
2003 );2019 );
2004 yield* runtime.storage.executorState.providerAuthGrants.upsert({2020 yield* executorStateForRuntime(runtime).providerAuthGrants.upsert({
2005 id: grantId,2021 id: grantId,
2006 scopeId: installation.scopeId,2022 scopeId: installation.scopeId,
2007 actorScopeId: installation.actorScopeId,2023 actorScopeId: installation.actorScopeId,
@@ -2026,7 +2042,7 @@ describe("executor-runtime", () => { · 18 unmodified lines
2026 `src_auth_${crypto.randomUUID()}`,2042 `src_auth_${crypto.randomUUID()}`,
2027 );2043 );
2028 const state = `provider-state-${crypto.randomUUID()}`;2044 const state = `provider-state-${crypto.randomUUID()}`;
2029 yield* runtime.storage.executorState.sourceAuthSessions.upsert({2045 yield* executorStateForRuntime(runtime).sourceAuthSessions.upsert({
2030 id: sessionId,2046 id: sessionId,
2031 scopeId: installation.scopeId,2047 scopeId: installation.scopeId,
2032 sourceId: SourceIdSchema.make(2048 sourceId: SourceIdSchema.make(
@@ -2097,7 +2113,7 @@ describe("executor-runtime", () => { · 64 unmodified lines
2097 expect(callbackPage).toContain("Connected 1 source");2113 expect(callbackPage).toContain("Connected 1 source");
2098 2114
2099 const storedGrant =2115 const storedGrant =
2100 yield* runtime.storage.executorState.providerAuthGrants.getById(grantId);2116 yield* executorStateForRuntime(runtime).providerAuthGrants.getById(grantId);
2101 assertTrue(Option.isSome(storedGrant));2117 assertTrue(Option.isSome(storedGrant));
2102 expect(storedGrant.value.refreshToken).toEqual({2118 expect(storedGrant.value.refreshToken).toEqual({
2103 providerId: "local",2119 providerId: "local",
@@ -2155,7 +2171,7 @@ describe("executor-runtime", () => { · 51 unmodified lines
2155 );2171 );
2156 const refreshSecretId = "sec_google_refresh_orphan";2172 const refreshSecretId = "sec_google_refresh_orphan";
2157 2173
2158 yield* runtime.storage.executorState.scopeOauthClients.upsert({2174 yield* executorStateForRuntime(runtime).scopeOauthClients.upsert({
2159 id: oauthClientId,2175 id: oauthClientId,
2160 scopeId: installation.scopeId,2176 scopeId: installation.scopeId,
2161 providerKey: "google_workspace",2177 providerKey: "google_workspace",
@@ -2177,7 +2193,7 @@ describe("executor-runtime", () => { · 15 unmodified lines
2177 const grantId = ProviderAuthGrantIdSchema.make(2193 const grantId = ProviderAuthGrantIdSchema.make(
2178 `provider_grant_${crypto.randomUUID()}`,2194 `provider_grant_${crypto.randomUUID()}`,
2179 );2195 );
2180 yield* runtime.storage.executorState.providerAuthGrants.upsert({2196 yield* executorStateForRuntime(runtime).providerAuthGrants.upsert({
2181 id: grantId,2197 id: grantId,
2182 scopeId: installation.scopeId,2198 scopeId: installation.scopeId,
2183 actorScopeId: installation.actorScopeId,2199 actorScopeId: installation.actorScopeId,
@@ -2228,7 +2244,7 @@ describe("executor-runtime", () => { · 44 unmodified lines
2228 expect(removed.removed).toBe(true);2244 expect(removed.removed).toBe(true);
2229 2245
2230 const storedGrant =2246 const storedGrant =
2231 yield* runtime.storage.executorState.providerAuthGrants.getById(grantId);2247 yield* executorStateForRuntime(runtime).providerAuthGrants.getById(grantId);
2232 assertTrue(Option.isSome(storedGrant));2248 assertTrue(Option.isSome(storedGrant));
2233 expect(storedGrant.value.orphanedAt).not.toBeNull();2249 expect(storedGrant.value.orphanedAt).not.toBeNull();
2234 }),2250 }),
@@ -2245,7 +2261,7 @@ describe("executor-runtime", () => { · 10 unmodified lines
2245 );2261 );
2246 const refreshSecretId = "sec_google_refresh_revoke";2262 const refreshSecretId = "sec_google_refresh_revoke";
2247 2263
2248 yield* runtime.storage.executorState.scopeOauthClients.upsert({2264 yield* executorStateForRuntime(runtime).scopeOauthClients.upsert({
2249 id: oauthClientId,2265 id: oauthClientId,
2250 scopeId: installation.scopeId,2266 scopeId: installation.scopeId,
2251 providerKey: "google_workspace",2267 providerKey: "google_workspace",
@@ -2267,7 +2283,7 @@ describe("executor-runtime", () => { · 15 unmodified lines
2267 const grantId = ProviderAuthGrantIdSchema.make(2283 const grantId = ProviderAuthGrantIdSchema.make(
2268 `provider_grant_${crypto.randomUUID()}`,2284 `provider_grant_${crypto.randomUUID()}`,
2269 );2285 );
2270 yield* runtime.storage.executorState.providerAuthGrants.upsert({2286 yield* executorStateForRuntime(runtime).providerAuthGrants.upsert({
2271 id: grantId,2287 id: grantId,
2272 scopeId: installation.scopeId,2288 scopeId: installation.scopeId,
2273 actorScopeId: installation.actorScopeId,2289 actorScopeId: installation.actorScopeId,
@@ -2335,11 +2351,11 @@ describe("executor-runtime", () => { · 61 unmodified lines
2335 expect(removed.removed).toBe(true);2351 expect(removed.removed).toBe(true);
2336 2352
2337 const storedGrant =2353 const storedGrant =
2338 yield* runtime.storage.executorState.providerAuthGrants.getById(grantId);2354 yield* executorStateForRuntime(runtime).providerAuthGrants.getById(grantId);
2339 assertTrue(Option.isNone(storedGrant));2355 assertTrue(Option.isNone(storedGrant));
2340 2356
2341 const storedRefreshSecret =2357 const storedRefreshSecret =
2342 yield* runtime.storage.executorState.secretMaterials.getById(2358 yield* executorStateForRuntime(runtime).secretMaterials.getById(
2343 SecretMaterialIdSchema.make(refreshSecretId),2359 SecretMaterialIdSchema.make(refreshSecretId),
2344 );2360 );
2345 assertTrue(Option.isNone(storedRefreshSecret));2361 assertTrue(Option.isNone(storedRefreshSecret));
packages/platform/sdk/src/runtime/sources/source-adapters/__fixtures__/mcp-stdio-server.mjs+26−0
@@ -0,0 +1,26 @@
1import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3import { z } from "zod/v4";
4
5const server = new McpServer(
6 { name: "mcp-stdio-test-server", version: "1.0.0" },
7 { capabilities: { tools: { listChanged: true } } },
8);
9
10server.registerTool(
11 "echo_stdio",
12 {
13 title: "Echo Stdio",
14 description: "Echo a value over stdio",
15 inputSchema: {
16 value: z.string(),
17 },
18 },
19 async ({ value }) => ({
20 content: [{ type: "text", text: "stdio:" + value }],
21 }),
22);
23
24const transport = new StdioServerTransport();
25await server.connect(transport);
26setInterval(() => {}, 1 << 30);
packages/platform/sdk/src/runtime/sources/source-adapters/mcp.test.ts+20−38
@@ -1,6 +1,9 @@
1import {1import {
2 randomUUID,2 randomUUID,
3} from "node:crypto";3} from "node:crypto";
4import {
5 fileURLToPath,
6} from "node:url";
4 7
5import {8import {
6 createMcpExpressApp,9 createMcpExpressApp,
@@ -70,6 +73,19 @@ type RealMcpServer = { · 63 unmodified lines
70 close: () => Promise<void>;73 close: () => Promise<void>;
71};74};
72 75
76const PACKAGE_ROOT = fileURLToPath(new URL("../../../../", import.meta.url));
77const STDIO_SERVER_SCRIPT_PATH = fileURLToPath(
78 new URL("./__fixtures__/mcp-stdio-server.mjs", import.meta.url),
79);
80const BUN_COMMAND =
81 process.env.BUN_INSTALL && process.env.BUN_INSTALL.length > 0
82 ? `${process.env.BUN_INSTALL}/bin/bun`
83 : "bun";
84const STDIO_TEST_ENV = {
85 PATH: process.env.PATH ?? "",
86 HOME: process.env.HOME ?? "",
87};
88
73const makeLoadedCatalog = (input: {89const makeLoadedCatalog = (input: {
74 source: Source;90 source: Source;
75 snapshot: CatalogSnapshotV1;91 snapshot: CatalogSnapshotV1;
@@ -567,40 +583,6 @@ const makeStatefulMcpServer = Effect.acquireRelease( · 491 unmodified lines
567 }).pipe(Effect.orDie),583 }).pipe(Effect.orDie),
568);584);
569 585
570const STDIO_TEST_SERVER_SCRIPT = `
571import {
572 McpServer,
573} from "@modelcontextprotocol/sdk/server/mcp.js";
574import {
575 StdioServerTransport,
576} from "@modelcontextprotocol/sdk/server/stdio.js";
577import {
578 z,
579} from "zod/v4";
580
581const server = new McpServer(
582 { name: "mcp-stdio-test-server", version: "1.0.0" },
583 { capabilities: { tools: { listChanged: true } } },
584);
585
586server.registerTool(
587 "echo_stdio",
588 {
589 title: "Echo Stdio",
590 description: "Echo a value over stdio",
591 inputSchema: {
592 value: z.string(),
593 },
594 },
595 async ({ value }) => ({
596 content: [{ type: "text", text: "stdio:" + value }],
597 }),
598);
599
600const transport = new StdioServerTransport();
601await server.connect(transport);
602`;
603
604const makeStaticAuthProvider = (accessToken: string): OAuthClientProvider => ({586const makeStaticAuthProvider = (accessToken: string): OAuthClientProvider => ({
605 get redirectUrl() {587 get redirectUrl() {
606 return "http://127.0.0.1/oauth/callback";588 return "http://127.0.0.1/oauth/callback";
@@ -1020,10 +1002,10 @@ describe("mcp source adapter", () => { · 413 unmodified lines
1020 transport: "stdio",1002 transport: "stdio",
1021 queryParams: null,1003 queryParams: null,
1022 headers: null,1004 headers: null,
1023 command: "node",1005 command: BUN_COMMAND,
1024 args: ["--input-type=module", "-e", STDIO_TEST_SERVER_SCRIPT],1006 args: [STDIO_SERVER_SCRIPT_PATH],
1025 env: null,1007 env: STDIO_TEST_ENV,
1026 cwd: process.cwd(),1008 cwd: PACKAGE_ROOT,
1027 },1009 },
1028 importAuthPolicy: "reuse_runtime",1010 importAuthPolicy: "reuse_runtime",
1029 importAuth: { kind: "none" },1011 importAuth: { kind: "none" },
packages/platform/sdk/src/runtime/sources/source-discovery.test.ts+1−1
@@ -13,7 +13,7 @@ import * as Effect from "effect/Effect"; · 12 unmodified lines
13 13
14import {14import {
15 discoverSource,15 discoverSource,
16} from "./source-discovery";16} from "../../sources/discovery";
17 17
18type TestServer = {18type TestServer = {
19 url: string;19 url: string;
packages/platform/sdk/src/runtime/sources/source-inspection.test.ts+1−1
@@ -10,7 +10,7 @@ import type { · 9 unmodified lines
10} from "../catalog/source/runtime";10} from "../catalog/source/runtime";
11import {11import {
12 inspectionToolDetailFromTool,12 inspectionToolDetailFromTool,
13} from "./source-inspection";13} from "../../sources/inspection";
14 14
15describe("source inspection", () => {15describe("source inspection", () => {
16 it.effect("keeps the shared TypeScript contract first-class on tool detail", () =>16 it.effect("keeps the shared TypeScript contract first-class on tool detail", () =>
packages/sources/mcp/src/adapter.ts+3−1
@@ -462,7 +462,9 @@ export const mcpSourceAdapter = { · 461 unmodified lines
462 Effect.mapError(462 Effect.mapError(
463 (cause) =>463 (cause) =>
464 new Error(464 new Error(
465 `Failed discovering MCP tools for ${source.id}: ${cause.message}`,465 `Failed discovering MCP tools for ${source.id}: ${cause.message}${
466 cause.details ? ` (${cause.details})` : ""
467 }`,
466 ),468 ),
467 ),469 ),
468 );470 );
packages/sources/mcp/src/connection.ts+5−27
@@ -1,6 +1,7 @@
1import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";1import type { OAuthClientProvider } from "@modelcontextprotocol/sdk/client/auth.js";
2import { Client } from "@modelcontextprotocol/sdk/client/index.js";2import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";3import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
4import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
4import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";5import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
5import * as Data from "effect/Data";6import * as Data from "effect/Data";
6import * as Effect from "effect/Effect";7import * as Effect from "effect/Effect";
@@ -104,31 +105,6 @@ const closeClient = (client: Client): Effect.Effect<void, never, never> => · 97 unmodified lines
104 }),105 }),
105 }).pipe(Effect.ignore);106 }).pipe(Effect.ignore);
106 107
107type DynamicImport = <TModule>(specifier: string) => Promise<TModule>;
108
109const dynamicImport = new Function(
110 "specifier",
111 "return import(specifier)",
112) as DynamicImport;
113
114const loadStdioClientTransport = (): Effect.Effect<
115 typeof import("@modelcontextprotocol/sdk/client/stdio.js").StdioClientTransport,
116 McpConnectionError,
117 never
118> =>
119 Effect.tryPromise({
120 try: async () =>
121 (await dynamicImport<typeof import("@modelcontextprotocol/sdk/client/stdio.js")>(
122 "@modelcontextprotocol/sdk/client/stdio.js",
123 )).StdioClientTransport,
124 catch: (cause) =>
125 mcpConnectionError({
126 transport: "stdio",
127 message: "Failed loading MCP stdio transport",
128 cause,
129 }),
130 });
131
132const connectClient = (input: {108const connectClient = (input: {
133 createClient: () => Client;109 createClient: () => Client;
134 transport: McpTransportPreference;110 transport: McpTransportPreference;
@@ -143,7 +119,10 @@ const connectClient = (input: { · 8 unmodified lines
143 catch: (cause) =>119 catch: (cause) =>
144 mcpConnectionError({120 mcpConnectionError({
145 transport: input.transport,121 transport: input.transport,
146 message: `Failed connecting to MCP server via ${input.transport}`,122 message:
123 `Failed connecting to MCP server via ${input.transport}: ${
124 cause instanceof Error ? cause.message : String(cause)
125 }`,
147 cause,126 cause,
148 }),127 }),
149 }).pipe(128 }).pipe(
@@ -183,7 +162,6 @@ export const createSdkMcpConnector = ( · 33 unmodified lines
183 });162 });
184 }163 }
185 164
186 const StdioClientTransport = yield* loadStdioClientTransport();
187 return yield* connectClient({165 return yield* connectClient({
188 createClient,166 createClient,
189 transport: "stdio",167 transport: "stdio",