History

remove legacy compatibility codepaths

Rhys Sullivan committed 7 months ago

Commit f99df261 parent fd4ec3f21 files · +111 −1158Browse files

Unified · Split

21 files
  1. apps/executor/src/cli/main.ts +0 −207
  2. packages/hosts/mcp/src/paused-result.test.ts +1 −1
  3. packages/platform/sdk-file/src/config.test.ts +8 −54
  4. packages/platform/sdk-file/src/config.ts +0 −172
  5. packages/platform/sdk-file/src/executor-state-store.ts +1 −148
  6. packages/platform/sdk-file/src/index.ts +8 −5
  7. packages/platform/sdk-file/src/secret-material-providers.ts +53 −21
  8. packages/platform/sdk/src/local/secret-stores.ts +17 −13
  9. packages/platform/sdk/src/plugins.ts +2 −2
  10. packages/platform/sdk/src/runtime/execution/scope/tool-invoker.ts +1 −1
  11. packages/platform/sdk/src/runtime/index.ts +2 −0
  12. packages/platform/sdk/src/runtime/source-artifacts.ts +2 −27
  13. packages/platform/sdk/src/runtime/sources/executor-tools.test.ts +0 −68
  14. packages/platform/sdk/src/runtime/sources/executor-tools.ts +0 −146
  15. packages/platform/sdk/src/runtime/sources/source-store/plugin-local-config.ts +0 −83
  16. packages/platform/server/src/index.test.ts +15 −14
  17. plugins/google-discovery/sdk/index.ts +0 −50
  18. plugins/graphql/sdk/index.ts +0 −43
  19. plugins/mcp/sdk/index.ts +0 −56
  20. plugins/mcp/sdk/tools.test.ts +1 −1
  21. plugins/openapi/sdk/index.ts +0 −46
