Unified · Split
| @@ -90,7 +90,6 @@ import { · 89 unmodified lines | |||
| 90 | import { decideInteractionHandling } from "./interaction-handling"; | 90 | import { decideInteractionHandling } from "./interaction-handling"; |
| 91 | import { | 91 | import { |
| 92 | renderMcpSessionSummary, | 92 | renderMcpSessionSummary, |
| 93 | renderUpSummary, | ||
| 94 | renderWebSessionSummary, | 93 | renderWebSessionSummary, |
| 95 | } from "./session-summary"; | 94 | } from "./session-summary"; |
| 96 | import { | 95 | import { |
| @@ -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 | ||
| 628 | type 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 | |||
| 643 | const 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 | |||
| 677 | const 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 | |||
| 690 | const 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 | |||
| 730 | const printJson = (value: unknown) => | ||
| 731 | Effect.sync(() => { | ||
| 732 | console.log(JSON.stringify(value, null, 2)); | ||
| 733 | }); | ||
| 734 | |||
| 735 | const printText = (value: string) => | 625 | const printText = (value: string) => |
| 736 | Effect.sync(() => { | 626 | Effect.sync(() => { |
| 737 | console.log(value); | 627 | console.log(value); |
| 738 | }); | 628 | }); |
| 739 | 629 | ||
| 740 | const 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 | |||
| 776 | const ensureServer = (baseUrl: string = DEFAULT_SERVER_BASE_URL) => | 630 | const 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 | ||
| 1246 | const 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 | |||
| 1261 | const 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 | |||
| 1274 | const 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 | |||
| 1286 | const 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 | |||
| 1303 | const callCommand = Command.make( | 1100 | const 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, |
| @@ -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, |
| @@ -8,7 +8,6 @@ import * as Effect from "effect/Effect"; · 7 unmodified lines | |||
| 8 | import { | 8 | import { |
| 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 | }); |
| @@ -33,9 +33,6 @@ export type FileLoadedExecutorConfig = LoadedLocalExecutorConfig & { · 32 unmodified lines | |||
| 33 | export const encodeLocalExecutorConfig = (config: LocalExecutorConfig): string => | 33 | export const encodeLocalExecutorConfig = (config: LocalExecutorConfig): string => |
| 34 | `${JSON.stringify(config, null, 2)}\n`; | 34 | `${JSON.stringify(config, null, 2)}\n`; |
| 35 | 35 | ||
| 36 | const encodeUnknownConfigDocument = (config: unknown): string => | ||
| 37 | `${JSON.stringify(config, null, 2)}\n`; | ||
| 38 | |||
| 39 | export const resolveConfigRelativePath = (input: { | 36 | export 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 | ||
| 229 | const 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 | |||
| 260 | const 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 | |||
| 265 | const 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 | |||
| 315 | const 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 | |||
| 353 | const mergeSourceMaps = ( | 226 | const 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 | ||
| 556 | export 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 | |||
| 589 | export 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 | |||
| 601 | export const loadLocalExecutorConfig = ( | 429 | export const loadLocalExecutorConfig = ( |
| 602 | context: ResolvedLocalWorkspaceContext, | 430 | context: ResolvedLocalWorkspaceContext, |
| 603 | ): Effect.Effect< | 431 | ): Effect.Effect< |
| @@ -28,8 +28,6 @@ import { · 27 unmodified lines | |||
| 28 | 28 | ||
| 29 | const LOCAL_EXECUTOR_STATE_VERSION = 1 as const; | 29 | const LOCAL_EXECUTOR_STATE_VERSION = 1 as const; |
| 30 | const LOCAL_EXECUTOR_STATE_BASENAME = "executor-state.json"; | 30 | const LOCAL_EXECUTOR_STATE_BASENAME = "executor-state.json"; |
| 31 | const LEGACY_LOCAL_SECRET_STORE_ID = "sts_builtin_local"; | ||
| 32 | const LEGACY_KEYCHAIN_SECRET_STORE_ID = "sts_builtin_keychain"; | ||
| 33 | 31 | ||
| 34 | const LocalExecutorStateSnapshotSchema = Schema.Struct({ | 32 | const 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 | |||
| 63 | const cloneValue = <T>(value: T): T => | 61 | const cloneValue = <T>(value: T): T => |
| 64 | JSON.parse(JSON.stringify(value)) as T; | 62 | JSON.parse(JSON.stringify(value)) as T; |
| 65 | 63 | ||
| 66 | const 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 | |||
| 73 | const 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 | |||
| 80 | const 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 | |||
| 93 | const 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 | |||
| 205 | const mapFileSystemError = (path: string, action: string) => (cause: unknown) => | 64 | const 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 | ||
| @@ -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"; |
| 31 | import * as Effect from "effect/Effect"; | 32 | import * as Effect from "effect/Effect"; |
| 32 | import { | 33 | import { |
| 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.resolveSecretMaterial | 258 | 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, |
| @@ -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"; |
| 7 | import type { | ||
| 8 | ExecutorSdkPluginRegistry, | ||
| 9 | } from "@executor/platform-sdk/plugins"; | ||
| 7 | import type { | 10 | import 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 | ||
| 46 | const builtinSecretStoreContributions = () => | 49 | const 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 | ||
| 55 | const resolveStoreContribution = (store: SecretStore) => | 60 | const resolveStoreContribution = ( |
| 56 | getSecretStoreContribution(store.kind); | 61 | pluginRegistry: ExecutorSdkPluginRegistry, |
| 57 | 62 | store: SecretStore, | |
| 58 | const loadStoreCapabilities = (store: SecretStore) => | 63 | ) => |
| 59 | resolveStoreContribution(store).getCapabilities({ | 64 | getSecretStoreContribution(pluginRegistry, store.kind); |
| 65 | |||
| 66 | const 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 | ||
| 74 | const resolveDefaultStoreId = ( | 85 | const 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 | ||
| 103 | export const provisionBuiltinSecretStores = (input: { | 115 | export 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 | ||
| 170 | export const createDefaultSecretMaterialResolver = (input: { | 183 | export 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 | ||
| 199 | export const createDefaultSecretMaterialStorer = (input: { | 213 | export 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 | ||
| 259 | export const createDefaultSecretMaterialUpdater = (input: { | 274 | export 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 | ||
| 314 | export const createDefaultSecretMaterialDeleter = (input: { | 330 | export 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.canDeleteSecrets | 348 | 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 | ||
| 349 | const listAvailableSecretStorePlugins = (): SecretProvider[] => { | 366 | const 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 | ||
| 357 | export const createLocalInstanceConfigResolver = (input: { | 376 | export 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 | ||
| 369 | export const SecretMaterialResolverLive = (input: { | 389 | export 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 | ||
| 381 | export const SecretMaterialStorerLive = (input: { | 402 | export 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 | ||
| 391 | export const SecretMaterialDeleterLive = (input: { | 413 | export 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 | ||
| 401 | export const SecretMaterialUpdaterLive = (input: { | 424 | export 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 | ||
| 411 | export const SecretMaterialLive = (input: { | 435 | export 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.resolveSecretMaterial | 446 | 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.resolveSecretMaterial | 458 | 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.resolveSecretMaterial | 470 | 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 | ||
| 450 | export const LocalInstanceConfigLive = (input: { | 481 | export const LocalInstanceConfigLive = (input: { |
| 451 | executorState: ExecutorStateStoreShape; | 482 | executorState: ExecutorStateStoreShape; |
| 483 | pluginRegistry: ExecutorSdkPluginRegistry; | ||
| 452 | }) => | 484 | }) => |
| 453 | Layer.succeed( | 485 | Layer.succeed( |
| 454 | LocalInstanceConfigService, | 486 | LocalInstanceConfigService, |
| @@ -18,6 +18,7 @@ import { · 17 unmodified lines | |||
| 18 | ControlPlaneStorageError, | 18 | ControlPlaneStorageError, |
| 19 | } from "../errors"; | 19 | } from "../errors"; |
| 20 | import { | 20 | import { |
| 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 | ||
| 78 | const getSecretStoreContributionOption = (kind: string) => { | 79 | const 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 | ||
| 86 | const defaultCapabilities: SecretStoreCapabilities = { | 89 | const 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 === undefined | 262 | const configRecord = input.payload.config === undefined |
| 259 | ? null | 263 | ? 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", |
| @@ -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, |
| @@ -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, |
| @@ -146,6 +146,8 @@ export type { · 145 unmodified lines | |||
| 146 | LocalToolRuntimeLoaderShape, | 146 | LocalToolRuntimeLoaderShape, |
| 147 | } from "./local-tool-runtime"; | 147 | } from "./local-tool-runtime"; |
| 148 | export { | 148 | export { |
| 149 | createExecutorPluginRegistry, | ||
| 150 | emptyExecutorPluginRegistry, | ||
| 149 | registeredSourceContributions, | 151 | registeredSourceContributions, |
| 150 | registeredSecretStoreContributions, | 152 | registeredSecretStoreContributions, |
| 151 | registeredManagementToolContributions, | 153 | registeredManagementToolContributions, |
| @@ -31,7 +31,6 @@ import type { · 30 unmodified lines | |||
| 31 | ExecutorSdkPluginRegistry, | 31 | ExecutorSdkPluginRegistry, |
| 32 | } from "../plugins"; | 32 | } from "../plugins"; |
| 33 | 33 | ||
| 34 | const LEGACY_LOCAL_SOURCE_ARTIFACT_VERSION = 3 as const; | ||
| 35 | const LOCAL_SOURCE_ARTIFACT_VERSION = 4 as const; | 34 | const LOCAL_SOURCE_ARTIFACT_VERSION = 4 as const; |
| 36 | 35 | ||
| 37 | export const LocalSourceArtifactSchema = Schema.Struct({ | 36 | export const LocalSourceArtifactSchema = Schema.Struct({ |
| @@ -45,15 +44,6 @@ export const LocalSourceArtifactSchema = Schema.Struct({ · 7 unmodified lines | |||
| 45 | 44 | ||
| 46 | export type LocalSourceArtifact = typeof LocalSourceArtifactSchema.Type; | 45 | export type LocalSourceArtifact = typeof LocalSourceArtifactSchema.Type; |
| 47 | 46 | ||
| 48 | const 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 | |||
| 57 | const ReadableLocalSourceArtifactSchema = Schema.Struct({ | 47 | const 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 | ||
| 66 | type ReadableLegacyLocalSourceArtifact = | ||
| 67 | typeof ReadableLegacyLocalSourceArtifactSchema.Type; | ||
| 68 | type ReadableLocalSourceArtifact = | 56 | type ReadableLocalSourceArtifact = |
| 69 | typeof ReadableLocalSourceArtifactSchema.Type; | 57 | typeof ReadableLocalSourceArtifactSchema.Type; |
| 70 | 58 | ||
| 71 | const decodeReadableLocalSourceArtifactOption = Schema.decodeUnknownOption( | 59 | const 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 | ||
| 80 | const 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 | |||
| 90 | const mutableRecord = <K extends string, V>( | 65 | const 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; |
| @@ -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 | }); |
| @@ -7,7 +7,6 @@ import { · 6 unmodified lines | |||
| 7 | } from "#schema"; | 7 | } from "#schema"; |
| 8 | import * as Effect from "effect/Effect"; | 8 | import * as Effect from "effect/Effect"; |
| 9 | import * as Layer from "effect/Layer"; | 9 | import * as Layer from "effect/Layer"; |
| 10 | import * as Option from "effect/Option"; | ||
| 11 | import * as Schema from "effect/Schema"; | 10 | import * as Schema from "effect/Schema"; |
| 12 | 11 | ||
| 13 | import type { | 12 | import type { |
| @@ -22,7 +21,6 @@ import { · 8 unmodified lines | |||
| 22 | createManagedSourceRecord, | 21 | createManagedSourceRecord, |
| 23 | } from "../../sources/operations"; | 22 | } from "../../sources/operations"; |
| 24 | import { | 23 | import { |
| 25 | deriveSchemaJson, | ||
| 26 | deriveSchemaTypeSignature, | 24 | deriveSchemaTypeSignature, |
| 27 | } from "../catalog/schema-type-signature"; | 25 | } from "../catalog/schema-type-signature"; |
| 28 | import { | 26 | import { |
| @@ -75,54 +73,6 @@ type RegisteredSourceContribution = · 46 unmodified lines | |||
| 75 | type RegisteredManagementToolContribution = | 73 | type RegisteredManagementToolContribution = |
| 76 | ExecutorSdkPluginRegistry["managementTools"][number]; | 74 | ExecutorSdkPluginRegistry["managementTools"][number]; |
| 77 | 75 | ||
| 78 | const 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 | |||
| 98 | export const getExecutorSourcesAddInputHint = ( | ||
| 99 | pluginRegistry: ExecutorSdkPluginRegistry, | ||
| 100 | ): string => | ||
| 101 | deriveSchemaTypeSignature( | ||
| 102 | createExecutorSourcesAddSchema( | ||
| 103 | registeredSourceContributions(pluginRegistry), | ||
| 104 | ), | ||
| 105 | 340, | ||
| 106 | ); | ||
| 107 | |||
| 108 | export const EXECUTOR_SOURCES_ADD_OUTPUT_SIGNATURE = deriveSchemaTypeSignature( | ||
| 109 | SourceSchema, | ||
| 110 | 260, | ||
| 111 | ); | ||
| 112 | |||
| 113 | export const getExecutorSourcesAddInputSchemaJson = ( | ||
| 114 | pluginRegistry: ExecutorSdkPluginRegistry, | ||
| 115 | ): Record<string, unknown> => | ||
| 116 | deriveSchemaJson( | ||
| 117 | createExecutorSourcesAddSchema( | ||
| 118 | registeredSourceContributions(pluginRegistry), | ||
| 119 | ), | ||
| 120 | ) ?? {}; | ||
| 121 | |||
| 122 | export const EXECUTOR_SOURCES_ADD_OUTPUT_SCHEMA = deriveSchemaJson( | ||
| 123 | SourceSchema, | ||
| 124 | ) ?? {}; | ||
| 125 | |||
| 126 | const coreSourceManagementHelpLines = (): readonly string[] => [ | 76 | const 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 | |||
| 162 | export 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 | ||
| 182 | export 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 | |||
| 197 | const createSourceConnectorHost = (input: { | 103 | const 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 | ||
| 300 | const 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 | |||
| 322 | const toSerializableValue = <A>(value: A): A => | 206 | const 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, |
| @@ -1,23 +1,12 @@ | |||
| 1 | import type { | 1 | import type { |
| 2 | LocalConfigSecretInput, | ||
| 3 | LocalConfigSource, | 2 | LocalConfigSource, |
| 4 | LocalExecutorConfig, | ||
| 5 | SecretRef, | ||
| 6 | Source, | 3 | Source, |
| 7 | } from "#schema"; | 4 | } from "#schema"; |
| 8 | import { | ||
| 9 | LocalConfigSecretInputSchema, | ||
| 10 | } from "#schema"; | ||
| 11 | import * as Option from "effect/Option"; | ||
| 12 | import * as Schema from "effect/Schema"; | 5 | import * as Schema from "effect/Schema"; |
| 13 | 6 | ||
| 14 | import { | ||
| 15 | toConfigSecretProviderId, | ||
| 16 | } from "../../scope/config-secrets"; | ||
| 17 | import { | 7 | import { |
| 18 | cloneJson, | 8 | cloneJson, |
| 19 | configSourceBaseFromLocalSource, | 9 | configSourceBaseFromLocalSource, |
| 20 | trimOrNull, | ||
| 21 | } from "./config"; | 10 | } from "./config"; |
| 22 | 11 | ||
| 23 | export const createPluginLocalConfigEntrySchema = < | 12 | export 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 | |||
| 53 | const decodeLocalConfigSecretInputOption = Schema.decodeUnknownOption( | ||
| 54 | LocalConfigSecretInputSchema, | ||
| 55 | ); | ||
| 56 | |||
| 57 | const 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 | |||
| 68 | export 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 | |||
| 96 | export 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 | }; | ||
| @@ -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 | ||
| 267 | const writeLegacyConfiguredWorkspaceSources = (input: { | 267 | const 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 | ||
| 290 | const writeMissingSourceCatalogArtifact = (input: { | 290 | const 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 | }, |
| @@ -18,7 +18,6 @@ import { · 17 unmodified lines | |||
| 18 | } from "@executor/platform-sdk/plugins"; | 18 | } from "@executor/platform-sdk/plugins"; |
| 19 | import { | 19 | import { |
| 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 | ); |
| 106 | const 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 | |||
| 129 | export type GoogleDiscoverySourceStorage = { | 105 | export 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 | ||
| @@ -24,7 +24,6 @@ import { · 23 unmodified lines | |||
| 24 | import { | 24 | import { |
| 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 | ||
| 161 | const 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 | |||
| 180 | const withoutUndefinedEntries = ( | 160 | const 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 | ||
| @@ -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 | ||
| 252 | const decodeMcpAuthOption = Schema.decodeUnknownOption(McpConnectionAuthSchema); | ||
| 253 | const decodeMcpCurrentLocalConfigOption = Schema.decodeUnknownOption( | 252 | const decodeMcpCurrentLocalConfigOption = Schema.decodeUnknownOption( |
| 254 | createPluginLocalConfigEntrySchema({ | 253 | createPluginLocalConfigEntrySchema({ |
| 255 | kind: "mcp", | 254 | kind: "mcp", |
| 256 | config: McpStoredSourceDataSchema, | 255 | config: McpStoredSourceDataSchema, |
| 257 | }), | 256 | }), |
| 258 | ); | 257 | ); |
| 259 | const 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 | ||
| 284 | const normalizeStoredSourceData = ( | 259 | const 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.", |
| @@ -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", |
| @@ -19,7 +19,6 @@ import { · 18 unmodified lines | |||
| 19 | } from "@executor/platform-sdk/plugins"; | 19 | } from "@executor/platform-sdk/plugins"; |
| 20 | import { | 20 | import { |
| 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 | ||
| 166 | const 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 | |||
| 186 | const createStoredSourceData = ( | 165 | const 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 | ||