History

Refactor platform core into sdk api and internal packages

Rhys Sullivan committed 7 months ago

Commit d03db821 parent 747f7a3205 files · +4875 −3170Browse files

Unified · Split

205 files
  1. .executor/executor.jsonc +5 −21
  2. ARCHITECTURE.md +2 −2
  3. README.md +4 −2
  4. apps/docs/developer/cli.mdx +1 −1
  5. apps/docs/developer/overview.mdx +1 −1
  6. apps/executor/package.json +3 −1
  7. apps/executor/src/cli/dev.ts +2 −4
  8. apps/executor/src/cli/main.ts +11 −5
  9. apps/executor/src/cli/pending-interaction-output.test.ts +1 −1
  10. apps/executor/src/cli/pending-interaction-output.ts +1 −1
  11. apps/executor/src/server/server.real-ingestion.test.ts +2 −2
  12. apps/executor/src/server/server.test.ts +5 −3
  13. bun.lock +56 −17
  14. package.json +1 −1
  15. packages/clients/react/package.json +2 −1
  16. packages/clients/react/src/index.test.tsx +7 −3
  17. packages/clients/react/src/index.ts +24 −22
  18. packages/dev/kitchen-sink/package.json +2 −2
  19. packages/dev/kitchen-sink/src/source-runtime.test.ts +1 −1
  20. packages/hosts/mcp/package.json +1 −1
  21. packages/hosts/mcp/src/index.ts +4 −4
  22. packages/hosts/mcp/src/paused-result.test.ts +4 −1
  23. packages/hosts/mcp/src/paused-result.ts +1 −1
  24. packages/platform/api/package.json +40 −0
  25. packages/platform/api/src/api.ts +0 −0
  26. packages/platform/api/src/client.ts +1 −1
  27. packages/platform/api/src/errors.ts +7 −0
  28. packages/platform/api/src/executions/api.ts +14 −17
  29. packages/platform/api/src/executions/http.ts +1 −1
  30. packages/platform/api/src/http.ts +0 −0
  31. packages/platform/api/src/index.ts +19 −1
  32. packages/platform/api/src/local-context.ts +3 −3
  33. packages/platform/api/src/local/api.ts +69 −0
  34. packages/platform/api/src/local/http.ts +29 −0
  35. packages/platform/api/src/oauth/api.ts +3 −3
  36. packages/platform/api/src/oauth/http.ts +2 −2
  37. packages/platform/api/src/payload-schemas.test.ts +0 −0
  38. packages/platform/api/src/policies/api.ts +18 −24
  39. packages/platform/api/src/policies/http.ts +14 −7
  40. packages/platform/api/src/sources/api.ts +43 −162
  41. packages/platform/api/src/sources/http.ts +170 −96
  42. packages/platform/api/src/string-schemas.ts +4 −0
  43. packages/platform/api/tsconfig.json +1 −8
  44. packages/platform/api/vitest.config.ts +0 −0
  45. packages/platform/control-plane/CHANGELOG.md +0 −13
  46. packages/platform/control-plane/src/api/local/http.ts +0 −255
  47. packages/platform/control-plane/src/index.ts +0 −4
  48. packages/platform/control-plane/src/runtime/control-plane-runtime.test.ts +0 −1991
  49. packages/platform/internal/package.json +22 −0
  50. packages/platform/internal/src/index.ts +1 −0
  51. packages/platform/internal/src/tools/executor-admin.ts +692 −0
  52. packages/platform/internal/tsconfig.json +13 −0
  53. packages/platform/sdk/package.json +36 −25
  54. packages/platform/sdk/src/errors.ts +0 −0
  55. packages/platform/sdk/src/executions/contracts.ts +21 −0
  56. packages/platform/sdk/src/index.ts +12 −0
  57. packages/platform/sdk/src/local/contracts.ts +1 −52
  58. packages/platform/sdk/src/local/operations.ts +112 −94
  59. packages/platform/sdk/src/local/secrets.ts +283 −0
  60. packages/platform/sdk/src/policies/contracts.ts +23 −0
  61. packages/platform/sdk/src/policies/operations.ts +126 −72
  62. packages/platform/sdk/src/runtime/__fixtures__/README.md +1 −1
  63. packages/platform/sdk/src/runtime/__fixtures__/v1.2.3-google-calendar-workspace/.executor/artifacts/sources/google-calendar.json +0 −0
  64. packages/platform/sdk/src/runtime/__fixtures__/v1.2.3-google-calendar-workspace/.executor/artifacts/sources/google-calendar/documents/doc_3c24a6731267de91.txt +0 −0
  65. packages/platform/sdk/src/runtime/__fixtures__/v1.2.3-google-calendar-workspace/.executor/executor.jsonc +0 −0
  66. packages/platform/sdk/src/runtime/__fixtures__/v1.2.3-google-calendar-workspace/.executor/state/workspace-state.json +0 −0
  67. packages/platform/sdk/src/runtime/__fixtures__/v1.2.3-google-calendar-workspace/fixture.json +0 −0
  68. packages/platform/sdk/src/runtime/auth/auth-artifacts.ts +0 −0
  69. packages/platform/sdk/src/runtime/auth/auth-leases.ts +0 −0
  70. packages/platform/sdk/src/runtime/auth/mcp-auth-provider.ts +0 −0
  71. packages/platform/sdk/src/runtime/auth/mcp-oauth.ts +0 −0
  72. packages/platform/sdk/src/runtime/auth/oauth-loopback.test.ts +0 −0
  73. packages/platform/sdk/src/runtime/auth/oauth-loopback.ts +0 −0
  74. packages/platform/sdk/src/runtime/auth/oauth2-pkce.ts +0 −0
  75. packages/platform/sdk/src/runtime/auth/provider-grant-lifecycle.ts +0 −0
  76. packages/platform/sdk/src/runtime/auth/source-auth-material.test.ts +0 −0
  77. packages/platform/sdk/src/runtime/auth/source-auth-material.ts +0 −0
  78. packages/platform/sdk/src/runtime/catalog/catalog-typescript.ts +0 −0
  79. packages/platform/sdk/src/runtime/catalog/prettier-format.ts +0 −0
  80. packages/platform/sdk/src/runtime/catalog/schema-type-signature.ts +0 −0
  81. packages/platform/sdk/src/runtime/catalog/source/reconcile.ts +0 −0
  82. packages/platform/sdk/src/runtime/catalog/source/runtime.test.ts +0 −0
  83. packages/platform/sdk/src/runtime/catalog/source/runtime.ts +0 −0
  84. packages/platform/sdk/src/runtime/catalog/source/snapshot.test.ts +0 −0
  85. packages/platform/sdk/src/runtime/catalog/source/sync.ts +0 −0
  86. packages/platform/sdk/src/runtime/catalog/source/type-declarations.test.ts +0 −0
  87. packages/platform/sdk/src/runtime/catalog/source/type-declarations.ts +0 −0
  88. packages/platform/sdk/src/runtime/control-plane-runtime.test.ts +2356 −0
  89. packages/platform/sdk/src/runtime/effect-errors.ts +0 −0
  90. packages/platform/sdk/src/runtime/execution/http.test.ts +0 −0
  91. packages/platform/sdk/src/runtime/execution/ir-execution.ts +0 −0
  92. packages/platform/sdk/src/runtime/execution/live.test.ts +0 −0
  93. packages/platform/sdk/src/runtime/execution/live.ts +0 −0
  94. packages/platform/sdk/src/runtime/execution/mcp-resume.test.ts +0 −0
  95. packages/platform/sdk/src/runtime/execution/runtime-config.test.ts +0 −0
  96. packages/platform/sdk/src/runtime/execution/runtime.ts +0 −0
  97. packages/platform/sdk/src/runtime/execution/service.ts +2 −2
  98. packages/platform/sdk/src/runtime/execution/state.ts +0 −0
  99. packages/platform/sdk/src/runtime/execution/test-http-client.ts +3 −4
  100. packages/platform/sdk/src/runtime/execution/workspace/authorization.ts +0 −0
  101. packages/platform/sdk/src/runtime/execution/workspace/environment.ts +51 −22
  102. packages/platform/sdk/src/runtime/execution/workspace/local.ts +0 −0
  103. packages/platform/sdk/src/runtime/execution/workspace/source-catalog.ts +0 −0
  104. packages/platform/sdk/src/runtime/execution/workspace/tool-invoker.ts +89 −41
  105. packages/platform/sdk/src/runtime/fixtures/google-sheets-discovery.json +0 −0
  106. packages/platform/sdk/src/runtime/fixtures/linear-introspection.json +0 −0
  107. packages/platform/sdk/src/runtime/fixtures/neon-openapi.json +0 −0
  108. packages/platform/sdk/src/runtime/fixtures/vercel-openapi.json +0 −0
  109. packages/platform/sdk/src/runtime/index.ts +63 −46
  110. packages/platform/sdk/src/runtime/local/capture-release-workspace-fixture.ts +0 −0
  111. packages/platform/sdk/src/runtime/local/config-secrets.ts +0 −0
  112. packages/platform/sdk/src/runtime/local/config.test.ts +0 −0
  113. packages/platform/sdk/src/runtime/local/config.ts +0 −0
  114. packages/platform/sdk/src/runtime/local/control-plane-store.test.ts +0 −0
  115. packages/platform/sdk/src/runtime/local/control-plane-store.ts +0 −0
  116. packages/platform/sdk/src/runtime/local/errors.ts +0 −0
  117. packages/platform/sdk/src/runtime/local/installation.test.ts +0 −0
  118. packages/platform/sdk/src/runtime/local/installation.ts +0 −0
  119. packages/platform/sdk/src/runtime/local/operations.ts +1 −0
  120. packages/platform/sdk/src/runtime/local/release-upgrade-fixtures.ts +0 −0
  121. packages/platform/sdk/src/runtime/local/runtime-context.ts +0 −0
  122. packages/platform/sdk/src/runtime/local/secret-material-providers.ts +0 −0
  123. packages/platform/sdk/src/runtime/local/secrets-operations.ts +1 −0
  124. packages/platform/sdk/src/runtime/local/source-artifacts.test.ts +0 −0
  125. packages/platform/sdk/src/runtime/local/source-artifacts.ts +0 −0
  126. packages/platform/sdk/src/runtime/local/storage.ts +0 −0
  127. packages/platform/sdk/src/runtime/local/tools.ts +0 −0
  128. packages/platform/sdk/src/runtime/local/workspace-state.ts +0 −0
  129. packages/platform/sdk/src/runtime/local/workspace-sync.ts +0 −0
  130. packages/platform/sdk/src/runtime/policy/invocation-policy-engine.test.ts +0 −0
  131. packages/platform/sdk/src/runtime/policy/invocation-policy-engine.ts +0 −0
  132. packages/platform/sdk/src/runtime/policy/operation-errors.ts +1 −1
  133. packages/platform/sdk/src/runtime/policy/operations-shared.ts +1 −1
  134. packages/platform/sdk/src/runtime/policy/policies-operations.ts +1 −0
  135. packages/platform/sdk/src/runtime/sources/catalog-sync-result.ts +0 −0
  136. packages/platform/sdk/src/runtime/sources/executor-tools.ts +0 −0
  137. packages/platform/sdk/src/runtime/sources/graphql-tools.test.ts +0 −0
  138. packages/platform/sdk/src/runtime/sources/graphql-tools.ts +0 −0
  139. packages/platform/sdk/src/runtime/sources/slug.ts +0 −0
  140. packages/platform/sdk/src/runtime/sources/source-adapter-fixture-matrix.test.ts +0 −0
  141. packages/platform/sdk/src/runtime/sources/source-adapters/google-discovery.test.ts +0 −0
  142. packages/platform/sdk/src/runtime/sources/source-adapters/google-discovery.ts +0 −0
  143. packages/platform/sdk/src/runtime/sources/source-adapters/graphql.test.ts +0 −0
  144. packages/platform/sdk/src/runtime/sources/source-adapters/graphql.ts +0 −0
  145. packages/platform/sdk/src/runtime/sources/source-adapters/index.ts +0 −0
  146. packages/platform/sdk/src/runtime/sources/source-adapters/internal.ts +0 −0
  147. packages/platform/sdk/src/runtime/sources/source-adapters/mcp.test.ts +0 −0
  148. packages/platform/sdk/src/runtime/sources/source-adapters/mcp.ts +0 −0
  149. packages/platform/sdk/src/runtime/sources/source-adapters/openapi.ts +0 −0
  150. packages/platform/sdk/src/runtime/sources/source-adapters/shared.ts +0 −0
  151. packages/platform/sdk/src/runtime/sources/source-adapters/types.ts +0 −0
  152. packages/platform/sdk/src/runtime/sources/source-auth-service.ts +0 −0
  153. packages/platform/sdk/src/runtime/sources/source-credential-interactions.ts +0 −0
  154. packages/platform/sdk/src/runtime/sources/source-definitions.test.ts +0 −0
  155. packages/platform/sdk/src/runtime/sources/source-definitions.ts +2 −2
  156. packages/platform/sdk/src/runtime/sources/source-discovery.test.ts +0 −0
  157. packages/platform/sdk/src/runtime/sources/source-discovery.ts +1 −0
  158. packages/platform/sdk/src/runtime/sources/source-inspection.test.ts +0 −0
  159. packages/platform/sdk/src/runtime/sources/source-inspection.ts +1 −0
  160. packages/platform/sdk/src/runtime/sources/source-names.ts +0 −0
  161. packages/platform/sdk/src/runtime/sources/source-store.test.ts +0 −0
  162. packages/platform/sdk/src/runtime/sources/source-store.ts +0 −0
  163. packages/platform/sdk/src/runtime/sources/source-store/auth.ts +0 −0
  164. packages/platform/sdk/src/runtime/sources/source-store/config.ts +0 −0
  165. packages/platform/sdk/src/runtime/sources/source-store/deps.ts +0 −0
  166. packages/platform/sdk/src/runtime/sources/source-store/lifecycle.ts +0 −0
  167. packages/platform/sdk/src/runtime/sources/source-store/records.ts +0 −0
  168. packages/platform/sdk/src/runtime/sources/sources-operations.ts +1 −0
  169. packages/platform/sdk/src/runtime/store.ts +0 −0
  170. packages/platform/sdk/src/schema/common.ts +0 −0
  171. packages/platform/sdk/src/schema/enums.ts +0 −0
  172. packages/platform/sdk/src/schema/ids.ts +0 −0
  173. packages/platform/sdk/src/schema/index.ts +0 −0
  174. packages/platform/sdk/src/schema/models/auth-artifact.ts +0 −0
  175. packages/platform/sdk/src/schema/models/auth-lease.ts +0 −0
  176. packages/platform/sdk/src/schema/models/auth/account.ts +0 −0
  177. packages/platform/sdk/src/schema/models/auth/index.ts +0 −0
  178. packages/platform/sdk/src/schema/models/auth/principal.ts +0 −0
  179. packages/platform/sdk/src/schema/models/code-migration.ts +0 −0
  180. packages/platform/sdk/src/schema/models/credential.ts +0 −0
  181. packages/platform/sdk/src/schema/models/execution.ts +0 −0
  182. packages/platform/sdk/src/schema/models/local-config.ts +0 −0
  183. packages/platform/sdk/src/schema/models/local-installation.ts +0 −0
  184. packages/platform/sdk/src/schema/models/policy.ts +0 −0
  185. packages/platform/sdk/src/schema/models/provider-auth-grant.ts +0 −0
  186. packages/platform/sdk/src/schema/models/secret-material.ts +0 −0
  187. packages/platform/sdk/src/schema/models/source-auth-session.ts +0 −0
  188. packages/platform/sdk/src/schema/models/source-catalog.ts +0 −0
  189. packages/platform/sdk/src/schema/models/source-discovery.ts +0 −0
  190. packages/platform/sdk/src/schema/models/source-inspection.ts +0 −0
  191. packages/platform/sdk/src/schema/models/source-oauth-client.ts +0 −0
  192. packages/platform/sdk/src/schema/models/source.ts +0 −0
  193. packages/platform/sdk/src/schema/models/workspace-oauth-client.ts +0 −0
  194. packages/platform/sdk/src/schema/schema.test.ts +0 −0
  195. packages/platform/sdk/src/sources/contracts.ts +170 −0
  196. packages/platform/sdk/src/sources/discovery.ts +3 −3
  197. packages/platform/sdk/src/sources/inspection.ts +88 −41
  198. packages/platform/sdk/src/sources/operations.ts +85 −72
  199. packages/platform/sdk/src/string-schemas.ts +0 −0
  200. packages/platform/sdk/tsconfig.json +14 −0
  201. packages/platform/sdk/vitest.config.ts +8 −0
  202. packages/platform/server/package.json +3 −1
  203. packages/platform/server/src/index.ts +6 −2
  204. packages/sources/openapi/src/real-spec-coverage.test.ts +1 −1
  205. packages/sources/openapi/src/tool-presentation.test.ts +1 −1
.executor/executor.jsonc+5−21
@@ -1,25 +1,9 @@
11 {
2− "sources": {
3− "vercel": {
4− "name": "Vercel API",
5− "connection": {
6− "endpoint": "https://api.vercel.com/"
7− },
8− "kind": "openapi",
9− "binding": {
10− "specUrl": "https://openapi.vercel.sh/",
11− "defaultHeaders": null
12− }
13− },
14− "linear": {
15− "kind": "graphql",
16− "name": "Linear GraphQL",
17− "connection": {
18− "endpoint": "https://api.linear.app/graphql"
19− },
20− "binding": {
21− "defaultHeaders": null
22− }
2+ "policies": {
3+ "demo.*": {
4+ "match": "demo.*",
5+ "action": "allow",
6+ "approval": "auto"
237 }
248 }
259 }
ARCHITECTURE.md+2−2
@@ -88,7 +88,7 @@ Responsibilities: · 87 unmodified lines
8888
8989 This is an important architectural choice: the API and UI are served by the same local process, so the product behaves like one install rather than a pile of separate services.
9090
91−### `packages/platform/control-plane`: product core
91+### `packages/platform/sdk`: product core
9292
9393 This is the center of the system.
9494
@@ -408,4 +408,4 @@ A few practical boundaries are worth calling out: · 313 unmodified lines
408408 - `README.md` for the product view and usage guidance
409409 - `apps/executor/src/cli/main.ts` for the CLI surface
410410 - `packages/platform/server/src/index.ts` for how the local server is assembled
411−- `packages/platform/control-plane/src/runtime/` for the core runtime flows
411+- `packages/platform/sdk/src/runtime/` for the core runtime flows
README.md+4−2
@@ -329,7 +329,9 @@ If you are exploring the repo, these are the directories that matter most: · 328 unmodified lines
329329 - `apps/executor`: packaged CLI entrypoint and daemon lifecycle commands
330330 - `apps/web`: local React web UI
331331 - `packages/platform/server`: local HTTP server that serves API, MCP, and UI
332−- `packages/platform/control-plane`: source management, secrets, persistence, execution, and inspection
332+- `packages/platform/sdk`: source management, secrets, persistence, execution, and inspection
333+- `packages/platform/api`: thin HTTP adapter over the platform SDK
334+- `packages/platform/internal`: thin internal tool adapter over the platform SDK
333335 - `packages/kernel/runtime-deno-subprocess`: optional Deno subprocess runtime for TypeScript execution
334336 - `packages/kernel/runtime-quickjs`: default QuickJS sandbox runtime for TypeScript execution
335337 - `packages/kernel/runtime-ses`: optional SES sandbox runtime for TypeScript execution
@@ -349,7 +351,7 @@ If you are exploring the repo, these are the directories that matter most: · 13 unmodified lines
349351 - One-time npm setup: either configure npm trusted publishing for `RhysSullivan/executor` with the workflow file `.github/workflows/publish-executor-package.yml`, or add a GitHub Actions secret named `NPM_TOKEN` that can publish the `executor` package.
350352 - Stable releases use a normal semver like `1.2.3` and publish to npm under `latest`.
351353 - Beta releases use a prerelease semver like `1.3.0-beta.1` and publish to npm under `beta`.
352−- When a release should become an upgrade test fixture, capture a real workspace snapshot with `bun run fixture:release:capture -- ...` and commit it under [`packages/platform/control-plane/src/runtime/__fixtures__`](./packages/platform/control-plane/src/runtime/__fixtures__/README.md).
354+- When a release should become an upgrade test fixture, capture a real workspace snapshot with `bun run fixture:release:capture -- ...` and commit it under [`packages/platform/sdk/src/runtime/__fixtures__`](./packages/platform/sdk/src/runtime/__fixtures__/README.md).
353355
354356 ## Project status
355357
apps/docs/developer/cli.mdx+1−1
@@ -41,7 +41,7 @@ At a high level, the CLI: · 40 unmodified lines
4141
4242 1. ensures the local daemon is available
4343 2. calls the local HTTP API exposed by `packages/platform/server`
44−3. hands off real work to `packages/platform/control-plane`
44+3. hands off real work to `packages/platform/sdk` and `packages/platform/api`
4545
4646 That makes the CLI a stable shell over a shared local runtime rather than a second product core.
4747
apps/docs/developer/overview.mdx+1−1
@@ -41,7 +41,7 @@ Responsibilities: · 40 unmodified lines
4141 - mount the local MCP server at `/mcp`
4242 - serve the web UI
4343
44−### `packages/platform/control-plane`
44+### `packages/platform/sdk`
4545
4646 The product core.
4747
apps/executor/package.json+3−1
@@ -37,8 +37,10 @@ · 36 unmodified lines
3737 "@effect/platform": "catalog:",
3838 "@effect/platform-node": "catalog:",
3939 "@executor/codemode-core": "workspace:*",
40− "@executor/control-plane": "workspace:*",
4140 "@executor/executor-mcp": "workspace:*",
41+ "@executor/platform-api": "workspace:*",
42+ "@executor/platform-internal": "workspace:*",
43+ "@executor/platform-sdk": "workspace:*",
4244 "@executor/server": "workspace:*",
4345 "effect": "catalog:"
4446 },
apps/executor/src/cli/dev.ts+2−4
@@ -1,7 +1,5 @@
1−import {
2− type ControlPlaneClient,
3− WorkspaceIdSchema,
4−} from "@executor/control-plane";
1+import type { ControlPlaneClient } from "@executor/platform-api";
2+import { WorkspaceIdSchema } from "@executor/platform-sdk/schema";
53 import * as Effect from "effect/Effect";
64
75 const readBindingString = (binding: Record<string, unknown>, key: string): string | null =>
apps/executor/src/cli/main.ts+11−5
@@ -9,17 +9,22 @@ import { · 8 unmodified lines
99 NodePath,
1010 NodeRuntime,
1111 } from "@effect/platform-node";
12+import {
13+ createControlPlaneClient,
14+ type ControlPlaneClient,
15+} from "@executor/platform-api";
16+import { createExecutorAdminToolMap } from "@executor/platform-internal";
1217 import {
1318 EXECUTOR_SOURCES_ADD_HELP_LINES,
14− ExecutionIdSchema,
1519 RuntimeExecutionResolverService,
16− createControlPlaneClient,
1720 createControlPlaneRuntime,
18− type ControlPlaneClient,
21+ type ControlPlaneRuntime,
22+} from "@executor/platform-sdk/runtime";
23+import {
24+ ExecutionIdSchema,
1925 type ExecutionEnvelope,
2026 type ExecutionInteraction,
21− type ControlPlaneRuntime,
22−} from "@executor/control-plane";
27+} from "@executor/platform-sdk/schema";
2328 import type { ToolCatalog } from "@executor/codemode-core";
2429
2530 import * as Effect from "effect/Effect";
@@ -272,6 +277,7 @@ const loadRunWorkflowText = (): Effect.Effect<string, Error, never> => · 246 unmodified lines
272277 Effect.acquireUseRelease(
273278 createControlPlaneRuntime({
274279 localDataDir: DEFAULT_LOCAL_DATA_DIR,
280+ createInternalToolMap: createExecutorAdminToolMap,
275281 }).pipe(Effect.mapError(toError)),
276282 (runtime) =>
277283 Effect.gen(function* () {
apps/executor/src/cli/pending-interaction-output.test.ts+1−1
@@ -1,5 +1,5 @@
11 import { describe, expect, it } from "@effect/vitest";
2−import type { ExecutionInteraction } from "@executor/control-plane";
2+import type { ExecutionInteraction } from "@executor/platform-sdk/schema";
33
44 import {
55 buildPausedExecutionOutput,
apps/executor/src/cli/pending-interaction-output.ts+1−1
@@ -1,4 +1,4 @@
1−import type { ExecutionInteraction } from "@executor/control-plane";
1+import type { ExecutionInteraction } from "@executor/platform-sdk/schema";
22
33 const isRecord = (value: unknown): value is Record<string, unknown> =>
44 typeof value === "object" && value !== null && !Array.isArray(value);
apps/executor/src/server/server.real-ingestion.test.ts+2−2
@@ -5,8 +5,8 @@ import * as Effect from "effect/Effect"; · 4 unmodified lines
55
66 import {
77 createControlPlaneClient,
8− type SourceDiscoveryKind,
9−} from "@executor/control-plane";
8+} from "@executor/platform-api";
9+import { type SourceDiscoveryKind } from "@executor/platform-sdk/schema";
1010 import { createLocalExecutorServer } from "@executor/server";
1111
1212 const REAL_VERCEL_SPEC_URL = "https://openapi.vercel.sh";
apps/executor/src/server/server.test.ts+5−3
@@ -20,15 +20,17 @@ import { makeToolInvokerFromTools, toTool } from "@executor/codemode-core"; · 19 unmodified lines
2020 import {
2121 createControlPlaneClient,
2222 controlPlaneOpenApiSpec,
23+} from "@executor/platform-api";
24+import {
2325 buildLocalSourceArtifact,
2426 catalogSyncResultFromMcpManifest,
2527 deriveLocalInstallation,
26− type ResolveExecutionEnvironment,
2728 resolveLocalWorkspaceContext,
28− SourceIdSchema,
2929 writeLocalSourceArtifact,
3030 writeProjectLocalExecutorConfig,
31−} from "@executor/control-plane";
31+ type ResolveExecutionEnvironment,
32+} from "@executor/platform-sdk/runtime";
33+import { SourceIdSchema } from "@executor/platform-sdk/schema";
3234 import { makeSesExecutor } from "@executor/runtime-ses";
3335 import { Client } from "@modelcontextprotocol/sdk/client/index.js";
3436 import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";
bun.lock+56−17
@@ -19,7 +19,7 @@ · 18 unmodified lines
1919 },
2020 "apps/executor": {
2121 "name": "executor",
22− "version": "1.2.3",
22+ "version": "1.2.4-beta.4",
2323 "bin": {
2424 "executor": "bin/executor",
2525 },
@@ -28,8 +28,10 @@ · 2 unmodified lines
2828 "@effect/platform": "catalog:",
2929 "@effect/platform-node": "catalog:",
3030 "@executor/codemode-core": "workspace:*",
31− "@executor/control-plane": "workspace:*",
3231 "@executor/executor-mcp": "workspace:*",
32+ "@executor/platform-api": "workspace:*",
33+ "@executor/platform-internal": "workspace:*",
34+ "@executor/platform-sdk": "workspace:*",
3335 "@executor/server": "workspace:*",
3436 "effect": "catalog:",
3537 },
@@ -129,7 +131,8 @@ · 93 unmodified lines
129131 "dependencies": {
130132 "@effect-atom/atom": "0.5.3",
131133 "@effect-atom/atom-react": "0.5.0",
132− "@executor/control-plane": "workspace:*",
134+ "@executor/platform-api": "workspace:*",
135+ "@executor/platform-sdk": "workspace:*",
133136 "effect": "catalog:",
134137 },
135138 "devDependencies": {
@@ -156,7 +159,7 @@ · 20 unmodified lines
156159 "@effect/platform": "catalog:",
157160 "@executor/ai-sdk-adapter": "workspace:*",
158161 "@executor/codemode-core": "workspace:*",
159− "@executor/control-plane": "workspace:*",
162+ "@executor/platform-sdk": "workspace:*",
160163 "@executor/runtime-ses": "workspace:*",
161164 "@executor/source-openapi": "workspace:*",
162165 "effect": "catalog:",
@@ -187,7 +190,7 @@ · 24 unmodified lines
187190 "name": "@executor/executor-mcp",
188191 "version": "0.0.0",
189192 "dependencies": {
190− "@executor/control-plane": "workspace:*",
193+ "@executor/platform-sdk": "workspace:*",
191194 "@modelcontextprotocol/sdk": "^1.26.0",
192195 "effect": "catalog:",
193196 "zod": "catalog:",
@@ -270,11 +273,41 @@ · 76 unmodified lines
270273 "bun-types": "catalog:",
271274 },
272275 },
273− "packages/platform/control-plane": {
274− "name": "@executor/control-plane",
276+ "packages/platform/api": {
277+ "name": "@executor/platform-api",
275278 "version": "0.0.0",
276279 "dependencies": {
277280 "@effect/platform": "catalog:",
281+ "@executor/platform-sdk": "workspace:*",
282+ "effect": "catalog:",
283+ },
284+ "devDependencies": {
285+ "@effect/vitest": "catalog:",
286+ "bun-types": "catalog:",
287+ "typescript": "^5.9.3",
288+ "vitest": "catalog:",
289+ },
290+ },
291+ "packages/platform/internal": {
292+ "name": "@executor/platform-internal",
293+ "version": "0.0.0",
294+ "dependencies": {
295+ "@executor/codemode-core": "workspace:*",
296+ "@executor/platform-sdk": "workspace:*",
297+ "effect": "catalog:",
298+ },
299+ "devDependencies": {
300+ "@types/node": "catalog:",
301+ "bun-types": "catalog:",
302+ "typescript": "^5.9.3",
303+ },
304+ },
305+ "packages/platform/sdk": {
306+ "name": "@executor/platform-sdk",
307+ "version": "0.0.0",
308+ "dependencies": {
309+ "@effect/platform": "catalog:",
310+ "@effect/platform-node": "catalog:",
278311 "@executor/auth-mcp-oauth": "workspace:*",
279312 "@executor/auth-oauth2": "workspace:*",
280313 "@executor/codemode-core": "workspace:*",
@@ -289,20 +322,20 @@ · 8 unmodified lines
289322 "@executor/source-mcp": "workspace:*",
290323 "@executor/source-openapi": "workspace:*",
291324 "effect": "catalog:",
292− "graphql": "^16.12.0",
293− "jsonc-parser": "3.3.1",
294− "postgres": "^3.4.8",
295− "prettier": "^3.8.1",
296− "typescript": "^5.9.3",
297325 },
298326 "devDependencies": {
299− "@effect/platform-node": "catalog:",
300327 "@effect/vitest": "catalog:",
328+ "@executor/platform-api": "workspace:*",
329+ "@executor/platform-internal": "workspace:*",
301330 "@modelcontextprotocol/sdk": "^1.26.0",
302331 "@types/node": "catalog:",
303332 "bun-types": "catalog:",
333+ "graphql": "^16.12.0",
334+ "jsonc-parser": "3.3.1",
335+ "postgres": "^3.4.8",
336+ "prettier": "^3.8.1",
337+ "typescript": "^5.9.3",
304338 "vitest": "catalog:",
305− "zod": "catalog:",
306339 },
307340 },
308341 "packages/platform/server": {
@@ -312,8 +345,10 @@ · 3 unmodified lines
312345 "@effect/opentelemetry": "^0.61.0",
313346 "@effect/platform": "catalog:",
314347 "@effect/platform-node": "catalog:",
315− "@executor/control-plane": "workspace:*",
316348 "@executor/executor-mcp": "workspace:*",
349+ "@executor/platform-api": "workspace:*",
350+ "@executor/platform-internal": "workspace:*",
351+ "@executor/platform-sdk": "workspace:*",
317352 "@opentelemetry/api": "^1.9.0",
318353 "@opentelemetry/exporter-trace-otlp-grpc": "^0.213.0",
319354 "@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
@@ -641,8 +676,6 @@ · 321 unmodified lines
641676
642677 "@executor/codemode-core": ["@executor/codemode-core@workspace:packages/kernel/core"],
643678
644− "@executor/control-plane": ["@executor/control-plane@workspace:packages/platform/control-plane"],
645−
646679 "@executor/docs": ["@executor/docs@workspace:apps/docs"],
647680
648681 "@executor/executor-mcp": ["@executor/executor-mcp@workspace:packages/hosts/mcp"],
@@ -653,6 +686,12 @@ · 4 unmodified lines
653686
654687 "@executor/mcp-elicitation-demo": ["@executor/mcp-elicitation-demo@workspace:examples/mcp-elicitation-demo"],
655688
689+ "@executor/platform-api": ["@executor/platform-api@workspace:packages/platform/api"],
690+
691+ "@executor/platform-internal": ["@executor/platform-internal@workspace:packages/platform/internal"],
692+
693+ "@executor/platform-sdk": ["@executor/platform-sdk@workspace:packages/platform/sdk"],
694+
656695 "@executor/react": ["@executor/react@workspace:packages/clients/react"],
657696
658697 "@executor/runtime-deno-subprocess": ["@executor/runtime-deno-subprocess@workspace:packages/kernel/runtime-deno-subprocess"],
package.json+1−1
@@ -35,7 +35,7 @@ · 34 unmodified lines
3535 "release:pre:exit": "changeset pre exit",
3636 "release:beta:start": "changeset pre enter beta",
3737 "release:beta:stop": "changeset pre exit",
38− "fixture:release:capture": "bun run --cwd packages/platform/control-plane fixture:release:capture",
38+ "fixture:release:capture": "bun run --cwd packages/platform/sdk fixture:release:capture",
3939 "typecheck": "bun run --filter='*' typecheck",
4040 "knip": "knip"
4141 },
packages/clients/react/package.json+2−1
@@ -13,7 +13,8 @@ · 12 unmodified lines
1313 "dependencies": {
1414 "@effect-atom/atom": "0.5.3",
1515 "@effect-atom/atom-react": "0.5.0",
16− "@executor/control-plane": "workspace:*",
16+ "@executor/platform-api": "workspace:*",
17+ "@executor/platform-sdk": "workspace:*",
1718 "effect": "catalog:"
1819 },
1920 "peerDependencies": {
packages/clients/react/src/index.test.tsx+7−3
@@ -5,11 +5,15 @@ import { NodeFileSystem, NodeHttpServer } from "@effect/platform-node"; · 4 unmodified lines
55 import { describe, expect, it } from "@effect/vitest";
66 import {
77 createControlPlaneApiLayer,
8+} from "@executor/platform-api";
9+import {
810 createControlPlaneRuntime,
9− type LocalInstallation,
10− type Source,
1111 type ControlPlaneRuntime,
12−} from "@executor/control-plane";
12+} from "@executor/platform-sdk/runtime";
13+import type {
14+ LocalInstallation,
15+ Source,
16+} from "@executor/platform-sdk/schema";
1317 import * as Context from "effect/Context";
1418 import * as Effect from "effect/Effect";
1519 import * as Exit from "effect/Exit";
packages/clients/react/src/index.ts+24−22
@@ -1,35 +1,37 @@
11 import { Atom, Result } from "@effect-atom/atom";
22 import type * as Registry from "@effect-atom/atom/Registry";
33 import { RegistryContext, RegistryProvider, useAtomValue } from "@effect-atom/atom-react";
4−import { createControlPlaneClient } from "@executor/control-plane/client";
4+import {
5+ createControlPlaneClient,
6+ type CompleteSourceOAuthResult,
7+ type ConnectSourceBatchPayload,
8+ type ConnectSourceBatchResult,
9+ type ConnectSourcePayload,
10+ type ConnectSourceResult,
11+ type ControlPlaneClient,
12+ type CreateSecretPayload,
13+ type CreateSecretResult,
14+ type CreateSourcePayload,
15+ type CreateWorkspaceOauthClientPayload,
16+ type DeleteSecretResult,
17+ type DiscoverSourcePayload,
18+ type InstanceConfig,
19+ type LocalInstallation,
20+ type SecretListItem,
21+ type StartSourceOAuthPayload,
22+ type StartSourceOAuthResult,
23+ type UpdateSecretPayload,
24+ type UpdateSecretResult,
25+ type UpdateSourcePayload,
26+} from "@executor/platform-api";
527 import type {
6− CompleteSourceOAuthResult,
7− ConnectSourceBatchPayload,
8− ConnectSourceBatchResult,
9− ConnectSourcePayload,
10− ConnectSourceResult,
11− ControlPlaneClient,
12− CreateSecretPayload,
13− CreateSecretResult,
14− CreateSourcePayload,
15− CreateWorkspaceOauthClientPayload,
16− DeleteSecretResult,
17− DiscoverSourcePayload,
18− InstanceConfig,
19− LocalInstallation,
20− SecretListItem,
2128 Source,
2229 SourceDiscoveryResult,
2330 SourceInspection,
2431 SourceInspectionDiscoverResult,
2532 SourceInspectionToolDetail,
26− StartSourceOAuthPayload,
27− StartSourceOAuthResult,
28− UpdateSecretPayload,
29− UpdateSecretResult,
30− UpdateSourcePayload,
3133 WorkspaceOauthClient,
32−} from "@executor/control-plane";
34+} from "@executor/platform-sdk/schema";
3335 import * as Cause from "effect/Cause";
3436 import * as Effect from "effect/Effect";
3537 import * as Exit from "effect/Exit";
packages/dev/kitchen-sink/package.json+2−2
@@ -12,9 +12,9 @@ · 11 unmodified lines
1212 "@effect/platform": "catalog:",
1313 "@executor/ai-sdk-adapter": "workspace:*",
1414 "@executor/codemode-core": "workspace:*",
15− "@executor/source-openapi": "workspace:*",
16− "@executor/control-plane": "workspace:*",
15+ "@executor/platform-sdk": "workspace:*",
1716 "@executor/runtime-ses": "workspace:*",
17+ "@executor/source-openapi": "workspace:*",
1818 "effect": "catalog:"
1919 },
2020 "devDependencies": {
packages/dev/kitchen-sink/src/source-runtime.test.ts+1−1
@@ -17,7 +17,7 @@ import { · 16 unmodified lines
1717 type Source,
1818 type SourceId,
1919 type WorkspaceId,
20−} from "@executor/control-plane";
20+} from "@executor/platform-sdk/schema";
2121 import {
2222 createToolCatalogDiscovery,
2323 createToolCatalogFromTools,
packages/hosts/mcp/package.json+1−1
@@ -11,7 +11,7 @@ · 10 unmodified lines
1111 "test:watch": "vitest"
1212 },
1313 "dependencies": {
14− "@executor/control-plane": "workspace:*",
14+ "@executor/platform-sdk": "workspace:*",
1515 "@modelcontextprotocol/sdk": "^1.26.0",
1616 "effect": "catalog:",
1717 "zod": "catalog:"
packages/hosts/mcp/src/index.ts+4−4
@@ -1,15 +1,15 @@
11 import type {
22 ExecutionEnvelope,
3− ControlPlaneRuntime,
4−} from "@executor/control-plane";
3+} from "@executor/platform-sdk/schema";
54 import {
65 EXECUTOR_SOURCES_ADD_HELP_LINES,
7− ExecutionIdSchema,
86 RuntimeExecutionResolverService,
97 createExecution,
108 getExecution,
119 resumeExecution,
12−} from "@executor/control-plane";
10+ type ControlPlaneRuntime,
11+} from "@executor/platform-sdk/runtime";
12+import { ExecutionIdSchema } from "@executor/platform-sdk/schema";
1313 import * as Effect from "effect/Effect";
1414 import * as Cause from "effect/Cause";
1515 import * as Exit from "effect/Exit";
packages/hosts/mcp/src/paused-result.test.ts+4−1
@@ -1,5 +1,8 @@
11 import { describe, expect, it } from "@effect/vitest";
2−import type { ExecutionEnvelope, ExecutionInteraction } from "@executor/control-plane";
2+import type {
3+ ExecutionEnvelope,
4+ ExecutionInteraction,
5+} from "@executor/platform-sdk/schema";
36
47 import {
58 buildPausedResultText,
packages/hosts/mcp/src/paused-result.ts+1−1
@@ -1,7 +1,7 @@
11 import type {
22 ExecutionEnvelope,
33 ExecutionInteraction,
4−} from "@executor/control-plane";
4+} from "@executor/platform-sdk/schema";
55
66 export type ParsedInteractionPayload = {
77 mode: "form" | "url";
packages/platform/api/package.json+40−0
@@ -0,0 +1,40 @@
1+{
2+ "name": "@executor/platform-api",
3+ "private": true,
4+ "type": "module",
5+ "exports": {
6+ ".": "./src/index.ts",
7+ "./api": "./src/api.ts",
8+ "./client": "./src/client.ts",
9+ "./http": "./src/http.ts",
10+ "./errors": "./src/errors.ts",
11+ "./local/api": "./src/local/api.ts",
12+ "./local/http": "./src/local/http.ts",
13+ "./local-context": "./src/local-context.ts",
14+ "./oauth/api": "./src/oauth/api.ts",
15+ "./oauth/http": "./src/oauth/http.ts",
16+ "./policies/api": "./src/policies/api.ts",
17+ "./policies/http": "./src/policies/http.ts",
18+ "./sources/api": "./src/sources/api.ts",
19+ "./sources/http": "./src/sources/http.ts",
20+ "./executions/api": "./src/executions/api.ts",
21+ "./executions/http": "./src/executions/http.ts"
22+ },
23+ "scripts": {
24+ "test": "vitest run",
25+ "test:watch": "vitest",
26+ "typecheck": "bunx tsc --noEmit -p tsconfig.json"
27+ },
28+ "dependencies": {
29+ "@effect/platform": "catalog:",
30+ "@executor/platform-sdk": "workspace:*",
31+ "effect": "catalog:"
32+ },
33+ "devDependencies": {
34+ "@effect/vitest": "catalog:",
35+ "bun-types": "catalog:",
36+ "typescript": "^5.9.3",
37+ "vitest": "catalog:"
38+ },
39+ "version": "0.0.0"
40+}
packages/platform/api/src/api.tsrenamed from packages/platform/control-plane/src/api/api.ts+0−0

File renamed without changes.

packages/platform/api/src/client.tsrenamed from packages/platform/control-plane/src/client.ts+1−1
@@ -4,7 +4,7 @@ import { · 3 unmodified lines
44 } from "@effect/platform";
55 import * as Effect from "effect/Effect";
66
7−import { ControlPlaneApi } from "./api/api";
7+import { ControlPlaneApi } from "./api";
88
99 export const createControlPlaneClient = (input: {
1010 baseUrl: string;
packages/platform/api/src/errors.ts+7−0
@@ -0,0 +1,7 @@
1+export {
2+ ControlPlaneBadRequestError,
3+ ControlPlaneForbiddenError,
4+ ControlPlaneNotFoundError,
5+ ControlPlaneStorageError,
6+ ControlPlaneUnauthorizedError,
7+} from "@executor/platform-sdk/errors";
packages/platform/api/src/executions/api.tsrenamed from packages/platform/control-plane/src/api/executions/api.ts+14−17
@@ -1,10 +1,17 @@
11 import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform";
2+import {
3+ CreateExecutionPayloadSchema,
4+ ResumeExecutionPayloadSchema,
5+} from "@executor/platform-sdk/executions/contracts";
6+export type {
7+ CreateExecutionPayload,
8+ ResumeExecutionPayload,
9+} from "@executor/platform-sdk/executions/contracts";
210 import {
311 ExecutionIdSchema,
412 ExecutionEnvelopeSchema,
513 WorkspaceIdSchema,
6−} from "#schema";
7−import * as Schema from "effect/Schema";
14+} from "@executor/platform-sdk/schema";
815
916 import {
1017 ControlPlaneBadRequestError,
@@ -12,22 +19,12 @@ import { · 1 unmodified line
1219 ControlPlaneNotFoundError,
1320 ControlPlaneStorageError,
1421 ControlPlaneUnauthorizedError,
15−} from "../errors";
16−import { TrimmedNonEmptyStringSchema } from "../string-schemas";
17−
18−export const CreateExecutionPayloadSchema = Schema.Struct({
19− code: TrimmedNonEmptyStringSchema,
20− interactionMode: Schema.optional(Schema.Literal("live", "live_form", "detach")),
21−});
22−
23−export type CreateExecutionPayload = typeof CreateExecutionPayloadSchema.Type;
24−
25−export const ResumeExecutionPayloadSchema = Schema.Struct({
26− responseJson: Schema.optional(Schema.String),
27− interactionMode: Schema.optional(Schema.Literal("live", "live_form", "detach")),
28−});
22+} from "@executor/platform-sdk/errors";
2923
30−export type ResumeExecutionPayload = typeof ResumeExecutionPayloadSchema.Type;
24+export {
25+ CreateExecutionPayloadSchema,
26+ ResumeExecutionPayloadSchema,
27+};
3128
3229 const workspaceIdParam = HttpApiSchema.param("workspaceId", WorkspaceIdSchema);
3330 const executionIdParam = HttpApiSchema.param("executionId", ExecutionIdSchema);
packages/platform/api/src/executions/http.tsrenamed from packages/platform/control-plane/src/api/executions/http.ts+1−1
@@ -4,7 +4,7 @@ import { · 3 unmodified lines
44 createExecution,
55 getExecution,
66 resumeExecution,
7−} from "../../runtime/execution/service";
7+} from "@executor/platform-sdk/runtime";
88
99 import { ControlPlaneApi } from "../api";
1010 import { resolveRequestedLocalWorkspace } from "../local-context";
packages/platform/api/src/http.tsrenamed from packages/platform/control-plane/src/api/http.ts+0−0

File renamed without changes.

packages/platform/api/src/index.tsrenamed from packages/platform/control-plane/src/api/index.ts+19−1
@@ -2,8 +2,12 @@ export { · 1 unmodified line
22 ControlPlaneApi,
33 controlPlaneOpenApiSpec,
44 } from "./api";
5+export {
6+ createControlPlaneClient,
7+ type ControlPlaneClient,
8+} from "./client";
59
6−export type { LocalInstallation } from "#schema";
10+export type { LocalInstallation } from "@executor/platform-sdk/schema";
711
812 export {
913 ControlPlaneBadRequestError,
@@ -22,10 +26,12 @@ export { · 12 unmodified lines
2226
2327 export {
2428 CreateExecutionPayloadSchema,
29+ ExecutionsApi,
2530 ResumeExecutionPayloadSchema,
2631 type CreateExecutionPayload,
2732 type ResumeExecutionPayload,
2833 } from "./executions/api";
34+export { ControlPlaneExecutionsLive } from "./executions/http";
2935
3036 export {
3137 LocalApi,
@@ -38,16 +44,22 @@ export { · 6 unmodified lines
3844 type UpdateSecretResult,
3945 type DeleteSecretResult,
4046 } from "./local/api";
47+export { ControlPlaneLocalLive } from "./local/http";
4148
4249 export {
4350 OAuthApi,
4451 StartSourceOAuthPayloadSchema,
4552 StartSourceOAuthResultSchema,
4653 CompleteSourceOAuthResultSchema,
54+ SourceOAuthPopupFailureResultSchema,
55+ SourceOAuthPopupResultSchema,
56+ SourceOAuthPopupSuccessResultSchema,
4757 type StartSourceOAuthPayload,
4858 type StartSourceOAuthResult,
4959 type CompleteSourceOAuthResult,
60+ type SourceOAuthPopupResult,
5061 } from "./oauth/api";
62+export { ControlPlaneOAuthLive } from "./oauth/http";
5163
5264 export {
5365 ConnectSourceBatchPayloadSchema,
@@ -57,6 +69,7 @@ export { · 3 unmodified lines
5769 CreateWorkspaceOauthClientPayloadSchema,
5870 CreateSourcePayloadSchema,
5971 DiscoverSourcePayloadSchema,
72+ SourcesApi,
6073 UpdateSourcePayloadSchema,
6174 type ConnectSourceBatchPayload,
6275 type ConnectSourceBatchResult,
@@ -67,10 +80,15 @@ export { · 4 unmodified lines
6780 type DiscoverSourcePayload,
6881 type UpdateSourcePayload,
6982 } from "./sources/api";
83+export { ControlPlaneSourcesLive } from "./sources/http";
7084
7185 export {
7286 CreatePolicyPayloadSchema,
87+ PoliciesApi,
7388 UpdatePolicyPayloadSchema,
7489 type CreatePolicyPayload,
7590 type UpdatePolicyPayload,
7691 } from "./policies/api";
92+export { ControlPlanePoliciesLive } from "./policies/http";
93+
94+export { resolveRequestedLocalWorkspace } from "./local-context";
packages/platform/api/src/local-context.tsrenamed from packages/platform/control-plane/src/api/local-context.ts+3−3
@@ -1,10 +1,10 @@
1−import type { WorkspaceId } from "#schema";
1+import type { WorkspaceId } from "@executor/platform-sdk/schema";
22 import * as Effect from "effect/Effect";
33
44 import {
55 RuntimeLocalWorkspaceMismatchError,
6−} from "../runtime/local/errors";
7−import { requireRuntimeLocalWorkspace } from "../runtime/local/runtime-context";
6+ requireRuntimeLocalWorkspace,
7+} from "@executor/platform-sdk/runtime";
88 import { ControlPlaneForbiddenError } from "./errors";
99
1010 export const resolveRequestedLocalWorkspace = (
packages/platform/api/src/local/api.ts+69−0
@@ -0,0 +1,69 @@
1+import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform";
2+import {
3+ CreateSecretPayloadSchema,
4+ CreateSecretResultSchema,
5+ DeleteSecretResultSchema,
6+ InstanceConfigSchema,
7+ SecretListItemSchema,
8+ UpdateSecretPayloadSchema,
9+ UpdateSecretResultSchema,
10+} from "@executor/platform-sdk/local/contracts";
11+import {
12+ ControlPlaneBadRequestError,
13+ ControlPlaneNotFoundError,
14+ ControlPlaneStorageError,
15+} from "@executor/platform-sdk/errors";
16+import { LocalInstallationSchema } from "@executor/platform-sdk/schema";
17+import * as Schema from "effect/Schema";
18+
19+export type {
20+ CreateSecretPayload,
21+ CreateSecretResult,
22+ DeleteSecretResult,
23+ InstanceConfig,
24+ SecretLinkedSource,
25+ SecretListItem,
26+ SecretProvider,
27+ UpdateSecretPayload,
28+ UpdateSecretResult,
29+} from "@executor/platform-sdk/local/contracts";
30+
31+export class LocalApi extends HttpApiGroup.make("local")
32+ .add(
33+ HttpApiEndpoint.get("installation")`/local/installation`
34+ .addSuccess(LocalInstallationSchema)
35+ .addError(ControlPlaneBadRequestError)
36+ .addError(ControlPlaneNotFoundError)
37+ .addError(ControlPlaneStorageError),
38+ )
39+ .add(
40+ HttpApiEndpoint.get("config")`/local/config`
41+ .addSuccess(InstanceConfigSchema),
42+ )
43+ .add(
44+ HttpApiEndpoint.get("listSecrets")`/local/secrets`
45+ .addSuccess(Schema.Array(SecretListItemSchema))
46+ .addError(ControlPlaneStorageError),
47+ )
48+ .add(
49+ HttpApiEndpoint.post("createSecret")`/local/secrets`
50+ .setPayload(CreateSecretPayloadSchema)
51+ .addSuccess(CreateSecretResultSchema)
52+ .addError(ControlPlaneBadRequestError)
53+ .addError(ControlPlaneStorageError),
54+ )
55+ .add(
56+ HttpApiEndpoint.patch("updateSecret")`/local/secrets/${HttpApiSchema.param("secretId", Schema.String)}`
57+ .setPayload(UpdateSecretPayloadSchema)
58+ .addSuccess(UpdateSecretResultSchema)
59+ .addError(ControlPlaneBadRequestError)
60+ .addError(ControlPlaneNotFoundError)
61+ .addError(ControlPlaneStorageError),
62+ )
63+ .add(
64+ HttpApiEndpoint.del("deleteSecret")`/local/secrets/${HttpApiSchema.param("secretId", Schema.String)}`
65+ .addSuccess(DeleteSecretResultSchema)
66+ .addError(ControlPlaneNotFoundError)
67+ .addError(ControlPlaneStorageError),
68+ )
69+ .prefix("/v1") {}
packages/platform/api/src/local/http.ts+29−0
@@ -0,0 +1,29 @@
1+import { HttpApiBuilder } from "@effect/platform";
2+import {
3+ createLocalSecret,
4+ deleteLocalSecret,
5+ getLocalInstanceConfig,
6+ getLocalInstallation,
7+ listLocalSecrets,
8+ updateLocalSecret,
9+} from "@executor/platform-sdk";
10+
11+import { ControlPlaneApi } from "../api";
12+
13+export const ControlPlaneLocalLive = HttpApiBuilder.group(
14+ ControlPlaneApi,
15+ "local",
16+ (handlers) =>
17+ handlers
18+ .handle("installation", () => getLocalInstallation())
19+ .handle("config", () => getLocalInstanceConfig())
20+ .handle("listSecrets", () => listLocalSecrets())
21+ .handle("createSecret", ({ payload }) => createLocalSecret(payload))
22+ .handle("updateSecret", ({ path, payload }) =>
23+ updateLocalSecret({
24+ secretId: path.secretId,
25+ payload,
26+ }),
27+ )
28+ .handle("deleteSecret", ({ path }) => deleteLocalSecret(path.secretId)),
29+);
packages/platform/api/src/oauth/api.tsrenamed from packages/platform/control-plane/src/api/oauth/api.ts+3−3
@@ -5,7 +5,7 @@ import { · 4 unmodified lines
55 SourceTransportSchema,
66 StringMapSchema,
77 WorkspaceIdSchema,
8−} from "#schema";
8+} from "@executor/platform-sdk/schema";
99 import * as Schema from "effect/Schema";
1010
1111 import {
@@ -13,8 +13,8 @@ import { · 1 unmodified line
1313 ControlPlaneForbiddenError,
1414 ControlPlaneStorageError,
1515 ControlPlaneUnauthorizedError,
16−} from "../errors";
17−import { TrimmedNonEmptyStringSchema } from "../string-schemas";
16+} from "@executor/platform-sdk/errors";
17+import { TrimmedNonEmptyStringSchema } from "@executor/platform-sdk/string-schemas";
1818
1919 const workspaceIdParam = HttpApiSchema.param("workspaceId", WorkspaceIdSchema);
2020
packages/platform/api/src/oauth/http.tsrenamed from packages/platform/control-plane/src/api/oauth/http.ts+2−2
@@ -3,13 +3,13 @@ import { · 2 unmodified lines
33 HttpServerRequest,
44 HttpServerResponse,
55 } from "@effect/platform";
6−import type { WorkspaceId } from "#schema";
6+import type { WorkspaceId } from "@executor/platform-sdk/schema";
77 import * as Cause from "effect/Cause";
88 import * as Either from "effect/Either";
99 import * as Effect from "effect/Effect";
1010 import * as Schema from "effect/Schema";
1111
12−import { RuntimeSourceAuthServiceTag } from "../../runtime/sources/source-auth-service";
12+import { RuntimeSourceAuthServiceTag } from "@executor/platform-sdk/runtime";
1313 import { ControlPlaneApi } from "../api";
1414 import {
1515 ControlPlaneBadRequestError,
packages/platform/api/src/payload-schemas.test.tsrenamed from packages/platform/control-plane/src/api/payload-schemas.test.ts+0−0

File renamed without changes.

packages/platform/api/src/policies/api.tsrenamed from packages/platform/control-plane/src/api/policies/api.ts+18−24
@@ -1,37 +1,31 @@
11 import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform";
22 import {
3− LocalWorkspacePolicyApprovalModeSchema,
4− LocalWorkspacePolicyEffectSchema,
5− LocalWorkspacePolicySchema,
6− PolicyIdSchema,
7− WorkspaceIdSchema,
8−} from "#schema";
9−import * as Schema from "effect/Schema";
10−
3+ CreatePolicyPayloadSchema,
4+ UpdatePolicyPayloadSchema,
5+} from "@executor/platform-sdk/policies/contracts";
116 import {
127 ControlPlaneBadRequestError,
138 ControlPlaneForbiddenError,
149 ControlPlaneNotFoundError,
1510 ControlPlaneStorageError,
1611 ControlPlaneUnauthorizedError,
17−} from "../errors";
18−import { OptionalTrimmedNonEmptyStringSchema } from "../string-schemas";
19−
20−const LocalWorkspacePolicyPayloadSchema = Schema.Struct({
21− resourcePattern: OptionalTrimmedNonEmptyStringSchema,
22− effect: Schema.optional(LocalWorkspacePolicyEffectSchema),
23− approvalMode: Schema.optional(LocalWorkspacePolicyApprovalModeSchema),
24− priority: Schema.optional(Schema.Number),
25− enabled: Schema.optional(Schema.Boolean),
26−});
27−
28−export const CreatePolicyPayloadSchema = LocalWorkspacePolicyPayloadSchema;
29−
30−export type CreatePolicyPayload = typeof CreatePolicyPayloadSchema.Type;
12+} from "@executor/platform-sdk/errors";
13+import {
14+ LocalWorkspacePolicySchema,
15+ PolicyIdSchema,
16+ WorkspaceIdSchema,
17+} from "@executor/platform-sdk/schema";
18+import * as Schema from "effect/Schema";
3119
32−export const UpdatePolicyPayloadSchema = LocalWorkspacePolicyPayloadSchema;
20+export type {
21+ CreatePolicyPayload,
22+ UpdatePolicyPayload,
23+} from "@executor/platform-sdk/policies/contracts";
3324
34−export type UpdatePolicyPayload = typeof UpdatePolicyPayloadSchema.Type;
25+export {
26+ CreatePolicyPayloadSchema,
27+ UpdatePolicyPayloadSchema,
28+};
3529
3630 const workspaceIdParam = HttpApiSchema.param("workspaceId", WorkspaceIdSchema);
3731 const policyIdParam = HttpApiSchema.param("policyId", PolicyIdSchema);
packages/platform/api/src/policies/http.tsrenamed from packages/platform/control-plane/src/api/policies/http.ts+14−7
@@ -6,7 +6,7 @@ import { · 5 unmodified lines
66 listPolicies,
77 removePolicy,
88 updatePolicy,
9−} from "../../runtime/policy/policies-operations";
9+} from "@executor/platform-sdk";
1010
1111 import { ControlPlaneApi } from "../api";
1212 import { resolveRequestedLocalWorkspace } from "../local-context";
@@ -18,13 +18,14 @@ export const ControlPlanePoliciesLive = HttpApiBuilder.group( · 5 unmodified lines
1818 handlers
1919 .handle("list", ({ path }) =>
2020 resolveRequestedLocalWorkspace("policies.list", path.workspaceId).pipe(
21− Effect.zipRight(
22− listPolicies(path.workspaceId),
23− ),
21+ Effect.zipRight(listPolicies(path.workspaceId)),
2422 ),
2523 )
2624 .handle("create", ({ path, payload }) =>
27− resolveRequestedLocalWorkspace("policies.create", path.workspaceId).pipe(
25+ resolveRequestedLocalWorkspace(
26+ "policies.create",
27+ path.workspaceId,
28+ ).pipe(
2829 Effect.zipRight(
2930 createPolicy({ workspaceId: path.workspaceId, payload }),
3031 ),
@@ -41,7 +42,10 @@ export const ControlPlanePoliciesLive = HttpApiBuilder.group( · 10 unmodified lines
4142 ),
4243 )
4344 .handle("update", ({ path, payload }) =>
44− resolveRequestedLocalWorkspace("policies.update", path.workspaceId).pipe(
45+ resolveRequestedLocalWorkspace(
46+ "policies.update",
47+ path.workspaceId,
48+ ).pipe(
4549 Effect.zipRight(
4650 updatePolicy({
4751 workspaceId: path.workspaceId,
@@ -52,7 +56,10 @@ export const ControlPlanePoliciesLive = HttpApiBuilder.group( · 4 unmodified lines
5256 ),
5357 )
5458 .handle("remove", ({ path }) =>
55− resolveRequestedLocalWorkspace("policies.remove", path.workspaceId).pipe(
59+ resolveRequestedLocalWorkspace(
60+ "policies.remove",
61+ path.workspaceId,
62+ ).pipe(
5663 Effect.zipRight(
5764 removePolicy({
5865 workspaceId: path.workspaceId,
packages/platform/api/src/sources/api.tsrenamed from packages/platform/control-plane/src/api/sources/api.ts+43−162
@@ -1,189 +1,70 @@
11 import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform";
22 import {
3− SourceOauthClientInputSchema,
4− ExecutionInteractionIdSchema,
5− JsonObjectSchema,
3+ ConnectSourceBatchPayloadSchema,
4+ ConnectSourceBatchResultSchema,
5+ ConnectSourcePayloadSchema,
6+ ConnectSourceResultSchema,
7+ CreateSourcePayloadSchema,
8+ CreateWorkspaceOauthClientPayloadSchema,
9+ CredentialOauthCompleteUrlParamsSchema,
10+ CredentialPageUrlParamsSchema,
11+ CredentialSubmitPayloadSchema,
12+ DiscoverSourcePayloadSchema,
13+ UpdateSourcePayloadSchema,
14+ WorkspaceOauthClientQuerySchema,
15+} from "@executor/platform-sdk/sources/contracts";
16+import {
17+ ControlPlaneBadRequestError,
18+ ControlPlaneForbiddenError,
19+ ControlPlaneNotFoundError,
20+ ControlPlaneStorageError,
21+ ControlPlaneUnauthorizedError,
22+} from "@executor/platform-sdk/errors";
23+import {
624 ProviderAuthGrantIdSchema,
7− SourceAuthSchema,
8− SourceAuthSessionIdSchema,
925 SourceDiscoveryResultSchema,
1026 SourceIdSchema,
1127 SourceInspectionDiscoverPayloadSchema,
1228 SourceInspectionDiscoverResultSchema,
1329 SourceInspectionSchema,
1430 SourceInspectionToolDetailSchema,
15− SourceImportAuthPolicySchema,
16− SourceKindSchema,
17− SourceProbeAuthSchema,
1831 SourceSchema,
19− SourceStatusSchema,
2032 WorkspaceOauthClientIdSchema,
2133 WorkspaceOauthClientSchema,
2234 WorkspaceIdSchema,
23−} from "#schema";
35+} from "@executor/platform-sdk/schema";
2436 import * as Schema from "effect/Schema";
2537
26−import {
27− ControlPlaneBadRequestError,
28− ControlPlaneForbiddenError,
29− ControlPlaneNotFoundError,
30− ControlPlaneStorageError,
31− ControlPlaneUnauthorizedError,
32−} from "../errors";
33−import {
34− OptionalTrimmedNonEmptyStringSchema,
35− TrimmedNonEmptyStringSchema,
36−} from "../string-schemas";
37−import {
38− ConnectSourcePayloadSchema,
39− type ConnectSourcePayload,
40−} from "../../runtime/sources/source-adapters";
41−
42−const createSourcePayloadRequiredSchema = Schema.Struct({
43− name: TrimmedNonEmptyStringSchema,
44− kind: SourceKindSchema,
45− endpoint: TrimmedNonEmptyStringSchema,
46−});
47−
48−const createSourcePayloadOptionalSchema = Schema.Struct({
49− status: Schema.optional(SourceStatusSchema),
50− enabled: Schema.optional(Schema.Boolean),
51− namespace: Schema.optional(Schema.NullOr(Schema.String)),
52− binding: Schema.optional(JsonObjectSchema),
53− importAuthPolicy: Schema.optional(SourceImportAuthPolicySchema),
54− importAuth: Schema.optional(SourceAuthSchema),
55− auth: Schema.optional(SourceAuthSchema),
56− sourceHash: Schema.optional(Schema.NullOr(Schema.String)),
57− lastError: Schema.optional(Schema.NullOr(Schema.String)),
58−});
59−
60−export const CreateSourcePayloadSchema = Schema.extend(
61− createSourcePayloadRequiredSchema,
62− createSourcePayloadOptionalSchema,
63−);
64−
65−export type CreateSourcePayload = typeof CreateSourcePayloadSchema.Type;
66−export type CreateWorkspaceOauthClientPayload =
67− typeof CreateWorkspaceOauthClientPayloadSchema.Type;
68−
69−export const UpdateSourcePayloadSchema = Schema.Struct({
70− name: OptionalTrimmedNonEmptyStringSchema,
71− endpoint: OptionalTrimmedNonEmptyStringSchema,
72− status: Schema.optional(SourceStatusSchema),
73− enabled: Schema.optional(Schema.Boolean),
74− namespace: Schema.optional(Schema.NullOr(Schema.String)),
75− binding: Schema.optional(JsonObjectSchema),
76− importAuthPolicy: Schema.optional(SourceImportAuthPolicySchema),
77− importAuth: Schema.optional(SourceAuthSchema),
78− auth: Schema.optional(SourceAuthSchema),
79− sourceHash: Schema.optional(Schema.NullOr(Schema.String)),
80− lastError: Schema.optional(Schema.NullOr(Schema.String)),
81−});
82−
83−export type UpdateSourcePayload = typeof UpdateSourcePayloadSchema.Type;
84−
85−const workspaceIdParam = HttpApiSchema.param("workspaceId", WorkspaceIdSchema);
86−const sourceIdParam = HttpApiSchema.param("sourceId", SourceIdSchema);
87−const toolPathParam = HttpApiSchema.param("toolPath", Schema.String);
88−
89−const CredentialPageUrlParamsSchema = Schema.Struct({
90− interactionId: ExecutionInteractionIdSchema,
91−});
92−
93−const CredentialSubmitPayloadSchema = Schema.Struct({
94− action: Schema.optional(Schema.Literal("submit", "continue", "cancel")),
95− token: Schema.optional(Schema.String),
96−});
97−
98−const CredentialOauthCompleteUrlParamsSchema = Schema.Struct({
99− state: Schema.String,
100− code: Schema.optional(Schema.String),
101− error: Schema.optional(Schema.String),
102− error_description: Schema.optional(Schema.String),
103−});
104−
105−const WorkspaceOauthClientQuerySchema = Schema.Struct({
106− providerKey: Schema.String,
107−});
108−
109−const CreateWorkspaceOauthClientPayloadSchema = Schema.Struct({
110− providerKey: Schema.String,
111− label: Schema.optional(Schema.NullOr(Schema.String)),
112− oauthClient: SourceOauthClientInputSchema,
113−});
114−
115−const oauthClientIdParam = HttpApiSchema.param("oauthClientId", WorkspaceOauthClientIdSchema);
116−const grantIdParam = HttpApiSchema.param("grantId", ProviderAuthGrantIdSchema);
117−
118−const ConnectGoogleDiscoveryBatchSourceSchema = Schema.Struct({
119− service: TrimmedNonEmptyStringSchema,
120− version: TrimmedNonEmptyStringSchema,
121− discoveryUrl: Schema.optional(Schema.NullOr(TrimmedNonEmptyStringSchema)),
122− scopes: Schema.optional(Schema.Array(TrimmedNonEmptyStringSchema)),
123− name: Schema.optional(Schema.NullOr(Schema.String)),
124− namespace: Schema.optional(Schema.NullOr(Schema.String)),
125−});
126−
127−const ConnectSourceBatchPayloadSchema = Schema.Struct({
128− workspaceOauthClientId: WorkspaceOauthClientIdSchema,
129− sources: Schema.Array(ConnectGoogleDiscoveryBatchSourceSchema),
130−});
131−
132−const ConnectSourceBatchResultSchema = Schema.Struct({
133− results: Schema.Array(
134− Schema.Struct({
135− source: SourceSchema,
136− status: Schema.Literal("connected", "pending_oauth"),
137− }),
138− ),
139− providerOauthSession: Schema.NullOr(
140− Schema.Struct({
141− sessionId: SourceAuthSessionIdSchema,
142− authorizationUrl: Schema.String,
143− sourceIds: Schema.Array(SourceIdSchema),
144− }),
145− ),
146−});
147−
148−const DiscoverSourcePayloadSchema = Schema.Struct({
149− url: TrimmedNonEmptyStringSchema,
150− probeAuth: Schema.optional(SourceProbeAuthSchema),
151−});
152−
153−export type DiscoverSourcePayload = typeof DiscoverSourcePayloadSchema.Type;
154−
155−const ConnectSourceResultSchema = Schema.Union(
156− Schema.Struct({
157− kind: Schema.Literal("connected"),
158− source: SourceSchema,
159− }),
160− Schema.Struct({
161− kind: Schema.Literal("credential_required"),
162− source: SourceSchema,
163− credentialSlot: Schema.Literal("runtime", "import"),
164− }),
165− Schema.Struct({
166− kind: Schema.Literal("oauth_required"),
167− source: SourceSchema,
168− sessionId: SourceAuthSessionIdSchema,
169− authorizationUrl: Schema.String,
170− }),
171−);
172−
173−export type ConnectSourceResult = typeof ConnectSourceResultSchema.Type;
174−export type ConnectSourceBatchPayload = typeof ConnectSourceBatchPayloadSchema.Type;
175−export type ConnectSourceBatchResult = typeof ConnectSourceBatchResultSchema.Type;
38+export type {
39+ ConnectSourceBatchPayload,
40+ ConnectSourceBatchResult,
41+ ConnectSourcePayload,
42+ ConnectSourceResult,
43+ CreateSourcePayload,
44+ CreateWorkspaceOauthClientPayload,
45+ DiscoverSourcePayload,
46+ UpdateSourcePayload,
47+} from "@executor/platform-sdk/sources/contracts";
17648
17749 export {
178− ConnectSourcePayloadSchema,
17950 ConnectSourceBatchPayloadSchema,
18051 ConnectSourceBatchResultSchema,
52+ ConnectSourcePayloadSchema,
18153 ConnectSourceResultSchema,
54+ CreateSourcePayloadSchema,
18255 CreateWorkspaceOauthClientPayloadSchema,
18356 DiscoverSourcePayloadSchema,
57+ UpdateSourcePayloadSchema,
18458 };
18559
186−export type { ConnectSourcePayload };
60+const workspaceIdParam = HttpApiSchema.param("workspaceId", WorkspaceIdSchema);
61+const sourceIdParam = HttpApiSchema.param("sourceId", SourceIdSchema);
62+const toolPathParam = HttpApiSchema.param("toolPath", Schema.String);
63+const oauthClientIdParam = HttpApiSchema.param(
64+ "oauthClientId",
65+ WorkspaceOauthClientIdSchema,
66+);
67+const grantIdParam = HttpApiSchema.param("grantId", ProviderAuthGrantIdSchema);
18768
18869 const HtmlSchema = HttpApiSchema.Text({
18970 contentType: "text/html",
packages/platform/api/src/sources/http.tsrenamed from packages/platform/control-plane/src/api/sources/http.ts+170−96
@@ -3,32 +3,32 @@ import { · 2 unmodified lines
33 HttpServerRequest,
44 HttpServerResponse,
55 } from "@effect/platform";
6−import type { ExecutionInteraction, Source, WorkspaceId } from "#schema";
6+import type {
7+ ExecutionInteraction,
8+ Source,
9+ WorkspaceId,
10+} from "@executor/platform-sdk/schema";
711 import * as Effect from "effect/Effect";
812
913 import {
14+ completeSourceCredentialSetup,
1015 createSource,
11− getSource,
12− listSources,
13− removeSource,
14− updateSource,
15−} from "../../runtime/sources/sources-operations";
16−import {
16+ discoverSource,
1717 discoverSourceInspectionTools,
18+ getSource,
19+ getSourceCredentialInteraction,
1820 getSourceInspection,
1921 getSourceInspectionToolDetail,
20−} from "../../runtime/sources/source-inspection";
21−import {
22− completeSourceCredentialSetup,
23− getSourceCredentialInteraction,
22+ listSources,
23+ removeSource,
2424 submitSourceCredentialInteraction,
25−} from "../../runtime/local/operations";
26−import { discoverSource } from "../../runtime/sources/source-discovery";
27−import { sourceAdapterRequiresInteractiveConnect } from "../../runtime/sources/source-adapters";
25+ updateSource,
26+} from "@executor/platform-sdk";
2827 import {
28+ sourceAdapterRequiresInteractiveConnect,
2929 RuntimeSourceAuthServiceTag,
3030 type ExecutorAddSourceInput,
31−} from "../../runtime/sources/source-auth-service";
31+} from "@executor/platform-sdk/runtime";
3232
3333 import {
3434 ControlPlaneBadRequestError,
@@ -58,7 +58,10 @@ const readHeader = (headers: unknown, name: string): string | null => { · 23 unmodified lines
5858 return null;
5959 };
6060
61−const resolveRequestOrigin = (request: { url: string; headers: unknown }): string | null => {
61+const resolveRequestOrigin = (request: {
62+ url: string;
63+ headers: unknown;
64+}): string | null => {
6265 try {
6366 return new URL(request.url).origin;
6467 } catch {
@@ -69,7 +72,8 @@ const resolveRequestOrigin = (request: { url: string; headers: unknown }): strin · 4 unmodified lines
6972 }
7073
7174 const forwardedProto = readHeader(request.headers, "x-forwarded-proto");
72− const protocol = forwardedProto && forwardedProto.length > 0 ? forwardedProto : "http";
75+ const protocol =
76+ forwardedProto && forwardedProto.length > 0 ? forwardedProto : "http";
7377 return `${protocol}://${host}`;
7478 }
7579 };
@@ -86,7 +90,8 @@ const toBadRequestError = (operation: string, cause: unknown) => { · 10 unmodified lines
8690 const isInteractiveConnectPayload = (
8791 payload: ConnectSourcePayload,
8892 ): payload is Extract<ConnectSourcePayload, { kind?: "mcp" }> =>
89− payload.kind === undefined || sourceAdapterRequiresInteractiveConnect(payload.kind);
93+ payload.kind === undefined ||
94+ sourceAdapterRequiresInteractiveConnect(payload.kind);
9095
9196 const escapeHtml = (value: string): string =>
9297 value
@@ -415,11 +420,7 @@ const credentialPageDocument = (input: { · 322 unmodified lines
415420
416421 <hr />
417422
418− ${
419− input.error
420− ? `<p class="alert">${escapeHtml(input.error)}</p>`
421− : ""
422− }
423+ ${input.error ? `<p class="alert">${escapeHtml(input.error)}</p>` : ""}
423424 ${
424425 input.state === "stored"
425426 ? `<div class="result-card success">
@@ -431,17 +432,17 @@ const credentialPageDocument = (input: { · 5 unmodified lines
431432 <p class="result-heading">Continuing without auth</p>
432433 <p>Executor resumed the source connection without stored credentials. You can close this window.</p>
433434 </div>`
434− : input.state === "cancelled"
435− ? `<div class="result-card neutral">
435+ : input.state === "cancelled"
436+ ? `<div class="result-card neutral">
436437 <p class="result-heading">Request cancelled</p>
437438 <p>Executor was notified. You can close this window.</p>
438439 </div>`
439− : input.state === "inactive"
440− ? `<div class="result-card neutral">
440+ : input.state === "inactive"
441+ ? `<div class="result-card neutral">
441442 <p class="result-heading">No longer active</p>
442443 <p>This credential request has expired. Start a new source connection flow if needed.</p>
443444 </div>`
444− : `<form method="post">
445+ : `<form method="post">
445446 <div class="field">
446447 <label for="token">Personal access token</label>
447448 <input
@@ -474,8 +475,8 @@ const sourceOAuthPopupResultDocument = (input: { · 26 unmodified lines
474475 sessionId?: string | null;
475476 payload:
476477 | {
477− type: "executor:source-oauth-result";
478− ok: true;
478+ type: "executor:source-oauth-result";
479+ ok: true;
479480 sourceId: Source["id"];
480481 }
481482 | {
@@ -595,9 +596,7 @@ const sourceOAuthPopupResultDocument = (input: { · 113 unmodified lines
595596 </html>`;
596597
597598 const htmlResponse = (html: string, status = 200) =>
598− HttpServerResponse.html(html).pipe(
599− HttpServerResponse.setStatus(status),
600− );
599+ HttpServerResponse.html(html).pipe(HttpServerResponse.setStatus(status));
601600
602601 const credentialErrorResponse = (input: {
603602 title: string;
@@ -619,7 +618,10 @@ const credentialErrorResponse = (input: { · 15 unmodified lines
619618 );
620619
621620 const credentialErrorStatus = (
622− error: ControlPlaneBadRequestError | ControlPlaneNotFoundError | ControlPlaneStorageError,
621+ error:
622+ | ControlPlaneBadRequestError
623+ | ControlPlaneNotFoundError
624+ | ControlPlaneStorageError,
623625 ) =>
624626 error instanceof ControlPlaneNotFoundError
625627 ? 404
@@ -679,15 +681,18 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 53 unmodified lines
679681 ),
680682 )
681683 .handle("connect", ({ path, payload }) =>
682− resolveRequestedLocalWorkspace("sources.connect", path.workspaceId).pipe(
684+ resolveRequestedLocalWorkspace(
685+ "sources.connect",
686+ path.workspaceId,
687+ ).pipe(
683688 Effect.flatMap((runtimeLocalWorkspace) =>
684689 Effect.gen(function* () {
685690 const request = yield* HttpServerRequest.HttpServerRequest;
686691 const sourceAuthService = yield* RuntimeSourceAuthServiceTag;
687692 const baseUrl = resolveRequestOrigin(request);
688693
689− if (isInteractiveConnectPayload(payload)) {
690− return yield* sourceAuthService.connectMcpSource({
694+ if (isInteractiveConnectPayload(payload)) {
695+ return yield* sourceAuthService.connectMcpSource({
691696 workspaceId: path.workspaceId,
692697 actorAccountId: runtimeLocalWorkspace.installation.accountId,
693698 endpoint: payload.endpoint,
@@ -704,26 +709,29 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 10 unmodified lines
704709 });
705710 }
706711
707− return yield* sourceAuthService.addExecutorSource(
708− ({
709− workspaceId: path.workspaceId,
710− actorAccountId: runtimeLocalWorkspace.installation.accountId,
711− executionId: null,
712− interactionId: null,
713− ...(payload as Record<string, unknown>),
714− } as ExecutorAddSourceInput),
715− { baseUrl },
716− );
712+ return yield* sourceAuthService.addExecutorSource(
713+ {
714+ workspaceId: path.workspaceId,
715+ actorAccountId: runtimeLocalWorkspace.installation.accountId,
716+ executionId: null,
717+ interactionId: null,
718+ ...(payload as Record<string, unknown>),
719+ } as ExecutorAddSourceInput,
720+ { baseUrl },
721+ );
717722 }).pipe(
718723 Effect.catchAll((cause) =>
719724 Effect.fail(toBadRequestError("sources.connect", cause)),
720725 ),
721− )
726+ ),
722727 ),
723728 ),
724729 )
725730 .handle("connectBatch", ({ path, payload }) =>
726− resolveRequestedLocalWorkspace("sources.connectBatch", path.workspaceId).pipe(
731+ resolveRequestedLocalWorkspace(
732+ "sources.connectBatch",
733+ path.workspaceId,
734+ ).pipe(
727735 Effect.flatMap((runtimeLocalWorkspace) =>
728736 Effect.gen(function* () {
729737 const request = yield* HttpServerRequest.HttpServerRequest;
@@ -748,7 +756,10 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 18 unmodified lines
748756 ),
749757 )
750758 .handle("listWorkspaceOauthClients", ({ path, urlParams }) =>
751− resolveRequestedLocalWorkspace("sources.listWorkspaceOauthClients", path.workspaceId).pipe(
759+ resolveRequestedLocalWorkspace(
760+ "sources.listWorkspaceOauthClients",
761+ path.workspaceId,
762+ ).pipe(
752763 Effect.flatMap(() =>
753764 Effect.gen(function* () {
754765 const sourceAuthService = yield* RuntimeSourceAuthServiceTag;
@@ -758,14 +769,19 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 3 unmodified lines
758769 });
759770 }).pipe(
760771 Effect.catchAll((cause) =>
761− Effect.fail(toBadRequestError("sources.listWorkspaceOauthClients", cause)),
772+ Effect.fail(
773+ toBadRequestError("sources.listWorkspaceOauthClients", cause),
774+ ),
762775 ),
763776 ),
764777 ),
765778 ),
766779 )
767780 .handle("createWorkspaceOauthClient", ({ path, payload }) =>
768− resolveRequestedLocalWorkspace("sources.createWorkspaceOauthClient", path.workspaceId).pipe(
781+ resolveRequestedLocalWorkspace(
782+ "sources.createWorkspaceOauthClient",
783+ path.workspaceId,
784+ ).pipe(
769785 Effect.flatMap(() =>
770786 Effect.gen(function* () {
771787 const sourceAuthService = yield* RuntimeSourceAuthServiceTag;
@@ -777,44 +793,68 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 5 unmodified lines
777793 });
778794 }).pipe(
779795 Effect.catchAll((cause) =>
780− Effect.fail(toBadRequestError("sources.createWorkspaceOauthClient", cause)),
796+ Effect.fail(
797+ toBadRequestError(
798+ "sources.createWorkspaceOauthClient",
799+ cause,
800+ ),
801+ ),
781802 ),
782803 ),
783804 ),
784805 ),
785806 )
786807 .handle("removeWorkspaceOauthClient", ({ path }) =>
787− resolveRequestedLocalWorkspace("sources.removeWorkspaceOauthClient", path.workspaceId).pipe(
808+ resolveRequestedLocalWorkspace(
809+ "sources.removeWorkspaceOauthClient",
810+ path.workspaceId,
811+ ).pipe(
788812 Effect.flatMap(() =>
789813 Effect.gen(function* () {
790814 const sourceAuthService = yield* RuntimeSourceAuthServiceTag;
791− const removed = yield* sourceAuthService.removeWorkspaceOauthClient({
792− workspaceId: path.workspaceId,
793− oauthClientId: path.oauthClientId,
794− });
815+ const removed =
816+ yield* sourceAuthService.removeWorkspaceOauthClient({
817+ workspaceId: path.workspaceId,
818+ oauthClientId: path.oauthClientId,
819+ });
795820
796821 return { removed };
797822 }).pipe(
798823 Effect.catchAll((cause) =>
799− Effect.fail(toBadRequestError("sources.removeWorkspaceOauthClient", cause)),
824+ Effect.fail(
825+ toBadRequestError(
826+ "sources.removeWorkspaceOauthClient",
827+ cause,
828+ ),
829+ ),
800830 ),
801831 ),
802832 ),
803833 ),
804834 )
805835 .handle("removeProviderAuthGrant", ({ path }) =>
806− resolveRequestedLocalWorkspace("sources.removeProviderAuthGrant", path.workspaceId).pipe(
836+ resolveRequestedLocalWorkspace(
837+ "sources.removeProviderAuthGrant",
838+ path.workspaceId,
839+ ).pipe(
807840 Effect.flatMap(() =>
808841 Effect.gen(function* () {
809842 const sourceAuthService = yield* RuntimeSourceAuthServiceTag;
810− const removed = yield* sourceAuthService.removeProviderAuthGrant({
811− workspaceId: path.workspaceId,
812− grantId: path.grantId,
813− }).pipe(
814− Effect.catchAll((cause) =>
815− Effect.fail(toBadRequestError("sources.removeProviderAuthGrant", cause)),
816− ),
817− );
843+ const removed = yield* sourceAuthService
844+ .removeProviderAuthGrant({
845+ workspaceId: path.workspaceId,
846+ grantId: path.grantId,
847+ })
848+ .pipe(
849+ Effect.catchAll((cause) =>
850+ Effect.fail(
851+ toBadRequestError(
852+ "sources.removeProviderAuthGrant",
853+ cause,
854+ ),
855+ ),
856+ ),
857+ );
818858
819859 return { removed };
820860 }),
@@ -827,7 +867,7 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 6 unmodified lines
827867 listSources({
828868 workspaceId: path.workspaceId,
829869 accountId: runtimeLocalWorkspace.installation.accountId,
830− })
870+ }),
831871 ),
832872 ),
833873 )
@@ -838,7 +878,7 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 4 unmodified lines
838878 workspaceId: path.workspaceId,
839879 accountId: runtimeLocalWorkspace.installation.accountId,
840880 payload,
841− })
881+ }),
842882 ),
843883 ),
844884 )
@@ -849,12 +889,15 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 4 unmodified lines
849889 workspaceId: path.workspaceId,
850890 sourceId: path.sourceId,
851891 accountId: runtimeLocalWorkspace.installation.accountId,
852− })
892+ }),
853893 ),
854894 ),
855895 )
856896 .handle("inspection", ({ path }) =>
857− resolveRequestedLocalWorkspace("sources.inspection", path.workspaceId).pipe(
897+ resolveRequestedLocalWorkspace(
898+ "sources.inspection",
899+ path.workspaceId,
900+ ).pipe(
858901 Effect.zipRight(
859902 getSourceInspection({
860903 workspaceId: path.workspaceId,
@@ -864,7 +907,10 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 3 unmodified lines
864907 ),
865908 )
866909 .handle("inspectionTool", ({ path }) =>
867− resolveRequestedLocalWorkspace("sources.inspection_tool", path.workspaceId).pipe(
910+ resolveRequestedLocalWorkspace(
911+ "sources.inspection_tool",
912+ path.workspaceId,
913+ ).pipe(
868914 Effect.zipRight(
869915 getSourceInspectionToolDetail({
870916 workspaceId: path.workspaceId,
@@ -875,7 +921,10 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 4 unmodified lines
875921 ),
876922 )
877923 .handle("inspectionDiscover", ({ path, payload }) =>
878− resolveRequestedLocalWorkspace("sources.inspection_discover", path.workspaceId).pipe(
924+ resolveRequestedLocalWorkspace(
925+ "sources.inspection_discover",
926+ path.workspaceId,
927+ ).pipe(
879928 Effect.zipRight(
880929 discoverSourceInspectionTools({
881930 workspaceId: path.workspaceId,
@@ -893,7 +942,7 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 11 unmodified lines
893942 sourceId: path.sourceId,
894943 accountId: runtimeLocalWorkspace.installation.accountId,
895944 payload,
896− })
945+ }),
897946 ),
898947 ),
899948 )
@@ -922,9 +971,10 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 22 unmodified lines
922971 sourceLabel: interaction.sourceLabel,
923972 endpoint: interaction.endpoint,
924973 interactionId: interaction.interactionId,
925− state: interaction.status === "pending" ? "pending" : "inactive",
974+ state:
975+ interaction.status === "pending" ? "pending" : "inactive",
926976 }),
927− )
977+ ),
928978 ),
929979 Effect.catchTags({
930980 ControlPlaneNotFoundError: (error) =>
@@ -967,14 +1017,14 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 36 unmodified lines
9671017 ? "Source Credential Stored"
9681018 : result.kind === "continued"
9691019 ? "Source Access Updated"
970− : "Source Credential Request Cancelled",
1020+ : "Source Credential Request Cancelled",
9711021 eyebrow: "Executor",
9721022 message:
9731023 result.kind === "stored"
9741024 ? "Executor stored the credential and resumed the source connection."
9751025 : result.kind === "continued"
9761026 ? "Executor resumed the source connection without stored credentials."
977− : "Executor cancelled the source credential request.",
1027+ : "Executor cancelled the source credential request.",
9781028 sourceLabel: result.sourceLabel,
9791029 endpoint: result.endpoint,
9801030 interactionId: urlParams.interactionId,
@@ -985,7 +1035,7 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 4 unmodified lines
9851035 ? "continued"
9861036 : "cancelled",
9871037 }),
988− )
1038+ ),
9891039 ),
9901040 Effect.catchTags({
9911041 ControlPlaneBadRequestError: (error) =>
@@ -1044,12 +1094,18 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 52 unmodified lines
10441094 htmlResponse(
10451095 sourceOAuthPopupResultDocument({
10461096 title: "OAuth failed",
1047− message: error instanceof Error ? error.message : "Failed completing OAuth",
1097+ message:
1098+ error instanceof Error
1099+ ? error.message
1100+ : "Failed completing OAuth",
10481101 state: "cancelled",
10491102 payload: {
10501103 type: "executor:source-oauth-result",
10511104 ok: false,
1052− error: error instanceof Error ? error.message : "Failed completing OAuth",
1105+ error:
1106+ error instanceof Error
1107+ ? error.message
1108+ : "Failed completing OAuth",
10531109 },
10541110 }),
10551111 500,
@@ -1059,18 +1115,22 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 3 unmodified lines
10591115 ),
10601116 )
10611117 .handle("providerOauthComplete", ({ path, urlParams }) =>
1062− resolveRequestedLocalWorkspace("sources.providerOauthComplete", path.workspaceId).pipe(
1118+ resolveRequestedLocalWorkspace(
1119+ "sources.providerOauthComplete",
1120+ path.workspaceId,
1121+ ).pipe(
10631122 Effect.flatMap((runtimeLocalWorkspace) =>
10641123 Effect.gen(function* () {
10651124 const sourceAuthService = yield* RuntimeSourceAuthServiceTag;
1066− const completed = yield* sourceAuthService.completeProviderOauthCallback({
1067− workspaceId: path.workspaceId,
1068− actorAccountId: runtimeLocalWorkspace.installation.accountId,
1069− state: urlParams.state,
1070− code: urlParams.code,
1071− error: urlParams.error,
1072− errorDescription: urlParams.error_description,
1073− });
1125+ const completed =
1126+ yield* sourceAuthService.completeProviderOauthCallback({
1127+ workspaceId: path.workspaceId,
1128+ actorAccountId: runtimeLocalWorkspace.installation.accountId,
1129+ state: urlParams.state,
1130+ code: urlParams.code,
1131+ error: urlParams.error,
1132+ errorDescription: urlParams.error_description,
1133+ });
10741134 const primarySource = completed.sources[0] ?? null;
10751135
10761136 return htmlResponse(
@@ -1082,7 +1142,9 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 5 unmodified lines
10821142 payload: {
10831143 type: "executor:source-oauth-result",
10841144 ok: true,
1085− sourceId: primarySource?.id ?? ("src_oauth_complete" as Source["id"]),
1145+ sourceId:
1146+ primarySource?.id ??
1147+ ("src_oauth_complete" as Source["id"]),
10861148 },
10871149 }),
10881150 );
@@ -1092,12 +1154,18 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 3 unmodified lines
10921154 htmlResponse(
10931155 sourceOAuthPopupResultDocument({
10941156 title: "OAuth failed",
1095− message: error instanceof Error ? error.message : "Failed completing OAuth",
1157+ message:
1158+ error instanceof Error
1159+ ? error.message
1160+ : "Failed completing OAuth",
10961161 state: "cancelled",
10971162 payload: {
10981163 type: "executor:source-oauth-result",
10991164 ok: false,
1100− error: error instanceof Error ? error.message : "Failed completing OAuth",
1165+ error:
1166+ error instanceof Error
1167+ ? error.message
1168+ : "Failed completing OAuth",
11011169 },
11021170 }),
11031171 500,
@@ -1111,12 +1179,18 @@ export const ControlPlaneSourcesLive = HttpApiBuilder.group( · 7 unmodified lines
11111179 htmlResponse(
11121180 sourceOAuthPopupResultDocument({
11131181 title: "OAuth failed",
1114− message: error instanceof Error ? error.message : "Failed completing OAuth",
1182+ message:
1183+ error instanceof Error
1184+ ? error.message
1185+ : "Failed completing OAuth",
11151186 state: "cancelled",
11161187 payload: {
11171188 type: "executor:source-oauth-result",
11181189 ok: false,
1119− error: error instanceof Error ? error.message : "Failed completing OAuth",
1190+ error:
1191+ error instanceof Error
1192+ ? error.message
1193+ : "Failed completing OAuth",
11201194 },
11211195 }),
11221196 500,
packages/platform/api/src/string-schemas.ts+4−0
@@ -0,0 +1,4 @@
1+export {
2+ OptionalTrimmedNonEmptyStringSchema,
3+ TrimmedNonEmptyStringSchema,
4+} from "@executor/platform-sdk/string-schemas";
packages/platform/api/tsconfig.jsonrenamed from packages/platform/control-plane/tsconfig.json+1−8
@@ -9,12 +9,5 @@ · 8 unmodified lines
99 "lib": ["ES2022"],
1010 "types": ["bun-types", "node"]
1111 },
12− "include": [
13− "src/**/*.ts",
14− "src/**/*.d.ts"
15− ],
16− "exclude": [
17− "src/**/*.test.ts",
18− "src/persistence/**/*.ts"
19− ]
12+ "include": ["src/**/*.ts"]
2013 }
packages/platform/api/vitest.config.tsrenamed from packages/platform/control-plane/vitest.config.ts+0−0

File renamed without changes.

packages/platform/control-plane/CHANGELOG.md+0−13
@@ -1,13 +0,0 @@
1−# @executor/control-plane
2−
3−## null
4−
5−### Patch Changes
6−
7−- @executor/codemode-core@null
8−- @executor/source-google-discovery@null
9−- @executor/source-mcp@null
10−- @executor/source-openapi@null
11−- @executor/runtime-deno-subprocess@null
12−- @executor/runtime-quickjs@null
13−- @executor/runtime-ses@null
packages/platform/control-plane/src/api/local/http.ts+0−255
@@ -1,255 +0,0 @@
1−import {
2− HttpApiBuilder,
3−} from "@effect/platform";
4−import * as Effect from "effect/Effect";
5−import * as Option from "effect/Option";
6−
7−import { SecretMaterialIdSchema } from "#schema";
8−import {
9− getLocalInstallation,
10−} from "../../runtime/local/operations";
11−import { requireRuntimeLocalWorkspace } from "../../runtime/local/runtime-context";
12−import {
13− createDefaultSecretMaterialDeleter,
14− createDefaultSecretMaterialStorer,
15− createDefaultSecretMaterialUpdater,
16− ENV_SECRET_PROVIDER_ID,
17− KEYCHAIN_SECRET_PROVIDER_ID,
18− LOCAL_SECRET_PROVIDER_ID,
19− parseSecretStoreProviderId,
20− resolveDefaultSecretStoreProviderId,
21−} from "../../runtime/local/secret-material-providers";
22−import { RuntimeSourceStoreService } from "../../runtime/sources/source-store";
23−import { ControlPlaneStore } from "../../runtime/store";
24−import type {
25− CreateSecretResult,
26− InstanceConfig,
27− SecretProvider,
28− UpdateSecretResult,
29−} from "./api";
30−
31−import { ControlPlaneApi } from "../api";
32−import {
33− ControlPlaneBadRequestError,
34− ControlPlaneNotFoundError,
35− ControlPlaneStorageError,
36−} from "../errors";
37−
38−const SECRET_STORE_PROVIDER_ENV = "EXECUTOR_SECRET_STORE_PROVIDER";
39−
40−const getInstanceConfig = (): Effect.Effect<InstanceConfig> => {
41− const explicitDefaultStoreProvider =
42− parseSecretStoreProviderId(process.env[SECRET_STORE_PROVIDER_ENV]);
43− const providers: SecretProvider[] = [
44− {
45− id: LOCAL_SECRET_PROVIDER_ID,
46− name: "Local store",
47− canStore: true,
48− },
49− ];
50−
51− if (process.platform === "darwin" || process.platform === "linux") {
52− providers.push({
53− id: KEYCHAIN_SECRET_PROVIDER_ID,
54− name: process.platform === "darwin" ? "macOS Keychain" : "Desktop Keyring",
55− canStore:
56− process.platform === "darwin"
57− || explicitDefaultStoreProvider === KEYCHAIN_SECRET_PROVIDER_ID,
58− });
59− }
60−
61− providers.push({
62− id: ENV_SECRET_PROVIDER_ID,
63− name: "Environment variable",
64− canStore: false,
65− });
66−
67− return resolveDefaultSecretStoreProviderId({
68− storeProviderId: explicitDefaultStoreProvider ?? undefined,
69− }).pipe(
70− Effect.map((resolvedDefaultStoreProvider) => ({
71− platform: process.platform,
72− secretProviders: providers,
73− defaultSecretStoreProvider: resolvedDefaultStoreProvider,
74− })),
75− );
76−};
77−
78−const storageError = (message: string) =>
79− new ControlPlaneStorageError({
80− operation: "secrets",
81− message,
82− details: message,
83− });
84−
85−export const ControlPlaneLocalLive = HttpApiBuilder.group(
86− ControlPlaneApi,
87− "local",
88− (handlers) =>
89− handlers
90− .handle("installation", () =>
91− getLocalInstallation(),
92− )
93− .handle("config", () =>
94− getInstanceConfig(),
95− )
96− .handle("listSecrets", () =>
97− Effect.gen(function* () {
98− const store = yield* ControlPlaneStore;
99− const sourceStore = yield* RuntimeSourceStoreService;
100− const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace().pipe(
101− Effect.mapError(() => storageError("Failed resolving local workspace.")),
102− );
103− const rows = yield* store.secretMaterials.listAll().pipe(
104− Effect.mapError(() => storageError("Failed listing secrets.")),
105− );
106− const linkedSourcesMap = yield* sourceStore.listLinkedSecretSourcesInWorkspace(
107− runtimeLocalWorkspace.installation.workspaceId,
108− {
109− actorAccountId: runtimeLocalWorkspace.installation.accountId,
110− },
111− ).pipe(
112− Effect.mapError(() => storageError("Failed loading linked sources.")),
113− );
114− return rows.map((row) => ({
115− ...row,
116− linkedSources: linkedSourcesMap.get(row.id) ?? [],
117− }));
118− }),
119− )
120− .handle("createSecret", ({ payload }) =>
121− Effect.gen(function* () {
122− const name = payload.name.trim();
123− const value = payload.value;
124− const purpose = payload.purpose ?? "auth_material";
125− const requestedProviderId = payload.providerId === undefined
126− ? null
127− : parseSecretStoreProviderId(payload.providerId);
128−
129− if (name.length === 0) {
130− return yield* new ControlPlaneBadRequestError({
131− operation: "secrets.create",
132− message: "Secret name is required.",
133− details: "Secret name is required.",
134− });
135− }
136− if (payload.providerId !== undefined && requestedProviderId === null) {
137− return yield* new ControlPlaneBadRequestError({
138− operation: "secrets.create",
139− message: `Unsupported secret provider: ${payload.providerId}`,
140− details: `Unsupported secret provider: ${payload.providerId}`,
141− });
142− }
143−
144− const store = yield* ControlPlaneStore;
145− const storeSecretMaterial = createDefaultSecretMaterialStorer({
146− rows: store,
147− ...(requestedProviderId ? { storeProviderId: requestedProviderId } : {}),
148− });
149− const ref = yield* storeSecretMaterial({
150− name,
151− purpose,
152− value,
153− }).pipe(
154− Effect.mapError((cause) => storageError(
155− cause instanceof Error ? cause.message : "Failed creating secret.",
156− )),
157− );
158− const secretId = SecretMaterialIdSchema.make(ref.handle);
159− const created = yield* store.secretMaterials.getById(secretId).pipe(
160− Effect.mapError(() => storageError("Failed loading created secret.")),
161− );
162−
163− if (Option.isNone(created)) {
164− return yield* storageError(`Created secret not found: ${ref.handle}`);
165− }
166−
167− return {
168− id: created.value.id,
169− name: created.value.name,
170− providerId: created.value.providerId,
171− purpose: created.value.purpose,
172− createdAt: created.value.createdAt,
173− updatedAt: created.value.updatedAt,
174− } satisfies CreateSecretResult;
175− }),
176− )
177− .handle("updateSecret", ({ path, payload }) =>
178− Effect.gen(function* () {
179− const secretId = SecretMaterialIdSchema.make(path.secretId);
180− const store = yield* ControlPlaneStore;
181−
182− const existing = yield* store.secretMaterials.getById(secretId).pipe(
183− Effect.mapError(() => storageError("Failed looking up secret.")),
184− );
185−
186− if (Option.isNone(existing)) {
187− return yield* new ControlPlaneNotFoundError({
188− operation: "secrets.update",
189− message: `Secret not found: ${path.secretId}`,
190− details: `Secret not found: ${path.secretId}`,
191− });
192− }
193−
194− const update: { name?: string | null; value?: string } = {};
195− if (payload.name !== undefined) update.name = payload.name.trim() || null;
196− if (payload.value !== undefined) update.value = payload.value;
197−
198− const updateSecretMaterial = createDefaultSecretMaterialUpdater({
199− rows: store,
200− });
201− const updated = yield* updateSecretMaterial({
202− ref: {
203− providerId: existing.value.providerId,
204− handle: existing.value.id,
205− },
206− ...update,
207− }).pipe(
208− Effect.mapError(() => storageError("Failed updating secret.")),
209− );
210−
211− return {
212− id: updated.id,
213− providerId: updated.providerId,
214− name: updated.name,
215− purpose: updated.purpose,
216− createdAt: updated.createdAt,
217− updatedAt: updated.updatedAt,
218− } satisfies UpdateSecretResult;
219− }),
220− )
221− .handle("deleteSecret", ({ path }) =>
222− Effect.gen(function* () {
223− const secretId = SecretMaterialIdSchema.make(path.secretId);
224− const store = yield* ControlPlaneStore;
225−
226− const existing = yield* store.secretMaterials.getById(secretId).pipe(
227− Effect.mapError(() => storageError("Failed looking up secret.")),
228− );
229−
230− if (Option.isNone(existing)) {
231− return yield* new ControlPlaneNotFoundError({
232− operation: "secrets.delete",
233− message: `Secret not found: ${path.secretId}`,
234− details: `Secret not found: ${path.secretId}`,
235− });
236− }
237−
238− const deleteSecretMaterial = createDefaultSecretMaterialDeleter({
239− rows: store,
240− });
241− const removed = yield* deleteSecretMaterial({
242− providerId: existing.value.providerId,
243− handle: existing.value.id,
244− }).pipe(
245− Effect.mapError(() => storageError("Failed removing secret.")),
246− );
247−
248− if (!removed) {
249− return yield* storageError(`Failed removing secret: ${path.secretId}`);
250− }
251−
252− return { removed: true };
253− }),
254− ),
255−);
packages/platform/control-plane/src/index.ts+0−4
@@ -1,4 +0,0 @@
1−export * from "./schema/index";
2−export * from "./api/index";
3−export * from "./runtime/index";
4−export * from "./client";
packages/platform/control-plane/src/runtime/control-plane-runtime.test.ts+0−1991
@@ -1,1991 +0,0 @@
1−import { createServer } from "node:http";
2−import { join } from "node:path";
3−
4−import { FileSystem } from "@effect/platform";
5−import { NodeFileSystem } from "@effect/platform-node";
6−import { describe, expect, it } from "@effect/vitest";
7−import { assertTrue } from "@effect/vitest/utils";
8−import * as Either from "effect/Either";
9−import * as Effect from "effect/Effect";
10−import * as Fiber from "effect/Fiber";
11−import * as Option from "effect/Option";
12−
13−import {
14− ExecutionIdSchema,
15− ExecutionInteractionIdSchema,
16− type ExecutionInteraction,
17− ProviderAuthGrantIdSchema,
18− SecretMaterialIdSchema,
19− SourceAuthSessionIdSchema,
20− SourceIdSchema,
21− WorkspaceOauthClientIdSchema,
22−} from "#schema";
23−import type { ToolPath } from "@executor/codemode-core";
24−import { createCatalogImportMetadata } from "@executor/source-core";
25−import { createGraphqlCatalogFragment } from "@executor/source-graphql";
26−
27−import {
28− type ControlPlaneRuntime,
29− createControlPlaneRuntime,
30− LiveExecutionManagerService,
31− provideControlPlaneRuntime,
32−} from "./index";
33−import { createSourceFromPayload } from "./sources/source-definitions";
34−import { decodeSourceCredentialSelectionContent } from "./sources/source-credential-interactions";
35−import { persistSource } from "./sources/source-store";
36−import { withControlPlaneClient } from "./execution/test-http-client";
37−import { runtimeEffectError } from "./effect-errors";
38−import {
39− resolveLocalWorkspaceContext,
40− writeProjectLocalExecutorConfig,
41−} from "./local/config";
42−import { writeLocalControlPlaneState } from "./local/control-plane-store";
43−import { deriveLocalInstallation } from "./local/installation";
44−import {
45− buildLocalSourceArtifact,
46− readLocalSourceArtifact,
47− writeLocalSourceArtifact,
48−} from "./local/source-artifacts";
49−import { writeLocalWorkspaceState } from "./local/workspace-state";
50−
51−const makeRuntime = Effect.gen(function* () {
52− const fs = yield* FileSystem.FileSystem;
53− const workspaceRoot = yield* fs.makeTempDirectoryScoped({
54− prefix: "executor-control-plane-runtime-",
55− });
56− const homeConfigPath = join(workspaceRoot, ".executor-home.jsonc");
57− const homeStateDirectory = join(workspaceRoot, ".executor-home-state");
58−
59− return yield* Effect.acquireRelease(
60− createControlPlaneRuntime({
61− workspaceRoot,
62− homeConfigPath,
63− homeStateDirectory,
64− }),
65− (runtime) => Effect.promise(() => runtime.close()).pipe(Effect.orDie),
66− );
67−}).pipe(Effect.provide(NodeFileSystem.layer));
68−
69−type OpenApiSpecServer = {
70− baseUrl: string;
71− specUrl: string;
72− close: () => Promise<void>;
73−};
74−
75−type GoogleWorkspaceTestServer = {
76− baseUrl: string;
77− tokenEndpoint: string;
78− discoveryUrl: (input: {
79− service: string;
80− version: string;
81− scope: string;
82− }) => string;
83− queueTokenResponse: (response: {
84− access_token: string;
85− refresh_token?: string;
86− expires_in?: number;
87− scope?: string;
88− }) => void;
89− tokenRequests: URLSearchParams[];
90− discoveryAuthorizations: string[];
91− close: () => Promise<void>;
92−};
93−
94−const makeOpenApiSpecServer = Effect.acquireRelease(
95− Effect.promise<OpenApiSpecServer>(
96− () =>
97− new Promise<OpenApiSpecServer>((resolve, reject) => {
98− const openApiDocument = JSON.stringify({
99− openapi: "3.0.3",
100− info: {
101− title: "GitHub Test API",
102− version: "1.0.0",
103− },
104− paths: {
105− "/repos/{owner}/{repo}": {
106− get: {
107− operationId: "repos/get-repo",
108− tags: ["repos"],
109− summary: "Get a repository",
110− responses: {
111− 200: {
112− description: "ok",
113− },
114− },
115− },
116− },
117− },
118− });
119−
120− const server = createServer((request, response) => {
121− if (request.url !== "/openapi.json") {
122− response.statusCode = 404;
123− response.end();
124− return;
125− }
126−
127− response.statusCode = 200;
128− response.setHeader("content-type", "application/json");
129− response.end(openApiDocument);
130− });
131−
132− server.once("error", reject);
133− server.listen(0, "127.0.0.1", () => {
134− const address = server.address();
135− if (!address || typeof address === "string") {
136− reject(new Error("Failed to bind OpenAPI runtime test server"));
137− return;
138− }
139−
140− const baseUrl = `http://127.0.0.1:${address.port}`;
141− resolve({
142− baseUrl,
143− specUrl: `${baseUrl}/openapi.json`,
144− close: () =>
145− new Promise<void>((closeResolve, closeReject) => {
146− server.close((error) => {
147− if (error) {
148− closeReject(error);
149− return;
150− }
151−
152− closeResolve();
153− });
154− }),
155− });
156− });
157− }),
158− ),
159− (server) => Effect.promise(() => server.close()).pipe(Effect.orDie),
160−);
161−
162−const makeGoogleWorkspaceTestServer = Effect.acquireRelease(
163− Effect.promise<GoogleWorkspaceTestServer>(
164− () =>
165− new Promise<GoogleWorkspaceTestServer>((resolve, reject) => {
166− const discoveryScopes = new Map<string, string>();
167− const tokenResponses: Array<{
168− access_token: string;
169− refresh_token?: string;
170− expires_in?: number;
171− scope?: string;
172− }> = [];
173− const tokenRequests: URLSearchParams[] = [];
174− const discoveryAuthorizations: string[] = [];
175− let baseUrl = "";
176−
177− const server = createServer(async (request, response) => {
178− const requestUrl = new URL(
179− request.url ?? "/",
180− `http://${request.headers.host ?? "127.0.0.1"}`,
181− );
182−
183− if (request.method === "POST" && requestUrl.pathname === "/oauth/token") {
184− const chunks: Buffer[] = [];
185− for await (const chunk of request) {
186− chunks.push(Buffer.from(chunk));
187− }
188−
189− const params = new URLSearchParams(Buffer.concat(chunks).toString("utf8"));
190− tokenRequests.push(params);
191− const nextResponse = tokenResponses.shift();
192− if (!nextResponse) {
193− response.statusCode = 500;
194− response.setHeader("content-type", "application/json");
195− response.end(JSON.stringify({
196− error: "missing_token_response",
197− }));
198− return;
199− }
200−
201− response.statusCode = 200;
202− response.setHeader("content-type", "application/json");
203− response.end(JSON.stringify({
204− token_type: "Bearer",
205− expires_in: 3600,
206− ...nextResponse,
207− }));
208− return;
209− }
210−
211− const discoveryMatch = /^\/([^/]+)\/\$discovery\/rest$/.exec(requestUrl.pathname);
212− if (request.method === "GET" && discoveryMatch) {
213− const service = discoveryMatch[1]!;
214− const version = requestUrl.searchParams.get("version") ?? "v1";
215− const authorizationHeader =
216− typeof request.headers.authorization === "string"
217− ? request.headers.authorization
218− : Array.isArray(request.headers.authorization)
219− ? (request.headers.authorization[0] ?? "")
220− : "";
221− if (authorizationHeader.length > 0) {
222− discoveryAuthorizations.push(authorizationHeader);
223− }
224−
225− const scope = discoveryScopes.get(`${service}:${version}`)
226− ?? `https://example.test/auth/${service}`;
227− response.statusCode = 200;
228− response.setHeader("content-type", "application/json");
229− response.end(JSON.stringify({
230− name: service,
231− version,
232− title: `${service} API`,
233− description: `Test ${service} API`,
234− rootUrl: `${baseUrl}/`,
235− servicePath: `${service}/${version}/`,
236− auth: {
237− oauth2: {
238− scopes: {
239− [scope]: {
240− description: `${service} scope`,
241− },
242− },
243− },
244− },
245− methods: {
246− list: {
247− id: `${service}.list`,
248− path: "items",
249− httpMethod: "GET",
250− response: {
251− $ref: "ListResponse",
252− },
253− },
254− },
255− schemas: {
256− ListResponse: {
257− id: "ListResponse",
258− type: "object",
259− properties: {
260− items: {
261− type: "array",
262− items: {
263− type: "string",
264− },
265− },
266− },
267− },
268− },
269− }));
270− return;
271− }
272−
273− response.statusCode = 404;
274− response.end();
275− });
276−
277− server.once("error", reject);
278− server.listen(0, "127.0.0.1", () => {
279− const address = server.address();
280− if (!address || typeof address === "string") {
281− reject(new Error("Failed to bind Google workspace test server"));
282− return;
283− }
284−
285− baseUrl = `http://127.0.0.1:${address.port}`;
286− resolve({
287− baseUrl,
288− tokenEndpoint: `${baseUrl}/oauth/token`,
289− discoveryUrl: ({ service, version, scope }) => {
290− discoveryScopes.set(`${service}:${version}`, scope);
291− return `${baseUrl}/${service}/$discovery/rest?version=${encodeURIComponent(version)}`;
292− },
293− queueTokenResponse: (tokenResponse) => {
294− tokenResponses.push(tokenResponse);
295− },
296− tokenRequests,
297− discoveryAuthorizations,
298− close: () =>
299− new Promise<void>((closeResolve, closeReject) => {
300− server.close((error) => {
301− if (error) {
302− closeReject(error);
303− return;
304− }
305−
306− closeResolve();
307− });
308− }),
309− });
310− });
311− }),
312− ),
313− (server) => Effect.promise(() => server.close()).pipe(Effect.orDie),
314−);
315−
316−const upsertLocalSecret = (runtime: ControlPlaneRuntime, input: {
317− id: string;
318− name: string;
319− purpose: "oauth_refresh_token" | "oauth_client_info" | "oauth_access_token";
320− value: string;
321−}) =>
322− runtime.persistence.rows.secretMaterials.upsert({
323− id: SecretMaterialIdSchema.make(input.id),
324− providerId: "local",
325− handle: input.id,
326− name: input.name,
327− purpose: input.purpose,
328− value: input.value,
329− createdAt: Date.now(),
330− updatedAt: Date.now(),
331− });
332−
333−const createPersistedGoogleSource = (input: {
334− runtime: ControlPlaneRuntime;
335− name: string;
336− namespace: string;
337− service: string;
338− version: string;
339− discoveryUrl: string;
340− scopes: ReadonlyArray<string>;
341− auth: {
342− kind: "none";
343− } | {
344− kind: "provider_grant_ref";
345− grantId: ReturnType<typeof ProviderAuthGrantIdSchema.make>;
346− providerKey: string;
347− requiredScopes: ReadonlyArray<string>;
348− headerName: string;
349− prefix: string;
350− };
351−}) =>
352− Effect.gen(function* () {
353− const installation = input.runtime.localInstallation;
354− const source = yield* createSourceFromPayload({
355− workspaceId: installation.workspaceId,
356− sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),
357− payload: {
358− name: input.name,
359− kind: "google_discovery",
360− endpoint: input.discoveryUrl,
361− namespace: input.namespace,
362− binding: {
363− service: input.service,
364− version: input.version,
365− discoveryUrl: input.discoveryUrl,
366− scopes: [...input.scopes],
367− },
368− importAuthPolicy: "reuse_runtime",
369− importAuth: { kind: "none" },
370− auth: input.auth as any,
371− status: "connected",
372− enabled: true,
373− },
374− now: Date.now(),
375− });
376−
377− return yield* persistSource(input.runtime.persistence.rows, source, {
378− actorAccountId: installation.accountId,
379− }).pipe((effect) => provideControlPlaneRuntime(effect, input.runtime));
380− });
381−
382−const expectLeft = <A, E>(effect: Effect.Effect<A, E, never>) =>
383− Effect.either(effect).pipe(
384− Effect.flatMap((result) =>
385− Either.isLeft(result)
386− ? Effect.succeed(result.left)
387− : Effect.fail(runtimeEffectError("control-plane-runtime.test", "Expected effect to fail")),
388− ),
389− );
390−
391−describe("control-plane-runtime", () => {
392− it.scoped("writes local source changes through executor.jsonc", () =>
393− Effect.gen(function* () {
394− const fs = yield* FileSystem.FileSystem;
395− const workspaceRoot = yield* fs.makeTempDirectoryScoped({
396− prefix: "executor-local-config-runtime-",
397− });
398− const homeConfigPath = join(workspaceRoot, ".executor-home.jsonc");
399− const homeStateDirectory = join(workspaceRoot, ".executor-home-state");
400− const runtime = yield* Effect.acquireRelease(
401− createControlPlaneRuntime({
402− workspaceRoot,
403− homeConfigPath,
404− homeStateDirectory,
405− }),
406− (createdRuntime) => Effect.promise(() => createdRuntime.close()).pipe(Effect.orDie),
407− );
408− const openApiServer = yield* makeOpenApiSpecServer;
409− const installation = runtime.localInstallation;
410−
411− const createdSource = yield* withControlPlaneClient(
412− { runtime, accountId: installation.accountId },
413− (client) =>
414− client.sources.create({
415− path: { workspaceId: installation.workspaceId },
416− payload: {
417− name: "GitHub",
418− kind: "openapi",
419− endpoint: openApiServer.baseUrl,
420− namespace: "github",
421− binding: {
422− specUrl: openApiServer.specUrl,
423− defaultHeaders: null,
424− },
425− auth: { kind: "none" },
426− },
427− }),
428− );
429−
430− const configPath = join(workspaceRoot, ".executor", "executor.jsonc");
431− const createdConfig = JSON.parse(yield* fs.readFileString(configPath, "utf8")) as {
432− sources?: Record<string, { kind: string; connection: { endpoint: string } }>;
433− };
434− expect(createdConfig.sources?.github?.kind).toBe("openapi");
435− expect(createdConfig.sources?.github?.connection.endpoint).toBe(openApiServer.baseUrl);
436−
437− const removed = yield* withControlPlaneClient(
438− { runtime, accountId: installation.accountId },
439− (client) =>
440− client.sources.remove({
441− path: {
442− workspaceId: installation.workspaceId,
443− sourceId: createdSource.id,
444− },
445− }),
446− );
447− expect(removed.removed).toBe(true);
448−
449− const removedConfig = JSON.parse(yield* fs.readFileString(configPath, "utf8")) as {
450− sources?: Record<string, unknown>;
451− };
452− expect(removedConfig.sources?.github).toBeUndefined();
453− }).pipe(Effect.provide(NodeFileSystem.layer)),
454− 60_000,
455− );
456−
457− it.scoped("manages local secrets through provider-backed metadata rows", () =>
458− Effect.gen(function* () {
459− const runtime = yield* makeRuntime;
460−
461− const created = yield* withControlPlaneClient(
462− { runtime },
463− (client) =>
464− client.local.createSecret({
465− payload: {
466− name: "GitHub PAT",
467− value: "ghp_test_token",
468− providerId: "local",
469− },
470− }),
471− );
472−
473− expect(created.providerId).toBe("local");
474−
475− const listed = yield* withControlPlaneClient(
476− { runtime },
477− (client) => client.local.listSecrets({}),
478− );
479− expect(listed).toEqual([
480− expect.objectContaining({
481− id: created.id,
482− providerId: "local",
483− name: "GitHub PAT",
484− }),
485− ]);
486−
487− const updated = yield* withControlPlaneClient(
488− { runtime },
489− (client) =>
490− client.local.updateSecret({
491− path: { secretId: created.id },
492− payload: {
493− name: "GitHub PAT Updated",
494− value: "ghp_test_token_v2",
495− },
496− }),
497− );
498− expect(updated.providerId).toBe("local");
499− expect(updated.name).toBe("GitHub PAT Updated");
500−
501− const storedSecret = yield* runtime.persistence.rows.secretMaterials.getById(
502− SecretMaterialIdSchema.make(created.id),
503− );
504− assertTrue(Option.isSome(storedSecret));
505− expect(storedSecret.value.providerId).toBe("local");
506− expect(storedSecret.value.value).toBe("ghp_test_token_v2");
507−
508− const removed = yield* withControlPlaneClient(
509− { runtime },
510− (client) =>
511− client.local.deleteSecret({
512− path: { secretId: created.id },
513− }),
514− );
515− expect(removed.removed).toBe(true);
516− }),
517− );
518−
519− it.scoped("reports safe secret storage options for the current platform", () =>
520− Effect.gen(function* () {
521− const runtime = yield* makeRuntime;
522−
523− const config = yield* withControlPlaneClient(
524− { runtime },
525− (client) => client.local.config({}),
526− );
527−
528− const keychainProvider =
529− config.secretProviders.find((provider) => provider.id === "keychain") ?? null;
530−
531− if (process.platform === "linux") {
532− expect(config.defaultSecretStoreProvider).toBe("local");
533− expect(keychainProvider?.canStore).toBe(false);
534− return;
535− }
536−
537− if (process.platform === "darwin") {
538− expect(keychainProvider?.canStore).toBe(true);
539− return;
540− }
541−
542− expect(keychainProvider).toBeNull();
543− }),
544− );
545−
546− it.scoped("captures credential requests through the local HTML flow without persisting raw tokens", () =>
547− Effect.gen(function* () {
548− const runtime = yield* makeRuntime;
549− const installation = runtime.localInstallation;
550− const executionId = ExecutionIdSchema.make("exec_local_credential");
551− const sourceId = SourceIdSchema.make("github");
552− const interactionSuffix = "executor.sources.add:test";
553− const interactionId = ExecutionInteractionIdSchema.make(
554− `${executionId}:${interactionSuffix}`,
555− );
556− const now = Date.now();
557−
558− yield* runtime.persistence.rows.executions.insert({
559− id: executionId,
560− workspaceId: installation.workspaceId,
561− createdByAccountId: installation.accountId,
562− status: "running",
563− code: "return await tools.executor.sources.add(...)",
564− resultJson: null,
565− errorText: null,
566− logsJson: null,
567− startedAt: now,
568− completedAt: null,
569− createdAt: now,
570− updatedAt: now,
571− });
572−
573− const localSource = yield* createSourceFromPayload({
574− workspaceId: installation.workspaceId,
575− sourceId,
576− payload: {
577− name: "GitHub",
578− kind: "openapi",
579− endpoint: "https://api.github.com",
580− status: "auth_required",
581− enabled: true,
582− namespace: "github",
583− importAuthPolicy: "reuse_runtime",
584− binding: {
585− specUrl: "https://example.com/github-openapi.yaml",
586− defaultHeaders: null,
587− },
588− importAuth: { kind: "none" },
589− auth: { kind: "none" },
590− },
591− now,
592− }).pipe(Effect.orDie);
593− yield* persistSource(runtime.persistence.rows, localSource, {
594− actorAccountId: installation.accountId,
595− }).pipe((effect) => provideControlPlaneRuntime(effect, runtime), Effect.orDie);
596−
597− const interactionFiber = yield* Effect.gen(function* () {
598− const liveExecutionManager = yield* LiveExecutionManagerService;
599− const onElicitation = liveExecutionManager.createOnElicitation({
600− rows: runtime.persistence.rows,
601− executionId,
602− });
603−
604− return yield* onElicitation({
605− interactionId: interactionSuffix,
606− path: "executor.sources.add" as ToolPath,
607− sourceKey: "executor",
608− args: {
609− kind: "openapi",
610− endpoint: "https://api.github.com",
611− specUrl: "https://example.com/github-openapi.yaml",
612− name: "GitHub",
613− workspaceId: installation.workspaceId,
614− sourceId,
615− },
616− elicitation: {
617− mode: "url",
618− message: "Open the secure credential page to connect GitHub",
619− url: `http://127.0.0.1/v1/workspaces/${encodeURIComponent(installation.workspaceId)}/sources/${encodeURIComponent(sourceId)}/credentials?interactionId=${encodeURIComponent(interactionId)}`,
620− elicitationId: interactionSuffix,
621− },
622− });
623− }).pipe(
624− (effect) => provideControlPlaneRuntime(effect, runtime),
625− Effect.fork,
626− );
627−
628− const waitForPendingInteraction = (
629− remaining: number,
630− ): Effect.Effect<Option.Option<ExecutionInteraction>, Error> =>
631− runtime.persistence.rows.executionInteractions.getPendingByExecutionId(executionId).pipe(
632− Effect.flatMap((pendingInteraction) =>
633− Option.isSome(pendingInteraction) || remaining <= 0
634− ? Effect.succeed(pendingInteraction)
635− : Effect.yieldNow().pipe(
636− Effect.zipRight(waitForPendingInteraction(remaining - 1)),
637− ),
638− ),
639− );
640−
641− const pendingInteraction = yield* waitForPendingInteraction(20);
642− assertTrue(Option.isSome(pendingInteraction));
643− expect(pendingInteraction.value.id).toBe(interactionId);
644−
645− const page = yield* withControlPlaneClient(
646− { runtime },
647− (client) =>
648− client.sources.credentialPage({
649− path: {
650− workspaceId: installation.workspaceId,
651− sourceId,
652− },
653− urlParams: {
654− interactionId: pendingInteraction.value.id,
655− },
656− }),
657− );
658− expect(page).toContain("Configure Source Access");
659− expect(page).toContain("GitHub");
660− expect(page).toContain("Continue without auth");
661−
662− const submittedPage = yield* withControlPlaneClient(
663− { runtime },
664− (client) =>
665− client.sources.credentialSubmit({
666− path: {
667− workspaceId: installation.workspaceId,
668− sourceId,
669− },
670− urlParams: {
671− interactionId: pendingInteraction.value.id,
672− },
673− payload: {
674− action: "submit",
675− token: "ghp_local_test_token",
676− },
677− }),
678− );
679− expect(submittedPage).toContain("Credential Stored");
680−
681− const response = yield* Fiber.join(interactionFiber);
682− expect(response.action).toBe("accept");
683− expect(response.content?.authKind).toBe("bearer");
684−
685− const decodedCredentialSelection = decodeSourceCredentialSelectionContent(
686− response.content,
687− );
688− expect(decodedCredentialSelection.authKind).toBe("bearer");
689− expect(
690− decodedCredentialSelection.authKind === "bearer"
691− ? decodedCredentialSelection.tokenRef.providerId
692− : null,
693− ).toBe("local");
694−
695− const tokenSecretMaterialId = SecretMaterialIdSchema.make(
696− decodedCredentialSelection.authKind === "bearer"
697− ? decodedCredentialSelection.tokenRef.handle
698− : "",
699− );
700− const storedSecret = yield* runtime.persistence.rows.secretMaterials.getById(
701− tokenSecretMaterialId,
702− );
703− assertTrue(Option.isSome(storedSecret));
704− expect(storedSecret.value.value).toBe("ghp_local_test_token");
705−
706− const storedInteraction = yield* runtime.persistence.rows.executionInteractions.getById(
707− pendingInteraction.value.id,
708− );
709− assertTrue(Option.isSome(storedInteraction));
710− expect(storedInteraction.value.responseJson).toContain("\"authKind\":\"bearer\"");
711− expect(storedInteraction.value.responseJson).not.toContain("tokenRef");
712− expect(storedInteraction.value.responseJson).not.toContain("ghp_local_test_token");
713− }),
714− );
715−
716− it.scoped("loads a v1.2.3-style workspace artifact on startup", () =>
717− Effect.gen(function* () {
718− const fs = yield* FileSystem.FileSystem;
719− const workspaceRoot = yield* fs.makeTempDirectoryScoped({
720− prefix: "executor-control-plane-runtime-v123-",
721− });
722− const homeConfigPath = join(workspaceRoot, ".executor-home.jsonc");
723− const homeStateDirectory = join(workspaceRoot, ".executor-home-state");
724− const configDirectory = join(workspaceRoot, ".executor");
725− const configPath = join(configDirectory, "executor.jsonc");
726− const sourceId = SourceIdSchema.make("graphql");
727− const now = Date.now();
728−
729− yield* fs.makeDirectory(configDirectory, { recursive: true });
730− yield* fs.writeFileString(
731− configPath,
732− `{
733− "sources": {
734− "graphql": {
735− "kind": "graphql",
736− "name": "GraphQL API",
737− "namespace": "graphql",
738− "connection": {
739− "endpoint": "https://example.com/graphql"
740− },
741− "binding": {
742− "defaultHeaders": null
743− }
744− }
745− }
746−}
747−`,
748− );
749−
750− const context = yield* resolveLocalWorkspaceContext({
751− workspaceRoot,
752− homeConfigPath,
753− homeStateDirectory,
754− });
755− const installation = deriveLocalInstallation(context);
756− const source = yield* createSourceFromPayload({
757− workspaceId: installation.workspaceId,
758− sourceId,
759− payload: {
760− name: "GraphQL API",
761− kind: "graphql",
762− endpoint: "https://example.com/graphql",
763− status: "connected",
764− enabled: true,
765− namespace: "graphql",
766− importAuthPolicy: "reuse_runtime",
767− binding: {
768− defaultHeaders: null,
769− },
770− importAuth: { kind: "none" },
771− auth: { kind: "none" },
772− },
773− now,
774− }).pipe(Effect.orDie);
775−
776− const artifact = buildLocalSourceArtifact({
777− source,
778− syncResult: {
779− fragment: createGraphqlCatalogFragment({
780− source,
781− documents: [
782− {
783− documentKind: "graphql_introspection",
784− documentKey: source.endpoint,
785− contentText: '{"__schema":{}}',
786− fetchedAt: now,
787− },
788− ],
789− operations: [
790− {
791− toolId: "viewer",
792− title: "Viewer",
793− description: "Load the current viewer",
794− effect: "read",
795− inputSchema: { type: "object", properties: {} },
796− outputSchema: {
797− type: "object",
798− properties: { login: { type: "string" } },
799− },
800− providerData: {
801− kind: "graphql",
802− toolKind: "field",
803− toolId: "viewer",
804− rawToolId: "viewer",
805− group: "query",
806− leaf: "viewer",
807− fieldName: "viewer",
808− operationType: "query",
809− operationName: "ViewerQuery",
810− operationDocument: "query ViewerQuery { viewer { login } }",
811− queryTypeName: "Query",
812− mutationTypeName: null,
813− subscriptionTypeName: null,
814− },
815− },
816− ],
817− }),
818− importMetadata: createCatalogImportMetadata({
819− source,
820− adapterKey: "graphql",
821− }),
822− sourceHash: source.sourceHash,
823− },
824− });
825−
826− yield* writeLocalWorkspaceState({
827− context,
828− state: {
829− version: 1,
830− sources: {
831− [sourceId]: {
832− status: source.status,
833− lastError: source.lastError,
834− sourceHash: source.sourceHash,
835− createdAt: source.createdAt,
836− updatedAt: source.updatedAt,
837− },
838− },
839− policies: {},
840− },
841− });
842− yield* writeLocalControlPlaneState({
843− context,
844− state: {
845− version: 1,
846− authArtifacts: [],
847− authLeases: [],
848− sourceOauthClients: [],
849− workspaceOauthClients: [],
850− providerAuthGrants: [],
851− sourceAuthSessions: [],
852− secretMaterials: [],
853− executions: [],
854− executionInteractions: [],
855− executionSteps: [],
856− },
857− });
858− yield* writeLocalSourceArtifact({
859− context,
860− sourceId,
861− artifact,
862− });
863−
864− const artifactPath = join(context.artifactsDirectory, "sources", `${sourceId}.json`);
865− const legacyArtifact = JSON.parse(
866− yield* fs.readFileString(artifactPath, "utf8"),
867− ) as Record<string, unknown>;
868− legacyArtifact.version = 3;
869− yield* fs.writeFileString(artifactPath, `${JSON.stringify(legacyArtifact)}\n`);
870−
871− const runtime = yield* Effect.acquireRelease(
872− createControlPlaneRuntime({
873− workspaceRoot,
874− homeConfigPath,
875− homeStateDirectory,
876− }),
877− (createdRuntime) => Effect.promise(() => createdRuntime.close()).pipe(Effect.orDie),
878− );
879−
880− const inspection = yield* withControlPlaneClient(
881− { runtime, accountId: installation.accountId },
882− (client) =>
883− client.sources.inspection({
884− path: {
885− workspaceId: installation.workspaceId,
886− sourceId,
887− },
888− }),
889− );
890−
891− expect(inspection.source.id).toBe(sourceId);
892− expect(inspection.source.kind).toBe("graphql");
893− expect(inspection.toolCount).toBe(1);
894− expect(inspection.tools.map((tool) => tool.path)).toContain("graphql.viewer");
895− }).pipe(Effect.provide(NodeFileSystem.layer)),
896− );
897−
898− it.scoped("rebuilds missing connected source artifacts on startup", () =>
899− Effect.gen(function* () {
900− const fs = yield* FileSystem.FileSystem;
901− const openApiServer = yield* makeOpenApiSpecServer;
902− const workspaceRoot = yield* fs.makeTempDirectoryScoped({
903− prefix: "executor-control-plane-runtime-startup-rebuild-",
904− });
905− const homeConfigPath = join(workspaceRoot, ".executor-home.jsonc");
906− const homeStateDirectory = join(workspaceRoot, ".executor-home-state");
907− const context = yield* resolveLocalWorkspaceContext({
908− workspaceRoot,
909− homeConfigPath,
910− homeStateDirectory,
911− });
912− const installation = deriveLocalInstallation(context);
913− const sourceId = SourceIdSchema.make("github");
914− const now = Date.now();
915− const source = yield* createSourceFromPayload({
916− workspaceId: installation.workspaceId,
917− sourceId,
918− payload: {
919− name: "GitHub",
920− kind: "openapi",
921− endpoint: openApiServer.baseUrl,
922− status: "connected",
923− enabled: true,
924− namespace: "github",
925− importAuthPolicy: "reuse_runtime",
926− binding: {
927− specUrl: openApiServer.specUrl,
928− defaultHeaders: null,
929− },
930− importAuth: { kind: "none" },
931− auth: { kind: "none" },
932− },
933− now,
934− }).pipe(Effect.orDie);
935−
936− yield* writeProjectLocalExecutorConfig({
937− context,
938− config: {
939− sources: {
940− [sourceId]: {
941− kind: "openapi",
942− name: "GitHub",
943− namespace: "github",
944− connection: {
945− endpoint: openApiServer.baseUrl,
946− },
947− binding: {
948− specUrl: openApiServer.specUrl,
949− defaultHeaders: null,
950− },
951− },
952− },
953− },
954− });
955− yield* writeLocalWorkspaceState({
956− context,
957− state: {
958− version: 1,
959− sources: {
960− [sourceId]: {
961− status: source.status,
962− lastError: source.lastError,
963− sourceHash: source.sourceHash,
964− createdAt: source.createdAt,
965− updatedAt: source.updatedAt,
966− },
967− },
968− policies: {},
969− },
970− });
971−
972− const runtime = yield* Effect.acquireRelease(
973− createControlPlaneRuntime({
974− workspaceRoot,
975− homeConfigPath,
976− homeStateDirectory,
977− }),
978− (createdRuntime) => Effect.promise(() => createdRuntime.close()).pipe(Effect.orDie),
979− );
980−
981− const rebuiltArtifact = yield* readLocalSourceArtifact({
982− context,
983− sourceId,
984− }).pipe(Effect.provide(NodeFileSystem.layer));
985− expect(rebuiltArtifact).not.toBeNull();
986−
987− const inspection = yield* withControlPlaneClient(
988− { runtime, accountId: installation.accountId },
989− (client) =>
990− client.sources.inspection({
991− path: {
992− workspaceId: installation.workspaceId,
993− sourceId,
994− },
995− }),
996− );
997− expect(inspection.tools.length).toBeGreaterThan(0);
998− }).pipe(Effect.provide(NodeFileSystem.layer)),
999− 60_000,
1000− );
1001−
1002− it.scoped("returns an empty inspection bundle for auth-required sources without a catalog artifact", () =>
1003− Effect.gen(function* () {
1004− const runtime = yield* makeRuntime;
1005− const installation = runtime.localInstallation;
1006− const sourceId = SourceIdSchema.make("googleapis");
1007− const now = Date.now();
1008−
1009− const localSource = yield* createSourceFromPayload({
1010− workspaceId: installation.workspaceId,
1011− sourceId,
1012− payload: {
1013− name: "Google Drive",
1014− kind: "google_discovery",
1015− endpoint: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest",
1016− status: "auth_required",
1017− enabled: true,
1018− namespace: "googleapis",
1019− importAuthPolicy: "reuse_runtime",
1020− binding: {
1021− service: "drive",
1022− version: "v3",
1023− discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest",
1024− scopes: ["https://www.googleapis.com/auth/drive.readonly"],
1025− },
1026− importAuth: { kind: "none" },
1027− auth: { kind: "none" },
1028− },
1029− now,
1030− }).pipe(Effect.orDie);
1031− yield* persistSource(runtime.persistence.rows, localSource, {
1032− actorAccountId: installation.accountId,
1033− }).pipe((effect) => provideControlPlaneRuntime(effect, runtime), Effect.orDie);
1034−
1035− const inspection = yield* withControlPlaneClient(
1036− { runtime, accountId: installation.accountId },
1037− (client) =>
1038− client.sources.inspection({
1039− path: {
1040− workspaceId: installation.workspaceId,
1041− sourceId,
1042− },
1043− }),
1044− );
1045−
1046− expect(inspection.source.id).toBe(sourceId);
1047− expect(inspection.source.status).toBe("auth_required");
1048− expect(inspection.toolCount).toBe(0);
1049− expect(inspection.tools).toEqual([]);
1050− }),
1051− );
1052−
1053− it.scoped("still fails inspection when a connected source is missing its catalog artifact", () =>
1054− Effect.gen(function* () {
1055− const runtime = yield* makeRuntime;
1056− const installation = runtime.localInstallation;
1057− const sourceId = SourceIdSchema.make("googleapis");
1058− const now = Date.now();
1059−
1060− const localSource = yield* createSourceFromPayload({
1061− workspaceId: installation.workspaceId,
1062− sourceId,
1063− payload: {
1064− name: "Google Drive",
1065− kind: "google_discovery",
1066− endpoint: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest",
1067− status: "connected",
1068− enabled: true,
1069− namespace: "googleapis",
1070− importAuthPolicy: "reuse_runtime",
1071− binding: {
1072− service: "drive",
1073− version: "v3",
1074− discoveryUrl: "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest",
1075− scopes: ["https://www.googleapis.com/auth/drive.readonly"],
1076− },
1077− importAuth: { kind: "none" },
1078− auth: { kind: "none" },
1079− },
1080− now,
1081− }).pipe(Effect.orDie);
1082− yield* persistSource(runtime.persistence.rows, localSource, {
1083− actorAccountId: installation.accountId,
1084− }).pipe((effect) => provideControlPlaneRuntime(effect, runtime), Effect.orDie);
1085−
1086− const error = yield* expectLeft(
1087− withControlPlaneClient(
1088− { runtime, accountId: installation.accountId },
1089− (client) =>
1090− client.sources.inspection({
1091− path: {
1092− workspaceId: installation.workspaceId,
1093− sourceId,
1094− },
1095− }),
1096− ),
1097− );
1098−
1099− expect(error.message).toContain("Catalog artifact missing for source googleapis");
1100− }),
1101− );
1102−
1103− it.scoped("allows continuing an OpenAPI source credential request without auth", () =>
1104− Effect.gen(function* () {
1105− const runtime = yield* makeRuntime;
1106− const installation = runtime.localInstallation;
1107− const executionId = ExecutionIdSchema.make("exec_local_credential_continue");
1108− const sourceId = SourceIdSchema.make("github");
1109− const interactionSuffix = "executor.sources.add:continue";
1110− const interactionId = ExecutionInteractionIdSchema.make(
1111− `${executionId}:${interactionSuffix}`,
1112− );
1113− const now = Date.now();
1114−
1115− yield* runtime.persistence.rows.executions.insert({
1116− id: executionId,
1117− workspaceId: installation.workspaceId,
1118− createdByAccountId: installation.accountId,
1119− status: "running",
1120− code: "return await tools.executor.sources.add(...)",
1121− resultJson: null,
1122− errorText: null,
1123− logsJson: null,
1124− startedAt: now,
1125− completedAt: null,
1126− createdAt: now,
1127− updatedAt: now,
1128− });
1129−
1130− const localSource = yield* createSourceFromPayload({
1131− workspaceId: installation.workspaceId,
1132− sourceId,
1133− payload: {
1134− name: "GitHub",
1135− kind: "openapi",
1136− endpoint: "https://api.github.com",
1137− status: "auth_required",
1138− enabled: true,
1139− namespace: "github",
1140− importAuthPolicy: "reuse_runtime",
1141− binding: {
1142− specUrl: "https://example.com/github-openapi.yaml",
1143− defaultHeaders: null,
1144− },
1145− importAuth: { kind: "none" },
1146− auth: { kind: "none" },
1147− },
1148− now,
1149− }).pipe(Effect.orDie);
1150− yield* persistSource(runtime.persistence.rows, localSource, {
1151− actorAccountId: installation.accountId,
1152− }).pipe((effect) => provideControlPlaneRuntime(effect, runtime), Effect.orDie);
1153−
1154− const interactionFiber = yield* Effect.gen(function* () {
1155− const liveExecutionManager = yield* LiveExecutionManagerService;
1156− const onElicitation = liveExecutionManager.createOnElicitation({
1157− rows: runtime.persistence.rows,
1158− executionId,
1159− });
1160−
1161− return yield* onElicitation({
1162− interactionId: interactionSuffix,
1163− path: "executor.sources.add" as ToolPath,
1164− sourceKey: "executor",
1165− args: {
1166− kind: "openapi",
1167− endpoint: "https://api.github.com",
1168− specUrl: "https://example.com/github-openapi.yaml",
1169− name: "GitHub",
1170− workspaceId: installation.workspaceId,
1171− sourceId,
1172− },
1173− elicitation: {
1174− mode: "url",
1175− message: "Open the secure credential page to connect GitHub",
1176− url: `http://127.0.0.1/v1/workspaces/${encodeURIComponent(installation.workspaceId)}/sources/${encodeURIComponent(sourceId)}/credentials?interactionId=${encodeURIComponent(interactionId)}`,
1177− elicitationId: interactionSuffix,
1178− },
1179− });
1180− }).pipe(
1181− (effect) => provideControlPlaneRuntime(effect, runtime),
1182− Effect.fork,
1183− );
1184−
1185− const waitForPendingInteraction = (
1186− remaining: number,
1187− ): Effect.Effect<Option.Option<ExecutionInteraction>, Error> =>
1188− runtime.persistence.rows.executionInteractions.getPendingByExecutionId(executionId).pipe(
1189− Effect.flatMap((pendingInteraction) =>
1190− Option.isSome(pendingInteraction) || remaining <= 0
1191− ? Effect.succeed(pendingInteraction)
1192− : Effect.yieldNow().pipe(
1193− Effect.zipRight(waitForPendingInteraction(remaining - 1)),
1194− ),
1195− ),
1196− );
1197−
1198− const pendingInteraction = yield* waitForPendingInteraction(20);
1199− assertTrue(Option.isSome(pendingInteraction));
1200−
1201− const submittedPage = yield* withControlPlaneClient(
1202− { runtime },
1203− (client) =>
1204− client.sources.credentialSubmit({
1205− path: {
1206− workspaceId: installation.workspaceId,
1207− sourceId,
1208− },
1209− urlParams: {
1210− interactionId: pendingInteraction.value.id,
1211− },
1212− payload: {
1213− action: "continue",
1214− },
1215− }),
1216− );
1217−
1218− expect(submittedPage).toContain("Continuing without auth");
1219−
1220− const response = yield* Fiber.join(interactionFiber);
1221− expect(response.action).toBe("accept");
1222− expect(response.content).toEqual({
1223− authKind: "none",
1224− });
1225−
1226− const storedInteraction = yield* runtime.persistence.rows.executionInteractions.getById(
1227− pendingInteraction.value.id,
1228− );
1229− assertTrue(Option.isSome(storedInteraction));
1230− expect(storedInteraction.value.responseJson).toContain("\"authKind\":\"none\"");
1231− expect(storedInteraction.value.responseJson).not.toContain("tokenRef");
1232− }),
1233− );
1234−
1235− it.scoped("reuses existing Google provider grants for single connects and clears orphan state", () =>
1236− Effect.gen(function* () {
1237− const runtime = yield* makeRuntime;
1238− const googleServer = yield* makeGoogleWorkspaceTestServer;
1239− const installation = runtime.localInstallation;
1240−
1241− const oauthClient = yield* withControlPlaneClient(
1242− { runtime },
1243− (client) =>
1244− client.sources.createWorkspaceOauthClient({
1245− path: {
1246− workspaceId: installation.workspaceId,
1247− },
1248− payload: {
1249− providerKey: "google_workspace",
1250− label: "Local Google Workspace Client",
1251− oauthClient: {
1252− clientId: "google-client-id",
1253− clientSecret: "google-client-secret",
1254− },
1255− },
1256− }),
1257− );
1258−
1259− const refreshSecretId = "sec_google_refresh_single_reuse";
1260− yield* upsertLocalSecret(runtime, {
1261− id: refreshSecretId,
1262− name: "Google Refresh Token",
1263− purpose: "oauth_refresh_token",
1264− value: "refresh-token-reuse",
1265− });
1266−
1267− const grantId = ProviderAuthGrantIdSchema.make(`provider_grant_${crypto.randomUUID()}`);
1268− yield* runtime.persistence.rows.providerAuthGrants.upsert({
1269− id: grantId,
1270− workspaceId: installation.workspaceId,
1271− actorAccountId: installation.accountId,
1272− providerKey: "google_workspace",
1273− oauthClientId: oauthClient.id,
1274− tokenEndpoint: googleServer.tokenEndpoint,
1275− clientAuthentication: "client_secret_post",
1276− headerName: "Authorization",
1277− prefix: "Bearer ",
1278− refreshToken: {
1279− providerId: "local",
1280− handle: refreshSecretId,
1281− },
1282− grantedScopes: [
1283− "scope:drive.readonly",
1284− ],
1285− lastRefreshedAt: null,
1286− orphanedAt: 123,
1287− createdAt: Date.now(),
1288− updatedAt: Date.now(),
1289− });
1290−
1291− googleServer.queueTokenResponse({
1292− access_token: "reuse-access-token",
1293− scope: "scope:drive.readonly",
1294− });
1295−
1296− const result = yield* withControlPlaneClient(
1297− { runtime },
1298− (client) =>
1299− client.sources.connect({
1300− path: {
1301− workspaceId: installation.workspaceId,
1302− },
1303− payload: {
1304− kind: "google_discovery",
1305− workspaceOauthClientId: oauthClient.id,
1306− service: "drive",
1307− version: "v3",
1308− discoveryUrl: googleServer.discoveryUrl({
1309− service: "drive",
1310− version: "v3",
1311− scope: "scope:drive.readonly",
1312− }),
1313− scopes: ["scope:drive.readonly"],
1314− name: "Drive",
1315− namespace: "google.drive",
1316− },
1317− }),
1318− );
1319−
1320− expect(result.kind).toBe("connected");
1321− if (result.kind !== "connected") {
1322− throw new Error(`Expected connected result, received ${result.kind}`);
1323− }
1324− expect(result.source.auth).toEqual({
1325− kind: "provider_grant_ref",
1326− grantId,
1327− providerKey: "google_workspace",
1328− requiredScopes: ["scope:drive.readonly"],
1329− headerName: "Authorization",
1330− prefix: "Bearer ",
1331− });
1332−
1333− const storedGrant = yield* runtime.persistence.rows.providerAuthGrants.getById(grantId);
1334− assertTrue(Option.isSome(storedGrant));
1335− expect(storedGrant.value.orphanedAt).toBeNull();
1336− expect(googleServer.discoveryAuthorizations).toContain("Bearer reuse-access-token");
1337− }),
1338− 60_000,
1339− );
1340−
1341− it.scoped("uses the app callback redirect for browser-started Google single-source OAuth", () =>
1342− Effect.gen(function* () {
1343− const runtime = yield* makeRuntime;
1344− const googleServer = yield* makeGoogleWorkspaceTestServer;
1345− const installation = runtime.localInstallation;
1346−
1347− const oauthClient = yield* withControlPlaneClient(
1348− { runtime },
1349− (client) =>
1350− client.sources.createWorkspaceOauthClient({
1351− path: {
1352− workspaceId: installation.workspaceId,
1353− },
1354− payload: {
1355− providerKey: "google_workspace",
1356− label: "Local Google Workspace Client",
1357− oauthClient: {
1358− clientId: "google-client-id",
1359− clientSecret: "google-client-secret",
1360− },
1361− },
1362− }),
1363− );
1364−
1365− const result = yield* withControlPlaneClient(
1366− { runtime },
1367− (client) =>
1368− client.sources.connect({
1369− path: {
1370− workspaceId: installation.workspaceId,
1371− },
1372− payload: {
1373− kind: "google_discovery",
1374− workspaceOauthClientId: oauthClient.id,
1375− service: "gmail",
1376− version: "v1",
1377− discoveryUrl: googleServer.discoveryUrl({
1378− service: "gmail",
1379− version: "v1",
1380− scope: "scope:gmail.readonly",
1381− }),
1382− scopes: ["scope:gmail.readonly"],
1383− name: "Gmail",
1384− namespace: "google.gmail",
1385− },
1386− }),
1387− );
1388−
1389− expect(result.kind).toBe("oauth_required");
1390− if (result.kind !== "oauth_required") {
1391− throw new Error(`Expected oauth_required result, received ${result.kind}`);
1392− }
1393−
1394− const redirectUri = new URL(result.authorizationUrl).searchParams.get("redirect_uri");
1395− expect(redirectUri).not.toBeNull();
1396− expect(new URL(redirectUri!).pathname).toBe(
1397− `/v1/workspaces/${encodeURIComponent(installation.workspaceId)}/oauth/provider/callback`,
1398− );
1399− expect(result.source.status).toBe("auth_required");
1400− }),
1401− 60_000,
1402− );
1403−
1404− it.scoped("reuses existing Google provider grants for batch connects and clears orphan state", () =>
1405− Effect.gen(function* () {
1406− const runtime = yield* makeRuntime;
1407− const googleServer = yield* makeGoogleWorkspaceTestServer;
1408− const installation = runtime.localInstallation;
1409−
1410− const oauthClient = yield* withControlPlaneClient(
1411− { runtime },
1412− (client) =>
1413− client.sources.createWorkspaceOauthClient({
1414− path: {
1415− workspaceId: installation.workspaceId,
1416− },
1417− payload: {
1418− providerKey: "google_workspace",
1419− label: "Local Google Workspace Client",
1420− oauthClient: {
1421− clientId: "google-client-id",
1422− clientSecret: "google-client-secret",
1423− },
1424− },
1425− }),
1426− );
1427−
1428− const refreshSecretId = "sec_google_refresh_reuse";
1429− yield* upsertLocalSecret(runtime, {
1430− id: refreshSecretId,
1431− name: "Google Refresh Token",
1432− purpose: "oauth_refresh_token",
1433− value: "refresh-token-reuse",
1434− });
1435−
1436− const grantId = ProviderAuthGrantIdSchema.make(`provider_grant_${crypto.randomUUID()}`);
1437− yield* runtime.persistence.rows.providerAuthGrants.upsert({
1438− id: grantId,
1439− workspaceId: installation.workspaceId,
1440− actorAccountId: installation.accountId,
1441− providerKey: "google_workspace",
1442− oauthClientId: oauthClient.id,
1443− tokenEndpoint: googleServer.tokenEndpoint,
1444− clientAuthentication: "client_secret_post",
1445− headerName: "Authorization",
1446− prefix: "Bearer ",
1447− refreshToken: {
1448− providerId: "local",
1449− handle: refreshSecretId,
1450− },
1451− grantedScopes: [
1452− "scope:gmail.readonly",
1453− "scope:calendar.readonly",
1454− ],
1455− lastRefreshedAt: null,
1456− orphanedAt: 123,
1457− createdAt: Date.now(),
1458− updatedAt: Date.now(),
1459− });
1460−
1461− googleServer.queueTokenResponse({
1462− access_token: "reuse-access-token",
1463− scope: "scope:gmail.readonly scope:calendar.readonly",
1464− });
1465− googleServer.queueTokenResponse({
1466− access_token: "reuse-access-token",
1467− scope: "scope:gmail.readonly scope:calendar.readonly",
1468− });
1469−
1470− const result = yield* withControlPlaneClient(
1471− { runtime },
1472− (client) =>
1473− client.sources.connectBatch({
1474− path: {
1475− workspaceId: installation.workspaceId,
1476− },
1477− payload: {
1478− workspaceOauthClientId: oauthClient.id,
1479− sources: [
1480− {
1481− service: "gmail",
1482− version: "v1",
1483− discoveryUrl: googleServer.discoveryUrl({
1484− service: "gmail",
1485− version: "v1",
1486− scope: "scope:gmail.readonly",
1487− }),
1488− scopes: ["scope:gmail.readonly"],
1489− name: "Gmail",
1490− namespace: "google.gmail",
1491− },
1492− {
1493− service: "calendar",
1494− version: "v3",
1495− discoveryUrl: googleServer.discoveryUrl({
1496− service: "calendar",
1497− version: "v3",
1498− scope: "scope:calendar.readonly",
1499− }),
1500− scopes: ["scope:calendar.readonly"],
1501− name: "Calendar",
1502− namespace: "google.calendar",
1503− },
1504− ],
1505− },
1506− }),
1507− );
1508−
1509− expect(result.providerOauthSession).toBeNull();
1510− expect(result.results).toHaveLength(2);
1511− expect(result.results.every((entry) => entry.status === "connected")).toBe(true);
1512− expect(result.results.every((entry) => entry.source.auth.kind === "provider_grant_ref")).toBe(true);
1513−
1514− const storedGrant = yield* runtime.persistence.rows.providerAuthGrants.getById(grantId);
1515− assertTrue(Option.isSome(storedGrant));
1516− expect(storedGrant.value.orphanedAt).toBeNull();
1517− expect(googleServer.tokenRequests.length).toBeGreaterThanOrEqual(1);
1518− expect(
1519− googleServer.discoveryAuthorizations.every(
1520− (authorization) => authorization === "Bearer reuse-access-token",
1521− ),
1522− ).toBe(true);
1523− }),
1524− 60_000,
1525− );
1526−
1527− it.scoped("uses the app callback redirect for browser-started Google batch OAuth", () =>
1528− Effect.gen(function* () {
1529− const runtime = yield* makeRuntime;
1530− const googleServer = yield* makeGoogleWorkspaceTestServer;
1531− const installation = runtime.localInstallation;
1532−
1533− const oauthClient = yield* withControlPlaneClient(
1534− { runtime },
1535− (client) =>
1536− client.sources.createWorkspaceOauthClient({
1537− path: {
1538− workspaceId: installation.workspaceId,
1539− },
1540− payload: {
1541− providerKey: "google_workspace",
1542− label: "Local Google Workspace Client",
1543− oauthClient: {
1544− clientId: "google-client-id",
1545− clientSecret: "google-client-secret",
1546− },
1547− },
1548− }),
1549− );
1550−
1551− const result = yield* withControlPlaneClient(
1552− { runtime },
1553− (client) =>
1554− client.sources.connectBatch({
1555− path: {
1556− workspaceId: installation.workspaceId,
1557− },
1558− payload: {
1559− workspaceOauthClientId: oauthClient.id,
1560− sources: [{
1561− service: "gmail",
1562− version: "v1",
1563− discoveryUrl: googleServer.discoveryUrl({
1564− service: "gmail",
1565− version: "v1",
1566− scope: "scope:gmail.readonly",
1567− }),
1568− scopes: ["scope:gmail.readonly"],
1569− name: "Gmail",
1570− namespace: "google.gmail",
1571− }],
1572− },
1573− }),
1574− );
1575−
1576− expect(result.providerOauthSession).not.toBeNull();
1577− const redirectUri = new URL(
1578− result.providerOauthSession!.authorizationUrl,
1579− ).searchParams.get("redirect_uri");
1580−
1581− expect(redirectUri).not.toBeNull();
1582− expect(new URL(redirectUri!).pathname).toBe(
1583− `/v1/workspaces/${encodeURIComponent(installation.workspaceId)}/oauth/provider/callback`,
1584− );
1585− }),
1586− 60_000,
1587− );
1588−
1589− it.scoped("preserves the existing provider refresh token when callback expansion omits refresh_token", () =>
1590− Effect.gen(function* () {
1591− const runtime = yield* makeRuntime;
1592− const googleServer = yield* makeGoogleWorkspaceTestServer;
1593− const installation = runtime.localInstallation;
1594−
1595− const oauthClient = yield* withControlPlaneClient(
1596− { runtime },
1597− (client) =>
1598− client.sources.createWorkspaceOauthClient({
1599− path: {
1600− workspaceId: installation.workspaceId,
1601− },
1602− payload: {
1603− providerKey: "google_workspace",
1604− label: "Local Google Workspace Client",
1605− oauthClient: {
1606− clientId: "google-client-id",
1607− clientSecret: "google-client-secret",
1608− },
1609− },
1610− }),
1611− );
1612−
1613− const preservedRefreshSecretId = "sec_google_refresh_preserved";
1614− yield* upsertLocalSecret(runtime, {
1615− id: preservedRefreshSecretId,
1616− name: "Preserved Google Refresh Token",
1617− purpose: "oauth_refresh_token",
1618− value: "refresh-token-preserved",
1619− });
1620−
1621− const source = yield* createPersistedGoogleSource({
1622− runtime,
1623− name: "Drive",
1624− namespace: "google.drive",
1625− service: "drive",
1626− version: "v3",
1627− discoveryUrl: googleServer.discoveryUrl({
1628− service: "drive",
1629− version: "v3",
1630− scope: "scope:drive.readonly",
1631− }),
1632− scopes: ["scope:drive.readonly"],
1633− auth: {
1634− kind: "none",
1635− },
1636− });
1637−
1638− const grantId = ProviderAuthGrantIdSchema.make(`provider_grant_${crypto.randomUUID()}`);
1639− yield* runtime.persistence.rows.providerAuthGrants.upsert({
1640− id: grantId,
1641− workspaceId: installation.workspaceId,
1642− actorAccountId: installation.accountId,
1643− providerKey: "google_workspace",
1644− oauthClientId: oauthClient.id,
1645− tokenEndpoint: googleServer.tokenEndpoint,
1646− clientAuthentication: "client_secret_post",
1647− headerName: "Authorization",
1648− prefix: "Bearer ",
1649− refreshToken: {
1650− providerId: "local",
1651− handle: preservedRefreshSecretId,
1652− },
1653− grantedScopes: [
1654− "scope:gmail.readonly",
1655− ],
1656− lastRefreshedAt: null,
1657− orphanedAt: null,
1658− createdAt: Date.now(),
1659− updatedAt: Date.now(),
1660− });
1661−
1662− const sessionId = SourceAuthSessionIdSchema.make(`src_auth_${crypto.randomUUID()}`);
1663− const state = `provider-state-${crypto.randomUUID()}`;
1664− yield* runtime.persistence.rows.sourceAuthSessions.upsert({
1665− id: sessionId,
1666− workspaceId: installation.workspaceId,
1667− sourceId: SourceIdSchema.make(`oauth_provider_${crypto.randomUUID()}`),
1668− actorAccountId: installation.accountId,
1669− credentialSlot: "runtime",
1670− executionId: null,
1671− interactionId: null,
1672− providerKind: "oauth2_provider_batch",
1673− status: "pending",
1674− state,
1675− sessionDataJson: JSON.stringify({
1676− kind: "provider_oauth_batch",
1677− providerKey: "google_workspace",
1678− authorizationEndpoint: "https://accounts.google.com/o/oauth2/v2/auth",
1679− tokenEndpoint: googleServer.tokenEndpoint,
1680− redirectUri: `${googleServer.baseUrl}/oauth/callback`,
1681− oauthClientId: oauthClient.id,
1682− clientAuthentication: "client_secret_post",
1683− scopes: [
1684− "scope:gmail.readonly",
1685− "scope:drive.readonly",
1686− ],
1687− headerName: "Authorization",
1688− prefix: "Bearer ",
1689− authorizationParams: {
1690− access_type: "offline",
1691− prompt: "consent",
1692− include_granted_scopes: "true",
1693− },
1694− targetSources: [{
1695− sourceId: source.id,
1696− requiredScopes: ["scope:drive.readonly"],
1697− }],
1698− codeVerifier: "pkce-verifier",
1699− authorizationUrl: `https://accounts.google.com/o/oauth2/v2/auth?state=${encodeURIComponent(state)}`,
1700− }),
1701− errorText: null,
1702− completedAt: null,
1703− createdAt: Date.now(),
1704− updatedAt: Date.now(),
1705− });
1706−
1707− googleServer.queueTokenResponse({
1708− access_token: "authorization-code-access-token",
1709− scope: "scope:gmail.readonly scope:drive.readonly",
1710− });
1711− googleServer.queueTokenResponse({
1712− access_token: "post-callback-sync-access-token",
1713− scope: "scope:gmail.readonly scope:drive.readonly",
1714− });
1715−
1716− const callbackPage = yield* withControlPlaneClient(
1717− { runtime },
1718− (client) =>
1719− client.sources.providerOauthComplete({
1720− path: {
1721− workspaceId: installation.workspaceId,
1722− },
1723− urlParams: {
1724− state,
1725− code: "authorization-code",
1726− },
1727− }),
1728− );
1729−
1730− expect(callbackPage).toContain("Connected 1 source");
1731−
1732− const storedGrant = yield* runtime.persistence.rows.providerAuthGrants.getById(grantId);
1733− assertTrue(Option.isSome(storedGrant));
1734− expect(storedGrant.value.refreshToken).toEqual({
1735− providerId: "local",
1736− handle: preservedRefreshSecretId,
1737− });
1738− expect(storedGrant.value.grantedScopes).toEqual([
1739− "scope:gmail.readonly",
1740− "scope:drive.readonly",
1741− ]);
1742−
1743− const refreshedSource = yield* withControlPlaneClient(
1744− { runtime },
1745− (client) =>
1746− client.sources.get({
1747− path: {
1748− workspaceId: installation.workspaceId,
1749− sourceId: source.id,
1750− },
1751− }),
1752− );
1753−
1754− expect(refreshedSource.status).toBe("connected");
1755− expect(refreshedSource.auth).toEqual({
1756− kind: "provider_grant_ref",
1757− grantId,
1758− providerKey: "google_workspace",
1759− requiredScopes: ["scope:drive.readonly"],
1760− headerName: "Authorization",
1761− prefix: "Bearer ",
1762− });
1763−
1764− expect(googleServer.tokenRequests.map((request) => request.get("grant_type"))).toEqual([
1765− "authorization_code",
1766− "refresh_token",
1767− ]);
1768− expect(googleServer.tokenRequests[1]?.get("refresh_token")).toBe("refresh-token-preserved");
1769− expect(googleServer.discoveryAuthorizations).toContain("Bearer post-callback-sync-access-token");
1770− }),
1771− 60_000,
1772− );
1773−
1774− it.scoped("marks provider grants as orphaned when the last referencing source is removed", () =>
1775− Effect.gen(function* () {
1776− const runtime = yield* makeRuntime;
1777− const installation = runtime.localInstallation;
1778− const oauthClientId = WorkspaceOauthClientIdSchema.make(`ws_oauth_client_${crypto.randomUUID()}`);
1779− const refreshSecretId = "sec_google_refresh_orphan";
1780−
1781− yield* runtime.persistence.rows.workspaceOauthClients.upsert({
1782− id: oauthClientId,
1783− workspaceId: installation.workspaceId,
1784− providerKey: "google_workspace",
1785− label: "Local Google Workspace Client",
1786− clientId: "google-client-id",
1787− clientSecretProviderId: null,
1788− clientSecretHandle: null,
1789− clientMetadataJson: null,
1790− createdAt: Date.now(),
1791− updatedAt: Date.now(),
1792− });
1793− yield* upsertLocalSecret(runtime, {
1794− id: refreshSecretId,
1795− name: "Google Refresh Token",
1796− purpose: "oauth_refresh_token",
1797− value: "refresh-token-orphan",
1798− });
1799−
1800− const grantId = ProviderAuthGrantIdSchema.make(`provider_grant_${crypto.randomUUID()}`);
1801− yield* runtime.persistence.rows.providerAuthGrants.upsert({
1802− id: grantId,
1803− workspaceId: installation.workspaceId,
1804− actorAccountId: installation.accountId,
1805− providerKey: "google_workspace",
1806− oauthClientId,
1807− tokenEndpoint: "https://example.test/oauth/token",
1808− clientAuthentication: "client_secret_post",
1809− headerName: "Authorization",
1810− prefix: "Bearer ",
1811− refreshToken: {
1812− providerId: "local",
1813− handle: refreshSecretId,
1814− },
1815− grantedScopes: ["scope:drive.readonly"],
1816− lastRefreshedAt: null,
1817− orphanedAt: null,
1818− createdAt: Date.now(),
1819− updatedAt: Date.now(),
1820− });
1821−
1822− const source = yield* createPersistedGoogleSource({
1823− runtime,
1824− name: "Drive",
1825− namespace: "google.drive",
1826− service: "drive",
1827− version: "v3",
1828− discoveryUrl: "https://example.test/drive/$discovery/rest?version=v3",
1829− scopes: ["scope:drive.readonly"],
1830− auth: {
1831− kind: "provider_grant_ref",
1832− grantId,
1833− providerKey: "google_workspace",
1834− requiredScopes: ["scope:drive.readonly"],
1835− headerName: "Authorization",
1836− prefix: "Bearer ",
1837− },
1838− });
1839−
1840− const removed = yield* withControlPlaneClient(
1841− { runtime },
1842− (client) =>
1843− client.sources.remove({
1844− path: {
1845− workspaceId: installation.workspaceId,
1846− sourceId: source.id,
1847− },
1848− }),
1849− );
1850−
1851− expect(removed.removed).toBe(true);
1852−
1853− const storedGrant = yield* runtime.persistence.rows.providerAuthGrants.getById(grantId);
1854− assertTrue(Option.isSome(storedGrant));
1855− expect(storedGrant.value.orphanedAt).not.toBeNull();
1856− }),
1857− );
1858−
1859− it.scoped("revokes shared provider grants through the API and disconnects linked sources", () =>
1860− Effect.gen(function* () {
1861− const runtime = yield* makeRuntime;
1862− const installation = runtime.localInstallation;
1863− const oauthClientId = WorkspaceOauthClientIdSchema.make(`ws_oauth_client_${crypto.randomUUID()}`);
1864− const refreshSecretId = "sec_google_refresh_revoke";
1865−
1866− yield* runtime.persistence.rows.workspaceOauthClients.upsert({
1867− id: oauthClientId,
1868− workspaceId: installation.workspaceId,
1869− providerKey: "google_workspace",
1870− label: "Local Google Workspace Client",
1871− clientId: "google-client-id",
1872− clientSecretProviderId: null,
1873− clientSecretHandle: null,
1874− clientMetadataJson: null,
1875− createdAt: Date.now(),
1876− updatedAt: Date.now(),
1877− });
1878− yield* upsertLocalSecret(runtime, {
1879− id: refreshSecretId,
1880− name: "Google Refresh Token",
1881− purpose: "oauth_refresh_token",
1882− value: "refresh-token-revoke",
1883− });
1884−
1885− const grantId = ProviderAuthGrantIdSchema.make(`provider_grant_${crypto.randomUUID()}`);
1886− yield* runtime.persistence.rows.providerAuthGrants.upsert({
1887− id: grantId,
1888− workspaceId: installation.workspaceId,
1889− actorAccountId: installation.accountId,
1890− providerKey: "google_workspace",
1891− oauthClientId,
1892− tokenEndpoint: "https://example.test/oauth/token",
1893− clientAuthentication: "client_secret_post",
1894− headerName: "Authorization",
1895− prefix: "Bearer ",
1896− refreshToken: {
1897− providerId: "local",
1898− handle: refreshSecretId,
1899− },
1900− grantedScopes: ["scope:drive.readonly", "scope:gmail.readonly"],
1901− lastRefreshedAt: null,
1902− orphanedAt: null,
1903− createdAt: Date.now(),
1904− updatedAt: Date.now(),
1905− });
1906−
1907− const driveSource = yield* createPersistedGoogleSource({
1908− runtime,
1909− name: "Drive",
1910− namespace: "google.drive",
1911− service: "drive",
1912− version: "v3",
1913− discoveryUrl: "https://example.test/drive/$discovery/rest?version=v3",
1914− scopes: ["scope:drive.readonly"],
1915− auth: {
1916− kind: "provider_grant_ref",
1917− grantId,
1918− providerKey: "google_workspace",
1919− requiredScopes: ["scope:drive.readonly"],
1920− headerName: "Authorization",
1921− prefix: "Bearer ",
1922− },
1923− });
1924− const gmailSource = yield* createPersistedGoogleSource({
1925− runtime,
1926− name: "Gmail",
1927− namespace: "google.gmail",
1928− service: "gmail",
1929− version: "v1",
1930− discoveryUrl: "https://example.test/gmail/$discovery/rest?version=v1",
1931− scopes: ["scope:gmail.readonly"],
1932− auth: {
1933− kind: "provider_grant_ref",
1934− grantId,
1935− providerKey: "google_workspace",
1936− requiredScopes: ["scope:gmail.readonly"],
1937− headerName: "Authorization",
1938− prefix: "Bearer ",
1939− },
1940− });
1941−
1942− const removed = yield* withControlPlaneClient(
1943− { runtime },
1944− (client) =>
1945− client.sources.removeProviderAuthGrant({
1946− path: {
1947− workspaceId: installation.workspaceId,
1948− grantId,
1949− },
1950− }),
1951− );
1952−
1953− expect(removed.removed).toBe(true);
1954−
1955− const storedGrant = yield* runtime.persistence.rows.providerAuthGrants.getById(grantId);
1956− assertTrue(Option.isNone(storedGrant));
1957−
1958− const storedRefreshSecret = yield* runtime.persistence.rows.secretMaterials.getById(
1959− SecretMaterialIdSchema.make(refreshSecretId),
1960− );
1961− assertTrue(Option.isNone(storedRefreshSecret));
1962−
1963− const disconnectedDrive = yield* withControlPlaneClient(
1964− { runtime },
1965− (client) =>
1966− client.sources.get({
1967− path: {
1968− workspaceId: installation.workspaceId,
1969− sourceId: driveSource.id,
1970− },
1971− }),
1972− );
1973− const disconnectedGmail = yield* withControlPlaneClient(
1974− { runtime },
1975− (client) =>
1976− client.sources.get({
1977− path: {
1978− workspaceId: installation.workspaceId,
1979− sourceId: gmailSource.id,
1980− },
1981− }),
1982− );
1983−
1984− expect(disconnectedDrive.status).toBe("auth_required");
1985− expect(disconnectedDrive.auth).toEqual({ kind: "none" });
1986− expect(disconnectedGmail.status).toBe("auth_required");
1987− expect(disconnectedGmail.auth).toEqual({ kind: "none" });
1988− }),
1989− );
1990−
1991−});
packages/platform/internal/package.json+22−0
@@ -0,0 +1,22 @@
1+{
2+ "name": "@executor/platform-internal",
3+ "private": true,
4+ "type": "module",
5+ "exports": {
6+ ".": "./src/index.ts"
7+ },
8+ "scripts": {
9+ "typecheck": "bunx tsc --noEmit -p tsconfig.json"
10+ },
11+ "dependencies": {
12+ "@executor/codemode-core": "workspace:*",
13+ "@executor/platform-sdk": "workspace:*",
14+ "effect": "catalog:"
15+ },
16+ "devDependencies": {
17+ "@types/node": "catalog:",
18+ "bun-types": "catalog:",
19+ "typescript": "^5.9.3"
20+ },
21+ "version": "0.0.0"
22+}
packages/platform/internal/src/index.ts+1−0
@@ -0,0 +1 @@
1+export * from "./tools/executor-admin";
packages/platform/internal/src/tools/executor-admin.ts+692−0
@@ -0,0 +1,692 @@
1+import { toTool, type ToolMap } from "@executor/codemode-core";
2+import {
3+ type AccountId,
4+ LocalInstallationSchema,
5+ LocalWorkspacePolicySchema,
6+ SourceDiscoveryResultSchema,
7+ SourceIdSchema,
8+ SourceInspectionDiscoverPayloadSchema,
9+ SourceInspectionDiscoverResultSchema,
10+ SourceInspectionSchema,
11+ SourceInspectionToolDetailSchema,
12+ SourceSchema,
13+ type WorkspaceId,
14+ WorkspaceOauthClientSchema,
15+} from "@executor/platform-sdk/schema";
16+import * as Effect from "effect/Effect";
17+import * as Layer from "effect/Layer";
18+import * as Schema from "effect/Schema";
19+
20+import {
21+ ControlPlaneStore,
22+ type ControlPlaneStoreShape,
23+ type WorkspaceInternalToolContext,
24+ RuntimeLocalWorkspaceState,
25+ RuntimeSourceAuthService,
26+ RuntimeSourceCatalogSyncService,
27+ RuntimeSourceStore,
28+ RuntimeSourceStoreService,
29+ SourceArtifactStore,
30+ type SourceArtifactStoreShape,
31+ WorkspaceConfigStore,
32+ type WorkspaceConfigStoreShape,
33+ WorkspaceStateStore,
34+ type WorkspaceStateStoreShape,
35+ makeWorkspaceStorageLayer,
36+ provideOptionalRuntimeLocalWorkspace,
37+} from "@executor/platform-sdk/runtime";
38+import {
39+ CreateSecretPayloadSchema,
40+ CreateSecretResultSchema,
41+ type CreateSecretPayload,
42+ DeleteSecretResultSchema,
43+ InstanceConfigSchema,
44+ SecretListItemSchema,
45+ UpdateSecretPayloadSchema,
46+ UpdateSecretResultSchema,
47+ type UpdateSecretPayload,
48+} from "@executor/platform-sdk/local/contracts";
49+import {
50+ createLocalSecret,
51+ deleteLocalSecret,
52+ getLocalInstanceConfig,
53+ listLocalSecrets,
54+ updateLocalSecret,
55+} from "@executor/platform-sdk/local/secrets";
56+import {
57+ CreatePolicyPayloadSchema,
58+ type CreatePolicyPayload,
59+ type UpdatePolicyPayload,
60+ UpdatePolicyPayloadSchema,
61+} from "@executor/platform-sdk/policies/contracts";
62+import {
63+ createPolicy,
64+ getPolicy,
65+ listPolicies,
66+ removePolicy,
67+ updatePolicy,
68+} from "@executor/platform-sdk/policies/operations";
69+import {
70+ CreateWorkspaceOauthClientPayloadSchema,
71+ DiscoverSourcePayloadSchema,
72+ type CreateWorkspaceOauthClientPayload,
73+ UpdateSourcePayloadSchema,
74+} from "@executor/platform-sdk/sources/contracts";
75+import {
76+ discoverSource,
77+} from "@executor/platform-sdk/sources/discovery";
78+import {
79+ discoverSourceInspectionTools,
80+ getSourceInspection,
81+ getSourceInspectionToolDetail,
82+} from "@executor/platform-sdk/sources/inspection";
83+import {
84+ getSource,
85+ listSources,
86+ removeSource,
87+ updateSource,
88+} from "@executor/platform-sdk/sources/operations";
89+
90+const emptyInputSchema = Schema.standardSchemaV1(Schema.Struct({}));
91+const localInstallationOutputSchema = Schema.standardSchemaV1(
92+ LocalInstallationSchema,
93+);
94+const instanceConfigOutputSchema =
95+ Schema.standardSchemaV1(InstanceConfigSchema);
96+const secretListOutputSchema = Schema.standardSchemaV1(
97+ Schema.Array(SecretListItemSchema),
98+);
99+const createSecretInputSchema = Schema.standardSchemaV1(
100+ CreateSecretPayloadSchema,
101+);
102+const updateSecretInputSchema = Schema.standardSchemaV1(
103+ Schema.Struct({
104+ secretId: Schema.String,
105+ payload: UpdateSecretPayloadSchema,
106+ }),
107+);
108+const removeSecretInputSchema = Schema.standardSchemaV1(
109+ Schema.Struct({
110+ secretId: Schema.String,
111+ }),
112+);
113+const removeResultSchema = Schema.standardSchemaV1(
114+ Schema.Struct({
115+ removed: Schema.Boolean,
116+ }),
117+);
118+const listSourcesOutputSchema = Schema.standardSchemaV1(
119+ Schema.Array(SourceSchema),
120+);
121+const getSourceInputSchema = Schema.standardSchemaV1(
122+ Schema.Struct({
123+ sourceId: SourceIdSchema,
124+ }),
125+);
126+const updateSourceInputSchema = Schema.standardSchemaV1(
127+ Schema.Struct({
128+ sourceId: SourceIdSchema,
129+ payload: UpdateSourcePayloadSchema,
130+ }),
131+);
132+const discoverSourceInputSchema = Schema.standardSchemaV1(
133+ DiscoverSourcePayloadSchema,
134+);
135+const inspectToolInputSchema = Schema.standardSchemaV1(
136+ Schema.Struct({
137+ sourceId: SourceIdSchema,
138+ toolPath: Schema.String,
139+ }),
140+);
141+const inspectDiscoverInputSchema = Schema.standardSchemaV1(
142+ Schema.Struct({
143+ sourceId: SourceIdSchema,
144+ payload: SourceInspectionDiscoverPayloadSchema,
145+ }),
146+);
147+const listPoliciesOutputSchema = Schema.standardSchemaV1(
148+ Schema.Array(LocalWorkspacePolicySchema),
149+);
150+const policyIdInputSchema = Schema.standardSchemaV1(
151+ Schema.Struct({
152+ policyId: Schema.String,
153+ }),
154+);
155+const createPolicyInputSchema = Schema.standardSchemaV1(
156+ CreatePolicyPayloadSchema,
157+);
158+const updatePolicyInputSchema = Schema.standardSchemaV1(
159+ Schema.Struct({
160+ policyId: Schema.String,
161+ payload: UpdatePolicyPayloadSchema,
162+ }),
163+);
164+const workspaceOauthClientListInputSchema = Schema.standardSchemaV1(
165+ Schema.Struct({
166+ providerKey: Schema.String,
167+ }),
168+);
169+const workspaceOauthClientListOutputSchema = Schema.standardSchemaV1(
170+ Schema.Array(WorkspaceOauthClientSchema),
171+);
172+const createWorkspaceOauthClientInputSchema = Schema.standardSchemaV1(
173+ CreateWorkspaceOauthClientPayloadSchema,
174+);
175+const removeWorkspaceOauthClientInputSchema = Schema.standardSchemaV1(
176+ Schema.Struct({
177+ oauthClientId: Schema.String,
178+ }),
179+);
180+const removeProviderGrantInputSchema = Schema.standardSchemaV1(
181+ Schema.Struct({
182+ grantId: Schema.String,
183+ }),
184+);
185+const sourceOutputSchema = Schema.standardSchemaV1(SourceSchema);
186+const sourceInspectionOutputSchema = Schema.standardSchemaV1(
187+ SourceInspectionSchema,
188+);
189+const sourceInspectionToolOutputSchema = Schema.standardSchemaV1(
190+ SourceInspectionToolDetailSchema,
191+);
192+const sourceInspectionDiscoverOutputSchema = Schema.standardSchemaV1(
193+ SourceInspectionDiscoverResultSchema,
194+);
195+const sourceDiscoveryOutputSchema = Schema.standardSchemaV1(
196+ SourceDiscoveryResultSchema,
197+);
198+const localWorkspacePolicyOutputSchema = Schema.standardSchemaV1(
199+ LocalWorkspacePolicySchema,
200+);
201+
202+const makeRuntimeLayer = (input: {
203+ controlPlaneStore: ControlPlaneStoreShape;
204+ sourceStore: RuntimeSourceStore;
205+ sourceCatalogSyncService: Effect.Effect.Success<
206+ typeof RuntimeSourceCatalogSyncService
207+ >;
208+ workspaceConfigStore: WorkspaceConfigStoreShape;
209+ workspaceStateStore: WorkspaceStateStoreShape;
210+ sourceArtifactStore: SourceArtifactStoreShape;
211+}) =>
212+ Layer.mergeAll(
213+ Layer.succeed(ControlPlaneStore, input.controlPlaneStore),
214+ Layer.succeed(RuntimeSourceStoreService, input.sourceStore),
215+ Layer.succeed(
216+ RuntimeSourceCatalogSyncService,
217+ input.sourceCatalogSyncService,
218+ ),
219+ makeWorkspaceStorageLayer({
220+ workspaceConfigStore: input.workspaceConfigStore,
221+ workspaceStateStore: input.workspaceStateStore,
222+ sourceArtifactStore: input.sourceArtifactStore,
223+ }),
224+ );
225+
226+const runRuntimeEffect = <A, E, R>(input: {
227+ effect: Effect.Effect<A, E, R>;
228+ runtimeLayer: Layer.Layer<
229+ | ControlPlaneStore
230+ | RuntimeSourceStoreService
231+ | RuntimeSourceCatalogSyncService
232+ | WorkspaceConfigStore
233+ | WorkspaceStateStore
234+ | SourceArtifactStore,
235+ never,
236+ never
237+ >;
238+ runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null;
239+}) =>
240+ Effect.runPromise(
241+ provideOptionalRuntimeLocalWorkspace(
242+ input.effect.pipe(Effect.provide(input.runtimeLayer)),
243+ input.runtimeLocalWorkspace,
244+ ) as Effect.Effect<A, E, never>,
245+ );
246+
247+const runWorkspaceStorageEffect = <A, E, R>(input: {
248+ effect: Effect.Effect<A, E, R>;
249+ workspaceStorageLayer: Layer.Layer<
250+ WorkspaceConfigStore | WorkspaceStateStore | SourceArtifactStore,
251+ never,
252+ never
253+ >;
254+ runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null;
255+}) =>
256+ Effect.runPromise(
257+ provideOptionalRuntimeLocalWorkspace(
258+ input.effect.pipe(Effect.provide(input.workspaceStorageLayer)),
259+ input.runtimeLocalWorkspace,
260+ ) as Effect.Effect<A, E, never>,
261+ );
262+
263+export const createExecutorAdminToolMap = (
264+ input: WorkspaceInternalToolContext,
265+): ToolMap => {
266+ const runtimeLayer = makeRuntimeLayer({
267+ controlPlaneStore: input.controlPlaneStore,
268+ sourceStore: input.sourceStore,
269+ sourceCatalogSyncService: input.sourceCatalogSyncService,
270+ workspaceConfigStore: input.workspaceConfigStore,
271+ workspaceStateStore: input.workspaceStateStore,
272+ sourceArtifactStore: input.sourceArtifactStore,
273+ });
274+ const workspaceStorageLayer = makeWorkspaceStorageLayer({
275+ workspaceConfigStore: input.workspaceConfigStore,
276+ workspaceStateStore: input.workspaceStateStore,
277+ sourceArtifactStore: input.sourceArtifactStore,
278+ });
279+
280+ const metadata = {
281+ sourceKey: "executor",
282+ interaction: "auto" as const,
283+ };
284+
285+ return {
286+ "executor.local.installation.get": toTool({
287+ tool: {
288+ description:
289+ "Get the active local executor installation account and workspace ids.",
290+ inputSchema: emptyInputSchema,
291+ outputSchema: localInstallationOutputSchema,
292+ execute: async () => ({
293+ workspaceId: input.workspaceId,
294+ accountId: input.accountId,
295+ }),
296+ },
297+ metadata,
298+ }),
299+ "executor.local.config.get": toTool({
300+ tool: {
301+ description:
302+ "Get local instance config such as supported secret providers.",
303+ inputSchema: emptyInputSchema,
304+ outputSchema: instanceConfigOutputSchema,
305+ execute: () => Effect.runPromise(getLocalInstanceConfig()),
306+ },
307+ metadata,
308+ }),
309+ "executor.secrets.list": toTool({
310+ tool: {
311+ description:
312+ "List locally stored secrets and the sources linked to them.",
313+ inputSchema: emptyInputSchema,
314+ outputSchema: secretListOutputSchema,
315+ execute: () =>
316+ runRuntimeEffect({
317+ effect: listLocalSecrets(),
318+ runtimeLayer,
319+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
320+ }),
321+ },
322+ metadata,
323+ }),
324+ "executor.secrets.create": toTool({
325+ tool: {
326+ description:
327+ "Create a local secret without putting the raw value into source config.",
328+ inputSchema: createSecretInputSchema,
329+ outputSchema: Schema.standardSchemaV1(CreateSecretResultSchema),
330+ execute: (payload: CreateSecretPayload) =>
331+ runRuntimeEffect({
332+ effect: createLocalSecret(payload),
333+ runtimeLayer,
334+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
335+ }),
336+ },
337+ metadata,
338+ }),
339+ "executor.secrets.update": toTool({
340+ tool: {
341+ description:
342+ "Update a stored secret name and optionally rotate its value.",
343+ inputSchema: updateSecretInputSchema,
344+ outputSchema: Schema.standardSchemaV1(UpdateSecretResultSchema),
345+ execute: (payload: {
346+ secretId: string;
347+ payload: UpdateSecretPayload;
348+ }) =>
349+ runRuntimeEffect({
350+ effect: updateLocalSecret(payload),
351+ runtimeLayer,
352+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
353+ }),
354+ },
355+ metadata,
356+ }),
357+ "executor.secrets.remove": toTool({
358+ tool: {
359+ description: "Remove a stored local secret.",
360+ inputSchema: removeSecretInputSchema,
361+ outputSchema: Schema.standardSchemaV1(DeleteSecretResultSchema),
362+ execute: ({ secretId }: { secretId: string }) =>
363+ runRuntimeEffect({
364+ effect: deleteLocalSecret(secretId),
365+ runtimeLayer,
366+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
367+ }),
368+ },
369+ metadata,
370+ }),
371+ "executor.sources.discover": toTool({
372+ tool: {
373+ description:
374+ "Probe a URL and infer whether it looks like MCP, OpenAPI, GraphQL, or another supported source.",
375+ inputSchema: discoverSourceInputSchema,
376+ outputSchema: sourceDiscoveryOutputSchema,
377+ execute: (payload: { url: string; probeAuth?: unknown }) =>
378+ Effect.runPromise(
379+ discoverSource({
380+ url: payload.url,
381+ probeAuth: payload.probeAuth as never,
382+ }),
383+ ),
384+ },
385+ metadata,
386+ }),
387+ "executor.sources.list": toTool({
388+ tool: {
389+ description: "List sources connected in the current workspace.",
390+ inputSchema: emptyInputSchema,
391+ outputSchema: listSourcesOutputSchema,
392+ execute: () =>
393+ runRuntimeEffect({
394+ effect: listSources({
395+ workspaceId: input.workspaceId,
396+ accountId: input.accountId as never,
397+ }),
398+ runtimeLayer,
399+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
400+ }),
401+ },
402+ metadata,
403+ }),
404+ "executor.sources.get": toTool({
405+ tool: {
406+ description: "Get one source by id.",
407+ inputSchema: getSourceInputSchema,
408+ outputSchema: sourceOutputSchema,
409+ execute: ({ sourceId }: { sourceId: string }) =>
410+ runRuntimeEffect({
411+ effect: getSource({
412+ workspaceId: input.workspaceId,
413+ sourceId: sourceId as never,
414+ accountId: input.accountId as never,
415+ }),
416+ runtimeLayer,
417+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
418+ }),
419+ },
420+ metadata,
421+ }),
422+ "executor.sources.update": toTool({
423+ tool: {
424+ description: "Update a source definition in the current workspace.",
425+ inputSchema: updateSourceInputSchema,
426+ outputSchema: sourceOutputSchema,
427+ execute: (payload: {
428+ sourceId: string;
429+ payload: Record<string, unknown>;
430+ }) =>
431+ runRuntimeEffect({
432+ effect: updateSource({
433+ workspaceId: input.workspaceId,
434+ sourceId: payload.sourceId as never,
435+ accountId: input.accountId as never,
436+ payload: payload.payload as never,
437+ }),
438+ runtimeLayer,
439+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
440+ }),
441+ },
442+ metadata,
443+ }),
444+ "executor.sources.remove": toTool({
445+ tool: {
446+ description: "Remove a source from the current workspace.",
447+ inputSchema: getSourceInputSchema,
448+ outputSchema: removeResultSchema,
449+ execute: ({ sourceId }: { sourceId: string }) =>
450+ runRuntimeEffect({
451+ effect: removeSource({
452+ workspaceId: input.workspaceId,
453+ sourceId: sourceId as never,
454+ }),
455+ runtimeLayer,
456+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
457+ }),
458+ },
459+ metadata,
460+ }),
461+ "executor.sources.inspect.get": toTool({
462+ tool: {
463+ description: "Inspect the tool model for one connected source.",
464+ inputSchema: getSourceInputSchema,
465+ outputSchema: sourceInspectionOutputSchema,
466+ execute: ({ sourceId }: { sourceId: string }) =>
467+ runRuntimeEffect({
468+ effect: getSourceInspection({
469+ workspaceId: input.workspaceId,
470+ sourceId: sourceId as never,
471+ }),
472+ runtimeLayer,
473+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
474+ }),
475+ },
476+ metadata,
477+ }),
478+ "executor.sources.inspect.tool": toTool({
479+ tool: {
480+ description: "Inspect one tool inside a connected source.",
481+ inputSchema: inspectToolInputSchema,
482+ outputSchema: sourceInspectionToolOutputSchema,
483+ execute: ({
484+ sourceId,
485+ toolPath,
486+ }: {
487+ sourceId: string;
488+ toolPath: string;
489+ }) =>
490+ runRuntimeEffect({
491+ effect: getSourceInspectionToolDetail({
492+ workspaceId: input.workspaceId,
493+ sourceId: sourceId as never,
494+ toolPath,
495+ }),
496+ runtimeLayer,
497+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
498+ }),
499+ },
500+ metadata,
501+ }),
502+ "executor.sources.inspect.discover": toTool({
503+ tool: {
504+ description: "Search within a single source's inspected tools.",
505+ inputSchema: inspectDiscoverInputSchema,
506+ outputSchema: sourceInspectionDiscoverOutputSchema,
507+ execute: ({
508+ sourceId,
509+ payload,
510+ }: {
511+ sourceId: string;
512+ payload: { query: string; limit?: number };
513+ }) =>
514+ runRuntimeEffect({
515+ effect: discoverSourceInspectionTools({
516+ workspaceId: input.workspaceId,
517+ sourceId: sourceId as never,
518+ payload: payload as never,
519+ }),
520+ runtimeLayer,
521+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
522+ }),
523+ },
524+ metadata,
525+ }),
526+ "executor.sources.oauthClients.list": toTool({
527+ tool: {
528+ description: "List workspace OAuth clients for a provider key.",
529+ inputSchema: workspaceOauthClientListInputSchema,
530+ outputSchema: workspaceOauthClientListOutputSchema,
531+ execute: ({ providerKey }: { providerKey: string }) =>
532+ runWorkspaceStorageEffect({
533+ effect: input.sourceAuthService.listWorkspaceOauthClients({
534+ workspaceId: input.workspaceId,
535+ providerKey,
536+ }),
537+ workspaceStorageLayer,
538+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
539+ }),
540+ },
541+ metadata,
542+ }),
543+ "executor.sources.oauthClients.create": toTool({
544+ tool: {
545+ description:
546+ "Create a workspace OAuth client used for shared provider auth flows.",
547+ inputSchema: createWorkspaceOauthClientInputSchema,
548+ outputSchema: Schema.standardSchemaV1(WorkspaceOauthClientSchema),
549+ execute: (payload: CreateWorkspaceOauthClientPayload) =>
550+ runWorkspaceStorageEffect({
551+ effect: input.sourceAuthService.createWorkspaceOauthClient({
552+ workspaceId: input.workspaceId,
553+ providerKey: payload.providerKey,
554+ label: payload.label,
555+ oauthClient: payload.oauthClient,
556+ }),
557+ workspaceStorageLayer,
558+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
559+ }),
560+ },
561+ metadata,
562+ }),
563+ "executor.sources.oauthClients.remove": toTool({
564+ tool: {
565+ description: "Remove a workspace OAuth client.",
566+ inputSchema: removeWorkspaceOauthClientInputSchema,
567+ outputSchema: removeResultSchema,
568+ execute: ({ oauthClientId }: { oauthClientId: string }) =>
569+ runWorkspaceStorageEffect({
570+ effect: input.sourceAuthService
571+ .removeWorkspaceOauthClient({
572+ workspaceId: input.workspaceId,
573+ oauthClientId: oauthClientId as never,
574+ })
575+ .pipe(Effect.map((removed) => ({ removed }))),
576+ workspaceStorageLayer,
577+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
578+ }),
579+ },
580+ metadata,
581+ }),
582+ "executor.sources.providerGrants.remove": toTool({
583+ tool: {
584+ description:
585+ "Revoke a shared provider grant reference from the local workspace.",
586+ inputSchema: removeProviderGrantInputSchema,
587+ outputSchema: removeResultSchema,
588+ execute: ({ grantId }: { grantId: string }) =>
589+ runWorkspaceStorageEffect({
590+ effect: input.sourceAuthService
591+ .removeProviderAuthGrant({
592+ workspaceId: input.workspaceId,
593+ grantId: grantId as never,
594+ })
595+ .pipe(Effect.map((removed) => ({ removed }))),
596+ workspaceStorageLayer,
597+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
598+ }),
599+ },
600+ metadata,
601+ }),
602+ "executor.policies.list": toTool({
603+ tool: {
604+ description: "List local workspace policies.",
605+ inputSchema: emptyInputSchema,
606+ outputSchema: listPoliciesOutputSchema,
607+ execute: () =>
608+ runRuntimeEffect({
609+ effect: listPolicies(input.workspaceId),
610+ runtimeLayer,
611+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
612+ }),
613+ },
614+ metadata,
615+ }),
616+ "executor.policies.create": toTool({
617+ tool: {
618+ description: "Create a local workspace policy.",
619+ inputSchema: createPolicyInputSchema,
620+ outputSchema: localWorkspacePolicyOutputSchema,
621+ execute: (payload: CreatePolicyPayload) =>
622+ runRuntimeEffect({
623+ effect: createPolicy({
624+ workspaceId: input.workspaceId,
625+ payload,
626+ }),
627+ runtimeLayer,
628+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
629+ }),
630+ },
631+ metadata,
632+ }),
633+ "executor.policies.get": toTool({
634+ tool: {
635+ description: "Get one local workspace policy by id.",
636+ inputSchema: policyIdInputSchema,
637+ outputSchema: localWorkspacePolicyOutputSchema,
638+ execute: ({ policyId }: { policyId: string }) =>
639+ runRuntimeEffect({
640+ effect: getPolicy({
641+ workspaceId: input.workspaceId,
642+ policyId: policyId as never,
643+ }),
644+ runtimeLayer,
645+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
646+ }),
647+ },
648+ metadata,
649+ }),
650+ "executor.policies.update": toTool({
651+ tool: {
652+ description: "Update a local workspace policy.",
653+ inputSchema: updatePolicyInputSchema,
654+ outputSchema: localWorkspacePolicyOutputSchema,
655+ execute: ({
656+ policyId,
657+ payload,
658+ }: {
659+ policyId: string;
660+ payload: UpdatePolicyPayload;
661+ }) =>
662+ runRuntimeEffect({
663+ effect: updatePolicy({
664+ workspaceId: input.workspaceId,
665+ policyId: policyId as never,
666+ payload,
667+ }),
668+ runtimeLayer,
669+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
670+ }),
671+ },
672+ metadata,
673+ }),
674+ "executor.policies.remove": toTool({
675+ tool: {
676+ description: "Remove a local workspace policy.",
677+ inputSchema: policyIdInputSchema,
678+ outputSchema: removeResultSchema,
679+ execute: ({ policyId }: { policyId: string }) =>
680+ runRuntimeEffect({
681+ effect: removePolicy({
682+ workspaceId: input.workspaceId,
683+ policyId: policyId as never,
684+ }),
685+ runtimeLayer,
686+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
687+ }),
688+ },
689+ metadata,
690+ }),
691+ };
692+};
packages/platform/internal/tsconfig.json+13−0
@@ -0,0 +1,13 @@
1+{
2+ "compilerOptions": {
3+ "target": "ES2022",
4+ "module": "ESNext",
5+ "moduleResolution": "Bundler",
6+ "strict": true,
7+ "skipLibCheck": true,
8+ "noEmit": true,
9+ "lib": ["ES2022"],
10+ "types": ["bun-types", "node"]
11+ },
12+ "include": ["src/**/*.ts"]
13+}
packages/platform/sdk/package.jsonrenamed from packages/platform/control-plane/package.json+36−25
@@ -1,54 +1,65 @@
11 {
2− "name": "@executor/control-plane",
2+ "name": "@executor/platform-sdk",
33 "private": true,
44 "type": "module",
55 "exports": {
66 ".": "./src/index.ts",
7− "./client": "./src/client.ts",
8− "./schema": "./src/schema/index.ts"
7+ "./errors": "./src/errors.ts",
8+ "./string-schemas": "./src/string-schemas.ts",
9+ "./executions/contracts": "./src/executions/contracts.ts",
10+ "./schema": "./src/schema/index.ts",
11+ "./runtime": "./src/runtime/index.ts",
12+ "./local/contracts": "./src/local/contracts.ts",
13+ "./local/operations": "./src/local/operations.ts",
14+ "./local/secrets": "./src/local/secrets.ts",
15+ "./policies/contracts": "./src/policies/contracts.ts",
16+ "./policies/operations": "./src/policies/operations.ts",
17+ "./sources/contracts": "./src/sources/contracts.ts",
18+ "./sources/discovery": "./src/sources/discovery.ts",
19+ "./sources/inspection": "./src/sources/inspection.ts",
20+ "./sources/operations": "./src/sources/operations.ts"
921 },
1022 "imports": {
11− "#schema": "./src/schema/index.ts",
12− "#domain": "./src/domain/index.ts",
13− "#api": "./src/api/index.ts",
14− "#runtime": "./src/runtime/index.ts"
23+ "#runtime": "./src/runtime/index.ts",
24+ "#schema": "./src/schema/index.ts"
1525 },
1626 "scripts": {
1727 "fixture:release:capture": "bun run ./src/runtime/local/capture-release-workspace-fixture.ts",
18− "typecheck": "bunx tsc --noEmit -p tsconfig.json",
1928 "test": "vitest run",
20− "test:watch": "vitest"
29+ "test:watch": "vitest",
30+ "typecheck": "bunx tsc --noEmit -p tsconfig.json"
2131 },
2232 "dependencies": {
2333 "@effect/platform": "catalog:",
24− "@executor/source-core": "workspace:*",
25− "@executor/source-builtins": "workspace:*",
34+ "@effect/platform-node": "catalog:",
2635 "@executor/auth-mcp-oauth": "workspace:*",
2736 "@executor/auth-oauth2": "workspace:*",
2837 "@executor/codemode-core": "workspace:*",
29− "@executor/source-google-discovery": "workspace:*",
30− "@executor/source-graphql": "workspace:*",
3138 "@executor/ir": "workspace:*",
32− "@executor/source-mcp": "workspace:*",
33− "@executor/source-openapi": "workspace:*",
3439 "@executor/runtime-deno-subprocess": "workspace:*",
3540 "@executor/runtime-quickjs": "workspace:*",
3641 "@executor/runtime-ses": "workspace:*",
37− "effect": "catalog:",
38− "graphql": "^16.12.0",
39− "jsonc-parser": "3.3.1",
40− "postgres": "^3.4.8",
41− "prettier": "^3.8.1",
42− "typescript": "^5.9.3"
42+ "@executor/source-core": "workspace:*",
43+ "@executor/source-builtins": "workspace:*",
44+ "@executor/source-google-discovery": "workspace:*",
45+ "@executor/source-graphql": "workspace:*",
46+ "@executor/source-mcp": "workspace:*",
47+ "@executor/source-openapi": "workspace:*",
48+ "effect": "catalog:"
4349 },
4450 "devDependencies": {
4551 "@effect/vitest": "catalog:",
46− "@types/node": "catalog:",
47− "@effect/platform-node": "catalog:",
52+ "@executor/platform-api": "workspace:*",
53+ "@executor/platform-internal": "workspace:*",
4854 "@modelcontextprotocol/sdk": "^1.26.0",
55+ "@types/node": "catalog:",
4956 "bun-types": "catalog:",
50− "vitest": "catalog:",
51− "zod": "catalog:"
57+ "graphql": "^16.12.0",
58+ "jsonc-parser": "3.3.1",
59+ "postgres": "^3.4.8",
60+ "prettier": "^3.8.1",
61+ "typescript": "^5.9.3",
62+ "vitest": "catalog:"
5263 },
5364 "version": "0.0.0"
5465 }
packages/platform/sdk/src/errors.tsrenamed from packages/platform/control-plane/src/api/errors.ts+0−0

File renamed without changes.

packages/platform/sdk/src/executions/contracts.ts+21−0
@@ -0,0 +1,21 @@
1+import * as Schema from "effect/Schema";
2+
3+import { TrimmedNonEmptyStringSchema } from "../string-schemas";
4+
5+export const CreateExecutionPayloadSchema = Schema.Struct({
6+ code: TrimmedNonEmptyStringSchema,
7+ interactionMode: Schema.optional(
8+ Schema.Literal("live", "live_form", "detach"),
9+ ),
10+});
11+
12+export type CreateExecutionPayload = typeof CreateExecutionPayloadSchema.Type;
13+
14+export const ResumeExecutionPayloadSchema = Schema.Struct({
15+ responseJson: Schema.optional(Schema.String),
16+ interactionMode: Schema.optional(
17+ Schema.Literal("live", "live_form", "detach"),
18+ ),
19+});
20+
21+export type ResumeExecutionPayload = typeof ResumeExecutionPayloadSchema.Type;
packages/platform/sdk/src/index.ts+12−0
@@ -0,0 +1,12 @@
1+export * from "./errors";
2+export * from "./string-schemas";
3+export * from "./executions/contracts";
4+export * from "./local/contracts";
5+export * from "./local/operations";
6+export * from "./local/secrets";
7+export * from "./policies/contracts";
8+export * from "./policies/operations";
9+export * from "./sources/contracts";
10+export * from "./sources/discovery";
11+export * from "./sources/inspection";
12+export * from "./sources/operations";
packages/platform/sdk/src/local/contracts.tsrenamed from packages/platform/control-plane/src/api/local/api.ts+1−52
@@ -1,13 +1,6 @@
1−import { HttpApiEndpoint, HttpApiGroup, HttpApiSchema } from "@effect/platform";
2−import { LocalInstallationSchema, SecretMaterialPurposeSchema } from "#schema";
1+import { SecretMaterialPurposeSchema } from "../schema";
32 import * as Schema from "effect/Schema";
43
5−import {
6− ControlPlaneBadRequestError,
7− ControlPlaneNotFoundError,
8− ControlPlaneStorageError,
9−} from "../errors";
10−
114 export const SecretProviderSchema = Schema.Struct({
125 id: Schema.String,
136 name: Schema.String,
@@ -23,8 +16,6 @@ export const InstanceConfigSchema = Schema.Struct({ · 9 unmodified lines
2316 export type SecretProvider = typeof SecretProviderSchema.Type;
2417 export type InstanceConfig = typeof InstanceConfigSchema.Type;
2518
26−// -- Secrets CRUD schemas ---------------------------------------------------
27−
2819 export const SecretLinkedSourceSchema = Schema.Struct({
2920 sourceId: Schema.String,
3021 sourceName: Schema.String,
@@ -87,45 +78,3 @@ export const DeleteSecretResultSchema = Schema.Struct({ · 56 unmodified lines
8778 });
8879
8980 export type DeleteSecretResult = typeof DeleteSecretResultSchema.Type;
90−
91−// -- API group --------------------------------------------------------------
92−
93−export class LocalApi extends HttpApiGroup.make("local")
94− .add(
95− HttpApiEndpoint.get("installation")`/local/installation`
96− .addSuccess(LocalInstallationSchema)
97− .addError(ControlPlaneBadRequestError)
98− .addError(ControlPlaneNotFoundError)
99− .addError(ControlPlaneStorageError),
100− )
101− .add(
102− HttpApiEndpoint.get("config")`/local/config`
103− .addSuccess(InstanceConfigSchema),
104− )
105− .add(
106− HttpApiEndpoint.get("listSecrets")`/local/secrets`
107− .addSuccess(Schema.Array(SecretListItemSchema))
108− .addError(ControlPlaneStorageError),
109− )
110− .add(
111− HttpApiEndpoint.post("createSecret")`/local/secrets`
112− .setPayload(CreateSecretPayloadSchema)
113− .addSuccess(CreateSecretResultSchema)
114− .addError(ControlPlaneBadRequestError)
115− .addError(ControlPlaneStorageError),
116− )
117− .add(
118− HttpApiEndpoint.patch("updateSecret")`/local/secrets/${HttpApiSchema.param("secretId", Schema.String)}`
119− .setPayload(UpdateSecretPayloadSchema)
120− .addSuccess(UpdateSecretResultSchema)
121− .addError(ControlPlaneBadRequestError)
122− .addError(ControlPlaneNotFoundError)
123− .addError(ControlPlaneStorageError),
124− )
125− .add(
126− HttpApiEndpoint.del("deleteSecret")`/local/secrets/${HttpApiSchema.param("secretId", Schema.String)}`
127− .addSuccess(DeleteSecretResultSchema)
128− .addError(ControlPlaneNotFoundError)
129− .addError(ControlPlaneStorageError),
130− )
131− .prefix("/v1") {}
packages/platform/sdk/src/local/operations.tsrenamed from packages/platform/control-plane/src/runtime/local/operations.ts+112−94
@@ -3,28 +3,29 @@ import type { · 2 unmodified lines
33 ExecutionInteraction,
44 Source,
55 WorkspaceId,
6−} from "#schema";
6+} from "../schema";
77 import {
88 SourceIdSchema,
99 WorkspaceIdSchema,
10−} from "#schema";
10+} from "../schema";
1111 import * as Effect from "effect/Effect";
1212 import * as Option from "effect/Option";
1313
14−import { submitExecutionInteractionResponse } from "../execution/service";
15−import { LiveExecutionManagerService } from "../execution/live";
14+import { submitExecutionInteractionResponse } from "../runtime/execution/service";
15+import { LiveExecutionManagerService } from "../runtime/execution/live";
16+import { operationErrors } from "../runtime/policy/operation-errors";
17+import { resolveLocalWorkspaceContext } from "../runtime/local/config";
1618 import {
17− operationErrors,
18−} from "../policy/operation-errors";
19−import { resolveLocalWorkspaceContext } from "./config";
20−import { InstallationStore, LocalInstallationStore } from "./storage";
21−import { getRuntimeLocalWorkspaceOption } from "./runtime-context";
22−import { RuntimeSourceAuthServiceTag } from "../sources/source-auth-service";
19+ InstallationStore,
20+ LocalInstallationStore,
21+} from "../runtime/local/storage";
22+import { getRuntimeLocalWorkspaceOption } from "../runtime/local/runtime-context";
23+import { RuntimeSourceAuthServiceTag } from "../runtime/sources/source-auth-service";
2324 import {
2425 createSourceCredentialSelectionBearerContent,
2526 createSourceCredentialSelectionNoneContent,
26−} from "../sources/source-credential-interactions";
27−import { ControlPlaneStore } from "../store";
27+} from "../runtime/sources/source-credential-interactions";
28+import { ControlPlaneStore } from "../runtime/store";
2829
2930 const localOps = {
3031 installation: operationErrors("local.installation.get"),
@@ -59,13 +60,15 @@ const trimOrNull = (value: string | null | undefined): string | null => { · 28 unmodified lines
5960 return trimmed.length > 0 ? trimmed : null;
6061 };
6162
62−const decodeSourceCredentialInteraction = (
63+export const decodeSourceCredentialInteraction = (
6364 interaction: ExecutionInteraction,
6465 ): Omit<SourceCredentialInteraction, "sourceLabel" | "endpoint"> | null => {
6566 try {
66− if (interaction.purpose !== "source_connect_oauth2"
67− && interaction.purpose !== "source_connect_secret"
68− && interaction.purpose !== "elicitation") {
67+ if (
68+ interaction.purpose !== "source_connect_oauth2" &&
69+ interaction.purpose !== "source_connect_secret" &&
70+ interaction.purpose !== "elicitation"
71+ ) {
6972 return null;
7073 }
7174
@@ -80,18 +83,25 @@ const decodeSourceCredentialInteraction = ( · 8 unmodified lines
8083 return null;
8184 }
8285
83− if (payload.path !== "executor.sources.add" || args.kind !== "openapi") {
86+ if (payload.path !== "executor.sources.add") {
8487 return null;
8588 }
86− const effectivePurpose = interaction.purpose === "elicitation"
87− ? elicitation.mode === "url"
88− ? "source_connect_oauth2"
89− : "source_connect_secret"
90− : interaction.purpose;
91− if (effectivePurpose === "source_connect_oauth2" && elicitation.mode !== "url") {
89+ const effectivePurpose =
90+ interaction.purpose === "elicitation"
91+ ? elicitation.mode === "url"
92+ ? "source_connect_oauth2"
93+ : "source_connect_secret"
94+ : interaction.purpose;
95+ if (
96+ effectivePurpose === "source_connect_oauth2" &&
97+ elicitation.mode !== "url"
98+ ) {
9299 return null;
93100 }
94− if (effectivePurpose === "source_connect_secret" && elicitation.mode !== "form") {
101+ if (
102+ effectivePurpose === "source_connect_secret" &&
103+ elicitation.mode !== "form"
104+ ) {
95105 return null;
96106 }
97107
@@ -127,45 +137,49 @@ const loadSourceCredentialInteraction = (input: { · 29 unmodified lines
127137 const store = yield* ControlPlaneStore;
128138 const sourceAuthService = yield* RuntimeSourceAuthServiceTag;
129139
130− const stored = yield* store.executionInteractions.getById(input.interactionId).pipe(
131− Effect.mapError((error) =>
132− input.operation.unknownStorage(
133− error,
134− `Failed loading execution interaction ${input.interactionId}`,
140+ const stored = yield* store.executionInteractions
141+ .getById(input.interactionId)
142+ .pipe(
143+ Effect.mapError((error) =>
144+ input.operation.unknownStorage(
145+ error,
146+ `Failed loading execution interaction ${input.interactionId}`,
147+ ),
135148 ),
136− ),
137− );
149+ );
138150
139151 if (Option.isNone(stored)) {
140152 return yield* input.operation.notFound(
141− "Source credential request not found",
142− `interactionId=${input.interactionId}`,
143− );
153+ "Source credential request not found",
154+ `interactionId=${input.interactionId}`,
155+ );
144156 }
145157
146158 const decoded = decodeSourceCredentialInteraction(stored.value);
147159 if (
148− decoded === null
149− || decoded.workspaceId !== input.workspaceId
150− || decoded.sourceId !== input.sourceId
160+ decoded === null ||
161+ decoded.workspaceId !== input.workspaceId ||
162+ decoded.sourceId !== input.sourceId
151163 ) {
152164 return yield* input.operation.notFound(
153− "Source credential request not found",
154− `workspaceId=${input.workspaceId} sourceId=${input.sourceId} interactionId=${input.interactionId}`,
155− );
165+ "Source credential request not found",
166+ `workspaceId=${input.workspaceId} sourceId=${input.sourceId} interactionId=${input.interactionId}`,
167+ );
156168 }
157169
158− const source = yield* sourceAuthService.getSourceById({
159− workspaceId: input.workspaceId,
160− sourceId: input.sourceId,
161− }).pipe(
162− Effect.mapError((error) =>
163− input.operation.unknownStorage(
164− error,
165− `Failed loading source ${input.sourceId}`,
170+ const source = yield* sourceAuthService
171+ .getSourceById({
172+ workspaceId: input.workspaceId,
173+ sourceId: input.sourceId,
174+ })
175+ .pipe(
176+ Effect.mapError((error) =>
177+ input.operation.unknownStorage(
178+ error,
179+ `Failed loading source ${input.sourceId}`,
180+ ),
166181 ),
167− ),
168− );
182+ );
169183
170184 return {
171185 ...decoded,
@@ -177,23 +191,23 @@ const loadSourceCredentialInteraction = (input: { · 5 unmodified lines
177191 export const getLocalInstallation = () =>
178192 Effect.gen(function* () {
179193 const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption();
180− const context = runtimeLocalWorkspace?.context
181− ?? (yield* resolveLocalWorkspaceContext().pipe(
194+ const context =
195+ runtimeLocalWorkspace?.context ??
196+ (yield* resolveLocalWorkspaceContext().pipe(
182197 Effect.mapError((error) =>
183198 localOps.installation.unknownStorage(
184199 error,
185200 "Failed resolving local workspace context",
186201 ),
187− ),
202+ ),
188203 ));
189204
190− const installationStore = runtimeLocalWorkspace !== null
191− ? yield* InstallationStore
192− : LocalInstallationStore;
205+ const installationStore =
206+ runtimeLocalWorkspace !== null
207+ ? yield* InstallationStore
208+ : LocalInstallationStore;
193209
194− return yield* installationStore.load(
195− context,
196− );
210+ return yield* installationStore.load(context);
197211 });
198212
199213 export const getSourceCredentialInteraction = (input: {
@@ -223,9 +237,9 @@ export const submitSourceCredentialInteraction = (input: { · 23 unmodified lines
223237
224238 if (interaction.status !== "pending") {
225239 return yield* localOps.sourceCredentialSubmit.badRequest(
226− "Source credential request is no longer active",
227− `interactionId=${interaction.interactionId} status=${interaction.status}`,
228− );
240+ "Source credential request is no longer active",
241+ `interactionId=${interaction.interactionId} status=${interaction.status}`,
242+ );
229243 }
230244
231245 const liveExecutionManager = yield* LiveExecutionManagerService;
@@ -249,15 +263,15 @@ export const submitSourceCredentialInteraction = (input: { · 17 unmodified lines
249263 localOps.sourceCredentialSubmit.unknownStorage(
250264 error,
251265 `Failed resuming execution for interaction ${interaction.interactionId}`,
252− )
266+ ),
253267 ),
254268 );
255269
256270 if (!persisted) {
257271 return yield* localOps.sourceCredentialSubmit.badRequest(
258− "Source credential request is no longer resumable",
259− `interactionId=${interaction.interactionId}`,
260− );
272+ "Source credential request is no longer resumable",
273+ `interactionId=${interaction.interactionId}`,
274+ );
261275 }
262276 }
263277
@@ -289,15 +303,15 @@ export const submitSourceCredentialInteraction = (input: { · 25 unmodified lines
289303 localOps.sourceCredentialSubmit.unknownStorage(
290304 error,
291305 `Failed resuming execution for interaction ${interaction.interactionId}`,
292− )
306+ ),
293307 ),
294308 );
295309
296310 if (!persisted) {
297311 return yield* localOps.sourceCredentialSubmit.badRequest(
298− "Source credential request is no longer resumable",
299− `interactionId=${interaction.interactionId}`,
300− );
312+ "Source credential request is no longer resumable",
313+ `interactionId=${interaction.interactionId}`,
314+ );
301315 }
302316 }
303317
@@ -311,23 +325,25 @@ export const submitSourceCredentialInteraction = (input: { · 7 unmodified lines
311325 const token = trimOrNull(input.token);
312326 if (token === null) {
313327 return yield* localOps.sourceCredentialSubmit.badRequest(
314− "Missing token",
315− `interactionId=${interaction.interactionId}`,
316− );
328+ "Missing token",
329+ `interactionId=${interaction.interactionId}`,
330+ );
317331 }
318332
319333 const sourceAuthService = yield* RuntimeSourceAuthServiceTag;
320− const tokenRef = yield* sourceAuthService.storeSecretMaterial({
321− purpose: "auth_material",
322− value: token,
323− }).pipe(
324− Effect.mapError((error) =>
325− localOps.sourceCredentialSubmit.unknownStorage(
326− error,
327− `Failed storing credential material for interaction ${interaction.interactionId}`,
334+ const tokenRef = yield* sourceAuthService
335+ .storeSecretMaterial({
336+ purpose: "auth_material",
337+ value: token,
338+ })
339+ .pipe(
340+ Effect.mapError((error) =>
341+ localOps.sourceCredentialSubmit.unknownStorage(
342+ error,
343+ `Failed storing credential material for interaction ${interaction.interactionId}`,
344+ ),
328345 ),
329− ),
330− );
346+ );
331347 const resumed = yield* liveExecutionManager.resolveInteraction({
332348 executionId: interaction.executionId,
333349 response: {
@@ -348,15 +364,15 @@ export const submitSourceCredentialInteraction = (input: { · 14 unmodified lines
348364 localOps.sourceCredentialSubmit.unknownStorage(
349365 error,
350366 `Failed resuming execution for interaction ${interaction.interactionId}`,
351− )
367+ ),
352368 ),
353369 );
354370
355371 if (!persisted) {
356372 return yield* localOps.sourceCredentialSubmit.badRequest(
357− "Source credential request is no longer resumable",
358− `interactionId=${interaction.interactionId}`,
359− );
373+ "Source credential request is no longer resumable",
374+ `interactionId=${interaction.interactionId}`,
375+ );
360376 }
361377 }
362378
@@ -378,12 +394,14 @@ export const completeSourceCredentialSetup = (input: { · 15 unmodified lines
378394 Effect.gen(function* () {
379395 const sourceAuthService = yield* RuntimeSourceAuthServiceTag;
380396
381− return yield* sourceAuthService.completeSourceCredentialSetup(input).pipe(
382− Effect.mapError((error) =>
383− localOps.sourceCredentialComplete.unknownStorage(
384− error,
385− "Failed completing source credential setup",
397+ return yield* sourceAuthService
398+ .completeSourceCredentialSetup(input)
399+ .pipe(
400+ Effect.mapError((error) =>
401+ localOps.sourceCredentialComplete.unknownStorage(
402+ error,
403+ "Failed completing source credential setup",
404+ ),
386405 ),
387− ),
388− );
406+ );
389407 });
packages/platform/sdk/src/local/secrets.ts+283−0
@@ -0,0 +1,283 @@
1+import * as Effect from "effect/Effect";
2+import * as Option from "effect/Option";
3+
4+import { SecretMaterialIdSchema } from "../schema";
5+import {
6+ type CreateSecretPayload,
7+ type CreateSecretResult,
8+ type DeleteSecretResult,
9+ type InstanceConfig,
10+ type SecretListItem,
11+ type UpdateSecretPayload,
12+ type UpdateSecretResult,
13+} from "./contracts";
14+import {
15+ ControlPlaneBadRequestError,
16+ ControlPlaneNotFoundError,
17+ ControlPlaneStorageError,
18+} from "../errors";
19+import { requireRuntimeLocalWorkspace } from "../runtime/local/runtime-context";
20+import {
21+ createDefaultSecretMaterialDeleter,
22+ createDefaultSecretMaterialStorer,
23+ createDefaultSecretMaterialUpdater,
24+ ENV_SECRET_PROVIDER_ID,
25+ KEYCHAIN_SECRET_PROVIDER_ID,
26+ LOCAL_SECRET_PROVIDER_ID,
27+ parseSecretStoreProviderId,
28+ resolveDefaultSecretStoreProviderId,
29+} from "../runtime/local/secret-material-providers";
30+import { RuntimeSourceStoreService } from "../runtime/sources/source-store";
31+import { ControlPlaneStore } from "../runtime/store";
32+
33+const SECRET_STORE_PROVIDER_ENV = "EXECUTOR_SECRET_STORE_PROVIDER";
34+
35+const secretStorageError = (operation: string, message: string) =>
36+ new ControlPlaneStorageError({
37+ operation,
38+ message,
39+ details: message,
40+ });
41+
42+export const getLocalInstanceConfig = (): Effect.Effect<InstanceConfig> => {
43+ const explicitDefaultStoreProvider = parseSecretStoreProviderId(
44+ process.env[SECRET_STORE_PROVIDER_ENV],
45+ );
46+ const providers = [
47+ {
48+ id: LOCAL_SECRET_PROVIDER_ID,
49+ name: "Local store",
50+ canStore: true,
51+ },
52+ ];
53+
54+ if (process.platform === "darwin" || process.platform === "linux") {
55+ providers.push({
56+ id: KEYCHAIN_SECRET_PROVIDER_ID,
57+ name:
58+ process.platform === "darwin" ? "macOS Keychain" : "Desktop Keyring",
59+ canStore:
60+ process.platform === "darwin" ||
61+ explicitDefaultStoreProvider === KEYCHAIN_SECRET_PROVIDER_ID,
62+ });
63+ }
64+
65+ providers.push({
66+ id: ENV_SECRET_PROVIDER_ID,
67+ name: "Environment variable",
68+ canStore: false,
69+ });
70+
71+ return resolveDefaultSecretStoreProviderId({
72+ storeProviderId: explicitDefaultStoreProvider ?? undefined,
73+ }).pipe(
74+ Effect.map((resolvedDefaultStoreProvider) => ({
75+ platform: process.platform,
76+ secretProviders: providers,
77+ defaultSecretStoreProvider: resolvedDefaultStoreProvider,
78+ })),
79+ );
80+};
81+
82+export const listLocalSecrets = () =>
83+ Effect.gen(function* () {
84+ const store = yield* ControlPlaneStore;
85+ const sourceStore = yield* RuntimeSourceStoreService;
86+ const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace().pipe(
87+ Effect.mapError(() =>
88+ secretStorageError("secrets.list", "Failed resolving local workspace."),
89+ ),
90+ );
91+ const rows = yield* store.secretMaterials
92+ .listAll()
93+ .pipe(
94+ Effect.mapError(() =>
95+ secretStorageError("secrets.list", "Failed listing secrets."),
96+ ),
97+ );
98+ const linkedSourcesMap = yield* sourceStore
99+ .listLinkedSecretSourcesInWorkspace(
100+ runtimeLocalWorkspace.installation.workspaceId,
101+ {
102+ actorAccountId: runtimeLocalWorkspace.installation.accountId,
103+ },
104+ )
105+ .pipe(
106+ Effect.mapError(() =>
107+ secretStorageError("secrets.list", "Failed loading linked sources."),
108+ ),
109+ );
110+
111+ return rows.map((row) => ({
112+ ...row,
113+ linkedSources: linkedSourcesMap.get(row.id) ?? [],
114+ }));
115+ });
116+
117+export const createLocalSecret = (payload: CreateSecretPayload) =>
118+ Effect.gen(function* () {
119+ const name = payload.name.trim();
120+ const value = payload.value;
121+ const purpose = payload.purpose ?? "auth_material";
122+ const requestedProviderId =
123+ payload.providerId === undefined
124+ ? null
125+ : parseSecretStoreProviderId(payload.providerId);
126+
127+ if (name.length === 0) {
128+ return yield* new ControlPlaneBadRequestError({
129+ operation: "secrets.create",
130+ message: "Secret name is required.",
131+ details: "Secret name is required.",
132+ });
133+ }
134+ if (payload.providerId !== undefined && requestedProviderId === null) {
135+ return yield* new ControlPlaneBadRequestError({
136+ operation: "secrets.create",
137+ message: `Unsupported secret provider: ${payload.providerId}`,
138+ details: `Unsupported secret provider: ${payload.providerId}`,
139+ });
140+ }
141+
142+ const store = yield* ControlPlaneStore;
143+ const storeSecretMaterial = createDefaultSecretMaterialStorer({
144+ rows: store,
145+ ...(requestedProviderId ? { storeProviderId: requestedProviderId } : {}),
146+ });
147+ const ref = yield* storeSecretMaterial({
148+ name,
149+ purpose,
150+ value,
151+ }).pipe(
152+ Effect.mapError((cause) =>
153+ secretStorageError(
154+ "secrets.create",
155+ cause instanceof Error ? cause.message : "Failed creating secret.",
156+ ),
157+ ),
158+ );
159+ const secretId = SecretMaterialIdSchema.make(ref.handle);
160+ const created = yield* store.secretMaterials
161+ .getById(secretId)
162+ .pipe(
163+ Effect.mapError(() =>
164+ secretStorageError(
165+ "secrets.create",
166+ "Failed loading created secret.",
167+ ),
168+ ),
169+ );
170+
171+ if (Option.isNone(created)) {
172+ return yield* secretStorageError(
173+ "secrets.create",
174+ `Created secret not found: ${ref.handle}`,
175+ );
176+ }
177+
178+ return {
179+ id: created.value.id,
180+ name: created.value.name,
181+ providerId: created.value.providerId,
182+ purpose: created.value.purpose,
183+ createdAt: created.value.createdAt,
184+ updatedAt: created.value.updatedAt,
185+ } satisfies CreateSecretResult;
186+ });
187+
188+export const updateLocalSecret = (input: {
189+ secretId: string;
190+ payload: UpdateSecretPayload;
191+}) =>
192+ Effect.gen(function* () {
193+ const secretId = SecretMaterialIdSchema.make(input.secretId);
194+ const store = yield* ControlPlaneStore;
195+
196+ const existing = yield* store.secretMaterials
197+ .getById(secretId)
198+ .pipe(
199+ Effect.mapError(() =>
200+ secretStorageError("secrets.update", "Failed looking up secret."),
201+ ),
202+ );
203+
204+ if (Option.isNone(existing)) {
205+ return yield* new ControlPlaneNotFoundError({
206+ operation: "secrets.update",
207+ message: `Secret not found: ${input.secretId}`,
208+ details: `Secret not found: ${input.secretId}`,
209+ });
210+ }
211+
212+ const update: { name?: string | null; value?: string } = {};
213+ if (input.payload.name !== undefined)
214+ update.name = input.payload.name.trim() || null;
215+ if (input.payload.value !== undefined) update.value = input.payload.value;
216+
217+ const updateSecretMaterial = createDefaultSecretMaterialUpdater({
218+ rows: store,
219+ });
220+ const updated = yield* updateSecretMaterial({
221+ ref: {
222+ providerId: existing.value.providerId,
223+ handle: existing.value.id,
224+ },
225+ ...update,
226+ }).pipe(
227+ Effect.mapError(() =>
228+ secretStorageError("secrets.update", "Failed updating secret."),
229+ ),
230+ );
231+
232+ return {
233+ id: updated.id,
234+ providerId: updated.providerId,
235+ name: updated.name,
236+ purpose: updated.purpose,
237+ createdAt: updated.createdAt,
238+ updatedAt: updated.updatedAt,
239+ } satisfies UpdateSecretResult;
240+ });
241+
242+export const deleteLocalSecret = (secretId: string) =>
243+ Effect.gen(function* () {
244+ const parsedSecretId = SecretMaterialIdSchema.make(secretId);
245+ const store = yield* ControlPlaneStore;
246+
247+ const existing = yield* store.secretMaterials
248+ .getById(parsedSecretId)
249+ .pipe(
250+ Effect.mapError(() =>
251+ secretStorageError("secrets.delete", "Failed looking up secret."),
252+ ),
253+ );
254+
255+ if (Option.isNone(existing)) {
256+ return yield* new ControlPlaneNotFoundError({
257+ operation: "secrets.delete",
258+ message: `Secret not found: ${secretId}`,
259+ details: `Secret not found: ${secretId}`,
260+ });
261+ }
262+
263+ const deleteSecretMaterial = createDefaultSecretMaterialDeleter({
264+ rows: store,
265+ });
266+ const removed = yield* deleteSecretMaterial({
267+ providerId: existing.value.providerId,
268+ handle: existing.value.id,
269+ }).pipe(
270+ Effect.mapError(() =>
271+ secretStorageError("secrets.delete", "Failed removing secret."),
272+ ),
273+ );
274+
275+ if (!removed) {
276+ return yield* secretStorageError(
277+ "secrets.delete",
278+ `Failed removing secret: ${secretId}`,
279+ );
280+ }
281+
282+ return { removed: true } satisfies DeleteSecretResult;
283+ });
packages/platform/sdk/src/policies/contracts.ts+23−0
@@ -0,0 +1,23 @@
1+import {
2+ LocalWorkspacePolicyApprovalModeSchema,
3+ LocalWorkspacePolicyEffectSchema,
4+} from "../schema";
5+import * as Schema from "effect/Schema";
6+
7+import { OptionalTrimmedNonEmptyStringSchema } from "../string-schemas";
8+
9+const LocalWorkspacePolicyPayloadSchema = Schema.Struct({
10+ resourcePattern: OptionalTrimmedNonEmptyStringSchema,
11+ effect: Schema.optional(LocalWorkspacePolicyEffectSchema),
12+ approvalMode: Schema.optional(LocalWorkspacePolicyApprovalModeSchema),
13+ priority: Schema.optional(Schema.Number),
14+ enabled: Schema.optional(Schema.Boolean),
15+});
16+
17+export const CreatePolicyPayloadSchema = LocalWorkspacePolicyPayloadSchema;
18+
19+export type CreatePolicyPayload = typeof CreatePolicyPayloadSchema.Type;
20+
21+export const UpdatePolicyPayloadSchema = LocalWorkspacePolicyPayloadSchema;
22+
23+export type UpdatePolicyPayload = typeof UpdatePolicyPayloadSchema.Type;
packages/platform/sdk/src/policies/operations.tsrenamed from packages/platform/control-plane/src/runtime/policy/policies-operations.ts+126−72
@@ -3,38 +3,32 @@ import { createHash } from "node:crypto"; · 2 unmodified lines
33 import type {
44 CreatePolicyPayload,
55 UpdatePolicyPayload,
6−} from "../../api/policies/api";
6+} from "./contracts";
77 import {
88 PolicyIdSchema,
99 type LocalExecutorConfig,
1010 type LocalWorkspacePolicy,
1111 type PolicyId,
1212 type WorkspaceId,
13−} from "#schema";
13+} from "../schema";
1414 import * as Effect from "effect/Effect";
1515
16−import {
17− requireRuntimeLocalWorkspace,
18−} from "../local/runtime-context";
16+import { requireRuntimeLocalWorkspace } from "../runtime/local/runtime-context";
1917 import type {
2018 WorkspaceConfigStoreShape,
2119 WorkspaceStateStoreShape,
22−} from "../local/storage";
20+} from "../runtime/local/storage";
2321 import {
2422 WorkspaceConfigStore,
2523 WorkspaceStateStore,
26−} from "../local/storage";
27−import type { ResolvedLocalWorkspaceContext } from "../local/config";
28−import {
29− type LocalWorkspaceState,
30−} from "../local/workspace-state";
31−import {
32− derivePolicyConfigKey,
33−} from "../local/workspace-sync";
24+} from "../runtime/local/storage";
25+import type { ResolvedLocalWorkspaceContext } from "../runtime/local/config";
26+import { type LocalWorkspaceState } from "../runtime/local/workspace-state";
27+import { derivePolicyConfigKey } from "../runtime/local/workspace-sync";
3428 import {
3529 type OperationErrors,
3630 operationErrors,
37−} from "./operation-errors";
31+} from "../runtime/policy/operation-errors";
3832
3933 const policyOps = {
4034 list: operationErrors("policies.list"),
@@ -44,8 +38,7 @@ const policyOps = { · 3 unmodified lines
4438 remove: operationErrors("policies.remove"),
4539 } as const;
4640
47−const cloneJson = <T>(value: T): T =>
48− JSON.parse(JSON.stringify(value)) as T;
41+const cloneJson = <T>(value: T): T => JSON.parse(JSON.stringify(value)) as T;
4942
5043 const localPolicyIdForKey = (input: {
5144 workspaceRoot: string;
@@ -62,10 +55,12 @@ const toLocalWorkspacePolicy = (input: { · 10 unmodified lines
6255 policyConfig: NonNullable<LocalExecutorConfig["policies"]>[string];
6356 state: LocalWorkspaceState["policies"][string] | undefined;
6457 }): LocalWorkspacePolicy => ({
65− id: input.state?.id ?? localPolicyIdForKey({
66− workspaceRoot: input.workspaceRoot,
67− key: input.key,
68− }),
58+ id:
59+ input.state?.id ??
60+ localPolicyIdForKey({
61+ workspaceRoot: input.workspaceRoot,
62+ key: input.key,
63+ }),
6964 key: input.key,
7065 workspaceId: input.workspaceId,
7166 resourcePattern: input.policyConfig.match.trim(),
@@ -79,7 +74,8 @@ const toLocalWorkspacePolicy = (input: { · 7 unmodified lines
7974
8075 export const loadRuntimeLocalWorkspacePolicies = (workspaceId: WorkspaceId) =>
8176 Effect.gen(function* () {
82− const runtimeLocalWorkspace = yield* requireRuntimeLocalWorkspace(workspaceId);
77+ const runtimeLocalWorkspace =
78+ yield* requireRuntimeLocalWorkspace(workspaceId);
8379 const workspaceConfigStore = yield* WorkspaceConfigStore;
8480 const workspaceStateStore = yield* WorkspaceStateStore;
8581 const loadedConfig = yield* workspaceConfigStore.load(
@@ -89,14 +85,16 @@ export const loadRuntimeLocalWorkspacePolicies = (workspaceId: WorkspaceId) => · 3 unmodified lines
8985 runtimeLocalWorkspace.context,
9086 );
9187
92− const policies = Object.entries(loadedConfig.config?.policies ?? {}).map(([key, policyConfig]) =>
93− toLocalWorkspacePolicy({
94− workspaceId,
95− workspaceRoot: runtimeLocalWorkspace.context.workspaceRoot,
96− key,
97− policyConfig,
98− state: workspaceState.policies[key],
99− }));
88+ const policies = Object.entries(loadedConfig.config?.policies ?? {}).map(
89+ ([key, policyConfig]) =>
90+ toLocalWorkspacePolicy({
91+ workspaceId,
92+ workspaceRoot: runtimeLocalWorkspace.context.workspaceRoot,
93+ key,
94+ policyConfig,
95+ state: workspaceState.policies[key],
96+ }),
97+ );
10098
10199 return {
102100 runtimeLocalWorkspace,
@@ -114,16 +112,19 @@ const writeLocalPolicyFiles = (input: { · 11 unmodified lines
114112 projectConfig: LocalExecutorConfig;
115113 workspaceState: LocalWorkspaceState;
116114 }) =>
117− Effect.all([
118− input.workspaceConfigStore.writeProject({
119− context: input.context,
120− config: input.projectConfig,
121− }),
122− input.workspaceStateStore.write({
123− context: input.context,
124− state: input.workspaceState,
125− }),
126− ], { discard: true }).pipe(
115+ Effect.all(
116+ [
117+ input.workspaceConfigStore.writeProject({
118+ context: input.context,
119+ config: input.projectConfig,
120+ }),
121+ input.workspaceStateStore.write({
122+ context: input.context,
123+ state: input.workspaceState,
124+ }),
125+ ],
126+ { discard: true },
127+ ).pipe(
127128 Effect.mapError((cause) =>
128129 input.operation.unknownStorage(
129130 cause,
@@ -148,7 +149,9 @@ const loadWorkspacePolicyContext = ( · 18 unmodified lines
148149 export const listPolicies = (workspaceId: WorkspaceId) =>
149150 Effect.gen(function* () {
150151 yield* loadWorkspacePolicyContext(policyOps.list, workspaceId);
151− const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(workspaceId).pipe(
152+ const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(
153+ workspaceId,
154+ ).pipe(
152155 Effect.mapError((cause) =>
153156 policyOps.list.unknownStorage(
154157 cause,
@@ -164,10 +167,15 @@ export const createPolicy = (input: { · 9 unmodified lines
164167 payload: CreatePolicyPayload;
165168 }) =>
166169 Effect.gen(function* () {
167− const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(policyOps.create, input.workspaceId);
170+ const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(
171+ policyOps.create,
172+ input.workspaceId,
173+ );
168174 const workspaceConfigStore = yield* WorkspaceConfigStore;
169175 const workspaceStateStore = yield* WorkspaceStateStore;
170− const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(input.workspaceId).pipe(
176+ const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(
177+ input.workspaceId,
178+ ).pipe(
171179 Effect.mapError((cause) =>
172180 policyOps.create.unknownStorage(
173181 cause,
@@ -176,7 +184,9 @@ export const createPolicy = (input: { · 2 unmodified lines
176184 ),
177185 );
178186 const now = Date.now();
179− const projectConfig = cloneJson(localWorkspace.loadedConfig.projectConfig ?? {});
187+ const projectConfig = cloneJson(
188+ localWorkspace.loadedConfig.projectConfig ?? {},
189+ );
180190 const policies = { ...projectConfig.policies };
181191 const key = derivePolicyConfigKey(
182192 {
@@ -190,9 +200,14 @@ export const createPolicy = (input: { · 7 unmodified lines
190200 policies[key] = {
191201 match: input.payload.resourcePattern ?? "*",
192202 action: input.payload.effect ?? "allow",
193− approval: (input.payload.approvalMode ?? "auto") === "required" ? "manual" : "auto",
203+ approval:
204+ (input.payload.approvalMode ?? "auto") === "required"
205+ ? "manual"
206+ : "auto",
194207 ...(input.payload.enabled === false ? { enabled: false } : {}),
195− ...((input.payload.priority ?? 0) !== 0 ? { priority: input.payload.priority ?? 0 } : {}),
208+ ...((input.payload.priority ?? 0) !== 0
209+ ? { priority: input.payload.priority ?? 0 }
210+ : {}),
196211 };
197212
198213 const existingState = localWorkspace.workspaceState.policies[key];
@@ -201,10 +216,12 @@ export const createPolicy = (input: { · 2 unmodified lines
201216 policies: {
202217 ...localWorkspace.workspaceState.policies,
203218 [key]: {
204− id: existingState?.id ?? localPolicyIdForKey({
205− workspaceRoot: runtimeLocalWorkspace.context.workspaceRoot,
206− key,
207− }),
219+ id:
220+ existingState?.id ??
221+ localPolicyIdForKey({
222+ workspaceRoot: runtimeLocalWorkspace.context.workspaceRoot,
223+ key,
224+ }),
208225 createdAt: existingState?.createdAt ?? now,
209226 updatedAt: now,
210227 },
@@ -238,7 +255,9 @@ export const getPolicy = (input: { · 27 unmodified lines
238255 }) =>
239256 Effect.gen(function* () {
240257 yield* loadWorkspacePolicyContext(policyOps.get, input.workspaceId);
241− const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(input.workspaceId).pipe(
258+ const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(
259+ input.workspaceId,
260+ ).pipe(
242261 Effect.mapError((cause) =>
243262 policyOps.get.unknownStorage(
244263 cause,
@@ -246,12 +265,15 @@ export const getPolicy = (input: { · 1 unmodified line
246265 ),
247266 ),
248267 );
249− const policy = localWorkspace.policies.find((candidate) => candidate.id === input.policyId) ?? null;
268+ const policy =
269+ localWorkspace.policies.find(
270+ (candidate) => candidate.id === input.policyId,
271+ ) ?? null;
250272 if (policy === null) {
251273 return yield* policyOps.get.notFound(
252− "Policy not found",
253− `workspaceId=${input.workspaceId} policyId=${input.policyId}`,
254− );
274+ "Policy not found",
275+ `workspaceId=${input.workspaceId} policyId=${input.policyId}`,
276+ );
255277 }
256278 return policy;
257279 });
@@ -262,10 +284,15 @@ export const updatePolicy = (input: { · 4 unmodified lines
262284 payload: UpdatePolicyPayload;
263285 }) =>
264286 Effect.gen(function* () {
265− const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(policyOps.update, input.workspaceId);
287+ const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(
288+ policyOps.update,
289+ input.workspaceId,
290+ );
266291 const workspaceConfigStore = yield* WorkspaceConfigStore;
267292 const workspaceStateStore = yield* WorkspaceStateStore;
268− const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(input.workspaceId).pipe(
293+ const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(
294+ input.workspaceId,
295+ ).pipe(
269296 Effect.mapError((cause) =>
270297 policyOps.update.unknownStorage(
271298 cause,
@@ -273,27 +300,43 @@ export const updatePolicy = (input: { · 1 unmodified line
273300 ),
274301 ),
275302 );
276− const existing = localWorkspace.policies.find((candidate) => candidate.id === input.policyId) ?? null;
303+ const existing =
304+ localWorkspace.policies.find(
305+ (candidate) => candidate.id === input.policyId,
306+ ) ?? null;
277307 if (existing === null) {
278308 return yield* policyOps.update.notFound(
279− "Policy not found",
280− `workspaceId=${input.workspaceId} policyId=${input.policyId}`,
281− );
309+ "Policy not found",
310+ `workspaceId=${input.workspaceId} policyId=${input.policyId}`,
311+ );
282312 }
283313
284− const projectConfig = cloneJson(localWorkspace.loadedConfig.projectConfig ?? {});
314+ const projectConfig = cloneJson(
315+ localWorkspace.loadedConfig.projectConfig ?? {},
316+ );
285317 const policies = { ...projectConfig.policies };
286318 const existingConfig = policies[existing.key]!;
287319
288320 policies[existing.key] = {
289321 ...existingConfig,
290− ...(input.payload.resourcePattern !== undefined ? { match: input.payload.resourcePattern } : {}),
291− ...(input.payload.effect !== undefined ? { action: input.payload.effect } : {}),
322+ ...(input.payload.resourcePattern !== undefined
323+ ? { match: input.payload.resourcePattern }
324+ : {}),
325+ ...(input.payload.effect !== undefined
326+ ? { action: input.payload.effect }
327+ : {}),
292328 ...(input.payload.approvalMode !== undefined
293− ? { approval: input.payload.approvalMode === "required" ? "manual" : "auto" }
329+ ? {
330+ approval:
331+ input.payload.approvalMode === "required" ? "manual" : "auto",
332+ }
333+ : {}),
334+ ...(input.payload.enabled !== undefined
335+ ? { enabled: input.payload.enabled }
336+ : {}),
337+ ...(input.payload.priority !== undefined
338+ ? { priority: input.payload.priority }
294339 : {}),
295− ...(input.payload.enabled !== undefined ? { enabled: input.payload.enabled } : {}),
296− ...(input.payload.priority !== undefined ? { priority: input.payload.priority } : {}),
297340 };
298341
299342 const updatedAt = Date.now();
@@ -336,10 +379,15 @@ export const removePolicy = (input: { · 36 unmodified lines
336379 policyId: PolicyId;
337380 }) =>
338381 Effect.gen(function* () {
339− const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(policyOps.remove, input.workspaceId);
382+ const runtimeLocalWorkspace = yield* loadWorkspacePolicyContext(
383+ policyOps.remove,
384+ input.workspaceId,
385+ );
340386 const workspaceConfigStore = yield* WorkspaceConfigStore;
341387 const workspaceStateStore = yield* WorkspaceStateStore;
342− const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(input.workspaceId).pipe(
388+ const localWorkspace = yield* loadRuntimeLocalWorkspacePolicies(
389+ input.workspaceId,
390+ ).pipe(
343391 Effect.mapError((cause) =>
344392 policyOps.remove.unknownStorage(
345393 cause,
@@ -347,16 +395,22 @@ export const removePolicy = (input: { · 1 unmodified line
347395 ),
348396 ),
349397 );
350− const existing = localWorkspace.policies.find((candidate) => candidate.id === input.policyId) ?? null;
398+ const existing =
399+ localWorkspace.policies.find(
400+ (candidate) => candidate.id === input.policyId,
401+ ) ?? null;
351402 if (existing === null) {
352403 return { removed: false };
353404 }
354405
355− const projectConfig = cloneJson(localWorkspace.loadedConfig.projectConfig ?? {});
406+ const projectConfig = cloneJson(
407+ localWorkspace.loadedConfig.projectConfig ?? {},
408+ );
356409 const policies = { ...projectConfig.policies };
357410 delete policies[existing.key];
358411
359− const { [existing.key]: _removedPolicy, ...remainingPolicies } = localWorkspace.workspaceState.policies;
412+ const { [existing.key]: _removedPolicy, ...remainingPolicies } =
413+ localWorkspace.workspaceState.policies;
360414 yield* writeLocalPolicyFiles({
361415 operation: policyOps.remove,
362416 context: runtimeLocalWorkspace.context,
packages/platform/sdk/src/runtime/__fixtures__/README.mdrenamed from packages/platform/control-plane/src/runtime/__fixtures__/README.md+1−1
@@ -35,4 +35,4 @@ On releases where you want upgrade coverage: · 34 unmodified lines
3535 1. Start from a real workspace that has a fully synced source on the released version.
3636 2. Run `bun run fixture:release:capture -- ...` for that source.
3737 3. Review the fixture for secrets before committing it.
38−4. Run `bun run --cwd packages/platform/control-plane test -- src/runtime/local/source-artifacts.test.ts`.
38+4. Run `bun run --cwd packages/platform/sdk test -- src/runtime/local/source-artifacts.test.ts`.
packages/platform/sdk/src/runtime/__fixtures__/v1.2.3-google-calendar-workspace/.executor/artifacts/sources/google-calendar.jsonrenamed from packages/platform/control-plane/src/runtime/__fixtures__/v1.2.3-google-calendar-workspace/.executor/artifacts/sources/google-calendar.json+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/__fixtures__/v1.2.3-google-calendar-workspace/.executor/artifacts/sources/google-calendar/documents/doc_3c24a6731267de91.txtrenamed from packages/platform/control-plane/src/runtime/__fixtures__/v1.2.3-google-calendar-workspace/.executor/artifacts/sources/google-calendar/documents/doc_3c24a6731267de91.txt+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/__fixtures__/v1.2.3-google-calendar-workspace/.executor/executor.jsoncrenamed from packages/platform/control-plane/src/runtime/__fixtures__/v1.2.3-google-calendar-workspace/.executor/executor.jsonc+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/__fixtures__/v1.2.3-google-calendar-workspace/.executor/state/workspace-state.jsonrenamed from packages/platform/control-plane/src/runtime/__fixtures__/v1.2.3-google-calendar-workspace/.executor/state/workspace-state.json+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/__fixtures__/v1.2.3-google-calendar-workspace/fixture.jsonrenamed from packages/platform/control-plane/src/runtime/__fixtures__/v1.2.3-google-calendar-workspace/fixture.json+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/auth/auth-artifacts.tsrenamed from packages/platform/control-plane/src/runtime/auth/auth-artifacts.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/auth/auth-leases.tsrenamed from packages/platform/control-plane/src/runtime/auth/auth-leases.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/auth/mcp-auth-provider.tsrenamed from packages/platform/control-plane/src/runtime/auth/mcp-auth-provider.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/auth/mcp-oauth.tsrenamed from packages/platform/control-plane/src/runtime/auth/mcp-oauth.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/auth/oauth-loopback.test.tsrenamed from packages/platform/control-plane/src/runtime/auth/oauth-loopback.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/auth/oauth-loopback.tsrenamed from packages/platform/control-plane/src/runtime/auth/oauth-loopback.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/auth/oauth2-pkce.tsrenamed from packages/platform/control-plane/src/runtime/auth/oauth2-pkce.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/auth/provider-grant-lifecycle.tsrenamed from packages/platform/control-plane/src/runtime/auth/provider-grant-lifecycle.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/auth/source-auth-material.test.tsrenamed from packages/platform/control-plane/src/runtime/auth/source-auth-material.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/auth/source-auth-material.tsrenamed from packages/platform/control-plane/src/runtime/auth/source-auth-material.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/catalog/catalog-typescript.tsrenamed from packages/platform/control-plane/src/runtime/catalog/catalog-typescript.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/catalog/prettier-format.tsrenamed from packages/platform/control-plane/src/runtime/catalog/prettier-format.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/catalog/schema-type-signature.tsrenamed from packages/platform/control-plane/src/runtime/catalog/schema-type-signature.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/catalog/source/reconcile.tsrenamed from packages/platform/control-plane/src/runtime/catalog/source/reconcile.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/catalog/source/runtime.test.tsrenamed from packages/platform/control-plane/src/runtime/catalog/source/runtime.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/catalog/source/runtime.tsrenamed from packages/platform/control-plane/src/runtime/catalog/source/runtime.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/catalog/source/snapshot.test.tsrenamed from packages/platform/control-plane/src/runtime/catalog/source/snapshot.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/catalog/source/sync.tsrenamed from packages/platform/control-plane/src/runtime/catalog/source/sync.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/catalog/source/type-declarations.test.tsrenamed from packages/platform/control-plane/src/runtime/catalog/source/type-declarations.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/catalog/source/type-declarations.tsrenamed from packages/platform/control-plane/src/runtime/catalog/source/type-declarations.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/control-plane-runtime.test.ts+2356−0
@@ -0,0 +1,2356 @@
1+import { createServer } from "node:http";
2+import { join } from "node:path";
3+
4+import { FileSystem } from "@effect/platform";
5+import { NodeFileSystem } from "@effect/platform-node";
6+import { describe, expect, it } from "@effect/vitest";
7+import { assertTrue } from "@effect/vitest/utils";
8+import * as Either from "effect/Either";
9+import * as Effect from "effect/Effect";
10+import * as Fiber from "effect/Fiber";
11+import * as Option from "effect/Option";
12+
13+import {
14+ ExecutionIdSchema,
15+ ExecutionInteractionIdSchema,
16+ type ExecutionInteraction,
17+ ProviderAuthGrantIdSchema,
18+ SecretMaterialIdSchema,
19+ SourceAuthSessionIdSchema,
20+ SourceIdSchema,
21+ WorkspaceOauthClientIdSchema,
22+} from "#schema";
23+import type { ToolPath } from "@executor/codemode-core";
24+import { createCatalogImportMetadata } from "@executor/source-core";
25+import { createGraphqlCatalogFragment } from "@executor/source-graphql";
26+import { createExecutorAdminToolMap } from "@executor/platform-internal";
27+
28+import {
29+ type ControlPlaneRuntime,
30+ createControlPlaneRuntime,
31+ LiveExecutionManagerService,
32+ provideControlPlaneRuntime,
33+ RuntimeExecutionResolverService,
34+} from "./index";
35+import { createSourceFromPayload } from "./sources/source-definitions";
36+import { decodeSourceCredentialSelectionContent } from "./sources/source-credential-interactions";
37+import { persistSource } from "./sources/source-store";
38+import { withControlPlaneClient } from "./execution/test-http-client";
39+import { runtimeEffectError } from "./effect-errors";
40+import {
41+ resolveLocalWorkspaceContext,
42+ writeProjectLocalExecutorConfig,
43+} from "./local/config";
44+import { writeLocalControlPlaneState } from "./local/control-plane-store";
45+import { deriveLocalInstallation } from "./local/installation";
46+import {
47+ decodeSourceCredentialInteraction,
48+ submitSourceCredentialInteraction,
49+} from "./local/operations";
50+import {
51+ buildLocalSourceArtifact,
52+ readLocalSourceArtifact,
53+ writeLocalSourceArtifact,
54+} from "./local/source-artifacts";
55+import { writeLocalWorkspaceState } from "./local/workspace-state";
56+
57+const makeRuntime = Effect.gen(function* () {
58+ const fs = yield* FileSystem.FileSystem;
59+ const workspaceRoot = yield* fs.makeTempDirectoryScoped({
60+ prefix: "executor-control-plane-runtime-",
61+ });
62+ const homeConfigPath = join(workspaceRoot, ".executor-home.jsonc");
63+ const homeStateDirectory = join(workspaceRoot, ".executor-home-state");
64+
65+ return yield* Effect.acquireRelease(
66+ createControlPlaneRuntime({
67+ workspaceRoot,
68+ homeConfigPath,
69+ homeStateDirectory,
70+ createInternalToolMap: createExecutorAdminToolMap,
71+ }),
72+ (runtime) => Effect.promise(() => runtime.close()).pipe(Effect.orDie),
73+ );
74+}).pipe(Effect.provide(NodeFileSystem.layer));
75+
76+type OpenApiSpecServer = {
77+ baseUrl: string;
78+ specUrl: string;
79+ close: () => Promise<void>;
80+};
81+
82+type GoogleWorkspaceTestServer = {
83+ baseUrl: string;
84+ tokenEndpoint: string;
85+ discoveryUrl: (input: {
86+ service: string;
87+ version: string;
88+ scope: string;
89+ }) => string;
90+ queueTokenResponse: (response: {
91+ access_token: string;
92+ refresh_token?: string;
93+ expires_in?: number;
94+ scope?: string;
95+ }) => void;
96+ tokenRequests: URLSearchParams[];
97+ discoveryAuthorizations: string[];
98+ close: () => Promise<void>;
99+};
100+
101+const makeOpenApiSpecServer = Effect.acquireRelease(
102+ Effect.promise<OpenApiSpecServer>(
103+ () =>
104+ new Promise<OpenApiSpecServer>((resolve, reject) => {
105+ const openApiDocument = JSON.stringify({
106+ openapi: "3.0.3",
107+ info: {
108+ title: "GitHub Test API",
109+ version: "1.0.0",
110+ },
111+ paths: {
112+ "/repos/{owner}/{repo}": {
113+ get: {
114+ operationId: "repos/get-repo",
115+ tags: ["repos"],
116+ summary: "Get a repository",
117+ responses: {
118+ 200: {
119+ description: "ok",
120+ },
121+ },
122+ },
123+ },
124+ },
125+ });
126+
127+ const server = createServer((request, response) => {
128+ if (request.url !== "/openapi.json") {
129+ response.statusCode = 404;
130+ response.end();
131+ return;
132+ }
133+
134+ response.statusCode = 200;
135+ response.setHeader("content-type", "application/json");
136+ response.end(openApiDocument);
137+ });
138+
139+ server.once("error", reject);
140+ server.listen(0, "127.0.0.1", () => {
141+ const address = server.address();
142+ if (!address || typeof address === "string") {
143+ reject(new Error("Failed to bind OpenAPI runtime test server"));
144+ return;
145+ }
146+
147+ const baseUrl = `http://127.0.0.1:${address.port}`;
148+ resolve({
149+ baseUrl,
150+ specUrl: `${baseUrl}/openapi.json`,
151+ close: () =>
152+ new Promise<void>((closeResolve, closeReject) => {
153+ server.close((error) => {
154+ if (error) {
155+ closeReject(error);
156+ return;
157+ }
158+
159+ closeResolve();
160+ });
161+ }),
162+ });
163+ });
164+ }),
165+ ),
166+ (server) => Effect.promise(() => server.close()).pipe(Effect.orDie),
167+);
168+
169+const makeGoogleWorkspaceTestServer = Effect.acquireRelease(
170+ Effect.promise<GoogleWorkspaceTestServer>(
171+ () =>
172+ new Promise<GoogleWorkspaceTestServer>((resolve, reject) => {
173+ const discoveryScopes = new Map<string, string>();
174+ const tokenResponses: Array<{
175+ access_token: string;
176+ refresh_token?: string;
177+ expires_in?: number;
178+ scope?: string;
179+ }> = [];
180+ const tokenRequests: URLSearchParams[] = [];
181+ const discoveryAuthorizations: string[] = [];
182+ let baseUrl = "";
183+
184+ const server = createServer(async (request, response) => {
185+ const requestUrl = new URL(
186+ request.url ?? "/",
187+ `http://${request.headers.host ?? "127.0.0.1"}`,
188+ );
189+
190+ if (
191+ request.method === "POST" &&
192+ requestUrl.pathname === "/oauth/token"
193+ ) {
194+ const chunks: Buffer[] = [];
195+ for await (const chunk of request) {
196+ chunks.push(Buffer.from(chunk));
197+ }
198+
199+ const params = new URLSearchParams(
200+ Buffer.concat(chunks).toString("utf8"),
201+ );
202+ tokenRequests.push(params);
203+ const nextResponse = tokenResponses.shift();
204+ if (!nextResponse) {
205+ response.statusCode = 500;
206+ response.setHeader("content-type", "application/json");
207+ response.end(
208+ JSON.stringify({
209+ error: "missing_token_response",
210+ }),
211+ );
212+ return;
213+ }
214+
215+ response.statusCode = 200;
216+ response.setHeader("content-type", "application/json");
217+ response.end(
218+ JSON.stringify({
219+ token_type: "Bearer",
220+ expires_in: 3600,
221+ ...nextResponse,
222+ }),
223+ );
224+ return;
225+ }
226+
227+ const discoveryMatch = /^\/([^/]+)\/\$discovery\/rest$/.exec(
228+ requestUrl.pathname,
229+ );
230+ if (request.method === "GET" && discoveryMatch) {
231+ const service = discoveryMatch[1]!;
232+ const version = requestUrl.searchParams.get("version") ?? "v1";
233+ const authorizationHeader =
234+ typeof request.headers.authorization === "string"
235+ ? request.headers.authorization
236+ : Array.isArray(request.headers.authorization)
237+ ? (request.headers.authorization[0] ?? "")
238+ : "";
239+ if (authorizationHeader.length > 0) {
240+ discoveryAuthorizations.push(authorizationHeader);
241+ }
242+
243+ const scope =
244+ discoveryScopes.get(`${service}:${version}`) ??
245+ `https://example.test/auth/${service}`;
246+ response.statusCode = 200;
247+ response.setHeader("content-type", "application/json");
248+ response.end(
249+ JSON.stringify({
250+ name: service,
251+ version,
252+ title: `${service} API`,
253+ description: `Test ${service} API`,
254+ rootUrl: `${baseUrl}/`,
255+ servicePath: `${service}/${version}/`,
256+ auth: {
257+ oauth2: {
258+ scopes: {
259+ [scope]: {
260+ description: `${service} scope`,
261+ },
262+ },
263+ },
264+ },
265+ methods: {
266+ list: {
267+ id: `${service}.list`,
268+ path: "items",
269+ httpMethod: "GET",
270+ response: {
271+ $ref: "ListResponse",
272+ },
273+ },
274+ },
275+ schemas: {
276+ ListResponse: {
277+ id: "ListResponse",
278+ type: "object",
279+ properties: {
280+ items: {
281+ type: "array",
282+ items: {
283+ type: "string",
284+ },
285+ },
286+ },
287+ },
288+ },
289+ }),
290+ );
291+ return;
292+ }
293+
294+ response.statusCode = 404;
295+ response.end();
296+ });
297+
298+ server.once("error", reject);
299+ server.listen(0, "127.0.0.1", () => {
300+ const address = server.address();
301+ if (!address || typeof address === "string") {
302+ reject(new Error("Failed to bind Google workspace test server"));
303+ return;
304+ }
305+
306+ baseUrl = `http://127.0.0.1:${address.port}`;
307+ resolve({
308+ baseUrl,
309+ tokenEndpoint: `${baseUrl}/oauth/token`,
310+ discoveryUrl: ({ service, version, scope }) => {
311+ discoveryScopes.set(`${service}:${version}`, scope);
312+ return `${baseUrl}/${service}/$discovery/rest?version=${encodeURIComponent(version)}`;
313+ },
314+ queueTokenResponse: (tokenResponse) => {
315+ tokenResponses.push(tokenResponse);
316+ },
317+ tokenRequests,
318+ discoveryAuthorizations,
319+ close: () =>
320+ new Promise<void>((closeResolve, closeReject) => {
321+ server.close((error) => {
322+ if (error) {
323+ closeReject(error);
324+ return;
325+ }
326+
327+ closeResolve();
328+ });
329+ }),
330+ });
331+ });
332+ }),
333+ ),
334+ (server) => Effect.promise(() => server.close()).pipe(Effect.orDie),
335+);
336+
337+const upsertLocalSecret = (
338+ runtime: ControlPlaneRuntime,
339+ input: {
340+ id: string;
341+ name: string;
342+ purpose: "oauth_refresh_token" | "oauth_client_info" | "oauth_access_token";
343+ value: string;
344+ },
345+) =>
346+ runtime.persistence.rows.secretMaterials.upsert({
347+ id: SecretMaterialIdSchema.make(input.id),
348+ providerId: "local",
349+ handle: input.id,
350+ name: input.name,
351+ purpose: input.purpose,
352+ value: input.value,
353+ createdAt: Date.now(),
354+ updatedAt: Date.now(),
355+ });
356+
357+const createPersistedGoogleSource = (input: {
358+ runtime: ControlPlaneRuntime;
359+ name: string;
360+ namespace: string;
361+ service: string;
362+ version: string;
363+ discoveryUrl: string;
364+ scopes: ReadonlyArray<string>;
365+ auth:
366+ | {
367+ kind: "none";
368+ }
369+ | {
370+ kind: "provider_grant_ref";
371+ grantId: ReturnType<typeof ProviderAuthGrantIdSchema.make>;
372+ providerKey: string;
373+ requiredScopes: ReadonlyArray<string>;
374+ headerName: string;
375+ prefix: string;
376+ };
377+}) =>
378+ Effect.gen(function* () {
379+ const installation = input.runtime.localInstallation;
380+ const source = yield* createSourceFromPayload({
381+ workspaceId: installation.workspaceId,
382+ sourceId: SourceIdSchema.make(`src_${crypto.randomUUID()}`),
383+ payload: {
384+ name: input.name,
385+ kind: "google_discovery",
386+ endpoint: input.discoveryUrl,
387+ namespace: input.namespace,
388+ binding: {
389+ service: input.service,
390+ version: input.version,
391+ discoveryUrl: input.discoveryUrl,
392+ scopes: [...input.scopes],
393+ },
394+ importAuthPolicy: "reuse_runtime",
395+ importAuth: { kind: "none" },
396+ auth: input.auth as any,
397+ status: "connected",
398+ enabled: true,
399+ },
400+ now: Date.now(),
401+ });
402+
403+ return yield* persistSource(input.runtime.persistence.rows, source, {
404+ actorAccountId: installation.accountId,
405+ }).pipe((effect) => provideControlPlaneRuntime(effect, input.runtime));
406+ });
407+
408+const expectLeft = <A, E>(effect: Effect.Effect<A, E, never>) =>
409+ Effect.either(effect).pipe(
410+ Effect.flatMap((result) =>
411+ Either.isLeft(result)
412+ ? Effect.succeed(result.left)
413+ : Effect.fail(
414+ runtimeEffectError(
415+ "control-plane-runtime.test",
416+ "Expected effect to fail",
417+ ),
418+ ),
419+ ),
420+ );
421+
422+const invokeWorkspaceTool = <A>(input: {
423+ runtime: ControlPlaneRuntime;
424+ path: ToolPath;
425+ args: unknown;
426+}) =>
427+ provideControlPlaneRuntime(
428+ Effect.gen(function* () {
429+ const installation = input.runtime.localInstallation;
430+ const resolveEnvironment = yield* RuntimeExecutionResolverService;
431+ const environment = yield* resolveEnvironment({
432+ workspaceId: installation.workspaceId,
433+ accountId: installation.accountId,
434+ executionId: ExecutionIdSchema.make(`exec_tool_${crypto.randomUUID()}`),
435+ });
436+
437+ return (yield* environment.toolInvoker.invoke({
438+ path: input.path,
439+ args: input.args,
440+ })) as A;
441+ }),
442+ input.runtime,
443+ );
444+
445+describe("control-plane-runtime", () => {
446+ it.scoped(
447+ "writes local source changes through executor.jsonc",
448+ () =>
449+ Effect.gen(function* () {
450+ const fs = yield* FileSystem.FileSystem;
451+ const workspaceRoot = yield* fs.makeTempDirectoryScoped({
452+ prefix: "executor-local-config-runtime-",
453+ });
454+ const homeConfigPath = join(workspaceRoot, ".executor-home.jsonc");
455+ const homeStateDirectory = join(workspaceRoot, ".executor-home-state");
456+ const runtime = yield* Effect.acquireRelease(
457+ createControlPlaneRuntime({
458+ workspaceRoot,
459+ homeConfigPath,
460+ homeStateDirectory,
461+ }),
462+ (createdRuntime) =>
463+ Effect.promise(() => createdRuntime.close()).pipe(Effect.orDie),
464+ );
465+ const openApiServer = yield* makeOpenApiSpecServer;
466+ const installation = runtime.localInstallation;
467+
468+ const createdSource = yield* withControlPlaneClient(
469+ { runtime, accountId: installation.accountId },
470+ (client) =>
471+ client.sources.create({
472+ path: { workspaceId: installation.workspaceId },
473+ payload: {
474+ name: "GitHub",
475+ kind: "openapi",
476+ endpoint: openApiServer.baseUrl,
477+ namespace: "github",
478+ binding: {
479+ specUrl: openApiServer.specUrl,
480+ defaultHeaders: null,
481+ },
482+ auth: { kind: "none" },
483+ },
484+ }),
485+ );
486+
487+ const configPath = join(workspaceRoot, ".executor", "executor.jsonc");
488+ const createdConfig = JSON.parse(
489+ yield* fs.readFileString(configPath, "utf8"),
490+ ) as {
491+ sources?: Record<
492+ string,
493+ { kind: string; connection: { endpoint: string } }
494+ >;
495+ };
496+ expect(createdConfig.sources?.github?.kind).toBe("openapi");
497+ expect(createdConfig.sources?.github?.connection.endpoint).toBe(
498+ openApiServer.baseUrl,
499+ );
500+
501+ const removed = yield* withControlPlaneClient(
502+ { runtime, accountId: installation.accountId },
503+ (client) =>
504+ client.sources.remove({
505+ path: {
506+ workspaceId: installation.workspaceId,
507+ sourceId: createdSource.id,
508+ },
509+ }),
510+ );
511+ expect(removed.removed).toBe(true);
512+
513+ const removedConfig = JSON.parse(
514+ yield* fs.readFileString(configPath, "utf8"),
515+ ) as {
516+ sources?: Record<string, unknown>;
517+ };
518+ expect(removedConfig.sources?.github).toBeUndefined();
519+ }).pipe(Effect.provide(NodeFileSystem.layer)),
520+ 60_000,
521+ );
522+
523+ it.scoped("manages local secrets through provider-backed metadata rows", () =>
524+ Effect.gen(function* () {
525+ const runtime = yield* makeRuntime;
526+
527+ const created = yield* withControlPlaneClient({ runtime }, (client) =>
528+ client.local.createSecret({
529+ payload: {
530+ name: "GitHub PAT",
531+ value: "ghp_test_token",
532+ providerId: "local",
533+ },
534+ }),
535+ );
536+
537+ expect(created.providerId).toBe("local");
538+
539+ const listed = yield* withControlPlaneClient({ runtime }, (client) =>
540+ client.local.listSecrets({}),
541+ );
542+ expect(listed).toEqual([
543+ expect.objectContaining({
544+ id: created.id,
545+ providerId: "local",
546+ name: "GitHub PAT",
547+ }),
548+ ]);
549+
550+ const updated = yield* withControlPlaneClient({ runtime }, (client) =>
551+ client.local.updateSecret({
552+ path: { secretId: created.id },
553+ payload: {
554+ name: "GitHub PAT Updated",
555+ value: "ghp_test_token_v2",
556+ },
557+ }),
558+ );
559+ expect(updated.providerId).toBe("local");
560+ expect(updated.name).toBe("GitHub PAT Updated");
561+
562+ const storedSecret =
563+ yield* runtime.persistence.rows.secretMaterials.getById(
564+ SecretMaterialIdSchema.make(created.id),
565+ );
566+ assertTrue(Option.isSome(storedSecret));
567+ expect(storedSecret.value.providerId).toBe("local");
568+ expect(storedSecret.value.value).toBe("ghp_test_token_v2");
569+
570+ const removed = yield* withControlPlaneClient({ runtime }, (client) =>
571+ client.local.deleteSecret({
572+ path: { secretId: created.id },
573+ }),
574+ );
575+ expect(removed.removed).toBe(true);
576+ }),
577+ );
578+
579+ it.scoped(
580+ "reports safe secret storage options for the current platform",
581+ () =>
582+ Effect.gen(function* () {
583+ const runtime = yield* makeRuntime;
584+
585+ const config = yield* withControlPlaneClient({ runtime }, (client) =>
586+ client.local.config({}),
587+ );
588+
589+ const keychainProvider =
590+ config.secretProviders.find(
591+ (provider) => provider.id === "keychain",
592+ ) ?? null;
593+
594+ if (process.platform === "linux") {
595+ expect(config.defaultSecretStoreProvider).toBe("local");
596+ expect(keychainProvider?.canStore).toBe(false);
597+ return;
598+ }
599+
600+ if (process.platform === "darwin") {
601+ expect(keychainProvider?.canStore).toBe(true);
602+ return;
603+ }
604+
605+ expect(keychainProvider).toBeNull();
606+ }),
607+ );
608+
609+ it.scoped(
610+ "exposes shared local admin operations through executor tools",
611+ () =>
612+ Effect.gen(function* () {
613+ const runtime = yield* makeRuntime;
614+ const openApiServer = yield* makeOpenApiSpecServer;
615+ const installation = runtime.localInstallation;
616+ const createdSource = yield* withControlPlaneClient(
617+ { runtime, accountId: installation.accountId },
618+ (client) =>
619+ client.sources.create({
620+ path: {
621+ workspaceId: installation.workspaceId,
622+ },
623+ payload: {
624+ name: "GitHub",
625+ kind: "openapi",
626+ endpoint: openApiServer.baseUrl,
627+ status: "connected",
628+ enabled: true,
629+ namespace: "github",
630+ importAuthPolicy: "reuse_runtime",
631+ binding: {
632+ specUrl: openApiServer.specUrl,
633+ defaultHeaders: null,
634+ },
635+ importAuth: { kind: "none" },
636+ auth: { kind: "none" },
637+ },
638+ }),
639+ );
640+
641+ const localInstallation = yield* invokeWorkspaceTool<{
642+ workspaceId: string;
643+ accountId: string;
644+ }>({
645+ runtime,
646+ path: "executor.local.installation.get" as ToolPath,
647+ args: {},
648+ });
649+ expect(localInstallation.workspaceId).toBe(installation.workspaceId);
650+ expect(localInstallation.accountId).toBe(installation.accountId);
651+
652+ const localConfig = yield* invokeWorkspaceTool<{
653+ defaultSecretStoreProvider: string;
654+ secretProviders: Array<{ id: string }>;
655+ }>({
656+ runtime,
657+ path: "executor.local.config.get" as ToolPath,
658+ args: {},
659+ });
660+ expect(localConfig.secretProviders.length).toBeGreaterThan(0);
661+ expect(typeof localConfig.defaultSecretStoreProvider).toBe("string");
662+
663+ const createdSecret = yield* invokeWorkspaceTool<{
664+ id: string;
665+ providerId: string;
666+ name: string | null;
667+ }>({
668+ runtime,
669+ path: "executor.secrets.create" as ToolPath,
670+ args: {
671+ name: "Executor Admin Token",
672+ value: "shh",
673+ providerId: "local",
674+ },
675+ });
676+ expect(createdSecret.providerId).toBe("local");
677+
678+ const listedSecrets = yield* invokeWorkspaceTool<
679+ Array<{ id: string; providerId: string; name: string | null }>
680+ >({
681+ runtime,
682+ path: "executor.secrets.list" as ToolPath,
683+ args: {},
684+ });
685+ expect(
686+ listedSecrets.some((secret) => secret.id === createdSecret.id),
687+ ).toBe(true);
688+
689+ const apiSecrets = yield* withControlPlaneClient(
690+ { runtime },
691+ (client) => client.local.listSecrets({}),
692+ );
693+ expect(
694+ apiSecrets.some((secret) => secret.id === createdSecret.id),
695+ ).toBe(true);
696+
697+ const createdPolicy = yield* invokeWorkspaceTool<{
698+ id: string;
699+ resourcePattern: string;
700+ approvalMode: string;
701+ }>({
702+ runtime,
703+ path: "executor.policies.create" as ToolPath,
704+ args: {
705+ resourcePattern: "github.*",
706+ effect: "allow",
707+ approvalMode: "auto",
708+ },
709+ });
710+ expect(createdPolicy.resourcePattern).toBe("github.*");
711+
712+ const loadedPolicy = yield* invokeWorkspaceTool<{
713+ id: string;
714+ resourcePattern: string;
715+ }>({
716+ runtime,
717+ path: "executor.policies.get" as ToolPath,
718+ args: {
719+ policyId: createdPolicy.id,
720+ },
721+ });
722+ expect(loadedPolicy.id).toBe(createdPolicy.id);
723+
724+ const listedPolicies = yield* withControlPlaneClient(
725+ { runtime, accountId: installation.accountId },
726+ (client) =>
727+ client.policies.list({
728+ path: {
729+ workspaceId: installation.workspaceId,
730+ },
731+ }),
732+ );
733+ expect(
734+ listedPolicies.some((policy) => policy.id === createdPolicy.id),
735+ ).toBe(true);
736+
737+ const listedSources = yield* invokeWorkspaceTool<Array<{ id: string }>>(
738+ {
739+ runtime,
740+ path: "executor.sources.list" as ToolPath,
741+ args: {},
742+ },
743+ );
744+ expect(
745+ listedSources.some((source) => source.id === createdSource.id),
746+ ).toBe(true);
747+
748+ const loadedSource = yield* invokeWorkspaceTool<{
749+ id: string;
750+ name: string;
751+ }>({
752+ runtime,
753+ path: "executor.sources.get" as ToolPath,
754+ args: {
755+ sourceId: createdSource.id,
756+ },
757+ });
758+ expect(loadedSource.name).toBe("GitHub");
759+
760+ const removedSource = yield* invokeWorkspaceTool<{ removed: boolean }>({
761+ runtime,
762+ path: "executor.sources.remove" as ToolPath,
763+ args: {
764+ sourceId: createdSource.id,
765+ },
766+ });
767+ expect(removedSource.removed).toBe(true);
768+
769+ const apiSources = yield* withControlPlaneClient(
770+ { runtime, accountId: installation.accountId },
771+ (client) =>
772+ client.sources.list({
773+ path: {
774+ workspaceId: installation.workspaceId,
775+ },
776+ }),
777+ );
778+ expect(
779+ apiSources.some((source) => source.id === createdSource.id),
780+ ).toBe(false);
781+ }),
782+ );
783+
784+ it.scoped(
785+ "captures credential requests through the local HTML flow without persisting raw tokens",
786+ () =>
787+ Effect.gen(function* () {
788+ const runtime = yield* makeRuntime;
789+ const installation = runtime.localInstallation;
790+ const executionId = ExecutionIdSchema.make("exec_local_credential");
791+ const sourceId = SourceIdSchema.make("github");
792+ const interactionSuffix = "executor.sources.add:test";
793+ const interactionId = ExecutionInteractionIdSchema.make(
794+ `${executionId}:${interactionSuffix}`,
795+ );
796+ const now = Date.now();
797+
798+ yield* runtime.persistence.rows.executions.insert({
799+ id: executionId,
800+ workspaceId: installation.workspaceId,
801+ createdByAccountId: installation.accountId,
802+ status: "running",
803+ code: "return await tools.executor.sources.add(...)",
804+ resultJson: null,
805+ errorText: null,
806+ logsJson: null,
807+ startedAt: now,
808+ completedAt: null,
809+ createdAt: now,
810+ updatedAt: now,
811+ });
812+
813+ const localSource = yield* createSourceFromPayload({
814+ workspaceId: installation.workspaceId,
815+ sourceId,
816+ payload: {
817+ name: "GitHub",
818+ kind: "openapi",
819+ endpoint: "https://api.github.com",
820+ status: "auth_required",
821+ enabled: true,
822+ namespace: "github",
823+ importAuthPolicy: "reuse_runtime",
824+ binding: {
825+ specUrl: "https://example.com/github-openapi.yaml",
826+ defaultHeaders: null,
827+ },
828+ importAuth: { kind: "none" },
829+ auth: { kind: "none" },
830+ },
831+ now,
832+ }).pipe(Effect.orDie);
833+ yield* persistSource(runtime.persistence.rows, localSource, {
834+ actorAccountId: installation.accountId,
835+ }).pipe(
836+ (effect) => provideControlPlaneRuntime(effect, runtime),
837+ Effect.orDie,
838+ );
839+
840+ const interactionFiber = yield* Effect.gen(function* () {
841+ const liveExecutionManager = yield* LiveExecutionManagerService;
842+ const onElicitation = liveExecutionManager.createOnElicitation({
843+ rows: runtime.persistence.rows,
844+ executionId,
845+ });
846+
847+ return yield* onElicitation({
848+ interactionId: interactionSuffix,
849+ path: "executor.sources.add" as ToolPath,
850+ sourceKey: "executor",
851+ args: {
852+ kind: "openapi",
853+ endpoint: "https://api.github.com",
854+ specUrl: "https://example.com/github-openapi.yaml",
855+ name: "GitHub",
856+ workspaceId: installation.workspaceId,
857+ sourceId,
858+ },
859+ elicitation: {
860+ mode: "url",
861+ message: "Open the secure credential page to connect GitHub",
862+ url: `http://127.0.0.1/v1/workspaces/${encodeURIComponent(installation.workspaceId)}/sources/${encodeURIComponent(sourceId)}/credentials?interactionId=${encodeURIComponent(interactionId)}`,
863+ elicitationId: interactionSuffix,
864+ },
865+ });
866+ }).pipe(
867+ (effect) => provideControlPlaneRuntime(effect, runtime),
868+ Effect.fork,
869+ );
870+
871+ const waitForPendingInteraction = (
872+ remaining: number,
873+ ): Effect.Effect<Option.Option<ExecutionInteraction>, Error> =>
874+ runtime.persistence.rows.executionInteractions
875+ .getPendingByExecutionId(executionId)
876+ .pipe(
877+ Effect.flatMap((pendingInteraction) =>
878+ Option.isSome(pendingInteraction) || remaining <= 0
879+ ? Effect.succeed(pendingInteraction)
880+ : Effect.yieldNow().pipe(
881+ Effect.zipRight(waitForPendingInteraction(remaining - 1)),
882+ ),
883+ ),
884+ );
885+
886+ const pendingInteraction = yield* waitForPendingInteraction(20);
887+ assertTrue(Option.isSome(pendingInteraction));
888+ expect(pendingInteraction.value.id).toBe(interactionId);
889+
890+ const page = yield* withControlPlaneClient({ runtime }, (client) =>
891+ client.sources.credentialPage({
892+ path: {
893+ workspaceId: installation.workspaceId,
894+ sourceId,
895+ },
896+ urlParams: {
897+ interactionId: pendingInteraction.value.id,
898+ },
899+ }),
900+ );
901+ expect(page).toContain("Configure Source Access");
902+ expect(page).toContain("GitHub");
903+ expect(page).toContain("Continue without auth");
904+
905+ const submittedPage = yield* withControlPlaneClient(
906+ { runtime },
907+ (client) =>
908+ client.sources.credentialSubmit({
909+ path: {
910+ workspaceId: installation.workspaceId,
911+ sourceId,
912+ },
913+ urlParams: {
914+ interactionId: pendingInteraction.value.id,
915+ },
916+ payload: {
917+ action: "submit",
918+ token: "ghp_local_test_token",
919+ },
920+ }),
921+ );
922+ expect(submittedPage).toContain("Credential Stored");
923+
924+ const response = yield* Fiber.join(interactionFiber);
925+ expect(response.action).toBe("accept");
926+ expect(response.content?.authKind).toBe("bearer");
927+
928+ const decodedCredentialSelection =
929+ decodeSourceCredentialSelectionContent(response.content);
930+ expect(decodedCredentialSelection.authKind).toBe("bearer");
931+
932+ const tokenSecretMaterialId = SecretMaterialIdSchema.make(
933+ decodedCredentialSelection.authKind === "bearer"
934+ ? decodedCredentialSelection.tokenRef.handle
935+ : "",
936+ );
937+ const storedSecret =
938+ yield* runtime.persistence.rows.secretMaterials.getById(
939+ tokenSecretMaterialId,
940+ );
941+ assertTrue(Option.isSome(storedSecret));
942+ expect(
943+ decodedCredentialSelection.authKind === "bearer"
944+ ? decodedCredentialSelection.tokenRef.providerId
945+ : null,
946+ ).toBe(storedSecret.value.providerId);
947+ expect(storedSecret.value.value).toBe(
948+ storedSecret.value.providerId === "local"
949+ ? "ghp_local_test_token"
950+ : null,
951+ );
952+
953+ const storedInteraction =
954+ yield* runtime.persistence.rows.executionInteractions.getById(
955+ pendingInteraction.value.id,
956+ );
957+ assertTrue(Option.isSome(storedInteraction));
958+ expect(storedInteraction.value.responseJson).toContain(
959+ '"authKind":"bearer"',
960+ );
961+ expect(storedInteraction.value.responseJson).not.toContain("tokenRef");
962+ expect(storedInteraction.value.responseJson).not.toContain(
963+ "ghp_local_test_token",
964+ );
965+ }),
966+ );
967+
968+ it.scoped("loads a v1.2.3-style workspace artifact on startup", () =>
969+ Effect.gen(function* () {
970+ const fs = yield* FileSystem.FileSystem;
971+ const workspaceRoot = yield* fs.makeTempDirectoryScoped({
972+ prefix: "executor-control-plane-runtime-v123-",
973+ });
974+ const homeConfigPath = join(workspaceRoot, ".executor-home.jsonc");
975+ const homeStateDirectory = join(workspaceRoot, ".executor-home-state");
976+ const configDirectory = join(workspaceRoot, ".executor");
977+ const configPath = join(configDirectory, "executor.jsonc");
978+ const sourceId = SourceIdSchema.make("graphql");
979+ const now = Date.now();
980+
981+ yield* fs.makeDirectory(configDirectory, { recursive: true });
982+ yield* fs.writeFileString(
983+ configPath,
984+ `{
985+ "sources": {
986+ "graphql": {
987+ "kind": "graphql",
988+ "name": "GraphQL API",
989+ "namespace": "graphql",
990+ "connection": {
991+ "endpoint": "https://example.com/graphql"
992+ },
993+ "binding": {
994+ "defaultHeaders": null
995+ }
996+ }
997+ }
998+}
999+`,
1000+ );
1001+
1002+ const context = yield* resolveLocalWorkspaceContext({
1003+ workspaceRoot,
1004+ homeConfigPath,
1005+ homeStateDirectory,
1006+ });
1007+ const installation = deriveLocalInstallation(context);
1008+ const source = yield* createSourceFromPayload({
1009+ workspaceId: installation.workspaceId,
1010+ sourceId,
1011+ payload: {
1012+ name: "GraphQL API",
1013+ kind: "graphql",
1014+ endpoint: "https://example.com/graphql",
1015+ status: "connected",
1016+ enabled: true,
1017+ namespace: "graphql",
1018+ importAuthPolicy: "reuse_runtime",
1019+ binding: {
1020+ defaultHeaders: null,
1021+ },
1022+ importAuth: { kind: "none" },
1023+ auth: { kind: "none" },
1024+ },
1025+ now,
1026+ }).pipe(Effect.orDie);
1027+
1028+ const artifact = buildLocalSourceArtifact({
1029+ source,
1030+ syncResult: {
1031+ fragment: createGraphqlCatalogFragment({
1032+ source,
1033+ documents: [
1034+ {
1035+ documentKind: "graphql_introspection",
1036+ documentKey: source.endpoint,
1037+ contentText: '{"__schema":{}}',
1038+ fetchedAt: now,
1039+ },
1040+ ],
1041+ operations: [
1042+ {
1043+ toolId: "viewer",
1044+ title: "Viewer",
1045+ description: "Load the current viewer",
1046+ effect: "read",
1047+ inputSchema: { type: "object", properties: {} },
1048+ outputSchema: {
1049+ type: "object",
1050+ properties: { login: { type: "string" } },
1051+ },
1052+ providerData: {
1053+ kind: "graphql",
1054+ toolKind: "field",
1055+ toolId: "viewer",
1056+ rawToolId: "viewer",
1057+ group: "query",
1058+ leaf: "viewer",
1059+ fieldName: "viewer",
1060+ operationType: "query",
1061+ operationName: "ViewerQuery",
1062+ operationDocument: "query ViewerQuery { viewer { login } }",
1063+ queryTypeName: "Query",
1064+ mutationTypeName: null,
1065+ subscriptionTypeName: null,
1066+ },
1067+ },
1068+ ],
1069+ }),
1070+ importMetadata: createCatalogImportMetadata({
1071+ source,
1072+ adapterKey: "graphql",
1073+ }),
1074+ sourceHash: source.sourceHash,
1075+ },
1076+ });
1077+
1078+ yield* writeLocalWorkspaceState({
1079+ context,
1080+ state: {
1081+ version: 1,
1082+ sources: {
1083+ [sourceId]: {
1084+ status: source.status,
1085+ lastError: source.lastError,
1086+ sourceHash: source.sourceHash,
1087+ createdAt: source.createdAt,
1088+ updatedAt: source.updatedAt,
1089+ },
1090+ },
1091+ policies: {},
1092+ },
1093+ });
1094+ yield* writeLocalControlPlaneState({
1095+ context,
1096+ state: {
1097+ version: 1,
1098+ authArtifacts: [],
1099+ authLeases: [],
1100+ sourceOauthClients: [],
1101+ workspaceOauthClients: [],
1102+ providerAuthGrants: [],
1103+ sourceAuthSessions: [],
1104+ secretMaterials: [],
1105+ executions: [],
1106+ executionInteractions: [],
1107+ executionSteps: [],
1108+ },
1109+ });
1110+ yield* writeLocalSourceArtifact({
1111+ context,
1112+ sourceId,
1113+ artifact,
1114+ });
1115+
1116+ const artifactPath = join(
1117+ context.artifactsDirectory,
1118+ "sources",
1119+ `${sourceId}.json`,
1120+ );
1121+ const legacyArtifact = JSON.parse(
1122+ yield* fs.readFileString(artifactPath, "utf8"),
1123+ ) as Record<string, unknown>;
1124+ legacyArtifact.version = 3;
1125+ yield* fs.writeFileString(
1126+ artifactPath,
1127+ `${JSON.stringify(legacyArtifact)}\n`,
1128+ );
1129+
1130+ const runtime = yield* Effect.acquireRelease(
1131+ createControlPlaneRuntime({
1132+ workspaceRoot,
1133+ homeConfigPath,
1134+ homeStateDirectory,
1135+ }),
1136+ (createdRuntime) =>
1137+ Effect.promise(() => createdRuntime.close()).pipe(Effect.orDie),
1138+ );
1139+
1140+ const inspection = yield* withControlPlaneClient(
1141+ { runtime, accountId: installation.accountId },
1142+ (client) =>
1143+ client.sources.inspection({
1144+ path: {
1145+ workspaceId: installation.workspaceId,
1146+ sourceId,
1147+ },
1148+ }),
1149+ );
1150+
1151+ expect(inspection.source.id).toBe(sourceId);
1152+ expect(inspection.source.kind).toBe("graphql");
1153+ expect(inspection.toolCount).toBe(1);
1154+ expect(inspection.tools.map((tool) => tool.path)).toContain(
1155+ "graphql.viewer",
1156+ );
1157+ }).pipe(Effect.provide(NodeFileSystem.layer)),
1158+ );
1159+
1160+ it.scoped(
1161+ "rebuilds missing connected source artifacts on startup",
1162+ () =>
1163+ Effect.gen(function* () {
1164+ const fs = yield* FileSystem.FileSystem;
1165+ const openApiServer = yield* makeOpenApiSpecServer;
1166+ const workspaceRoot = yield* fs.makeTempDirectoryScoped({
1167+ prefix: "executor-control-plane-runtime-startup-rebuild-",
1168+ });
1169+ const homeConfigPath = join(workspaceRoot, ".executor-home.jsonc");
1170+ const homeStateDirectory = join(workspaceRoot, ".executor-home-state");
1171+ const context = yield* resolveLocalWorkspaceContext({
1172+ workspaceRoot,
1173+ homeConfigPath,
1174+ homeStateDirectory,
1175+ });
1176+ const installation = deriveLocalInstallation(context);
1177+ const sourceId = SourceIdSchema.make("github");
1178+ const now = Date.now();
1179+ const source = yield* createSourceFromPayload({
1180+ workspaceId: installation.workspaceId,
1181+ sourceId,
1182+ payload: {
1183+ name: "GitHub",
1184+ kind: "openapi",
1185+ endpoint: openApiServer.baseUrl,
1186+ status: "connected",
1187+ enabled: true,
1188+ namespace: "github",
1189+ importAuthPolicy: "reuse_runtime",
1190+ binding: {
1191+ specUrl: openApiServer.specUrl,
1192+ defaultHeaders: null,
1193+ },
1194+ importAuth: { kind: "none" },
1195+ auth: { kind: "none" },
1196+ },
1197+ now,
1198+ }).pipe(Effect.orDie);
1199+
1200+ yield* writeProjectLocalExecutorConfig({
1201+ context,
1202+ config: {
1203+ sources: {
1204+ [sourceId]: {
1205+ kind: "openapi",
1206+ name: "GitHub",
1207+ namespace: "github",
1208+ connection: {
1209+ endpoint: openApiServer.baseUrl,
1210+ },
1211+ binding: {
1212+ specUrl: openApiServer.specUrl,
1213+ defaultHeaders: null,
1214+ },
1215+ },
1216+ },
1217+ },
1218+ });
1219+ yield* writeLocalWorkspaceState({
1220+ context,
1221+ state: {
1222+ version: 1,
1223+ sources: {
1224+ [sourceId]: {
1225+ status: source.status,
1226+ lastError: source.lastError,
1227+ sourceHash: source.sourceHash,
1228+ createdAt: source.createdAt,
1229+ updatedAt: source.updatedAt,
1230+ },
1231+ },
1232+ policies: {},
1233+ },
1234+ });
1235+
1236+ const runtime = yield* Effect.acquireRelease(
1237+ createControlPlaneRuntime({
1238+ workspaceRoot,
1239+ homeConfigPath,
1240+ homeStateDirectory,
1241+ }),
1242+ (createdRuntime) =>
1243+ Effect.promise(() => createdRuntime.close()).pipe(Effect.orDie),
1244+ );
1245+
1246+ const rebuiltArtifact = yield* readLocalSourceArtifact({
1247+ context,
1248+ sourceId,
1249+ }).pipe(Effect.provide(NodeFileSystem.layer));
1250+ expect(rebuiltArtifact).not.toBeNull();
1251+
1252+ const inspection = yield* withControlPlaneClient(
1253+ { runtime, accountId: installation.accountId },
1254+ (client) =>
1255+ client.sources.inspection({
1256+ path: {
1257+ workspaceId: installation.workspaceId,
1258+ sourceId,
1259+ },
1260+ }),
1261+ );
1262+ expect(inspection.tools.length).toBeGreaterThan(0);
1263+ }).pipe(Effect.provide(NodeFileSystem.layer)),
1264+ 60_000,
1265+ );
1266+
1267+ it.scoped(
1268+ "returns an empty inspection bundle for auth-required sources without a catalog artifact",
1269+ () =>
1270+ Effect.gen(function* () {
1271+ const runtime = yield* makeRuntime;
1272+ const installation = runtime.localInstallation;
1273+ const sourceId = SourceIdSchema.make("googleapis");
1274+ const now = Date.now();
1275+
1276+ const localSource = yield* createSourceFromPayload({
1277+ workspaceId: installation.workspaceId,
1278+ sourceId,
1279+ payload: {
1280+ name: "Google Drive",
1281+ kind: "google_discovery",
1282+ endpoint:
1283+ "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest",
1284+ status: "auth_required",
1285+ enabled: true,
1286+ namespace: "googleapis",
1287+ importAuthPolicy: "reuse_runtime",
1288+ binding: {
1289+ service: "drive",
1290+ version: "v3",
1291+ discoveryUrl:
1292+ "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest",
1293+ scopes: ["https://www.googleapis.com/auth/drive.readonly"],
1294+ },
1295+ importAuth: { kind: "none" },
1296+ auth: { kind: "none" },
1297+ },
1298+ now,
1299+ }).pipe(Effect.orDie);
1300+ yield* persistSource(runtime.persistence.rows, localSource, {
1301+ actorAccountId: installation.accountId,
1302+ }).pipe(
1303+ (effect) => provideControlPlaneRuntime(effect, runtime),
1304+ Effect.orDie,
1305+ );
1306+
1307+ const inspection = yield* withControlPlaneClient(
1308+ { runtime, accountId: installation.accountId },
1309+ (client) =>
1310+ client.sources.inspection({
1311+ path: {
1312+ workspaceId: installation.workspaceId,
1313+ sourceId,
1314+ },
1315+ }),
1316+ );
1317+
1318+ expect(inspection.source.id).toBe(sourceId);
1319+ expect(inspection.source.status).toBe("auth_required");
1320+ expect(inspection.toolCount).toBe(0);
1321+ expect(inspection.tools).toEqual([]);
1322+ }),
1323+ );
1324+
1325+ it.scoped(
1326+ "still fails inspection when a connected source is missing its catalog artifact",
1327+ () =>
1328+ Effect.gen(function* () {
1329+ const runtime = yield* makeRuntime;
1330+ const installation = runtime.localInstallation;
1331+ const sourceId = SourceIdSchema.make("googleapis");
1332+ const now = Date.now();
1333+
1334+ const localSource = yield* createSourceFromPayload({
1335+ workspaceId: installation.workspaceId,
1336+ sourceId,
1337+ payload: {
1338+ name: "Google Drive",
1339+ kind: "google_discovery",
1340+ endpoint:
1341+ "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest",
1342+ status: "connected",
1343+ enabled: true,
1344+ namespace: "googleapis",
1345+ importAuthPolicy: "reuse_runtime",
1346+ binding: {
1347+ service: "drive",
1348+ version: "v3",
1349+ discoveryUrl:
1350+ "https://www.googleapis.com/discovery/v1/apis/drive/v3/rest",
1351+ scopes: ["https://www.googleapis.com/auth/drive.readonly"],
1352+ },
1353+ importAuth: { kind: "none" },
1354+ auth: { kind: "none" },
1355+ },
1356+ now,
1357+ }).pipe(Effect.orDie);
1358+ yield* persistSource(runtime.persistence.rows, localSource, {
1359+ actorAccountId: installation.accountId,
1360+ }).pipe(
1361+ (effect) => provideControlPlaneRuntime(effect, runtime),
1362+ Effect.orDie,
1363+ );
1364+
1365+ const error = yield* expectLeft(
1366+ withControlPlaneClient(
1367+ { runtime, accountId: installation.accountId },
1368+ (client) =>
1369+ client.sources.inspection({
1370+ path: {
1371+ workspaceId: installation.workspaceId,
1372+ sourceId,
1373+ },
1374+ }),
1375+ ),
1376+ );
1377+
1378+ expect(error.message).toContain(
1379+ "Catalog artifact missing for source googleapis",
1380+ );
1381+ }),
1382+ );
1383+
1384+ it.scoped(
1385+ "allows continuing an OpenAPI source credential request without auth",
1386+ () =>
1387+ Effect.gen(function* () {
1388+ const runtime = yield* makeRuntime;
1389+ const installation = runtime.localInstallation;
1390+ const executionId = ExecutionIdSchema.make(
1391+ "exec_local_credential_continue",
1392+ );
1393+ const sourceId = SourceIdSchema.make("github");
1394+ const interactionSuffix = "executor.sources.add:continue";
1395+ const interactionId = ExecutionInteractionIdSchema.make(
1396+ `${executionId}:${interactionSuffix}`,
1397+ );
1398+ const now = Date.now();
1399+
1400+ yield* runtime.persistence.rows.executions.insert({
1401+ id: executionId,
1402+ workspaceId: installation.workspaceId,
1403+ createdByAccountId: installation.accountId,
1404+ status: "running",
1405+ code: "return await tools.executor.sources.add(...)",
1406+ resultJson: null,
1407+ errorText: null,
1408+ logsJson: null,
1409+ startedAt: now,
1410+ completedAt: null,
1411+ createdAt: now,
1412+ updatedAt: now,
1413+ });
1414+
1415+ const localSource = yield* createSourceFromPayload({
1416+ workspaceId: installation.workspaceId,
1417+ sourceId,
1418+ payload: {
1419+ name: "GitHub",
1420+ kind: "openapi",
1421+ endpoint: "https://api.github.com",
1422+ status: "auth_required",
1423+ enabled: true,
1424+ namespace: "github",
1425+ importAuthPolicy: "reuse_runtime",
1426+ binding: {
1427+ specUrl: "https://example.com/github-openapi.yaml",
1428+ defaultHeaders: null,
1429+ },
1430+ importAuth: { kind: "none" },
1431+ auth: { kind: "none" },
1432+ },
1433+ now,
1434+ }).pipe(Effect.orDie);
1435+ yield* persistSource(runtime.persistence.rows, localSource, {
1436+ actorAccountId: installation.accountId,
1437+ }).pipe(
1438+ (effect) => provideControlPlaneRuntime(effect, runtime),
1439+ Effect.orDie,
1440+ );
1441+
1442+ const interactionFiber = yield* Effect.gen(function* () {
1443+ const liveExecutionManager = yield* LiveExecutionManagerService;
1444+ const onElicitation = liveExecutionManager.createOnElicitation({
1445+ rows: runtime.persistence.rows,
1446+ executionId,
1447+ });
1448+
1449+ return yield* onElicitation({
1450+ interactionId: interactionSuffix,
1451+ path: "executor.sources.add" as ToolPath,
1452+ sourceKey: "executor",
1453+ args: {
1454+ kind: "openapi",
1455+ endpoint: "https://api.github.com",
1456+ specUrl: "https://example.com/github-openapi.yaml",
1457+ name: "GitHub",
1458+ workspaceId: installation.workspaceId,
1459+ sourceId,
1460+ },
1461+ elicitation: {
1462+ mode: "url",
1463+ message: "Open the secure credential page to connect GitHub",
1464+ url: `http://127.0.0.1/v1/workspaces/${encodeURIComponent(installation.workspaceId)}/sources/${encodeURIComponent(sourceId)}/credentials?interactionId=${encodeURIComponent(interactionId)}`,
1465+ elicitationId: interactionSuffix,
1466+ },
1467+ });
1468+ }).pipe(
1469+ (effect) => provideControlPlaneRuntime(effect, runtime),
1470+ Effect.fork,
1471+ );
1472+
1473+ const waitForPendingInteraction = (
1474+ remaining: number,
1475+ ): Effect.Effect<Option.Option<ExecutionInteraction>, Error> =>
1476+ runtime.persistence.rows.executionInteractions
1477+ .getPendingByExecutionId(executionId)
1478+ .pipe(
1479+ Effect.flatMap((pendingInteraction) =>
1480+ Option.isSome(pendingInteraction) || remaining <= 0
1481+ ? Effect.succeed(pendingInteraction)
1482+ : Effect.yieldNow().pipe(
1483+ Effect.zipRight(waitForPendingInteraction(remaining - 1)),
1484+ ),
1485+ ),
1486+ );
1487+
1488+ const pendingInteraction = yield* waitForPendingInteraction(20);
1489+ assertTrue(Option.isSome(pendingInteraction));
1490+
1491+ const submitted = yield* submitSourceCredentialInteraction({
1492+ workspaceId: installation.workspaceId,
1493+ sourceId,
1494+ interactionId: pendingInteraction.value.id,
1495+ action: "continue",
1496+ }).pipe((effect) => provideControlPlaneRuntime(effect, runtime));
1497+
1498+ expect(submitted.kind).toBe("continued");
1499+
1500+ const response = yield* Fiber.join(interactionFiber);
1501+ expect(response.action).toBe("accept");
1502+ expect(response.content).toEqual({
1503+ authKind: "none",
1504+ });
1505+
1506+ const storedInteraction =
1507+ yield* runtime.persistence.rows.executionInteractions.getById(
1508+ pendingInteraction.value.id,
1509+ );
1510+ assertTrue(Option.isSome(storedInteraction));
1511+ expect(storedInteraction.value.responseJson).toContain(
1512+ '"authKind":"none"',
1513+ );
1514+ expect(storedInteraction.value.responseJson).not.toContain("tokenRef");
1515+ }),
1516+ );
1517+
1518+ it.scoped(
1519+ "decodes GraphQL source credential interactions for the local credential flow",
1520+ () =>
1521+ Effect.gen(function* () {
1522+ const executionId = ExecutionIdSchema.make(
1523+ "exec_local_graphql_credential_continue",
1524+ );
1525+ const workspaceId = "ws_local_graphql" as const;
1526+ const sourceId = SourceIdSchema.make("graphql_api");
1527+ const now = Date.now();
1528+ const decoded = decodeSourceCredentialInteraction({
1529+ id: ExecutionInteractionIdSchema.make(
1530+ "exec_local_graphql_credential_continue:executor.sources.add:graphql-continue",
1531+ ),
1532+ executionId,
1533+ status: "pending",
1534+ purpose: "source_connect_oauth2",
1535+ payloadJson: JSON.stringify({
1536+ path: "executor.sources.add",
1537+ args: {
1538+ kind: "graphql",
1539+ endpoint: "https://example.com/graphql",
1540+ name: "GraphQL API",
1541+ namespace: "graphql",
1542+ workspaceId,
1543+ sourceId,
1544+ },
1545+ elicitation: {
1546+ mode: "url",
1547+ message: "Open the secure credential page to connect GraphQL API",
1548+ },
1549+ }),
1550+ responseJson: null,
1551+ responsePrivateJson: null,
1552+ createdAt: now,
1553+ updatedAt: now,
1554+ });
1555+
1556+ expect(decoded).not.toBeNull();
1557+ expect(decoded?.workspaceId).toBe(workspaceId);
1558+ expect(decoded?.sourceId).toBe(sourceId);
1559+ expect(decoded?.message).toContain("GraphQL API");
1560+ }),
1561+ );
1562+
1563+ it.scoped(
1564+ "reuses existing Google provider grants for single connects and clears orphan state",
1565+ () =>
1566+ Effect.gen(function* () {
1567+ const runtime = yield* makeRuntime;
1568+ const googleServer = yield* makeGoogleWorkspaceTestServer;
1569+ const installation = runtime.localInstallation;
1570+
1571+ const oauthClient = yield* withControlPlaneClient(
1572+ { runtime },
1573+ (client) =>
1574+ client.sources.createWorkspaceOauthClient({
1575+ path: {
1576+ workspaceId: installation.workspaceId,
1577+ },
1578+ payload: {
1579+ providerKey: "google_workspace",
1580+ label: "Local Google Workspace Client",
1581+ oauthClient: {
1582+ clientId: "google-client-id",
1583+ clientSecret: "google-client-secret",
1584+ },
1585+ },
1586+ }),
1587+ );
1588+
1589+ const refreshSecretId = "sec_google_refresh_single_reuse";
1590+ yield* upsertLocalSecret(runtime, {
1591+ id: refreshSecretId,
1592+ name: "Google Refresh Token",
1593+ purpose: "oauth_refresh_token",
1594+ value: "refresh-token-reuse",
1595+ });
1596+
1597+ const grantId = ProviderAuthGrantIdSchema.make(
1598+ `provider_grant_${crypto.randomUUID()}`,
1599+ );
1600+ yield* runtime.persistence.rows.providerAuthGrants.upsert({
1601+ id: grantId,
1602+ workspaceId: installation.workspaceId,
1603+ actorAccountId: installation.accountId,
1604+ providerKey: "google_workspace",
1605+ oauthClientId: oauthClient.id,
1606+ tokenEndpoint: googleServer.tokenEndpoint,
1607+ clientAuthentication: "client_secret_post",
1608+ headerName: "Authorization",
1609+ prefix: "Bearer ",
1610+ refreshToken: {
1611+ providerId: "local",
1612+ handle: refreshSecretId,
1613+ },
1614+ grantedScopes: ["scope:drive.readonly"],
1615+ lastRefreshedAt: null,
1616+ orphanedAt: 123,
1617+ createdAt: Date.now(),
1618+ updatedAt: Date.now(),
1619+ });
1620+
1621+ googleServer.queueTokenResponse({
1622+ access_token: "reuse-access-token",
1623+ scope: "scope:drive.readonly",
1624+ });
1625+
1626+ const result = yield* withControlPlaneClient({ runtime }, (client) =>
1627+ client.sources.connect({
1628+ path: {
1629+ workspaceId: installation.workspaceId,
1630+ },
1631+ payload: {
1632+ kind: "google_discovery",
1633+ workspaceOauthClientId: oauthClient.id,
1634+ service: "drive",
1635+ version: "v3",
1636+ discoveryUrl: googleServer.discoveryUrl({
1637+ service: "drive",
1638+ version: "v3",
1639+ scope: "scope:drive.readonly",
1640+ }),
1641+ scopes: ["scope:drive.readonly"],
1642+ name: "Drive",
1643+ namespace: "google.drive",
1644+ },
1645+ }),
1646+ );
1647+
1648+ expect(result.kind).toBe("connected");
1649+ if (result.kind !== "connected") {
1650+ throw new Error(`Expected connected result, received ${result.kind}`);
1651+ }
1652+ expect(result.source.auth).toEqual({
1653+ kind: "provider_grant_ref",
1654+ grantId,
1655+ providerKey: "google_workspace",
1656+ requiredScopes: ["scope:drive.readonly"],
1657+ headerName: "Authorization",
1658+ prefix: "Bearer ",
1659+ });
1660+
1661+ const storedGrant =
1662+ yield* runtime.persistence.rows.providerAuthGrants.getById(grantId);
1663+ assertTrue(Option.isSome(storedGrant));
1664+ expect(storedGrant.value.orphanedAt).toBeNull();
1665+ expect(googleServer.discoveryAuthorizations).toContain(
1666+ "Bearer reuse-access-token",
1667+ );
1668+ }),
1669+ 60_000,
1670+ );
1671+
1672+ it.scoped(
1673+ "uses the app callback redirect for browser-started Google single-source OAuth",
1674+ () =>
1675+ Effect.gen(function* () {
1676+ const runtime = yield* makeRuntime;
1677+ const googleServer = yield* makeGoogleWorkspaceTestServer;
1678+ const installation = runtime.localInstallation;
1679+
1680+ const oauthClient = yield* withControlPlaneClient(
1681+ { runtime },
1682+ (client) =>
1683+ client.sources.createWorkspaceOauthClient({
1684+ path: {
1685+ workspaceId: installation.workspaceId,
1686+ },
1687+ payload: {
1688+ providerKey: "google_workspace",
1689+ label: "Local Google Workspace Client",
1690+ oauthClient: {
1691+ clientId: "google-client-id",
1692+ clientSecret: "google-client-secret",
1693+ },
1694+ },
1695+ }),
1696+ );
1697+
1698+ const result = yield* withControlPlaneClient({ runtime }, (client) =>
1699+ client.sources.connect({
1700+ path: {
1701+ workspaceId: installation.workspaceId,
1702+ },
1703+ payload: {
1704+ kind: "google_discovery",
1705+ workspaceOauthClientId: oauthClient.id,
1706+ service: "gmail",
1707+ version: "v1",
1708+ discoveryUrl: googleServer.discoveryUrl({
1709+ service: "gmail",
1710+ version: "v1",
1711+ scope: "scope:gmail.readonly",
1712+ }),
1713+ scopes: ["scope:gmail.readonly"],
1714+ name: "Gmail",
1715+ namespace: "google.gmail",
1716+ },
1717+ }),
1718+ );
1719+
1720+ expect(result.kind).toBe("oauth_required");
1721+ if (result.kind !== "oauth_required") {
1722+ throw new Error(
1723+ `Expected oauth_required result, received ${result.kind}`,
1724+ );
1725+ }
1726+
1727+ const redirectUri = new URL(result.authorizationUrl).searchParams.get(
1728+ "redirect_uri",
1729+ );
1730+ expect(redirectUri).not.toBeNull();
1731+ expect(new URL(redirectUri!).pathname).toBe(
1732+ `/v1/workspaces/${encodeURIComponent(installation.workspaceId)}/oauth/provider/callback`,
1733+ );
1734+ expect(result.source.status).toBe("auth_required");
1735+ }),
1736+ 60_000,
1737+ );
1738+
1739+ it.scoped(
1740+ "reuses existing Google provider grants for batch connects and clears orphan state",
1741+ () =>
1742+ Effect.gen(function* () {
1743+ const runtime = yield* makeRuntime;
1744+ const googleServer = yield* makeGoogleWorkspaceTestServer;
1745+ const installation = runtime.localInstallation;
1746+
1747+ const oauthClient = yield* withControlPlaneClient(
1748+ { runtime },
1749+ (client) =>
1750+ client.sources.createWorkspaceOauthClient({
1751+ path: {
1752+ workspaceId: installation.workspaceId,
1753+ },
1754+ payload: {
1755+ providerKey: "google_workspace",
1756+ label: "Local Google Workspace Client",
1757+ oauthClient: {
1758+ clientId: "google-client-id",
1759+ clientSecret: "google-client-secret",
1760+ },
1761+ },
1762+ }),
1763+ );
1764+
1765+ const refreshSecretId = "sec_google_refresh_reuse";
1766+ yield* upsertLocalSecret(runtime, {
1767+ id: refreshSecretId,
1768+ name: "Google Refresh Token",
1769+ purpose: "oauth_refresh_token",
1770+ value: "refresh-token-reuse",
1771+ });
1772+
1773+ const grantId = ProviderAuthGrantIdSchema.make(
1774+ `provider_grant_${crypto.randomUUID()}`,
1775+ );
1776+ yield* runtime.persistence.rows.providerAuthGrants.upsert({
1777+ id: grantId,
1778+ workspaceId: installation.workspaceId,
1779+ actorAccountId: installation.accountId,
1780+ providerKey: "google_workspace",
1781+ oauthClientId: oauthClient.id,
1782+ tokenEndpoint: googleServer.tokenEndpoint,
1783+ clientAuthentication: "client_secret_post",
1784+ headerName: "Authorization",
1785+ prefix: "Bearer ",
1786+ refreshToken: {
1787+ providerId: "local",
1788+ handle: refreshSecretId,
1789+ },
1790+ grantedScopes: ["scope:gmail.readonly", "scope:calendar.readonly"],
1791+ lastRefreshedAt: null,
1792+ orphanedAt: 123,
1793+ createdAt: Date.now(),
1794+ updatedAt: Date.now(),
1795+ });
1796+
1797+ googleServer.queueTokenResponse({
1798+ access_token: "reuse-access-token",
1799+ scope: "scope:gmail.readonly scope:calendar.readonly",
1800+ });
1801+ googleServer.queueTokenResponse({
1802+ access_token: "reuse-access-token",
1803+ scope: "scope:gmail.readonly scope:calendar.readonly",
1804+ });
1805+
1806+ const result = yield* withControlPlaneClient({ runtime }, (client) =>
1807+ client.sources.connectBatch({
1808+ path: {
1809+ workspaceId: installation.workspaceId,
1810+ },
1811+ payload: {
1812+ workspaceOauthClientId: oauthClient.id,
1813+ sources: [
1814+ {
1815+ service: "gmail",
1816+ version: "v1",
1817+ discoveryUrl: googleServer.discoveryUrl({
1818+ service: "gmail",
1819+ version: "v1",
1820+ scope: "scope:gmail.readonly",
1821+ }),
1822+ scopes: ["scope:gmail.readonly"],
1823+ name: "Gmail",
1824+ namespace: "google.gmail",
1825+ },
1826+ {
1827+ service: "calendar",
1828+ version: "v3",
1829+ discoveryUrl: googleServer.discoveryUrl({
1830+ service: "calendar",
1831+ version: "v3",
1832+ scope: "scope:calendar.readonly",
1833+ }),
1834+ scopes: ["scope:calendar.readonly"],
1835+ name: "Calendar",
1836+ namespace: "google.calendar",
1837+ },
1838+ ],
1839+ },
1840+ }),
1841+ );
1842+
1843+ expect(result.providerOauthSession).toBeNull();
1844+ expect(result.results).toHaveLength(2);
1845+ expect(
1846+ result.results.every((entry) => entry.status === "connected"),
1847+ ).toBe(true);
1848+ expect(
1849+ result.results.every(
1850+ (entry) => entry.source.auth.kind === "provider_grant_ref",
1851+ ),
1852+ ).toBe(true);
1853+
1854+ const storedGrant =
1855+ yield* runtime.persistence.rows.providerAuthGrants.getById(grantId);
1856+ assertTrue(Option.isSome(storedGrant));
1857+ expect(storedGrant.value.orphanedAt).toBeNull();
1858+ expect(googleServer.tokenRequests.length).toBeGreaterThanOrEqual(1);
1859+ expect(
1860+ googleServer.discoveryAuthorizations.every(
1861+ (authorization) => authorization === "Bearer reuse-access-token",
1862+ ),
1863+ ).toBe(true);
1864+ }),
1865+ 60_000,
1866+ );
1867+
1868+ it.scoped(
1869+ "uses the app callback redirect for browser-started Google batch OAuth",
1870+ () =>
1871+ Effect.gen(function* () {
1872+ const runtime = yield* makeRuntime;
1873+ const googleServer = yield* makeGoogleWorkspaceTestServer;
1874+ const installation = runtime.localInstallation;
1875+
1876+ const oauthClient = yield* withControlPlaneClient(
1877+ { runtime },
1878+ (client) =>
1879+ client.sources.createWorkspaceOauthClient({
1880+ path: {
1881+ workspaceId: installation.workspaceId,
1882+ },
1883+ payload: {
1884+ providerKey: "google_workspace",
1885+ label: "Local Google Workspace Client",
1886+ oauthClient: {
1887+ clientId: "google-client-id",
1888+ clientSecret: "google-client-secret",
1889+ },
1890+ },
1891+ }),
1892+ );
1893+
1894+ const result = yield* withControlPlaneClient({ runtime }, (client) =>
1895+ client.sources.connectBatch({
1896+ path: {
1897+ workspaceId: installation.workspaceId,
1898+ },
1899+ payload: {
1900+ workspaceOauthClientId: oauthClient.id,
1901+ sources: [
1902+ {
1903+ service: "gmail",
1904+ version: "v1",
1905+ discoveryUrl: googleServer.discoveryUrl({
1906+ service: "gmail",
1907+ version: "v1",
1908+ scope: "scope:gmail.readonly",
1909+ }),
1910+ scopes: ["scope:gmail.readonly"],
1911+ name: "Gmail",
1912+ namespace: "google.gmail",
1913+ },
1914+ ],
1915+ },
1916+ }),
1917+ );
1918+
1919+ expect(result.providerOauthSession).not.toBeNull();
1920+ const redirectUri = new URL(
1921+ result.providerOauthSession!.authorizationUrl,
1922+ ).searchParams.get("redirect_uri");
1923+
1924+ expect(redirectUri).not.toBeNull();
1925+ expect(new URL(redirectUri!).pathname).toBe(
1926+ `/v1/workspaces/${encodeURIComponent(installation.workspaceId)}/oauth/provider/callback`,
1927+ );
1928+ }),
1929+ 60_000,
1930+ );
1931+
1932+ it.scoped(
1933+ "preserves the existing provider refresh token when callback expansion omits refresh_token",
1934+ () =>
1935+ Effect.gen(function* () {
1936+ const runtime = yield* makeRuntime;
1937+ const googleServer = yield* makeGoogleWorkspaceTestServer;
1938+ const installation = runtime.localInstallation;
1939+
1940+ const oauthClient = yield* withControlPlaneClient(
1941+ { runtime },
1942+ (client) =>
1943+ client.sources.createWorkspaceOauthClient({
1944+ path: {
1945+ workspaceId: installation.workspaceId,
1946+ },
1947+ payload: {
1948+ providerKey: "google_workspace",
1949+ label: "Local Google Workspace Client",
1950+ oauthClient: {
1951+ clientId: "google-client-id",
1952+ clientSecret: "google-client-secret",
1953+ },
1954+ },
1955+ }),
1956+ );
1957+
1958+ const preservedRefreshSecretId = "sec_google_refresh_preserved";
1959+ yield* upsertLocalSecret(runtime, {
1960+ id: preservedRefreshSecretId,
1961+ name: "Preserved Google Refresh Token",
1962+ purpose: "oauth_refresh_token",
1963+ value: "refresh-token-preserved",
1964+ });
1965+
1966+ const source = yield* createPersistedGoogleSource({
1967+ runtime,
1968+ name: "Drive",
1969+ namespace: "google.drive",
1970+ service: "drive",
1971+ version: "v3",
1972+ discoveryUrl: googleServer.discoveryUrl({
1973+ service: "drive",
1974+ version: "v3",
1975+ scope: "scope:drive.readonly",
1976+ }),
1977+ scopes: ["scope:drive.readonly"],
1978+ auth: {
1979+ kind: "none",
1980+ },
1981+ });
1982+
1983+ const grantId = ProviderAuthGrantIdSchema.make(
1984+ `provider_grant_${crypto.randomUUID()}`,
1985+ );
1986+ yield* runtime.persistence.rows.providerAuthGrants.upsert({
1987+ id: grantId,
1988+ workspaceId: installation.workspaceId,
1989+ actorAccountId: installation.accountId,
1990+ providerKey: "google_workspace",
1991+ oauthClientId: oauthClient.id,
1992+ tokenEndpoint: googleServer.tokenEndpoint,
1993+ clientAuthentication: "client_secret_post",
1994+ headerName: "Authorization",
1995+ prefix: "Bearer ",
1996+ refreshToken: {
1997+ providerId: "local",
1998+ handle: preservedRefreshSecretId,
1999+ },
2000+ grantedScopes: ["scope:gmail.readonly"],
2001+ lastRefreshedAt: null,
2002+ orphanedAt: null,
2003+ createdAt: Date.now(),
2004+ updatedAt: Date.now(),
2005+ });
2006+
2007+ const sessionId = SourceAuthSessionIdSchema.make(
2008+ `src_auth_${crypto.randomUUID()}`,
2009+ );
2010+ const state = `provider-state-${crypto.randomUUID()}`;
2011+ yield* runtime.persistence.rows.sourceAuthSessions.upsert({
2012+ id: sessionId,
2013+ workspaceId: installation.workspaceId,
2014+ sourceId: SourceIdSchema.make(
2015+ `oauth_provider_${crypto.randomUUID()}`,
2016+ ),
2017+ actorAccountId: installation.accountId,
2018+ credentialSlot: "runtime",
2019+ executionId: null,
2020+ interactionId: null,
2021+ providerKind: "oauth2_provider_batch",
2022+ status: "pending",
2023+ state,
2024+ sessionDataJson: JSON.stringify({
2025+ kind: "provider_oauth_batch",
2026+ providerKey: "google_workspace",
2027+ authorizationEndpoint:
2028+ "https://accounts.google.com/o/oauth2/v2/auth",
2029+ tokenEndpoint: googleServer.tokenEndpoint,
2030+ redirectUri: `${googleServer.baseUrl}/oauth/callback`,
2031+ oauthClientId: oauthClient.id,
2032+ clientAuthentication: "client_secret_post",
2033+ scopes: ["scope:gmail.readonly", "scope:drive.readonly"],
2034+ headerName: "Authorization",
2035+ prefix: "Bearer ",
2036+ authorizationParams: {
2037+ access_type: "offline",
2038+ prompt: "consent",
2039+ include_granted_scopes: "true",
2040+ },
2041+ targetSources: [
2042+ {
2043+ sourceId: source.id,
2044+ requiredScopes: ["scope:drive.readonly"],
2045+ },
2046+ ],
2047+ codeVerifier: "pkce-verifier",
2048+ authorizationUrl: `https://accounts.google.com/o/oauth2/v2/auth?state=${encodeURIComponent(state)}`,
2049+ }),
2050+ errorText: null,
2051+ completedAt: null,
2052+ createdAt: Date.now(),
2053+ updatedAt: Date.now(),
2054+ });
2055+
2056+ googleServer.queueTokenResponse({
2057+ access_token: "authorization-code-access-token",
2058+ scope: "scope:gmail.readonly scope:drive.readonly",
2059+ });
2060+ googleServer.queueTokenResponse({
2061+ access_token: "post-callback-sync-access-token",
2062+ scope: "scope:gmail.readonly scope:drive.readonly",
2063+ });
2064+
2065+ const callbackPage = yield* withControlPlaneClient(
2066+ { runtime },
2067+ (client) =>
2068+ client.sources.providerOauthComplete({
2069+ path: {
2070+ workspaceId: installation.workspaceId,
2071+ },
2072+ urlParams: {
2073+ state,
2074+ code: "authorization-code",
2075+ },
2076+ }),
2077+ );
2078+
2079+ expect(callbackPage).toContain("Connected 1 source");
2080+
2081+ const storedGrant =
2082+ yield* runtime.persistence.rows.providerAuthGrants.getById(grantId);
2083+ assertTrue(Option.isSome(storedGrant));
2084+ expect(storedGrant.value.refreshToken).toEqual({
2085+ providerId: "local",
2086+ handle: preservedRefreshSecretId,
2087+ });
2088+ expect(storedGrant.value.grantedScopes).toEqual([
2089+ "scope:gmail.readonly",
2090+ "scope:drive.readonly",
2091+ ]);
2092+
2093+ const refreshedSource = yield* withControlPlaneClient(
2094+ { runtime },
2095+ (client) =>
2096+ client.sources.get({
2097+ path: {
2098+ workspaceId: installation.workspaceId,
2099+ sourceId: source.id,
2100+ },
2101+ }),
2102+ );
2103+
2104+ expect(refreshedSource.status).toBe("connected");
2105+ expect(refreshedSource.auth).toEqual({
2106+ kind: "provider_grant_ref",
2107+ grantId,
2108+ providerKey: "google_workspace",
2109+ requiredScopes: ["scope:drive.readonly"],
2110+ headerName: "Authorization",
2111+ prefix: "Bearer ",
2112+ });
2113+
2114+ expect(
2115+ googleServer.tokenRequests.map((request) =>
2116+ request.get("grant_type"),
2117+ ),
2118+ ).toEqual(["authorization_code", "refresh_token"]);
2119+ expect(googleServer.tokenRequests[1]?.get("refresh_token")).toBe(
2120+ "refresh-token-preserved",
2121+ );
2122+ expect(googleServer.discoveryAuthorizations).toContain(
2123+ "Bearer post-callback-sync-access-token",
2124+ );
2125+ }),
2126+ 60_000,
2127+ );
2128+
2129+ it.scoped(
2130+ "marks provider grants as orphaned when the last referencing source is removed",
2131+ () =>
2132+ Effect.gen(function* () {
2133+ const runtime = yield* makeRuntime;
2134+ const installation = runtime.localInstallation;
2135+ const oauthClientId = WorkspaceOauthClientIdSchema.make(
2136+ `ws_oauth_client_${crypto.randomUUID()}`,
2137+ );
2138+ const refreshSecretId = "sec_google_refresh_orphan";
2139+
2140+ yield* runtime.persistence.rows.workspaceOauthClients.upsert({
2141+ id: oauthClientId,
2142+ workspaceId: installation.workspaceId,
2143+ providerKey: "google_workspace",
2144+ label: "Local Google Workspace Client",
2145+ clientId: "google-client-id",
2146+ clientSecretProviderId: null,
2147+ clientSecretHandle: null,
2148+ clientMetadataJson: null,
2149+ createdAt: Date.now(),
2150+ updatedAt: Date.now(),
2151+ });
2152+ yield* upsertLocalSecret(runtime, {
2153+ id: refreshSecretId,
2154+ name: "Google Refresh Token",
2155+ purpose: "oauth_refresh_token",
2156+ value: "refresh-token-orphan",
2157+ });
2158+
2159+ const grantId = ProviderAuthGrantIdSchema.make(
2160+ `provider_grant_${crypto.randomUUID()}`,
2161+ );
2162+ yield* runtime.persistence.rows.providerAuthGrants.upsert({
2163+ id: grantId,
2164+ workspaceId: installation.workspaceId,
2165+ actorAccountId: installation.accountId,
2166+ providerKey: "google_workspace",
2167+ oauthClientId,
2168+ tokenEndpoint: "https://example.test/oauth/token",
2169+ clientAuthentication: "client_secret_post",
2170+ headerName: "Authorization",
2171+ prefix: "Bearer ",
2172+ refreshToken: {
2173+ providerId: "local",
2174+ handle: refreshSecretId,
2175+ },
2176+ grantedScopes: ["scope:drive.readonly"],
2177+ lastRefreshedAt: null,
2178+ orphanedAt: null,
2179+ createdAt: Date.now(),
2180+ updatedAt: Date.now(),
2181+ });
2182+
2183+ const source = yield* createPersistedGoogleSource({
2184+ runtime,
2185+ name: "Drive",
2186+ namespace: "google.drive",
2187+ service: "drive",
2188+ version: "v3",
2189+ discoveryUrl: "https://example.test/drive/$discovery/rest?version=v3",
2190+ scopes: ["scope:drive.readonly"],
2191+ auth: {
2192+ kind: "provider_grant_ref",
2193+ grantId,
2194+ providerKey: "google_workspace",
2195+ requiredScopes: ["scope:drive.readonly"],
2196+ headerName: "Authorization",
2197+ prefix: "Bearer ",
2198+ },
2199+ });
2200+
2201+ const removed = yield* withControlPlaneClient({ runtime }, (client) =>
2202+ client.sources.remove({
2203+ path: {
2204+ workspaceId: installation.workspaceId,
2205+ sourceId: source.id,
2206+ },
2207+ }),
2208+ );
2209+
2210+ expect(removed.removed).toBe(true);
2211+
2212+ const storedGrant =
2213+ yield* runtime.persistence.rows.providerAuthGrants.getById(grantId);
2214+ assertTrue(Option.isSome(storedGrant));
2215+ expect(storedGrant.value.orphanedAt).not.toBeNull();
2216+ }),
2217+ );
2218+
2219+ it.scoped(
2220+ "revokes shared provider grants through the API and disconnects linked sources",
2221+ () =>
2222+ Effect.gen(function* () {
2223+ const runtime = yield* makeRuntime;
2224+ const installation = runtime.localInstallation;
2225+ const oauthClientId = WorkspaceOauthClientIdSchema.make(
2226+ `ws_oauth_client_${crypto.randomUUID()}`,
2227+ );
2228+ const refreshSecretId = "sec_google_refresh_revoke";
2229+
2230+ yield* runtime.persistence.rows.workspaceOauthClients.upsert({
2231+ id: oauthClientId,
2232+ workspaceId: installation.workspaceId,
2233+ providerKey: "google_workspace",
2234+ label: "Local Google Workspace Client",
2235+ clientId: "google-client-id",
2236+ clientSecretProviderId: null,
2237+ clientSecretHandle: null,
2238+ clientMetadataJson: null,
2239+ createdAt: Date.now(),
2240+ updatedAt: Date.now(),
2241+ });
2242+ yield* upsertLocalSecret(runtime, {
2243+ id: refreshSecretId,
2244+ name: "Google Refresh Token",
2245+ purpose: "oauth_refresh_token",
2246+ value: "refresh-token-revoke",
2247+ });
2248+
2249+ const grantId = ProviderAuthGrantIdSchema.make(
2250+ `provider_grant_${crypto.randomUUID()}`,
2251+ );
2252+ yield* runtime.persistence.rows.providerAuthGrants.upsert({
2253+ id: grantId,
2254+ workspaceId: installation.workspaceId,
2255+ actorAccountId: installation.accountId,
2256+ providerKey: "google_workspace",
2257+ oauthClientId,
2258+ tokenEndpoint: "https://example.test/oauth/token",
2259+ clientAuthentication: "client_secret_post",
2260+ headerName: "Authorization",
2261+ prefix: "Bearer ",
2262+ refreshToken: {
2263+ providerId: "local",
2264+ handle: refreshSecretId,
2265+ },
2266+ grantedScopes: ["scope:drive.readonly", "scope:gmail.readonly"],
2267+ lastRefreshedAt: null,
2268+ orphanedAt: null,
2269+ createdAt: Date.now(),
2270+ updatedAt: Date.now(),
2271+ });
2272+
2273+ const driveSource = yield* createPersistedGoogleSource({
2274+ runtime,
2275+ name: "Drive",
2276+ namespace: "google.drive",
2277+ service: "drive",
2278+ version: "v3",
2279+ discoveryUrl: "https://example.test/drive/$discovery/rest?version=v3",
2280+ scopes: ["scope:drive.readonly"],
2281+ auth: {
2282+ kind: "provider_grant_ref",
2283+ grantId,
2284+ providerKey: "google_workspace",
2285+ requiredScopes: ["scope:drive.readonly"],
2286+ headerName: "Authorization",
2287+ prefix: "Bearer ",
2288+ },
2289+ });
2290+ const gmailSource = yield* createPersistedGoogleSource({
2291+ runtime,
2292+ name: "Gmail",
2293+ namespace: "google.gmail",
2294+ service: "gmail",
2295+ version: "v1",
2296+ discoveryUrl: "https://example.test/gmail/$discovery/rest?version=v1",
2297+ scopes: ["scope:gmail.readonly"],
2298+ auth: {
2299+ kind: "provider_grant_ref",
2300+ grantId,
2301+ providerKey: "google_workspace",
2302+ requiredScopes: ["scope:gmail.readonly"],
2303+ headerName: "Authorization",
2304+ prefix: "Bearer ",
2305+ },
2306+ });
2307+
2308+ const removed = yield* withControlPlaneClient({ runtime }, (client) =>
2309+ client.sources.removeProviderAuthGrant({
2310+ path: {
2311+ workspaceId: installation.workspaceId,
2312+ grantId,
2313+ },
2314+ }),
2315+ );
2316+
2317+ expect(removed.removed).toBe(true);
2318+
2319+ const storedGrant =
2320+ yield* runtime.persistence.rows.providerAuthGrants.getById(grantId);
2321+ assertTrue(Option.isNone(storedGrant));
2322+
2323+ const storedRefreshSecret =
2324+ yield* runtime.persistence.rows.secretMaterials.getById(
2325+ SecretMaterialIdSchema.make(refreshSecretId),
2326+ );
2327+ assertTrue(Option.isNone(storedRefreshSecret));
2328+
2329+ const disconnectedDrive = yield* withControlPlaneClient(
2330+ { runtime },
2331+ (client) =>
2332+ client.sources.get({
2333+ path: {
2334+ workspaceId: installation.workspaceId,
2335+ sourceId: driveSource.id,
2336+ },
2337+ }),
2338+ );
2339+ const disconnectedGmail = yield* withControlPlaneClient(
2340+ { runtime },
2341+ (client) =>
2342+ client.sources.get({
2343+ path: {
2344+ workspaceId: installation.workspaceId,
2345+ sourceId: gmailSource.id,
2346+ },
2347+ }),
2348+ );
2349+
2350+ expect(disconnectedDrive.status).toBe("auth_required");
2351+ expect(disconnectedDrive.auth).toEqual({ kind: "none" });
2352+ expect(disconnectedGmail.status).toBe("auth_required");
2353+ expect(disconnectedGmail.auth).toEqual({ kind: "none" });
2354+ }),
2355+ );
2356+});
packages/platform/sdk/src/runtime/effect-errors.tsrenamed from packages/platform/control-plane/src/runtime/effect-errors.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/execution/http.test.tsrenamed from packages/platform/control-plane/src/runtime/execution/http.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/execution/ir-execution.tsrenamed from packages/platform/control-plane/src/runtime/execution/ir-execution.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/execution/live.test.tsrenamed from packages/platform/control-plane/src/runtime/execution/live.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/execution/live.tsrenamed from packages/platform/control-plane/src/runtime/execution/live.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/execution/mcp-resume.test.tsrenamed from packages/platform/control-plane/src/runtime/execution/mcp-resume.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/execution/runtime-config.test.tsrenamed from packages/platform/control-plane/src/runtime/execution/runtime-config.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/execution/runtime.tsrenamed from packages/platform/control-plane/src/runtime/execution/runtime.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/execution/service.tsrenamed from packages/platform/control-plane/src/runtime/execution/service.ts+2−2
@@ -1,7 +1,7 @@
11 import {
22 ControlPlaneNotFoundError,
33 ControlPlaneStorageError,
4−} from "../../api/errors";
4+} from "../../errors";
55 import type {
66 ElicitationResponse,
77 OnElicitation,
@@ -11,7 +11,7 @@ import type { · 3 unmodified lines
1111 import type {
1212 CreateExecutionPayload,
1313 ResumeExecutionPayload,
14−} from "../../api/executions/api";
14+} from "../../executions/contracts";
1515 import {
1616 ExecutionIdSchema,
1717 ExecutionInteractionIdSchema,
packages/platform/sdk/src/runtime/execution/state.tsrenamed from packages/platform/control-plane/src/runtime/execution/state.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/execution/test-http-client.tsrenamed from packages/platform/control-plane/src/runtime/execution/test-http-client.ts+3−4
@@ -3,13 +3,12 @@ import { · 2 unmodified lines
33 HttpApiClient,
44 } from "@effect/platform";
55 import { NodeHttpServer } from "@effect/platform-node";
6−import * as Effect from "effect/Effect";
7−import * as Layer from "effect/Layer";
8−
96 import {
107 ControlPlaneApi,
118 createControlPlaneApiLayer,
12−} from "#api";
9+} from "@executor/platform-api";
10+import * as Effect from "effect/Effect";
11+import * as Layer from "effect/Layer";
1312
1413 import {
1514 type ControlPlaneRuntime,
packages/platform/sdk/src/runtime/execution/workspace/authorization.tsrenamed from packages/platform/control-plane/src/runtime/execution/workspace/authorization.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/execution/workspace/environment.tsrenamed from packages/platform/control-plane/src/runtime/execution/workspace/environment.ts+51−22
@@ -15,16 +15,11 @@ import { · 14 unmodified lines
1515 resolveConfiguredExecutionRuntime,
1616 } from "../runtime";
1717 import { createWorkspaceToolInvoker } from "./tool-invoker";
18−import {
19− RuntimeSourceAuthServiceTag,
20−} from "../../sources/source-auth-service";
21−import {
22− RuntimeSourceCatalogStoreService,
23−} from "../../catalog/source/runtime";
18+import { RuntimeSourceAuthServiceTag } from "../../sources/source-auth-service";
19+import { RuntimeSourceCatalogStoreService } from "../../catalog/source/runtime";
2420 import { RuntimeSourceAuthMaterialService } from "../../auth/source-auth-material";
25−import {
26− getRuntimeLocalWorkspaceOption,
27−} from "../../local/runtime-context";
21+import { RuntimeSourceCatalogSyncService } from "../../catalog/source/sync";
22+import { getRuntimeLocalWorkspaceOption } from "../../local/runtime-context";
2823 import {
2924 LocalToolRuntimeLoaderService,
3025 type LocalToolRuntimeLoaderShape,
@@ -38,6 +33,9 @@ import { · 7 unmodified lines
3833 WorkspaceStateStore,
3934 type WorkspaceStateStoreShape,
4035 } from "../../local/storage";
36+import { ControlPlaneStore } from "../../store";
37+import { RuntimeSourceStoreService } from "../../sources/source-store";
38+import type { CreateWorkspaceInternalToolMap } from "./tool-invoker";
4139 export {
4240 createCodeExecutorForRuntime,
4341 resolveConfiguredExecutionRuntime,
@@ -50,29 +48,49 @@ const createEmptyLocalToolRuntime = (): LocalToolRuntime => ({ · 6 unmodified lines
5048 toolPaths: new Set<string>(),
5149 });
5250
53−export const createWorkspaceExecutionEnvironmentResolver = (input: {
54− sourceAuthMaterialService: Effect.Effect.Success<typeof RuntimeSourceAuthMaterialService>;
55− sourceAuthService: Effect.Effect.Success<typeof RuntimeSourceAuthServiceTag>;
56− sourceCatalogStore: Effect.Effect.Success<typeof RuntimeSourceCatalogStoreService>;
57− localToolRuntimeLoader: LocalToolRuntimeLoaderShape;
58− workspaceConfigStore: WorkspaceConfigStoreShape;
59− workspaceStateStore: WorkspaceStateStoreShape;
60− sourceArtifactStore: SourceArtifactStoreShape;
61−}): ResolveExecutionEnvironment =>
51+export const createWorkspaceExecutionEnvironmentResolver =
52+ (input: {
53+ controlPlaneStore: Effect.Effect.Success<typeof ControlPlaneStore>;
54+ sourceStore: Effect.Effect.Success<typeof RuntimeSourceStoreService>;
55+ sourceCatalogSyncService: Effect.Effect.Success<
56+ typeof RuntimeSourceCatalogSyncService
57+ >;
58+ sourceAuthMaterialService: Effect.Effect.Success<
59+ typeof RuntimeSourceAuthMaterialService
60+ >;
61+ sourceAuthService: Effect.Effect.Success<
62+ typeof RuntimeSourceAuthServiceTag
63+ >;
64+ sourceCatalogStore: Effect.Effect.Success<
65+ typeof RuntimeSourceCatalogStoreService
66+ >;
67+ localToolRuntimeLoader: LocalToolRuntimeLoaderShape;
68+ workspaceConfigStore: WorkspaceConfigStoreShape;
69+ workspaceStateStore: WorkspaceStateStoreShape;
70+ sourceArtifactStore: SourceArtifactStoreShape;
71+ createInternalToolMap?: CreateWorkspaceInternalToolMap;
72+ }): ResolveExecutionEnvironment =>
6273 ({ workspaceId, accountId, onElicitation }) =>
6374 Effect.gen(function* () {
6475 const runtimeLocalWorkspace = yield* getRuntimeLocalWorkspaceOption();
6576 const loadedConfig =
6677 runtimeLocalWorkspace === null
6778 ? null
68− : yield* input.workspaceConfigStore.load(runtimeLocalWorkspace.context);
79+ : yield* input.workspaceConfigStore.load(
80+ runtimeLocalWorkspace.context,
81+ );
6982 const localToolRuntime =
7083 runtimeLocalWorkspace === null
7184 ? createEmptyLocalToolRuntime()
72− : yield* input.localToolRuntimeLoader.load(runtimeLocalWorkspace.context);
85+ : yield* input.localToolRuntimeLoader.load(
86+ runtimeLocalWorkspace.context,
87+ );
7388 const { catalog, toolInvoker } = createWorkspaceToolInvoker({
7489 workspaceId,
7590 accountId,
91+ controlPlaneStore: input.controlPlaneStore,
92+ sourceStore: input.sourceStore,
93+ sourceCatalogSyncService: input.sourceCatalogSyncService,
7694 sourceCatalogStore: input.sourceCatalogStore,
7795 workspaceConfigStore: input.workspaceConfigStore,
7896 workspaceStateStore: input.workspaceStateStore,
@@ -81,6 +99,7 @@ export const createWorkspaceExecutionEnvironmentResolver = (input: { · 2 unmodified lines
8199 sourceAuthService: input.sourceAuthService,
82100 runtimeLocalWorkspace,
83101 localToolRuntime,
102+ createInternalToolMap: input.createInternalToolMap,
84103 onElicitation,
85104 });
86105
@@ -105,14 +124,20 @@ export class RuntimeExecutionResolverService extends Context.Tag( · 18 unmodified lines
105124 export const RuntimeExecutionResolverLive = (
106125 input: {
107126 executionResolver?: ResolveExecutionEnvironment;
127+ createInternalToolMap?: CreateWorkspaceInternalToolMap;
108128 } = {},
109129 ) =>
110130 input.executionResolver
111131 ? Layer.succeed(RuntimeExecutionResolverService, input.executionResolver)
112− : Layer.effect(
132+ : Layer.effect(
113133 RuntimeExecutionResolverService,
114134 Effect.gen(function* () {
115− const sourceAuthMaterialService = yield* RuntimeSourceAuthMaterialService;
135+ const controlPlaneStore = yield* ControlPlaneStore;
136+ const sourceStore = yield* RuntimeSourceStoreService;
137+ const sourceCatalogSyncService =
138+ yield* RuntimeSourceCatalogSyncService;
139+ const sourceAuthMaterialService =
140+ yield* RuntimeSourceAuthMaterialService;
116141 const sourceAuthService = yield* RuntimeSourceAuthServiceTag;
117142 const sourceCatalogStore = yield* RuntimeSourceCatalogStoreService;
118143 const localToolRuntimeLoader = yield* LocalToolRuntimeLoaderService;
@@ -121,6 +146,9 @@ export const RuntimeExecutionResolverLive = ( · 2 unmodified lines
121146 const sourceArtifactStore = yield* SourceArtifactStore;
122147
123148 return createWorkspaceExecutionEnvironmentResolver({
149+ controlPlaneStore,
150+ sourceStore,
151+ sourceCatalogSyncService,
124152 sourceAuthService,
125153 sourceAuthMaterialService,
126154 sourceCatalogStore,
@@ -128,6 +156,7 @@ export const RuntimeExecutionResolverLive = ( · 1 unmodified line
128156 workspaceConfigStore,
129157 workspaceStateStore,
130158 sourceArtifactStore,
159+ createInternalToolMap: input.createInternalToolMap,
131160 });
132161 }),
133162 );
packages/platform/sdk/src/runtime/execution/workspace/local.tsrenamed from packages/platform/control-plane/src/runtime/execution/workspace/local.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/execution/workspace/source-catalog.tsrenamed from packages/platform/control-plane/src/runtime/execution/workspace/source-catalog.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/execution/workspace/tool-invoker.tsrenamed from packages/platform/control-plane/src/runtime/execution/workspace/tool-invoker.ts+89−41
@@ -5,6 +5,7 @@ import { · 4 unmodified lines
55 mergeToolCatalogs,
66 mergeToolMaps,
77 type ToolCatalog,
8+ type ToolMap,
89 type ToolInvoker,
910 } from "@executor/codemode-core";
1011 import type { AccountId, Source } from "#schema";
@@ -13,19 +14,18 @@ import * as Effect from "effect/Effect"; · 2 unmodified lines
1314 import { RuntimeSourceAuthMaterialService } from "../../auth/source-auth-material";
1415 import { RuntimeSourceCatalogStoreService } from "../../catalog/source/runtime";
1516 import type { RuntimeLocalWorkspaceState } from "../../local/runtime-context";
16−import {
17− type LocalToolRuntime,
18−} from "../../local/tools";
17+import { type LocalToolRuntime } from "../../local/tools";
1918 import {
2019 makeWorkspaceStorageLayer,
2120 type SourceArtifactStoreShape,
2221 type WorkspaceConfigStoreShape,
2322 type WorkspaceStateStoreShape,
2423 } from "../../local/storage";
25−import {
26− RuntimeSourceAuthService,
27−} from "../../sources/source-auth-service";
24+import type { ControlPlaneStoreShape } from "../../store";
25+import { RuntimeSourceAuthService } from "../../sources/source-auth-service";
26+import { type RuntimeSourceStore } from "../../sources/source-store";
2827 import { createExecutorToolMap } from "../../sources/executor-tools";
28+import { RuntimeSourceCatalogSyncService } from "../../catalog/source/sync";
2929 import { invokeIrTool } from "../ir-execution";
3030 import {
3131 authorizePersistedToolInvocation,
@@ -38,17 +38,46 @@ import { · 6 unmodified lines
3838 } from "./source-catalog";
3939 import { runtimeEffectError } from "../../effect-errors";
4040
41+export type WorkspaceInternalToolContext = {
42+ workspaceId: Source["workspaceId"];
43+ accountId: AccountId;
44+ controlPlaneStore: ControlPlaneStoreShape;
45+ sourceStore: RuntimeSourceStore;
46+ sourceCatalogSyncService: Effect.Effect.Success<
47+ typeof RuntimeSourceCatalogSyncService
48+ >;
49+ sourceAuthService: RuntimeSourceAuthService;
50+ workspaceConfigStore: WorkspaceConfigStoreShape;
51+ workspaceStateStore: WorkspaceStateStoreShape;
52+ sourceArtifactStore: SourceArtifactStoreShape;
53+ runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null;
54+};
55+
56+export type CreateWorkspaceInternalToolMap = (
57+ input: WorkspaceInternalToolContext,
58+) => ToolMap;
59+
4160 export const createWorkspaceToolInvoker = (input: {
4261 workspaceId: Source["workspaceId"];
4362 accountId: AccountId;
44− sourceCatalogStore: Effect.Effect.Success<typeof RuntimeSourceCatalogStoreService>;
63+ controlPlaneStore: ControlPlaneStoreShape;
64+ sourceStore: RuntimeSourceStore;
65+ sourceCatalogSyncService: Effect.Effect.Success<
66+ typeof RuntimeSourceCatalogSyncService
67+ >;
68+ sourceCatalogStore: Effect.Effect.Success<
69+ typeof RuntimeSourceCatalogStoreService
70+ >;
4571 workspaceConfigStore: WorkspaceConfigStoreShape;
4672 workspaceStateStore: WorkspaceStateStoreShape;
4773 sourceArtifactStore: SourceArtifactStoreShape;
48− sourceAuthMaterialService: Effect.Effect.Success<typeof RuntimeSourceAuthMaterialService>;
74+ sourceAuthMaterialService: Effect.Effect.Success<
75+ typeof RuntimeSourceAuthMaterialService
76+ >;
4977 sourceAuthService: RuntimeSourceAuthService;
5078 runtimeLocalWorkspace: RuntimeLocalWorkspaceState | null;
5179 localToolRuntime: LocalToolRuntime;
80+ createInternalToolMap?: CreateWorkspaceInternalToolMap;
5281 onElicitation?: Parameters<
5382 typeof makeToolInvokerFromTools
5483 >[0]["onElicitation"];
@@ -70,6 +99,19 @@ export const createWorkspaceToolInvoker = (input: { · 15 unmodified lines
7099 sourceAuthService: input.sourceAuthService,
71100 runtimeLocalWorkspace: input.runtimeLocalWorkspace,
72101 });
102+ const internalTools =
103+ input.createInternalToolMap?.({
104+ workspaceId: input.workspaceId,
105+ accountId: input.accountId,
106+ controlPlaneStore: input.controlPlaneStore,
107+ sourceStore: input.sourceStore,
108+ sourceCatalogSyncService: input.sourceCatalogSyncService,
109+ sourceAuthService: input.sourceAuthService,
110+ workspaceConfigStore: input.workspaceConfigStore,
111+ workspaceStateStore: input.workspaceStateStore,
112+ sourceArtifactStore: input.sourceArtifactStore,
113+ runtimeLocalWorkspace: input.runtimeLocalWorkspace,
114+ }) ?? {};
73115 const sourceCatalog = createWorkspaceSourceCatalog({
74116 workspaceId: input.workspaceId,
75117 accountId: input.accountId,
@@ -92,6 +134,7 @@ export const createWorkspaceToolInvoker = (input: { · 16 unmodified lines
92134 const authoredTools = mergeToolMaps([
93135 systemTools,
94136 executorTools,
137+ internalTools,
95138 input.localToolRuntime.tools,
96139 ]);
97140 const authoredCatalog = createToolCatalogFromTools({
@@ -112,41 +155,46 @@ export const createWorkspaceToolInvoker = (input: { · 14 unmodified lines
112155 context?: Record<string, unknown>;
113156 }) =>
114157 provideRuntimeLocalWorkspace(
115− provideWorkspaceStorage(Effect.gen(function* () {
116− const catalogTool = yield* loadWorkspaceCatalogToolByPath({
117− workspaceId: input.workspaceId,
118− accountId: input.accountId,
119− sourceCatalogStore: input.sourceCatalogStore,
120− path: invocation.path,
121− includeSchemas: false,
122− });
123− if (!catalogTool) {
124− return yield* runtimeEffectError("execution/workspace/tool-invoker", `Unknown tool path: ${invocation.path}`);
125− }
158+ provideWorkspaceStorage(
159+ Effect.gen(function* () {
160+ const catalogTool = yield* loadWorkspaceCatalogToolByPath({
161+ workspaceId: input.workspaceId,
162+ accountId: input.accountId,
163+ sourceCatalogStore: input.sourceCatalogStore,
164+ path: invocation.path,
165+ includeSchemas: false,
166+ });
167+ if (!catalogTool) {
168+ return yield* runtimeEffectError(
169+ "execution/workspace/tool-invoker",
170+ `Unknown tool path: ${invocation.path}`,
171+ );
172+ }
126173
127− yield* authorizePersistedToolInvocation({
128− workspaceId: input.workspaceId,
129− tool: catalogTool,
130− args: invocation.args,
131− context: invocation.context,
132− onElicitation: input.onElicitation,
133− });
174+ yield* authorizePersistedToolInvocation({
175+ workspaceId: input.workspaceId,
176+ tool: catalogTool,
177+ args: invocation.args,
178+ context: invocation.context,
179+ onElicitation: input.onElicitation,
180+ });
134181
135− const auth = yield* input.sourceAuthMaterialService.resolve({
136− source: catalogTool.source,
137− actorAccountId: input.accountId,
138− context: toSecretResolutionContext(invocation.context),
139− });
140− return yield* invokeIrTool({
141− workspaceId: input.workspaceId,
142− accountId: input.accountId,
143− tool: catalogTool,
144− auth,
145− args: invocation.args,
146− onElicitation: input.onElicitation,
147− context: invocation.context,
148− });
149− })),
182+ const auth = yield* input.sourceAuthMaterialService.resolve({
183+ source: catalogTool.source,
184+ actorAccountId: input.accountId,
185+ context: toSecretResolutionContext(invocation.context),
186+ });
187+ return yield* invokeIrTool({
188+ workspaceId: input.workspaceId,
189+ accountId: input.accountId,
190+ tool: catalogTool,
191+ auth,
192+ args: invocation.args,
193+ onElicitation: input.onElicitation,
194+ context: invocation.context,
195+ });
196+ }),
197+ ),
150198 input.runtimeLocalWorkspace,
151199 );
152200
packages/platform/sdk/src/runtime/fixtures/google-sheets-discovery.jsonrenamed from packages/platform/control-plane/src/runtime/fixtures/google-sheets-discovery.json+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/fixtures/linear-introspection.jsonrenamed from packages/platform/control-plane/src/runtime/fixtures/linear-introspection.json+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/fixtures/neon-openapi.jsonrenamed from packages/platform/control-plane/src/runtime/fixtures/neon-openapi.json+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/fixtures/vercel-openapi.jsonrenamed from packages/platform/control-plane/src/runtime/fixtures/vercel-openapi.json+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/index.tsrenamed from packages/platform/control-plane/src/runtime/index.ts+63−46
@@ -6,8 +6,6 @@ import * as ManagedRuntime from "effect/ManagedRuntime"; · 5 unmodified lines
66 import * as Scope from "effect/Scope";
77 import { NodeFileSystem } from "@effect/platform-node";
88 import { clearAllMcpConnectionPools } from "@executor/source-mcp";
9−
10−import { type ControlPlaneApiRuntimeContext } from "#api";
119 import type { LocalInstallation } from "#schema";
1210
1311 import { type ResolveExecutionEnvironment } from "./execution/state";
@@ -20,9 +18,7 @@ import { · 6 unmodified lines
2018 type LocalControlPlanePersistence,
2119 } from "./local/control-plane-store";
2220
23−import {
24− resolveLocalWorkspaceContext,
25−} from "./local/config";
21+import { resolveLocalWorkspaceContext } from "./local/config";
2622 import {
2723 LocalStorageLive,
2824 LocalInstallationStore,
@@ -40,36 +36,57 @@ import { RuntimeSourceCatalogStoreLive } from "./catalog/source/runtime"; · 11 unmodified lines
4036 import { reconcileMissingSourceCatalogArtifacts } from "./catalog/source/reconcile";
4137 import { RuntimeSourceAuthMaterialLive } from "./auth/source-auth-material";
4238 import { RuntimeSourceCatalogSyncLive } from "./catalog/source/sync";
43−import {
44− RuntimeSourceAuthServiceLive,
45−} from "./sources/source-auth-service";
39+import { RuntimeSourceAuthServiceLive } from "./sources/source-auth-service";
4640 import type { ResolveSecretMaterial } from "./local/secret-material-providers";
4741 import { SecretMaterialLive } from "./local/secret-material-providers";
48−import {
49− RuntimeExecutionResolverLive,
50−} from "./execution/workspace/environment";
42+import { RuntimeExecutionResolverLive } from "./execution/workspace/environment";
43+import type { CreateWorkspaceInternalToolMap } from "./execution/workspace/tool-invoker";
5144
5245 export * from "./execution/state";
5346 export * from "./sources/executor-tools";
5447 export * from "./execution/live";
5548 export * from "./local/config";
49+export * from "./local/errors";
5650 export * from "./local/installation";
51+export * from "./local/runtime-context";
5752 export * from "./local/storage";
5853 export * from "./local/source-artifacts";
5954 export * from "./local/tools";
6055 export * from "./catalog/schema-type-signature";
56+export * from "./catalog/source/runtime";
57+export * from "./catalog/source/sync";
6158 export * from "./sources/source-auth-service";
6259 export * from "./local/secret-material-providers";
6360 export * from "./sources/source-credential-interactions";
6461 export * from "./sources/source-adapters/mcp";
62+export * from "./sources/source-store";
6563 export * from "./store";
6664 export * from "./execution/workspace/environment";
67−export * from "./sources/source-inspection";
68−export * from "./sources/source-discovery";
65+export * from "../sources/inspection";
66+export * from "../sources/discovery";
6967 export * from "./execution/service";
68+export type {
69+ CreateWorkspaceInternalToolMap,
70+ WorkspaceInternalToolContext,
71+} from "./execution/workspace/tool-invoker";
72+export {
73+ builtInSourceAdapters,
74+ connectableSourceAdapters,
75+ executorAddableSourceAdapters,
76+ localConfigurableSourceAdapters,
77+ getSourceAdapter,
78+ getSourceAdapterForSource,
79+ findSourceAdapterByProviderKey,
80+ sourceBindingStateFromSource,
81+ sourceAdapterCatalogKind,
82+ sourceAdapterRequiresInteractiveConnect,
83+ sourceAdapterUsesCredentialManagedAuth,
84+ isInternalSourceAdapter,
85+} from "./sources/source-adapters";
7086
7187 export type RuntimeControlPlaneOptions = {
7288 executionResolver?: ResolveExecutionEnvironment;
89+ createInternalToolMap?: CreateWorkspaceInternalToolMap;
7390 resolveSecretMaterial?: ResolveSecretMaterial;
7491 getLocalServerBaseUrl?: () => string | undefined;
7592 localDataDir?: string;
@@ -81,9 +98,7 @@ export type RuntimeControlPlaneOptions = { · 5 unmodified lines
8198 const detailsFromCause = (cause: unknown): string =>
8299 cause instanceof Error ? cause.message : String(cause);
83100
84−const toLocalRuntimeBootstrapError = (
85− cause: unknown,
86−): Error => {
101+const toLocalRuntimeBootstrapError = (cause: unknown): Error => {
87102 const details = detailsFromCause(cause);
88103 return new Error(`Failed initializing local runtime: ${details}`);
89104 };
@@ -91,11 +106,7 @@ const toLocalRuntimeBootstrapError = ( · 1 unmodified line
91106 const closeScope = (scope: Scope.CloseableScope) =>
92107 Scope.close(scope, Exit.void).pipe(Effect.orDie);
93108
94−export type RuntimeControlPlaneLayer = Layer.Layer<
95− ControlPlaneApiRuntimeContext,
96− never,
97− never
98−>;
109+export type RuntimeControlPlaneLayer = Layer.Layer<any, never, never>;
99110
100111 export const createRuntimeControlPlaneLayer = (
101112 input: RuntimeControlPlaneOptions & {
@@ -105,9 +116,7 @@ export const createRuntimeControlPlaneLayer = ( · 3 unmodified lines
105116 },
106117 ) => {
107118 const platformLayer = NodeFileSystem.layer;
108− const storageLayer = LocalStorageLive.pipe(
109− Layer.provide(platformLayer),
110− );
119+ const storageLayer = LocalStorageLive.pipe(Layer.provide(platformLayer));
111120 const localToolRuntimeLayer = LocalToolRuntimeLoaderLive.pipe(
112121 Layer.provide(platformLayer),
113122 );
@@ -157,11 +166,13 @@ export const createRuntimeControlPlaneLayer = ( · 43 unmodified lines
157166
158167 const executionResolverLayer = RuntimeExecutionResolverLive({
159168 executionResolver: input.executionResolver,
169+ createInternalToolMap: input.createInternalToolMap,
160170 }).pipe(
161171 Layer.provide(
162172 Layer.mergeAll(
163173 baseLayer,
164174 secretMaterialLayer,
175+ sourceStoreLayer,
165176 sourceAuthMaterialLayer,
166177 sourceCatalogSyncLayer,
167178 sourceAuthLayer,
@@ -187,7 +198,7 @@ export const createRuntimeControlPlaneLayer = ( · 19 unmodified lines
187198 export type ControlPlaneRuntime = {
188199 persistence: LocalControlPlanePersistence;
189200 localInstallation: LocalInstallation;
190− managedRuntime: ManagedRuntime.ManagedRuntime<ControlPlaneApiRuntimeContext, never>;
201+ managedRuntime: ManagedRuntime.ManagedRuntime<any, never>;
191202 runtimeLayer: RuntimeControlPlaneLayer;
192203 close: () => Promise<void>;
193204 };
@@ -197,13 +208,13 @@ export type CreateControlPlaneRuntimeOptions = RuntimeControlPlaneOptions; · 3 unmodified lines
197208 export const provideControlPlaneRuntime = <A, E, R>(
198209 effect: Effect.Effect<A, E, R>,
199210 runtime: ControlPlaneRuntime,
200−): Effect.Effect<A, E | never, Exclude<R, ControlPlaneApiRuntimeContext>> =>
211+): Effect.Effect<A, E | never, any> =>
201212 effect.pipe(Effect.provide(runtime.managedRuntime));
202213
203214 export const createControlPlaneRuntime = (
204215 options: CreateControlPlaneRuntimeOptions,
205216 ): Effect.Effect<ControlPlaneRuntime, Error> =>
206− Effect.gen(function* () {
217+ (Effect.gen(function* () {
207218 const scope = yield* Scope.make();
208219
209220 const localWorkspaceContext = yield* resolveLocalWorkspaceContext({
@@ -217,19 +228,20 @@ export const createControlPlaneRuntime = ( · 7 unmodified lines
217228 ),
218229 );
219230
220−
221231 const installationStore = LocalInstallationStore;
222232 const workspaceConfigStore = LocalWorkspaceConfigStore;
223233 const fileSystem = yield* FileSystem.FileSystem;
224234
225− const localInstallation = yield* installationStore.getOrProvision({
226− context: localWorkspaceContext,
227− }).pipe(
228− Effect.mapError(toLocalRuntimeBootstrapError),
229− Effect.catchAll((error) =>
230− closeScope(scope).pipe(Effect.zipRight(Effect.fail(error))),
231− ),
232− );
235+ const localInstallation = yield* installationStore
236+ .getOrProvision({
237+ context: localWorkspaceContext,
238+ })
239+ .pipe(
240+ Effect.mapError(toLocalRuntimeBootstrapError),
241+ Effect.catchAll((error) =>
242+ closeScope(scope).pipe(Effect.zipRight(Effect.fail(error))),
243+ ),
244+ );
233245
234246 const persistence = createLocalControlPlanePersistence(
235247 localWorkspaceContext,
@@ -237,14 +249,14 @@ export const createControlPlaneRuntime = ( · 1 unmodified line
237249 );
238250 const rows = persistence.rows;
239251
240− const loadedLocalConfig = yield* workspaceConfigStore.load(
241− localWorkspaceContext,
242− ).pipe(
243− Effect.mapError(toLocalRuntimeBootstrapError),
244− Effect.catchAll((error) =>
245− closeScope(scope).pipe(Effect.zipRight(Effect.fail(error))),
246− ),
247− );
252+ const loadedLocalConfig = yield* workspaceConfigStore
253+ .load(localWorkspaceContext)
254+ .pipe(
255+ Effect.mapError(toLocalRuntimeBootstrapError),
256+ Effect.catchAll((error) =>
257+ closeScope(scope).pipe(Effect.zipRight(Effect.fail(error))),
258+ ),
259+ );
248260
249261 const effectiveLocalConfig = yield* synchronizeLocalWorkspaceState({
250262 context: localWorkspaceContext,
@@ -291,8 +303,13 @@ export const createControlPlaneRuntime = ( · 40 unmodified lines
291303 managedRuntime,
292304 runtimeLayer: concreteRuntimeLayer as RuntimeControlPlaneLayer,
293305 close: async () => {
294− await Effect.runPromise(clearAllMcpConnectionPools()).catch(() => undefined);
306+ await Effect.runPromise(clearAllMcpConnectionPools()).catch(
307+ () => undefined,
308+ );
295309 await managedRuntime.dispose().catch(() => undefined);
296310 },
297311 };
298− }).pipe(Effect.provide(NodeFileSystem.layer));
312+ }).pipe(Effect.provide(NodeFileSystem.layer)) as Effect.Effect<
313+ ControlPlaneRuntime,
314+ Error
315+ >);
packages/platform/sdk/src/runtime/local/capture-release-workspace-fixture.tsrenamed from packages/platform/control-plane/src/runtime/local/capture-release-workspace-fixture.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/local/config-secrets.tsrenamed from packages/platform/control-plane/src/runtime/local/config-secrets.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/local/config.test.tsrenamed from packages/platform/control-plane/src/runtime/local/config.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/local/config.tsrenamed from packages/platform/control-plane/src/runtime/local/config.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/local/control-plane-store.test.tsrenamed from packages/platform/control-plane/src/runtime/local/control-plane-store.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/local/control-plane-store.tsrenamed from packages/platform/control-plane/src/runtime/local/control-plane-store.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/local/errors.tsrenamed from packages/platform/control-plane/src/runtime/local/errors.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/local/installation.test.tsrenamed from packages/platform/control-plane/src/runtime/local/installation.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/local/installation.tsrenamed from packages/platform/control-plane/src/runtime/local/installation.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/local/operations.ts+1−0
@@ -0,0 +1 @@
1+export * from "../../local/operations";
packages/platform/sdk/src/runtime/local/release-upgrade-fixtures.tsrenamed from packages/platform/control-plane/src/runtime/local/release-upgrade-fixtures.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/local/runtime-context.tsrenamed from packages/platform/control-plane/src/runtime/local/runtime-context.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/local/secret-material-providers.tsrenamed from packages/platform/control-plane/src/runtime/local/secret-material-providers.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/local/secrets-operations.ts+1−0
@@ -0,0 +1 @@
1+export * from "../../local/secrets";
packages/platform/sdk/src/runtime/local/source-artifacts.test.tsrenamed from packages/platform/control-plane/src/runtime/local/source-artifacts.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/local/source-artifacts.tsrenamed from packages/platform/control-plane/src/runtime/local/source-artifacts.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/local/storage.tsrenamed from packages/platform/control-plane/src/runtime/local/storage.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/local/tools.tsrenamed from packages/platform/control-plane/src/runtime/local/tools.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/local/workspace-state.tsrenamed from packages/platform/control-plane/src/runtime/local/workspace-state.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/local/workspace-sync.tsrenamed from packages/platform/control-plane/src/runtime/local/workspace-sync.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/policy/invocation-policy-engine.test.tsrenamed from packages/platform/control-plane/src/runtime/policy/invocation-policy-engine.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/policy/invocation-policy-engine.tsrenamed from packages/platform/control-plane/src/runtime/policy/invocation-policy-engine.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/policy/operation-errors.tsrenamed from packages/platform/control-plane/src/runtime/policy/operation-errors.ts+1−1
@@ -2,7 +2,7 @@ import { · 1 unmodified line
22 ControlPlaneBadRequestError,
33 ControlPlaneNotFoundError,
44 ControlPlaneStorageError,
5−} from "../../api/errors";
5+} from "../../errors";
66 import * as Effect from "effect/Effect";
77
88 export type OperationErrors<TOperation extends string = string> = {
packages/platform/sdk/src/runtime/policy/operations-shared.tsrenamed from packages/platform/control-plane/src/runtime/policy/operations-shared.ts+1−1
@@ -1,7 +1,7 @@
11 import {
22 ControlPlaneBadRequestError,
33 ControlPlaneStorageError,
4−} from "../../api/errors";
4+} from "../../errors";
55 import * as Effect from "effect/Effect";
66
77 import {
packages/platform/sdk/src/runtime/policy/policies-operations.ts+1−0
@@ -0,0 +1 @@
1+export * from "../../policies/operations";
packages/platform/sdk/src/runtime/sources/catalog-sync-result.tsrenamed from packages/platform/control-plane/src/runtime/sources/catalog-sync-result.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/executor-tools.tsrenamed from packages/platform/control-plane/src/runtime/sources/executor-tools.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/graphql-tools.test.tsrenamed from packages/platform/control-plane/src/runtime/sources/graphql-tools.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/graphql-tools.tsrenamed from packages/platform/control-plane/src/runtime/sources/graphql-tools.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/slug.tsrenamed from packages/platform/control-plane/src/runtime/sources/slug.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-adapter-fixture-matrix.test.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-adapter-fixture-matrix.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-adapters/google-discovery.test.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-adapters/google-discovery.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-adapters/google-discovery.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-adapters/google-discovery.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-adapters/graphql.test.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-adapters/graphql.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-adapters/graphql.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-adapters/graphql.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-adapters/index.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-adapters/index.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-adapters/internal.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-adapters/internal.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-adapters/mcp.test.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-adapters/mcp.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-adapters/mcp.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-adapters/mcp.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-adapters/openapi.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-adapters/openapi.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-adapters/shared.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-adapters/shared.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-adapters/types.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-adapters/types.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-auth-service.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-auth-service.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-credential-interactions.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-credential-interactions.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-definitions.test.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-definitions.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-definitions.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-definitions.ts+2−2
@@ -3,7 +3,7 @@ import { sha256Hex } from "@executor/codemode-core"; · 2 unmodified lines
33 import type {
44 CreateSourcePayload,
55 UpdateSourcePayload,
6−} from "#api";
6+} from "../../sources/contracts";
77 import type {
88 AccountId,
99 AuthArtifact,
@@ -26,7 +26,7 @@ import { · 16 unmodified lines
2626 SourceCatalogRevisionIdSchema,
2727 } from "#schema";
2828 import * as Effect from "effect/Effect";
29−
29+
3030 import { getSourceAdapter, getSourceAdapterForSource } from "./source-adapters";
3131 import {
3232 authArtifactFromSourceAuth,
packages/platform/sdk/src/runtime/sources/source-discovery.test.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-discovery.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-discovery.ts+1−0
@@ -0,0 +1 @@
1+export * from "../../sources/discovery";
packages/platform/sdk/src/runtime/sources/source-inspection.test.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-inspection.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-inspection.ts+1−0
@@ -0,0 +1 @@
1+export * from "../../sources/inspection";
packages/platform/sdk/src/runtime/sources/source-names.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-names.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-store.test.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-store.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-store.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-store.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-store/auth.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-store/auth.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-store/config.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-store/config.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-store/deps.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-store/deps.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-store/lifecycle.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-store/lifecycle.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/source-store/records.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-store/records.ts+0−0

File renamed without changes.

packages/platform/sdk/src/runtime/sources/sources-operations.ts+1−0
@@ -0,0 +1 @@
1+export * from "../../sources/operations";
packages/platform/sdk/src/runtime/store.tsrenamed from packages/platform/control-plane/src/runtime/store.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/common.tsrenamed from packages/platform/control-plane/src/schema/common.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/enums.tsrenamed from packages/platform/control-plane/src/schema/enums.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/ids.tsrenamed from packages/platform/control-plane/src/schema/ids.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/index.tsrenamed from packages/platform/control-plane/src/schema/index.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/auth-artifact.tsrenamed from packages/platform/control-plane/src/schema/models/auth-artifact.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/auth-lease.tsrenamed from packages/platform/control-plane/src/schema/models/auth-lease.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/auth/account.tsrenamed from packages/platform/control-plane/src/schema/models/auth/account.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/auth/index.tsrenamed from packages/platform/control-plane/src/schema/models/auth/index.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/auth/principal.tsrenamed from packages/platform/control-plane/src/schema/models/auth/principal.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/code-migration.tsrenamed from packages/platform/control-plane/src/schema/models/code-migration.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/credential.tsrenamed from packages/platform/control-plane/src/schema/models/credential.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/execution.tsrenamed from packages/platform/control-plane/src/schema/models/execution.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/local-config.tsrenamed from packages/platform/control-plane/src/schema/models/local-config.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/local-installation.tsrenamed from packages/platform/control-plane/src/schema/models/local-installation.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/policy.tsrenamed from packages/platform/control-plane/src/schema/models/policy.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/provider-auth-grant.tsrenamed from packages/platform/control-plane/src/schema/models/provider-auth-grant.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/secret-material.tsrenamed from packages/platform/control-plane/src/schema/models/secret-material.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/source-auth-session.tsrenamed from packages/platform/control-plane/src/schema/models/source-auth-session.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/source-catalog.tsrenamed from packages/platform/control-plane/src/schema/models/source-catalog.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/source-discovery.tsrenamed from packages/platform/control-plane/src/schema/models/source-discovery.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/source-inspection.tsrenamed from packages/platform/control-plane/src/schema/models/source-inspection.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/source-oauth-client.tsrenamed from packages/platform/control-plane/src/schema/models/source-oauth-client.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/source.tsrenamed from packages/platform/control-plane/src/schema/models/source.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/models/workspace-oauth-client.tsrenamed from packages/platform/control-plane/src/schema/models/workspace-oauth-client.ts+0−0

File renamed without changes.

packages/platform/sdk/src/schema/schema.test.tsrenamed from packages/platform/control-plane/src/schema/schema.test.ts+0−0

File renamed without changes.

packages/platform/sdk/src/sources/contracts.ts+170−0
@@ -0,0 +1,170 @@
1+import {
2+ ExecutionInteractionIdSchema,
3+ JsonObjectSchema,
4+ ProviderAuthGrantIdSchema,
5+ SourceAuthSchema,
6+ SourceAuthSessionIdSchema,
7+ SourceDiscoveryResultSchema,
8+ SourceIdSchema,
9+ SourceImportAuthPolicySchema,
10+ SourceKindSchema,
11+ SourceProbeAuthSchema,
12+ SourceSchema,
13+ SourceStatusSchema,
14+ SourceOauthClientInputSchema,
15+ WorkspaceIdSchema,
16+ WorkspaceOauthClientIdSchema,
17+ WorkspaceOauthClientSchema,
18+} from "../schema";
19+import {
20+ ConnectSourcePayloadSchema,
21+ type ConnectSourcePayload,
22+} from "../runtime/sources/source-adapters";
23+import * as Schema from "effect/Schema";
24+
25+import {
26+ OptionalTrimmedNonEmptyStringSchema,
27+ TrimmedNonEmptyStringSchema,
28+} from "../string-schemas";
29+
30+const createSourcePayloadRequiredSchema = Schema.Struct({
31+ name: TrimmedNonEmptyStringSchema,
32+ kind: SourceKindSchema,
33+ endpoint: TrimmedNonEmptyStringSchema,
34+});
35+
36+const createSourcePayloadOptionalSchema = Schema.Struct({
37+ status: Schema.optional(SourceStatusSchema),
38+ enabled: Schema.optional(Schema.Boolean),
39+ namespace: Schema.optional(Schema.NullOr(Schema.String)),
40+ binding: Schema.optional(JsonObjectSchema),
41+ importAuthPolicy: Schema.optional(SourceImportAuthPolicySchema),
42+ importAuth: Schema.optional(SourceAuthSchema),
43+ auth: Schema.optional(SourceAuthSchema),
44+ sourceHash: Schema.optional(Schema.NullOr(Schema.String)),
45+ lastError: Schema.optional(Schema.NullOr(Schema.String)),
46+});
47+
48+export const CreateSourcePayloadSchema = Schema.extend(
49+ createSourcePayloadRequiredSchema,
50+ createSourcePayloadOptionalSchema,
51+);
52+
53+export type CreateSourcePayload = typeof CreateSourcePayloadSchema.Type;
54+
55+export const UpdateSourcePayloadSchema = Schema.Struct({
56+ name: OptionalTrimmedNonEmptyStringSchema,
57+ endpoint: OptionalTrimmedNonEmptyStringSchema,
58+ status: Schema.optional(SourceStatusSchema),
59+ enabled: Schema.optional(Schema.Boolean),
60+ namespace: Schema.optional(Schema.NullOr(Schema.String)),
61+ binding: Schema.optional(JsonObjectSchema),
62+ importAuthPolicy: Schema.optional(SourceImportAuthPolicySchema),
63+ importAuth: Schema.optional(SourceAuthSchema),
64+ auth: Schema.optional(SourceAuthSchema),
65+ sourceHash: Schema.optional(Schema.NullOr(Schema.String)),
66+ lastError: Schema.optional(Schema.NullOr(Schema.String)),
67+});
68+
69+export type UpdateSourcePayload = typeof UpdateSourcePayloadSchema.Type;
70+
71+export const CredentialPageUrlParamsSchema = Schema.Struct({
72+ interactionId: ExecutionInteractionIdSchema,
73+});
74+
75+export const CredentialSubmitPayloadSchema = Schema.Struct({
76+ action: Schema.optional(Schema.Literal("submit", "continue", "cancel")),
77+ token: Schema.optional(Schema.String),
78+});
79+
80+export const CredentialOauthCompleteUrlParamsSchema = Schema.Struct({
81+ state: Schema.String,
82+ code: Schema.optional(Schema.String),
83+ error: Schema.optional(Schema.String),
84+ error_description: Schema.optional(Schema.String),
85+});
86+
87+export const WorkspaceOauthClientQuerySchema = Schema.Struct({
88+ providerKey: Schema.String,
89+});
90+
91+export const CreateWorkspaceOauthClientPayloadSchema = Schema.Struct({
92+ providerKey: Schema.String,
93+ label: Schema.optional(Schema.NullOr(Schema.String)),
94+ oauthClient: SourceOauthClientInputSchema,
95+});
96+
97+export type CreateWorkspaceOauthClientPayload =
98+ typeof CreateWorkspaceOauthClientPayloadSchema.Type;
99+
100+export const oauthClientIdParam = WorkspaceOauthClientIdSchema;
101+export const grantIdParam = ProviderAuthGrantIdSchema;
102+
103+const ConnectGoogleDiscoveryBatchSourceSchema = Schema.Struct({
104+ service: TrimmedNonEmptyStringSchema,
105+ version: TrimmedNonEmptyStringSchema,
106+ discoveryUrl: Schema.optional(Schema.NullOr(TrimmedNonEmptyStringSchema)),
107+ scopes: Schema.optional(Schema.Array(TrimmedNonEmptyStringSchema)),
108+ name: Schema.optional(Schema.NullOr(Schema.String)),
109+ namespace: Schema.optional(Schema.NullOr(Schema.String)),
110+});
111+
112+export const ConnectSourceBatchPayloadSchema = Schema.Struct({
113+ workspaceOauthClientId: WorkspaceOauthClientIdSchema,
114+ sources: Schema.Array(ConnectGoogleDiscoveryBatchSourceSchema),
115+});
116+
117+export type ConnectSourceBatchPayload = typeof ConnectSourceBatchPayloadSchema.Type;
118+
119+export const ConnectSourceBatchResultSchema = Schema.Struct({
120+ results: Schema.Array(
121+ Schema.Struct({
122+ source: SourceSchema,
123+ status: Schema.Literal("connected", "pending_oauth"),
124+ }),
125+ ),
126+ providerOauthSession: Schema.NullOr(
127+ Schema.Struct({
128+ sessionId: SourceAuthSessionIdSchema,
129+ authorizationUrl: Schema.String,
130+ sourceIds: Schema.Array(SourceIdSchema),
131+ }),
132+ ),
133+});
134+
135+export type ConnectSourceBatchResult = typeof ConnectSourceBatchResultSchema.Type;
136+
137+export const DiscoverSourcePayloadSchema = Schema.Struct({
138+ url: TrimmedNonEmptyStringSchema,
139+ probeAuth: Schema.optional(SourceProbeAuthSchema),
140+});
141+
142+export type DiscoverSourcePayload = typeof DiscoverSourcePayloadSchema.Type;
143+
144+export const ConnectSourceResultSchema = Schema.Union(
145+ Schema.Struct({
146+ kind: Schema.Literal("connected"),
147+ source: SourceSchema,
148+ }),
149+ Schema.Struct({
150+ kind: Schema.Literal("credential_required"),
151+ source: SourceSchema,
152+ credentialSlot: Schema.Literal("runtime", "import"),
153+ }),
154+ Schema.Struct({
155+ kind: Schema.Literal("oauth_required"),
156+ source: SourceSchema,
157+ sessionId: SourceAuthSessionIdSchema,
158+ authorizationUrl: Schema.String,
159+ }),
160+);
161+
162+export type ConnectSourceResult = typeof ConnectSourceResultSchema.Type;
163+
164+export {
165+ ConnectSourcePayloadSchema,
166+ type ConnectSourcePayload,
167+ SourceDiscoveryResultSchema,
168+ WorkspaceIdSchema,
169+ WorkspaceOauthClientSchema,
170+};
packages/platform/sdk/src/sources/discovery.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-discovery.ts+3−3
@@ -9,7 +9,7 @@ import { · 8 unmodified lines
99 type SourceDiscoveryResult,
1010 } from "@executor/source-core";
1111
12−import { builtInSourceAdapters } from "./source-adapters";
12+import { builtInSourceAdapters } from "../runtime/sources/source-adapters";
1313
1414 type DiscoverableSourceAdapter = SourceAdapter & {
1515 detectSource: NonNullable<SourceAdapter["detectSource"]>;
@@ -36,8 +36,8 @@ export const discoverSource = (input: { · 20 unmodified lines
3636
3737 const adaptersByPriority = [...discoverableSourceAdapters].sort(
3838 (left, right) =>
39− (right.discoveryPriority?.({ normalizedUrl }) ?? 0)
40− - (left.discoveryPriority?.({ normalizedUrl }) ?? 0),
39+ (right.discoveryPriority?.({ normalizedUrl }) ?? 0) -
40+ (left.discoveryPriority?.({ normalizedUrl }) ?? 0),
4141 );
4242
4343 for (const adapter of adaptersByPriority) {
packages/platform/sdk/src/sources/inspection.tsrenamed from packages/platform/control-plane/src/runtime/sources/source-inspection.ts+88−41
@@ -9,23 +9,23 @@ import type { · 8 unmodified lines
99 SourceInspectionToolListItem,
1010 SourceInspectionToolSummary,
1111 WorkspaceId,
12−} from "#schema";
12+} from "../schema";
1313 import {
1414 ControlPlaneNotFoundError,
1515 ControlPlaneStorageError,
16−} from "../../api/errors";
16+} from "../errors";
1717 import * as Effect from "effect/Effect";
1818
19−import { LocalSourceArtifactMissingError } from "../local/errors";
20−import { operationErrors } from "../policy/operation-errors";
19+import { LocalSourceArtifactMissingError } from "../runtime/local/errors";
20+import { operationErrors } from "../runtime/policy/operation-errors";
2121 import {
2222 buildLoadedSourceCatalogToolContract,
2323 expandCatalogToolByPath,
2424 expandCatalogTools,
2525 loadSourceWithCatalog,
2626 type LoadedSourceCatalogTool,
27−} from "../catalog/source/runtime";
28−import { RuntimeSourceStoreService } from "./source-store";
27+} from "../runtime/catalog/source/runtime";
28+import { RuntimeSourceStoreService } from "../runtime/sources/source-store";
2929
3030 const sourceInspectOps = {
3131 bundle: operationErrors("sources.inspect.bundle"),
@@ -41,9 +41,9 @@ const tokenize = (value: string): Array<string> => · 9 unmodified lines
4141 .filter(Boolean);
4242
4343 const canInspectSourceWithoutCatalog = (source: Source): boolean =>
44− source.status === "draft"
45− || source.status === "probing"
46− || source.status === "auth_required";
44+ source.status === "draft" ||
45+ source.status === "probing" ||
46+ source.status === "auth_required";
4747
4848 const loadSourceForMissingCatalog = (input: {
4949 workspaceId: WorkspaceId;
@@ -88,7 +88,8 @@ const loadSourceCatalogOrEmpty = (input: { · 38 unmodified lines
8888 kind: "empty" as const,
8989 source,
9090 };
91− })),
91+ }),
92+ ),
9293 );
9394
9495 const executableDetails = (tool: LoadedSourceCatalogTool) => {
@@ -105,7 +106,9 @@ const executableDetails = (tool: LoadedSourceCatalogTool) => { · 10 unmodified lines
105106 };
106107 };
107108
108−const inspectionToolListItemFromTool = (tool: LoadedSourceCatalogTool): SourceInspectionToolListItem => ({
109+const inspectionToolListItemFromTool = (
110+ tool: LoadedSourceCatalogTool,
111+): SourceInspectionToolListItem => ({
109112 path: tool.path,
110113 method: executableDetails(tool).method,
111114 ...(tool.descriptor.contract?.inputTypePreview
@@ -116,15 +119,23 @@ const inspectionToolListItemFromTool = (tool: LoadedSourceCatalogTool): SourceIn · 4 unmodified lines
116119 : {}),
117120 });
118121
119−const persistedToolSummaryFromTool = (tool: LoadedSourceCatalogTool): SourceInspectionToolSummary => {
122+const persistedToolSummaryFromTool = (
123+ tool: LoadedSourceCatalogTool,
124+): SourceInspectionToolSummary => {
120125 const details = executableDetails(tool);
121126
122127 return {
123128 path: tool.path,
124129 sourceKey: tool.source.id,
125− ...(tool.capability.surface.title ? { title: tool.capability.surface.title } : {}),
130+ ...(tool.capability.surface.title
131+ ? { title: tool.capability.surface.title }
132+ : {}),
126133 ...(tool.capability.surface.summary || tool.capability.surface.description
127− ? { description: tool.capability.surface.summary ?? tool.capability.surface.description! }
134+ ? {
135+ description:
136+ tool.capability.surface.summary ??
137+ tool.capability.surface.description!,
138+ }
128139 : {}),
129140 protocol: details.protocol,
130141 toolId: tool.path.split(".").at(-1) ?? tool.path,
@@ -145,7 +156,10 @@ const persistedToolSummaryFromTool = (tool: LoadedSourceCatalogTool): SourceInsp · 14 unmodified lines
145156 };
146157
147158 const nativeEncodingLanguage = (encoding: string | undefined): string =>
148− encoding === "graphql" || encoding === "yaml" || encoding === "json" || encoding === "text"
159+ encoding === "graphql" ||
160+ encoding === "yaml" ||
161+ encoding === "json" ||
162+ encoding === "text"
149163 ? encoding
150164 : "json";
151165
@@ -171,12 +185,24 @@ export const inspectionToolDetailFromTool = ( · 19 unmodified lines
171185 const contract = yield* buildLoadedSourceCatalogToolContract(tool);
172186 const overviewItems = [
173187 { label: "Protocol", value: details.protocol },
174− ...(details.method ? [{ label: "Method", value: details.method, mono: true }] : []),
175− ...(details.pathTemplate ? [{ label: "Target", value: details.pathTemplate, mono: true }] : []),
176− ...(details.operationId ? [{ label: "Operation", value: details.operationId, mono: true }] : []),
177− ...(details.group ? [{ label: "Group", value: details.group, mono: true }] : []),
178− ...(details.leaf ? [{ label: "Leaf", value: details.leaf, mono: true }] : []),
179− ...(details.rawToolId ? [{ label: "Raw tool", value: details.rawToolId, mono: true }] : []),
188+ ...(details.method
189+ ? [{ label: "Method", value: details.method, mono: true }]
190+ : []),
191+ ...(details.pathTemplate
192+ ? [{ label: "Target", value: details.pathTemplate, mono: true }]
193+ : []),
194+ ...(details.operationId
195+ ? [{ label: "Operation", value: details.operationId, mono: true }]
196+ : []),
197+ ...(details.group
198+ ? [{ label: "Group", value: details.group, mono: true }]
199+ : []),
200+ ...(details.leaf
201+ ? [{ label: "Leaf", value: details.leaf, mono: true }]
202+ : []),
203+ ...(details.rawToolId
204+ ? [{ label: "Raw tool", value: details.rawToolId, mono: true }]
205+ : []),
180206 { label: "Signature", value: contract.callSignature, mono: true },
181207 { label: "Call shape", value: contract.callShapeId, mono: true },
182208 ...(contract.resultShapeId
@@ -211,11 +237,13 @@ export const inspectionToolDetailFromTool = ( · 28 unmodified lines
211237 items: overviewItems,
212238 },
213239 ...(summary.description
214− ? [{
215− kind: "markdown" as const,
216− title: "Description",
217− body: summary.description,
218− }]
240+ ? [
241+ {
242+ kind: "markdown" as const,
243+ title: "Description",
244+ body: summary.description,
245+ },
246+ ]
219247 : []),
220248 ...([
221249 jsonSection("Capability", tool.capability),
@@ -231,7 +259,9 @@ export const inspectionToolDetailFromTool = ( · 9 unmodified lines
231259 summary: tool.capability.surface.summary,
232260 description: tool.capability.surface.description,
233261 }),
234− ].filter((section) => section !== null) as Array<SourceInspectionToolDetail["sections"][number]>),
262+ ].filter((section) => section !== null) as Array<
263+ SourceInspectionToolDetail["sections"][number]
264+ >),
235265 ...nativeSections,
236266 ];
237267
@@ -323,7 +353,9 @@ const scoreTool = (input: { · 85 unmodified lines
323353 const titleTokens = tokenize(summary.title ?? "");
324354 const descriptionTokens = tokenize(summary.description ?? "");
325355 const tagTokens = summary.tags.flatMap(tokenize);
326− const methodPathTokens = tokenize(`${summary.method ?? ""} ${summary.pathTemplate ?? ""}`);
356+ const methodPathTokens = tokenize(
357+ `${summary.method ?? ""} ${summary.pathTemplate ?? ""}`,
358+ );
327359
328360 for (const token of input.queryTokens) {
329361 if (pathTokens.includes(token)) {
@@ -346,7 +378,10 @@ const scoreTool = (input: { · 16 unmodified lines
346378 reasons.push(`method/path matches ${token} (+6)`);
347379 continue;
348380 }
349− if (descriptionTokens.includes(token) || input.tool.searchText.includes(token)) {
381+ if (
382+ descriptionTokens.includes(token) ||
383+ input.tool.searchText.includes(token)
384+ ) {
350385 score += 2;
351386 reasons.push(`description/text matches ${token} (+2)`);
352387 }
@@ -360,8 +395,12 @@ const scoreTool = (input: { · 7 unmodified lines
360395 path: input.tool.path,
361396 score,
362397 ...(summary.description ? { description: summary.description } : {}),
363− ...(summary.inputTypePreview ? { inputTypePreview: summary.inputTypePreview } : {}),
364− ...(summary.outputTypePreview ? { outputTypePreview: summary.outputTypePreview } : {}),
398+ ...(summary.inputTypePreview
399+ ? { inputTypePreview: summary.inputTypePreview }
400+ : {}),
401+ ...(summary.outputTypePreview
402+ ? { outputTypePreview: summary.outputTypePreview }
403+ : {}),
365404 reasons,
366405 };
367406 };
@@ -402,7 +441,9 @@ export const getSourceInspection = (input: { · 34 unmodified lines
402441 namespace: inspection.namespace,
403442 pipelineKind: inspection.pipelineKind,
404443 toolCount: inspection.tools.length,
405− tools: inspection.tools.map((tool) => inspectionToolListItemFromTool(tool)),
444+ tools: inspection.tools.map((tool) =>
445+ inspectionToolListItemFromTool(tool),
446+ ),
406447 } satisfies SourceInspection;
407448 }).pipe(
408449 Effect.mapError((cause) =>
@@ -410,7 +451,8 @@ export const getSourceInspection = (input: { · 1 unmodified line
410451 sourceInspectOps.bundle,
411452 cause,
412453 "Failed building source inspection bundle",
413− )),
454+ ),
455+ ),
414456 );
415457
416458 export const getSourceInspectionToolDetail = (input: {
@@ -428,9 +470,9 @@ export const getSourceInspectionToolDetail = (input: { · 11 unmodified lines
428470
429471 if (!tool) {
430472 return yield* sourceInspectOps.tool.notFound(
431− "Tool not found",
432− `workspaceId=${input.workspaceId} sourceId=${input.sourceId} path=${input.toolPath}`,
433− );
473+ "Tool not found",
474+ `workspaceId=${input.workspaceId} sourceId=${input.sourceId} path=${input.toolPath}`,
475+ );
434476 }
435477
436478 return yield* inspectionToolDetailFromTool(tool);
@@ -440,7 +482,8 @@ export const getSourceInspectionToolDetail = (input: { · 3 unmodified lines
440482 sourceInspectOps.tool,
441483 cause,
442484 "Failed building source inspection tool detail",
443− )),
485+ ),
486+ ),
444487 );
445488
446489 export const discoverSourceInspectionTools = (input: {
@@ -463,9 +506,12 @@ export const discoverSourceInspectionTools = (input: { · 16 unmodified lines
463506 tool,
464507 }),
465508 )
466− .filter((value): value is SourceInspectionDiscoverResultItem => value !== null)
467− .sort((left, right) =>
468− right.score - left.score || left.path.localeCompare(right.path),
509+ .filter(
510+ (value): value is SourceInspectionDiscoverResultItem => value !== null,
511+ )
512+ .sort(
513+ (left, right) =>
514+ right.score - left.score || left.path.localeCompare(right.path),
469515 )
470516 .slice(0, input.payload.limit ?? 12);
471517
@@ -482,5 +528,6 @@ export const discoverSourceInspectionTools = (input: { · 10 unmodified lines
482528 sourceInspectOps.discover,
483529 cause,
484530 "Failed building source inspection discovery",
485− )),
531+ ),
532+ ),
486533 );
packages/platform/sdk/src/sources/operations.tsrenamed from packages/platform/control-plane/src/runtime/sources/sources-operations.ts+85−72
@@ -1,33 +1,30 @@
11 import type {
22 CreateSourcePayload,
33 UpdateSourcePayload,
4−} from "../../api/sources/api";
4+} from "./contracts";
55 import {
66 type AccountId,
77 SourceIdSchema,
88 type Source,
99 type SourceId,
1010 type WorkspaceId,
11−} from "#schema";
11+} from "../schema";
1212 import * as Either from "effect/Either";
1313 import * as Effect from "effect/Effect";
1414
1515 import {
1616 createSourceFromPayload,
1717 updateSourceFromPayload,
18−} from "./source-definitions";
19−import { getSourceAdapterForSource } from "./source-adapters";
18+} from "../runtime/sources/source-definitions";
19+import { getSourceAdapterForSource } from "../runtime/sources/source-adapters";
20+import { mapPersistenceError } from "../runtime/policy/operations-shared";
21+import { operationErrors } from "../runtime/policy/operation-errors";
2022 import {
21− mapPersistenceError,
22−} from "../policy/operations-shared";
23−import {
24− operationErrors,
25−} from "../policy/operation-errors";
26−import { ControlPlaneStore, type ControlPlaneStoreShape } from "../store";
27−import { RuntimeSourceCatalogSyncService } from "../catalog/source/sync";
28−import {
29− RuntimeSourceStoreService,
30−} from "./source-store";
23+ ControlPlaneStore,
24+ type ControlPlaneStoreShape,
25+} from "../runtime/store";
26+import { RuntimeSourceCatalogSyncService } from "../runtime/catalog/source/sync";
27+import { RuntimeSourceStoreService } from "../runtime/sources/source-store";
3128
3229 const sourceOps = {
3330 list: operationErrors("sources.list"),
@@ -45,9 +42,7 @@ const syncArtifactsForSource = (input: { · 11 unmodified lines
4542 sourceStore: Effect.Effect.Success<typeof RuntimeSourceStoreService>;
4643 source: Source;
4744 actorAccountId: AccountId;
48− operation:
49− | typeof sourceOps.create
50− | typeof sourceOps.update;
45+ operation: typeof sourceOps.create | typeof sourceOps.update;
5146 }) =>
5247 Effect.gen(function* () {
5348 const catalogSyncService = yield* RuntimeSourceCatalogSyncService;
@@ -96,7 +91,10 @@ const syncArtifactsForSource = (input: { · 42 unmodified lines
9691 }),
9792 onLeft: (error) =>
9893 Effect.gen(function* () {
99− if (autoProbe || (input.source.enabled && input.source.status === "connected")) {
94+ if (
95+ autoProbe ||
96+ (input.source.enabled && input.source.status === "connected")
97+ ) {
10098 const erroredSource = yield* updateSourceFromPayload({
10199 source: input.source,
102100 payload: {
@@ -104,11 +102,11 @@ const syncArtifactsForSource = (input: { · 1 unmodified line
104102 lastError: error.message,
105103 },
106104 now: Date.now(),
107− }).pipe(
108− Effect.mapError((cause) =>
109− input.operation.badRequest(
110− "Failed indexing source tools",
111− cause instanceof Error ? cause.message : String(cause),
105+ }).pipe(
106+ Effect.mapError((cause) =>
107+ input.operation.badRequest(
108+ "Failed indexing source tools",
109+ cause instanceof Error ? cause.message : String(cause),
112110 ),
113111 ),
114112 );
@@ -121,7 +119,10 @@ const syncArtifactsForSource = (input: { · 6 unmodified lines
121119 );
122120 }
123121
124− return yield* input.operation.unknownStorage(error, "Failed syncing source tools");
122+ return yield* input.operation.unknownStorage(
123+ error,
124+ "Failed syncing source tools",
125+ );
125126 }),
126127 });
127128 });
@@ -134,17 +135,20 @@ export const listSources = (input: { · 6 unmodified lines
134135 Effect.gen(function* () {
135136 const sourceStore = yield* RuntimeSourceStoreService;
136137
137− return yield* sourceStore.loadSourcesInWorkspace(input.workspaceId, {
138− actorAccountId: input.accountId,
139− }).pipe(
140− Effect.mapError((error) =>
141− sourceOps.list.unknownStorage(
142− error,
143− "Failed projecting stored sources",
138+ return yield* sourceStore
139+ .loadSourcesInWorkspace(input.workspaceId, {
140+ actorAccountId: input.accountId,
141+ })
142+ .pipe(
143+ Effect.mapError((error) =>
144+ sourceOps.list.unknownStorage(
145+ error,
146+ "Failed projecting stored sources",
147+ ),
144148 ),
145− ),
146− );
147− }));
149+ );
150+ }),
151+ );
148152
149153 export const createSource = (input: {
150154 workspaceId: WorkspaceId;
@@ -186,7 +190,8 @@ export const createSource = (input: { · 35 unmodified lines
186190 });
187191
188192 return synchronizedSource;
189− }));
193+ }),
194+ );
190195
191196 export const getSource = (input: {
192197 workspaceId: WorkspaceId;
@@ -197,24 +202,28 @@ export const getSource = (input: { · 4 unmodified lines
197202 Effect.gen(function* () {
198203 const sourceStore = yield* RuntimeSourceStoreService;
199204
200− return yield* sourceStore.loadSourceById({
201− workspaceId: input.workspaceId,
202− sourceId: input.sourceId,
203− actorAccountId: input.accountId,
204− }).pipe(
205− Effect.mapError((cause) =>
206− cause instanceof Error && cause.message.startsWith("Source not found:")
207− ? sourceOps.get.notFound(
208− "Source not found",
209− `workspaceId=${input.workspaceId} sourceId=${input.sourceId}`,
210− )
211− : sourceOps.get.unknownStorage(
212− cause,
213− "Failed projecting stored source",
214− ),
215− ),
216− );
217− }));
205+ return yield* sourceStore
206+ .loadSourceById({
207+ workspaceId: input.workspaceId,
208+ sourceId: input.sourceId,
209+ actorAccountId: input.accountId,
210+ })
211+ .pipe(
212+ Effect.mapError((cause) =>
213+ cause instanceof Error &&
214+ cause.message.startsWith("Source not found:")
215+ ? sourceOps.get.notFound(
216+ "Source not found",
217+ `workspaceId=${input.workspaceId} sourceId=${input.sourceId}`,
218+ )
219+ : sourceOps.get.unknownStorage(
220+ cause,
221+ "Failed projecting stored source",
222+ ),
223+ ),
224+ );
225+ }),
226+ );
218227
219228 export const updateSource = (input: {
220229 workspaceId: WorkspaceId;
@@ -225,23 +234,26 @@ export const updateSource = (input: { · 4 unmodified lines
225234 Effect.flatMap(ControlPlaneStore, (store) =>
226235 Effect.gen(function* () {
227236 const sourceStore = yield* RuntimeSourceStoreService;
228− const existingSource = yield* sourceStore.loadSourceById({
229− workspaceId: input.workspaceId,
230− sourceId: input.sourceId,
231− actorAccountId: input.accountId,
232− }).pipe(
233− Effect.mapError((cause) =>
234− cause instanceof Error && cause.message.startsWith("Source not found:")
235− ? sourceOps.update.notFound(
236− "Source not found",
237− `workspaceId=${input.workspaceId} sourceId=${input.sourceId}`,
238− )
239− : sourceOps.update.unknownStorage(
240− cause,
241− "Failed projecting stored source",
242− ),
243− ),
244− );
237+ const existingSource = yield* sourceStore
238+ .loadSourceById({
239+ workspaceId: input.workspaceId,
240+ sourceId: input.sourceId,
241+ actorAccountId: input.accountId,
242+ })
243+ .pipe(
244+ Effect.mapError((cause) =>
245+ cause instanceof Error &&
246+ cause.message.startsWith("Source not found:")
247+ ? sourceOps.update.notFound(
248+ "Source not found",
249+ `workspaceId=${input.workspaceId} sourceId=${input.sourceId}`,
250+ )
251+ : sourceOps.update.unknownStorage(
252+ cause,
253+ "Failed projecting stored source",
254+ ),
255+ ),
256+ );
245257
246258 const updatedSource = yield* updateSourceFromPayload({
247259 source: existingSource,
@@ -272,7 +284,8 @@ export const updateSource = (input: { · 24 unmodified lines
272284 });
273285
274286 return synchronizedSource;
275− }));
287+ }),
288+ );
276289
277290 export const removeSource = (input: {
278291 workspaceId: WorkspaceId;
@@ -290,5 +303,5 @@ export const removeSource = (input: { · 11 unmodified lines
290303 );
291304
292305 return { removed };
293− })
306+ }),
294307 );
packages/platform/sdk/src/string-schemas.tsrenamed from packages/platform/control-plane/src/api/string-schemas.ts+0−0

File renamed without changes.

packages/platform/sdk/tsconfig.json+14−0
@@ -0,0 +1,14 @@
1+{
2+ "compilerOptions": {
3+ "target": "ES2022",
4+ "module": "ESNext",
5+ "moduleResolution": "Bundler",
6+ "strict": true,
7+ "skipLibCheck": true,
8+ "noEmit": true,
9+ "lib": ["ES2022"],
10+ "types": ["bun-types", "node"]
11+ },
12+ "include": ["src/**/*.ts"],
13+ "exclude": ["src/**/*.test.ts", "src/runtime/execution/test-http-client.ts"]
14+}
packages/platform/sdk/vitest.config.ts+8−0
@@ -0,0 +1,8 @@
1+import { defineConfig } from "vitest/config";
2+
3+export default defineConfig({
4+ test: {
5+ include: ["src/**/*.test.ts"],
6+ testTimeout: 30_000,
7+ },
8+});
packages/platform/server/package.json+3−1
@@ -12,8 +12,10 @@ · 11 unmodified lines
1212 "@effect/opentelemetry": "^0.61.0",
1313 "@effect/platform": "catalog:",
1414 "@effect/platform-node": "catalog:",
15− "@executor/control-plane": "workspace:*",
1615 "@executor/executor-mcp": "workspace:*",
16+ "@executor/platform-api": "workspace:*",
17+ "@executor/platform-internal": "workspace:*",
18+ "@executor/platform-sdk": "workspace:*",
1719 "@opentelemetry/api": "^1.9.0",
1820 "@opentelemetry/exporter-trace-otlp-grpc": "^0.213.0",
1921 "@opentelemetry/exporter-trace-otlp-http": "^0.213.0",
packages/platform/server/src/index.ts+6−2
@@ -10,12 +10,15 @@ import { FileSystem, HttpApiBuilder, HttpServer } from "@effect/platform"; · 9 unmodified lines
1010 import { NodeFileSystem } from "@effect/platform-node";
1111 import {
1212 createControlPlaneApiLayer,
13+} from "@executor/platform-api";
14+import { createExecutorMcpRequestHandler } from "@executor/executor-mcp";
15+import { createExecutorAdminToolMap } from "@executor/platform-internal";
16+import {
1317 createControlPlaneRuntime,
1418 type ResolveExecutionEnvironment,
1519 type ResolveSecretMaterial,
1620 type ControlPlaneRuntime,
17−} from "@executor/control-plane";
18−import { createExecutorMcpRequestHandler } from "@executor/executor-mcp";
21+} from "@executor/platform-sdk/runtime";
1922 import * as Effect from "effect/Effect";
2023 import * as Layer from "effect/Layer";
2124 import type * as Scope from "effect/Scope";
@@ -110,6 +113,7 @@ const createRuntime = ( · 88 unmodified lines
110113 createControlPlaneRuntime({
111114 workspaceRoot: options.workspaceRoot,
112115 executionResolver: options.executionResolver,
116+ createInternalToolMap: createExecutorAdminToolMap,
113117 resolveSecretMaterial: options.resolveSecretMaterial,
114118 getLocalServerBaseUrl,
115119 localDataDir,
packages/sources/openapi/src/real-spec-coverage.test.ts+1−1
@@ -20,7 +20,7 @@ const readFixture = (name: string) => · 19 unmodified lines
2020 Effect.flatMap((fs) =>
2121 fs.readFileString(
2222 fileURLToPath(
23− new URL(`../../../platform/control-plane/src/runtime/fixtures/${name}`, import.meta.url),
23+ new URL(`../../../platform/sdk/src/runtime/fixtures/${name}`, import.meta.url),
2424 ),
2525 "utf8",
2626 )
packages/sources/openapi/src/tool-presentation.test.ts+1−1
@@ -15,7 +15,7 @@ const readFixture = (name: string) => · 14 unmodified lines
1515 Effect.flatMap((fs) =>
1616 fs.readFileString(
1717 fileURLToPath(
18− new URL(`../../../platform/control-plane/src/runtime/fixtures/${name}`, import.meta.url),
18+ new URL(`../../../platform/sdk/src/runtime/fixtures/${name}`, import.meta.url),
1919 ),
2020 "utf8",
2121 )