apps/executor/src/cli/main.ts+0−207
@@ -90,7 +90,6 @@ import { · 89 unmodified lines
90import { decideInteractionHandling } from "./interaction-handling";90import { decideInteractionHandling } from "./interaction-handling";
91import {91import {
92 renderMcpSessionSummary,92 renderMcpSessionSummary,
93 renderUpSummary,
94 renderWebSessionSummary,93 renderWebSessionSummary,
95} from "./session-summary";94} from "./session-summary";
96import {95import {
@@ -415,8 +414,6 @@ const printRootHelp = (workflow: string) => · 318 unmodified lines
415 " Execute code against the local executor server.",414 " Execute code against the local executor server.",
416 " resume",415 " resume",
417 " Resume a paused execution.",416 " Resume a paused execution.",
418 " up / down / status / doctor",
419 " Compatibility commands for the detached local daemon.",
420 "",417 "",
421 "TIP",418 "TIP",
422 "",419 "",
@@ -625,154 +622,11 @@ const waitForReachability = (baseUrl: string, expected: boolean) => · 202 unmodified lines
625 return yield* failReachabilityTimeout({ baseUrl, expected });622 return yield* failReachabilityTimeout({ baseUrl, expected });
626 });623 });
627 624
628type LocalServerStatus = {
629 baseUrl: string;
630 reachable: boolean;
631 pidFile: string;
632 pid: number | null;
633 pidRunning: boolean;
634 logFile: string;
635 localDataDir: string;
636 webAssetsDir: string | null;
637 installation: {
638 accountId: string;
639 workspaceId: string;
640 } | null;
641};
642
643const getServerStatus = (
644 baseUrl: string,
645): Effect.Effect<LocalServerStatus, Error, FileSystem.FileSystem> =>
646 Effect.gen(function* () {
647 const pidRecord = yield* readPidRecord();
648 const reachable = yield* isServerReachable(baseUrl);
649 const installation = reachable
650 ? yield* getBootstrapClient(baseUrl).pipe(
651 Effect.flatMap((client) => client.local.installation({})),
652 Effect.map((installation) => ({
653 accountId: installation.actorScopeId,
654 workspaceId: installation.scopeId,
655 })),
656 Effect.catchAll(() => Effect.succeed(null)),
657 )
658 : null;
659
660 const pid = typeof pidRecord?.pid === "number" ? pidRecord.pid : null;
661 const pidRunning = pid !== null ? isPidRunning(pid) : false;
662 const logFile = pidRecord?.logFile ?? DEFAULT_SERVER_LOG_FILE;
663
664 return {
665 baseUrl,
666 reachable,
667 pidFile: DEFAULT_SERVER_PID_FILE,
668 pid,
669 pidRunning,
670 logFile,
671 localDataDir: CLI_LOCAL_DATA_DIR,
672 webAssetsDir: resolveRuntimeWebAssetsDir(),
673 installation,
674 } satisfies LocalServerStatus;
675 });
676
677const renderStatus = (status: LocalServerStatus): string =>
678 [
679 `baseUrl: ${status.baseUrl}`,
680 `reachable: ${status.reachable ? "yes" : "no"}`,
681 `pid: ${status.pid ?? "none"}`,
682 `pidRunning: ${status.pidRunning ? "yes" : "no"}`,
683 `pidFile: ${status.pidFile}`,
684 `logFile: ${status.logFile}`,
685 `localDataDir: ${status.localDataDir}`,
686 `webAssetsDir: ${status.webAssetsDir ?? "missing"}`,
687 `workspaceId: ${status.installation?.workspaceId ?? "unavailable"}`,
688 ].join("\n");
689
690const getDoctorReport = (baseUrl: string) =>
691 getServerStatus(baseUrl).pipe(
692 Effect.map((status) => {
693 const checks = {
694 serverReachable: {
695 ok: status.reachable,
696 detail: status.reachable ? `reachable at ${status.baseUrl}` : `not reachable at ${status.baseUrl}`,
697 },
698 pidFile: {
699 ok: status.pid !== null,
700 detail: status.pid !== null ? `pid ${status.pid}` : `missing pid file at ${status.pidFile}`,
701 },
702 process: {
703 ok: status.pidRunning,
704 detail: status.pidRunning ? `pid ${status.pid}` : "no live daemon process recorded",
705 },
706 database: {
707 ok: status.localDataDir.length > 0,
708 detail: status.localDataDir,
709 },
710 webAssets: {
711 ok: status.webAssetsDir !== null,
712 detail: status.webAssetsDir ?? "missing bundled web assets",
713 },
714 installation: {
715 ok: status.installation !== null,
716 detail: status.installation
717 ? `workspace ${status.installation.workspaceId}`
718 : "local installation unavailable",
719 },
720 } as const;
721
722 return {
723 ok: Object.values(checks).every((check) => check.ok),
724 status,
725 checks,
726 };
727 }),
728 );
729
730const printJson = (value: unknown) =>
731 Effect.sync(() => {
732 console.log(JSON.stringify(value, null, 2));
733 });
734
735const printText = (value: string) =>625const printText = (value: string) =>
736 Effect.sync(() => {626 Effect.sync(() => {
737 console.log(value);627 console.log(value);
738 });628 });
739 629
740const stopServer = (baseUrl: string) =>
741 Effect.gen(function* () {
742 const fs = yield* FileSystem.FileSystem;
743 const removePidFile = fs.remove(DEFAULT_SERVER_PID_FILE, {
744 force: true,
745 }).pipe(Effect.ignore);
746 const pidRecord = yield* readPidRecord();
747 const pid = typeof pidRecord?.pid === "number" ? pidRecord.pid : null;
748
749 if (pid === null) {
750 yield* removePidFile;
751 return false;
752 }
753
754 if (!isPidRunning(pid)) {
755 yield* removePidFile;
756 return false;
757 }
758
759
760 yield* Effect.sync(() => {
761 process.kill(pid, "SIGTERM");
762 });
763
764 yield* waitForReachability(baseUrl, false).pipe(
765 Effect.catchAll(() =>
766 removePidFile.pipe(
767 Effect.ignore,
768 Effect.zipRight(Effect.fail(executorAppEffectError("cli/main", `Timed out stopping local executor server pid ${pid}`))),
769 ),
770 ),
771 );
772
773 return true;
774 });
775
776const ensureServer = (baseUrl: string = DEFAULT_SERVER_BASE_URL) =>630const ensureServer = (baseUrl: string = DEFAULT_SERVER_BASE_URL) =>
777 Effect.gen(function* () {631 Effect.gen(function* () {
778 const reachable = yield* isServerReachable(baseUrl);632 const reachable = yield* isServerReachable(baseUrl);
@@ -1243,63 +1097,6 @@ const mcpCommand = Command.make( · 464 unmodified lines
1243 : runForegroundSession({ kind: "mcp", port }),1097 : runForegroundSession({ kind: "mcp", port }),
1244).pipe(Command.withDescription("Start a foreground MCP session, or run stdio MCP with --stdio"));1098).pipe(Command.withDescription("Start a foreground MCP session, or run stdio MCP with --stdio"));
1245 1099
1246const upCommand = Command.make(
1247 "up",
1248 {
1249 baseUrl: Options.text("base-url").pipe(Options.withDefault(DEFAULT_SERVER_BASE_URL)),
1250 },
1251 ({ baseUrl }) =>
1252 ensureServer(baseUrl).pipe(
1253 Effect.flatMap(({ started }) =>
1254 getServerStatus(baseUrl).pipe(
1255 Effect.flatMap((status) => printText(renderUpSummary({ started, status }))),
1256 )
1257 ),
1258 ),
1259).pipe(Command.withDescription("Start the local executor server if needed and show how to use it"));
1260
1261const downCommand = Command.make(
1262 "down",
1263 {
1264 baseUrl: Options.text("base-url").pipe(Options.withDefault(DEFAULT_SERVER_BASE_URL)),
1265 },
1266 ({ baseUrl }) =>
1267 stopServer(baseUrl).pipe(
1268 Effect.flatMap((stopped) =>
1269 printText(stopped ? "Stopped local executor server." : "Local executor server is not running."),
1270 ),
1271 ),
1272).pipe(Command.withDescription("Stop the local executor server"));
1273
1274const statusCommand = Command.make(
1275 "status",
1276 {
1277 baseUrl: Options.text("base-url").pipe(Options.withDefault(DEFAULT_SERVER_BASE_URL)),
1278 json: Options.boolean("json").pipe(Options.withDefault(false)),
1279 },
1280 ({ baseUrl, json }) =>
1281 getServerStatus(baseUrl).pipe(
1282 Effect.flatMap((status) => json ? printJson(status) : printText(renderStatus(status))),
1283 ),
1284).pipe(Command.withDescription("Show local executor server status"));
1285
1286const doctorCommand = Command.make(
1287 "doctor",
1288 {
1289 baseUrl: Options.text("base-url").pipe(Options.withDefault(DEFAULT_SERVER_BASE_URL)),
1290 json: Options.boolean("json").pipe(Options.withDefault(false)),
1291 },
1292 ({ baseUrl, json }) =>
1293 getDoctorReport(baseUrl).pipe(
1294 Effect.flatMap((report) => json
1295 ? printJson(report)
1296 : printText([
1297 `ok: ${report.ok ? "yes" : "no"}`,
1298 ...Object.entries(report.checks).map(([name, check]) => `${name}: ${check.ok ? "ok" : "fail"} - ${check.detail}`),
1299 ].join("\n"))),
1300 ),
1301).pipe(Command.withDescription("Check local executor install and daemon health"));
1302
1303const callCommand = Command.make(1100const callCommand = Command.make(
1304 "call",1101 "call",
1305 {1102 {
@@ -1386,10 +1183,6 @@ const root = Command.make("executor").pipe( · 80 unmodified lines
1386 serverCommand,1183 serverCommand,
1387 webCommand,1184 webCommand,
1388 mcpCommand,1185 mcpCommand,
1389 upCommand,
1390 downCommand,
1391 statusCommand,
1392 doctorCommand,
1393 callCommand,1186 callCommand,
1394 resumeCommand,1187 resumeCommand,
1395 devCommand,1188 devCommand,
packages/hosts/mcp/src/paused-result.test.ts+1−1
@@ -38,7 +38,7 @@ const makeEnvelope = ( · 37 unmodified lines
38 scopeId: "ws_123" as never,38 scopeId: "ws_123" as never,
39 createdByScopeId: "acct_123" as never,39 createdByScopeId: "acct_123" as never,
40 status: "waiting_for_interaction",40 status: "waiting_for_interaction",
41 code: "return await tools.executor.sources.add({ endpoint: 'https://mcp.axiom.co/mcp' });",41 code: "return await tools.executor.mcp.createSource({ name: 'Axiom', endpoint: 'https://mcp.axiom.co/mcp', transport: 'streamable-http', queryParams: null, headers: null, command: null, args: null, env: null, cwd: null, auth: { kind: 'none' } });",
42 resultJson: null,42 resultJson: null,
43 errorText: null,43 errorText: null,
44 logsJson: null,44 logsJson: null,
packages/platform/sdk-file/src/config.test.ts+8−54
@@ -8,7 +8,6 @@ import * as Effect from "effect/Effect"; · 7 unmodified lines
8import {8import {
9 loadLocalExecutorConfig,9 loadLocalExecutorConfig,
10 mergeLocalExecutorConfigs,10 mergeLocalExecutorConfigs,
11 migrateLegacyLocalExecutorConfigFile,
12 resolveDefaultHomeConfigCandidates,11 resolveDefaultHomeConfigCandidates,
13 resolveDefaultHomeStateDirectory,12 resolveDefaultHomeStateDirectory,
14 resolveLocalWorkspaceContext,13 resolveLocalWorkspaceContext,
@@ -39,10 +38,15 @@ describe("local-config", () => { · 24 unmodified lines
39 "sources": {38 "sources": {
40 "github": {39 "github": {
41 "kind": "openapi",40 "kind": "openapi",
42 "binding": {41 "config": {
43 "specUrl": "https://example.com/openapi.json",42 "specUrl": "https://example.com/openapi.json",
44 },43 "baseUrl": null,
45 },44 "auth": {
45 "kind": "none"
46 },
47 "defaultHeaders": null
48 }
49 }
46 },50 },
47}51}
48`,52`,
@@ -137,54 +141,4 @@ describe("local-config", () => { · 88 unmodified lines
137 141
138 expect(merged?.runtime).toBe("deno");142 expect(merged?.runtime).toBe("deno");
139 });143 });
140
141 it.effect("migrates legacy preset source stubs before strict decode", () =>
142 Effect.gen(function* () {
143 const fs = yield* FileSystem.FileSystem;
144 const workspaceRoot = yield* makeWorkspaceRoot();
145 const homeConfigPath = join(workspaceRoot, "executor-home.jsonc");
146
147 yield* fs.writeFileString(
148 homeConfigPath,
149 `{
150 "sources": {
151 "axiom": {
152 "name": "Axiom MCP",
153 "kind": "mcp"
154 },
155 "stripe-api": {
156 "name": "Stripe API",
157 "namespace": "stripe.api",
158 "kind": "openapi"
159 },
160 "kept": {
161 "name": "Kept",
162 "kind": "openapi",
163 "connection": {
164 "endpoint": "https://example.com"
165 },
166 "binding": {
167 "specUrl": "https://example.com/openapi.json"
168 }
169 }
170 }
171}
172`,
173 );
174
175 const changed = yield* migrateLegacyLocalExecutorConfigFile(homeConfigPath);
176 const migratedContent = yield* fs.readFileString(homeConfigPath, "utf8");
177 const context = yield* resolveLocalWorkspaceContext({
178 workspaceRoot,
179 homeConfigPath,
180 });
181 const loaded = yield* loadLocalExecutorConfig(context);
182
183 expect(changed).toBe(true);
184 expect(migratedContent).not.toContain("\"axiom\"");
185 expect(migratedContent).not.toContain("\"stripe-api\"");
186 expect(loaded.config?.sources?.kept?.kind).toBe("openapi");
187 expect(loaded.config?.sources?.axiom).toBeUndefined();
188 }).pipe(Effect.provide(NodeFileSystem.layer)),
189 );
190});144});
packages/platform/sdk-file/src/config.ts+0−172
@@ -33,9 +33,6 @@ export type FileLoadedExecutorConfig = LoadedLocalExecutorConfig & { · 32 unmodified lines
33export const encodeLocalExecutorConfig = (config: LocalExecutorConfig): string =>33export const encodeLocalExecutorConfig = (config: LocalExecutorConfig): string =>
34 `${JSON.stringify(config, null, 2)}\n`;34 `${JSON.stringify(config, null, 2)}\n`;
35 35
36const encodeUnknownConfigDocument = (config: unknown): string =>
37 `${JSON.stringify(config, null, 2)}\n`;
38
39export const resolveConfigRelativePath = (input: {36export const resolveConfigRelativePath = (input: {
40 path: string;37 path: string;
41 scopeRoot: string;38 scopeRoot: string;
@@ -226,130 +223,6 @@ const parseJsonc = (input: { path: string; content: string }): LocalExecutorConf · 184 unmodified lines
226 }223 }
227};224};
228 225
229const parseUnknownJsoncDocument = (input: {
230 path: string;
231 content: string;
232}): unknown => {
233 const errors: JsoncParseError[] = [];
234
235 try {
236 const parsed = parseJsoncDocument(input.content, errors, {
237 allowTrailingComma: true,
238 });
239 if (errors.length > 0) {
240 throw new LocalExecutorConfigDecodeError({
241 message: `Invalid executor config at ${input.path}: ${formatJsoncParseErrors(input.content, errors)}`,
242 path: input.path,
243 details: formatJsoncParseErrors(input.content, errors),
244 });
245 }
246
247 return parsed;
248 } catch (cause) {
249 if (cause instanceof LocalExecutorConfigDecodeError) {
250 throw cause;
251 }
252 throw new LocalExecutorConfigDecodeError({
253 message: `Invalid executor config at ${input.path}: ${unknownLocalErrorDetails(cause)}`,
254 path: input.path,
255 details: unknownLocalErrorDetails(cause),
256 });
257 }
258};
259
260const asRecord = (value: unknown): Record<string, unknown> | null =>
261 value !== null && typeof value === "object" && !Array.isArray(value)
262 ? value as Record<string, unknown>
263 : null;
264
265const migrateLegacySourceEntries = (
266 sources: unknown,
267): {
268 sources: unknown;
269 changed: boolean;
270} => {
271 const sourceMap = asRecord(sources);
272 if (!sourceMap) {
273 return {
274 sources,
275 changed: false,
276 };
277 }
278
279 let changed = false;
280 const migratedEntries = Object.entries(sourceMap).flatMap(([key, value]) => {
281 const source = asRecord(value);
282 if (!source) {
283 return [[key, value] as const];
284 }
285
286 const hasConnection = asRecord(source.connection) !== null;
287 const kind = typeof source.kind === "string" ? source.kind : null;
288 const isLegacyPresetStub =
289 kind !== null
290 && hasConnection === false;
291
292 if (isLegacyPresetStub) {
293 changed = true;
294 return [];
295 }
296
297 return [[key, value] as const];
298 });
299
300 if (!changed) {
301 return {
302 sources,
303 changed: false,
304 };
305 }
306
307 return {
308 sources: migratedEntries.length > 0
309 ? Object.fromEntries(migratedEntries)
310 : undefined,
311 changed: true,
312 };
313};
314
315const migrateLegacyLocalExecutorConfigDocument = (
316 value: unknown,
317): {
318 value: unknown;
319 changed: boolean;
320} => {
321 const root = asRecord(value);
322 if (!root) {
323 return {
324 value,
325 changed: false,
326 };
327 }
328
329 const migratedSources = migrateLegacySourceEntries(root.sources);
330 if (!migratedSources.changed) {
331 return {
332 value,
333 changed: false,
334 };
335 }
336
337 const nextRoot: Record<string, unknown> = {
338 ...root,
339 };
340
341 if (migratedSources.sources === undefined) {
342 delete nextRoot.sources;
343 } else {
344 nextRoot.sources = migratedSources.sources;
345 }
346
347 return {
348 value: nextRoot,
349 changed: true,
350 };
351};
352
353const mergeSourceMaps = (226const mergeSourceMaps = (
354 base: Record<string, LocalConfigSource> | undefined,227 base: Record<string, LocalConfigSource> | undefined,
355 extra: Record<string, LocalConfigSource> | undefined,228 extra: Record<string, LocalConfigSource> | undefined,
@@ -553,51 +426,6 @@ export const readOptionalLocalExecutorConfig = ( · 197 unmodified lines
553 });426 });
554 });427 });
555 428
556export const migrateLegacyLocalExecutorConfigFile = (
557 path: string,
558): Effect.Effect<
559 boolean,
560 LocalFileSystemError | LocalExecutorConfigDecodeError,
561 FileSystem.FileSystem
562> =>
563 Effect.gen(function* () {
564 const fs = yield* FileSystem.FileSystem;
565 const exists = yield* fs.exists(path).pipe(
566 Effect.mapError(mapFileSystemError(path, "check config path")),
567 );
568 if (!exists) {
569 return false;
570 }
571
572 const content = yield* fs.readFileString(path, "utf8").pipe(
573 Effect.mapError(mapFileSystemError(path, "read config")),
574 );
575 const parsed = parseUnknownJsoncDocument({ path, content });
576 const migrated = migrateLegacyLocalExecutorConfigDocument(parsed);
577
578 if (!migrated.changed) {
579 return false;
580 }
581
582 const decoded = decodeLocalExecutorConfig(migrated.value);
583 yield* fs.writeFileString(path, encodeUnknownConfigDocument(decoded)).pipe(
584 Effect.mapError(mapFileSystemError(path, "write config")),
585 );
586 return true;
587 });
588
589export const migrateLegacyLocalExecutorConfigs = (
590 context: ResolvedLocalWorkspaceContext,
591): Effect.Effect<
592 void,
593 LocalFileSystemError | LocalExecutorConfigDecodeError,
594 FileSystem.FileSystem
595> =>
596 Effect.gen(function* () {
597 yield* migrateLegacyLocalExecutorConfigFile(context.homeConfigPath);
598 yield* migrateLegacyLocalExecutorConfigFile(context.projectConfigPath);
599 });
600
601export const loadLocalExecutorConfig = (429export const loadLocalExecutorConfig = (
602 context: ResolvedLocalWorkspaceContext,430 context: ResolvedLocalWorkspaceContext,
603): Effect.Effect<431): Effect.Effect<
packages/platform/sdk-file/src/executor-state-store.ts+1−148
@@ -28,8 +28,6 @@ import { · 27 unmodified lines
28 28
29const LOCAL_EXECUTOR_STATE_VERSION = 1 as const;29const LOCAL_EXECUTOR_STATE_VERSION = 1 as const;
30const LOCAL_EXECUTOR_STATE_BASENAME = "executor-state.json";30const LOCAL_EXECUTOR_STATE_BASENAME = "executor-state.json";
31const LEGACY_LOCAL_SECRET_STORE_ID = "sts_builtin_local";
32const LEGACY_KEYCHAIN_SECRET_STORE_ID = "sts_builtin_keychain";
33 31
34const LocalExecutorStateSnapshotSchema = Schema.Struct({32const LocalExecutorStateSnapshotSchema = Schema.Struct({
35 version: Schema.Literal(LOCAL_EXECUTOR_STATE_VERSION),33 version: Schema.Literal(LOCAL_EXECUTOR_STATE_VERSION),
@@ -63,145 +61,6 @@ const defaultLocalExecutorStateSnapshot = (): LocalExecutorStateSnapshot => ({ · 27 unmodified lines
63const cloneValue = <T>(value: T): T =>61const cloneValue = <T>(value: T): T =>
64 JSON.parse(JSON.stringify(value)) as T;62 JSON.parse(JSON.stringify(value)) as T;
65 63
66const asRecord = (
67 value: unknown,
68): Record<string, unknown> | null =>
69 value !== null && typeof value === "object" && !Array.isArray(value)
70 ? (value as Record<string, unknown>)
71 : null;
72
73const legacySecretStoreIdForProviderId = (providerId: string): string =>
74 providerId === "local"
75 ? LEGACY_LOCAL_SECRET_STORE_ID
76 : providerId === "keychain"
77 ? LEGACY_KEYCHAIN_SECRET_STORE_ID
78 : `sts_legacy_${providerId}`;
79
80const legacySecretStoreNameForProviderId = (providerId: string): string => {
81 switch (providerId) {
82 case "local":
83 return "Local Store";
84 case "keychain":
85 return process.platform === "darwin"
86 ? "macOS Keychain"
87 : "Desktop Keyring";
88 default:
89 return `Legacy ${providerId}`;
90 }
91};
92
93const migrateLegacyExecutorStateValue = (
94 value: unknown,
95 input: {
96 scopeId: string;
97 },
98): {
99 value: unknown;
100 migrated: boolean;
101} => {
102 if (Array.isArray(value)) {
103 let migrated = false;
104 const next = value.map((item) => {
105 const result = migrateLegacyExecutorStateValue(item, input);
106 migrated = migrated || result.migrated;
107 return result.value;
108 });
109 return { value: next, migrated };
110 }
111
112 const record = asRecord(value);
113 if (record === null) {
114 return { value, migrated: false };
115 }
116
117 let migrated = false;
118 const next: Record<string, unknown> = {};
119
120 for (const [key, entry] of Object.entries(record)) {
121 let nextKey = key;
122 if (key === "workspaceId") {
123 nextKey = "scopeId";
124 migrated = true;
125 } else if (key === "actorAccountId") {
126 nextKey = "actorScopeId";
127 migrated = true;
128 } else if (key === "createdByAccountId") {
129 nextKey = "createdByScopeId";
130 migrated = true;
131 } else if (key === "workspaceOauthClients") {
132 nextKey = "scopeOauthClients";
133 migrated = true;
134 }
135
136 const migratedEntry = migrateLegacyExecutorStateValue(entry, input);
137 migrated = migrated || migratedEntry.migrated;
138 next[nextKey] = migratedEntry.value;
139 }
140
141 if (next.version === LOCAL_EXECUTOR_STATE_VERSION) {
142 const secretMaterialsValue = Array.isArray(next.secretMaterials)
143 ? next.secretMaterials
144 : [];
145 const secretStoresValue = Array.isArray(next.secretStores)
146 ? next.secretStores
147 : [];
148 const secretStoresById = new Map<string, Record<string, unknown>>();
149
150 for (const entry of secretStoresValue) {
151 const record = asRecord(entry);
152 if (record !== null && typeof record.id === "string") {
153 secretStoresById.set(record.id, record);
154 }
155 }
156
157 const migratedSecretMaterials = secretMaterialsValue.map((entry) => {
158 const record = asRecord(entry);
159 if (record === null) {
160 return entry;
161 }
162
163 if (typeof record.providerId !== "string" || typeof record.storeId === "string") {
164 return entry;
165 }
166
167 const providerId = record.providerId;
168 const storeId = legacySecretStoreIdForProviderId(providerId);
169 const createdAt =
170 typeof record.createdAt === "number" ? record.createdAt : 0;
171 const updatedAt =
172 typeof record.updatedAt === "number" ? record.updatedAt : createdAt;
173
174 secretStoresById.set(storeId, {
175 id: storeId,
176 scopeId: input.scopeId,
177 name: legacySecretStoreNameForProviderId(providerId),
178 kind: providerId,
179 status: providerId === "local" || providerId === "keychain"
180 ? "connected"
181 : "error",
182 enabled: true,
183 createdAt,
184 updatedAt,
185 });
186
187 migrated = true;
188 return {
189 ...record,
190 storeId,
191 };
192 });
193
194 if (!Array.isArray(next.secretStores)) {
195 migrated = true;
196 }
197
198 next.secretStores = [...secretStoresById.values()];
199 next.secretMaterials = migratedSecretMaterials;
200 }
201
202 return { value: next, migrated };
203};
204
205const mapFileSystemError = (path: string, action: string) => (cause: unknown) =>64const mapFileSystemError = (path: string, action: string) => (cause: unknown) =>
206 new LocalFileSystemError({65 new LocalFileSystemError({
207 message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`,66 message: `Failed to ${action} ${path}: ${unknownLocalErrorDetails(cause)}`,
@@ -258,15 +117,9 @@ const readStateFromDisk = ( · 50 unmodified lines
258 try: () => JSON.parse(content) as unknown,117 try: () => JSON.parse(content) as unknown,
259 catch: mapFileSystemError(path, "parse executor state"),118 catch: mapFileSystemError(path, "parse executor state"),
260 });119 });
261 const migrated = migrateLegacyExecutorStateValue(parsed, {120 const decoded = yield* decodeLocalExecutorStateSnapshot(parsed).pipe(
262 scopeId: deriveLocalInstallation(context).scopeId,
263 });
264 const decoded = yield* decodeLocalExecutorStateSnapshot(migrated.value).pipe(
265 Effect.mapError(mapFileSystemError(path, "decode executor state")),121 Effect.mapError(mapFileSystemError(path, "decode executor state")),
266 );122 );
267 if (migrated.migrated) {
268 yield* writeStateToDisk(context, decoded);
269 }
270 return decoded;123 return decoded;
271 });124 });
272 125
packages/platform/sdk-file/src/index.ts+8−5
@@ -27,11 +27,11 @@ import { · 26 unmodified lines
27 type ExecutorRuntime,27 type ExecutorRuntime,
28 type ExecutorRuntimeOptions,28 type ExecutorRuntimeOptions,
29 type ResolveSecretMaterial,29 type ResolveSecretMaterial,
30 emptyExecutorPluginRegistry,
30} from "@executor/platform-sdk/runtime";31} from "@executor/platform-sdk/runtime";
31import * as Effect from "effect/Effect";32import * as Effect from "effect/Effect";
32import {33import {
33 type LoadedLocalExecutorConfig,34 type LoadedLocalExecutorConfig,
34 migrateLegacyLocalExecutorConfigs,
35 type ResolvedLocalWorkspaceContext,35 type ResolvedLocalWorkspaceContext,
36 loadLocalExecutorConfig,36 loadLocalExecutorConfig,
37 resolveConfigRelativePath,37 resolveConfigRelativePath,
@@ -238,10 +238,6 @@ export const createLocalExecutorRepositoriesEffect = ( · 200 unmodified lines
238 homeStateDirectory: options.homeStateDirectory,238 homeStateDirectory: options.homeStateDirectory,
239 }),239 }),
240 ).pipe(Effect.mapError(toError));240 ).pipe(Effect.mapError(toError));
241 yield* bindFileSystem(
242 fileSystem,
243 migrateLegacyLocalExecutorConfigs(workspaceContext),
244 ).pipe(Effect.mapError(toError));
245 241
246 const executorStateStorage = createLocalExecutorStatePersistence(242 const executorStateStorage = createLocalExecutorStatePersistence(
247 workspaceContext,243 workspaceContext,
@@ -252,14 +248,17 @@ export const createLocalExecutorRepositoriesEffect = ( · 4 unmodified lines
252 fileSystem,248 fileSystem,
253 );249 );
254 const loadedConfig = yield* workspaceConfigStore.load();250 const loadedConfig = yield* workspaceConfigStore.load();
251 const pluginRegistry = runtimeOptions.pluginRegistry ?? emptyExecutorPluginRegistry();
255 yield* provisionBuiltinSecretStores({252 yield* provisionBuiltinSecretStores({
256 executorState: executorStateStorage.executorState,253 executorState: executorStateStorage.executorState,
254 pluginRegistry,
257 scopeId: deriveLocalInstallation(workspaceContext).scopeId,255 scopeId: deriveLocalInstallation(workspaceContext).scopeId,
258 });256 });
259 const resolveSecretMaterial: ResolveSecretMaterial =257 const resolveSecretMaterial: ResolveSecretMaterial =
260 runtimeOptions.resolveSecretMaterial258 runtimeOptions.resolveSecretMaterial
261 ?? createDefaultSecretMaterialResolver({259 ?? createDefaultSecretMaterialResolver({
262 executorState: executorStateStorage.executorState,260 executorState: executorStateStorage.executorState,
261 pluginRegistry,
263 });262 });
264 263
265 return {264 return {
@@ -288,14 +287,17 @@ export const createLocalExecutorRepositoriesEffect = ( · 22 unmodified lines
288 resolve: resolveSecretMaterial,287 resolve: resolveSecretMaterial,
289 store: createDefaultSecretMaterialStorer({288 store: createDefaultSecretMaterialStorer({
290 executorState: executorStateStorage.executorState,289 executorState: executorStateStorage.executorState,
290 pluginRegistry,
291 resolveSecretMaterial,291 resolveSecretMaterial,
292 }),292 }),
293 delete: createDefaultSecretMaterialDeleter({293 delete: createDefaultSecretMaterialDeleter({
294 executorState: executorStateStorage.executorState,294 executorState: executorStateStorage.executorState,
295 pluginRegistry,
295 resolveSecretMaterial,296 resolveSecretMaterial,
296 }),297 }),
297 update: createDefaultSecretMaterialUpdater({298 update: createDefaultSecretMaterialUpdater({
298 executorState: executorStateStorage.executorState,299 executorState: executorStateStorage.executorState,
300 pluginRegistry,
299 resolveSecretMaterial,301 resolveSecretMaterial,
300 }),302 }),
301 },303 },
@@ -308,6 +310,7 @@ export const createLocalExecutorRepositoriesEffect = ( · 6 unmodified lines
308 instanceConfig: {310 instanceConfig: {
309 resolve: createLocalInstanceConfigResolver({311 resolve: createLocalInstanceConfigResolver({
310 executorState: executorStateStorage.executorState,312 executorState: executorStateStorage.executorState,
313 pluginRegistry,
311 }),314 }),
312 },315 },
313 close: executorStateStorage.close,316 close: executorStateStorage.close,
packages/platform/sdk-file/src/secret-material-providers.ts+53−21
@@ -4,6 +4,9 @@ import type { · 3 unmodified lines
4 InstanceConfig,4 InstanceConfig,
5 SecretProvider,5 SecretProvider,
6} from "@executor/platform-sdk/contracts";6} from "@executor/platform-sdk/contracts";
7import type {
8 ExecutorSdkPluginRegistry,
9} from "@executor/platform-sdk/plugins";
7import type {10import type {
8 SecretMaterial,11 SecretMaterial,
9 SecretMaterialPurpose,12 SecretMaterialPurpose,
@@ -43,8 +46,10 @@ const trimOrNull = (value: string | null | undefined): string | null => { · 33 unmodified lines
43 return trimmed.length > 0 ? trimmed : null;46 return trimmed.length > 0 ? trimmed : null;
44};47};
45 48
46const builtinSecretStoreContributions = () =>49const builtinSecretStoreContributions = (
47 registeredSecretStoreContributions()50 pluginRegistry: ExecutorSdkPluginRegistry,
51) =>
52 registeredSecretStoreContributions(pluginRegistry)
48 .filter((entry) => entry.builtin !== undefined)53 .filter((entry) => entry.builtin !== undefined)
49 .filter((entry) => entry.builtin?.enabled?.() ?? true)54 .filter((entry) => entry.builtin?.enabled?.() ?? true)
50 .map((entry) => ({55 .map((entry) => ({
@@ -52,11 +57,17 @@ const builtinSecretStoreContributions = () => · 1 unmodified line
52 builtin: entry.builtin!,57 builtin: entry.builtin!,
53 }));58 }));
54 59
55const resolveStoreContribution = (store: SecretStore) =>60const resolveStoreContribution = (
56 getSecretStoreContribution(store.kind);61 pluginRegistry: ExecutorSdkPluginRegistry,
57 62 store: SecretStore,
58const loadStoreCapabilities = (store: SecretStore) =>63) =>
59 resolveStoreContribution(store).getCapabilities({64 getSecretStoreContribution(pluginRegistry, store.kind);
65
66const loadStoreCapabilities = (
67 pluginRegistry: ExecutorSdkPluginRegistry,
68 store: SecretStore,
69) =>
70 resolveStoreContribution(pluginRegistry, store).getCapabilities({
60 store,71 store,
61 }) as Effect.Effect<{72 }) as Effect.Effect<{
62 canCreateSecrets: boolean;73 canCreateSecrets: boolean;
@@ -72,6 +83,7 @@ const parseDefaultStoreKind = (value: string | undefined): string | null => { · 9 unmodified lines
72};83};
73 84
74const resolveDefaultStoreId = (85const resolveDefaultStoreId = (
86 pluginRegistry: ExecutorSdkPluginRegistry,
75 stores: ReadonlyArray<{87 stores: ReadonlyArray<{
76 id: string;88 id: string;
77 kind: string;89 kind: string;
@@ -85,7 +97,7 @@ const resolveDefaultStoreId = ( · 7 unmodified lines
85 }97 }
86 }98 }
87 99
88 const prioritizedBuiltins = builtinSecretStoreContributions()100 const prioritizedBuiltins = builtinSecretStoreContributions(pluginRegistry)
89 .sort((left, right) =>101 .sort((left, right) =>
90 (right.builtin.defaultPriority ?? 0) - (left.builtin.defaultPriority ?? 0)102 (right.builtin.defaultPriority ?? 0) - (left.builtin.defaultPriority ?? 0)
91 );103 );
@@ -102,12 +114,13 @@ const resolveDefaultStoreId = ( · 10 unmodified lines
102 114
103export const provisionBuiltinSecretStores = (input: {115export const provisionBuiltinSecretStores = (input: {
104 executorState: ExecutorStateStoreShape;116 executorState: ExecutorStateStoreShape;
117 pluginRegistry: ExecutorSdkPluginRegistry;
105 scopeId: string;118 scopeId: string;
106}) =>119}) =>
107 Effect.gen(function* () {120 Effect.gen(function* () {
108 const now = Date.now();121 const now = Date.now();
109 122
110 for (const entry of builtinSecretStoreContributions()) {123 for (const entry of builtinSecretStoreContributions(input.pluginRegistry)) {
111 const existing = yield* input.executorState.secretStores.getById(124 const existing = yield* input.executorState.secretStores.getById(
112 entry.builtin.storeId as SecretStore["id"],125 entry.builtin.storeId as SecretStore["id"],
113 );126 );
@@ -169,6 +182,7 @@ const provideSecretResolver = <A, E>( · 55 unmodified lines
169 182
170export const createDefaultSecretMaterialResolver = (input: {183export const createDefaultSecretMaterialResolver = (input: {
171 executorState: ExecutorStateStoreShape;184 executorState: ExecutorStateStoreShape;
185 pluginRegistry: ExecutorSdkPluginRegistry;
172 keychainServiceName?: string;186 keychainServiceName?: string;
173}): ResolveSecretMaterial => {187}): ResolveSecretMaterial => {
174 let resolveSecretMaterial!: ResolveSecretMaterial;188 let resolveSecretMaterial!: ResolveSecretMaterial;
@@ -182,7 +196,7 @@ export const createDefaultSecretMaterialResolver = (input: { · 7 unmodified lines
182 executorState: input.executorState,196 executorState: input.executorState,
183 storeId: material.storeId,197 storeId: material.storeId,
184 });198 });
185 const contribution = resolveStoreContribution(store);199 const contribution = resolveStoreContribution(input.pluginRegistry, store);
186 return yield* provideSecretResolver(200 return yield* provideSecretResolver(
187 contribution.resolveSecret({201 contribution.resolveSecret({
188 secret: material,202 secret: material,
@@ -198,13 +212,14 @@ export const createDefaultSecretMaterialResolver = (input: { · 9 unmodified lines
198 212
199export const createDefaultSecretMaterialStorer = (input: {213export const createDefaultSecretMaterialStorer = (input: {
200 executorState: ExecutorStateStoreShape;214 executorState: ExecutorStateStoreShape;
215 pluginRegistry: ExecutorSdkPluginRegistry;
201 resolveSecretMaterial: ResolveSecretMaterial;216 resolveSecretMaterial: ResolveSecretMaterial;
202 keychainServiceName?: string;217 keychainServiceName?: string;
203}): StoreSecretMaterial => {218}): StoreSecretMaterial => {
204 return ({ purpose, value, name, storeId }) =>219 return ({ purpose, value, name, storeId }) =>
205 Effect.gen(function* () {220 Effect.gen(function* () {
206 const stores = yield* input.executorState.secretStores.listAll();221 const stores = yield* input.executorState.secretStores.listAll();
207 const resolvedStoreId = storeId ?? resolveDefaultStoreId(stores);222 const resolvedStoreId = storeId ?? resolveDefaultStoreId(input.pluginRegistry, stores);
208 if (resolvedStoreId === null) {223 if (resolvedStoreId === null) {
209 return yield* runtimeEffectError(224 return yield* runtimeEffectError(
210 "local/secret-material-providers",225 "local/secret-material-providers",
@@ -216,9 +231,9 @@ export const createDefaultSecretMaterialStorer = (input: { · 5 unmodified lines
216 executorState: input.executorState,231 executorState: input.executorState,
217 storeId: resolvedStoreId as SecretStore["id"],232 storeId: resolvedStoreId as SecretStore["id"],
218 });233 });
219 const contribution = resolveStoreContribution(store);234 const contribution = resolveStoreContribution(input.pluginRegistry, store);
220 235
221 const capabilities = yield* loadStoreCapabilities(store);236 const capabilities = yield* loadStoreCapabilities(input.pluginRegistry, store);
222 if (!capabilities.canCreateSecrets) {237 if (!capabilities.canCreateSecrets) {
223 return yield* runtimeEffectError(238 return yield* runtimeEffectError(
224 "local/secret-material-providers",239 "local/secret-material-providers",
@@ -258,6 +273,7 @@ export const createDefaultSecretMaterialStorer = (input: { · 33 unmodified lines
258 273
259export const createDefaultSecretMaterialUpdater = (input: {274export const createDefaultSecretMaterialUpdater = (input: {
260 executorState: ExecutorStateStoreShape;275 executorState: ExecutorStateStoreShape;
276 pluginRegistry: ExecutorSdkPluginRegistry;
261 resolveSecretMaterial: ResolveSecretMaterial;277 resolveSecretMaterial: ResolveSecretMaterial;
262 keychainServiceName?: string;278 keychainServiceName?: string;
263}): UpdateSecretMaterial => {279}): UpdateSecretMaterial => {
@@ -271,9 +287,9 @@ export const createDefaultSecretMaterialUpdater = (input: { · 7 unmodified lines
271 executorState: input.executorState,287 executorState: input.executorState,
272 storeId: material.storeId,288 storeId: material.storeId,
273 });289 });
274 const contribution = resolveStoreContribution(store);290 const contribution = resolveStoreContribution(input.pluginRegistry, store);
275 291
276 const capabilities = yield* loadStoreCapabilities(store);292 const capabilities = yield* loadStoreCapabilities(input.pluginRegistry, store);
277 if (!capabilities.canUpdateSecrets) {293 if (!capabilities.canUpdateSecrets) {
278 return yield* runtimeEffectError(294 return yield* runtimeEffectError(
279 "local/secret-material-providers",295 "local/secret-material-providers",
@@ -313,6 +329,7 @@ export const createDefaultSecretMaterialUpdater = (input: { · 33 unmodified lines
313 329
314export const createDefaultSecretMaterialDeleter = (input: {330export const createDefaultSecretMaterialDeleter = (input: {
315 executorState: ExecutorStateStoreShape;331 executorState: ExecutorStateStoreShape;
332 pluginRegistry: ExecutorSdkPluginRegistry;
316 resolveSecretMaterial: ResolveSecretMaterial;333 resolveSecretMaterial: ResolveSecretMaterial;
317 keychainServiceName?: string;334 keychainServiceName?: string;
318}): DeleteSecretMaterial => {335}): DeleteSecretMaterial => {
@@ -326,8 +343,8 @@ export const createDefaultSecretMaterialDeleter = (input: { · 7 unmodified lines
326 executorState: input.executorState,343 executorState: input.executorState,
327 storeId: material.storeId,344 storeId: material.storeId,
328 });345 });
329 const contribution = resolveStoreContribution(store);346 const contribution = resolveStoreContribution(input.pluginRegistry, store);
330 const capabilities = yield* loadStoreCapabilities(store);347 const capabilities = yield* loadStoreCapabilities(input.pluginRegistry, store);
331 const deleted = capabilities.canDeleteSecrets348 const deleted = capabilities.canDeleteSecrets
332 ? yield* provideSecretResolver(349 ? yield* provideSecretResolver(
333 contribution.deleteSecret({350 contribution.deleteSecret({
@@ -346,8 +363,10 @@ export const createDefaultSecretMaterialDeleter = (input: { · 12 unmodified lines
346 });363 });
347};364};
348 365
349const listAvailableSecretStorePlugins = (): SecretProvider[] => {366const listAvailableSecretStorePlugins = (
350 return registeredSecretStoreContributions().map((entry) => ({367 pluginRegistry: ExecutorSdkPluginRegistry,
368): SecretProvider[] => {
369 return registeredSecretStoreContributions(pluginRegistry).map((entry) => ({
351 kind: entry.kind,370 kind: entry.kind,
352 displayName: entry.displayName,371 displayName: entry.displayName,
353 canCreate: entry.canCreate,372 canCreate: entry.canCreate,
@@ -356,18 +375,20 @@ const listAvailableSecretStorePlugins = (): SecretProvider[] => { · 2 unmodified lines
356 375
357export const createLocalInstanceConfigResolver = (input: {376export const createLocalInstanceConfigResolver = (input: {
358 executorState: ExecutorStateStoreShape;377 executorState: ExecutorStateStoreShape;
378 pluginRegistry: ExecutorSdkPluginRegistry;
359}): ResolveInstanceConfig => () =>379}): ResolveInstanceConfig => () =>
360 Effect.gen(function* () {380 Effect.gen(function* () {
361 const stores = yield* input.executorState.secretStores.listAll();381 const stores = yield* input.executorState.secretStores.listAll();
362 return {382 return {
363 platform: process.platform,383 platform: process.platform,
364 secretStorePlugins: listAvailableSecretStorePlugins(),384 secretStorePlugins: listAvailableSecretStorePlugins(input.pluginRegistry),
365 defaultSecretStoreId: resolveDefaultStoreId(stores),385 defaultSecretStoreId: resolveDefaultStoreId(input.pluginRegistry, stores),
366 } satisfies InstanceConfig;386 } satisfies InstanceConfig;
367 });387 });
368 388
369export const SecretMaterialResolverLive = (input: {389export const SecretMaterialResolverLive = (input: {
370 executorState: ExecutorStateStoreShape;390 executorState: ExecutorStateStoreShape;
391 pluginRegistry: ExecutorSdkPluginRegistry;
371 resolveSecretMaterial?: ResolveSecretMaterial;392 resolveSecretMaterial?: ResolveSecretMaterial;
372 keychainServiceName?: string;393 keychainServiceName?: string;
373}) =>394}) =>
@@ -380,6 +401,7 @@ export const SecretMaterialResolverLive = (input: { · 6 unmodified lines
380 401
381export const SecretMaterialStorerLive = (input: {402export const SecretMaterialStorerLive = (input: {
382 executorState: ExecutorStateStoreShape;403 executorState: ExecutorStateStoreShape;
404 pluginRegistry: ExecutorSdkPluginRegistry;
383 resolveSecretMaterial: ResolveSecretMaterial;405 resolveSecretMaterial: ResolveSecretMaterial;
384 keychainServiceName?: string;406 keychainServiceName?: string;
385}) =>407}) =>
@@ -390,6 +412,7 @@ export const SecretMaterialStorerLive = (input: { · 4 unmodified lines
390 412
391export const SecretMaterialDeleterLive = (input: {413export const SecretMaterialDeleterLive = (input: {
392 executorState: ExecutorStateStoreShape;414 executorState: ExecutorStateStoreShape;
415 pluginRegistry: ExecutorSdkPluginRegistry;
393 resolveSecretMaterial: ResolveSecretMaterial;416 resolveSecretMaterial: ResolveSecretMaterial;
394 keychainServiceName?: string;417 keychainServiceName?: string;
395}) =>418}) =>
@@ -400,6 +423,7 @@ export const SecretMaterialDeleterLive = (input: { · 4 unmodified lines
400 423
401export const SecretMaterialUpdaterLive = (input: {424export const SecretMaterialUpdaterLive = (input: {
402 executorState: ExecutorStateStoreShape;425 executorState: ExecutorStateStoreShape;
426 pluginRegistry: ExecutorSdkPluginRegistry;
403 resolveSecretMaterial: ResolveSecretMaterial;427 resolveSecretMaterial: ResolveSecretMaterial;
404 keychainServiceName?: string;428 keychainServiceName?: string;
405}) =>429}) =>
@@ -410,6 +434,7 @@ export const SecretMaterialUpdaterLive = (input: { · 4 unmodified lines
410 434
411export const SecretMaterialLive = (input: {435export const SecretMaterialLive = (input: {
412 executorState: ExecutorStateStoreShape;436 executorState: ExecutorStateStoreShape;
437 pluginRegistry: ExecutorSdkPluginRegistry;
413 resolveSecretMaterial?: ResolveSecretMaterial;438 resolveSecretMaterial?: ResolveSecretMaterial;
414 keychainServiceName?: string;439 keychainServiceName?: string;
415}) =>440}) =>
@@ -421,8 +446,10 @@ export const SecretMaterialLive = (input: { · 5 unmodified lines
421 input.resolveSecretMaterial446 input.resolveSecretMaterial
422 ?? createDefaultSecretMaterialResolver({447 ?? createDefaultSecretMaterialResolver({
423 executorState: input.executorState,448 executorState: input.executorState,
449 pluginRegistry: input.pluginRegistry,
424 keychainServiceName: input.keychainServiceName,450 keychainServiceName: input.keychainServiceName,
425 }),451 }),
452 pluginRegistry: input.pluginRegistry,
426 keychainServiceName: input.keychainServiceName,453 keychainServiceName: input.keychainServiceName,
427 }),454 }),
428 SecretMaterialDeleterLive({455 SecretMaterialDeleterLive({
@@ -431,8 +458,10 @@ export const SecretMaterialLive = (input: { · 2 unmodified lines
431 input.resolveSecretMaterial458 input.resolveSecretMaterial
432 ?? createDefaultSecretMaterialResolver({459 ?? createDefaultSecretMaterialResolver({
433 executorState: input.executorState,460 executorState: input.executorState,
461 pluginRegistry: input.pluginRegistry,
434 keychainServiceName: input.keychainServiceName,462 keychainServiceName: input.keychainServiceName,
435 }),463 }),
464 pluginRegistry: input.pluginRegistry,
436 keychainServiceName: input.keychainServiceName,465 keychainServiceName: input.keychainServiceName,
437 }),466 }),
438 SecretMaterialUpdaterLive({467 SecretMaterialUpdaterLive({
@@ -441,14 +470,17 @@ export const SecretMaterialLive = (input: { · 2 unmodified lines
441 input.resolveSecretMaterial470 input.resolveSecretMaterial
442 ?? createDefaultSecretMaterialResolver({471 ?? createDefaultSecretMaterialResolver({
443 executorState: input.executorState,472 executorState: input.executorState,
473 pluginRegistry: input.pluginRegistry,
444 keychainServiceName: input.keychainServiceName,474 keychainServiceName: input.keychainServiceName,
445 }),475 }),
476 pluginRegistry: input.pluginRegistry,
446 keychainServiceName: input.keychainServiceName,477 keychainServiceName: input.keychainServiceName,
447 }),478 }),
448 );479 );
449 480
450export const LocalInstanceConfigLive = (input: {481export const LocalInstanceConfigLive = (input: {
451 executorState: ExecutorStateStoreShape;482 executorState: ExecutorStateStoreShape;
483 pluginRegistry: ExecutorSdkPluginRegistry;
452}) =>484}) =>
453 Layer.succeed(485 Layer.succeed(
454 LocalInstanceConfigService,486 LocalInstanceConfigService,
packages/platform/sdk/src/local/secret-stores.ts+17−13
@@ -18,6 +18,7 @@ import { · 17 unmodified lines
18 ControlPlaneStorageError,18 ControlPlaneStorageError,
19} from "../errors";19} from "../errors";
20import {20import {
21 ExecutorPluginRegistryService,
21 createManagedSecretStoreRecord,22 createManagedSecretStoreRecord,
22 getManagedSecretStore,23 getManagedSecretStore,
23 getSecretStoreContribution,24 getSecretStoreContribution,
@@ -75,13 +76,15 @@ const asRecord = (value: unknown): Record<string, unknown> | null => · 51 unmodified lines
75 ? value as Record<string, unknown>76 ? value as Record<string, unknown>
76 : null;77 : null;
77 78
78const getSecretStoreContributionOption = (kind: string) => {79const getSecretStoreContributionOption = (kind: string) =>
79 try {80 Effect.gen(function* () {
80 return Option.some(getSecretStoreContribution(kind));81 const pluginRegistry = yield* ExecutorPluginRegistryService;
81 } catch {82 try {
82 return Option.none<typeof getSecretStoreContribution extends (kind: string) => infer T ? T : never>();83 return Option.some(getSecretStoreContribution(pluginRegistry, kind));
83 }84 } catch {
84};85 return Option.none<typeof getSecretStoreContribution extends (registry: any, kind: string) => infer T ? T : never>();
86 }
87 });
85 88
86const defaultCapabilities: SecretStoreCapabilities = {89const defaultCapabilities: SecretStoreCapabilities = {
87 canCreateSecrets: false,90 canCreateSecrets: false,
@@ -95,7 +98,7 @@ const loadStoreCapabilities = ( · 7 unmodified lines
95 store: SecretStore,98 store: SecretStore,
96)=>99)=>
97 Effect.gen(function* () {100 Effect.gen(function* () {
98 const contribution = getSecretStoreContributionOption(store.kind);101 const contribution = yield* getSecretStoreContributionOption(store.kind);
99 if (Option.isNone(contribution)) {102 if (Option.isNone(contribution)) {
100 return defaultCapabilities;103 return defaultCapabilities;
101 }104 }
@@ -183,8 +186,9 @@ export const createLocalSecretStore = (payload: CreateSecretStorePayload) => · 81 unmodified lines
183 ),186 ),
184 ),187 ),
185 );188 );
189 const pluginRegistry = yield* ExecutorPluginRegistryService;
186 const contribution = yield* Effect.try({190 const contribution = yield* Effect.try({
187 try: () => getSecretStoreContribution(payload.kind),191 try: () => getSecretStoreContribution(pluginRegistry, payload.kind),
188 catch: (cause) =>192 catch: (cause) =>
189 cause instanceof Error ? cause : new Error(String(cause)),193 cause instanceof Error ? cause : new Error(String(cause)),
190 }).pipe(194 }).pipe(
@@ -254,7 +258,7 @@ export const updateLocalSecretStore = (input: { · 63 unmodified lines
254 ),258 ),
255 );259 );
256 260
257 const contribution = getSecretStoreContributionOption(store.kind);261 const contribution = yield* getSecretStoreContributionOption(store.kind);
258 const configRecord = input.payload.config === undefined262 const configRecord = input.payload.config === undefined
259 ? null263 ? null
260 : asRecord(input.payload.config);264 : asRecord(input.payload.config);
@@ -318,7 +322,7 @@ export const deleteLocalSecretStore = (storeId: string) => · 57 unmodified lines
318 });322 });
319 }323 }
320 324
321 const contribution = getSecretStoreContributionOption(store.value.kind);325 const contribution = yield* getSecretStoreContributionOption(store.value.kind);
322 if (Option.isNone(contribution)) {326 if (Option.isNone(contribution)) {
323 return yield* new ControlPlaneBadRequestError({327 return yield* new ControlPlaneBadRequestError({
324 operation: "secretStores.delete",328 operation: "secretStores.delete",
@@ -359,7 +363,7 @@ export const browseLocalSecretStore = (input: { · 34 unmodified lines
359 ),363 ),
360 );364 );
361 365
362 const contribution = getSecretStoreContributionOption(store.kind);366 const contribution = yield* getSecretStoreContributionOption(store.kind);
363 const capabilities = yield* loadStoreCapabilities(store);367 const capabilities = yield* loadStoreCapabilities(store);
364 if (Option.isNone(contribution) || !capabilities.canBrowseSecrets) {368 if (Option.isNone(contribution) || !capabilities.canBrowseSecrets) {
365 return yield* new ControlPlaneBadRequestError({369 return yield* new ControlPlaneBadRequestError({
@@ -399,7 +403,7 @@ export const importLocalSecretFromStore = (input: { · 33 unmodified lines
399 ),403 ),
400 );404 );
401 405
402 const contribution = getSecretStoreContributionOption(store.kind);406 const contribution = yield* getSecretStoreContributionOption(store.kind);
403 if (Option.isNone(contribution)) {407 if (Option.isNone(contribution)) {
404 return yield* new ControlPlaneBadRequestError({408 return yield* new ControlPlaneBadRequestError({
405 operation: "secretStores.import",409 operation: "secretStores.import",
packages/platform/sdk/src/plugins.ts+2−2
@@ -1402,7 +1402,7 @@ export const defineExecutorSecretStorePlugin = < · 1401 unmodified lines
1402 ...pluginInput,1402 ...pluginInput,
1403 secretStore: createExecutorSecretStorePluginApi(1403 secretStore: createExecutorSecretStorePluginApi(
1404 input.secretStore,1404 input.secretStore,
1405 pluginInput.host as ExecutorSecretStorePluginInternalHost,1405 pluginInput.host as unknown as ExecutorSecretStorePluginInternalHost,
1406 ),1406 ),
1407 })1407 })
1408 : undefined,1408 : undefined,
@@ -1412,7 +1412,7 @@ export const defineExecutorSecretStorePlugin = < · 3 unmodified lines
1412 ...pluginInput,1412 ...pluginInput,
1413 secretStore: createExecutorSecretStorePluginApi(1413 secretStore: createExecutorSecretStorePluginApi(
1414 input.secretStore,1414 input.secretStore,
1415 pluginInput.host as ExecutorSecretStorePluginInternalHost,1415 pluginInput.host as unknown as ExecutorSecretStorePluginInternalHost,
1416 ),1416 ),
1417 })1417 })
1418 : undefined,1418 : undefined,
packages/platform/sdk/src/runtime/execution/scope/tool-invoker.ts+1−1
@@ -156,7 +156,7 @@ export const createScopeToolInvoker = (input: { · 155 unmodified lines
156 return catalog;156 return catalog;
157 },157 },
158 });158 });
159 const hasLocalToolsSourceContribution = registeredSourceContributions()159 const hasLocalToolsSourceContribution = registeredSourceContributions(input.pluginRegistry)
160 .some((source) => source.kind === "local-tools");160 .some((source) => source.kind === "local-tools");
161 const authoredTools = mergeToolMaps([161 const authoredTools = mergeToolMaps([
162 systemTools,162 systemTools,
packages/platform/sdk/src/runtime/index.ts+2−0
@@ -146,6 +146,8 @@ export type { · 145 unmodified lines
146 LocalToolRuntimeLoaderShape,146 LocalToolRuntimeLoaderShape,
147} from "./local-tool-runtime";147} from "./local-tool-runtime";
148export {148export {
149 createExecutorPluginRegistry,
150 emptyExecutorPluginRegistry,
149 registeredSourceContributions,151 registeredSourceContributions,
150 registeredSecretStoreContributions,152 registeredSecretStoreContributions,
151 registeredManagementToolContributions,153 registeredManagementToolContributions,
packages/platform/sdk/src/runtime/source-artifacts.ts+2−27
@@ -31,7 +31,6 @@ import type { · 30 unmodified lines
31 ExecutorSdkPluginRegistry,31 ExecutorSdkPluginRegistry,
32} from "../plugins";32} from "../plugins";
33 33
34const LEGACY_LOCAL_SOURCE_ARTIFACT_VERSION = 3 as const;
35const LOCAL_SOURCE_ARTIFACT_VERSION = 4 as const;34const LOCAL_SOURCE_ARTIFACT_VERSION = 4 as const;
36 35
37export const LocalSourceArtifactSchema = Schema.Struct({36export const LocalSourceArtifactSchema = Schema.Struct({
@@ -45,15 +44,6 @@ export const LocalSourceArtifactSchema = Schema.Struct({ · 7 unmodified lines
45 44
46export type LocalSourceArtifact = typeof LocalSourceArtifactSchema.Type;45export type LocalSourceArtifact = typeof LocalSourceArtifactSchema.Type;
47 46
48const ReadableLegacyLocalSourceArtifactSchema = Schema.Struct({
49 version: Schema.Literal(LEGACY_LOCAL_SOURCE_ARTIFACT_VERSION),
50 sourceId: SourceIdSchema,
51 catalogId: SourceCatalogIdSchema,
52 generatedAt: TimestampMsSchema,
53 revision: StoredSourceCatalogRevisionRecordSchema,
54 snapshot: Schema.Unknown,
55});
56
57const ReadableLocalSourceArtifactSchema = Schema.Struct({47const ReadableLocalSourceArtifactSchema = Schema.Struct({
58 version: Schema.Literal(LOCAL_SOURCE_ARTIFACT_VERSION),48 version: Schema.Literal(LOCAL_SOURCE_ARTIFACT_VERSION),
59 sourceId: SourceIdSchema,49 sourceId: SourceIdSchema,
@@ -63,30 +53,15 @@ const ReadableLocalSourceArtifactSchema = Schema.Struct({ · 3 unmodified lines
63 snapshot: Schema.Unknown,53 snapshot: Schema.Unknown,
64});54});
65 55
66type ReadableLegacyLocalSourceArtifact =
67 typeof ReadableLegacyLocalSourceArtifactSchema.Type;
68type ReadableLocalSourceArtifact =56type ReadableLocalSourceArtifact =
69 typeof ReadableLocalSourceArtifactSchema.Type;57 typeof ReadableLocalSourceArtifactSchema.Type;
70 58
71const decodeReadableLocalSourceArtifactOption = Schema.decodeUnknownOption(59const decodeReadableLocalSourceArtifactOption = Schema.decodeUnknownOption(
72 Schema.parseJson(60 Schema.parseJson(
73 Schema.Union(61 ReadableLocalSourceArtifactSchema,
74 ReadableLocalSourceArtifactSchema,
75 ReadableLegacyLocalSourceArtifactSchema,
76 ),
77 ),62 ),
78);63);
79 64
80const normalizeLocalSourceArtifact = (
81 artifact: ReadableLocalSourceArtifact | ReadableLegacyLocalSourceArtifact,
82): Omit<LocalSourceArtifact, "snapshot"> & { snapshot: unknown } =>
83 artifact.version === LOCAL_SOURCE_ARTIFACT_VERSION
84 ? artifact
85 : {
86 ...artifact,
87 version: LOCAL_SOURCE_ARTIFACT_VERSION,
88 };
89
90const mutableRecord = <K extends string, V>(65const mutableRecord = <K extends string, V>(
91 value: Readonly<Record<K, V>>,66 value: Readonly<Record<K, V>>,
92): Record<K, V> => value as Record<K, V>;67): Record<K, V> => value as Record<K, V>;
@@ -188,7 +163,7 @@ export const decodeStoredLocalSourceArtifact = ( · 95 unmodified lines
188 return null;163 return null;
189 }164 }
190 165
191 const artifact = normalizeLocalSourceArtifact(decodedArtifact.value);166 const artifact = decodedArtifact.value;
192 const snapshot = decodeCatalogSnapshotV1Option(artifact.snapshot);167 const snapshot = decodeCatalogSnapshotV1Option(artifact.snapshot);
193 if (snapshot === null) {168 if (snapshot === null) {
194 return null;169 return null;
packages/platform/sdk/src/runtime/sources/executor-tools.test.ts+0−68
@@ -219,72 +219,4 @@ describe("executor internal tools", () => { · 218 unmodified lines
219 expect(stored.has((created as { id: string }).id)).toBe(false);219 expect(stored.has((created as { id: string }).id)).toBe(false);
220 }).pipe(Effect.provide(NodeFileSystem.layer)),220 }).pipe(Effect.provide(NodeFileSystem.layer)),
221 );221 );
222
223 it.scoped("keeps executor.sources.add as a compatibility alias", () =>
224 Effect.gen(function* () {
225 const fs = yield* FileSystem.FileSystem;
226 const workspaceRoot = yield* fs.makeTempDirectory({
227 prefix: "executor-add-alias-",
228 });
229 const specServer = yield* Effect.acquireRelease(
230 Effect.tryPromise({
231 try: () => startStaticSpecServer(),
232 catch: (cause) =>
233 cause instanceof Error ? cause : new Error(String(cause)),
234 }),
235 (server) =>
236 Effect.promise(() => server.close()).pipe(Effect.orDie),
237 );
238 const executor = yield* Effect.acquireRelease(
239 createLocalExecutorEffect({
240 localDataDir: ":memory:",
241 workspaceRoot,
242 plugins: [
243 openApiSdkPlugin({
244 storage: {
245 get: () => Effect.succeed(null),
246 put: () => Effect.void,
247 remove: () => Effect.void,
248 },
249 }),
250 ] as const,
251 }),
252 (openedExecutor) =>
253 Effect.promise(() => openedExecutor.close()).pipe(
254 Effect.orDie,
255 Effect.zipRight(
256 fs.remove(workspaceRoot, {
257 recursive: true,
258 force: true,
259 }),
260 ),
261 ),
262 );
263
264 const environment = yield* resolveExecutionEnvironment(executor);
265 const aliasTool = yield* environment.catalog!.getToolByPath({
266 path: "executor.sources.add",
267 includeSchemas: false,
268 });
269 expect(aliasTool?.path).toBe("executor.sources.add");
270
271 const created = yield* environment.toolInvoker.invoke({
272 path: "executor.sources.add",
273 args: {
274 kind: "openapi",
275 name: "Alias API",
276 specUrl: specServer.specUrl,
277 baseUrl: null,
278 auth: {
279 kind: "none",
280 },
281 },
282 });
283
284 expect(created).toMatchObject({
285 kind: "openapi",
286 name: "Alias API",
287 });
288 }).pipe(Effect.provide(NodeFileSystem.layer)),
289 );
290});222});
packages/platform/sdk/src/runtime/sources/executor-tools.ts+0−146
@@ -7,7 +7,6 @@ import { · 6 unmodified lines
7} from "#schema";7} from "#schema";
8import * as Effect from "effect/Effect";8import * as Effect from "effect/Effect";
9import * as Layer from "effect/Layer";9import * as Layer from "effect/Layer";
10import * as Option from "effect/Option";
11import * as Schema from "effect/Schema";10import * as Schema from "effect/Schema";
12 11
13import type {12import type {
@@ -22,7 +21,6 @@ import { · 8 unmodified lines
22 createManagedSourceRecord,21 createManagedSourceRecord,
23} from "../../sources/operations";22} from "../../sources/operations";
24import {23import {
25 deriveSchemaJson,
26 deriveSchemaTypeSignature,24 deriveSchemaTypeSignature,
27} from "../catalog/schema-type-signature";25} from "../catalog/schema-type-signature";
28import {26import {
@@ -75,54 +73,6 @@ type RegisteredSourceContribution = · 46 unmodified lines
75type RegisteredManagementToolContribution =73type RegisteredManagementToolContribution =
76 ExecutorSdkPluginRegistry["managementTools"][number];74 ExecutorSdkPluginRegistry["managementTools"][number];
77 75
78const createExecutorSourcesAddSchema = (
79 sources: ReadonlyArray<RegisteredSourceContribution>,
80): Schema.Schema<any, any, never> => {
81 if (sources.length === 0) {
82 return Schema.Unknown;
83 }
84
85 if (sources.length === 1) {
86 return sources[0]!.inputSchema;
87 }
88
89 return Schema.Union(
90 ...(sources.map((source) => source.inputSchema) as [
91 Schema.Schema<any, any, never>,
92 Schema.Schema<any, any, never>,
93 ...Array<Schema.Schema<any, any, never>>,
94 ]),
95 );
96};
97
98export const getExecutorSourcesAddInputHint = (
99 pluginRegistry: ExecutorSdkPluginRegistry,
100): string =>
101 deriveSchemaTypeSignature(
102 createExecutorSourcesAddSchema(
103 registeredSourceContributions(pluginRegistry),
104 ),
105 340,
106 );
107
108export const EXECUTOR_SOURCES_ADD_OUTPUT_SIGNATURE = deriveSchemaTypeSignature(
109 SourceSchema,
110 260,
111);
112
113export const getExecutorSourcesAddInputSchemaJson = (
114 pluginRegistry: ExecutorSdkPluginRegistry,
115): Record<string, unknown> =>
116 deriveSchemaJson(
117 createExecutorSourcesAddSchema(
118 registeredSourceContributions(pluginRegistry),
119 ),
120 ) ?? {};
121
122export const EXECUTOR_SOURCES_ADD_OUTPUT_SCHEMA = deriveSchemaJson(
123 SourceSchema,
124) ?? {};
125
126const coreSourceManagementHelpLines = (): readonly string[] => [76const coreSourceManagementHelpLines = (): readonly string[] => [
127 "- executor.sources.list",77 "- executor.sources.list",
128 `- executor.sources.get: ${deriveSchemaTypeSignature(SourceIdInputSchema, 180)}`,78 `- executor.sources.get: ${deriveSchemaTypeSignature(SourceIdInputSchema, 180)}`,
@@ -147,53 +97,9 @@ export const getExecutorInternalToolHelpLines = ( · 18 unmodified lines
147 `- ${tool.path}: ${deriveSchemaTypeSignature(tool.inputSchema, 260)}`97 `- ${tool.path}: ${deriveSchemaTypeSignature(tool.inputSchema, 260)}`
148 ),98 ),
149 ]),99 ]),
150 ...(sources.length === 0
151 ? []
152 : [
153 "Legacy compatibility tool:",
154 `- executor.sources.add: ${deriveSchemaTypeSignature(
155 createExecutorSourcesAddSchema(sources),
156 340,
157 )}`,
158 ]),
159 ];
160};
161
162export const getExecutorSourcesAddHelpLines = (
163 pluginRegistry: ExecutorSdkPluginRegistry,
164): readonly string[] => {
165 const sources = registeredSourceContributions(pluginRegistry);
166 if (sources.length === 0) {
167 return ["No source plugins are registered in this build."] as const;
168 }
169
170 return [
171 "Source add input shapes:",
172 ...sources.flatMap((source) => [
173 `- ${source.displayName}: ${deriveSchemaTypeSignature(
174 source.inputSchema,
175 source.inputSignatureWidth ?? 260,
176 )}`,
177 ...(source.helpText ?? []).map((line) => ` ${line}`),
178 ]),
179 ];100 ];
180};101};
181 102
182export const buildExecutorSourcesAddDescription = (
183 pluginRegistry: ExecutorSdkPluginRegistry,
184): string => {
185 const sources = registeredSourceContributions(pluginRegistry);
186 if (sources.length === 0) {
187 return "No source plugins are registered in this build.";
188 }
189
190 return [
191 "Legacy compatibility alias for creating a source.",
192 "Prefer plugin-specific tools such as executor.openapi.createSource or executor.mcp.createSource.",
193 ...getExecutorSourcesAddHelpLines(pluginRegistry),
194 ].join("\n");
195};
196
197const createSourceConnectorHost = (input: {103const createSourceConnectorHost = (input: {
198 scopeId: ScopeId;104 scopeId: ScopeId;
199 actorScopeId: ScopeId;105 actorScopeId: ScopeId;
@@ -297,28 +203,6 @@ const runExecutorToolEffect = async <A>( · 97 unmodified lines
297 );203 );
298};204};
299 205
300const resolveSourceContribution = (
301 sources: ReadonlyArray<RegisteredSourceContribution>,
302 args: unknown,
303):
304 | {
305 source: RegisteredSourceContribution;
306 parsedArgs: unknown;
307 }
308 | null => {
309 for (const source of sources) {
310 const parsed = Schema.decodeUnknownOption(source.inputSchema)(args);
311 if (Option.isSome(parsed)) {
312 return {
313 source,
314 parsedArgs: parsed.value,
315 };
316 }
317 }
318
319 return null;
320};
321
322const toSerializableValue = <A>(value: A): A =>206const toSerializableValue = <A>(value: A): A =>
323 JSON.parse(JSON.stringify(value)) as A;207 JSON.parse(JSON.stringify(value)) as A;
324 208
@@ -549,36 +433,6 @@ export const createExecutorToolMap = (input: { · 224 unmodified lines
549 }),433 }),
550 };434 };
551 435
552 if (sources.length > 0) {
553 coreTools["executor.sources.add"] = toTool({
554 tool: {
555 description: buildExecutorSourcesAddDescription(input.pluginRegistry),
556 inputSchema: Schema.standardSchemaV1(
557 createExecutorSourcesAddSchema(sources),
558 ),
559 outputSchema: Schema.standardSchemaV1(SourceSchema),
560 execute: async (args: unknown): Promise<Source> => {
561 const matched = resolveSourceContribution(sources, args);
562 if (matched === null) {
563 throw new Error(
564 "executor.sources.add input did not match a registered source plugin.",
565 );
566 }
567
568 const createdSource = await runExecutorToolEffect(
569 matched.source.createSource({
570 args: matched.parsedArgs,
571 host,
572 }),
573 runtime,
574 );
575
576 return toSerializableValue(createdSource);
577 },
578 },
579 });
580 }
581
582 return {436 return {
583 ...coreTools,437 ...coreTools,
584 ...pluginToolMap,438 ...pluginToolMap,
packages/platform/sdk/src/runtime/sources/source-store/plugin-local-config.ts+0−83
@@ -1,23 +1,12 @@
1import type {1import type {
2 LocalConfigSecretInput,
3 LocalConfigSource,2 LocalConfigSource,
4 LocalExecutorConfig,
5 SecretRef,
6 Source,3 Source,
7} from "#schema";4} from "#schema";
8import {
9 LocalConfigSecretInputSchema,
10} from "#schema";
11import * as Option from "effect/Option";
12import * as Schema from "effect/Schema";5import * as Schema from "effect/Schema";
13 6
14import {
15 toConfigSecretProviderId,
16} from "../../scope/config-secrets";
17import {7import {
18 cloneJson,8 cloneJson,
19 configSourceBaseFromLocalSource,9 configSourceBaseFromLocalSource,
20 trimOrNull,
21} from "./config";10} from "./config";
22 11
23export const createPluginLocalConfigEntrySchema = <12export const createPluginLocalConfigEntrySchema = <
@@ -49,75 +38,3 @@ export const pluginLocalConfigSourceFromConfig = <TConfig>(input: { · 25 unmodified lines
49 kind: input.source.kind as LocalConfigSource["kind"],38 kind: input.source.kind as LocalConfigSource["kind"],
50 config: cloneJson(input.config),39 config: cloneJson(input.config),
51});40});
52
53const decodeLocalConfigSecretInputOption = Schema.decodeUnknownOption(
54 LocalConfigSecretInputSchema,
55);
56
57const resolveDefaultConfigSecretProviderAlias = (
58 config: LocalExecutorConfig | null | undefined,
59): string | null => {
60 const defaultAlias = trimOrNull(config?.secrets?.defaults?.env);
61 if (defaultAlias !== null && config?.secrets?.providers?.[defaultAlias]) {
62 return defaultAlias;
63 }
64
65 return config?.secrets?.providers?.default ? "default" : null;
66};
67
68export const secretRefFromLocalConfigSecretInput = (input: {
69 auth: LocalConfigSecretInput;
70 loadedConfig: LocalExecutorConfig | null;
71}): SecretRef => {
72 if (typeof input.auth === "string") {
73 const providerAlias = resolveDefaultConfigSecretProviderAlias(
74 input.loadedConfig,
75 );
76
77 return {
78 providerId: providerAlias ? toConfigSecretProviderId(providerAlias) : "env",
79 handle: input.auth,
80 };
81 }
82
83 return {
84 providerId:
85 input.auth.provider === "params"
86 ? "params"
87 : input.auth.provider
88 ? toConfigSecretProviderId(input.auth.provider)
89 : input.auth.source === "params"
90 ? "params"
91 : "env",
92 handle: input.auth.id,
93 };
94};
95
96export const decodeCurrentOrLegacyLocalConfigAuth = <TAuth>(input: {
97 auth: unknown;
98 authSchema: Schema.Schema<TAuth, any, never>;
99 loadedConfig: LocalExecutorConfig | null;
100 onLegacySecretRef: (ref: SecretRef) => TAuth;
101 fallback: TAuth;
102}): TAuth => {
103 if (input.auth === undefined) {
104 return input.fallback;
105 }
106
107 const current = Schema.decodeUnknownOption(input.authSchema)(input.auth);
108 if (Option.isSome(current)) {
109 return current.value;
110 }
111
112 const legacy = decodeLocalConfigSecretInputOption(input.auth);
113 if (Option.isSome(legacy)) {
114 return input.onLegacySecretRef(
115 secretRefFromLocalConfigSecretInput({
116 auth: legacy.value,
117 loadedConfig: input.loadedConfig,
118 }),
119 );
120 }
121
122 throw new Error("Unsupported local source auth configuration.");
123};
packages/platform/server/src/index.test.ts+15−14
@@ -264,7 +264,7 @@ const writeConfiguredLocalMcpSource = (input: { · 263 unmodified lines
264 });264 });
265 }).pipe(Effect.provide(NodeFileSystem.layer));265 }).pipe(Effect.provide(NodeFileSystem.layer));
266 266
267const writeLegacyConfiguredWorkspaceSources = (input: {267const writeConfiguredWorkspaceSources = (input: {
268 workspaceRoot: string;268 workspaceRoot: string;
269 sources: Record<string, LocalConfigSource>;269 sources: Record<string, LocalConfigSource>;
270}) =>270}) =>
@@ -288,8 +288,8 @@ const writeLegacyConfiguredWorkspaceSources = (input: { · 17 unmodified lines
288 }).pipe(Effect.provide(NodeFileSystem.layer));288 }).pipe(Effect.provide(NodeFileSystem.layer));
289 289
290const writeMissingSourceCatalogArtifact = (input: {290const writeMissingSourceCatalogArtifact = (input: {
291 context: Effect.Effect.Success<ReturnType<typeof writeLegacyConfiguredWorkspaceSources>>["context"];291 context: Effect.Effect.Success<ReturnType<typeof writeConfiguredWorkspaceSources>>["context"];
292 installation: Effect.Effect.Success<ReturnType<typeof writeLegacyConfiguredWorkspaceSources>>["installation"];292 installation: Effect.Effect.Success<ReturnType<typeof writeConfiguredWorkspaceSources>>["installation"];
293 source: {293 source: {
294 id: string;294 id: string;
295 name: string;295 name: string;
@@ -1632,7 +1632,7 @@ describe("local-executor-server", () => { · 1336 unmodified lines
1632 15_000,1632 15_000,
1633 );1633 );
1634 1634
1635 it.scoped("adds an OpenAPI source via executor.sources.add and calls it end to end", () =>1635 it.scoped("adds an OpenAPI source via executor.openapi.createSource and calls it end to end", () =>
1636 Effect.gen(function* () {1636 Effect.gen(function* () {
1637 const openApiServer = yield* Effect.acquireRelease(1637 const openApiServer = yield* Effect.acquireRelease(
1638 Effect.promise(() => startOpenApiDemoServer()),1638 Effect.promise(() => startOpenApiDemoServer()),
@@ -1645,8 +1645,7 @@ describe("local-executor-server", () => { · 6 unmodified lines
1645 workspaceId: installation.scopeId,1645 workspaceId: installation.scopeId,
1646 },1646 },
1647 payload: {1647 payload: {
1648 code: `return await tools.executor.sources.add({1648 code: `return await tools.executor.openapi.createSource({
1649 kind: "openapi",
1650 name: "GitHub",1649 name: "GitHub",
1651 specUrl: ${JSON.stringify(openApiServer.specUrl)},1650 specUrl: ${JSON.stringify(openApiServer.specUrl)},
1652 baseUrl: ${JSON.stringify(openApiServer.baseUrl)},1651 baseUrl: ${JSON.stringify(openApiServer.baseUrl)},
@@ -1831,16 +1830,14 @@ describe("local-executor-server", () => { · 178 unmodified lines
1831 (server) => Effect.promise(() => server.close()).pipe(Effect.orDie),1830 (server) => Effect.promise(() => server.close()).pipe(Effect.orDie),
1832 );1831 );
1833 1832
1834 const workspace = yield* writeLegacyConfiguredWorkspaceSources({1833 const workspace = yield* writeConfiguredWorkspaceSources({
1835 workspaceRoot,1834 workspaceRoot,
1836 sources: {1835 sources: {
1837 deepwiki: {1836 deepwiki: {
1838 kind: "mcp",1837 kind: "mcp",
1839 name: "DeepWiki MCP",1838 name: "DeepWiki MCP",
1840 connection: {1839 config: {
1841 endpoint: demoServer.endpoint,1840 endpoint: demoServer.endpoint,
1842 },
1843 binding: {
1844 transport: "streamable-http",1841 transport: "streamable-http",
1845 queryParams: null,1842 queryParams: null,
1846 headers: null,1843 headers: null,
@@ -1848,16 +1845,20 @@ describe("local-executor-server", () => { · 1 unmodified line
1848 args: null,1845 args: null,
1849 env: null,1846 env: null,
1850 cwd: null,1847 cwd: null,
1848 auth: {
1849 kind: "none",
1850 },
1851 },1851 },
1852 },1852 },
1853 vercel: {1853 vercel: {
1854 kind: "openapi",1854 kind: "openapi",
1855 name: "Vercel API",1855 name: "Vercel API",
1856 connection: {1856 config: {
1857 endpoint: openApiServer.baseUrl,
1858 },
1859 binding: {
1860 specUrl: openApiServer.specUrl,1857 specUrl: openApiServer.specUrl,
1858 baseUrl: openApiServer.baseUrl,
1859 auth: {
1860 kind: "none",
1861 },
1861 defaultHeaders: null,1862 defaultHeaders: null,
1862 },1863 },
1863 },1864 },
plugins/google-discovery/sdk/index.ts+0−50
@@ -18,7 +18,6 @@ import { · 17 unmodified lines
18} from "@executor/platform-sdk/plugins";18} from "@executor/platform-sdk/plugins";
19import {19import {
20 createPluginLocalConfigEntrySchema,20 createPluginLocalConfigEntrySchema,
21 decodeCurrentOrLegacyLocalConfigAuth,
22 pluginLocalConfigSourceFromConfig,21 pluginLocalConfigSourceFromConfig,
23 SecretMaterialDeleterService,22 SecretMaterialDeleterService,
24 SecretMaterialResolverService,23 SecretMaterialResolverService,
@@ -103,29 +102,6 @@ const decodeGoogleDiscoveryCurrentLocalConfigOption = Schema.decodeUnknownOption · 78 unmodified lines
103 config: GoogleDiscoveryStoredSourceDataSchema,102 config: GoogleDiscoveryStoredSourceDataSchema,
104 }),103 }),
105);104);
106const decodeGoogleDiscoveryLegacyLocalConfigOption = Schema.decodeUnknownOption(
107 Schema.Struct({
108 kind: Schema.Literal(GOOGLE_DISCOVERY_SOURCE_KIND),
109 name: Schema.optional(Schema.String),
110 namespace: Schema.optional(Schema.String),
111 enabled: Schema.optional(Schema.Boolean),
112 connection: Schema.Struct({
113 endpoint: Schema.optional(Schema.NullOr(Schema.String)),
114 auth: Schema.optional(Schema.Unknown),
115 }),
116 binding: Schema.Struct({
117 service: Schema.String,
118 version: Schema.String,
119 discoveryUrl: Schema.optional(Schema.NullOr(Schema.String)),
120 defaultHeaders: Schema.optional(
121 Schema.NullOr(Schema.Record({ key: Schema.String, value: Schema.String })),
122 ),
123 scopes: Schema.optional(Schema.Array(Schema.String)),
124 }),
125 config: Schema.optional(Schema.Unknown),
126 }),
127);
128
129export type GoogleDiscoverySourceStorage = {105export type GoogleDiscoverySourceStorage = {
130 get: (input: {106 get: (input: {
131 scopeId: string;107 scopeId: string;
@@ -582,32 +558,6 @@ const googleDiscoveryStoredSourceDataFromLocalConfig = (input: { · 450 unmodified lines
582 return current.value.config;558 return current.value.config;
583 }559 }
584 560
585 const legacy = decodeGoogleDiscoveryLegacyLocalConfigOption(input.config);
586 if (Option.isSome(legacy)) {
587 return storedSourceDataFromInput({
588 name:
589 legacy.value.name
590 ?? `${legacy.value.binding.service} ${legacy.value.binding.version}`,
591 service: legacy.value.binding.service,
592 version: legacy.value.binding.version,
593 discoveryUrl: legacy.value.binding.discoveryUrl ?? null,
594 defaultHeaders: legacy.value.binding.defaultHeaders ?? null,
595 scopes: legacy.value.binding.scopes ?? [],
596 auth: decodeCurrentOrLegacyLocalConfigAuth({
597 auth: legacy.value.connection.auth,
598 authSchema: GoogleDiscoveryConnectionAuthSchema,
599 loadedConfig: input.loadedConfig,
600 onLegacySecretRef: (tokenSecretRef) => ({
601 kind: "bearer",
602 tokenSecretRef,
603 }),
604 fallback: {
605 kind: "none",
606 },
607 }),
608 });
609 }
610
611 throw new Error("Unsupported Google Discovery local source config.");561 throw new Error("Unsupported Google Discovery local source config.");
612};562};
613 563
plugins/graphql/sdk/index.ts+0−43
@@ -24,7 +24,6 @@ import { · 23 unmodified lines
24import {24import {
25 SecretMaterialResolverService,25 SecretMaterialResolverService,
26 createPluginLocalConfigEntrySchema,26 createPluginLocalConfigEntrySchema,
27 decodeCurrentOrLegacyLocalConfigAuth,
28 pluginLocalConfigSourceFromConfig,27 pluginLocalConfigSourceFromConfig,
29 provideExecutorRuntime,28 provideExecutorRuntime,
30 runtimeEffectError,29 runtimeEffectError,
@@ -158,25 +157,6 @@ const decodeGraphqlCurrentLocalConfigOption = Schema.decodeUnknownOption( · 127 unmodified lines
158 }),157 }),
159);158);
160 159
161const decodeGraphqlLegacyLocalConfigOption = Schema.decodeUnknownOption(
162 Schema.Struct({
163 kind: Schema.Literal("graphql"),
164 name: Schema.optional(Schema.String),
165 namespace: Schema.optional(Schema.String),
166 enabled: Schema.optional(Schema.Boolean),
167 connection: Schema.Struct({
168 endpoint: Schema.String,
169 auth: Schema.optional(Schema.Unknown),
170 }),
171 binding: Schema.Struct({
172 defaultHeaders: Schema.optional(
173 Schema.NullOr(Schema.Record({ key: Schema.String, value: Schema.String })),
174 ),
175 }),
176 config: Schema.optional(Schema.Unknown),
177 }),
178);
179
180const withoutUndefinedEntries = (160const withoutUndefinedEntries = (
181 record: Readonly<Record<string, unknown>>,161 record: Readonly<Record<string, unknown>>,
182): Record<string, unknown> =>162): Record<string, unknown> =>
@@ -363,29 +343,6 @@ const graphqlStoredSourceDataFromLocalConfig = (input: { · 180 unmodified lines
363 return normalizeStoredSourceData(current.value.config);343 return normalizeStoredSourceData(current.value.config);
364 }344 }
365 345
366 const legacy = decodeGraphqlLegacyLocalConfigOption(input.config);
367 if (Option.isSome(legacy)) {
368 return storedSourceDataFromInput({
369 name: legacy.value.name ?? "GraphQL",
370 endpoint: legacy.value.connection.endpoint,
371 defaultHeaders: legacy.value.binding.defaultHeaders ?? null,
372 auth: decodeCurrentOrLegacyLocalConfigAuth({
373 auth: legacy.value.connection.auth,
374 authSchema: GraphqlConnectionAuthSchema,
375 loadedConfig: input.loadedConfig,
376 onLegacySecretRef: (tokenSecretRef) => ({
377 kind: "bearer",
378 tokenSecretRef,
379 headerName: null,
380 prefix: null,
381 }),
382 fallback: {
383 kind: "none",
384 },
385 }),
386 });
387 }
388
389 throw new Error("Unsupported GraphQL local source config.");346 throw new Error("Unsupported GraphQL local source config.");
390};347};
391 348
plugins/mcp/sdk/index.ts+0−56
@@ -249,37 +249,12 @@ const normalizeStringArray = ( · 248 unmodified lines
249 return normalized.length > 0 ? normalized : null;249 return normalized.length > 0 ? normalized : null;
250};250};
251 251
252const decodeMcpAuthOption = Schema.decodeUnknownOption(McpConnectionAuthSchema);
253const decodeMcpCurrentLocalConfigOption = Schema.decodeUnknownOption(252const decodeMcpCurrentLocalConfigOption = Schema.decodeUnknownOption(
254 createPluginLocalConfigEntrySchema({253 createPluginLocalConfigEntrySchema({
255 kind: "mcp",254 kind: "mcp",
256 config: McpStoredSourceDataSchema,255 config: McpStoredSourceDataSchema,
257 }),256 }),
258);257);
259const decodeMcpLegacyLocalConfigOption = Schema.decodeUnknownOption(
260 Schema.Struct({
261 kind: Schema.Literal("mcp"),
262 name: Schema.optional(Schema.String),
263 namespace: Schema.optional(Schema.String),
264 enabled: Schema.optional(Schema.Boolean),
265 connection: Schema.optional(
266 Schema.Struct({
267 endpoint: Schema.optional(Schema.NullOr(Schema.String)),
268 auth: Schema.optional(Schema.Unknown),
269 }),
270 ),
271 binding: Schema.Struct({
272 transport: Schema.optional(Schema.NullOr(SourceTransportSchema)),
273 queryParams: Schema.optional(Schema.NullOr(StringMapSchema)),
274 headers: Schema.optional(Schema.NullOr(StringMapSchema)),
275 command: Schema.optional(Schema.NullOr(Schema.String)),
276 args: Schema.optional(Schema.NullOr(StringArraySchema)),
277 env: Schema.optional(Schema.NullOr(StringMapSchema)),
278 cwd: Schema.optional(Schema.NullOr(Schema.String)),
279 }),
280 config: Schema.optional(Schema.Unknown),
281 }),
282);
283 258
284const normalizeStoredSourceData = (259const normalizeStoredSourceData = (
285 input: McpConnectInput | McpSourceConfigPayload,260 input: McpConnectInput | McpSourceConfigPayload,
@@ -399,37 +374,6 @@ const mcpStoredSourceDataFromLocalConfig = ( · 113 unmodified lines
399 return Effect.succeed(current.value.config);374 return Effect.succeed(current.value.config);
400 }375 }
401 376
402 const legacy = decodeMcpLegacyLocalConfigOption(config);
403 if (Option.isSome(legacy)) {
404 const auth = decodeMcpAuthOption(legacy.value.connection?.auth);
405 if (
406 legacy.value.connection?.auth !== undefined &&
407 Option.isNone(auth)
408 ) {
409 return runtimeEffectError(
410 "plugins/mcp/sdk",
411 "Unsupported MCP auth configuration in local source config.",
412 );
413 }
414
415 return normalizeStoredSourceData({
416 name: legacy.value.name ?? "MCP",
417 endpoint: legacy.value.connection?.endpoint ?? null,
418 transport: legacy.value.binding.transport ?? null,
419 queryParams: legacy.value.binding.queryParams ?? null,
420 headers: legacy.value.binding.headers ?? null,
421 command: legacy.value.binding.command ?? null,
422 args: legacy.value.binding.args ?? null,
423 env: legacy.value.binding.env ?? null,
424 cwd: legacy.value.binding.cwd ?? null,
425 auth: Option.isSome(auth)
426 ? auth.value
427 : {
428 kind: "none",
429 },
430 });
431 }
432
433 return runtimeEffectError(377 return runtimeEffectError(
434 "plugins/mcp/sdk",378 "plugins/mcp/sdk",
435 "Unsupported MCP local source config.",379 "Unsupported MCP local source config.",
plugins/mcp/sdk/tools.test.ts+1−1
@@ -500,7 +500,7 @@ describe("codemode-mcp", () => { · 499 unmodified lines
500 action: "accept" as const,500 action: "accept" as const,
501 };501 };
502 }),502 }),
503 path: "executor.sources.add" as ToolPath,503 path: "executor.mcp.createSource" as ToolPath,
504 sourceKey: "executor",504 sourceKey: "executor",
505 args: {505 args: {
506 endpoint: "https://example.com/mcp",506 endpoint: "https://example.com/mcp",
plugins/openapi/sdk/index.ts+0−46
@@ -19,7 +19,6 @@ import { · 18 unmodified lines
19} from "@executor/platform-sdk/plugins";19} from "@executor/platform-sdk/plugins";
20import {20import {
21 createPluginLocalConfigEntrySchema,21 createPluginLocalConfigEntrySchema,
22 decodeCurrentOrLegacyLocalConfigAuth,
23 pluginLocalConfigSourceFromConfig,22 pluginLocalConfigSourceFromConfig,
24 runtimeEffectError,23 runtimeEffectError,
25 SecretMaterialResolverService,24 SecretMaterialResolverService,
@@ -163,26 +162,6 @@ const decodeOpenApiCurrentLocalConfigOption = Schema.decodeUnknownOption( · 137 unmodified lines
163 }),162 }),
164);163);
165 164
166const decodeOpenApiLegacyLocalConfigOption = Schema.decodeUnknownOption(
167 Schema.Struct({
168 kind: Schema.Literal("openapi"),
169 name: Schema.optional(Schema.String),
170 namespace: Schema.optional(Schema.String),
171 enabled: Schema.optional(Schema.Boolean),
172 connection: Schema.Struct({
173 endpoint: Schema.optional(Schema.NullOr(Schema.String)),
174 auth: Schema.optional(Schema.Unknown),
175 }),
176 binding: Schema.Struct({
177 specUrl: Schema.String,
178 defaultHeaders: Schema.optional(
179 Schema.NullOr(Schema.Record({ key: Schema.String, value: Schema.String })),
180 ),
181 }),
182 config: Schema.optional(Schema.Unknown),
183 }),
184);
185
186const createStoredSourceData = (165const createStoredSourceData = (
187 input: OpenApiConnectInput,166 input: OpenApiConnectInput,
188): OpenApiStoredSourceData => ({167): OpenApiStoredSourceData => ({
@@ -224,31 +203,6 @@ const openApiStoredSourceDataFromLocalConfig = (input: { · 35 unmodified lines
224 });203 });
225 }204 }
226 205
227 const legacy = decodeOpenApiLegacyLocalConfigOption(input.config);
228 if (Option.isSome(legacy)) {
229 return normalizeStoredSourceData({
230 specUrl: legacy.value.binding.specUrl.trim(),
231 baseUrl: legacy.value.connection.endpoint?.trim() || null,
232 auth: decodeCurrentOrLegacyLocalConfigAuth({
233 auth: legacy.value.connection.auth,
234 authSchema: OpenApiConnectionAuthSchema,
235 loadedConfig: input.loadedConfig,
236 onLegacySecretRef: (tokenSecretRef) => ({
237 kind: "bearer",
238 tokenSecretRef,
239 headerName: null,
240 prefix: null,
241 }),
242 fallback: {
243 kind: "none",
244 },
245 }),
246 defaultHeaders: legacy.value.binding.defaultHeaders ?? null,
247 etag: null,
248 lastSyncAt: null,
249 });
250 }
251
252 throw new Error("Unsupported OpenAPI local source config.");206 throw new Error("Unsupported OpenAPI local source config.");
253};207};
254 208