History

fmt

Rhys Sullivan committed 6 months ago

Commit c92c57d1 parent 87ab5c7265 files · +5705 −8140Browse files

Unified · Split

265 files
  1. .agents/skills/wrdn-effect-atom-optimistic/SKILL.md +10 −8
  2. .agents/skills/wrdn-effect-typed-errors/SKILL.md +14 −13
  3. .skills/cli-release/SKILL.md +9 −0
  4. .skills/effect-atom-optimistic-updates/SKILL.md +4 −10
  5. .skills/graphite/SKILL.md +36 −6
  6. RELEASING.md +6 −2
  7. apps/cli/release-notes/next.md +15 −0
  8. apps/cli/src/build.ts +15 −16
  9. apps/cli/src/daemon-state.ts +22 −14
  10. apps/cli/src/daemon.ts +7 −2
  11. apps/cli/src/main.ts +3 −6
  12. apps/cli/src/tooling.ts +6 −7
  13. apps/cloud/drizzle/meta/0000_snapshot.json +20 −73
  14. apps/cloud/drizzle/meta/0001_snapshot.json +20 −73
  15. apps/cloud/drizzle/meta/0002_snapshot.json +20 −73
  16. apps/cloud/drizzle/meta/0003_snapshot.json +21 −77
  17. apps/cloud/drizzle/meta/0007_snapshot.json +21 −77
  18. apps/cloud/drizzle/meta/0008_snapshot.json +27 −104
  19. apps/cloud/executor.config.ts +1 −4
  20. apps/cloud/src/api.request-scope.node.test.ts +7 −17
  21. apps/cloud/src/api.test.ts +2 −7
  22. apps/cloud/src/api/autumn.ts +2 −10
  23. apps/cloud/src/api/error-response.ts +1 −4
  24. apps/cloud/src/api/layers.ts +4 −14
  25. apps/cloud/src/api/protected-layers.ts +1 −5
  26. apps/cloud/src/api/protected.ts +3 −12
  27. apps/cloud/src/api/router.ts +2 −7
  28. apps/cloud/src/api/slack.ts +1 −5
  29. apps/cloud/src/auth/context.ts +3 −4
  30. apps/cloud/src/auth/handlers.ts +8 −14
  31. apps/cloud/src/auth/middleware.ts +11 −10
  32. apps/cloud/src/auth/resolve-organization.ts +1 −3
  33. apps/cloud/src/auth/workos.ts +4 −6
  34. apps/cloud/src/jwks-cache.node.test.ts +1 −3
  35. apps/cloud/src/mcp-auth.node.test.ts +6 −4
  36. apps/cloud/src/mcp-flow.test.ts +7 −12
  37. apps/cloud/src/mcp-session.e2e.node.test.ts +7 −10
  38. apps/cloud/src/routes/billing_.plans.tsx +74 −63
  39. apps/cloud/src/routes/sources.add.$pluginKey.tsx +1 −8
  40. apps/cloud/src/secrets-isolation.e2e.node.test.ts +153 −165
  41. apps/cloud/src/services/__test-harness__/api-harness.ts +9 −35
  42. apps/cloud/src/services/db.schema.test.ts +1 −4
  43. apps/cloud/src/services/db.ts +5 −5
  44. apps/cloud/src/services/execution-stack.ts +3 −5
  45. apps/cloud/src/services/executor-schema.ts +430 −327
  46. apps/cloud/src/services/executor.ts +2 −10
  47. apps/cloud/src/services/mcp-oauth.node.test.ts +11 −34
  48. apps/cloud/src/services/mcp-worker-transport.test.ts +1 −3
  49. apps/cloud/src/services/mcp-worker-transport.ts +2 −6
  50. apps/cloud/src/services/slack.ts +8 −18
  51. apps/cloud/src/services/sources-api.node.test.ts +7 −13
  52. apps/cloud/src/services/telemetry.ts +1 −4
  53. apps/cloud/src/test-worker.ts +5 −6
  54. apps/cloud/src/web/pages/onboarding.tsx +4 −8
  55. apps/cloud/vite.config.ts +1 −4
  56. apps/cloud/wrangler.miniflare.jsonc +4 −4
  57. apps/cloud/wrangler.test.jsonc +4 −4
  58. apps/desktop/src/main.ts +10 −7
  59. apps/local/drizzle/meta/0000_snapshot.json +42 −140
  60. apps/local/drizzle/meta/0001_snapshot.json +42 −140
  61. apps/local/drizzle/meta/0002_snapshot.json +46 −153
  62. apps/local/drizzle/meta/0003_snapshot.json +49 −164
  63. apps/local/drizzle/meta/0004_snapshot.json +51 −172
  64. apps/local/drizzle/meta/0005_snapshot.json +51 −172
  65. apps/local/drizzle/meta/0006_snapshot.json +50 −165
  66. apps/local/drizzle/meta/0007_snapshot.json +95 −311
  67. apps/local/src/serve.test.ts +4 −1
  68. apps/local/src/serve.ts +8 −6
  69. apps/local/src/server/config-sync.ts +46 −50
  70. apps/local/src/server/db-upgrade.test.ts +16 −12
  71. apps/local/src/server/db-upgrade.ts +3 −3
  72. apps/local/src/server/executor-schema.ts +479 −370
  73. apps/local/src/server/executor.ts +9 −26
  74. apps/local/src/server/main.ts +5 −3
  75. apps/local/src/server/mcp-oauth.test.ts +19 −60
  76. apps/local/src/server/migrate-connections.test.ts +3 −9
  77. apps/local/src/server/migrate-connections.ts +26 −62
  78. apps/local/src/server/migrate-graphql-bindings.test.ts +12 −23
  79. apps/local/src/server/migrate-openapi-bindings.test.ts +1 −3
  80. apps/local/src/server/observability.ts +1 −1
  81. apps/marketing/src/styles/global.css +3 −2
  82. autumn.config.ts +64 −64
  83. examples/all-plugins/src/main.ts +3 −10
  84. notes/artifacts-workflows-and-generated-ui.md +45 −71
  85. notes/dynamic-plugin-loading-v1.md +177 −187
  86. notes/mcp-conn-pool.md +9 −7
  87. notes/old/connections-migration.md +1 −1
  88. notes/old/error-handling.md +4 −4
  89. notes/old/errored-sources.md +2 −1
  90. notes/old/indexes.md +1 −1
  91. notes/old/mcp-testing.md +6 −6
  92. notes/old/pluggable-storage.md +45 −37
  93. notes/old/promise-sdk-typed-errors.md +3 −1
  94. notes/old/rls.md +3 −3
  95. notes/old/scoped-source-auth.md +2 −7
  96. notes/old/scopes.md +6 −6
  97. notes/old/search.md +6 −2
  98. notes/old/storage-migration.md +23 −15
  99. notes/research/plugins/manifest-schema.md +92 −94
  100. packages/core/api/src/connections/api.ts +6 −15
  101. packages/core/api/src/handlers/connections.ts +27 −30
  102. packages/core/api/src/handlers/executions.ts +46 −42
  103. packages/core/api/src/handlers/oauth.ts +2 −1
  104. packages/core/api/src/handlers/scope.ts +20 −18
  105. packages/core/api/src/handlers/secrets.ts +42 −32
  106. packages/core/api/src/handlers/sources.ts +69 −59
  107. packages/core/api/src/handlers/tools.ts +32 −28
  108. packages/core/api/src/oauth-popup.test.ts +6 −4
  109. packages/core/api/src/oauth-popup.ts +1 −4
  110. packages/core/api/src/oauth/api.ts +1 −3
  111. packages/core/api/src/observability.test.ts +15 −18
  112. packages/core/api/src/observability.ts +22 −33
  113. packages/core/api/src/plugin-routes.ts +18 −16
  114. packages/core/api/src/secrets/api.ts +2 −6
  115. packages/core/api/src/sources/api.ts +2 −8
  116. packages/core/api/src/tools/api.ts +1 −3
  117. packages/core/cli/package.json +1 −1
  118. packages/core/cli/src/commands/generate.ts +4 −18
  119. packages/core/cli/src/generators/drizzle.ts +62 −104
  120. packages/core/cli/src/generators/index.ts +1 −4
  121. packages/core/cli/tsup.config.ts +1 −6
  122. packages/core/config/src/index.ts +1 −5
  123. packages/core/config/src/load-plugins.test.ts +2 −6
  124. packages/core/config/src/load-plugins.ts +3 −8
  125. packages/core/config/src/sink.ts +2 −6
  126. packages/core/execution/src/description.test.ts +32 −42
  127. packages/core/execution/src/description.ts +5 −7
  128. packages/core/execution/src/engine.test.ts +2 −4
  129. packages/core/execution/src/promise.ts +4 −12
  130. packages/core/execution/src/tool-invoker.test.ts +9 −18
  131. packages/core/execution/src/tool-invoker.ts +6 −4
  132. packages/core/sdk/README.md +21 −29
  133. packages/core/sdk/package.json +12 −12
  134. packages/core/sdk/src/blob.test.ts +2 −6
  135. packages/core/sdk/src/blob.ts +4 −16
  136. packages/core/sdk/src/client.ts +8 −20
  137. packages/core/sdk/src/connections.test.ts +358 −422
  138. packages/core/sdk/src/connections.ts +1 −3
  139. packages/core/sdk/src/core-schema.ts +6 −17
  140. packages/core/sdk/src/error-handling.test.ts +4 −11
  141. packages/core/sdk/src/errors.ts +4 −7
  142. packages/core/sdk/src/executor.test.ts +190 −276
  143. packages/core/sdk/src/index.ts +1 −5
  144. packages/core/sdk/src/oauth-discovery.test.ts +34 −66
  145. packages/core/sdk/src/oauth-helpers.test.ts +10 −11
  146. packages/core/sdk/src/oauth-helpers.ts +19 −46
  147. packages/core/sdk/src/oauth-popup-types.ts +1 −3
  148. packages/core/sdk/src/oauth.ts +12 −29
  149. packages/core/sdk/src/plugin.ts +25 −105
  150. packages/core/sdk/src/policies.test.ts +74 −83
  151. packages/core/sdk/src/policies.ts +2 −8
  152. packages/core/sdk/src/promise-executor.ts +22 −33
  153. packages/core/sdk/src/promise.test.ts +2 −4
  154. packages/core/sdk/src/promise.ts +1 −5
  155. packages/core/sdk/src/schema-types.ts +0 −1
  156. packages/core/sdk/src/scoped-adapter.test.ts +2 −6
  157. packages/core/sdk/src/scoped-adapter.ts +7 −34
  158. packages/core/sdk/src/secrets.ts +5 −20
  159. packages/core/sdk/src/testing.ts +8 −11
  160. packages/core/sdk/src/types.ts +1 −3
  161. packages/core/storage-core/LICENSE.md +21 −20
  162. packages/core/storage-core/README.md +1 −5
  163. packages/core/storage-core/package.json +6 −6
  164. packages/core/storage-core/src/adapter.ts +2 −7
  165. packages/core/storage-core/src/errors.ts +1 −3
  166. packages/core/storage-core/src/memory.test.ts +1 −4
  167. packages/core/storage-core/src/schema.ts +10 −28
  168. packages/core/storage-core/src/typed.ts +4 −14
  169. packages/core/storage-drizzle/src/adapter.test.ts +4 −11
  170. packages/core/storage-file/src/adapter.ts +2 −6
  171. packages/core/storage-file/src/blob-store.ts +5 −27
  172. packages/core/storage-file/src/index.test.ts +3 −16
  173. packages/core/storage-postgres/src/adapter.ts +2 −6
  174. packages/core/storage-postgres/src/blob-store.ts +5 −27
  175. packages/core/storage-postgres/src/index.test.ts +3 −8
  176. packages/core/storage-postgres/src/index.ts +1 −4
  177. packages/core/vite-plugin/package.json +3 −3
  178. packages/core/vite-plugin/src/index.ts +8 −23
  179. packages/hosts/mcp/src/stdio-integration.test.ts +35 −33
  180. packages/kernel/core/README.md +1 −5
  181. packages/kernel/core/src/code-recovery.test.ts +6 −13
  182. packages/kernel/core/src/code-recovery.ts +14 −10
  183. packages/kernel/core/src/strip-types.test.ts +4 −2
  184. packages/kernel/runtime-deno-subprocess/src/index.test.ts +19 −17
  185. packages/kernel/runtime-deno-subprocess/src/index.ts +1 −3
  186. packages/kernel/runtime-quickjs/README.md +5 −5
  187. packages/plugins/example/package.json +8 −8
  188. packages/plugins/example/src/client.tsx +2 −8
  189. packages/plugins/example/src/server.ts +1 −7
  190. packages/plugins/google-discovery/src/api/handlers.test.ts +20 −26
  191. packages/plugins/google-discovery/src/api/handlers.ts +4 −5
  192. packages/plugins/google-discovery/src/react/client.ts +3 −4
  193. packages/plugins/google-discovery/src/react/index.ts +1 −4
  194. packages/plugins/google-discovery/src/sdk/document.ts +22 −22
  195. packages/plugins/graphql/src/api/group.ts +6 −13
  196. packages/plugins/graphql/src/api/handlers.ts +50 −62
  197. packages/plugins/graphql/src/react/GraphqlSignInButton.tsx +2 −1
  198. packages/plugins/graphql/src/react/defaults.ts +1 −2
  199. packages/plugins/graphql/src/sdk/introspect.ts +1 −3
  200. packages/plugins/graphql/src/sdk/invoke.ts +1 −2
  201. packages/plugins/graphql/src/sdk/types.ts +7 −9
  202. packages/plugins/keychain/src/index.test.ts +3 −1
  203. packages/plugins/keychain/src/index.ts +24 −37
  204. packages/plugins/mcp/src/api/group.ts +10 −11
  205. packages/plugins/mcp/src/api/handlers.test.ts +16 −18
  206. packages/plugins/mcp/src/api/handlers.ts +3 −2
  207. packages/plugins/mcp/src/react/index.ts +1 −6
  208. packages/plugins/mcp/src/react/source-plugin.tsx +1 −3
  209. packages/plugins/mcp/src/sdk/connection-pool.test.ts +26 −44
  210. packages/plugins/mcp/src/sdk/cross-user-isolation.test.ts +2 −6
  211. packages/plugins/mcp/src/sdk/elicitation.test.ts +1 −2
  212. packages/plugins/mcp/src/sdk/per-user-auth-isolation.test.ts +136 −146
  213. packages/plugins/mcp/src/sdk/probe-shape.test.ts +3 −4
  214. packages/plugins/mcp/src/sdk/probe-shape.ts +1 −2
  215. packages/plugins/mcp/src/sdk/stdio-connector.ts +1 −3
  216. packages/plugins/onepassword/src/api/handlers.ts +38 −26
  217. packages/plugins/openapi/src/api/group.ts +7 −12
  218. packages/plugins/openapi/src/api/handlers.ts +3 −1
  219. packages/plugins/openapi/src/react/AddOpenApiSource.tsx +113 −113
  220. packages/plugins/openapi/src/react/OpenApiSourceSummary.tsx +1 −3
  221. packages/plugins/openapi/src/sdk/client-credentials-oauth.test.ts +43 −37
  222. packages/plugins/openapi/src/sdk/credential-status.test.ts +1 −6
  223. packages/plugins/openapi/src/sdk/credential-status.ts +5 −21
  224. packages/plugins/openapi/src/sdk/extract.ts +2 −6
  225. packages/plugins/openapi/src/sdk/form-urlencoded-body.test.ts +52 −58
  226. packages/plugins/openapi/src/sdk/index.test.ts +4 −3
  227. packages/plugins/openapi/src/sdk/invoke.ts +7 −20
  228. packages/plugins/openapi/src/sdk/multi-scope-bearer.test.ts +406 −424
  229. packages/plugins/openapi/src/sdk/multi-scope-oauth.test.ts +71 −57
  230. packages/plugins/openapi/src/sdk/non-json-body.test.ts +29 −53
  231. packages/plugins/openapi/src/sdk/openapi-utils.ts +2 −7
  232. packages/plugins/openapi/src/sdk/plugin.test.ts +101 −119
  233. packages/plugins/openapi/src/sdk/preview-oauth2.test.ts +1 −3
  234. packages/plugins/openapi/src/sdk/preview.ts +4 −10
  235. packages/plugins/openapi/tsconfig.json +1 −4
  236. packages/plugins/workos-vault/src/react/WorkOSVaultSettings.tsx +2 −2
  237. packages/plugins/workos-vault/src/sdk/plugin.ts +26 −27
  238. packages/plugins/workos-vault/src/sdk/secret-store.test.ts +102 −131
  239. packages/plugins/workos-vault/src/sdk/secret-store.ts +26 −49
  240. packages/plugins/workos-vault/src/sdk/testing.ts +11 −31
  241. packages/react/src/api/reactivity-keys.tsx +2 −8
  242. packages/react/src/components/calendar.tsx +1 −4
  243. packages/react/src/components/chart.tsx +1 −4
  244. packages/react/src/components/code-block.tsx +1 −3
  245. packages/react/src/components/markdown.tsx +3 −1
  246. packages/react/src/components/schema-explorer.tsx +1 −5
  247. packages/react/src/components/tool-tree.tsx +5 −9
  248. packages/react/src/hooks/use-policy-actions.ts +7 −6
  249. packages/react/src/pages/secrets.tsx +3 −13
  250. packages/react/src/plugins/headers-list.tsx +1 −3
  251. packages/react/src/plugins/namespace.ts +1 −3
  252. packages/react/src/plugins/secret-id.test.ts +2 −8
  253. packages/react/src/plugins/secret-id.tsx +1 −4
  254. packages/react/src/plugins/source-identity.tsx +1 −5
  255. scripts/check-release-notes.ts +2 −8
  256. scripts/oxlint-plugin-executor/rules/no-redundant-primitive-cast.js +2 −9
  257. scripts/oxlint-plugin-executor/rules/no-unsupported-effect-api.js +1 −2
  258. scripts/oxlint-plugin-executor/utils.js +3 −3
  259. scripts/publish-packages.ts +2 −6
  260. tests/daemon-bootstrap.test.ts +30 −23
  261. tests/daemon-state.test.ts +2 −1
  262. tests/presets-reachable.test.ts +2 −1
  263. tests/release-bootstrap-smoke.test.ts +118 −144
  264. tests/source-identity.test.ts +1 −4
  265. tests/tools-cli.test.ts +10 −6
.agents/skills/wrdn-effect-atom-optimistic/SKILL.md+10−8
@@ -46,10 +46,10 @@ When the trace cannot resolve with the files at hand, drop the finding. · 45 unmodified lines
4646
4747 ## Severity ladder
4848
49−| Level | Criteria |
50−|-------|----------|
49+| Level | Criteria |
50+| ---------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
5151 | **medium** | New optimistic-mutation code that bypasses `Atom.optimistic` / `Atom.optimisticFn` and rolls its own pending state. Or a mixed read/write where the read goes through the plain query atom and the write goes through the optimistic mutation. |
52−| **low** | Subtle defects in an `Atom.optimisticFn` reducer that work today but degrade under racing (clock-based identity, missing `Atom.family` wrapper, computed-once captures of `scopeId`). |
52+| **low** | Subtle defects in an `Atom.optimisticFn` reducer that work today but degrade under racing (clock-based identity, missing `Atom.family` wrapper, computed-once captures of `scopeId`). |
5353
5454 Do not invent `high`. Pick `low` when in doubt and explain why.
5555
@@ -103,9 +103,7 @@ export const updateSecretOptimistic = Atom.family((scopeId: ScopeId) => · 47 unmodified lines
103103 Atom.optimisticFn({
104104 reducer: (current, arg: { path: { secretId: SecretId }; payload: { value: string } }) =>
105105 Result.map(current, (rows) =>
106− rows.map((r) =>
107− r.id === arg.path.secretId ? { ...r, value: arg.payload.value } : r,
108− ),
106+ rows.map((r) => (r.id === arg.path.secretId ? { ...r, value: arg.payload.value } : r)),
109107 ),
110108 fn: updateSecret,
111109 }),
@@ -136,8 +134,12 @@ export interface PendingSecret { · 24 unmodified lines
136134 readonly value: string;
137135 }
138136
139−export const useSecretsWithPending = (scopeId: ScopeId) => { /* ... */ };
140−export const usePendingSecrets = () => { /* ... */ };
137+export const useSecretsWithPending = (scopeId: ScopeId) => {
138+ /* ... */
139+};
140+export const usePendingSecrets = () => {
141+ /* ... */
142+};
141143 ```
142144
143145 Flag any new entry in `PendingResource` and any new `use<X>WithPending` / `usePending<X>` hook. The file is closed for new patterns.
.agents/skills/wrdn-effect-typed-errors/SKILL.md+14−13
@@ -96,7 +96,7 @@ throw new Error("Missing source"); · 95 unmodified lines
9696 Good in `Effect.gen`:
9797
9898 ```ts
99−return yield* new SourceNotFoundError({ sourceId });
99+return yield * new SourceNotFoundError({ sourceId });
100100 ```
101101
102102 Good in combinators:
@@ -112,13 +112,13 @@ If a third-party interface requires throwing, keep the throw at the adapter edge · 9 unmodified lines
112112 Prefer yielding the error directly in generator code:
113113
114114 ```ts
115−return yield* new SourceNotFoundError({ sourceId });
115+return yield * new SourceNotFoundError({ sourceId });
116116 ```
117117
118118 Do not write:
119119
120120 ```ts
121−return yield* Effect.fail(new SourceNotFoundError({ sourceId }));
121+return yield * Effect.fail(new SourceNotFoundError({ sourceId }));
122122 ```
123123
124124 Use `Effect.fail(...)` in non-generator combinator code:
@@ -167,16 +167,14 @@ Effect.ignore( · 42 unmodified lines
167167 Bad in domain code:
168168
169169 ```ts
170−program.pipe(Effect.orDie)
171−Effect.die(error)
170+program.pipe(Effect.orDie);
171+Effect.die(error);
172172 ```
173173
174174 Good:
175175
176176 ```ts
177−program.pipe(
178− Effect.mapError((cause) => new DomainError({ message: "Operation failed", cause })),
179−)
177+program.pipe(Effect.mapError((cause) => new DomainError({ message: "Operation failed", cause })));
180178 ```
181179
182180 `Effect.die`, `Effect.dieMessage`, `Effect.orDie`, and `Effect.orDieWith` turn typed failures into defects. Use them only at a true runtime boundary where the host cannot represent typed failures, and keep that usage behind a narrow lint suppression with a `boundary:` reason. Do not use `orDie` to avoid threading an error type through normal Effect code.
@@ -305,16 +303,19 @@ Bad: · 122 unmodified lines
305303 const connectionError = (message: string) =>
306304 new McpConnectionError({ transport: "remote", message });
307305
308−return yield* connectionError("Endpoint URL is required");
306+return yield * connectionError("Endpoint URL is required");
309307 ```
310308
311309 Good:
312310
313311 ```ts
314−return yield* new McpConnectionError({
315− transport: "remote",
316− message: "Endpoint URL is required",
317−});
312+return (
313+ yield *
314+ new McpConnectionError({
315+ transport: "remote",
316+ message: "Endpoint URL is required",
317+ })
318+);
318319 ```
319320
320321 Helpers are allowed only when they do real work, such as:
.skills/cli-release/SKILL.md+9−0
@@ -6,16 +6,19 @@ description: Runbook for releasing the `executor` CLI package (stable and beta). · 5 unmodified lines
66 # Executor CLI release runbook
77
88 ## Authoritative doc
9+
910 `RELEASING.md` at repo root is the source of truth. This skill encodes the owner's preferences on top of it.
1011
1112 ## What the `executor` CLI actually ships
1213
1314 The CLI binary bundles:
15+
1416 - `apps/cli/**` — CLI source + daemon
1517 - `apps/local/**` — the web UI (embedded as a virtual module via `apps/cli/src/build.ts:178`) + drizzle migrations (`build.ts:205`)
1618 - `packages/**` — `core`, `kernel`, `hosts/mcp`, `runtime-quickjs`, and every plugin under `packages/plugins/**`
1719
1820 Does **not** ship in the CLI:
21+
1922 - `apps/cloud/**` (Cloudflare Workers deployment)
2023 - `apps/marketing/**`, `apps/desktop/**`
2124 - `examples/**`, `tests/**`
@@ -37,6 +40,7 @@ The owner doesn't want GitHub's auto-generated "PR title by @user" list. Release · 15 unmodified lines
3740 **`apps/cli/release-notes/next.md` is the canonical user-facing changelog.** Per-package workspace `CHANGELOG.md` files are one-line stubs required by `changesets/action@v1` (the GitHub Action wrapping the CLI in `release.yml`) — it reads each bumped package's `CHANGELOG.md` to build the Version Packages PR description and crashes with `ENOENT` if any is missing. The stubs satisfy that read; don't put release content in them.
3841
3942 ### How it's wired
43+
4044 `apps/cli/src/release.ts` reads `apps/cli/release-notes/next.md` and uses
4145 its contents as the GitHub Release body. If the file is missing or empty,
4246 falls back to `gh release create --generate-notes`. There's no
@@ -44,7 +48,9 @@ per-version archive in the repo — historical release bodies live on · 1 unmodified line
4448 GitHub Releases (durable, indexed, linkable).
4549
4650 ### Writing conventions
51+
4752 Structure release-notes files as:
53+
4854 ```
4955 ## Highlights
5056 ### <user-facing story> # e.g. "Per-user OAuth for OpenAPI and MCP sources"
@@ -74,17 +80,20 @@ External contributor bullets end with `Thanks @<user> (#PR)`: · 23 unmodified lines
7480 Do not `Thanks` maintainers, bots, or the repo owner — the lint script rejects `@claude`, `@anthropic`, `@github-actions`, `@dependabot`, `@renovate`, `@rhyssullivan`, `@rhys-sullivan`. Run `bun run lint:release-notes` before pushing notes.
7581
7682 ### When drafting from `git log`
83+
7784 - Look at `git diff v<last>..HEAD -- README.md` first — it's the best single view of user-facing changes.
7885 - Read commit messages in bulk (`git log --oneline v<last>..HEAD -- apps/cli apps/local packages`), then bucket by theme before writing prose.
7986 - Don't list every commit. Merge PRs and refactor-chain commits into one line.
8087
8188 ### Pairing with changesets
89+
8290 - A `.changeset/*.md` describes the version bump (semver level + a short summary for the Version Packages PR description). It is **not** the user-facing changelog.
8391 - If your PR adds a `.changeset/*.md` for the `executor` package, also edit `apps/cli/release-notes/next.md` for the user-facing story. They have different audiences.
8492 - The `.changeset/*.md` body can be a one-liner pointing at the release-notes section it expands; users read the GitHub release body, not the changeset.
8593 - Frontmatter is `"executor": patch` (or `minor`/`major` if owner says so).
8694
8795 ### Starting a new release cycle
96+
8897 There's no post-release rename step. When you start work on the next
8998 release, replace the existing `next.md` content with new entries — the
9099 previous cycle's content is already preserved on the matching `vX.Y.Z`
.skills/effect-atom-optimistic-updates/SKILL.md+4−10
@@ -16,7 +16,7 @@ sources/connections. · 15 unmodified lines
1616
1717 - A list query atom (`Atom.optimistic` wraps the query)
1818 - Plus one or more mutation atoms that change rows in that list (create / update / delete)
19−- And you want the UI to reflect the change *immediately* on click, before the server roundtrip
19+- And you want the UI to reflect the change _immediately_ on click, before the server roundtrip
2020
2121 If you only need the UI to be "eventually consistent" (i.e. you're fine waiting
2222 ~200ms for the server response and the existing reactivity refetch), skip
@@ -109,10 +109,7 @@ existing `reactivityKeys` plumbing still applies — pass them in the call. · 86 unmodified lines
109109 ```typescript
110110 import { useAtomValue, useAtomSet } from "@effect-atom/atom-react";
111111
112−import {
113− policiesOptimisticAtom,
114− updatePolicyOptimistic,
115−} from "../api/atoms";
112+import { policiesOptimisticAtom, updatePolicyOptimistic } from "../api/atoms";
116113 import { policyWriteKeys } from "../api/reactivity-keys";
117114
118115 export function PoliciesPage() {
@@ -203,10 +200,7 @@ export const removePolicyOptimistic = Atom.family((scopeId: ScopeId) => · 84 unmodified lines
203200 path: { scopeId: ScopeId; policyId: PolicyId };
204201 reactivityKeys?: ReadonlyArray<unknown>;
205202 },
206− ) =>
207− Result.map(current, (rows) =>
208− rows.filter((r) => r.id !== arg.path.policyId),
209− ),
203+ ) => Result.map(current, (rows) => rows.filter((r) => r.id !== arg.path.policyId)),
210204 fn: removePolicy,
211205 }),
212206 ),
@@ -244,7 +238,7 @@ read, optimistic create/update/remove, no custom state. · 31 unmodified lines
244238
245239 ## When you must NOT use this
246240
247−- The mutation has cross-cutting effects on data that *isn't* in the same list
241+- The mutation has cross-cutting effects on data that _isn't_ in the same list
248242 (e.g. a single mutation invalidates `tools` AND `policies`). Reactivity keys
249243 still handle that — optimistic only paints the list-local change.
250244 - You need to show transient UI state that isn't a row property (toasts,
.skills/graphite/SKILL.md+36−6
@@ -9,12 +9,12 @@ description: "Manage Git branches and PRs using Graphite CLI (gt) instead of raw · 8 unmodified lines
99
1010 Use `gt` commands instead of their `git` counterparts for commit and branch management. Raw git bypasses Graphite's stack metadata, leaving descendants un-restacked and the CLI's view of the stack out of sync.
1111
12−| Instead of | Use |
13−|------------|-----|
14−| `git checkout -b <name>` | `gt create <name>` |
15−| `git commit -m "msg"` | `gt modify -c -m "msg"` |
16−| `git commit --amend` | `gt modify --amend` |
17−| `git commit -am "msg"` | `gt modify -cam "msg"` |
12+| Instead of | Use |
13+| ------------------------ | ----------------------- |
14+| `git checkout -b <name>` | `gt create <name>` |
15+| `git commit -m "msg"` | `gt modify -c -m "msg"` |
16+| `git commit --amend` | `gt modify --amend` |
17+| `git commit -am "msg"` | `gt modify -cam "msg"` |
1818
1919 Why: `gt` keeps stack metadata consistent and auto-restacks descendant branches when a parent changes.
2020
@@ -54,6 +54,7 @@ To bail out: `gt abort`. · 33 unmodified lines
5454 Create a new branch stacked on current branch with staged changes.
5555
5656 **Flags:**
57+
5758 - `-a, --all` - Stage all unstaged changes including untracked files
5859 - `-m, --message` - Commit message
5960 - `-i, --insert` - Insert between current branch and its child
@@ -67,6 +68,7 @@ Create a new branch stacked on current branch with staged changes. · 7 unmodified lines
6768 Amend current branch's commit or create new commit. Auto-restacks descendants.
6869
6970 **Flags:**
71+
7072 - `-a, --all` - Stage all changes
7173 - `-c, --commit` - Create new commit instead of amending
7274 - `-e, --edit` - Open editor for commit message
@@ -82,6 +84,7 @@ Amend current branch's commit or create new commit. Auto-restacks descendants. · 9 unmodified lines
8284 Amend staged changes to relevant commits in current stack.
8385
8486 **Flags:**
87+
8588 - `-a, --all` - Stage all unstaged changes (not untracked)
8689 - `-d, --dry-run` - Print what would happen
8790 - `-f, --force` - Skip confirmation
@@ -92,6 +95,7 @@ Amend staged changes to relevant commits in current stack. · 4 unmodified lines
9295 Squash all commits in current branch into single commit.
9396
9497 **Flags:**
98+
9599 - `-m, --message` - Commit message
96100 - `--edit` - Modify existing message
97101 - `-n, --no-edit` - Keep existing message
@@ -101,6 +105,7 @@ Squash all commits in current branch into single commit. · 3 unmodified lines
101105 Fold branch's changes into parent, restack descendants.
102106
103107 **Flags:**
108+
104109 - `-k, --keep` - Keep current branch name instead of parent's
105110
106111 ### gt split
@@ -108,6 +113,7 @@ Fold branch's changes into parent, restack descendants. · 1 unmodified line
108113 Split current branch into multiple branches.
109114
110115 **Flags:**
116+
111117 - `-c, --commit, --by-commit` - Split by commit history
112118 - `-h, --hunk, --by-hunk` - Split by hunk interactively
113119 - `-f, --file, --by-file` - Split files matching pathspecs
@@ -119,6 +125,7 @@ Split current branch into multiple branches. · 5 unmodified lines
119125 Push branches and create/update PRs on GitHub.
120126
121127 **Flags:**
128+
122129 - `-s, --stack` - Include descendant branches
123130 - `-d, --draft` - Create PRs as drafts
124131 - `-p, --publish` - Publish draft PRs
@@ -148,6 +155,7 @@ Push branches and create/update PRs on GitHub. · 23 unmodified lines
148155 Merge PRs from trunk to current branch via Graphite.
149156
150157 **Flags:**
158+
151159 - `-c, --confirm` - Confirm before merging
152160 - `--dry-run` - Report what would merge
153161
@@ -158,6 +166,7 @@ Merge PRs from trunk to current branch via Graphite. · 4 unmodified lines
158166 Switch to branch. Interactive selector if no branch provided.
159167
160168 **Flags:**
169+
161170 - `-a, --all` - Show all trunks in selection
162171 - `-u, --show-untracked` - Include untracked branches
163172 - `-s, --stack` - Only show current stack
@@ -168,6 +177,7 @@ Switch to branch. Interactive selector if no branch provided. · 4 unmodified lines
168177 Switch to child of current branch.
169178
170179 **Flags:**
180+
171181 - `-n, --steps` - Levels to traverse
172182 - `--to` - Target branch to navigate toward
173183
@@ -176,6 +186,7 @@ Switch to child of current branch. · 2 unmodified lines
176186 Switch to parent of current branch.
177187
178188 **Flags:**
189+
179190 - `-n, --steps` - Levels to traverse
180191
181192 ### gt top
@@ -193,6 +204,7 @@ Switch to branch closest to trunk in current stack. · 11 unmodified lines
193204 Ensure each branch has parent in Git history, rebasing if needed.
194205
195206 **Flags:**
207+
196208 - `--branch` - Run from specified branch
197209 - `--downstack` - Only restack branch and ancestors
198210 - `--upstack` - Only restack branch and descendants
@@ -203,6 +215,7 @@ Ensure each branch has parent in Git history, rebasing if needed. · 4 unmodified lines
203215 Rebase current branch onto target, restack descendants.
204216
205217 **Flags:**
218+
206219 - `-o, --onto` - Target branch
207220 - `--source` - Branch to move (default: current)
208221 - `-a, --all` - Show all trunks in selection
@@ -218,6 +231,7 @@ Reorder branches between trunk and current via editor. · 9 unmodified lines
218231 Sync all branches with remote, clean merged/closed PRs, restack.
219232
220233 **Flags:**
234+
221235 - `-a, --all` - Sync across all trunks
222236 - `-f, --force` - Skip confirmations
223237 - `--restack` / `--no-restack` - Restack after sync (default: true)
@@ -227,6 +241,7 @@ Sync all branches with remote, clean merged/closed PRs, restack. · 3 unmodified lines
227241 Sync branches from trunk to specified branch/PR from remote.
228242
229243 **Flags:**
244+
230245 - `-d, --downstack` - Don't sync upstack branches
231246 - `-f, --force` - Overwrite with remote
232247 - `--restack` / `--no-restack` - Restack after (default: true)
@@ -239,6 +254,7 @@ Sync branches from trunk to specified branch/PR from remote. · 6 unmodified lines
239254 Delete branch and Graphite metadata. Children restack to parent.
240255
241256 **Flags:**
257+
242258 - `-f, --force` - Delete even if not merged/closed
243259 - `-c, --close` - Close associated PR on GitHub
244260 - `--downstack` - Also delete ancestors
@@ -249,6 +265,7 @@ Delete branch and Graphite metadata. Children restack to parent. · 4 unmodified lines
249265 Rename branch. Removes PR association.
250266
251267 **Flags:**
268+
252269 - `-f, --force` - Allow rename with open PR
253270
254271 ### gt track [branch]
@@ -256,6 +273,7 @@ Rename branch. Removes PR association. · 1 unmodified line
256273 Start tracking branch with Graphite by selecting parent.
257274
258275 **Flags:**
276+
259277 - `-f, --force` - Use most recent tracked ancestor as parent
260278 - `-p, --parent` - Specify parent branch
261279
@@ -264,6 +282,7 @@ Start tracking branch with Graphite by selecting parent. · 2 unmodified lines
264282 Stop tracking branch. Children also untracked.
265283
266284 **Flags:**
285+
267286 - `-f, --force` - Skip confirmation
268287
269288 ### gt pop
@@ -285,6 +304,7 @@ Unfreeze branch and upstack. · 15 unmodified lines
285304 Show stacks. Forms: `gt log`, `gt log short`, `gt log long`.
286305
287306 **Flags:**
307+
288308 - `-a, --all` - Show all trunks
289309 - `-r, --reverse` - Print upside down
290310 - `-u, --show-untracked` - Include untracked
@@ -297,6 +317,7 @@ Show stacks. Forms: `gt log`, `gt log short`, `gt log long`. · 6 unmodified lines
297317 Display branch information.
298318
299319 **Flags:**
320+
300321 - `-b, --body` - Show PR body
301322 - `-d, --diff` - Show diff vs parent
302323 - `-p, --patch` - Show commit changes
@@ -307,6 +328,7 @@ Display branch information. · 4 unmodified lines
307328 Show parent, children, or trunk of current branch.
308329
309330 `gt trunk` flags:
331+
310332 - `--add` - Add trunk
311333 - `-a, --all` - Show all trunks
312334
@@ -315,6 +337,7 @@ Show parent, children, or trunk of current branch. · 2 unmodified lines
315337 Open PR page for branch or PR number.
316338
317339 **Flags:**
340+
318341 - `--stack` - Open stack page
319342
320343 ## Conflict Resolution
@@ -324,6 +347,7 @@ Open PR page for branch or PR number. · 3 unmodified lines
324347 Continue command halted by rebase conflict.
325348
326349 **Flags:**
350+
327351 - `-a, --all` - Stage all changes first
328352
329353 ### gt abort
@@ -331,6 +355,7 @@ Continue command halted by rebase conflict. · 1 unmodified line
331355 Abort current command halted by conflict.
332356
333357 **Flags:**
358+
334359 - `-f, --force` - Skip confirmation
335360
336361 ### gt undo
@@ -338,6 +363,7 @@ Abort current command halted by conflict. · 1 unmodified line
338363 Undo most recent Graphite mutations.
339364
340365 **Flags:**
366+
341367 - `-f, --force` - Skip confirmation
342368
343369 ## Setup
@@ -347,6 +373,7 @@ Undo most recent Graphite mutations. · 3 unmodified lines
347373 Initialize Graphite by selecting trunk branch.
348374
349375 **Flags:**
376+
350377 - `--trunk` - Trunk branch name
351378 - `--reset` - Untrack all branches
352379
@@ -355,6 +382,7 @@ Initialize Graphite by selecting trunk branch. · 2 unmodified lines
355382 Add auth token for GitHub.
356383
357384 **Flags:**
385+
358386 - `-t, --token` - Auth token from https://app.graphite.com/activate
359387
360388 ### gt config
@@ -368,6 +396,7 @@ Update CLI to latest stable version. · 7 unmodified lines
368396 ## Git Passthroughs
369397
370398 These pass arguments directly to git:
399+
371400 - `gt add [args..]`
372401 - `gt cherry-pick [args..]`
373402 - `gt rebase [args..]`
@@ -379,4 +408,5 @@ These pass arguments directly to git: · 5 unmodified lines
379408 Create branch reverting a trunk commit.
380409
381410 **Flags:**
411+
382412 - `-e, --edit` - Edit commit message
RELEASING.md+6−2
@@ -74,14 +74,18 @@ Use this section structure (mirrors what's already in `next.md`): · 73 unmodified lines
7474
7575 ```markdown
7676 ## Highlights
77+
7778 ### <user-facing story>
78− bullets of concrete user value
79+
80+bullets of concrete user value
7981
8082 ## Fixes
8183
8284 ## Breaking changes
85+
8386 ### <specific surface>
84− before / after code blocks for migrations
87+
88+before / after code blocks for migrations
8589 ```
8690
8791 Lead with **user-visible stories**, not commit subjects. Group related
apps/cli/release-notes/next.md+15−0
@@ -1,17 +1,21 @@
11 ## Highlights
22
33 ### Install paths fixed
4+
45 `npm i -g executor` and `bun i -g executor` both work cleanly on a fresh machine. For machines without node, `curl … install.sh | bash` does the same thing.
56
67 > 1.4.13 and 1.4.14 were partial releases that didn't make it to npm cleanly. If you tried `npm i -g executor@latest` and got an older version, that's why. 1.4.15 is the first complete release of the new packaging.
78
89 ### MCP sources honor upstream `destructiveHint`
10+
911 MCP sources now read `destructiveHint` from upstream tool annotations. Tools marked destructive will require approval before running, surfaced via MCP elicitation. Refresh existing sources (or remove + re-add) to pick up annotations on tools added before this change.
1012
1113 ### Set tool policies from the Tools page
14+
1215 The local UI gains a **Policies** tab for managing approval rules, plus a per-row action menu on the Tools tree. Hover any tool or category and pick **Always run / Require approval / Block / Clear** — leaf rows save a rule for the exact tool id, group rows save a `prefix.*` wildcard. New rules are auto-placed by specificity so a freshly-added group rule never silently shadows an existing leaf rule. The same menu is available from the tool detail header and from any source-detail page.
1316
1417 ### Per-user OAuth for OpenAPI and MCP sources
18+
1519 OpenAPI and MCP sources now carry first-class **Connections** — a per-user sign-in state decoupled from the source definition itself.
1620
1721 - Save an OAuth2 OpenAPI or MCP source **before** signing in; users sign in later from the source page.
@@ -20,15 +24,18 @@ OpenAPI and MCP sources now carry first-class **Connections** — a per-user sig · 2 unmodified lines
2024 - Existing OpenAPI + MCP + google-discovery OAuth rows migrate into Connections automatically on first launch — no user action required.
2125
2226 ### OpenAPI: client-credentials, non-JSON bodies, source refresh
27+
2328 - Full **OAuth2 client-credentials** flow end-to-end.
2429 - **Non-JSON request bodies** dispatch correctly by content type; Executor honors OAS3 `encoding` and multi-content operations, and lets the caller pick which content type to send.
2530 - Relative OAuth2 URLs resolve against the source's `baseUrl`.
2631 - Refresh a source by re-fetching its origin URL from the edit page.
2732
2833 ### Layered scope isolation
34+
2935 Multi-tenant deployments get a proper security primitive. Every read and write now passes through a layered `ScopeStack`, with the write scope declared explicitly. Plugins have adopted the API; the UI exposes it via `CreatableSecretPicker`; and WorkOS sources enforce tenant-ownership on every access. Per-scope blob and secret lookups are batched into single `IN` queries, so the extra check doesn't cost a round-trip.
3036
3137 ### Natural CLI for tool discovery and invocation
38+
3239 Call tools by path instead of writing TypeScript:
3340
3441 ```bash
@@ -41,6 +48,7 @@ executor tools describe github.issues.create · 6 unmodified lines
4148 `executor call <path> --help` browses namespaces → resources → methods, with `--match <text> --limit <n>` to narrow huge namespaces. Errors are normalized for agent consumption, and the resume / help UX is cleaner for non-interactive flows.
4249
4350 ### Daemon lifecycle
51+
4452 ```bash
4553 executor daemon run
4654 executor daemon status
@@ -53,16 +61,20 @@ executor daemon restart · 6 unmodified lines
5361 `executor daemon run` now backgrounds by default. Pass `--foreground` to keep it attached for log inspection.
5462
5563 ### OpenTelemetry everywhere
64+
5665 Tool dispatch, plugins, storage, schema, and transport are now fully instrumented with OTEL spans, and the runtime is threaded through dispatch so spans actually export in all runtimes.
5766
5867 ## New presets
68+
5969 - **Notion** is now a featured MCP preset.
6070
6171 ## Performance
72+
6273 - `buildExecuteDescription` no longer calls `executor.tools.list`, making tool-description generation measurably faster on large workspaces.
6374 - Per-scope blob and secret lookups now use a single `IN` query instead of N per-scope round-trips.
6475
6576 ## Fixes
77+
6678 - Upgrade: preserve legacy OAuth connection backfills after the `connection.kind` column is removed.
6779 - OpenAPI: refreshing or editing sources with legacy inline secret/OAuth config now materializes the new source binding rows instead of dropping credentials.
6880 - Keychain: skip provider registration when the OS backend is unreachable (no more startup failure when running headless on Linux without a keyring).
@@ -72,6 +84,7 @@ Tool dispatch, plugins, storage, schema, and transport are now fully instrumente · 3 unmodified lines
7284 ## Breaking changes
7385
7486 ### `executor call` no longer accepts inline code
87+
7588 The old TypeScript-as-argument forms are gone:
7689
7790 ```bash
@@ -92,6 +105,7 @@ executor call github issues list '{"owner":"octocat","repo":"Hello-World"}' · 14 unmodified lines
92105 `tools.discover(...)` becomes `executor tools search "<query>"`.
93106
94107 ### `sources.add` CLI form simplified
108+
95109 Use the dedicated tool:
96110
97111 ```bash
@@ -105,4 +119,5 @@ executor call openapi addSource '{ · 7 unmodified lines
105119 Pass `baseUrl` when the OpenAPI document has relative `servers` entries.
106120
107121 ### SDK: layered scope
122+
108123 Every SDK write now takes an explicit scope. If you have plugins or host code calling the SDK directly, they'll need to adopt the new layered-scope API (see the in-tree plugins for reference — they've all been migrated). This does not affect users of the CLI or web UI.
apps/cli/src/build.ts+15−16
@@ -68,8 +68,7 @@ const platformName = (t: Target) => (t.os === "win32" ? "windows" : t.os); · 67 unmodified lines
6868 /** Per-platform suffix used in dist directory names, npm dist-tags, and the
6969 * semver prerelease segment of variant package versions. e.g. "linux-x64",
7070 * "linux-x64-musl", "darwin-arm64". */
71−const platformTag = (t: Target) =>
72− [platformName(t), t.arch, t.abi].filter(Boolean).join("-");
71+const platformTag = (t: Target) => [platformName(t), t.arch, t.abi].filter(Boolean).join("-");
7372
7473 /** Dist directory name (e.g. dist/executor-linux-x64). Only used as a build
7574 * artifact convention; the actual npm package name inside is `executor`. */
@@ -126,7 +125,10 @@ const resolveKeyringNative = (t: Target): string | null => { · 50 unmodified lines
126125 const platformMap: Record<string, { pkg: string; node: string }> = {
127126 "darwin-arm64": { pkg: "@napi-rs/keyring-darwin-arm64", node: "keyring.darwin-arm64.node" },
128127 "darwin-x64": { pkg: "@napi-rs/keyring-darwin-x64", node: "keyring.darwin-x64.node" },
129− "linux-arm64": { pkg: "@napi-rs/keyring-linux-arm64-gnu", node: "keyring.linux-arm64-gnu.node" },
128+ "linux-arm64": {
129+ pkg: "@napi-rs/keyring-linux-arm64-gnu",
130+ node: "keyring.linux-arm64-gnu.node",
131+ },
130132 "linux-x64": { pkg: "@napi-rs/keyring-linux-x64-gnu", node: "keyring.linux-x64-gnu.node" },
131133 "linux-arm64-musl": {
132134 pkg: "@napi-rs/keyring-linux-arm64-musl",
@@ -146,9 +148,7 @@ const resolveKeyringNative = (t: Target): string | null => { · 13 unmodified lines
146148 const entry = platformMap[key];
147149 if (!entry) return null;
148150 try {
149− const req = createRequire(
150− join(repoRoot, "node_modules", "@napi-rs/keyring", "package.json"),
151− );
151+ const req = createRequire(join(repoRoot, "node_modules", "@napi-rs/keyring", "package.json"));
152152 const pkgJson = req.resolve(`${entry.pkg}/package.json`);
153153 return join(dirname(pkgJson), entry.node);
154154 } catch {
@@ -455,7 +455,10 @@ const buildPreviewTarballs = async (binaries: Record<string, string>) => { · 300 unmodified lines
455455
456456 const resolveTargetsFromEnv = (env: string | undefined): Target[] => {
457457 if (!env) throw new Error("EXECUTOR_PREVIEW_TARGETS must be set (comma-separated package names)");
458− const names = env.split(",").map((s) => s.trim()).filter(Boolean);
458+ const names = env
459+ .split(",")
460+ .map((s) => s.trim())
461+ .filter(Boolean);
459462 const resolved = names.map((name) => {
460463 const match = ALL_TARGETS.find((t) => targetPackageName(t) === name);
461464 if (!match) throw new Error(`Unknown preview target: ${name}`);
@@ -482,10 +485,7 @@ const buildPreviewWrapperPackage = async (targets: Target[]) => { · 20 unmodified lines
482485 await writeFile(join(binDir, "executor"), NODE_SHIM);
483486 await chmod(join(binDir, "executor"), 0o755);
484487
485− const postinstall = PREVIEW_POSTINSTALL_SCRIPT.replaceAll(
486− "__CDN_BASE_URL__",
487− `${cdnUrl}/${sha}`,
488− );
488+ const postinstall = PREVIEW_POSTINSTALL_SCRIPT.replaceAll("__CDN_BASE_URL__", `${cdnUrl}/${sha}`);
489489 await writeFile(join(wrapperDir, "postinstall.cjs"), postinstall);
490490
491491 // Restrict os/cpu to platforms we actually built this run so npm refuses
@@ -565,11 +565,10 @@ const publishPackedPackage = async (pkgDir: string, channel: string) => { · 73 unmodified lines
565565 await $`bun pm pack`.cwd(pkgDir);
566566
567567 const provenance = process.env.GITHUB_ACTIONS === "true" ? ["--provenance"] : [];
568− const result =
569− await $`npm publish *.tgz --access public --tag ${channel} ${provenance}`
570− .cwd(pkgDir)
571− .nothrow()
572− .quiet();
568+ const result = await $`npm publish *.tgz --access public --tag ${channel} ${provenance}`
569+ .cwd(pkgDir)
570+ .nothrow()
571+ .quiet();
573572
574573 const stdout = result.stdout.toString();
575574 const stderr = result.stderr.toString();
apps/cli/src/daemon-state.ts+22−14
@@ -59,7 +59,8 @@ export const currentDaemonScopeId = (): string => { · 58 unmodified lines
5959 const resolveDaemonDataDir = (path: Path.Path): string =>
6060 process.env.EXECUTOR_DATA_DIR ?? path.join(homedir(), ".executor");
6161
62−const sanitizeHostForPath = (hostname: string): string => hostname.replaceAll(/[^a-z0-9.-]+/gi, "_");
62+const sanitizeHostForPath = (hostname: string): string =>
63+ hostname.replaceAll(/[^a-z0-9.-]+/gi, "_");
6364 const sanitizeScopeForPath = (scopeId: string): string => scopeId.replaceAll(/[^a-z0-9.-]+/gi, "_");
6465
6566 const daemonRecordPath = (path: Path.Path, input: { hostname: string; port: number }): string => {
@@ -67,14 +68,19 @@ const daemonRecordPath = (path: Path.Path, input: { hostname: string; port: numb · 1 unmodified line
6768 return path.join(resolveDaemonDataDir(path), `daemon-${host}-${input.port}.json`);
6869 };
6970
70−const daemonPointerPath = (path: Path.Path, input: { hostname: string; scopeId: string }): string => {
71+const daemonPointerPath = (
72+ path: Path.Path,
73+ input: { hostname: string; scopeId: string },
74+): string => {
7175 const host = sanitizeHostForPath(canonicalDaemonHost(input.hostname));
7276 const scope = sanitizeScopeForPath(input.scopeId);
7377 return path.join(resolveDaemonDataDir(path), `daemon-active-${host}-${scope}.json`);
7478 };
7579
76−const daemonStartLockPath = (path: Path.Path, input: { hostname: string; scopeId: string }): string =>
77− `${daemonPointerPath(path, input)}.lock`;
80+const daemonStartLockPath = (
81+ path: Path.Path,
82+ input: { hostname: string; scopeId: string },
83+): string => `${daemonPointerPath(path, input)}.lock`;
7884
7985 // ---------------------------------------------------------------------------
8086 // Persistence
@@ -195,9 +201,9 @@ export const readDaemonRecord = (input: { · 114 unmodified lines
195201 Effect.gen(function* () {
196202 const fs = yield* FileSystem.FileSystem;
197203 const path = yield* Path.Path;
198− const raw = yield* fs.readFileString(daemonRecordPath(path, input)).pipe(
199− Effect.catchCause(() => Effect.succeed(null)),
200− );
204+ const raw = yield* fs
205+ .readFileString(daemonRecordPath(path, input))
206+ .pipe(Effect.catchCause(() => Effect.succeed(null)));
201207 if (raw === null) return null;
202208 return parseRecord(raw);
203209 });
@@ -322,7 +328,9 @@ export const acquireDaemonStartLock = (input: { · 118 unmodified lines
322328 };
323329 }
324330
325− const existingRaw = yield* fs.readFileString(lockPath).pipe(Effect.catchCause(() => Effect.succeed(null)));
331+ const existingRaw = yield* fs
332+ .readFileString(lockPath)
333+ .pipe(Effect.catchCause(() => Effect.succeed(null)));
326334 if (existingRaw !== null) {
327335 const existingPid = parseLockPid(existingRaw);
328336 if (existingPid !== null && !isPidAlive(existingPid)) {
@@ -344,11 +352,9 @@ export const acquireDaemonStartLock = (input: { · 15 unmodified lines
344352 );
345353 });
346354
347−export const releaseDaemonStartLock = (input: DaemonStartLock): Effect.Effect<
348− void,
349− PlatformError,
350− FileSystem.FileSystem | Path.Path
351−> =>
355+export const releaseDaemonStartLock = (
356+ input: DaemonStartLock,
357+): Effect.Effect<void, PlatformError, FileSystem.FileSystem | Path.Path> =>
352358 Effect.gen(function* () {
353359 const fs = yield* FileSystem.FileSystem;
354360 yield* fs.remove(input.path, { force: true });
@@ -374,5 +380,7 @@ export const terminatePid = (pid: number): Effect.Effect<void, Error> => · 19 unmodified lines
374380 process.kill(pid, "SIGTERM");
375381 },
376382 catch: (cause) =>
377− cause instanceof Error ? cause : new Error(`Failed to terminate pid ${pid}: ${String(cause)}`),
383+ cause instanceof Error
384+ ? cause
385+ : new Error(`Failed to terminate pid ${pid}: ${String(cause)}`),
378386 });
apps/cli/src/daemon.ts+7−2
@@ -167,7 +167,10 @@ const toProbeHost = (hostname: string): string => { · 166 unmodified lines
167167 return hostname;
168168 };
169169
170−const isPortAvailable = (input: { hostname: string; port: number }): Effect.Effect<boolean, Error> =>
170+const isPortAvailable = (input: {
171+ hostname: string;
172+ port: number;
173+}): Effect.Effect<boolean, Error> =>
171174 Effect.tryPromise({
172175 try: () =>
173176 new Promise<boolean>((resolve) => {
@@ -215,7 +218,9 @@ const pickEphemeralPort = (hostname: string): Effect.Effect<number, Error> => · 41 unmodified lines
215218 server.listen({ port: 0, host: toProbeHost(hostname) });
216219 }),
217220 catch: (cause) =>
218− cause instanceof Error ? cause : new Error(`Failed selecting ephemeral port: ${String(cause)}`),
221+ cause instanceof Error
222+ ? cause
223+ : new Error(`Failed selecting ephemeral port: ${String(cause)}`),
219224 });
220225
221226 export const chooseDaemonPort = (input: {
apps/cli/src/main.ts+3−6
@@ -33,13 +33,10 @@ if (typeof Bun !== "undefined" && (await Bun.file(wasmOnDisk).exists())) { · 32 unmodified lines
3333 type QuickJSSyncVariant = import("quickjs-emscripten").QuickJSSyncVariant;
3434 const wasmBinary = await Bun.file(wasmOnDisk).arrayBuffer();
3535 const importFFI: QuickJSSyncVariant["importFFI"] = () =>
36− import("@jitl/quickjs-wasmfile-release-sync/ffi").then(
37− (m) => m.QuickJSFFI,
38− );
36+ import("@jitl/quickjs-wasmfile-release-sync/ffi").then((m) => m.QuickJSFFI);
3937 const importModuleLoader: QuickJSSyncVariant["importModuleLoader"] = async () => {
40− const { default: original } = await import(
41− "@jitl/quickjs-wasmfile-release-sync/emscripten-module"
42− );
38+ const { default: original } =
39+ await import("@jitl/quickjs-wasmfile-release-sync/emscripten-module");
4340 return (moduleArg = {}) => original({ ...moduleArg, wasmBinary });
4441 };
4542 const variant: QuickJSSyncVariant = {
apps/cli/src/tooling.ts+6−7
@@ -42,7 +42,10 @@ export const inspectToolPath = (input: { · 41 unmodified lines
4242 }): ToolPathInspection => {
4343 const prefixSegments =
4444 input.rawPrefixParts.length === 0 ? [] : buildToolPath(input.rawPrefixParts).split(".");
45− const children = new Map<string, { invokable: boolean; hasChildren: boolean; toolCount: number }>();
45+ const children = new Map<
46+ string,
47+ { invokable: boolean; hasChildren: boolean; toolCount: number }
48+ >();
4649 let exactPath: string | undefined = undefined;
4750 let matchingToolCount = 0;
4851
@@ -97,9 +100,7 @@ export const buildToolPath = (parts: ReadonlyArray<string>): string => { · 48 unmodified lines
97100 throw new Error("Tool path must include at least one segment");
98101 }
99102 if (segments.some((segment) => !TOOL_PATH_TOKEN.test(segment))) {
100− throw new Error(
101− "Tool path segments must contain only letters, numbers, '.', '_' or '-'",
102− );
103+ throw new Error("Tool path segments must contain only letters, numbers, '.', '_' or '-'");
103104 }
104105 return segments.join(".");
105106 };
@@ -186,9 +187,7 @@ export interface PausedInteraction { · 80 unmodified lines
186187 readonly requestedSchema?: Record<string, unknown>;
187188 }
188189
189−export const extractPausedInteraction = (
190− structured: unknown,
191−): PausedInteraction | undefined => {
190+export const extractPausedInteraction = (structured: unknown): PausedInteraction | undefined => {
192191 if (!isRecord(structured) || !isRecord(structured.interaction)) {
193192 return undefined;
194193 }
apps/cloud/drizzle/meta/0000_snapshot.json+20−73
@@ -60,12 +60,8 @@ · 59 unmodified lines
6060 "name": "memberships_account_id_accounts_id_fk",
6161 "tableFrom": "memberships",
6262 "tableTo": "accounts",
63− "columnsFrom": [
64− "account_id"
65− ],
66− "columnsTo": [
67− "id"
68− ],
63+ "columnsFrom": ["account_id"],
64+ "columnsTo": ["id"],
6965 "onDelete": "cascade",
7066 "onUpdate": "no action"
7167 },
@@ -73,12 +69,8 @@ · 1 unmodified line
7369 "name": "memberships_organization_id_organizations_id_fk",
7470 "tableFrom": "memberships",
7571 "tableTo": "organizations",
76− "columnsFrom": [
77− "organization_id"
78− ],
79− "columnsTo": [
80− "id"
81− ],
72+ "columnsFrom": ["organization_id"],
73+ "columnsTo": ["id"],
8274 "onDelete": "cascade",
8375 "onUpdate": "no action"
8476 }
@@ -86,10 +78,7 @@ · 1 unmodified line
8678 "compositePrimaryKeys": {
8779 "memberships_account_id_organization_id_pk": {
8880 "name": "memberships_account_id_organization_id_pk",
89− "columns": [
90− "account_id",
91− "organization_id"
92− ]
81+ "columns": ["account_id", "organization_id"]
9382 }
9483 },
9584 "uniqueConstraints": {},
@@ -157,10 +146,7 @@ · 61 unmodified lines
157146 "compositePrimaryKeys": {
158147 "blob_namespace_key_pk": {
159148 "name": "blob_namespace_key_pk",
160− "columns": [
161− "namespace",
162− "key"
163− ]
149+ "columns": ["namespace", "key"]
164150 }
165151 },
166152 "uniqueConstraints": {},
@@ -266,10 +252,7 @@ · 99 unmodified lines
266252 "compositePrimaryKeys": {
267253 "definition_scope_id_id_pk": {
268254 "name": "definition_scope_id_id_pk",
269− "columns": [
270− "scope_id",
271− "id"
272− ]
255+ "columns": ["scope_id", "id"]
273256 }
274257 },
275258 "uniqueConstraints": {},
@@ -342,10 +325,7 @@ · 66 unmodified lines
342325 "compositePrimaryKeys": {
343326 "graphql_operation_scope_id_id_pk": {
344327 "name": "graphql_operation_scope_id_id_pk",
345− "columns": [
346− "scope_id",
347− "id"
348− ]
328+ "columns": ["scope_id", "id"]
349329 }
350330 },
351331 "uniqueConstraints": {},
@@ -409,10 +389,7 @@ · 57 unmodified lines
409389 "compositePrimaryKeys": {
410390 "graphql_source_scope_id_id_pk": {
411391 "name": "graphql_source_scope_id_id_pk",
412− "columns": [
413− "scope_id",
414− "id"
415− ]
392+ "columns": ["scope_id", "id"]
416393 }
417394 },
418395 "uniqueConstraints": {},
@@ -491,10 +468,7 @@ · 72 unmodified lines
491468 "compositePrimaryKeys": {
492469 "mcp_binding_scope_id_id_pk": {
493470 "name": "mcp_binding_scope_id_id_pk",
494− "columns": [
495− "scope_id",
496− "id"
497− ]
471+ "columns": ["scope_id", "id"]
498472 }
499473 },
500474 "uniqueConstraints": {},
@@ -558,10 +532,7 @@ · 57 unmodified lines
558532 "compositePrimaryKeys": {
559533 "mcp_oauth_session_scope_id_id_pk": {
560534 "name": "mcp_oauth_session_scope_id_id_pk",
561− "columns": [
562− "scope_id",
563− "id"
564− ]
535+ "columns": ["scope_id", "id"]
565536 }
566537 },
567538 "uniqueConstraints": {},
@@ -625,10 +596,7 @@ · 57 unmodified lines
625596 "compositePrimaryKeys": {
626597 "mcp_source_scope_id_id_pk": {
627598 "name": "mcp_source_scope_id_id_pk",
628− "columns": [
629− "scope_id",
630− "id"
631− ]
599+ "columns": ["scope_id", "id"]
632600 }
633601 },
634602 "uniqueConstraints": {},
@@ -686,10 +654,7 @@ · 51 unmodified lines
686654 "compositePrimaryKeys": {
687655 "openapi_oauth_session_scope_id_id_pk": {
688656 "name": "openapi_oauth_session_scope_id_id_pk",
689− "columns": [
690− "scope_id",
691− "id"
692− ]
657+ "columns": ["scope_id", "id"]
693658 }
694659 },
695660 "uniqueConstraints": {},
@@ -762,10 +727,7 @@ · 66 unmodified lines
762727 "compositePrimaryKeys": {
763728 "openapi_operation_scope_id_id_pk": {
764729 "name": "openapi_operation_scope_id_id_pk",
765− "columns": [
766− "scope_id",
767− "id"
768− ]
730+ "columns": ["scope_id", "id"]
769731 }
770732 },
771733 "uniqueConstraints": {},
@@ -847,10 +809,7 @@ · 75 unmodified lines
847809 "compositePrimaryKeys": {
848810 "openapi_source_scope_id_id_pk": {
849811 "name": "openapi_source_scope_id_id_pk",
850− "columns": [
851− "scope_id",
852− "id"
853− ]
812+ "columns": ["scope_id", "id"]
854813 }
855814 },
856815 "uniqueConstraints": {},
@@ -929,10 +888,7 @@ · 72 unmodified lines
929888 "compositePrimaryKeys": {
930889 "secret_scope_id_id_pk": {
931890 "name": "secret_scope_id_id_pk",
932− "columns": [
933− "scope_id",
934− "id"
935− ]
891+ "columns": ["scope_id", "id"]
936892 }
937893 },
938894 "uniqueConstraints": {},
@@ -1050,10 +1006,7 @@ · 111 unmodified lines
10501006 "compositePrimaryKeys": {
10511007 "source_scope_id_id_pk": {
10521008 "name": "source_scope_id_id_pk",
1053− "columns": [
1054− "scope_id",
1055− "id"
1056− ]
1009+ "columns": ["scope_id", "id"]
10571010 }
10581011 },
10591012 "uniqueConstraints": {},
@@ -1177,10 +1130,7 @@ · 117 unmodified lines
11771130 "compositePrimaryKeys": {
11781131 "tool_scope_id_id_pk": {
11791132 "name": "tool_scope_id_id_pk",
1180− "columns": [
1181− "scope_id",
1182− "id"
1183− ]
1133+ "columns": ["scope_id", "id"]
11841134 }
11851135 },
11861136 "uniqueConstraints": {},
@@ -1244,10 +1194,7 @@ · 57 unmodified lines
12441194 "compositePrimaryKeys": {
12451195 "workos_vault_metadata_scope_id_id_pk": {
12461196 "name": "workos_vault_metadata_scope_id_id_pk",
1247− "columns": [
1248− "scope_id",
1249− "id"
1250− ]
1197+ "columns": ["scope_id", "id"]
12511198 }
12521199 },
12531200 "uniqueConstraints": {},
@@ -1267,4 +1214,4 @@ · 13 unmodified lines
12671214 "schemas": {},
12681215 "tables": {}
12691216 }
1270−}
1217+}
apps/cloud/drizzle/meta/0001_snapshot.json+20−73
@@ -60,12 +60,8 @@ · 59 unmodified lines
6060 "name": "memberships_account_id_accounts_id_fk",
6161 "tableFrom": "memberships",
6262 "tableTo": "accounts",
63− "columnsFrom": [
64− "account_id"
65− ],
66− "columnsTo": [
67− "id"
68− ],
63+ "columnsFrom": ["account_id"],
64+ "columnsTo": ["id"],
6965 "onDelete": "cascade",
7066 "onUpdate": "no action"
7167 },
@@ -73,12 +69,8 @@ · 1 unmodified line
7369 "name": "memberships_organization_id_organizations_id_fk",
7470 "tableFrom": "memberships",
7571 "tableTo": "organizations",
76− "columnsFrom": [
77− "organization_id"
78− ],
79− "columnsTo": [
80− "id"
81− ],
72+ "columnsFrom": ["organization_id"],
73+ "columnsTo": ["id"],
8274 "onDelete": "cascade",
8375 "onUpdate": "no action"
8476 }
@@ -86,10 +78,7 @@ · 1 unmodified line
8678 "compositePrimaryKeys": {
8779 "memberships_account_id_organization_id_pk": {
8880 "name": "memberships_account_id_organization_id_pk",
89− "columns": [
90− "account_id",
91− "organization_id"
92− ]
81+ "columns": ["account_id", "organization_id"]
9382 }
9483 },
9584 "uniqueConstraints": {},
@@ -157,10 +146,7 @@ · 61 unmodified lines
157146 "compositePrimaryKeys": {
158147 "blob_namespace_key_pk": {
159148 "name": "blob_namespace_key_pk",
160− "columns": [
161− "namespace",
162− "key"
163− ]
149+ "columns": ["namespace", "key"]
164150 }
165151 },
166152 "uniqueConstraints": {},
@@ -266,10 +252,7 @@ · 99 unmodified lines
266252 "compositePrimaryKeys": {
267253 "definition_scope_id_id_pk": {
268254 "name": "definition_scope_id_id_pk",
269− "columns": [
270− "scope_id",
271− "id"
272− ]
255+ "columns": ["scope_id", "id"]
273256 }
274257 },
275258 "uniqueConstraints": {},
@@ -342,10 +325,7 @@ · 66 unmodified lines
342325 "compositePrimaryKeys": {
343326 "graphql_operation_scope_id_id_pk": {
344327 "name": "graphql_operation_scope_id_id_pk",
345− "columns": [
346− "scope_id",
347− "id"
348− ]
328+ "columns": ["scope_id", "id"]
349329 }
350330 },
351331 "uniqueConstraints": {},
@@ -409,10 +389,7 @@ · 57 unmodified lines
409389 "compositePrimaryKeys": {
410390 "graphql_source_scope_id_id_pk": {
411391 "name": "graphql_source_scope_id_id_pk",
412− "columns": [
413− "scope_id",
414− "id"
415− ]
392+ "columns": ["scope_id", "id"]
416393 }
417394 },
418395 "uniqueConstraints": {},
@@ -491,10 +468,7 @@ · 72 unmodified lines
491468 "compositePrimaryKeys": {
492469 "mcp_binding_scope_id_id_pk": {
493470 "name": "mcp_binding_scope_id_id_pk",
494− "columns": [
495− "scope_id",
496− "id"
497− ]
471+ "columns": ["scope_id", "id"]
498472 }
499473 },
500474 "uniqueConstraints": {},
@@ -558,10 +532,7 @@ · 57 unmodified lines
558532 "compositePrimaryKeys": {
559533 "mcp_oauth_session_scope_id_id_pk": {
560534 "name": "mcp_oauth_session_scope_id_id_pk",
561− "columns": [
562− "scope_id",
563− "id"
564− ]
535+ "columns": ["scope_id", "id"]
565536 }
566537 },
567538 "uniqueConstraints": {},
@@ -625,10 +596,7 @@ · 57 unmodified lines
625596 "compositePrimaryKeys": {
626597 "mcp_source_scope_id_id_pk": {
627598 "name": "mcp_source_scope_id_id_pk",
628− "columns": [
629− "scope_id",
630− "id"
631− ]
599+ "columns": ["scope_id", "id"]
632600 }
633601 },
634602 "uniqueConstraints": {},
@@ -686,10 +654,7 @@ · 51 unmodified lines
686654 "compositePrimaryKeys": {
687655 "openapi_oauth_session_scope_id_id_pk": {
688656 "name": "openapi_oauth_session_scope_id_id_pk",
689− "columns": [
690− "scope_id",
691− "id"
692− ]
657+ "columns": ["scope_id", "id"]
693658 }
694659 },
695660 "uniqueConstraints": {},
@@ -762,10 +727,7 @@ · 66 unmodified lines
762727 "compositePrimaryKeys": {
763728 "openapi_operation_scope_id_id_pk": {
764729 "name": "openapi_operation_scope_id_id_pk",
765− "columns": [
766− "scope_id",
767− "id"
768− ]
730+ "columns": ["scope_id", "id"]
769731 }
770732 },
771733 "uniqueConstraints": {},
@@ -853,10 +815,7 @@ · 81 unmodified lines
853815 "compositePrimaryKeys": {
854816 "openapi_source_scope_id_id_pk": {
855817 "name": "openapi_source_scope_id_id_pk",
856− "columns": [
857− "scope_id",
858− "id"
859− ]
818+ "columns": ["scope_id", "id"]
860819 }
861820 },
862821 "uniqueConstraints": {},
@@ -935,10 +894,7 @@ · 72 unmodified lines
935894 "compositePrimaryKeys": {
936895 "secret_scope_id_id_pk": {
937896 "name": "secret_scope_id_id_pk",
938− "columns": [
939− "scope_id",
940− "id"
941− ]
897+ "columns": ["scope_id", "id"]
942898 }
943899 },
944900 "uniqueConstraints": {},
@@ -1056,10 +1012,7 @@ · 111 unmodified lines
10561012 "compositePrimaryKeys": {
10571013 "source_scope_id_id_pk": {
10581014 "name": "source_scope_id_id_pk",
1059− "columns": [
1060− "scope_id",
1061− "id"
1062− ]
1015+ "columns": ["scope_id", "id"]
10631016 }
10641017 },
10651018 "uniqueConstraints": {},
@@ -1183,10 +1136,7 @@ · 117 unmodified lines
11831136 "compositePrimaryKeys": {
11841137 "tool_scope_id_id_pk": {
11851138 "name": "tool_scope_id_id_pk",
1186− "columns": [
1187− "scope_id",
1188− "id"
1189− ]
1139+ "columns": ["scope_id", "id"]
11901140 }
11911141 },
11921142 "uniqueConstraints": {},
@@ -1250,10 +1200,7 @@ · 57 unmodified lines
12501200 "compositePrimaryKeys": {
12511201 "workos_vault_metadata_scope_id_id_pk": {
12521202 "name": "workos_vault_metadata_scope_id_id_pk",
1253− "columns": [
1254− "scope_id",
1255− "id"
1256− ]
1203+ "columns": ["scope_id", "id"]
12571204 }
12581205 },
12591206 "uniqueConstraints": {},
@@ -1273,4 +1220,4 @@ · 13 unmodified lines
12731220 "schemas": {},
12741221 "tables": {}
12751222 }
1276−}
1223+}
apps/cloud/drizzle/meta/0002_snapshot.json+20−73
@@ -60,12 +60,8 @@ · 59 unmodified lines
6060 "name": "memberships_account_id_accounts_id_fk",
6161 "tableFrom": "memberships",
6262 "tableTo": "accounts",
63− "columnsFrom": [
64− "account_id"
65− ],
66− "columnsTo": [
67− "id"
68− ],
63+ "columnsFrom": ["account_id"],
64+ "columnsTo": ["id"],
6965 "onDelete": "cascade",
7066 "onUpdate": "no action"
7167 },
@@ -73,12 +69,8 @@ · 1 unmodified line
7369 "name": "memberships_organization_id_organizations_id_fk",
7470 "tableFrom": "memberships",
7571 "tableTo": "organizations",
76− "columnsFrom": [
77− "organization_id"
78− ],
79− "columnsTo": [
80− "id"
81− ],
72+ "columnsFrom": ["organization_id"],
73+ "columnsTo": ["id"],
8274 "onDelete": "cascade",
8375 "onUpdate": "no action"
8476 }
@@ -86,10 +78,7 @@ · 1 unmodified line
8678 "compositePrimaryKeys": {
8779 "memberships_account_id_organization_id_pk": {
8880 "name": "memberships_account_id_organization_id_pk",
89− "columns": [
90− "account_id",
91− "organization_id"
92− ]
81+ "columns": ["account_id", "organization_id"]
9382 }
9483 },
9584 "uniqueConstraints": {},
@@ -157,10 +146,7 @@ · 61 unmodified lines
157146 "compositePrimaryKeys": {
158147 "blob_namespace_key_pk": {
159148 "name": "blob_namespace_key_pk",
160− "columns": [
161− "namespace",
162− "key"
163− ]
149+ "columns": ["namespace", "key"]
164150 }
165151 },
166152 "uniqueConstraints": {},
@@ -266,10 +252,7 @@ · 99 unmodified lines
266252 "compositePrimaryKeys": {
267253 "definition_scope_id_id_pk": {
268254 "name": "definition_scope_id_id_pk",
269− "columns": [
270− "scope_id",
271− "id"
272− ]
255+ "columns": ["scope_id", "id"]
273256 }
274257 },
275258 "uniqueConstraints": {},
@@ -342,10 +325,7 @@ · 66 unmodified lines
342325 "compositePrimaryKeys": {
343326 "graphql_operation_scope_id_id_pk": {
344327 "name": "graphql_operation_scope_id_id_pk",
345− "columns": [
346− "scope_id",
347− "id"
348− ]
328+ "columns": ["scope_id", "id"]
349329 }
350330 },
351331 "uniqueConstraints": {},
@@ -409,10 +389,7 @@ · 57 unmodified lines
409389 "compositePrimaryKeys": {
410390 "graphql_source_scope_id_id_pk": {
411391 "name": "graphql_source_scope_id_id_pk",
412− "columns": [
413− "scope_id",
414− "id"
415− ]
392+ "columns": ["scope_id", "id"]
416393 }
417394 },
418395 "uniqueConstraints": {},
@@ -491,10 +468,7 @@ · 72 unmodified lines
491468 "compositePrimaryKeys": {
492469 "mcp_binding_scope_id_id_pk": {
493470 "name": "mcp_binding_scope_id_id_pk",
494− "columns": [
495− "scope_id",
496− "id"
497− ]
471+ "columns": ["scope_id", "id"]
498472 }
499473 },
500474 "uniqueConstraints": {},
@@ -558,10 +532,7 @@ · 57 unmodified lines
558532 "compositePrimaryKeys": {
559533 "mcp_oauth_session_scope_id_id_pk": {
560534 "name": "mcp_oauth_session_scope_id_id_pk",
561− "columns": [
562− "scope_id",
563− "id"
564− ]
535+ "columns": ["scope_id", "id"]
565536 }
566537 },
567538 "uniqueConstraints": {},
@@ -625,10 +596,7 @@ · 57 unmodified lines
625596 "compositePrimaryKeys": {
626597 "mcp_source_scope_id_id_pk": {
627598 "name": "mcp_source_scope_id_id_pk",
628− "columns": [
629− "scope_id",
630− "id"
631− ]
599+ "columns": ["scope_id", "id"]
632600 }
633601 },
634602 "uniqueConstraints": {},
@@ -686,10 +654,7 @@ · 51 unmodified lines
686654 "compositePrimaryKeys": {
687655 "openapi_oauth_session_scope_id_id_pk": {
688656 "name": "openapi_oauth_session_scope_id_id_pk",
689− "columns": [
690− "scope_id",
691− "id"
692− ]
657+ "columns": ["scope_id", "id"]
693658 }
694659 },
695660 "uniqueConstraints": {},
@@ -762,10 +727,7 @@ · 66 unmodified lines
762727 "compositePrimaryKeys": {
763728 "openapi_operation_scope_id_id_pk": {
764729 "name": "openapi_operation_scope_id_id_pk",
765− "columns": [
766− "scope_id",
767− "id"
768− ]
730+ "columns": ["scope_id", "id"]
769731 }
770732 },
771733 "uniqueConstraints": {},
@@ -853,10 +815,7 @@ · 81 unmodified lines
853815 "compositePrimaryKeys": {
854816 "openapi_source_scope_id_id_pk": {
855817 "name": "openapi_source_scope_id_id_pk",
856− "columns": [
857− "scope_id",
858− "id"
859− ]
818+ "columns": ["scope_id", "id"]
860819 }
861820 },
862821 "uniqueConstraints": {},
@@ -935,10 +894,7 @@ · 72 unmodified lines
935894 "compositePrimaryKeys": {
936895 "secret_scope_id_id_pk": {
937896 "name": "secret_scope_id_id_pk",
938− "columns": [
939− "scope_id",
940− "id"
941− ]
897+ "columns": ["scope_id", "id"]
942898 }
943899 },
944900 "uniqueConstraints": {},
@@ -1056,10 +1012,7 @@ · 111 unmodified lines
10561012 "compositePrimaryKeys": {
10571013 "source_scope_id_id_pk": {
10581014 "name": "source_scope_id_id_pk",
1059− "columns": [
1060− "scope_id",
1061− "id"
1062− ]
1015+ "columns": ["scope_id", "id"]
10631016 }
10641017 },
10651018 "uniqueConstraints": {},
@@ -1183,10 +1136,7 @@ · 117 unmodified lines
11831136 "compositePrimaryKeys": {
11841137 "tool_scope_id_id_pk": {
11851138 "name": "tool_scope_id_id_pk",
1186− "columns": [
1187− "scope_id",
1188− "id"
1189− ]
1139+ "columns": ["scope_id", "id"]
11901140 }
11911141 },
11921142 "uniqueConstraints": {},
@@ -1250,10 +1200,7 @@ · 57 unmodified lines
12501200 "compositePrimaryKeys": {
12511201 "workos_vault_metadata_scope_id_id_pk": {
12521202 "name": "workos_vault_metadata_scope_id_id_pk",
1253− "columns": [
1254− "scope_id",
1255− "id"
1256− ]
1203+ "columns": ["scope_id", "id"]
12571204 }
12581205 },
12591206 "uniqueConstraints": {},
@@ -1273,4 +1220,4 @@ · 13 unmodified lines
12731220 "schemas": {},
12741221 "tables": {}
12751222 }
1276−}
1223+}
apps/cloud/drizzle/meta/0003_snapshot.json+21−77
@@ -60,12 +60,8 @@ · 59 unmodified lines
6060 "name": "memberships_account_id_accounts_id_fk",
6161 "tableFrom": "memberships",
6262 "tableTo": "accounts",
63− "columnsFrom": [
64− "account_id"
65− ],
66− "columnsTo": [
67− "id"
68− ],
63+ "columnsFrom": ["account_id"],
64+ "columnsTo": ["id"],
6965 "onDelete": "cascade",
7066 "onUpdate": "no action"
7167 },
@@ -73,12 +69,8 @@ · 1 unmodified line
7369 "name": "memberships_organization_id_organizations_id_fk",
7470 "tableFrom": "memberships",
7571 "tableTo": "organizations",
76− "columnsFrom": [
77− "organization_id"
78− ],
79− "columnsTo": [
80− "id"
81− ],
72+ "columnsFrom": ["organization_id"],
73+ "columnsTo": ["id"],
8274 "onDelete": "cascade",
8375 "onUpdate": "no action"
8476 }
@@ -86,10 +78,7 @@ · 1 unmodified line
8678 "compositePrimaryKeys": {
8779 "memberships_account_id_organization_id_pk": {
8880 "name": "memberships_account_id_organization_id_pk",
89− "columns": [
90− "account_id",
91− "organization_id"
92− ]
81+ "columns": ["account_id", "organization_id"]
9382 }
9483 },
9584 "uniqueConstraints": {},
@@ -157,10 +146,7 @@ · 61 unmodified lines
157146 "compositePrimaryKeys": {
158147 "blob_namespace_key_pk": {
159148 "name": "blob_namespace_key_pk",
160− "columns": [
161− "namespace",
162− "key"
163− ]
149+ "columns": ["namespace", "key"]
164150 }
165151 },
166152 "uniqueConstraints": {},
@@ -281,10 +267,7 @@ · 114 unmodified lines
281267 "compositePrimaryKeys": {
282268 "connection_scope_id_id_pk": {
283269 "name": "connection_scope_id_id_pk",
284− "columns": [
285− "scope_id",
286− "id"
287− ]
270+ "columns": ["scope_id", "id"]
288271 }
289272 },
290273 "uniqueConstraints": {},
@@ -390,10 +373,7 @@ · 99 unmodified lines
390373 "compositePrimaryKeys": {
391374 "definition_scope_id_id_pk": {
392375 "name": "definition_scope_id_id_pk",
393− "columns": [
394− "scope_id",
395− "id"
396− ]
376+ "columns": ["scope_id", "id"]
397377 }
398378 },
399379 "uniqueConstraints": {},
@@ -466,10 +446,7 @@ · 66 unmodified lines
466446 "compositePrimaryKeys": {
467447 "graphql_operation_scope_id_id_pk": {
468448 "name": "graphql_operation_scope_id_id_pk",
469− "columns": [
470− "scope_id",
471− "id"
472− ]
449+ "columns": ["scope_id", "id"]
473450 }
474451 },
475452 "uniqueConstraints": {},
@@ -533,10 +510,7 @@ · 57 unmodified lines
533510 "compositePrimaryKeys": {
534511 "graphql_source_scope_id_id_pk": {
535512 "name": "graphql_source_scope_id_id_pk",
536− "columns": [
537− "scope_id",
538− "id"
539− ]
513+ "columns": ["scope_id", "id"]
540514 }
541515 },
542516 "uniqueConstraints": {},
@@ -615,10 +589,7 @@ · 72 unmodified lines
615589 "compositePrimaryKeys": {
616590 "mcp_binding_scope_id_id_pk": {
617591 "name": "mcp_binding_scope_id_id_pk",
618− "columns": [
619− "scope_id",
620− "id"
621− ]
592+ "columns": ["scope_id", "id"]
622593 }
623594 },
624595 "uniqueConstraints": {},
@@ -682,10 +653,7 @@ · 57 unmodified lines
682653 "compositePrimaryKeys": {
683654 "mcp_oauth_session_scope_id_id_pk": {
684655 "name": "mcp_oauth_session_scope_id_id_pk",
685− "columns": [
686− "scope_id",
687− "id"
688− ]
656+ "columns": ["scope_id", "id"]
689657 }
690658 },
691659 "uniqueConstraints": {},
@@ -749,10 +717,7 @@ · 57 unmodified lines
749717 "compositePrimaryKeys": {
750718 "mcp_source_scope_id_id_pk": {
751719 "name": "mcp_source_scope_id_id_pk",
752− "columns": [
753− "scope_id",
754− "id"
755− ]
720+ "columns": ["scope_id", "id"]
756721 }
757722 },
758723 "uniqueConstraints": {},
@@ -810,10 +775,7 @@ · 51 unmodified lines
810775 "compositePrimaryKeys": {
811776 "openapi_oauth_session_scope_id_id_pk": {
812777 "name": "openapi_oauth_session_scope_id_id_pk",
813− "columns": [
814− "scope_id",
815− "id"
816− ]
778+ "columns": ["scope_id", "id"]
817779 }
818780 },
819781 "uniqueConstraints": {},
@@ -886,10 +848,7 @@ · 66 unmodified lines
886848 "compositePrimaryKeys": {
887849 "openapi_operation_scope_id_id_pk": {
888850 "name": "openapi_operation_scope_id_id_pk",
889− "columns": [
890− "scope_id",
891− "id"
892− ]
851+ "columns": ["scope_id", "id"]
893852 }
894853 },
895854 "uniqueConstraints": {},
@@ -977,10 +936,7 @@ · 81 unmodified lines
977936 "compositePrimaryKeys": {
978937 "openapi_source_scope_id_id_pk": {
979938 "name": "openapi_source_scope_id_id_pk",
980− "columns": [
981− "scope_id",
982− "id"
983− ]
939+ "columns": ["scope_id", "id"]
984940 }
985941 },
986942 "uniqueConstraints": {},
@@ -1080,10 +1036,7 @@ · 93 unmodified lines
10801036 "compositePrimaryKeys": {
10811037 "secret_scope_id_id_pk": {
10821038 "name": "secret_scope_id_id_pk",
1083− "columns": [
1084− "scope_id",
1085− "id"
1086− ]
1039+ "columns": ["scope_id", "id"]
10871040 }
10881041 },
10891042 "uniqueConstraints": {},
@@ -1201,10 +1154,7 @@ · 111 unmodified lines
12011154 "compositePrimaryKeys": {
12021155 "source_scope_id_id_pk": {
12031156 "name": "source_scope_id_id_pk",
1204− "columns": [
1205− "scope_id",
1206− "id"
1207− ]
1157+ "columns": ["scope_id", "id"]
12081158 }
12091159 },
12101160 "uniqueConstraints": {},
@@ -1328,10 +1278,7 @@ · 117 unmodified lines
13281278 "compositePrimaryKeys": {
13291279 "tool_scope_id_id_pk": {
13301280 "name": "tool_scope_id_id_pk",
1331− "columns": [
1332− "scope_id",
1333− "id"
1334− ]
1281+ "columns": ["scope_id", "id"]
13351282 }
13361283 },
13371284 "uniqueConstraints": {},
@@ -1395,10 +1342,7 @@ · 57 unmodified lines
13951342 "compositePrimaryKeys": {
13961343 "workos_vault_metadata_scope_id_id_pk": {
13971344 "name": "workos_vault_metadata_scope_id_id_pk",
1398− "columns": [
1399− "scope_id",
1400− "id"
1401− ]
1345+ "columns": ["scope_id", "id"]
14021346 }
14031347 },
14041348 "uniqueConstraints": {},
@@ -1418,4 +1362,4 @@ · 13 unmodified lines
14181362 "schemas": {},
14191363 "tables": {}
14201364 }
1421−}
1365+}
apps/cloud/drizzle/meta/0007_snapshot.json+21−77
@@ -60,12 +60,8 @@ · 59 unmodified lines
6060 "name": "memberships_account_id_accounts_id_fk",
6161 "tableFrom": "memberships",
6262 "tableTo": "accounts",
63− "columnsFrom": [
64− "account_id"
65− ],
66− "columnsTo": [
67− "id"
68− ],
63+ "columnsFrom": ["account_id"],
64+ "columnsTo": ["id"],
6965 "onDelete": "cascade",
7066 "onUpdate": "no action"
7167 },
@@ -73,12 +69,8 @@ · 1 unmodified line
7369 "name": "memberships_organization_id_organizations_id_fk",
7470 "tableFrom": "memberships",
7571 "tableTo": "organizations",
76− "columnsFrom": [
77− "organization_id"
78− ],
79− "columnsTo": [
80− "id"
81− ],
72+ "columnsFrom": ["organization_id"],
73+ "columnsTo": ["id"],
8274 "onDelete": "cascade",
8375 "onUpdate": "no action"
8476 }
@@ -86,10 +78,7 @@ · 1 unmodified line
8678 "compositePrimaryKeys": {
8779 "memberships_account_id_organization_id_pk": {
8880 "name": "memberships_account_id_organization_id_pk",
89− "columns": [
90− "account_id",
91− "organization_id"
92− ]
81+ "columns": ["account_id", "organization_id"]
9382 }
9483 },
9584 "uniqueConstraints": {},
@@ -157,10 +146,7 @@ · 61 unmodified lines
157146 "compositePrimaryKeys": {
158147 "blob_namespace_key_pk": {
159148 "name": "blob_namespace_key_pk",
160− "columns": [
161− "namespace",
162− "key"
163− ]
149+ "columns": ["namespace", "key"]
164150 }
165151 },
166152 "uniqueConstraints": {},
@@ -275,10 +261,7 @@ · 108 unmodified lines
275261 "compositePrimaryKeys": {
276262 "connection_scope_id_id_pk": {
277263 "name": "connection_scope_id_id_pk",
278− "columns": [
279− "scope_id",
280− "id"
281− ]
264+ "columns": ["scope_id", "id"]
282265 }
283266 },
284267 "uniqueConstraints": {},
@@ -384,10 +367,7 @@ · 99 unmodified lines
384367 "compositePrimaryKeys": {
385368 "definition_scope_id_id_pk": {
386369 "name": "definition_scope_id_id_pk",
387− "columns": [
388− "scope_id",
389− "id"
390− ]
370+ "columns": ["scope_id", "id"]
391371 }
392372 },
393373 "uniqueConstraints": {},
@@ -460,10 +440,7 @@ · 66 unmodified lines
460440 "compositePrimaryKeys": {
461441 "graphql_operation_scope_id_id_pk": {
462442 "name": "graphql_operation_scope_id_id_pk",
463− "columns": [
464− "scope_id",
465− "id"
466− ]
443+ "columns": ["scope_id", "id"]
467444 }
468445 },
469446 "uniqueConstraints": {},
@@ -539,10 +516,7 @@ · 69 unmodified lines
539516 "compositePrimaryKeys": {
540517 "graphql_source_scope_id_id_pk": {
541518 "name": "graphql_source_scope_id_id_pk",
542− "columns": [
543− "scope_id",
544− "id"
545− ]
519+ "columns": ["scope_id", "id"]
546520 }
547521 },
548522 "uniqueConstraints": {},
@@ -621,10 +595,7 @@ · 72 unmodified lines
621595 "compositePrimaryKeys": {
622596 "mcp_binding_scope_id_id_pk": {
623597 "name": "mcp_binding_scope_id_id_pk",
624− "columns": [
625− "scope_id",
626− "id"
627− ]
598+ "columns": ["scope_id", "id"]
628599 }
629600 },
630601 "uniqueConstraints": {},
@@ -688,10 +659,7 @@ · 57 unmodified lines
688659 "compositePrimaryKeys": {
689660 "mcp_source_scope_id_id_pk": {
690661 "name": "mcp_source_scope_id_id_pk",
691− "columns": [
692− "scope_id",
693− "id"
694− ]
662+ "columns": ["scope_id", "id"]
695663 }
696664 },
697665 "uniqueConstraints": {},
@@ -815,10 +783,7 @@ · 117 unmodified lines
815783 "compositePrimaryKeys": {
816784 "oauth2_session_scope_id_id_pk": {
817785 "name": "oauth2_session_scope_id_id_pk",
818− "columns": [
819− "scope_id",
820− "id"
821− ]
786+ "columns": ["scope_id", "id"]
822787 }
823788 },
824789 "uniqueConstraints": {},
@@ -891,10 +856,7 @@ · 66 unmodified lines
891856 "compositePrimaryKeys": {
892857 "openapi_operation_scope_id_id_pk": {
893858 "name": "openapi_operation_scope_id_id_pk",
894− "columns": [
895− "scope_id",
896− "id"
897− ]
859+ "columns": ["scope_id", "id"]
898860 }
899861 },
900862 "uniqueConstraints": {},
@@ -988,10 +950,7 @@ · 87 unmodified lines
988950 "compositePrimaryKeys": {
989951 "openapi_source_scope_id_id_pk": {
990952 "name": "openapi_source_scope_id_id_pk",
991− "columns": [
992− "scope_id",
993− "id"
994− ]
953+ "columns": ["scope_id", "id"]
995954 }
996955 },
997956 "uniqueConstraints": {},
@@ -1213,10 +1172,7 @@ · 215 unmodified lines
12131172 "compositePrimaryKeys": {
12141173 "secret_scope_id_id_pk": {
12151174 "name": "secret_scope_id_id_pk",
1216− "columns": [
1217− "scope_id",
1218− "id"
1219− ]
1175+ "columns": ["scope_id", "id"]
12201176 }
12211177 },
12221178 "uniqueConstraints": {},
@@ -1334,10 +1290,7 @@ · 111 unmodified lines
13341290 "compositePrimaryKeys": {
13351291 "source_scope_id_id_pk": {
13361292 "name": "source_scope_id_id_pk",
1337− "columns": [
1338− "scope_id",
1339− "id"
1340− ]
1293+ "columns": ["scope_id", "id"]
13411294 }
13421295 },
13431296 "uniqueConstraints": {},
@@ -1461,10 +1414,7 @@ · 117 unmodified lines
14611414 "compositePrimaryKeys": {
14621415 "tool_scope_id_id_pk": {
14631416 "name": "tool_scope_id_id_pk",
1464− "columns": [
1465− "scope_id",
1466− "id"
1467− ]
1417+ "columns": ["scope_id", "id"]
14681418 }
14691419 },
14701420 "uniqueConstraints": {},
@@ -1546,10 +1496,7 @@ · 75 unmodified lines
15461496 "compositePrimaryKeys": {
15471497 "tool_policy_scope_id_id_pk": {
15481498 "name": "tool_policy_scope_id_id_pk",
1549− "columns": [
1550− "scope_id",
1551− "id"
1552− ]
1499+ "columns": ["scope_id", "id"]
15531500 }
15541501 },
15551502 "uniqueConstraints": {},
@@ -1613,10 +1560,7 @@ · 57 unmodified lines
16131560 "compositePrimaryKeys": {
16141561 "workos_vault_metadata_scope_id_id_pk": {
16151562 "name": "workos_vault_metadata_scope_id_id_pk",
1616− "columns": [
1617− "scope_id",
1618− "id"
1619− ]
1563+ "columns": ["scope_id", "id"]
16201564 }
16211565 },
16221566 "uniqueConstraints": {},
@@ -1636,4 +1580,4 @@ · 13 unmodified lines
16361580 "schemas": {},
16371581 "tables": {}
16381582 }
1639−}
1583+}
apps/cloud/drizzle/meta/0008_snapshot.json+27−104
@@ -60,12 +60,8 @@ · 59 unmodified lines
6060 "name": "memberships_account_id_accounts_id_fk",
6161 "tableFrom": "memberships",
6262 "tableTo": "accounts",
63− "columnsFrom": [
64− "account_id"
65− ],
66− "columnsTo": [
67− "id"
68− ],
63+ "columnsFrom": ["account_id"],
64+ "columnsTo": ["id"],
6965 "onDelete": "cascade",
7066 "onUpdate": "no action"
7167 },
@@ -73,12 +69,8 @@ · 1 unmodified line
7369 "name": "memberships_organization_id_organizations_id_fk",
7470 "tableFrom": "memberships",
7571 "tableTo": "organizations",
76− "columnsFrom": [
77− "organization_id"
78− ],
79− "columnsTo": [
80− "id"
81− ],
72+ "columnsFrom": ["organization_id"],
73+ "columnsTo": ["id"],
8274 "onDelete": "cascade",
8375 "onUpdate": "no action"
8476 }
@@ -86,10 +78,7 @@ · 1 unmodified line
8678 "compositePrimaryKeys": {
8779 "memberships_account_id_organization_id_pk": {
8880 "name": "memberships_account_id_organization_id_pk",
89− "columns": [
90− "account_id",
91− "organization_id"
92− ]
81+ "columns": ["account_id", "organization_id"]
9382 }
9483 },
9584 "uniqueConstraints": {},
@@ -157,10 +146,7 @@ · 61 unmodified lines
157146 "compositePrimaryKeys": {
158147 "blob_namespace_key_pk": {
159148 "name": "blob_namespace_key_pk",
160− "columns": [
161− "namespace",
162− "key"
163− ]
149+ "columns": ["namespace", "key"]
164150 }
165151 },
166152 "uniqueConstraints": {},
@@ -275,10 +261,7 @@ · 108 unmodified lines
275261 "compositePrimaryKeys": {
276262 "connection_scope_id_id_pk": {
277263 "name": "connection_scope_id_id_pk",
278− "columns": [
279− "scope_id",
280− "id"
281− ]
264+ "columns": ["scope_id", "id"]
282265 }
283266 },
284267 "uniqueConstraints": {},
@@ -384,10 +367,7 @@ · 99 unmodified lines
384367 "compositePrimaryKeys": {
385368 "definition_scope_id_id_pk": {
386369 "name": "definition_scope_id_id_pk",
387− "columns": [
388− "scope_id",
389− "id"
390− ]
370+ "columns": ["scope_id", "id"]
391371 }
392372 },
393373 "uniqueConstraints": {},
@@ -460,10 +440,7 @@ · 66 unmodified lines
460440 "compositePrimaryKeys": {
461441 "graphql_operation_scope_id_id_pk": {
462442 "name": "graphql_operation_scope_id_id_pk",
463− "columns": [
464− "scope_id",
465− "id"
466− ]
443+ "columns": ["scope_id", "id"]
467444 }
468445 },
469446 "uniqueConstraints": {},
@@ -549,10 +526,7 @@ · 79 unmodified lines
549526 "compositePrimaryKeys": {
550527 "graphql_source_scope_id_id_pk": {
551528 "name": "graphql_source_scope_id_id_pk",
552− "columns": [
553− "scope_id",
554− "id"
555− ]
529+ "columns": ["scope_id", "id"]
556530 }
557531 },
558532 "uniqueConstraints": {},
@@ -631,10 +605,7 @@ · 72 unmodified lines
631605 "compositePrimaryKeys": {
632606 "mcp_binding_scope_id_id_pk": {
633607 "name": "mcp_binding_scope_id_id_pk",
634− "columns": [
635− "scope_id",
636− "id"
637− ]
608+ "columns": ["scope_id", "id"]
638609 }
639610 },
640611 "uniqueConstraints": {},
@@ -801,10 +772,7 @@ · 160 unmodified lines
801772 "compositePrimaryKeys": {
802773 "mcp_source_scope_id_id_pk": {
803774 "name": "mcp_source_scope_id_id_pk",
804− "columns": [
805− "scope_id",
806− "id"
807− ]
775+ "columns": ["scope_id", "id"]
808776 }
809777 },
810778 "uniqueConstraints": {},
@@ -928,10 +896,7 @@ · 117 unmodified lines
928896 "compositePrimaryKeys": {
929897 "oauth2_session_scope_id_id_pk": {
930898 "name": "oauth2_session_scope_id_id_pk",
931− "columns": [
932− "scope_id",
933− "id"
934− ]
899+ "columns": ["scope_id", "id"]
935900 }
936901 },
937902 "uniqueConstraints": {},
@@ -1004,10 +969,7 @@ · 66 unmodified lines
1004969 "compositePrimaryKeys": {
1005970 "openapi_operation_scope_id_id_pk": {
1006971 "name": "openapi_operation_scope_id_id_pk",
1007− "columns": [
1008− "scope_id",
1009− "id"
1010− ]
972+ "columns": ["scope_id", "id"]
1011973 }
1012974 },
1013975 "uniqueConstraints": {},
@@ -1089,10 +1051,7 @@ · 75 unmodified lines
10891051 "compositePrimaryKeys": {
10901052 "openapi_source_scope_id_id_pk": {
10911053 "name": "openapi_source_scope_id_id_pk",
1092− "columns": [
1093− "scope_id",
1094− "id"
1095− ]
1054+ "columns": ["scope_id", "id"]
10961055 }
10971056 },
10981057 "uniqueConstraints": {},
@@ -1363,10 +1322,7 @@ · 264 unmodified lines
13631322 "compositePrimaryKeys": {
13641323 "secret_scope_id_id_pk": {
13651324 "name": "secret_scope_id_id_pk",
1366− "columns": [
1367− "scope_id",
1368− "id"
1369− ]
1325+ "columns": ["scope_id", "id"]
13701326 }
13711327 },
13721328 "uniqueConstraints": {},
@@ -1484,10 +1440,7 @@ · 111 unmodified lines
14841440 "compositePrimaryKeys": {
14851441 "source_scope_id_id_pk": {
14861442 "name": "source_scope_id_id_pk",
1487− "columns": [
1488− "scope_id",
1489− "id"
1490− ]
1443+ "columns": ["scope_id", "id"]
14911444 }
14921445 },
14931446 "uniqueConstraints": {},
@@ -1611,10 +1564,7 @@ · 117 unmodified lines
16111564 "compositePrimaryKeys": {
16121565 "tool_scope_id_id_pk": {
16131566 "name": "tool_scope_id_id_pk",
1614− "columns": [
1615− "scope_id",
1616− "id"
1617− ]
1567+ "columns": ["scope_id", "id"]
16181568 }
16191569 },
16201570 "uniqueConstraints": {},
@@ -1696,10 +1646,7 @@ · 75 unmodified lines
16961646 "compositePrimaryKeys": {
16971647 "tool_policy_scope_id_id_pk": {
16981648 "name": "tool_policy_scope_id_id_pk",
1699− "columns": [
1700− "scope_id",
1701− "id"
1702− ]
1649+ "columns": ["scope_id", "id"]
17031650 }
17041651 },
17051652 "uniqueConstraints": {},
@@ -1763,10 +1710,7 @@ · 57 unmodified lines
17631710 "compositePrimaryKeys": {
17641711 "workos_vault_metadata_scope_id_id_pk": {
17651712 "name": "workos_vault_metadata_scope_id_id_pk",
1766− "columns": [
1767− "scope_id",
1768− "id"
1769− ]
1713+ "columns": ["scope_id", "id"]
17701714 }
17711715 },
17721716 "uniqueConstraints": {},
@@ -1878,10 +1822,7 @@ · 105 unmodified lines
18781822 "compositePrimaryKeys": {
18791823 "graphql_source_header_scope_id_id_pk": {
18801824 "name": "graphql_source_header_scope_id_id_pk",
1881− "columns": [
1882− "scope_id",
1883− "id"
1884− ]
1825+ "columns": ["scope_id", "id"]
18851826 }
18861827 },
18871828 "uniqueConstraints": {},
@@ -1993,10 +1934,7 @@ · 105 unmodified lines
19931934 "compositePrimaryKeys": {
19941935 "graphql_source_query_param_scope_id_id_pk": {
19951936 "name": "graphql_source_query_param_scope_id_id_pk",
1996− "columns": [
1997− "scope_id",
1998− "id"
1999− ]
1937+ "columns": ["scope_id", "id"]
20001938 }
20011939 },
20021940 "uniqueConstraints": {},
@@ -2108,10 +2046,7 @@ · 105 unmodified lines
21082046 "compositePrimaryKeys": {
21092047 "openapi_source_query_param_scope_id_id_pk": {
21102048 "name": "openapi_source_query_param_scope_id_id_pk",
2111− "columns": [
2112− "scope_id",
2113− "id"
2114− ]
2049+ "columns": ["scope_id", "id"]
21152050 }
21162051 },
21172052 "uniqueConstraints": {},
@@ -2223,10 +2158,7 @@ · 105 unmodified lines
22232158 "compositePrimaryKeys": {
22242159 "openapi_source_spec_fetch_header_scope_id_id_pk": {
22252160 "name": "openapi_source_spec_fetch_header_scope_id_id_pk",
2226− "columns": [
2227− "scope_id",
2228− "id"
2229− ]
2161+ "columns": ["scope_id", "id"]
22302162 }
22312163 },
22322164 "uniqueConstraints": {},
@@ -2338,10 +2270,7 @@ · 105 unmodified lines
23382270 "compositePrimaryKeys": {
23392271 "openapi_source_spec_fetch_query_param_scope_id_id_pk": {
23402272 "name": "openapi_source_spec_fetch_query_param_scope_id_id_pk",
2341− "columns": [
2342− "scope_id",
2343− "id"
2344− ]
2273+ "columns": ["scope_id", "id"]
23452274 }
23462275 },
23472276 "uniqueConstraints": {},
@@ -2453,10 +2382,7 @@ · 105 unmodified lines
24532382 "compositePrimaryKeys": {
24542383 "mcp_source_header_scope_id_id_pk": {
24552384 "name": "mcp_source_header_scope_id_id_pk",
2456− "columns": [
2457− "scope_id",
2458− "id"
2459− ]
2385+ "columns": ["scope_id", "id"]
24602386 }
24612387 },
24622388 "uniqueConstraints": {},
@@ -2568,10 +2494,7 @@ · 105 unmodified lines
25682494 "compositePrimaryKeys": {
25692495 "mcp_source_query_param_scope_id_id_pk": {
25702496 "name": "mcp_source_query_param_scope_id_id_pk",
2571− "columns": [
2572− "scope_id",
2573− "id"
2574− ]
2497+ "columns": ["scope_id", "id"]
25752498 }
25762499 },
25772500 "uniqueConstraints": {},
apps/cloud/executor.config.ts+1−4
@@ -2,10 +2,7 @@ import { defineExecutorConfig } from "@executor-js/sdk"; · 1 unmodified line
22 import { openApiPlugin } from "@executor-js/plugin-openapi";
33 import { mcpPlugin } from "@executor-js/plugin-mcp";
44 import { graphqlPlugin } from "@executor-js/plugin-graphql";
5−import {
6− workosVaultPlugin,
7− type WorkOSVaultClient,
8−} from "@executor-js/plugin-workos-vault";
5+import { workosVaultPlugin, type WorkOSVaultClient } from "@executor-js/plugin-workos-vault";
96
107 // ---------------------------------------------------------------------------
118 // Single source of truth for the cloud app's plugin list.
apps/cloud/src/api.request-scope.node.test.ts+7−17
@@ -29,15 +29,9 @@ import { RequestScopedServicesLive } from "./api/layers"; · 28 unmodified lines
2929 import { requestScopedMiddleware } from "./api/request-scoped";
3030 import { makeApiLive } from "./api/router";
3131
32−class Counter extends Context.Service<
33− Counter,
34− { readonly id: number }
35−>()("test/Counter") {}
36−
37−const makeCounterLive = (
38− counts: { acquires: number; releases: number },
39− acquireDelayMs = 0,
40−) =>
32+class Counter extends Context.Service<Counter, { readonly id: number }>()("test/Counter") {}
33+
34+const makeCounterLive = (counts: { acquires: number; releases: number }, acquireDelayMs = 0) =>
4135 Layer.effect(Counter)(
4236 Effect.acquireRelease(
4337 Effect.gen(function* () {
@@ -72,8 +66,7 @@ describe("HttpRouter.toWebHandler request scoping", () => { · 28 unmodified lines
7266 Layer.provideMerge(makeCounterLive(counts)),
7367 Layer.provideMerge(HttpServer.layerServices),
7468 );
75− const handler = HttpRouter.toWebHandler(App, { disableLogger: true })
76− .handler;
69+ const handler = HttpRouter.toWebHandler(App, { disableLogger: true }).handler;
7770
7871 const a = await handler(new Request("http://test.local/"));
7972 const b = await handler(new Request("http://test.local/"));
@@ -91,8 +84,7 @@ describe("HttpRouter.toWebHandler request scoping", () => { · 11 unmodified lines
9184 HttpRouter.provideRequest(makeCounterLive(counts)),
9285 Layer.provideMerge(HttpServer.layerServices),
9386 );
94− const handler = HttpRouter.toWebHandler(App, { disableLogger: true })
95− .handler;
87+ const handler = HttpRouter.toWebHandler(App, { disableLogger: true }).handler;
9688
9789 const a = await handler(new Request("http://test.local/"));
9890 const b = await handler(new Request("http://test.local/"));
@@ -111,8 +103,7 @@ describe("HttpRouter.toWebHandler request scoping", () => { · 12 unmodified lines
111103 Layer.provide(requestScopedMiddleware(makeCounterLive(counts)).layer),
112104 Layer.provideMerge(HttpServer.layerServices),
113105 );
114− const handler = HttpRouter.toWebHandler(App, { disableLogger: true })
115− .handler;
106+ const handler = HttpRouter.toWebHandler(App, { disableLogger: true }).handler;
116107
117108 const a = await handler(new Request("http://test.local/"));
118109 const b = await handler(new Request("http://test.local/"));
@@ -144,8 +135,7 @@ describe("HttpRouter.toWebHandler request scoping", () => { · 25 unmodified lines
144135 Layer.provide(requestScopedMiddleware(makeCounterLive(counts, 5)).layer),
145136 Layer.provideMerge(HttpServer.layerServices),
146137 );
147− const handler = HttpRouter.toWebHandler(App, { disableLogger: true })
148− .handler;
138+ const handler = HttpRouter.toWebHandler(App, { disableLogger: true }).handler;
149139
150140 const [a, b] = await Promise.all([
151141 handler(new Request("http://test.local/")),
apps/cloud/src/api.test.ts+2−7
@@ -93,9 +93,7 @@ const TestProtectedGate = HttpRouter.middleware()((httpEffect) => · 92 unmodified lines
9393 );
9494 }
9595 if (testState.mode === "error") {
96− return Effect.succeed(
97− HttpServerResponse.jsonUnsafe({ error: "boom" }, { status: 500 }),
98− );
96+ return Effect.succeed(HttpServerResponse.jsonUnsafe({ error: "boom" }, { status: 500 }));
9997 }
10098 if (testState.mode === "bad-status") {
10199 return Effect.succeed(
@@ -130,10 +128,7 @@ const TestApiLive = Layer.mergeAll( · 28 unmodified lines
130128 AuthTestLive,
131129 ProtectedTestLive,
132130 AutumnTestRoutesLive,
133−).pipe(
134− Layer.provideMerge(RouterConfig),
135− Layer.provideMerge(HttpServer.layerServices),
136−);
131+).pipe(Layer.provideMerge(RouterConfig), Layer.provideMerge(HttpServer.layerServices));
137132
138133 const requestHandler = HttpRouter.toWebHandler(TestApiLive, { disableLogger: true }).handler;
139134
apps/cloud/src/api/autumn.ts+2−10
@@ -1,18 +1,10 @@
11 import { env } from "cloudflare:workers";
22 import { Cause, Effect } from "effect";
3−import {
4− HttpRouter,
5− HttpServerRequest,
6− HttpServerResponse,
7−} from "effect/unstable/http";
3+import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
84 import { autumnHandler } from "autumn-js/backend";
95
106 import { WorkOSAuth } from "../auth/workos";
11−import {
12− HttpResponseError,
13− isServerError,
14− toErrorServerResponse,
15−} from "./error-response";
7+import { HttpResponseError, isServerError, toErrorServerResponse } from "./error-response";
168
179 const handler = Effect.gen(function* () {
1810 const request = yield* HttpServerRequest.HttpServerRequest;
apps/cloud/src/api/error-response.ts+1−4
@@ -1,8 +1,5 @@
11 import { Cause, Data, Effect, Predicate, Result } from "effect";
2−import {
3− HttpServerRespondable,
4− HttpServerResponse,
5−} from "effect/unstable/http";
2+import { HttpServerRespondable, HttpServerResponse } from "effect/unstable/http";
63
74 import { captureCause } from "../observability";
85
apps/cloud/src/api/layers.ts+4−14
@@ -18,11 +18,7 @@ import { CoreSharedServices } from "./core-shared-services"; · 17 unmodified lines
1818 import { ProtectedCloudApi, RouterConfig } from "./protected-layers";
1919 import { requestScopedMiddleware } from "./request-scoped";
2020
21−export {
22− CoreSharedServices,
23− ProtectedCloudApi,
24− RouterConfig,
25−};
21+export { CoreSharedServices, ProtectedCloudApi, RouterConfig };
2622
2723 const DbLive = DbService.Live;
2824 const UserStoreLive = UserStoreService.Live.pipe(Layer.provide(DbLive));
@@ -51,9 +47,7 @@ export const BootSharedServices = Layer.mergeAll( · 22 unmodified lines
5147 // without per-request scoping the postgres.js socket pins to the worker's
5248 // boot scope and Cloudflare Workers' I/O isolation kills the second
5349 // request.
54−export const makeNonProtectedApiLive = (
55− rsLive: Layer.Layer<DbService | UserStoreService>,
56−) =>
50+export const makeNonProtectedApiLive = (rsLive: Layer.Layer<DbService | UserStoreService>) =>
5751 HttpApiBuilder.layer(NonProtectedApi).pipe(
5852 Layer.provide(Layer.mergeAll(CloudAuthPublicHandlers, CloudSessionAuthHandlers)),
5953 Layer.provide(requestScopedMiddleware(rsLive).layer),
@@ -62,9 +56,7 @@ export const makeNonProtectedApiLive = ( · 2 unmodified lines
6256
6357 // Routes scoped to a specific org (membership management, switching, etc.).
6458 // Auth is enforced by `OrgAuth` middleware declared on `OrgHttpApi`.
65−export const makeOrgApiLive = (
66− rsLive: Layer.Layer<DbService | UserStoreService>,
67−) =>
59+export const makeOrgApiLive = (rsLive: Layer.Layer<DbService | UserStoreService>) =>
6860 HttpApiBuilder.layer(OrgHttpApi).pipe(
6961 Layer.provide(OrgHandlers),
7062 Layer.provide(requestScopedMiddleware(rsLive).layer),
@@ -74,7 +66,5 @@ export const makeOrgApiLive = ( · 3 unmodified lines
7466 // Default exports use the production per-request layer. Existing callers
7567 // that import `NonProtectedApiLive`/`OrgApiLive` continue to work; the
7668 // `make*` factories exist for tests that need to swap in a fake.
77−export const NonProtectedApiLive = makeNonProtectedApiLive(
78− RequestScopedServicesLive,
79−);
69+export const NonProtectedApiLive = makeNonProtectedApiLive(RequestScopedServicesLive);
8070 export const OrgApiLive = makeOrgApiLive(RequestScopedServicesLive);
apps/cloud/src/api/protected-layers.ts+1−5
@@ -8,11 +8,7 @@ import { HttpRouter, HttpServer } from "effect/unstable/http"; · 7 unmodified lines
88 import { Layer } from "effect";
99
1010 import { observabilityMiddleware } from "@executor-js/api";
11−import {
12− CoreHandlers,
13− composePluginApi,
14− composePluginHandlerLayer,
15−} from "@executor-js/api/server";
11+import { CoreHandlers, composePluginApi, composePluginHandlerLayer } from "@executor-js/api/server";
1612
1713 import { cloudPlugins } from "./cloud-plugins";
1814 import { UserStoreService } from "../auth/context";
apps/cloud/src/api/protected.ts+3−12
@@ -3,10 +3,7 @@ · 2 unmodified lines
33 // harness can't load in the workerd test runtime.
44
55 import { HttpApiSwagger } from "effect/unstable/httpapi";
6−import {
7− HttpRouter,
8− HttpServerRequest,
9−} from "effect/unstable/http";
6+import { HttpRouter, HttpServerRequest } from "effect/unstable/http";
107 import { Effect, Layer } from "effect";
118
129 import {
@@ -26,11 +23,7 @@ import { DbService } from "../services/db"; · 13 unmodified lines
2623 import { makeExecutionStack } from "../services/execution-stack";
2724 import { HttpResponseError } from "./error-response";
2825 import { RequestScopedServicesLive } from "./layers";
29−import {
30− ProtectedCloudApi,
31− ProtectedCloudApiLive,
32− RouterConfig,
33−} from "./protected-layers";
26+import { ProtectedCloudApi, ProtectedCloudApiLive, RouterConfig } from "./protected-layers";
3427 import { requestScopedMiddleware } from "./request-scoped";
3528
3629 // Pre-compute the per-plugin `Effect.provideService(extensionService,
@@ -119,9 +112,7 @@ const ExecutionStackMiddleware = HttpRouter.middleware<{ · 82 unmodified lines
119112 // the layer rebuilds per HTTP request, satisfying Cloudflare Workers'
120113 // I/O isolation. Exposed as a factory so tests can swap in a counting
121114 // fake — see `apps/cloud/src/api.request-scope.node.test.ts`.
122−export const makeProtectedApiLive = (
123− rsLive: Layer.Layer<DbService | UserStoreService>,
124−) => {
115+export const makeProtectedApiLive = (rsLive: Layer.Layer<DbService | UserStoreService>) => {
125116 const protectedMiddleware = ExecutionStackMiddleware.combine(
126117 requestScopedMiddleware(rsLive),
127118 ).layer;
apps/cloud/src/api/router.ts+2−7
@@ -25,18 +25,13 @@ import { SlackContactRoutesLive } from "./slack"; · 24 unmodified lines
2525 // so tests can substitute a counting fake for `DbService.Live` and
2626 // assert per-request semantics — see
2727 // `apps/cloud/src/api.request-scope.node.test.ts`.
28−export const makeApiLive = (
29− requestScopedLive: Layer.Layer<DbService | UserStoreService>,
30−) =>
28+export const makeApiLive = (requestScopedLive: Layer.Layer<DbService | UserStoreService>) =>
3129 Layer.mergeAll(
3230 makeNonProtectedApiLive(requestScopedLive),
3331 makeOrgApiLive(requestScopedLive),
3432 makeProtectedApiLive(requestScopedLive),
3533 AutumnRoutesLive,
3634 SlackContactRoutesLive,
37− ).pipe(
38− Layer.provideMerge(RouterConfig),
39− Layer.provideMerge(BootSharedServices),
40− );
35+ ).pipe(Layer.provideMerge(RouterConfig), Layer.provideMerge(BootSharedServices));
4136
4237 export const ApiLive = makeApiLive(RequestScopedServicesLive);
apps/cloud/src/api/slack.ts+1−5
@@ -3,11 +3,7 @@ import { Cause, Effect } from "effect"; · 2 unmodified lines
33 import { HttpRouter, HttpServerRequest, HttpServerResponse } from "effect/unstable/http";
44
55 import { SlackService } from "../services/slack";
6−import {
7− HttpResponseError,
8− isServerError,
9− toErrorServerResponse,
10−} from "./error-response";
6+import { HttpResponseError, isServerError, toErrorServerResponse } from "./error-response";
117
128 const isValidEmail = (s: string): boolean => /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(s);
139
apps/cloud/src/auth/context.ts+3−4
@@ -23,10 +23,9 @@ const makeService = (store: RawStore) => ({ · 22 unmodified lines
2323
2424 type UserStoreServiceType = ReturnType<typeof makeService>;
2525
26−export class UserStoreService extends Context.Service<
27− UserStoreService,
28− UserStoreServiceType
29−>()("@executor-js/cloud/UserStoreService") {
26+export class UserStoreService extends Context.Service<UserStoreService, UserStoreServiceType>()(
27+ "@executor-js/cloud/UserStoreService",
28+) {
3029 static Live = Layer.effect(this)(
3130 Effect.map(DbService.asEffect(), ({ db }) => makeService(makeUserStore(db))),
3231 );
apps/cloud/src/auth/handlers.ts+8−14
@@ -304,8 +304,7 @@ export const CloudSessionAuthHandlers = HttpApiBuilder.group( · 303 unmodified lines
304304 ? yield* workos.getUser(inv.inviterUserId).pipe(
305305 Effect.map((u) => ({
306306 email: u.email,
307− name:
308− [u.firstName, u.lastName].filter(Boolean).join(" ") || null,
307+ name: [u.firstName, u.lastName].filter(Boolean).join(" ") || null,
309308 })),
310309 Effect.orElseSucceed(() => null),
311310 )
@@ -354,20 +353,15 @@ export const CloudSessionAuthHandlers = HttpApiBuilder.group( · 42 unmodified lines
354353 // through login again. The acceptance has already succeeded
355354 // server-side, so the next login will pick up the membership.
356355 const refreshed = yield* workos.refreshSession(session.sealedSession, org.id);
357− const verified = refreshed
358− ? yield* workos.authenticateSealedSession(refreshed)
359− : null;
356+ const verified = refreshed ? yield* workos.authenticateSealedSession(refreshed) : null;
360357
361358 if (!refreshed || !verified || verified.organizationId !== org.id) {
362− yield* Effect.logWarning(
363− "acceptInvitation: unable to attach org to current session",
364− {
365− userId: session.accountId,
366− orgId: org.id,
367− refreshReturnedSession: refreshed != null,
368− verifiedOrgId: verified?.organizationId ?? null,
369− },
370− );
359+ yield* Effect.logWarning("acceptInvitation: unable to attach org to current session", {
360+ userId: session.accountId,
361+ orgId: org.id,
362+ refreshReturnedSession: refreshed != null,
363+ verifiedOrgId: verified?.organizationId ?? null,
364+ });
371365 deleteCookie("wos-session", { path: "/" });
372366 return yield* new WorkOSError();
373367 }
apps/cloud/src/auth/middleware.ts+11−10
@@ -23,8 +23,9 @@ export type Session = { · 22 unmodified lines
2323 readonly refreshedSession: string | null;
2424 };
2525
26−export class SessionContext extends Context.Service<SessionContext, Session
27−>()("@executor-js/cloud/Session") {}
26+export class SessionContext extends Context.Service<SessionContext, Session>()(
27+ "@executor-js/cloud/Session",
28+) {}
2829
2930 // ---------------------------------------------------------------------------
3031 // Errors
@@ -71,12 +72,12 @@ export class AuthContext extends Context.Service< · 40 unmodified lines
7172 }
7273 >()("@executor-js/cloud/AuthContext") {}
7374
74−export class OrgAuth extends HttpApiMiddleware.Service<
75− OrgAuth,
76− { provides: AuthContext }
77−>()("OrgAuth", {
78− error: [Unauthorized, NoOrganization],
79− security: {
80− cookie: HttpApiSecurity.apiKey({ in: "cookie", key: "wos-session" }),
75+export class OrgAuth extends HttpApiMiddleware.Service<OrgAuth, { provides: AuthContext }>()(
76+ "OrgAuth",
77+ {
78+ error: [Unauthorized, NoOrganization],
79+ security: {
80+ cookie: HttpApiSecurity.apiKey({ in: "cookie", key: "wos-session" }),
81+ },
8182 },
82−}) {}
83+) {}
apps/cloud/src/auth/resolve-organization.ts+1−3
@@ -24,7 +24,5 @@ export const resolveOrganization = (organizationId: string) => · 23 unmodified lines
2424
2525 const workos = yield* WorkOSAuth;
2626 const fresh = yield* workos.getOrganization(organizationId);
27− return yield* users.use((s) =>
28− s.upsertOrganization({ id: fresh.id, name: fresh.name }),
29− );
27+ return yield* users.use((s) => s.upsertOrganization({ id: fresh.id, name: fresh.name }));
3028 });
apps/cloud/src/auth/workos.ts+4−6
@@ -8,8 +8,7 @@ import { GeneratePortalLinkIntent, WorkOS } from "@workos-inc/node/worker"; · 7 unmodified lines
88 import { WorkOSError, tryPromiseService, withServiceLogging } from "./errors";
99
1010 const COOKIE_NAME = "wos-session";
11−const INVALID_COOKIE_PASSWORD_MESSAGE =
12− "WORKOS_COOKIE_PASSWORD must be at least 32 characters";
11+const INVALID_COOKIE_PASSWORD_MESSAGE = "WORKOS_COOKIE_PASSWORD must be at least 32 characters";
1312
1413 class WorkOSAuthConfigurationError extends Data.TaggedError("WorkOSAuthConfigurationError")<{
1514 readonly message: string;
@@ -247,10 +246,9 @@ const make = Effect.gen(function* () { · 231 unmodified lines
247246
248247 export type WorkOSAuthService = Effect.Success<typeof make>;
249248
250−export class WorkOSAuth extends Context.Service<
251− WorkOSAuth,
252− WorkOSAuthService
253−>()("@executor-js/cloud/WorkOSAuth") {
249+export class WorkOSAuth extends Context.Service<WorkOSAuth, WorkOSAuthService>()(
250+ "@executor-js/cloud/WorkOSAuth",
251+) {
254252 static Default = Layer.effect(this)(make).pipe(
255253 Layer.withSpan("WorkOSAuth", { attributes: { module: "WorkOSAuth" } }),
256254 );
apps/cloud/src/jwks-cache.node.test.ts+1−3
@@ -102,9 +102,7 @@ describe("createCachedRemoteJWKSet", () => { · 101 unmodified lines
102102 const jwks = createCachedRemoteJWKSet(jwksUrl, { fetch });
103103 const token = await sign(kp);
104104
105− const verifies = Array.from({ length: 10 }, () =>
106− jwtVerify(token, jwks, { issuer, audience }),
107− );
105+ const verifies = Array.from({ length: 10 }, () => jwtVerify(token, jwks, { issuer, audience }));
108106 // Let microtasks settle so all 10 calls hit the cache miss path.
109107 await new Promise((r) => setTimeout(r, 10));
110108 resolveFetch();
apps/cloud/src/mcp-auth.node.test.ts+6−4
@@ -36,10 +36,12 @@ describe("MCP AuthKit token verification", () => { · 35 unmodified lines
3636 const { jwks, sign } = yield* Effect.promise(() => makeVerifier());
3737 const token = yield* Effect.promise(() => sign({ aud: otherResource }));
3838
39− const error = yield* Effect.flip(verifyMcpAccessToken(token, jwks, {
40− issuer,
41− audience: resource,
42− }));
39+ const error = yield* Effect.flip(
40+ verifyMcpAccessToken(token, jwks, {
41+ issuer,
42+ audience: resource,
43+ }),
44+ );
4345
4446 expect(error).toBeInstanceOf(McpJwtVerificationError);
4547 }),
apps/cloud/src/mcp-flow.test.ts+7−12
@@ -45,10 +45,11 @@ const SESSION_TIMEOUT_MS = 5 * 60 * 1000; · 44 unmodified lines
4545 const SESSION_META_KEY = "session-meta";
4646 const LAST_ACTIVITY_KEY = "last-activity-ms";
4747
48−const doRuntimeControls = (instance: unknown): {
48+const doRuntimeControls = (
49+ instance: unknown,
50+): {
4951 closeRuntime: () => Effect.Effect<void>;
50−} =>
51− instance as { closeRuntime: () => Effect.Effect<void> };
52+} => instance as { closeRuntime: () => Effect.Effect<void> };
5253
5354 const doActivityState = (instance: unknown): { lastActivityMs: number } =>
5455 instance as { lastActivityMs: number };
@@ -340,9 +341,7 @@ describe("/mcp session restore", () => { · 285 unmodified lines
340341 const ns = env.MCP_SESSION;
341342 const stub = ns.get(ns.idFromString(sessionId!));
342343 await runInDurableObject(stub, async (instance) => {
343− await Effect.runPromise(
344− doRuntimeControls(instance).closeRuntime(),
345− );
344+ await Effect.runPromise(doRuntimeControls(instance).closeRuntime());
346345 });
347346
348347 const response = await mcpPost({
@@ -376,9 +375,7 @@ describe("/mcp session restore", () => { · 27 unmodified lines
376375 const ns = env.MCP_SESSION;
377376 const stub = ns.get(ns.idFromString(sessionId!));
378377 await runInDurableObject(stub, async (instance) => {
379− await Effect.runPromise(
380− doRuntimeControls(instance).closeRuntime(),
381− );
378+ await Effect.runPromise(doRuntimeControls(instance).closeRuntime());
382379 });
383380
384381 const getResponse = await mcpGet({ bearer, sessionId: sessionId! });
@@ -438,9 +435,7 @@ describe("/mcp session restore", () => { · 53 unmodified lines
438435 const ns = env.MCP_SESSION;
439436 const stub = ns.get(ns.idFromString(sessionId!));
440437 await runInDurableObject(stub, async (instance) => {
441− await Effect.runPromise(
442− doRuntimeControls(instance).closeRuntime(),
443− );
438+ await Effect.runPromise(doRuntimeControls(instance).closeRuntime());
444439 });
445440
446441 const response = await mcpPost({
apps/cloud/src/mcp-session.e2e.node.test.ts+7−10
@@ -33,10 +33,7 @@ import { · 32 unmodified lines
3333 createExecutor,
3434 definePlugin,
3535 } from "@executor-js/sdk";
36−import {
37− makePostgresAdapter,
38− makePostgresBlobStore,
39−} from "@executor-js/storage-postgres";
36+import { makePostgresAdapter, makePostgresBlobStore } from "@executor-js/storage-postgres";
4037 import { makeTestWorkOSVaultClient } from "@executor-js/plugin-workos-vault/testing";
4138 import executorConfig from "../executor.config";
4239 import { DbService } from "./services/db";
@@ -65,7 +62,11 @@ const elicitingTestPlugin = definePlugin(() => ({ · 22 unmodified lines
6562 name: "needsApproval",
6663 description: "Tool that asks the caller to approve before returning.",
6764 inputSchema: EMPTY_INPUT_SCHEMA,
68− handler: ({ elicit }: { elicit: (r: FormElicitation) => Effect.Effect<typeof ElicitationResponse.Type, unknown> }) =>
65+ handler: ({
66+ elicit,
67+ }: {
68+ elicit: (r: FormElicitation) => Effect.Effect<typeof ElicitationResponse.Type, unknown>;
69+ }) =>
6970 Effect.gen(function* () {
7071 const response = yield* elicit(
7172 new FormElicitation({
@@ -94,11 +95,7 @@ const ELICITATION_CAPS: ClientCapabilities = { · 22 unmodified lines
9495
9596 type BuildOptions = { readonly withElicitingPlugin?: boolean };
9697
97−const buildScopedExecutor = (
98− scopeId: string,
99− scopeName: string,
100− options: BuildOptions = {},
101−) =>
98+const buildScopedExecutor = (scopeId: string, scopeName: string, options: BuildOptions = {}) =>
10299 Effect.gen(function* () {
103100 const { db } = yield* DbService;
104101 const basePlugins = executorConfig.plugins({
apps/cloud/src/routes/billing_.plans.tsx+74−63
@@ -381,69 +381,69 @@ function SlackContactCta() { · 380 unmodified lines
381381 <span aria-hidden>→</span>
382382 </Button>
383383 </DialogTrigger>
384− <DialogContent>
385− {inviteUrl ? (
386− <>
387− <DialogHeader>
388− <DialogTitle>Check your inbox</DialogTitle>
389− <DialogDescription>
390− We've created a private Slack channel and emailed you an invite. You can also
391− open it directly:
392− </DialogDescription>
393− </DialogHeader>
394− <a
395− href={inviteUrl}
396− target="_blank"
397− rel="noreferrer"
398− className="inline-flex h-9 w-full items-center justify-center gap-2 rounded-md bg-primary text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
399− >
400− <SlackMark className="size-4" />
401− Open Slack invite
402− </a>
403− <DialogFooter>
404− <DialogClose asChild>
405− <Button type="button" variant="outline">
406− Done
407− </Button>
408− </DialogClose>
409− </DialogFooter>
410− </>
411− ) : (
412− <form onSubmit={onSubmit}>
413− <DialogHeader>
414− <DialogTitle>Get in touch on Slack</DialogTitle>
415− <DialogDescription>
416− We'll create a private Slack Connect channel between you and the Executor team.
417− </DialogDescription>
418− </DialogHeader>
419− <div className="grid gap-4 py-4">
420− <div className="grid gap-2">
421− <Label htmlFor="slack-contact-email">Work email</Label>
422− <Input
423− id="slack-contact-email"
424− type="email"
425− autoComplete="email"
426− required
427− value={email}
428− onChange={(e) => setEmail(e.currentTarget.value)}
429− placeholder="you@company.com"
430− />
384+ <DialogContent>
385+ {inviteUrl ? (
386+ <>
387+ <DialogHeader>
388+ <DialogTitle>Check your inbox</DialogTitle>
389+ <DialogDescription>
390+ We've created a private Slack channel and emailed you an invite. You can also
391+ open it directly:
392+ </DialogDescription>
393+ </DialogHeader>
394+ <a
395+ href={inviteUrl}
396+ target="_blank"
397+ rel="noreferrer"
398+ className="inline-flex h-9 w-full items-center justify-center gap-2 rounded-md bg-primary text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90"
399+ >
400+ <SlackMark className="size-4" />
401+ Open Slack invite
402+ </a>
403+ <DialogFooter>
404+ <DialogClose asChild>
405+ <Button type="button" variant="outline">
406+ Done
407+ </Button>
408+ </DialogClose>
409+ </DialogFooter>
410+ </>
411+ ) : (
412+ <form onSubmit={onSubmit}>
413+ <DialogHeader>
414+ <DialogTitle>Get in touch on Slack</DialogTitle>
415+ <DialogDescription>
416+ We'll create a private Slack Connect channel between you and the Executor team.
417+ </DialogDescription>
418+ </DialogHeader>
419+ <div className="grid gap-4 py-4">
420+ <div className="grid gap-2">
421+ <Label htmlFor="slack-contact-email">Work email</Label>
422+ <Input
423+ id="slack-contact-email"
424+ type="email"
425+ autoComplete="email"
426+ required
427+ value={email}
428+ onChange={(e) => setEmail(e.currentTarget.value)}
429+ placeholder="you@company.com"
430+ />
431+ </div>
432+ {error && <p className="text-sm text-destructive">{error}</p>}
431433 </div>
432− {error && <p className="text-sm text-destructive">{error}</p>}
433− </div>
434− <DialogFooter>
435− <DialogClose asChild>
436− <Button type="button" variant="outline" disabled={submitting}>
437− Cancel
434+ <DialogFooter>
435+ <DialogClose asChild>
436+ <Button type="button" variant="outline" disabled={submitting}>
437+ Cancel
438+ </Button>
439+ </DialogClose>
440+ <Button type="submit" disabled={submitting || !email}>
441+ {submitting ? "Sending…" : "Send invite"}
438442 </Button>
439− </DialogClose>
440− <Button type="submit" disabled={submitting || !email}>
441− {submitting ? "Sending…" : "Send invite"}
442− </Button>
443− </DialogFooter>
444− </form>
445− )}
446− </DialogContent>
443+ </DialogFooter>
444+ </form>
445+ )}
446+ </DialogContent>
447447 </Dialog>
448448 <span className="text-muted-foreground/60" aria-hidden>
449449 ·
@@ -463,8 +463,19 @@ function SlackContactCta() { · 13 unmodified lines
463463
464464 function MailIcon({ className }: { className?: string }) {
465465 return (
466− <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" className={className} aria-hidden>
467− <path strokeLinecap="round" strokeLinejoin="round" d="M3 7l9 6 9-6M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
466+ <svg
467+ viewBox="0 0 24 24"
468+ fill="none"
469+ stroke="currentColor"
470+ strokeWidth="1.8"
471+ className={className}
472+ aria-hidden
473+ >
474+ <path
475+ strokeLinecap="round"
476+ strokeLinejoin="round"
477+ d="M3 7l9 6 9-6M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
478+ />
468479 </svg>
469480 );
470481 }
apps/cloud/src/routes/sources.add.$pluginKey.tsx+1−8
@@ -15,13 +15,6 @@ export const Route = createFileRoute("/sources/add/$pluginKey")({ · 14 unmodified lines
1515 component: () => {
1616 const { pluginKey } = Route.useParams();
1717 const { url, preset, namespace } = Route.useSearch();
18− return (
19− <SourcesAddPage
20− pluginKey={pluginKey}
21− url={url}
22− preset={preset}
23− namespace={namespace}
24− />
25− );
18+ return <SourcesAddPage pluginKey={pluginKey} url={url} preset={preset} namespace={namespace} />;
2619 },
2720 });
apps/cloud/src/secrets-isolation.e2e.node.test.ts+153−165
@@ -31,192 +31,180 @@ import { Effect, Result } from "effect"; · 30 unmodified lines
3131
3232 import { ScopeId, SecretId } from "@executor-js/sdk";
3333
34−import {
35− asUser,
36− testUserOrgScopeId,
37−} from "./services/__test-harness__/api-harness";
34+import { asUser, testUserOrgScopeId } from "./services/__test-harness__/api-harness";
3835
3936 const uniq = () => crypto.randomUUID().slice(0, 8);
4037 const nextOrgId = () => `org_iso_${uniq()}`;
4138 const nextUserId = () => `user_iso_${uniq()}`;
4239
4340 describe("cloud secret isolation (HTTP, user-org scope stack)", () => {
44− it.effect(
45− "users in different orgs cannot read each other's org-scoped secrets",
46− () =>
47− Effect.gen(function* () {
48− const orgA = nextOrgId();
49− const orgB = nextOrgId();
50− const alice = nextUserId();
51− const charlie = nextUserId();
52− const id = `sec_${uniq()}`;
53−
54− yield* asUser(alice, orgA, (client) =>
55− client.secrets.set({
56− params: { scopeId: ScopeId.make(orgA) },
57− payload: {
58− id: SecretId.make(id),
59− name: "Shared",
60− value: "alice-org-secret",
61− },
62− }),
63− );
64−
65− const charlieStatus = yield* asUser(charlie, orgB, (client) =>
66− client.secrets.status({
67− params: { scopeId: ScopeId.make(orgB), secretId: SecretId.make(id) },
68− }),
69− );
70− expect(charlieStatus.status).toBe("missing");
41+ it.effect("users in different orgs cannot read each other's org-scoped secrets", () =>
42+ Effect.gen(function* () {
43+ const orgA = nextOrgId();
44+ const orgB = nextOrgId();
45+ const alice = nextUserId();
46+ const charlie = nextUserId();
47+ const id = `sec_${uniq()}`;
48+
49+ yield* asUser(alice, orgA, (client) =>
50+ client.secrets.set({
51+ params: { scopeId: ScopeId.make(orgA) },
52+ payload: {
53+ id: SecretId.make(id),
54+ name: "Shared",
55+ value: "alice-org-secret",
56+ },
57+ }),
58+ );
7159
72− const charlieList = yield* asUser(charlie, orgB, (client) =>
73− client.secrets.list({ params: { scopeId: ScopeId.make(orgB) } }),
74− );
75− expect(charlieList.map((s) => s.id)).not.toContain(id);
60+ const charlieStatus = yield* asUser(charlie, orgB, (client) =>
61+ client.secrets.status({
62+ params: { scopeId: ScopeId.make(orgB), secretId: SecretId.make(id) },
63+ }),
64+ );
65+ expect(charlieStatus.status).toBe("missing");
7666
77− }),
67+ const charlieList = yield* asUser(charlie, orgB, (client) =>
68+ client.secrets.list({ params: { scopeId: ScopeId.make(orgB) } }),
69+ );
70+ expect(charlieList.map((s) => s.id)).not.toContain(id);
71+ }),
7872 );
7973
80− it.effect(
81− "users in same org cannot read each other's user-scoped secrets",
82− () =>
83− Effect.gen(function* () {
84− const orgId = nextOrgId();
85− const aliceId = nextUserId();
86− const bobId = nextUserId();
87− const id = `sec_${uniq()}`;
88−
89− // Alice writes at her per-user scope — where OAuth tokens land.
90− yield* asUser(aliceId, orgId, (client) =>
91− client.secrets.set({
92− params: { scopeId: ScopeId.make(testUserOrgScopeId(aliceId, orgId)) },
93− payload: {
94− id: SecretId.make(id),
95− name: "Alice's token",
96− value: "alice-token-value",
97− },
98− }),
99− );
74+ it.effect("users in same org cannot read each other's user-scoped secrets", () =>
75+ Effect.gen(function* () {
76+ const orgId = nextOrgId();
77+ const aliceId = nextUserId();
78+ const bobId = nextUserId();
79+ const id = `sec_${uniq()}`;
80+
81+ // Alice writes at her per-user scope — where OAuth tokens land.
82+ yield* asUser(aliceId, orgId, (client) =>
83+ client.secrets.set({
84+ params: { scopeId: ScopeId.make(testUserOrgScopeId(aliceId, orgId)) },
85+ payload: {
86+ id: SecretId.make(id),
87+ name: "Alice's token",
88+ value: "alice-token-value",
89+ },
90+ }),
91+ );
10092
101− // Bob is in the same org — his user-org scope differs. He should
102− // not see the token in a list.
103− const bobList = yield* asUser(bobId, orgId, (client) =>
104− client.secrets.list({
105− params: { scopeId: ScopeId.make(testUserOrgScopeId(bobId, orgId)) },
106− }),
107− );
108− expect(bobList.map((s) => s.id)).not.toContain(id);
93+ // Bob is in the same org — his user-org scope differs. He should
94+ // not see the token in a list.
95+ const bobList = yield* asUser(bobId, orgId, (client) =>
96+ client.secrets.list({
97+ params: { scopeId: ScopeId.make(testUserOrgScopeId(bobId, orgId)) },
98+ }),
99+ );
100+ expect(bobList.map((s) => s.id)).not.toContain(id);
109101
110− const bobStatus = yield* asUser(bobId, orgId, (client) =>
111− client.secrets.status({
112− params: {
113− scopeId: ScopeId.make(testUserOrgScopeId(bobId, orgId)),
114− secretId: SecretId.make(id),
115− },
116− }),
117− );
118− expect(bobStatus.status).toBe("missing");
102+ const bobStatus = yield* asUser(bobId, orgId, (client) =>
103+ client.secrets.status({
104+ params: {
105+ scopeId: ScopeId.make(testUserOrgScopeId(bobId, orgId)),
106+ secretId: SecretId.make(id),
107+ },
108+ }),
109+ );
110+ expect(bobStatus.status).toBe("missing");
119111
120− // And Alice still sees her own token metadata.
121− const aliceStatus = yield* asUser(aliceId, orgId, (client) =>
122− client.secrets.status({
123− params: {
124− scopeId: ScopeId.make(testUserOrgScopeId(aliceId, orgId)),
125− secretId: SecretId.make(id),
126− },
127− }),
128− );
129− expect(aliceStatus.status).toBe("resolved");
130− }),
112+ // And Alice still sees her own token metadata.
113+ const aliceStatus = yield* asUser(aliceId, orgId, (client) =>
114+ client.secrets.status({
115+ params: {
116+ scopeId: ScopeId.make(testUserOrgScopeId(aliceId, orgId)),
117+ secretId: SecretId.make(id),
118+ },
119+ }),
120+ );
121+ expect(aliceStatus.status).toBe("resolved");
122+ }),
131123 );
132124
133− it.effect(
134− "org-scoped secrets are visible to every user in that org",
135− () =>
136− Effect.gen(function* () {
137− const orgId = nextOrgId();
138− const adminId = nextUserId();
139− const memberId = nextUserId();
140− const id = `sec_${uniq()}`;
141−
142− yield* asUser(adminId, orgId, (client) =>
143− client.secrets.set({
144− params: { scopeId: ScopeId.make(orgId) },
145− payload: {
146− id: SecretId.make(id),
147− name: "Org API Key",
148− value: "shared-org-key",
149− },
150− }),
151− );
125+ it.effect("org-scoped secrets are visible to every user in that org", () =>
126+ Effect.gen(function* () {
127+ const orgId = nextOrgId();
128+ const adminId = nextUserId();
129+ const memberId = nextUserId();
130+ const id = `sec_${uniq()}`;
131+
132+ yield* asUser(adminId, orgId, (client) =>
133+ client.secrets.set({
134+ params: { scopeId: ScopeId.make(orgId) },
135+ payload: {
136+ id: SecretId.make(id),
137+ name: "Org API Key",
138+ value: "shared-org-key",
139+ },
140+ }),
141+ );
152142
153− const adminStatus = yield* asUser(adminId, orgId, (client) =>
154− client.secrets.status({
155− params: { scopeId: ScopeId.make(orgId), secretId: SecretId.make(id) },
156− }),
157− );
158− const memberStatus = yield* asUser(memberId, orgId, (client) =>
159− client.secrets.status({
160− params: { scopeId: ScopeId.make(orgId), secretId: SecretId.make(id) },
161− }),
162− );
163− expect(adminStatus.status).toBe("resolved");
164− expect(memberStatus.status).toBe("resolved");
165− }),
143+ const adminStatus = yield* asUser(adminId, orgId, (client) =>
144+ client.secrets.status({
145+ params: { scopeId: ScopeId.make(orgId), secretId: SecretId.make(id) },
146+ }),
147+ );
148+ const memberStatus = yield* asUser(memberId, orgId, (client) =>
149+ client.secrets.status({
150+ params: { scopeId: ScopeId.make(orgId), secretId: SecretId.make(id) },
151+ }),
152+ );
153+ expect(adminStatus.status).toBe("resolved");
154+ expect(memberStatus.status).toBe("resolved");
155+ }),
166156 );
167157
168− it.effect(
169− "same userId in different orgs gets distinct per-user scopes",
170− () =>
171− Effect.gen(function* () {
172− const userId = nextUserId();
173− const orgA = nextOrgId();
174− const orgB = nextOrgId();
175− const id = `sec_${uniq()}`;
176−
177− yield* asUser(userId, orgA, (client) =>
178− client.secrets.set({
179− params: { scopeId: ScopeId.make(testUserOrgScopeId(userId, orgA)) },
180− payload: {
181− id: SecretId.make(id),
182− name: "A token",
183− value: "value-in-a",
184− },
185− }),
186− );
158+ it.effect("same userId in different orgs gets distinct per-user scopes", () =>
159+ Effect.gen(function* () {
160+ const userId = nextUserId();
161+ const orgA = nextOrgId();
162+ const orgB = nextOrgId();
163+ const id = `sec_${uniq()}`;
164+
165+ yield* asUser(userId, orgA, (client) =>
166+ client.secrets.set({
167+ params: { scopeId: ScopeId.make(testUserOrgScopeId(userId, orgA)) },
168+ payload: {
169+ id: SecretId.make(id),
170+ name: "A token",
171+ value: "value-in-a",
172+ },
173+ }),
174+ );
187175
188− // Same user id, different org → distinct user-org scope. The
189− // secret written in org A must not be visible when the same user
190− // logs into org B.
191− const listInB = yield* asUser(userId, orgB, (client) =>
192− client.secrets.list({
193− params: { scopeId: ScopeId.make(testUserOrgScopeId(userId, orgB)) },
194− }),
195− );
196− expect(listInB.map((s) => s.id)).not.toContain(id);
176+ // Same user id, different org → distinct user-org scope. The
177+ // secret written in org A must not be visible when the same user
178+ // logs into org B.
179+ const listInB = yield* asUser(userId, orgB, (client) =>
180+ client.secrets.list({
181+ params: { scopeId: ScopeId.make(testUserOrgScopeId(userId, orgB)) },
182+ }),
183+ );
184+ expect(listInB.map((s) => s.id)).not.toContain(id);
197185
198− const statusInB = yield* asUser(userId, orgB, (client) =>
199− client.secrets.status({
200− params: {
201− scopeId: ScopeId.make(testUserOrgScopeId(userId, orgB)),
202− secretId: SecretId.make(id),
203− },
204− }),
205− );
206− expect(statusInB.status).toBe("missing");
186+ const statusInB = yield* asUser(userId, orgB, (client) =>
187+ client.secrets.status({
188+ params: {
189+ scopeId: ScopeId.make(testUserOrgScopeId(userId, orgB)),
190+ secretId: SecretId.make(id),
191+ },
192+ }),
193+ );
194+ expect(statusInB.status).toBe("missing");
207195
208− // Sanity: the original write is still visible under the org-A
209− // user-org scope.
210− const statusInA = yield* asUser(userId, orgA, (client) =>
211− client.secrets.status({
212− params: {
213− scopeId: ScopeId.make(testUserOrgScopeId(userId, orgA)),
214− secretId: SecretId.make(id),
215− },
216− }),
217− );
218− expect(statusInA.status).toBe("resolved");
219− }),
196+ // Sanity: the original write is still visible under the org-A
197+ // user-org scope.
198+ const statusInA = yield* asUser(userId, orgA, (client) =>
199+ client.secrets.status({
200+ params: {
201+ scopeId: ScopeId.make(testUserOrgScopeId(userId, orgA)),
202+ secretId: SecretId.make(id),
203+ },
204+ }),
205+ );
206+ expect(statusInA.status).toBe("resolved");
207+ }),
220208 );
221209
222210 it.effect("secrets.set rejects a scope outside the executor's stack", () =>
apps/cloud/src/services/__test-harness__/api-harness.ts+9−35
@@ -16,12 +16,7 @@ · 15 unmodified lines
1616
1717 import { Effect, Layer } from "effect";
1818 import { HttpApiBuilder, HttpApiClient, HttpApiSwagger } from "effect/unstable/httpapi";
19−import {
20− FetchHttpClient,
21− HttpRouter,
22− HttpServer,
23− HttpServerRequest,
24−} from "effect/unstable/http";
19+import { FetchHttpClient, HttpRouter, HttpServer, HttpServerRequest } from "effect/unstable/http";
2520
2621 import {
2722 ExecutionEngineService,
@@ -31,16 +26,8 @@ import { · 3 unmodified lines
3126 } from "@executor-js/api/server";
3227 import { createExecutionEngine } from "@executor-js/execution";
3328 import { makeQuickJsExecutor } from "@executor-js/runtime-quickjs";
34−import {
35− Scope,
36− ScopeId,
37− collectSchemas,
38− createExecutor,
39−} from "@executor-js/sdk";
40−import {
41− makePostgresAdapter,
42− makePostgresBlobStore,
43−} from "@executor-js/storage-postgres";
29+import { Scope, ScopeId, collectSchemas, createExecutor } from "@executor-js/sdk";
30+import { makePostgresAdapter, makePostgresBlobStore } from "@executor-js/storage-postgres";
4431 import { makeTestWorkOSVaultClient } from "@executor-js/plugin-workos-vault/testing";
4532
4633 import executorConfig from "../../../executor.config";
@@ -59,8 +46,7 @@ export const TEST_USER_HEADER = "x-test-user-id"; · 12 unmodified lines
5946 // Mirrors apps/cloud/src/services/executor.ts#createScopedExecutor — the
6047 // per-user scope id bakes in the org so the same user id in a different
6148 // org gets a distinct scope row.
62−const userOrgScopeId = (userId: string, orgId: string) =>
63− `user-org:${userId}:${orgId}`;
49+const userOrgScopeId = (userId: string, orgId: string) => `user-org:${userId}:${orgId}`;
6450
6551 // `asOrg(orgId, …)` callers don't care which specific user they are, only
6652 // that the executor has a valid user-org scope. We give each org a stable
@@ -77,11 +63,7 @@ const defaultUserFor = (orgId: string) => `default_user_${orgId}`; · 10 unmodified lines
7763 const fakeVault = makeTestWorkOSVaultClient();
7864 const testPlugins = executorConfig.plugins({ workosVaultClient: fakeVault });
7965
80−const createTestScopedExecutor = (
81− userId: string,
82− orgId: string,
83− orgName: string,
84−) =>
66+const createTestScopedExecutor = (userId: string, orgId: string, orgName: string) =>
8567 Effect.gen(function* () {
8668 const { db } = yield* DbService;
8769 const plugins = testPlugins;
@@ -188,10 +170,7 @@ export const fetchForOrg = (orgId: string): typeof globalThis.fetch => · 100 unmodified lines
188170 return handler(req);
189171 }) as typeof globalThis.fetch;
190172
191−export const fetchForUser = (
192− userId: string,
193− orgId: string,
194−): typeof globalThis.fetch =>
173+export const fetchForUser = (userId: string, orgId: string): typeof globalThis.fetch =>
195174 ((input: RequestInfo | URL, init?: RequestInit) => {
196175 const base = input instanceof Request ? input : new Request(input, init);
197176 const req = new Request(base, {
@@ -211,9 +190,7 @@ export const clientLayerForOrg = (orgId: string) => · 13 unmodified lines
211190
212191 export const clientLayerForUser = (userId: string, orgId: string) =>
213192 FetchHttpClient.layer.pipe(
214− Layer.provide(
215− Layer.succeed(FetchHttpClient.Fetch)(fetchForUser(userId, orgId)),
216− ),
193+ Layer.provide(Layer.succeed(FetchHttpClient.Fetch)(fetchForUser(userId, orgId))),
217194 );
218195
219196 // Constructs an HttpApiClient bound to the given org, hands it to `body`,
@@ -242,14 +219,11 @@ export const asUser = <A, E>( · 22 unmodified lines
242219 Effect.gen(function* () {
243220 const client = yield* HttpApiClient.make(ProtectedCloudApi, { baseUrl: TEST_BASE_URL });
244221 return yield* body(client);
245− }).pipe(
246− Effect.provide(clientLayerForUser(userId, orgId)),
247− ) as Effect.Effect<A, E>;
222+ }).pipe(Effect.provide(clientLayerForUser(userId, orgId))) as Effect.Effect<A, E>;
248223
249224 // Exposed so tests can build the same user-org scope id the harness uses
250225 // when writing at a specific user's scope.
251−export const testUserOrgScopeId = (userId: string, orgId: string) =>
252− userOrgScopeId(userId, orgId);
226+export const testUserOrgScopeId = (userId: string, orgId: string) => userOrgScopeId(userId, orgId);
253227
254228 // Re-exports so call sites don't need a second import.
255229 export { ProtectedCloudApi };
apps/cloud/src/services/db.schema.test.ts+1−4
@@ -24,10 +24,7 @@ import { combinedSchema } from "./db"; · 23 unmodified lines
2424
2525 describe("combinedSchema", () => {
2626 it("spreads every cloud + executor schema export", () => {
27− const expected = new Set([
28− ...Object.keys(cloudSchema),
29− ...Object.keys(executorSchema),
30− ]);
27+ const expected = new Set([...Object.keys(cloudSchema), ...Object.keys(executorSchema)]);
3128 for (const key of expected) {
3229 expect(combinedSchema, `combinedSchema missing "${key}"`).toHaveProperty(key);
3330 }
apps/cloud/src/services/db.ts+5−5
@@ -60,11 +60,11 @@ const makeSql = (): Sql => · 59 unmodified lines
6060 onnotice: () => undefined,
6161 });
6262
63−export class DbService extends Context.Service<
64− DbService,
65− DbServiceShape
66−>()("@executor-js/cloud/DbService") {
67− static Live = Layer.effect(this)(Effect.acquireRelease(
63+export class DbService extends Context.Service<DbService, DbServiceShape>()(
64+ "@executor-js/cloud/DbService",
65+) {
66+ static Live = Layer.effect(this)(
67+ Effect.acquireRelease(
6868 Effect.sync((): DbServiceShape => {
6969 const sql = makeSql();
7070 return { sql, db: drizzle(sql, { schema: combinedSchema }) as DrizzleDb };
apps/cloud/src/services/execution-stack.ts+3−5
@@ -20,11 +20,9 @@ export const makeExecutionStack = ( · 19 unmodified lines
2020 organizationName: string,
2121 ) =>
2222 Effect.gen(function* () {
23− const executor = yield* createScopedExecutor(
24− userId,
25− organizationId,
26− organizationName,
27− ).pipe(Effect.withSpan("McpSessionDO.createScopedExecutor"));
23+ const executor = yield* createScopedExecutor(userId, organizationId, organizationName).pipe(
24+ Effect.withSpan("McpSessionDO.createScopedExecutor"),
25+ );
2826 const codeExecutor = makeDynamicWorkerExecutor({ loader: env.LOADER });
2927 const autumn = yield* AutumnService;
3028 const engine = withExecutionUsageTracking(
apps/cloud/src/services/executor-schema.ts+430−327
@@ -1,350 +1,453 @@
1−import { pgTable, text, boolean, timestamp, bigint, jsonb, index, primaryKey } from "drizzle-orm/pg-core";
1+import {
2+ pgTable,
3+ text,
4+ boolean,
5+ timestamp,
6+ bigint,
7+ jsonb,
8+ index,
9+ primaryKey,
10+} from "drizzle-orm/pg-core";
211
3−export const source = pgTable("source", {
4− id: text('id').notNull(),
5− scope_id: text('scope_id').notNull(),
6− plugin_id: text('plugin_id').notNull(),
7− kind: text('kind').notNull(),
8− name: text('name').notNull(),
9− url: text('url'),
10− can_remove: boolean('can_remove').default(true).notNull(),
11− can_refresh: boolean('can_refresh').default(false).notNull(),
12− can_edit: boolean('can_edit').default(false).notNull(),
13− created_at: timestamp('created_at').notNull(),
14− updated_at: timestamp('updated_at').notNull()
15−}, (table) => [
16− primaryKey({ columns: [table.scope_id, table.id] }),
17− index("source_scope_id_idx").on(table.scope_id),
18− index("source_plugin_id_idx").on(table.plugin_id),
19−]);
12+export const source = pgTable(
13+ "source",
14+ {
15+ id: text("id").notNull(),
16+ scope_id: text("scope_id").notNull(),
17+ plugin_id: text("plugin_id").notNull(),
18+ kind: text("kind").notNull(),
19+ name: text("name").notNull(),
20+ url: text("url"),
21+ can_remove: boolean("can_remove").default(true).notNull(),
22+ can_refresh: boolean("can_refresh").default(false).notNull(),
23+ can_edit: boolean("can_edit").default(false).notNull(),
24+ created_at: timestamp("created_at").notNull(),
25+ updated_at: timestamp("updated_at").notNull(),
26+ },
27+ (table) => [
28+ primaryKey({ columns: [table.scope_id, table.id] }),
29+ index("source_scope_id_idx").on(table.scope_id),
30+ index("source_plugin_id_idx").on(table.plugin_id),
31+ ],
32+);
2033
21−export const tool = pgTable("tool", {
22− id: text('id').notNull(),
23− scope_id: text('scope_id').notNull(),
24− source_id: text('source_id').notNull(),
25− plugin_id: text('plugin_id').notNull(),
26− name: text('name').notNull(),
27− description: text('description').notNull(),
28− input_schema: jsonb('input_schema'),
29− output_schema: jsonb('output_schema'),
30− created_at: timestamp('created_at').notNull(),
31− updated_at: timestamp('updated_at').notNull()
32−}, (table) => [
33− primaryKey({ columns: [table.scope_id, table.id] }),
34− index("tool_scope_id_idx").on(table.scope_id),
35− index("tool_source_id_idx").on(table.source_id),
36− index("tool_plugin_id_idx").on(table.plugin_id),
37−]);
34+export const tool = pgTable(
35+ "tool",
36+ {
37+ id: text("id").notNull(),
38+ scope_id: text("scope_id").notNull(),
39+ source_id: text("source_id").notNull(),
40+ plugin_id: text("plugin_id").notNull(),
41+ name: text("name").notNull(),
42+ description: text("description").notNull(),
43+ input_schema: jsonb("input_schema"),
44+ output_schema: jsonb("output_schema"),
45+ created_at: timestamp("created_at").notNull(),
46+ updated_at: timestamp("updated_at").notNull(),
47+ },
48+ (table) => [
49+ primaryKey({ columns: [table.scope_id, table.id] }),
50+ index("tool_scope_id_idx").on(table.scope_id),
51+ index("tool_source_id_idx").on(table.source_id),
52+ index("tool_plugin_id_idx").on(table.plugin_id),
53+ ],
54+);
3855
39−export const definition = pgTable("definition", {
40− id: text('id').notNull(),
41− scope_id: text('scope_id').notNull(),
42− source_id: text('source_id').notNull(),
43− plugin_id: text('plugin_id').notNull(),
44− name: text('name').notNull(),
45− schema: jsonb('schema').notNull(),
46− created_at: timestamp('created_at').notNull()
47−}, (table) => [
48− primaryKey({ columns: [table.scope_id, table.id] }),
49− index("definition_scope_id_idx").on(table.scope_id),
50− index("definition_source_id_idx").on(table.source_id),
51− index("definition_plugin_id_idx").on(table.plugin_id),
52−]);
56+export const definition = pgTable(
57+ "definition",
58+ {
59+ id: text("id").notNull(),
60+ scope_id: text("scope_id").notNull(),
61+ source_id: text("source_id").notNull(),
62+ plugin_id: text("plugin_id").notNull(),
63+ name: text("name").notNull(),
64+ schema: jsonb("schema").notNull(),
65+ created_at: timestamp("created_at").notNull(),
66+ },
67+ (table) => [
68+ primaryKey({ columns: [table.scope_id, table.id] }),
69+ index("definition_scope_id_idx").on(table.scope_id),
70+ index("definition_source_id_idx").on(table.source_id),
71+ index("definition_plugin_id_idx").on(table.plugin_id),
72+ ],
73+);
5374
54−export const secret = pgTable("secret", {
55− id: text('id').notNull(),
56− scope_id: text('scope_id').notNull(),
57− name: text('name').notNull(),
58− provider: text('provider').notNull(),
59− owned_by_connection_id: text('owned_by_connection_id'),
60− created_at: timestamp('created_at').notNull()
61−}, (table) => [
62− primaryKey({ columns: [table.scope_id, table.id] }),
63− index("secret_scope_id_idx").on(table.scope_id),
64− index("secret_provider_idx").on(table.provider),
65− index("secret_owned_by_connection_id_idx").on(table.owned_by_connection_id),
66−]);
75+export const secret = pgTable(
76+ "secret",
77+ {
78+ id: text("id").notNull(),
79+ scope_id: text("scope_id").notNull(),
80+ name: text("name").notNull(),
81+ provider: text("provider").notNull(),
82+ owned_by_connection_id: text("owned_by_connection_id"),
83+ created_at: timestamp("created_at").notNull(),
84+ },
85+ (table) => [
86+ primaryKey({ columns: [table.scope_id, table.id] }),
87+ index("secret_scope_id_idx").on(table.scope_id),
88+ index("secret_provider_idx").on(table.provider),
89+ index("secret_owned_by_connection_id_idx").on(table.owned_by_connection_id),
90+ ],
91+);
6792
68−export const connection = pgTable("connection", {
69− id: text('id').notNull(),
70− scope_id: text('scope_id').notNull(),
71− provider: text('provider').notNull(),
72− identity_label: text('identity_label'),
73− access_token_secret_id: text('access_token_secret_id').notNull(),
74− refresh_token_secret_id: text('refresh_token_secret_id'),
75− expires_at: bigint('expires_at', { mode: 'number' }),
76− scope: text('scope'),
77− provider_state: jsonb('provider_state'),
78− created_at: timestamp('created_at').notNull(),
79− updated_at: timestamp('updated_at').notNull()
80−}, (table) => [
81− primaryKey({ columns: [table.scope_id, table.id] }),
82− index("connection_scope_id_idx").on(table.scope_id),
83− index("connection_provider_idx").on(table.provider),
84−]);
93+export const connection = pgTable(
94+ "connection",
95+ {
96+ id: text("id").notNull(),
97+ scope_id: text("scope_id").notNull(),
98+ provider: text("provider").notNull(),
99+ identity_label: text("identity_label"),
100+ access_token_secret_id: text("access_token_secret_id").notNull(),
101+ refresh_token_secret_id: text("refresh_token_secret_id"),
102+ expires_at: bigint("expires_at", { mode: "number" }),
103+ scope: text("scope"),
104+ provider_state: jsonb("provider_state"),
105+ created_at: timestamp("created_at").notNull(),
106+ updated_at: timestamp("updated_at").notNull(),
107+ },
108+ (table) => [
109+ primaryKey({ columns: [table.scope_id, table.id] }),
110+ index("connection_scope_id_idx").on(table.scope_id),
111+ index("connection_provider_idx").on(table.provider),
112+ ],
113+);
85114
86−export const oauth2_session = pgTable("oauth2_session", {
87− id: text('id').notNull(),
88− scope_id: text('scope_id').notNull(),
89− plugin_id: text('plugin_id').notNull(),
90− strategy: text('strategy').notNull(),
91− connection_id: text('connection_id').notNull(),
92− token_scope: text('token_scope').notNull(),
93− redirect_url: text('redirect_url').notNull(),
94− payload: jsonb('payload').notNull(),
95− expires_at: bigint('expires_at', { mode: 'number' }).notNull(),
96− created_at: timestamp('created_at').notNull()
97−}, (table) => [
98− primaryKey({ columns: [table.scope_id, table.id] }),
99− index("oauth2_session_scope_id_idx").on(table.scope_id),
100− index("oauth2_session_plugin_id_idx").on(table.plugin_id),
101− index("oauth2_session_connection_id_idx").on(table.connection_id),
102−]);
115+export const oauth2_session = pgTable(
116+ "oauth2_session",
117+ {
118+ id: text("id").notNull(),
119+ scope_id: text("scope_id").notNull(),
120+ plugin_id: text("plugin_id").notNull(),
121+ strategy: text("strategy").notNull(),
122+ connection_id: text("connection_id").notNull(),
123+ token_scope: text("token_scope").notNull(),
124+ redirect_url: text("redirect_url").notNull(),
125+ payload: jsonb("payload").notNull(),
126+ expires_at: bigint("expires_at", { mode: "number" }).notNull(),
127+ created_at: timestamp("created_at").notNull(),
128+ },
129+ (table) => [
130+ primaryKey({ columns: [table.scope_id, table.id] }),
131+ index("oauth2_session_scope_id_idx").on(table.scope_id),
132+ index("oauth2_session_plugin_id_idx").on(table.plugin_id),
133+ index("oauth2_session_connection_id_idx").on(table.connection_id),
134+ ],
135+);
103136
104−export const tool_policy = pgTable("tool_policy", {
105− id: text('id').notNull(),
106− scope_id: text('scope_id').notNull(),
107− pattern: text('pattern').notNull(),
108− action: text('action').notNull(),
109− position: text('position').notNull(),
110− created_at: timestamp('created_at').notNull(),
111− updated_at: timestamp('updated_at').notNull()
112−}, (table) => [
113− primaryKey({ columns: [table.scope_id, table.id] }),
114− index("tool_policy_scope_id_position_idx").on(table.scope_id, table.position),
115−]);
137+export const tool_policy = pgTable(
138+ "tool_policy",
139+ {
140+ id: text("id").notNull(),
141+ scope_id: text("scope_id").notNull(),
142+ pattern: text("pattern").notNull(),
143+ action: text("action").notNull(),
144+ position: text("position").notNull(),
145+ created_at: timestamp("created_at").notNull(),
146+ updated_at: timestamp("updated_at").notNull(),
147+ },
148+ (table) => [
149+ primaryKey({ columns: [table.scope_id, table.id] }),
150+ index("tool_policy_scope_id_position_idx").on(table.scope_id, table.position),
151+ ],
152+);
116153
117−export const openapi_source = pgTable("openapi_source", {
118− id: text('id').notNull(),
119− scope_id: text('scope_id').notNull(),
120− name: text('name').notNull(),
121− spec: text('spec').notNull(),
122− source_url: text('source_url'),
123− base_url: text('base_url'),
124− headers: jsonb('headers'),
125− oauth2: jsonb('oauth2')
126−}, (table) => [
127− primaryKey({ columns: [table.scope_id, table.id] }),
128− index("openapi_source_scope_id_idx").on(table.scope_id),
129−]);
154+export const openapi_source = pgTable(
155+ "openapi_source",
156+ {
157+ id: text("id").notNull(),
158+ scope_id: text("scope_id").notNull(),
159+ name: text("name").notNull(),
160+ spec: text("spec").notNull(),
161+ source_url: text("source_url"),
162+ base_url: text("base_url"),
163+ headers: jsonb("headers"),
164+ oauth2: jsonb("oauth2"),
165+ },
166+ (table) => [
167+ primaryKey({ columns: [table.scope_id, table.id] }),
168+ index("openapi_source_scope_id_idx").on(table.scope_id),
169+ ],
170+);
130171
131−export const openapi_operation = pgTable("openapi_operation", {
132− id: text('id').notNull(),
133− scope_id: text('scope_id').notNull(),
134− source_id: text('source_id').notNull(),
135− binding: jsonb('binding').notNull()
136−}, (table) => [
137− primaryKey({ columns: [table.scope_id, table.id] }),
138− index("openapi_operation_scope_id_idx").on(table.scope_id),
139− index("openapi_operation_source_id_idx").on(table.source_id),
140−]);
172+export const openapi_operation = pgTable(
173+ "openapi_operation",
174+ {
175+ id: text("id").notNull(),
176+ scope_id: text("scope_id").notNull(),
177+ source_id: text("source_id").notNull(),
178+ binding: jsonb("binding").notNull(),
179+ },
180+ (table) => [
181+ primaryKey({ columns: [table.scope_id, table.id] }),
182+ index("openapi_operation_scope_id_idx").on(table.scope_id),
183+ index("openapi_operation_source_id_idx").on(table.source_id),
184+ ],
185+);
141186
142−export const openapi_source_binding = pgTable("openapi_source_binding", {
143− id: text('id').primaryKey(),
144− source_id: text('source_id').notNull(),
145− source_scope_id: text('source_scope_id').notNull(),
146− target_scope_id: text('target_scope_id').notNull(),
147− slot: text('slot').notNull(),
148− kind: text('kind', { enum: ['secret', 'connection', 'text'] }).notNull(),
149− secret_id: text('secret_id'),
150− connection_id: text('connection_id'),
151− text_value: text('text_value'),
152− created_at: timestamp('created_at').notNull(),
153− updated_at: timestamp('updated_at').notNull()
154−}, (table) => [
155− index("openapi_source_binding_source_id_idx").on(table.source_id),
156− index("openapi_source_binding_source_scope_id_idx").on(table.source_scope_id),
157− index("openapi_source_binding_target_scope_id_idx").on(table.target_scope_id),
158− index("openapi_source_binding_slot_idx").on(table.slot),
159− index("openapi_source_binding_secret_id_idx").on(table.secret_id),
160− index("openapi_source_binding_connection_id_idx").on(table.connection_id),
161−]);
187+export const openapi_source_binding = pgTable(
188+ "openapi_source_binding",
189+ {
190+ id: text("id").primaryKey(),
191+ source_id: text("source_id").notNull(),
192+ source_scope_id: text("source_scope_id").notNull(),
193+ target_scope_id: text("target_scope_id").notNull(),
194+ slot: text("slot").notNull(),
195+ kind: text("kind", { enum: ["secret", "connection", "text"] }).notNull(),
196+ secret_id: text("secret_id"),
197+ connection_id: text("connection_id"),
198+ text_value: text("text_value"),
199+ created_at: timestamp("created_at").notNull(),
200+ updated_at: timestamp("updated_at").notNull(),
201+ },
202+ (table) => [
203+ index("openapi_source_binding_source_id_idx").on(table.source_id),
204+ index("openapi_source_binding_source_scope_id_idx").on(table.source_scope_id),
205+ index("openapi_source_binding_target_scope_id_idx").on(table.target_scope_id),
206+ index("openapi_source_binding_slot_idx").on(table.slot),
207+ index("openapi_source_binding_secret_id_idx").on(table.secret_id),
208+ index("openapi_source_binding_connection_id_idx").on(table.connection_id),
209+ ],
210+);
162211
163−export const openapi_source_query_param = pgTable("openapi_source_query_param", {
164− id: text('id').notNull(),
165− scope_id: text('scope_id').notNull(),
166− source_id: text('source_id').notNull(),
167− name: text('name').notNull(),
168− kind: text('kind', { enum: ['text', 'secret'] }).notNull(),
169− text_value: text('text_value'),
170− secret_id: text('secret_id'),
171− secret_prefix: text('secret_prefix')
172−}, (table) => [
173− primaryKey({ columns: [table.scope_id, table.id] }),
174− index("openapi_source_query_param_scope_id_idx").on(table.scope_id),
175− index("openapi_source_query_param_source_id_idx").on(table.source_id),
176− index("openapi_source_query_param_secret_id_idx").on(table.secret_id),
177−]);
212+export const openapi_source_query_param = pgTable(
213+ "openapi_source_query_param",
214+ {
215+ id: text("id").notNull(),
216+ scope_id: text("scope_id").notNull(),
217+ source_id: text("source_id").notNull(),
218+ name: text("name").notNull(),
219+ kind: text("kind", { enum: ["text", "secret"] }).notNull(),
220+ text_value: text("text_value"),
221+ secret_id: text("secret_id"),
222+ secret_prefix: text("secret_prefix"),
223+ },
224+ (table) => [
225+ primaryKey({ columns: [table.scope_id, table.id] }),
226+ index("openapi_source_query_param_scope_id_idx").on(table.scope_id),
227+ index("openapi_source_query_param_source_id_idx").on(table.source_id),
228+ index("openapi_source_query_param_secret_id_idx").on(table.secret_id),
229+ ],
230+);
178231
179−export const openapi_source_spec_fetch_header = pgTable("openapi_source_spec_fetch_header", {
180− id: text('id').notNull(),
181− scope_id: text('scope_id').notNull(),
182− source_id: text('source_id').notNull(),
183− name: text('name').notNull(),
184− kind: text('kind', { enum: ['text', 'secret'] }).notNull(),
185− text_value: text('text_value'),
186− secret_id: text('secret_id'),
187− secret_prefix: text('secret_prefix')
188−}, (table) => [
189− primaryKey({ columns: [table.scope_id, table.id] }),
190− index("openapi_source_spec_fetch_header_scope_id_idx").on(table.scope_id),
191− index("openapi_source_spec_fetch_header_source_id_idx").on(table.source_id),
192− index("openapi_source_spec_fetch_header_secret_id_idx").on(table.secret_id),
193−]);
232+export const openapi_source_spec_fetch_header = pgTable(
233+ "openapi_source_spec_fetch_header",
234+ {
235+ id: text("id").notNull(),
236+ scope_id: text("scope_id").notNull(),
237+ source_id: text("source_id").notNull(),
238+ name: text("name").notNull(),
239+ kind: text("kind", { enum: ["text", "secret"] }).notNull(),
240+ text_value: text("text_value"),
241+ secret_id: text("secret_id"),
242+ secret_prefix: text("secret_prefix"),
243+ },
244+ (table) => [
245+ primaryKey({ columns: [table.scope_id, table.id] }),
246+ index("openapi_source_spec_fetch_header_scope_id_idx").on(table.scope_id),
247+ index("openapi_source_spec_fetch_header_source_id_idx").on(table.source_id),
248+ index("openapi_source_spec_fetch_header_secret_id_idx").on(table.secret_id),
249+ ],
250+);
194251
195−export const openapi_source_spec_fetch_query_param = pgTable("openapi_source_spec_fetch_query_param", {
196− id: text('id').notNull(),
197− scope_id: text('scope_id').notNull(),
198− source_id: text('source_id').notNull(),
199− name: text('name').notNull(),
200− kind: text('kind', { enum: ['text', 'secret'] }).notNull(),
201− text_value: text('text_value'),
202− secret_id: text('secret_id'),
203− secret_prefix: text('secret_prefix')
204−}, (table) => [
205− primaryKey({ columns: [table.scope_id, table.id] }),
206− index("openapi_source_spec_fetch_query_param_scope_id_idx").on(table.scope_id),
207− index("openapi_source_spec_fetch_query_param_source_id_idx").on(table.source_id),
208− index("openapi_source_spec_fetch_query_param_secret_id_idx").on(table.secret_id),
209−]);
252+export const openapi_source_spec_fetch_query_param = pgTable(
253+ "openapi_source_spec_fetch_query_param",
254+ {
255+ id: text("id").notNull(),
256+ scope_id: text("scope_id").notNull(),
257+ source_id: text("source_id").notNull(),
258+ name: text("name").notNull(),
259+ kind: text("kind", { enum: ["text", "secret"] }).notNull(),
260+ text_value: text("text_value"),
261+ secret_id: text("secret_id"),
262+ secret_prefix: text("secret_prefix"),
263+ },
264+ (table) => [
265+ primaryKey({ columns: [table.scope_id, table.id] }),
266+ index("openapi_source_spec_fetch_query_param_scope_id_idx").on(table.scope_id),
267+ index("openapi_source_spec_fetch_query_param_source_id_idx").on(table.source_id),
268+ index("openapi_source_spec_fetch_query_param_secret_id_idx").on(table.secret_id),
269+ ],
270+);
210271
211−export const mcp_source = pgTable("mcp_source", {
212− id: text('id').notNull(),
213− scope_id: text('scope_id').notNull(),
214− name: text('name').notNull(),
215− config: jsonb('config').notNull(),
216− auth_kind: text('auth_kind', { enum: ['none', 'header', 'oauth2'] }).default("none").notNull(),
217− auth_header_name: text('auth_header_name'),
218− auth_secret_id: text('auth_secret_id'),
219− auth_secret_prefix: text('auth_secret_prefix'),
220− auth_connection_id: text('auth_connection_id'),
221− auth_client_id_secret_id: text('auth_client_id_secret_id'),
222− auth_client_secret_secret_id: text('auth_client_secret_secret_id'),
223− created_at: timestamp('created_at').notNull()
224−}, (table) => [
225− primaryKey({ columns: [table.scope_id, table.id] }),
226− index("mcp_source_scope_id_idx").on(table.scope_id),
227− index("mcp_source_auth_secret_id_idx").on(table.auth_secret_id),
228− index("mcp_source_auth_connection_id_idx").on(table.auth_connection_id),
229− index("mcp_source_auth_client_id_secret_id_idx").on(table.auth_client_id_secret_id),
230− index("mcp_source_auth_client_secret_secret_id_idx").on(table.auth_client_secret_secret_id),
231−]);
272+export const mcp_source = pgTable(
273+ "mcp_source",
274+ {
275+ id: text("id").notNull(),
276+ scope_id: text("scope_id").notNull(),
277+ name: text("name").notNull(),
278+ config: jsonb("config").notNull(),
279+ auth_kind: text("auth_kind", { enum: ["none", "header", "oauth2"] })
280+ .default("none")
281+ .notNull(),
282+ auth_header_name: text("auth_header_name"),
283+ auth_secret_id: text("auth_secret_id"),
284+ auth_secret_prefix: text("auth_secret_prefix"),
285+ auth_connection_id: text("auth_connection_id"),
286+ auth_client_id_secret_id: text("auth_client_id_secret_id"),
287+ auth_client_secret_secret_id: text("auth_client_secret_secret_id"),
288+ created_at: timestamp("created_at").notNull(),
289+ },
290+ (table) => [
291+ primaryKey({ columns: [table.scope_id, table.id] }),
292+ index("mcp_source_scope_id_idx").on(table.scope_id),
293+ index("mcp_source_auth_secret_id_idx").on(table.auth_secret_id),
294+ index("mcp_source_auth_connection_id_idx").on(table.auth_connection_id),
295+ index("mcp_source_auth_client_id_secret_id_idx").on(table.auth_client_id_secret_id),
296+ index("mcp_source_auth_client_secret_secret_id_idx").on(table.auth_client_secret_secret_id),
297+ ],
298+);
232299
233−export const mcp_source_header = pgTable("mcp_source_header", {
234− id: text('id').notNull(),
235− scope_id: text('scope_id').notNull(),
236− source_id: text('source_id').notNull(),
237− name: text('name').notNull(),
238− kind: text('kind', { enum: ['text', 'secret'] }).notNull(),
239− text_value: text('text_value'),
240− secret_id: text('secret_id'),
241− secret_prefix: text('secret_prefix')
242−}, (table) => [
243− primaryKey({ columns: [table.scope_id, table.id] }),
244− index("mcp_source_header_scope_id_idx").on(table.scope_id),
245− index("mcp_source_header_source_id_idx").on(table.source_id),
246− index("mcp_source_header_secret_id_idx").on(table.secret_id),
247−]);
300+export const mcp_source_header = pgTable(
301+ "mcp_source_header",
302+ {
303+ id: text("id").notNull(),
304+ scope_id: text("scope_id").notNull(),
305+ source_id: text("source_id").notNull(),
306+ name: text("name").notNull(),
307+ kind: text("kind", { enum: ["text", "secret"] }).notNull(),
308+ text_value: text("text_value"),
309+ secret_id: text("secret_id"),
310+ secret_prefix: text("secret_prefix"),
311+ },
312+ (table) => [
313+ primaryKey({ columns: [table.scope_id, table.id] }),
314+ index("mcp_source_header_scope_id_idx").on(table.scope_id),
315+ index("mcp_source_header_source_id_idx").on(table.source_id),
316+ index("mcp_source_header_secret_id_idx").on(table.secret_id),
317+ ],
318+);
248319
249−export const mcp_source_query_param = pgTable("mcp_source_query_param", {
250− id: text('id').notNull(),
251− scope_id: text('scope_id').notNull(),
252− source_id: text('source_id').notNull(),
253− name: text('name').notNull(),
254− kind: text('kind', { enum: ['text', 'secret'] }).notNull(),
255− text_value: text('text_value'),
256− secret_id: text('secret_id'),
257− secret_prefix: text('secret_prefix')
258−}, (table) => [
259− primaryKey({ columns: [table.scope_id, table.id] }),
260− index("mcp_source_query_param_scope_id_idx").on(table.scope_id),
261− index("mcp_source_query_param_source_id_idx").on(table.source_id),
262− index("mcp_source_query_param_secret_id_idx").on(table.secret_id),
263−]);
320+export const mcp_source_query_param = pgTable(
321+ "mcp_source_query_param",
322+ {
323+ id: text("id").notNull(),
324+ scope_id: text("scope_id").notNull(),
325+ source_id: text("source_id").notNull(),
326+ name: text("name").notNull(),
327+ kind: text("kind", { enum: ["text", "secret"] }).notNull(),
328+ text_value: text("text_value"),
329+ secret_id: text("secret_id"),
330+ secret_prefix: text("secret_prefix"),
331+ },
332+ (table) => [
333+ primaryKey({ columns: [table.scope_id, table.id] }),
334+ index("mcp_source_query_param_scope_id_idx").on(table.scope_id),
335+ index("mcp_source_query_param_source_id_idx").on(table.source_id),
336+ index("mcp_source_query_param_secret_id_idx").on(table.secret_id),
337+ ],
338+);
264339
265−export const mcp_binding = pgTable("mcp_binding", {
266− id: text('id').notNull(),
267− scope_id: text('scope_id').notNull(),
268− source_id: text('source_id').notNull(),
269− binding: jsonb('binding').notNull(),
270− created_at: timestamp('created_at').notNull()
271−}, (table) => [
272− primaryKey({ columns: [table.scope_id, table.id] }),
273− index("mcp_binding_scope_id_idx").on(table.scope_id),
274− index("mcp_binding_source_id_idx").on(table.source_id),
275−]);
340+export const mcp_binding = pgTable(
341+ "mcp_binding",
342+ {
343+ id: text("id").notNull(),
344+ scope_id: text("scope_id").notNull(),
345+ source_id: text("source_id").notNull(),
346+ binding: jsonb("binding").notNull(),
347+ created_at: timestamp("created_at").notNull(),
348+ },
349+ (table) => [
350+ primaryKey({ columns: [table.scope_id, table.id] }),
351+ index("mcp_binding_scope_id_idx").on(table.scope_id),
352+ index("mcp_binding_source_id_idx").on(table.source_id),
353+ ],
354+);
276355
277−export const graphql_source = pgTable("graphql_source", {
278− id: text('id').notNull(),
279− scope_id: text('scope_id').notNull(),
280− name: text('name').notNull(),
281− endpoint: text('endpoint').notNull(),
282− auth_kind: text('auth_kind', { enum: ['none', 'oauth2'] }).default("none").notNull(),
283− auth_connection_id: text('auth_connection_id')
284−}, (table) => [
285− primaryKey({ columns: [table.scope_id, table.id] }),
286− index("graphql_source_scope_id_idx").on(table.scope_id),
287− index("graphql_source_auth_connection_id_idx").on(table.auth_connection_id),
288−]);
356+export const graphql_source = pgTable(
357+ "graphql_source",
358+ {
359+ id: text("id").notNull(),
360+ scope_id: text("scope_id").notNull(),
361+ name: text("name").notNull(),
362+ endpoint: text("endpoint").notNull(),
363+ auth_kind: text("auth_kind", { enum: ["none", "oauth2"] })
364+ .default("none")
365+ .notNull(),
366+ auth_connection_id: text("auth_connection_id"),
367+ },
368+ (table) => [
369+ primaryKey({ columns: [table.scope_id, table.id] }),
370+ index("graphql_source_scope_id_idx").on(table.scope_id),
371+ index("graphql_source_auth_connection_id_idx").on(table.auth_connection_id),
372+ ],
373+);
289374
290−export const graphql_source_header = pgTable("graphql_source_header", {
291− id: text('id').notNull(),
292− scope_id: text('scope_id').notNull(),
293− source_id: text('source_id').notNull(),
294− name: text('name').notNull(),
295− kind: text('kind', { enum: ['text', 'secret'] }).notNull(),
296− text_value: text('text_value'),
297− secret_id: text('secret_id'),
298− secret_prefix: text('secret_prefix')
299−}, (table) => [
300− primaryKey({ columns: [table.scope_id, table.id] }),
301− index("graphql_source_header_scope_id_idx").on(table.scope_id),
302− index("graphql_source_header_source_id_idx").on(table.source_id),
303− index("graphql_source_header_secret_id_idx").on(table.secret_id),
304−]);
375+export const graphql_source_header = pgTable(
376+ "graphql_source_header",
377+ {
378+ id: text("id").notNull(),
379+ scope_id: text("scope_id").notNull(),
380+ source_id: text("source_id").notNull(),
381+ name: text("name").notNull(),
382+ kind: text("kind", { enum: ["text", "secret"] }).notNull(),
383+ text_value: text("text_value"),
384+ secret_id: text("secret_id"),
385+ secret_prefix: text("secret_prefix"),
386+ },
387+ (table) => [
388+ primaryKey({ columns: [table.scope_id, table.id] }),
389+ index("graphql_source_header_scope_id_idx").on(table.scope_id),
390+ index("graphql_source_header_source_id_idx").on(table.source_id),
391+ index("graphql_source_header_secret_id_idx").on(table.secret_id),
392+ ],
393+);
305394
306−export const graphql_source_query_param = pgTable("graphql_source_query_param", {
307− id: text('id').notNull(),
308− scope_id: text('scope_id').notNull(),
309− source_id: text('source_id').notNull(),
310− name: text('name').notNull(),
311− kind: text('kind', { enum: ['text', 'secret'] }).notNull(),
312− text_value: text('text_value'),
313− secret_id: text('secret_id'),
314− secret_prefix: text('secret_prefix')
315−}, (table) => [
316− primaryKey({ columns: [table.scope_id, table.id] }),
317− index("graphql_source_query_param_scope_id_idx").on(table.scope_id),
318− index("graphql_source_query_param_source_id_idx").on(table.source_id),
319− index("graphql_source_query_param_secret_id_idx").on(table.secret_id),
320−]);
395+export const graphql_source_query_param = pgTable(
396+ "graphql_source_query_param",
397+ {
398+ id: text("id").notNull(),
399+ scope_id: text("scope_id").notNull(),
400+ source_id: text("source_id").notNull(),
401+ name: text("name").notNull(),
402+ kind: text("kind", { enum: ["text", "secret"] }).notNull(),
403+ text_value: text("text_value"),
404+ secret_id: text("secret_id"),
405+ secret_prefix: text("secret_prefix"),
406+ },
407+ (table) => [
408+ primaryKey({ columns: [table.scope_id, table.id] }),
409+ index("graphql_source_query_param_scope_id_idx").on(table.scope_id),
410+ index("graphql_source_query_param_source_id_idx").on(table.source_id),
411+ index("graphql_source_query_param_secret_id_idx").on(table.secret_id),
412+ ],
413+);
321414
322−export const graphql_operation = pgTable("graphql_operation", {
323− id: text('id').notNull(),
324− scope_id: text('scope_id').notNull(),
325− source_id: text('source_id').notNull(),
326− binding: jsonb('binding').notNull()
327−}, (table) => [
328− primaryKey({ columns: [table.scope_id, table.id] }),
329− index("graphql_operation_scope_id_idx").on(table.scope_id),
330− index("graphql_operation_source_id_idx").on(table.source_id),
331−]);
415+export const graphql_operation = pgTable(
416+ "graphql_operation",
417+ {
418+ id: text("id").notNull(),
419+ scope_id: text("scope_id").notNull(),
420+ source_id: text("source_id").notNull(),
421+ binding: jsonb("binding").notNull(),
422+ },
423+ (table) => [
424+ primaryKey({ columns: [table.scope_id, table.id] }),
425+ index("graphql_operation_scope_id_idx").on(table.scope_id),
426+ index("graphql_operation_source_id_idx").on(table.source_id),
427+ ],
428+);
332429
333−export const workos_vault_metadata = pgTable("workos_vault_metadata", {
334− id: text('id').notNull(),
335− scope_id: text('scope_id').notNull(),
336− name: text('name').notNull(),
337− purpose: text('purpose'),
338− created_at: timestamp('created_at').notNull()
339−}, (table) => [
340− primaryKey({ columns: [table.scope_id, table.id] }),
341− index("workos_vault_metadata_scope_id_idx").on(table.scope_id),
342−]);
430+export const workos_vault_metadata = pgTable(
431+ "workos_vault_metadata",
432+ {
433+ id: text("id").notNull(),
434+ scope_id: text("scope_id").notNull(),
435+ name: text("name").notNull(),
436+ purpose: text("purpose"),
437+ created_at: timestamp("created_at").notNull(),
438+ },
439+ (table) => [
440+ primaryKey({ columns: [table.scope_id, table.id] }),
441+ index("workos_vault_metadata_scope_id_idx").on(table.scope_id),
442+ ],
443+);
343444
344−export const blob = pgTable("blob", {
345− namespace: text('namespace').notNull(),
346− key: text('key').notNull(),
347− value: text('value').notNull()
348−}, (table) => [
349− primaryKey({ columns: [table.namespace, table.key] }),
350−]);
445+export const blob = pgTable(
446+ "blob",
447+ {
448+ namespace: text("namespace").notNull(),
449+ key: text("key").notNull(),
450+ value: text("value").notNull(),
451+ },
452+ (table) => [primaryKey({ columns: [table.namespace, table.key] })],
453+);
apps/cloud/src/services/executor.ts+2−10
@@ -10,16 +10,8 @@ · 9 unmodified lines
1010
1111 import { Effect } from "effect";
1212
13−import {
14− Scope,
15− ScopeId,
16− collectSchemas,
17− createExecutor,
18−} from "@executor-js/sdk";
19−import {
20− makePostgresAdapter,
21− makePostgresBlobStore,
22−} from "@executor-js/storage-postgres";
13+import { Scope, ScopeId, collectSchemas, createExecutor } from "@executor-js/sdk";
14+import { makePostgresAdapter, makePostgresBlobStore } from "@executor-js/storage-postgres";
2315
2416 import { env } from "cloudflare:workers";
2517 import executorConfig from "../../executor.config";
apps/cloud/src/services/mcp-oauth.node.test.ts+11−34
@@ -48,21 +48,15 @@ const RegistrationBody = Schema.Struct({ · 47 unmodified lines
4848 grant_types: Schema.optional(Schema.Array(Schema.String)),
4949 response_types: Schema.optional(Schema.Array(Schema.String)),
5050 });
51−const decodeRegistrationBody = Schema.decodeUnknownOption(
52− Schema.fromJsonString(RegistrationBody),
53−);
51+const decodeRegistrationBody = Schema.decodeUnknownOption(Schema.fromJsonString(RegistrationBody));
5452
5553 const startFakeServer = async (): Promise<FakeServer> => {
5654 const clients = new Map<string, { redirect_uris: readonly string[] }>();
57− const codes = new Map<
58− string,
59− { readonly clientId: string; readonly codeChallenge: string }
60− >();
55+ const codes = new Map<string, { readonly clientId: string; readonly codeChallenge: string }>();
6156 const accessTokens = new Map<string, { readonly refresh: string }>();
6257 const refreshTokens = new Map<string, string>();
6358 let seq = 0;
64− const next = (p: string) =>
65− `${p}_${++seq}_${randomBytes(6).toString("hex")}`;
59+ const next = (p: string) => `${p}_${++seq}_${randomBytes(6).toString("hex")}`;
6660 let registrations = 0;
6761 let tokenCalls = 0;
6862
@@ -76,16 +70,10 @@ const startFakeServer = async (): Promise<FakeServer> => { · 7 unmodified lines
7670
7771 const server: Server = createServer(async (req, res) => {
7872 const url = new URL(req.url!, `http://${req.headers.host}`);
79− const send = (
80− status: number,
81− body: unknown,
82− headers: Record<string, string> = {},
83− ) => {
84− const payload =
85− typeof body === "string" ? body : JSON.stringify(body);
73+ const send = (status: number, body: unknown, headers: Record<string, string> = {}) => {
74+ const payload = typeof body === "string" ? body : JSON.stringify(body);
8675 res.writeHead(status, {
87− "content-type":
88− typeof body === "string" ? "text/plain" : "application/json",
76+ "content-type": typeof body === "string" ? "text/plain" : "application/json",
8977 ...headers,
9078 });
9179 res.end(payload);
@@ -130,10 +118,7 @@ const startFakeServer = async (): Promise<FakeServer> => { · 38 unmodified lines
130118 client_id: clientId,
131119 client_id_issued_at: Math.floor(Date.now() / 1000),
132120 redirect_uris: parsed.redirect_uris ?? [],
133− grant_types: parsed.grant_types ?? [
134− "authorization_code",
135− "refresh_token",
136− ],
121+ grant_types: parsed.grant_types ?? ["authorization_code", "refresh_token"],
137122 response_types: parsed.response_types ?? ["code"],
138123 token_endpoint_auth_method: "none",
139124 });
@@ -171,9 +156,7 @@ const startFakeServer = async (): Promise<FakeServer> => { · 31 unmodified lines
171156 const record = codes.get(code);
172157 if (!record) return send(400, { error: "invalid_grant" });
173158 codes.delete(code);
174− const computed = createHash("sha256")
175− .update(verifier)
176− .digest("base64url");
159+ const computed = createHash("sha256").update(verifier).digest("base64url");
177160 if (computed !== record.codeChallenge) {
178161 return send(400, { error: "invalid_grant" });
179162 }
@@ -229,9 +212,7 @@ const startFakeServer = async (): Promise<FakeServer> => { · 49 unmodified lines
229212 }
230213 });
231214
232− await new Promise<void>((resolve) =>
233− server.listen(0, "127.0.0.1", () => resolve()),
234− );
215+ await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", () => resolve()));
235216 const address = server.address() as AddressInfo;
236217
237218 return {
@@ -352,9 +333,7 @@ describe("mcp oauth end-to-end (node pool, real OAuth + MCP server)", () => { · 114 unmodified lines
352333 },
353334 }),
354335 );
355− const redirA = yield* Effect.promise(() =>
356− followAuthorize(startedA.authorizationUrl!),
357− );
336+ const redirA = yield* Effect.promise(() => followAuthorize(startedA.authorizationUrl!));
358337 const completedA = yield* asUser(userA, orgId, (client) =>
359338 client.oauth.complete({
360339 params: { scopeId: scopeA },
@@ -377,9 +356,7 @@ describe("mcp oauth end-to-end (node pool, real OAuth + MCP server)", () => { · 16 unmodified lines
377356 },
378357 }),
379358 );
380− const redirB = yield* Effect.promise(() =>
381− followAuthorize(startedB.authorizationUrl!),
382− );
359+ const redirB = yield* Effect.promise(() => followAuthorize(startedB.authorizationUrl!));
383360 const completedB = yield* asUser(userB, orgId, (client) =>
384361 client.oauth.complete({
385362 params: { scopeId: scopeB },
apps/cloud/src/services/mcp-worker-transport.test.ts+1−3
@@ -58,9 +58,7 @@ describe("JsonRpcRequestIdQueue", () => { · 57 unmodified lines
5858 queue
5959 .run(jsonRpcRequest({ jsonrpc: "2.0", id: 2, method: "tools/call" }), async () => "done")
6060 .then((v) => ({ kind: "settled" as const, v })),
61− new Promise<{ kind: "blocked" }>((r) =>
62− setTimeout(() => r({ kind: "blocked" }), 200),
63− ),
61+ new Promise<{ kind: "blocked" }>((r) => setTimeout(() => r({ kind: "blocked" }), 200)),
6462 ]);
6563
6664 expect(otherDone.kind).toBe("settled");
apps/cloud/src/services/mcp-worker-transport.ts+2−6
@@ -64,9 +64,7 @@ const extractJsonRpcRequestIdKeys = async (request: Request): Promise<ReadonlyAr · 63 unmodified lines
6464 const contentType = request.headers.get("content-type") ?? "";
6565 if (!contentType.includes("application/json")) return [];
6666
67− const parsed = await Effect.runPromiseExit(
68− Effect.tryPromise(() => request.clone().json()),
69− );
67+ const parsed = await Effect.runPromiseExit(Effect.tryPromise(() => request.clone().json()));
7068 if (Exit.isFailure(parsed)) {
7169 return [];
7270 }
@@ -192,8 +190,6 @@ export const makeMcpWorkerTransport = ( · 119 unmodified lines
192190 try: () => transport.close(),
193191 catch: (cause) => new McpWorkerTransportError({ cause }),
194192 }),
195− ).pipe(
196− Effect.withSpan("mcp.worker_transport.close"),
197− ),
193+ ).pipe(Effect.withSpan("mcp.worker_transport.close")),
198194 } satisfies McpWorkerTransport;
199195 }).pipe(Effect.withSpan("mcp.worker_transport.make"));
apps/cloud/src/services/slack.ts+8−18
@@ -14,10 +14,7 @@ export class SlackError extends Data.TaggedError("SlackError")<{ · 13 unmodified lines
1414 }> {}
1515
1616 export type ISlackService = Readonly<{
17− createConnectInvite: (input: {
18− email: string;
19− organization?: string;
20− }) => Effect.Effect<
17+ createConnectInvite: (input: { email: string; organization?: string }) => Effect.Effect<
2118 {
2219 channel: { id: string; name: string };
2320 invite: { invite_id: string; url: string };
@@ -46,9 +43,7 @@ const make = Effect.sync(() => { · 22 unmodified lines
4643
4744 if (!token) {
4845 const notConfigured = (method: string) =>
49− Effect.fail(
50− new SlackError({ method, error: "SLACK_BOT_TOKEN is not configured" }),
51− );
46+ Effect.fail(new SlackError({ method, error: "SLACK_BOT_TOKEN is not configured" }));
5247 return {
5348 createConnectInvite: () => notConfigured("createConnectInvite"),
5449 } satisfies ISlackService;
@@ -83,20 +78,17 @@ const make = Effect.sync(() => { · 28 unmodified lines
8378 Effect.withSpan(`slack.${method}`),
8479 );
8580
86− const createConnectInvite: ISlackService["createConnectInvite"] = ({
87− email,
88− organization,
89− }) =>
81+ const createConnectInvite: ISlackService["createConnectInvite"] = ({ email, organization }) =>
9082 Effect.gen(function* () {
9183 // Slack channel names: lowercase, no spaces, max 80 chars, unique per
9284 // workspace. Use the org name when available; fall back to the email
9385 // local-part for unauthenticated submissions.
9486 const baseName = slugify(organization ?? email).slice(0, 80) || "contact";
9587 const tryCreate = (n: string) =>
96− call<SlackResponse & { channel: { id: string; name: string } }>(
97− "conversations.create",
98− { name: n, is_private: false },
99− );
88+ call<SlackResponse & { channel: { id: string; name: string } }>("conversations.create", {
89+ name: n,
90+ is_private: false,
91+ });
10092
10193 const created = yield* tryCreate(baseName).pipe(
10294 Effect.catchTag("SlackError", (err) =>
@@ -125,9 +117,7 @@ const make = Effect.sync(() => { · 22 unmodified lines
125117 channel: { id: channel.id, name: channel.name },
126118 invite: { invite_id: invite.invite_id, url: invite.url },
127119 };
128− }).pipe(
129− Effect.withSpan("slack.createConnectInvite", { attributes: { "slack.email": email } }),
130− );
120+ }).pipe(Effect.withSpan("slack.createConnectInvite", { attributes: { "slack.email": email } }));
131121
132122 return { createConnectInvite } satisfies ISlackService;
133123 });
apps/cloud/src/services/sources-api.node.test.ts+7−13
@@ -14,11 +14,7 @@ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/ · 13 unmodified lines
1414
1515 import { ScopeId, SecretId } from "@executor-js/sdk";
1616
17−import {
18− asOrg,
19− asUser,
20− testUserOrgScopeId,
21−} from "./__test-harness__/api-harness";
17+import { asOrg, asUser, testUserOrgScopeId } from "./__test-harness__/api-harness";
2218
2319 const MINIMAL_OPENAPI_SPEC = JSON.stringify({
2420 openapi: "3.0.0",
@@ -108,7 +104,10 @@ const startEchoServer = () => { · 83 unmodified lines
108104
109105 return new Promise<{
110106 readonly baseUrl: string;
111− readonly requests: () => ReadonlyArray<{ readonly path: string; readonly suffix: string | null }>;
107+ readonly requests: () => ReadonlyArray<{
108+ readonly path: string;
109+ readonly suffix: string | null;
110+ }>;
112111 readonly close: () => Promise<void>;
113112 }>((resolveServer) => {
114113 server.listen(0, "127.0.0.1", () => {
@@ -225,10 +224,7 @@ const startGraphqlServer = () => { · 110 unmodified lines
225224 };
226225
227226 const createCloudMcpServer = () => {
228− const server = new McpServer(
229− { name: "cloud-e2e-mcp", version: "1.0.0" },
230− { capabilities: {} },
231− );
227+ const server = new McpServer({ name: "cloud-e2e-mcp", version: "1.0.0" }, { capabilities: {} });
232228
233229 server.registerTool(
234230 "simple_echo",
@@ -831,9 +827,7 @@ describe("sources api (HTTP)", () => { · 596 unmodified lines
831827 const sources = yield* asOrg(orgId, (client) =>
832828 client.sources.list({ params: { scopeId: ScopeId.make(orgId) } }),
833829 );
834− expect(sources.find((source) => source.id === namespace)?.scopeId).toBe(
835− ScopeId.make(orgId),
836− );
830+ expect(sources.find((source) => source.id === namespace)?.scopeId).toBe(ScopeId.make(orgId));
837831 }),
838832 );
839833
apps/cloud/src/services/telemetry.ts+1−4
@@ -49,10 +49,7 @@ import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http/build · 48 unmodified lines
4949 import { resourceFromAttributes } from "@opentelemetry/resources";
5050 import { SimpleSpanProcessor } from "@opentelemetry/sdk-trace-base";
5151 import { WebTracerProvider } from "@opentelemetry/sdk-trace-web";
52−import {
53− ATTR_SERVICE_NAME,
54− ATTR_SERVICE_VERSION,
55−} from "@opentelemetry/semantic-conventions";
52+import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic-conventions";
5653 import { env } from "cloudflare:workers";
5754 import { Effect, Layer } from "effect";
5855
apps/cloud/src/test-worker.ts+5−6
@@ -53,8 +53,7 @@ const TestMcpAuthLive = Layer.succeed(McpAuth)({ · 52 unmodified lines
5353 });
5454
5555 const TestMcpOrganizationAuthLive = Layer.succeed(McpOrganizationAuth)({
56− authorize: (_accountId, organizationId) =>
57− Effect.succeed(!organizationId.startsWith("revoked_")),
56+ authorize: (_accountId, organizationId) => Effect.succeed(!organizationId.startsWith("revoked_")),
5857 });
5958
6059 // ---------------------------------------------------------------------------
@@ -112,14 +111,14 @@ const handleSeedOrg = async ( · 51 unmodified lines
112111 // OTLPTraceExporter — not Durable-Object-specific) to stand in.
113112 const testMcpFetch = HttpEffect.toWebHandler(
114113 mcpApp.pipe(
115− Effect.provide(
116− Layer.mergeAll(TestMcpAuthLive, TestMcpOrganizationAuthLive, DoTelemetryLive),
117− ),
114+ Effect.provide(Layer.mergeAll(TestMcpAuthLive, TestMcpOrganizationAuthLive, DoTelemetryLive)),
118115 ),
119116 );
120117
121118 const realAuthMcpFetch = HttpEffect.toWebHandler(
122− mcpApp.pipe(Effect.provide(Layer.mergeAll(McpAuthLive, McpOrganizationAuthLive, DoTelemetryLive))),
119+ mcpApp.pipe(
120+ Effect.provide(Layer.mergeAll(McpAuthLive, McpOrganizationAuthLive, DoTelemetryLive)),
121+ ),
123122 );
124123
125124 export default {
apps/cloud/src/web/pages/onboarding.tsx+4−8
@@ -69,9 +69,7 @@ export const OnboardingPage = () => { · 68 unmodified lines
6969 };
7070
7171 const suggestedName =
72− auth.status === "authenticated" &&
73− auth.user.name != null &&
74− auth.user.name.trim() !== ""
72+ auth.status === "authenticated" && auth.user.name != null && auth.user.name.trim() !== ""
7573 ? `${auth.user.name}'s Organization`
7674 : "";
7775
@@ -80,7 +78,8 @@ export const OnboardingPage = () => { · 2 unmodified lines
8078 onSuccess: () => {},
8179 });
8280
83− const isLoading = AsyncResult.isInitial(invitationsResult) || AsyncResult.isWaiting(invitationsResult);
81+ const isLoading =
82+ AsyncResult.isInitial(invitationsResult) || AsyncResult.isWaiting(invitationsResult);
8483 const invitations = AsyncResult.match(invitationsResult, {
8584 onInitial: () => [] as readonly PendingInvitation[],
8685 onFailure: () => [] as readonly PendingInvitation[],
@@ -130,10 +129,7 @@ export const OnboardingPage = () => { · 43 unmodified lines
130129 )}
131130
132131 {!isLoading && (count === 0 || sole || count > 1) && (
133− <CreateOrgSection
134− isPrimary={count === 0}
135− form={form}
136− />
132+ <CreateOrgSection isPrimary={count === 0} form={form} />
137133 )}
138134
139135 <footer className="flex items-center justify-center">
apps/cloud/vite.config.ts+1−4
@@ -19,10 +19,7 @@ const devCrashGuard = (): Plugin => { · 18 unmodified lines
1919 if (installed) return;
2020 installed = true;
2121 process.on("uncaughtException", (err, origin) => {
22− console.error(
23− `[dev-crash-guard] uncaughtException (origin=${origin}):`,
24− err,
25− );
22+ console.error(`[dev-crash-guard] uncaughtException (origin=${origin}):`, err);
2623 });
2724 process.on("unhandledRejection", (reason, promise) => {
2825 console.error("[dev-crash-guard] unhandledRejection:", reason, promise);
apps/cloud/wrangler.miniflare.jsonc+4−4
@@ -16,11 +16,11 @@ · 15 unmodified lines
1616 "WORKOS_COOKIE_PASSWORD": "test_cookie_password_at_least_32_chars!",
1717 "MCP_AUTHKIT_DOMAIN": "https://test-authkit.example.com",
1818 "MCP_RESOURCE_ORIGIN": "https://test-resource.example.com",
19− "NODE_ENV": "test"
19+ "NODE_ENV": "test",
2020 },
2121 "durable_objects": {
22− "bindings": [{"name": "MCP_SESSION", "class_name": "McpSessionDO"}]
22+ "bindings": [{ "name": "MCP_SESSION", "class_name": "McpSessionDO" }],
2323 },
24− "worker_loaders": [{"binding": "LOADER"}],
25− "migrations": [{"tag": "v1", "new_classes": ["McpSessionDO"]}]
24+ "worker_loaders": [{ "binding": "LOADER" }],
25+ "migrations": [{ "tag": "v1", "new_classes": ["McpSessionDO"] }],
2626 }
apps/cloud/wrangler.test.jsonc+4−4
@@ -12,11 +12,11 @@ · 11 unmodified lines
1212 "WORKOS_COOKIE_PASSWORD": "test_cookie_password_at_least_32_chars!",
1313 "MCP_AUTHKIT_DOMAIN": "https://test-authkit.example.com",
1414 "MCP_RESOURCE_ORIGIN": "https://test-resource.example.com",
15− "NODE_ENV": "test"
15+ "NODE_ENV": "test",
1616 },
1717 "durable_objects": {
18− "bindings": [{"name": "MCP_SESSION", "class_name": "McpSessionDO"}]
18+ "bindings": [{ "name": "MCP_SESSION", "class_name": "McpSessionDO" }],
1919 },
20− "worker_loaders": [{"binding": "LOADER"}],
21− "migrations": [{"tag": "v1", "new_classes": ["McpSessionDO"]}]
20+ "worker_loaders": [{ "binding": "LOADER" }],
21+ "migrations": [{ "tag": "v1", "new_classes": ["McpSessionDO"] }],
2222 }
apps/desktop/src/main.ts+10−7
@@ -99,11 +99,7 @@ const installCli = (): void => { · 98 unmodified lines
9999 // Patch shell profiles with PATH
100100 if (process.platform === "win32") {
101101 // Add bin dir to the user PATH via registry so new terminals pick it up
102− const result = spawn(
103− "reg",
104− ["query", "HKCU\\Environment", "/v", "Path"],
105− { stdio: "pipe" },
106− );
102+ const result = spawn("reg", ["query", "HKCU\\Environment", "/v", "Path"], { stdio: "pipe" });
107103 let out = "";
108104 result.stdout?.on("data", (d: Buffer) => (out += d.toString()));
109105 result.on("close", (code) => {
@@ -128,8 +124,15 @@ const installCli = (): void => { · 18 unmodified lines
128124 if (!current.toLowerCase().includes(CLI_BIN_DIR.toLowerCase())) {
129125 const updated = current ? `${current};${CLI_BIN_DIR}` : CLI_BIN_DIR;
130126 spawn("reg", [
131− "add", "HKCU\\Environment", "/v", "Path",
132− "/t", "REG_EXPAND_SZ", "/d", updated, "/f",
127+ "add",
128+ "HKCU\\Environment",
129+ "/v",
130+ "Path",
131+ "/t",
132+ "REG_EXPAND_SZ",
133+ "/d",
134+ updated,
135+ "/f",
133136 ]);
134137 }
135138 });
apps/local/drizzle/meta/0000_snapshot.json+42−140
@@ -33,10 +33,7 @@ · 32 unmodified lines
3333 "foreignKeys": {},
3434 "compositePrimaryKeys": {
3535 "blob_namespace_key_pk": {
36− "columns": [
37− "namespace",
38− "key"
39− ],
36+ "columns": ["namespace", "key"],
4037 "name": "blob_namespace_key_pk"
4138 }
4239 },
@@ -99,33 +96,24 @@ · 56 unmodified lines
9996 "indexes": {
10097 "definition_scope_id_idx": {
10198 "name": "definition_scope_id_idx",
102− "columns": [
103− "scope_id"
104− ],
99+ "columns": ["scope_id"],
105100 "isUnique": false
106101 },
107102 "definition_source_id_idx": {
108103 "name": "definition_source_id_idx",
109− "columns": [
110− "source_id"
111− ],
104+ "columns": ["source_id"],
112105 "isUnique": false
113106 },
114107 "definition_plugin_id_idx": {
115108 "name": "definition_plugin_id_idx",
116− "columns": [
117− "plugin_id"
118− ],
109+ "columns": ["plugin_id"],
119110 "isUnique": false
120111 }
121112 },
122113 "foreignKeys": {},
123114 "compositePrimaryKeys": {
124115 "definition_scope_id_id_pk": {
125− "columns": [
126− "scope_id",
127− "id"
128− ],
116+ "columns": ["scope_id", "id"],
129117 "name": "definition_scope_id_id_pk"
130118 }
131119 },
@@ -174,26 +162,19 @@ · 42 unmodified lines
174162 "indexes": {
175163 "google_discovery_binding_scope_id_idx": {
176164 "name": "google_discovery_binding_scope_id_idx",
177− "columns": [
178− "scope_id"
179− ],
165+ "columns": ["scope_id"],
180166 "isUnique": false
181167 },
182168 "google_discovery_binding_source_id_idx": {
183169 "name": "google_discovery_binding_source_id_idx",
184− "columns": [
185− "source_id"
186− ],
170+ "columns": ["source_id"],
187171 "isUnique": false
188172 }
189173 },
190174 "foreignKeys": {},
191175 "compositePrimaryKeys": {
192176 "google_discovery_binding_scope_id_id_pk": {
193− "columns": [
194− "scope_id",
195− "id"
196− ],
177+ "columns": ["scope_id", "id"],
197178 "name": "google_discovery_binding_scope_id_id_pk"
198179 }
199180 },
@@ -235,19 +216,14 @@ · 35 unmodified lines
235216 "indexes": {
236217 "google_discovery_oauth_session_scope_id_idx": {
237218 "name": "google_discovery_oauth_session_scope_id_idx",
238− "columns": [
239− "scope_id"
240− ],
219+ "columns": ["scope_id"],
241220 "isUnique": false
242221 }
243222 },
244223 "foreignKeys": {},
245224 "compositePrimaryKeys": {
246225 "google_discovery_oauth_session_scope_id_id_pk": {
247− "columns": [
248− "scope_id",
249− "id"
250− ],
226+ "columns": ["scope_id", "id"],
251227 "name": "google_discovery_oauth_session_scope_id_id_pk"
252228 }
253229 },
@@ -303,19 +279,14 @@ · 49 unmodified lines
303279 "indexes": {
304280 "google_discovery_source_scope_id_idx": {
305281 "name": "google_discovery_source_scope_id_idx",
306− "columns": [
307− "scope_id"
308− ],
282+ "columns": ["scope_id"],
309283 "isUnique": false
310284 }
311285 },
312286 "foreignKeys": {},
313287 "compositePrimaryKeys": {
314288 "google_discovery_source_scope_id_id_pk": {
315− "columns": [
316− "scope_id",
317− "id"
318− ],
289+ "columns": ["scope_id", "id"],
319290 "name": "google_discovery_source_scope_id_id_pk"
320291 }
321292 },
@@ -357,26 +328,19 @@ · 35 unmodified lines
357328 "indexes": {
358329 "graphql_operation_scope_id_idx": {
359330 "name": "graphql_operation_scope_id_idx",
360− "columns": [
361− "scope_id"
362− ],
331+ "columns": ["scope_id"],
363332 "isUnique": false
364333 },
365334 "graphql_operation_source_id_idx": {
366335 "name": "graphql_operation_source_id_idx",
367− "columns": [
368− "source_id"
369− ],
336+ "columns": ["source_id"],
370337 "isUnique": false
371338 }
372339 },
373340 "foreignKeys": {},
374341 "compositePrimaryKeys": {
375342 "graphql_operation_scope_id_id_pk": {
376− "columns": [
377− "scope_id",
378− "id"
379− ],
343+ "columns": ["scope_id", "id"],
380344 "name": "graphql_operation_scope_id_id_pk"
381345 }
382346 },
@@ -425,19 +389,14 @@ · 42 unmodified lines
425389 "indexes": {
426390 "graphql_source_scope_id_idx": {
427391 "name": "graphql_source_scope_id_idx",
428− "columns": [
429− "scope_id"
430− ],
392+ "columns": ["scope_id"],
431393 "isUnique": false
432394 }
433395 },
434396 "foreignKeys": {},
435397 "compositePrimaryKeys": {
436398 "graphql_source_scope_id_id_pk": {
437− "columns": [
438− "scope_id",
439− "id"
440− ],
399+ "columns": ["scope_id", "id"],
441400 "name": "graphql_source_scope_id_id_pk"
442401 }
443402 },
@@ -486,26 +445,19 @@ · 42 unmodified lines
486445 "indexes": {
487446 "mcp_binding_scope_id_idx": {
488447 "name": "mcp_binding_scope_id_idx",
489− "columns": [
490− "scope_id"
491− ],
448+ "columns": ["scope_id"],
492449 "isUnique": false
493450 },
494451 "mcp_binding_source_id_idx": {
495452 "name": "mcp_binding_source_id_idx",
496− "columns": [
497− "source_id"
498− ],
453+ "columns": ["source_id"],
499454 "isUnique": false
500455 }
501456 },
502457 "foreignKeys": {},
503458 "compositePrimaryKeys": {
504459 "mcp_binding_scope_id_id_pk": {
505− "columns": [
506− "scope_id",
507− "id"
508− ],
460+ "columns": ["scope_id", "id"],
509461 "name": "mcp_binding_scope_id_id_pk"
510462 }
511463 },
@@ -554,19 +506,14 @@ · 42 unmodified lines
554506 "indexes": {
555507 "mcp_oauth_session_scope_id_idx": {
556508 "name": "mcp_oauth_session_scope_id_idx",
557− "columns": [
558− "scope_id"
559− ],
509+ "columns": ["scope_id"],
560510 "isUnique": false
561511 }
562512 },
563513 "foreignKeys": {},
564514 "compositePrimaryKeys": {
565515 "mcp_oauth_session_scope_id_id_pk": {
566− "columns": [
567− "scope_id",
568− "id"
569− ],
516+ "columns": ["scope_id", "id"],
570517 "name": "mcp_oauth_session_scope_id_id_pk"
571518 }
572519 },
@@ -615,19 +562,14 @@ · 42 unmodified lines
615562 "indexes": {
616563 "mcp_source_scope_id_idx": {
617564 "name": "mcp_source_scope_id_idx",
618− "columns": [
619− "scope_id"
620− ],
565+ "columns": ["scope_id"],
621566 "isUnique": false
622567 }
623568 },
624569 "foreignKeys": {},
625570 "compositePrimaryKeys": {
626571 "mcp_source_scope_id_id_pk": {
627− "columns": [
628− "scope_id",
629− "id"
630− ],
572+ "columns": ["scope_id", "id"],
631573 "name": "mcp_source_scope_id_id_pk"
632574 }
633575 },
@@ -669,19 +611,14 @@ · 35 unmodified lines
669611 "indexes": {
670612 "openapi_oauth_session_scope_id_idx": {
671613 "name": "openapi_oauth_session_scope_id_idx",
672− "columns": [
673− "scope_id"
674− ],
614+ "columns": ["scope_id"],
675615 "isUnique": false
676616 }
677617 },
678618 "foreignKeys": {},
679619 "compositePrimaryKeys": {
680620 "openapi_oauth_session_scope_id_id_pk": {
681− "columns": [
682− "scope_id",
683− "id"
684− ],
621+ "columns": ["scope_id", "id"],
685622 "name": "openapi_oauth_session_scope_id_id_pk"
686623 }
687624 },
@@ -723,26 +660,19 @@ · 35 unmodified lines
723660 "indexes": {
724661 "openapi_operation_scope_id_idx": {
725662 "name": "openapi_operation_scope_id_idx",
726− "columns": [
727− "scope_id"
728− ],
663+ "columns": ["scope_id"],
729664 "isUnique": false
730665 },
731666 "openapi_operation_source_id_idx": {
732667 "name": "openapi_operation_source_id_idx",
733− "columns": [
734− "source_id"
735− ],
668+ "columns": ["source_id"],
736669 "isUnique": false
737670 }
738671 },
739672 "foreignKeys": {},
740673 "compositePrimaryKeys": {
741674 "openapi_operation_scope_id_id_pk": {
742− "columns": [
743− "scope_id",
744− "id"
745− ],
675+ "columns": ["scope_id", "id"],
746676 "name": "openapi_operation_scope_id_id_pk"
747677 }
748678 },
@@ -812,19 +742,14 @@ · 63 unmodified lines
812742 "indexes": {
813743 "openapi_source_scope_id_idx": {
814744 "name": "openapi_source_scope_id_idx",
815− "columns": [
816− "scope_id"
817− ],
745+ "columns": ["scope_id"],
818746 "isUnique": false
819747 }
820748 },
821749 "foreignKeys": {},
822750 "compositePrimaryKeys": {
823751 "openapi_source_scope_id_id_pk": {
824− "columns": [
825− "scope_id",
826− "id"
827− ],
752+ "columns": ["scope_id", "id"],
828753 "name": "openapi_source_scope_id_id_pk"
829754 }
830755 },
@@ -873,26 +798,19 @@ · 42 unmodified lines
873798 "indexes": {
874799 "secret_scope_id_idx": {
875800 "name": "secret_scope_id_idx",
876− "columns": [
877− "scope_id"
878− ],
801+ "columns": ["scope_id"],
879802 "isUnique": false
880803 },
881804 "secret_provider_idx": {
882805 "name": "secret_provider_idx",
883− "columns": [
884− "provider"
885− ],
806+ "columns": ["provider"],
886807 "isUnique": false
887808 }
888809 },
889810 "foreignKeys": {},
890811 "compositePrimaryKeys": {
891812 "secret_scope_id_id_pk": {
892− "columns": [
893− "scope_id",
894− "id"
895− ],
813+ "columns": ["scope_id", "id"],
896814 "name": "secret_scope_id_id_pk"
897815 }
898816 },
@@ -986,26 +904,19 @@ · 87 unmodified lines
986904 "indexes": {
987905 "source_scope_id_idx": {
988906 "name": "source_scope_id_idx",
989− "columns": [
990− "scope_id"
991− ],
907+ "columns": ["scope_id"],
992908 "isUnique": false
993909 },
994910 "source_plugin_id_idx": {
995911 "name": "source_plugin_id_idx",
996− "columns": [
997− "plugin_id"
998− ],
912+ "columns": ["plugin_id"],
999913 "isUnique": false
1000914 }
1001915 },
1002916 "foreignKeys": {},
1003917 "compositePrimaryKeys": {
1004918 "source_scope_id_id_pk": {
1005− "columns": [
1006− "scope_id",
1007− "id"
1008− ],
919+ "columns": ["scope_id", "id"],
1009920 "name": "source_scope_id_id_pk"
1010921 }
1011922 },
@@ -1089,33 +1000,24 @@ · 77 unmodified lines
10891000 "indexes": {
10901001 "tool_scope_id_idx": {
10911002 "name": "tool_scope_id_idx",
1092− "columns": [
1093− "scope_id"
1094− ],
1003+ "columns": ["scope_id"],
10951004 "isUnique": false
10961005 },
10971006 "tool_source_id_idx": {
10981007 "name": "tool_source_id_idx",
1099− "columns": [
1100− "source_id"
1101− ],
1008+ "columns": ["source_id"],
11021009 "isUnique": false
11031010 },
11041011 "tool_plugin_id_idx": {
11051012 "name": "tool_plugin_id_idx",
1106− "columns": [
1107− "plugin_id"
1108− ],
1013+ "columns": ["plugin_id"],
11091014 "isUnique": false
11101015 }
11111016 },
11121017 "foreignKeys": {},
11131018 "compositePrimaryKeys": {
11141019 "tool_scope_id_id_pk": {
1115− "columns": [
1116− "scope_id",
1117− "id"
1118− ],
1020+ "columns": ["scope_id", "id"],
11191021 "name": "tool_scope_id_id_pk"
11201022 }
11211023 },
@@ -1133,4 +1035,4 @@ · 11 unmodified lines
11331035 "internal": {
11341036 "indexes": {}
11351037 }
1136−}
1038+}
apps/local/drizzle/meta/0001_snapshot.json+42−140
@@ -33,10 +33,7 @@ · 32 unmodified lines
3333 "foreignKeys": {},
3434 "compositePrimaryKeys": {
3535 "blob_namespace_key_pk": {
36− "columns": [
37− "namespace",
38− "key"
39− ],
36+ "columns": ["namespace", "key"],
4037 "name": "blob_namespace_key_pk"
4138 }
4239 },
@@ -99,33 +96,24 @@ · 56 unmodified lines
9996 "indexes": {
10097 "definition_scope_id_idx": {
10198 "name": "definition_scope_id_idx",
102− "columns": [
103− "scope_id"
104− ],
99+ "columns": ["scope_id"],
105100 "isUnique": false
106101 },
107102 "definition_source_id_idx": {
108103 "name": "definition_source_id_idx",
109− "columns": [
110− "source_id"
111− ],
104+ "columns": ["source_id"],
112105 "isUnique": false
113106 },
114107 "definition_plugin_id_idx": {
115108 "name": "definition_plugin_id_idx",
116− "columns": [
117− "plugin_id"
118− ],
109+ "columns": ["plugin_id"],
119110 "isUnique": false
120111 }
121112 },
122113 "foreignKeys": {},
123114 "compositePrimaryKeys": {
124115 "definition_scope_id_id_pk": {
125− "columns": [
126− "scope_id",
127− "id"
128− ],
116+ "columns": ["scope_id", "id"],
129117 "name": "definition_scope_id_id_pk"
130118 }
131119 },
@@ -174,26 +162,19 @@ · 42 unmodified lines
174162 "indexes": {
175163 "google_discovery_binding_scope_id_idx": {
176164 "name": "google_discovery_binding_scope_id_idx",
177− "columns": [
178− "scope_id"
179− ],
165+ "columns": ["scope_id"],
180166 "isUnique": false
181167 },
182168 "google_discovery_binding_source_id_idx": {
183169 "name": "google_discovery_binding_source_id_idx",
184− "columns": [
185− "source_id"
186− ],
170+ "columns": ["source_id"],
187171 "isUnique": false
188172 }
189173 },
190174 "foreignKeys": {},
191175 "compositePrimaryKeys": {
192176 "google_discovery_binding_scope_id_id_pk": {
193− "columns": [
194− "scope_id",
195− "id"
196− ],
177+ "columns": ["scope_id", "id"],
197178 "name": "google_discovery_binding_scope_id_id_pk"
198179 }
199180 },
@@ -235,19 +216,14 @@ · 35 unmodified lines
235216 "indexes": {
236217 "google_discovery_oauth_session_scope_id_idx": {
237218 "name": "google_discovery_oauth_session_scope_id_idx",
238− "columns": [
239− "scope_id"
240− ],
219+ "columns": ["scope_id"],
241220 "isUnique": false
242221 }
243222 },
244223 "foreignKeys": {},
245224 "compositePrimaryKeys": {
246225 "google_discovery_oauth_session_scope_id_id_pk": {
247− "columns": [
248− "scope_id",
249− "id"
250− ],
226+ "columns": ["scope_id", "id"],
251227 "name": "google_discovery_oauth_session_scope_id_id_pk"
252228 }
253229 },
@@ -303,19 +279,14 @@ · 49 unmodified lines
303279 "indexes": {
304280 "google_discovery_source_scope_id_idx": {
305281 "name": "google_discovery_source_scope_id_idx",
306− "columns": [
307− "scope_id"
308− ],
282+ "columns": ["scope_id"],
309283 "isUnique": false
310284 }
311285 },
312286 "foreignKeys": {},
313287 "compositePrimaryKeys": {
314288 "google_discovery_source_scope_id_id_pk": {
315− "columns": [
316− "scope_id",
317− "id"
318− ],
289+ "columns": ["scope_id", "id"],
319290 "name": "google_discovery_source_scope_id_id_pk"
320291 }
321292 },
@@ -357,26 +328,19 @@ · 35 unmodified lines
357328 "indexes": {
358329 "graphql_operation_scope_id_idx": {
359330 "name": "graphql_operation_scope_id_idx",
360− "columns": [
361− "scope_id"
362− ],
331+ "columns": ["scope_id"],
363332 "isUnique": false
364333 },
365334 "graphql_operation_source_id_idx": {
366335 "name": "graphql_operation_source_id_idx",
367− "columns": [
368− "source_id"
369− ],
336+ "columns": ["source_id"],
370337 "isUnique": false
371338 }
372339 },
373340 "foreignKeys": {},
374341 "compositePrimaryKeys": {
375342 "graphql_operation_scope_id_id_pk": {
376− "columns": [
377− "scope_id",
378− "id"
379− ],
343+ "columns": ["scope_id", "id"],
380344 "name": "graphql_operation_scope_id_id_pk"
381345 }
382346 },
@@ -425,19 +389,14 @@ · 42 unmodified lines
425389 "indexes": {
426390 "graphql_source_scope_id_idx": {
427391 "name": "graphql_source_scope_id_idx",
428− "columns": [
429− "scope_id"
430− ],
392+ "columns": ["scope_id"],
431393 "isUnique": false
432394 }
433395 },
434396 "foreignKeys": {},
435397 "compositePrimaryKeys": {
436398 "graphql_source_scope_id_id_pk": {
437− "columns": [
438− "scope_id",
439− "id"
440− ],
399+ "columns": ["scope_id", "id"],
441400 "name": "graphql_source_scope_id_id_pk"
442401 }
443402 },
@@ -486,26 +445,19 @@ · 42 unmodified lines
486445 "indexes": {
487446 "mcp_binding_scope_id_idx": {
488447 "name": "mcp_binding_scope_id_idx",
489− "columns": [
490− "scope_id"
491− ],
448+ "columns": ["scope_id"],
492449 "isUnique": false
493450 },
494451 "mcp_binding_source_id_idx": {
495452 "name": "mcp_binding_source_id_idx",
496− "columns": [
497− "source_id"
498− ],
453+ "columns": ["source_id"],
499454 "isUnique": false
500455 }
501456 },
502457 "foreignKeys": {},
503458 "compositePrimaryKeys": {
504459 "mcp_binding_scope_id_id_pk": {
505− "columns": [
506− "scope_id",
507− "id"
508− ],
460+ "columns": ["scope_id", "id"],
509461 "name": "mcp_binding_scope_id_id_pk"
510462 }
511463 },
@@ -554,19 +506,14 @@ · 42 unmodified lines
554506 "indexes": {
555507 "mcp_oauth_session_scope_id_idx": {
556508 "name": "mcp_oauth_session_scope_id_idx",
557− "columns": [
558− "scope_id"
559− ],
509+ "columns": ["scope_id"],
560510 "isUnique": false
561511 }
562512 },
563513 "foreignKeys": {},
564514 "compositePrimaryKeys": {
565515 "mcp_oauth_session_scope_id_id_pk": {
566− "columns": [
567− "scope_id",
568− "id"
569− ],
516+ "columns": ["scope_id", "id"],
570517 "name": "mcp_oauth_session_scope_id_id_pk"
571518 }
572519 },
@@ -615,19 +562,14 @@ · 42 unmodified lines
615562 "indexes": {
616563 "mcp_source_scope_id_idx": {
617564 "name": "mcp_source_scope_id_idx",
618− "columns": [
619− "scope_id"
620− ],
565+ "columns": ["scope_id"],
621566 "isUnique": false
622567 }
623568 },
624569 "foreignKeys": {},
625570 "compositePrimaryKeys": {
626571 "mcp_source_scope_id_id_pk": {
627− "columns": [
628− "scope_id",
629− "id"
630− ],
572+ "columns": ["scope_id", "id"],
631573 "name": "mcp_source_scope_id_id_pk"
632574 }
633575 },
@@ -669,19 +611,14 @@ · 35 unmodified lines
669611 "indexes": {
670612 "openapi_oauth_session_scope_id_idx": {
671613 "name": "openapi_oauth_session_scope_id_idx",
672− "columns": [
673− "scope_id"
674− ],
614+ "columns": ["scope_id"],
675615 "isUnique": false
676616 }
677617 },
678618 "foreignKeys": {},
679619 "compositePrimaryKeys": {
680620 "openapi_oauth_session_scope_id_id_pk": {
681− "columns": [
682− "scope_id",
683− "id"
684− ],
621+ "columns": ["scope_id", "id"],
685622 "name": "openapi_oauth_session_scope_id_id_pk"
686623 }
687624 },
@@ -723,26 +660,19 @@ · 35 unmodified lines
723660 "indexes": {
724661 "openapi_operation_scope_id_idx": {
725662 "name": "openapi_operation_scope_id_idx",
726− "columns": [
727− "scope_id"
728− ],
663+ "columns": ["scope_id"],
729664 "isUnique": false
730665 },
731666 "openapi_operation_source_id_idx": {
732667 "name": "openapi_operation_source_id_idx",
733− "columns": [
734− "source_id"
735− ],
668+ "columns": ["source_id"],
736669 "isUnique": false
737670 }
738671 },
739672 "foreignKeys": {},
740673 "compositePrimaryKeys": {
741674 "openapi_operation_scope_id_id_pk": {
742− "columns": [
743− "scope_id",
744− "id"
745− ],
675+ "columns": ["scope_id", "id"],
746676 "name": "openapi_operation_scope_id_id_pk"
747677 }
748678 },
@@ -819,19 +749,14 @@ · 70 unmodified lines
819749 "indexes": {
820750 "openapi_source_scope_id_idx": {
821751 "name": "openapi_source_scope_id_idx",
822− "columns": [
823− "scope_id"
824− ],
752+ "columns": ["scope_id"],
825753 "isUnique": false
826754 }
827755 },
828756 "foreignKeys": {},
829757 "compositePrimaryKeys": {
830758 "openapi_source_scope_id_id_pk": {
831− "columns": [
832− "scope_id",
833− "id"
834− ],
759+ "columns": ["scope_id", "id"],
835760 "name": "openapi_source_scope_id_id_pk"
836761 }
837762 },
@@ -880,26 +805,19 @@ · 42 unmodified lines
880805 "indexes": {
881806 "secret_scope_id_idx": {
882807 "name": "secret_scope_id_idx",
883− "columns": [
884− "scope_id"
885− ],
808+ "columns": ["scope_id"],
886809 "isUnique": false
887810 },
888811 "secret_provider_idx": {
889812 "name": "secret_provider_idx",
890− "columns": [
891− "provider"
892− ],
813+ "columns": ["provider"],
893814 "isUnique": false
894815 }
895816 },
896817 "foreignKeys": {},
897818 "compositePrimaryKeys": {
898819 "secret_scope_id_id_pk": {
899− "columns": [
900− "scope_id",
901− "id"
902− ],
820+ "columns": ["scope_id", "id"],
903821 "name": "secret_scope_id_id_pk"
904822 }
905823 },
@@ -993,26 +911,19 @@ · 87 unmodified lines
993911 "indexes": {
994912 "source_scope_id_idx": {
995913 "name": "source_scope_id_idx",
996− "columns": [
997− "scope_id"
998− ],
914+ "columns": ["scope_id"],
999915 "isUnique": false
1000916 },
1001917 "source_plugin_id_idx": {
1002918 "name": "source_plugin_id_idx",
1003− "columns": [
1004− "plugin_id"
1005− ],
919+ "columns": ["plugin_id"],
1006920 "isUnique": false
1007921 }
1008922 },
1009923 "foreignKeys": {},
1010924 "compositePrimaryKeys": {
1011925 "source_scope_id_id_pk": {
1012− "columns": [
1013− "scope_id",
1014− "id"
1015− ],
926+ "columns": ["scope_id", "id"],
1016927 "name": "source_scope_id_id_pk"
1017928 }
1018929 },
@@ -1096,33 +1007,24 @@ · 77 unmodified lines
10961007 "indexes": {
10971008 "tool_scope_id_idx": {
10981009 "name": "tool_scope_id_idx",
1099− "columns": [
1100− "scope_id"
1101− ],
1010+ "columns": ["scope_id"],
11021011 "isUnique": false
11031012 },
11041013 "tool_source_id_idx": {
11051014 "name": "tool_source_id_idx",
1106− "columns": [
1107− "source_id"
1108− ],
1015+ "columns": ["source_id"],
11091016 "isUnique": false
11101017 },
11111018 "tool_plugin_id_idx": {
11121019 "name": "tool_plugin_id_idx",
1113− "columns": [
1114− "plugin_id"
1115− ],
1020+ "columns": ["plugin_id"],
11161021 "isUnique": false
11171022 }
11181023 },
11191024 "foreignKeys": {},
11201025 "compositePrimaryKeys": {
11211026 "tool_scope_id_id_pk": {
1122− "columns": [
1123− "scope_id",
1124− "id"
1125− ],
1027+ "columns": ["scope_id", "id"],
11261028 "name": "tool_scope_id_id_pk"
11271029 }
11281030 },
@@ -1140,4 +1042,4 @@ · 11 unmodified lines
11401042 "internal": {
11411043 "indexes": {}
11421044 }
1143−}
1045+}
apps/local/drizzle/meta/0002_snapshot.json+46−153
@@ -33,10 +33,7 @@ · 32 unmodified lines
3333 "foreignKeys": {},
3434 "compositePrimaryKeys": {
3535 "blob_namespace_key_pk": {
36− "columns": [
37− "namespace",
38− "key"
39− ],
36+ "columns": ["namespace", "key"],
4037 "name": "blob_namespace_key_pk"
4138 }
4239 },
@@ -134,26 +131,19 @@ · 91 unmodified lines
134131 "indexes": {
135132 "connection_scope_id_idx": {
136133 "name": "connection_scope_id_idx",
137− "columns": [
138− "scope_id"
139− ],
134+ "columns": ["scope_id"],
140135 "isUnique": false
141136 },
142137 "connection_provider_idx": {
143138 "name": "connection_provider_idx",
144− "columns": [
145− "provider"
146− ],
139+ "columns": ["provider"],
147140 "isUnique": false
148141 }
149142 },
150143 "foreignKeys": {},
151144 "compositePrimaryKeys": {
152145 "connection_scope_id_id_pk": {
153− "columns": [
154− "scope_id",
155− "id"
156− ],
146+ "columns": ["scope_id", "id"],
157147 "name": "connection_scope_id_id_pk"
158148 }
159149 },
@@ -216,33 +206,24 @@ · 56 unmodified lines
216206 "indexes": {
217207 "definition_scope_id_idx": {
218208 "name": "definition_scope_id_idx",
219− "columns": [
220− "scope_id"
221− ],
209+ "columns": ["scope_id"],
222210 "isUnique": false
223211 },
224212 "definition_source_id_idx": {
225213 "name": "definition_source_id_idx",
226− "columns": [
227− "source_id"
228− ],
214+ "columns": ["source_id"],
229215 "isUnique": false
230216 },
231217 "definition_plugin_id_idx": {
232218 "name": "definition_plugin_id_idx",
233− "columns": [
234− "plugin_id"
235− ],
219+ "columns": ["plugin_id"],
236220 "isUnique": false
237221 }
238222 },
239223 "foreignKeys": {},
240224 "compositePrimaryKeys": {
241225 "definition_scope_id_id_pk": {
242− "columns": [
243− "scope_id",
244− "id"
245− ],
226+ "columns": ["scope_id", "id"],
246227 "name": "definition_scope_id_id_pk"
247228 }
248229 },
@@ -291,26 +272,19 @@ · 42 unmodified lines
291272 "indexes": {
292273 "google_discovery_binding_scope_id_idx": {
293274 "name": "google_discovery_binding_scope_id_idx",
294− "columns": [
295− "scope_id"
296− ],
275+ "columns": ["scope_id"],
297276 "isUnique": false
298277 },
299278 "google_discovery_binding_source_id_idx": {
300279 "name": "google_discovery_binding_source_id_idx",
301− "columns": [
302− "source_id"
303− ],
280+ "columns": ["source_id"],
304281 "isUnique": false
305282 }
306283 },
307284 "foreignKeys": {},
308285 "compositePrimaryKeys": {
309286 "google_discovery_binding_scope_id_id_pk": {
310− "columns": [
311− "scope_id",
312− "id"
313− ],
287+ "columns": ["scope_id", "id"],
314288 "name": "google_discovery_binding_scope_id_id_pk"
315289 }
316290 },
@@ -352,19 +326,14 @@ · 35 unmodified lines
352326 "indexes": {
353327 "google_discovery_oauth_session_scope_id_idx": {
354328 "name": "google_discovery_oauth_session_scope_id_idx",
355− "columns": [
356− "scope_id"
357− ],
329+ "columns": ["scope_id"],
358330 "isUnique": false
359331 }
360332 },
361333 "foreignKeys": {},
362334 "compositePrimaryKeys": {
363335 "google_discovery_oauth_session_scope_id_id_pk": {
364− "columns": [
365− "scope_id",
366− "id"
367− ],
336+ "columns": ["scope_id", "id"],
368337 "name": "google_discovery_oauth_session_scope_id_id_pk"
369338 }
370339 },
@@ -420,19 +389,14 @@ · 49 unmodified lines
420389 "indexes": {
421390 "google_discovery_source_scope_id_idx": {
422391 "name": "google_discovery_source_scope_id_idx",
423− "columns": [
424− "scope_id"
425− ],
392+ "columns": ["scope_id"],
426393 "isUnique": false
427394 }
428395 },
429396 "foreignKeys": {},
430397 "compositePrimaryKeys": {
431398 "google_discovery_source_scope_id_id_pk": {
432− "columns": [
433− "scope_id",
434− "id"
435− ],
399+ "columns": ["scope_id", "id"],
436400 "name": "google_discovery_source_scope_id_id_pk"
437401 }
438402 },
@@ -474,26 +438,19 @@ · 35 unmodified lines
474438 "indexes": {
475439 "graphql_operation_scope_id_idx": {
476440 "name": "graphql_operation_scope_id_idx",
477− "columns": [
478− "scope_id"
479− ],
441+ "columns": ["scope_id"],
480442 "isUnique": false
481443 },
482444 "graphql_operation_source_id_idx": {
483445 "name": "graphql_operation_source_id_idx",
484− "columns": [
485− "source_id"
486− ],
446+ "columns": ["source_id"],
487447 "isUnique": false
488448 }
489449 },
490450 "foreignKeys": {},
491451 "compositePrimaryKeys": {
492452 "graphql_operation_scope_id_id_pk": {
493− "columns": [
494− "scope_id",
495− "id"
496− ],
453+ "columns": ["scope_id", "id"],
497454 "name": "graphql_operation_scope_id_id_pk"
498455 }
499456 },
@@ -542,19 +499,14 @@ · 42 unmodified lines
542499 "indexes": {
543500 "graphql_source_scope_id_idx": {
544501 "name": "graphql_source_scope_id_idx",
545− "columns": [
546− "scope_id"
547− ],
502+ "columns": ["scope_id"],
548503 "isUnique": false
549504 }
550505 },
551506 "foreignKeys": {},
552507 "compositePrimaryKeys": {
553508 "graphql_source_scope_id_id_pk": {
554− "columns": [
555− "scope_id",
556− "id"
557− ],
509+ "columns": ["scope_id", "id"],
558510 "name": "graphql_source_scope_id_id_pk"
559511 }
560512 },
@@ -603,26 +555,19 @@ · 42 unmodified lines
603555 "indexes": {
604556 "mcp_binding_scope_id_idx": {
605557 "name": "mcp_binding_scope_id_idx",
606− "columns": [
607− "scope_id"
608− ],
558+ "columns": ["scope_id"],
609559 "isUnique": false
610560 },
611561 "mcp_binding_source_id_idx": {
612562 "name": "mcp_binding_source_id_idx",
613− "columns": [
614− "source_id"
615− ],
563+ "columns": ["source_id"],
616564 "isUnique": false
617565 }
618566 },
619567 "foreignKeys": {},
620568 "compositePrimaryKeys": {
621569 "mcp_binding_scope_id_id_pk": {
622− "columns": [
623− "scope_id",
624− "id"
625− ],
570+ "columns": ["scope_id", "id"],
626571 "name": "mcp_binding_scope_id_id_pk"
627572 }
628573 },
@@ -671,19 +616,14 @@ · 42 unmodified lines
671616 "indexes": {
672617 "mcp_oauth_session_scope_id_idx": {
673618 "name": "mcp_oauth_session_scope_id_idx",
674− "columns": [
675− "scope_id"
676− ],
619+ "columns": ["scope_id"],
677620 "isUnique": false
678621 }
679622 },
680623 "foreignKeys": {},
681624 "compositePrimaryKeys": {
682625 "mcp_oauth_session_scope_id_id_pk": {
683− "columns": [
684− "scope_id",
685− "id"
686− ],
626+ "columns": ["scope_id", "id"],
687627 "name": "mcp_oauth_session_scope_id_id_pk"
688628 }
689629 },
@@ -732,19 +672,14 @@ · 42 unmodified lines
732672 "indexes": {
733673 "mcp_source_scope_id_idx": {
734674 "name": "mcp_source_scope_id_idx",
735− "columns": [
736− "scope_id"
737− ],
675+ "columns": ["scope_id"],
738676 "isUnique": false
739677 }
740678 },
741679 "foreignKeys": {},
742680 "compositePrimaryKeys": {
743681 "mcp_source_scope_id_id_pk": {
744− "columns": [
745− "scope_id",
746− "id"
747− ],
682+ "columns": ["scope_id", "id"],
748683 "name": "mcp_source_scope_id_id_pk"
749684 }
750685 },
@@ -786,19 +721,14 @@ · 35 unmodified lines
786721 "indexes": {
787722 "openapi_oauth_session_scope_id_idx": {
788723 "name": "openapi_oauth_session_scope_id_idx",
789− "columns": [
790− "scope_id"
791− ],
724+ "columns": ["scope_id"],
792725 "isUnique": false
793726 }
794727 },
795728 "foreignKeys": {},
796729 "compositePrimaryKeys": {
797730 "openapi_oauth_session_scope_id_id_pk": {
798− "columns": [
799− "scope_id",
800− "id"
801− ],
731+ "columns": ["scope_id", "id"],
802732 "name": "openapi_oauth_session_scope_id_id_pk"
803733 }
804734 },
@@ -840,26 +770,19 @@ · 35 unmodified lines
840770 "indexes": {
841771 "openapi_operation_scope_id_idx": {
842772 "name": "openapi_operation_scope_id_idx",
843− "columns": [
844− "scope_id"
845− ],
773+ "columns": ["scope_id"],
846774 "isUnique": false
847775 },
848776 "openapi_operation_source_id_idx": {
849777 "name": "openapi_operation_source_id_idx",
850− "columns": [
851− "source_id"
852− ],
778+ "columns": ["source_id"],
853779 "isUnique": false
854780 }
855781 },
856782 "foreignKeys": {},
857783 "compositePrimaryKeys": {
858784 "openapi_operation_scope_id_id_pk": {
859− "columns": [
860− "scope_id",
861− "id"
862− ],
785+ "columns": ["scope_id", "id"],
863786 "name": "openapi_operation_scope_id_id_pk"
864787 }
865788 },
@@ -936,19 +859,14 @@ · 70 unmodified lines
936859 "indexes": {
937860 "openapi_source_scope_id_idx": {
938861 "name": "openapi_source_scope_id_idx",
939− "columns": [
940− "scope_id"
941− ],
862+ "columns": ["scope_id"],
942863 "isUnique": false
943864 }
944865 },
945866 "foreignKeys": {},
946867 "compositePrimaryKeys": {
947868 "openapi_source_scope_id_id_pk": {
948− "columns": [
949− "scope_id",
950− "id"
951− ],
869+ "columns": ["scope_id", "id"],
952870 "name": "openapi_source_scope_id_id_pk"
953871 }
954872 },
@@ -1004,33 +922,24 @@ · 49 unmodified lines
1004922 "indexes": {
1005923 "secret_scope_id_idx": {
1006924 "name": "secret_scope_id_idx",
1007− "columns": [
1008− "scope_id"
1009− ],
925+ "columns": ["scope_id"],
1010926 "isUnique": false
1011927 },
1012928 "secret_provider_idx": {
1013929 "name": "secret_provider_idx",
1014− "columns": [
1015− "provider"
1016− ],
930+ "columns": ["provider"],
1017931 "isUnique": false
1018932 },
1019933 "secret_owned_by_connection_id_idx": {
1020934 "name": "secret_owned_by_connection_id_idx",
1021− "columns": [
1022− "owned_by_connection_id"
1023− ],
935+ "columns": ["owned_by_connection_id"],
1024936 "isUnique": false
1025937 }
1026938 },
1027939 "foreignKeys": {},
1028940 "compositePrimaryKeys": {
1029941 "secret_scope_id_id_pk": {
1030− "columns": [
1031− "scope_id",
1032− "id"
1033− ],
942+ "columns": ["scope_id", "id"],
1034943 "name": "secret_scope_id_id_pk"
1035944 }
1036945 },
@@ -1124,26 +1033,19 @@ · 87 unmodified lines
11241033 "indexes": {
11251034 "source_scope_id_idx": {
11261035 "name": "source_scope_id_idx",
1127− "columns": [
1128− "scope_id"
1129− ],
1036+ "columns": ["scope_id"],
11301037 "isUnique": false
11311038 },
11321039 "source_plugin_id_idx": {
11331040 "name": "source_plugin_id_idx",
1134− "columns": [
1135− "plugin_id"
1136− ],
1041+ "columns": ["plugin_id"],
11371042 "isUnique": false
11381043 }
11391044 },
11401045 "foreignKeys": {},
11411046 "compositePrimaryKeys": {
11421047 "source_scope_id_id_pk": {
1143− "columns": [
1144− "scope_id",
1145− "id"
1146− ],
1048+ "columns": ["scope_id", "id"],
11471049 "name": "source_scope_id_id_pk"
11481050 }
11491051 },
@@ -1227,33 +1129,24 @@ · 77 unmodified lines
12271129 "indexes": {
12281130 "tool_scope_id_idx": {
12291131 "name": "tool_scope_id_idx",
1230− "columns": [
1231− "scope_id"
1232− ],
1132+ "columns": ["scope_id"],
12331133 "isUnique": false
12341134 },
12351135 "tool_source_id_idx": {
12361136 "name": "tool_source_id_idx",
1237− "columns": [
1238− "source_id"
1239− ],
1137+ "columns": ["source_id"],
12401138 "isUnique": false
12411139 },
12421140 "tool_plugin_id_idx": {
12431141 "name": "tool_plugin_id_idx",
1244− "columns": [
1245− "plugin_id"
1246− ],
1142+ "columns": ["plugin_id"],
12471143 "isUnique": false
12481144 }
12491145 },
12501146 "foreignKeys": {},
12511147 "compositePrimaryKeys": {
12521148 "tool_scope_id_id_pk": {
1253− "columns": [
1254− "scope_id",
1255− "id"
1256− ],
1149+ "columns": ["scope_id", "id"],
12571150 "name": "tool_scope_id_id_pk"
12581151 }
12591152 },
@@ -1271,4 +1164,4 @@ · 11 unmodified lines
12711164 "internal": {
12721165 "indexes": {}
12731166 }
1274−}
1167+}
apps/local/drizzle/meta/0003_snapshot.json+49−164
@@ -88,26 +88,19 @@ · 87 unmodified lines
8888 "indexes": {
8989 "connection_scope_id_idx": {
9090 "name": "connection_scope_id_idx",
91− "columns": [
92− "scope_id"
93− ],
91+ "columns": ["scope_id"],
9492 "isUnique": false
9593 },
9694 "connection_provider_idx": {
9795 "name": "connection_provider_idx",
98− "columns": [
99− "provider"
100− ],
96+ "columns": ["provider"],
10197 "isUnique": false
10298 }
10399 },
104100 "foreignKeys": {},
105101 "compositePrimaryKeys": {
106102 "connection_scope_id_id_pk": {
107− "columns": [
108− "scope_id",
109− "id"
110− ],
103+ "columns": ["scope_id", "id"],
111104 "name": "connection_scope_id_id_pk"
112105 }
113106 },
@@ -170,33 +163,24 @@ · 56 unmodified lines
170163 "indexes": {
171164 "definition_scope_id_idx": {
172165 "name": "definition_scope_id_idx",
173− "columns": [
174− "scope_id"
175− ],
166+ "columns": ["scope_id"],
176167 "isUnique": false
177168 },
178169 "definition_source_id_idx": {
179170 "name": "definition_source_id_idx",
180− "columns": [
181− "source_id"
182− ],
171+ "columns": ["source_id"],
183172 "isUnique": false
184173 },
185174 "definition_plugin_id_idx": {
186175 "name": "definition_plugin_id_idx",
187− "columns": [
188− "plugin_id"
189− ],
176+ "columns": ["plugin_id"],
190177 "isUnique": false
191178 }
192179 },
193180 "foreignKeys": {},
194181 "compositePrimaryKeys": {
195182 "definition_scope_id_id_pk": {
196− "columns": [
197− "scope_id",
198− "id"
199− ],
183+ "columns": ["scope_id", "id"],
200184 "name": "definition_scope_id_id_pk"
201185 }
202186 },
@@ -245,26 +229,19 @@ · 42 unmodified lines
245229 "indexes": {
246230 "google_discovery_binding_scope_id_idx": {
247231 "name": "google_discovery_binding_scope_id_idx",
248− "columns": [
249− "scope_id"
250− ],
232+ "columns": ["scope_id"],
251233 "isUnique": false
252234 },
253235 "google_discovery_binding_source_id_idx": {
254236 "name": "google_discovery_binding_source_id_idx",
255− "columns": [
256− "source_id"
257− ],
237+ "columns": ["source_id"],
258238 "isUnique": false
259239 }
260240 },
261241 "foreignKeys": {},
262242 "compositePrimaryKeys": {
263243 "google_discovery_binding_scope_id_id_pk": {
264− "columns": [
265− "scope_id",
266− "id"
267− ],
244+ "columns": ["scope_id", "id"],
268245 "name": "google_discovery_binding_scope_id_id_pk"
269246 }
270247 },
@@ -306,19 +283,14 @@ · 35 unmodified lines
306283 "indexes": {
307284 "google_discovery_oauth_session_scope_id_idx": {
308285 "name": "google_discovery_oauth_session_scope_id_idx",
309− "columns": [
310− "scope_id"
311− ],
286+ "columns": ["scope_id"],
312287 "isUnique": false
313288 }
314289 },
315290 "foreignKeys": {},
316291 "compositePrimaryKeys": {
317292 "google_discovery_oauth_session_scope_id_id_pk": {
318− "columns": [
319− "scope_id",
320− "id"
321− ],
293+ "columns": ["scope_id", "id"],
322294 "name": "google_discovery_oauth_session_scope_id_id_pk"
323295 }
324296 },
@@ -374,19 +346,14 @@ · 49 unmodified lines
374346 "indexes": {
375347 "google_discovery_source_scope_id_idx": {
376348 "name": "google_discovery_source_scope_id_idx",
377− "columns": [
378− "scope_id"
379− ],
349+ "columns": ["scope_id"],
380350 "isUnique": false
381351 }
382352 },
383353 "foreignKeys": {},
384354 "compositePrimaryKeys": {
385355 "google_discovery_source_scope_id_id_pk": {
386− "columns": [
387− "scope_id",
388− "id"
389− ],
356+ "columns": ["scope_id", "id"],
390357 "name": "google_discovery_source_scope_id_id_pk"
391358 }
392359 },
@@ -428,26 +395,19 @@ · 35 unmodified lines
428395 "indexes": {
429396 "graphql_operation_scope_id_idx": {
430397 "name": "graphql_operation_scope_id_idx",
431− "columns": [
432− "scope_id"
433− ],
398+ "columns": ["scope_id"],
434399 "isUnique": false
435400 },
436401 "graphql_operation_source_id_idx": {
437402 "name": "graphql_operation_source_id_idx",
438− "columns": [
439− "source_id"
440− ],
403+ "columns": ["source_id"],
441404 "isUnique": false
442405 }
443406 },
444407 "foreignKeys": {},
445408 "compositePrimaryKeys": {
446409 "graphql_operation_scope_id_id_pk": {
447− "columns": [
448− "scope_id",
449− "id"
450− ],
410+ "columns": ["scope_id", "id"],
451411 "name": "graphql_operation_scope_id_id_pk"
452412 }
453413 },
@@ -496,19 +456,14 @@ · 42 unmodified lines
496456 "indexes": {
497457 "graphql_source_scope_id_idx": {
498458 "name": "graphql_source_scope_id_idx",
499− "columns": [
500− "scope_id"
501− ],
459+ "columns": ["scope_id"],
502460 "isUnique": false
503461 }
504462 },
505463 "foreignKeys": {},
506464 "compositePrimaryKeys": {
507465 "graphql_source_scope_id_id_pk": {
508− "columns": [
509− "scope_id",
510− "id"
511− ],
466+ "columns": ["scope_id", "id"],
512467 "name": "graphql_source_scope_id_id_pk"
513468 }
514469 },
@@ -557,26 +512,19 @@ · 42 unmodified lines
557512 "indexes": {
558513 "mcp_binding_scope_id_idx": {
559514 "name": "mcp_binding_scope_id_idx",
560− "columns": [
561− "scope_id"
562− ],
515+ "columns": ["scope_id"],
563516 "isUnique": false
564517 },
565518 "mcp_binding_source_id_idx": {
566519 "name": "mcp_binding_source_id_idx",
567− "columns": [
568− "source_id"
569− ],
520+ "columns": ["source_id"],
570521 "isUnique": false
571522 }
572523 },
573524 "foreignKeys": {},
574525 "compositePrimaryKeys": {
575526 "mcp_binding_scope_id_id_pk": {
576− "columns": [
577− "scope_id",
578− "id"
579− ],
527+ "columns": ["scope_id", "id"],
580528 "name": "mcp_binding_scope_id_id_pk"
581529 }
582530 },
@@ -625,19 +573,14 @@ · 42 unmodified lines
625573 "indexes": {
626574 "mcp_oauth_session_scope_id_idx": {
627575 "name": "mcp_oauth_session_scope_id_idx",
628− "columns": [
629− "scope_id"
630− ],
576+ "columns": ["scope_id"],
631577 "isUnique": false
632578 }
633579 },
634580 "foreignKeys": {},
635581 "compositePrimaryKeys": {
636582 "mcp_oauth_session_scope_id_id_pk": {
637− "columns": [
638− "scope_id",
639− "id"
640− ],
583+ "columns": ["scope_id", "id"],
641584 "name": "mcp_oauth_session_scope_id_id_pk"
642585 }
643586 },
@@ -686,19 +629,14 @@ · 42 unmodified lines
686629 "indexes": {
687630 "mcp_source_scope_id_idx": {
688631 "name": "mcp_source_scope_id_idx",
689− "columns": [
690− "scope_id"
691− ],
632+ "columns": ["scope_id"],
692633 "isUnique": false
693634 }
694635 },
695636 "foreignKeys": {},
696637 "compositePrimaryKeys": {
697638 "mcp_source_scope_id_id_pk": {
698− "columns": [
699− "scope_id",
700− "id"
701− ],
639+ "columns": ["scope_id", "id"],
702640 "name": "mcp_source_scope_id_id_pk"
703641 }
704642 },
@@ -740,19 +678,14 @@ · 35 unmodified lines
740678 "indexes": {
741679 "openapi_oauth_session_scope_id_idx": {
742680 "name": "openapi_oauth_session_scope_id_idx",
743− "columns": [
744− "scope_id"
745− ],
681+ "columns": ["scope_id"],
746682 "isUnique": false
747683 }
748684 },
749685 "foreignKeys": {},
750686 "compositePrimaryKeys": {
751687 "openapi_oauth_session_scope_id_id_pk": {
752− "columns": [
753− "scope_id",
754− "id"
755− ],
688+ "columns": ["scope_id", "id"],
756689 "name": "openapi_oauth_session_scope_id_id_pk"
757690 }
758691 },
@@ -794,26 +727,19 @@ · 35 unmodified lines
794727 "indexes": {
795728 "openapi_operation_scope_id_idx": {
796729 "name": "openapi_operation_scope_id_idx",
797− "columns": [
798− "scope_id"
799− ],
730+ "columns": ["scope_id"],
800731 "isUnique": false
801732 },
802733 "openapi_operation_source_id_idx": {
803734 "name": "openapi_operation_source_id_idx",
804− "columns": [
805− "source_id"
806− ],
735+ "columns": ["source_id"],
807736 "isUnique": false
808737 }
809738 },
810739 "foreignKeys": {},
811740 "compositePrimaryKeys": {
812741 "openapi_operation_scope_id_id_pk": {
813− "columns": [
814− "scope_id",
815− "id"
816− ],
742+ "columns": ["scope_id", "id"],
817743 "name": "openapi_operation_scope_id_id_pk"
818744 }
819745 },
@@ -890,19 +816,14 @@ · 70 unmodified lines
890816 "indexes": {
891817 "openapi_source_scope_id_idx": {
892818 "name": "openapi_source_scope_id_idx",
893− "columns": [
894− "scope_id"
895− ],
819+ "columns": ["scope_id"],
896820 "isUnique": false
897821 }
898822 },
899823 "foreignKeys": {},
900824 "compositePrimaryKeys": {
901825 "openapi_source_scope_id_id_pk": {
902− "columns": [
903− "scope_id",
904− "id"
905− ],
826+ "columns": ["scope_id", "id"],
906827 "name": "openapi_source_scope_id_id_pk"
907828 }
908829 },
@@ -972,30 +893,22 @@ · 63 unmodified lines
972893 "indexes": {
973894 "openapi_source_binding_source_id_idx": {
974895 "name": "openapi_source_binding_source_id_idx",
975− "columns": [
976− "source_id"
977− ],
896+ "columns": ["source_id"],
978897 "isUnique": false
979898 },
980899 "openapi_source_binding_source_scope_id_idx": {
981900 "name": "openapi_source_binding_source_scope_id_idx",
982− "columns": [
983− "source_scope_id"
984− ],
901+ "columns": ["source_scope_id"],
985902 "isUnique": false
986903 },
987904 "openapi_source_binding_target_scope_id_idx": {
988905 "name": "openapi_source_binding_target_scope_id_idx",
989− "columns": [
990− "target_scope_id"
991− ],
906+ "columns": ["target_scope_id"],
992907 "isUnique": false
993908 },
994909 "openapi_source_binding_slot_idx": {
995910 "name": "openapi_source_binding_slot_idx",
996− "columns": [
997− "slot"
998− ],
911+ "columns": ["slot"],
999912 "isUnique": false
1000913 }
1001914 },
@@ -1053,33 +966,24 @@ · 51 unmodified lines
1053966 "indexes": {
1054967 "secret_scope_id_idx": {
1055968 "name": "secret_scope_id_idx",
1056− "columns": [
1057− "scope_id"
1058− ],
969+ "columns": ["scope_id"],
1059970 "isUnique": false
1060971 },
1061972 "secret_provider_idx": {
1062973 "name": "secret_provider_idx",
1063− "columns": [
1064− "provider"
1065− ],
974+ "columns": ["provider"],
1066975 "isUnique": false
1067976 },
1068977 "secret_owned_by_connection_id_idx": {
1069978 "name": "secret_owned_by_connection_id_idx",
1070− "columns": [
1071− "owned_by_connection_id"
1072− ],
979+ "columns": ["owned_by_connection_id"],
1073980 "isUnique": false
1074981 }
1075982 },
1076983 "foreignKeys": {},
1077984 "compositePrimaryKeys": {
1078985 "secret_scope_id_id_pk": {
1079− "columns": [
1080− "scope_id",
1081− "id"
1082− ],
986+ "columns": ["scope_id", "id"],
1083987 "name": "secret_scope_id_id_pk"
1084988 }
1085989 },
@@ -1173,26 +1077,19 @@ · 87 unmodified lines
11731077 "indexes": {
11741078 "source_scope_id_idx": {
11751079 "name": "source_scope_id_idx",
1176− "columns": [
1177− "scope_id"
1178− ],
1080+ "columns": ["scope_id"],
11791081 "isUnique": false
11801082 },
11811083 "source_plugin_id_idx": {
11821084 "name": "source_plugin_id_idx",
1183− "columns": [
1184− "plugin_id"
1185− ],
1085+ "columns": ["plugin_id"],
11861086 "isUnique": false
11871087 }
11881088 },
11891089 "foreignKeys": {},
11901090 "compositePrimaryKeys": {
11911091 "source_scope_id_id_pk": {
1192− "columns": [
1193− "scope_id",
1194− "id"
1195− ],
1092+ "columns": ["scope_id", "id"],
11961093 "name": "source_scope_id_id_pk"
11971094 }
11981095 },
@@ -1276,33 +1173,24 @@ · 77 unmodified lines
12761173 "indexes": {
12771174 "tool_scope_id_idx": {
12781175 "name": "tool_scope_id_idx",
1279− "columns": [
1280− "scope_id"
1281− ],
1176+ "columns": ["scope_id"],
12821177 "isUnique": false
12831178 },
12841179 "tool_source_id_idx": {
12851180 "name": "tool_source_id_idx",
1286− "columns": [
1287− "source_id"
1288− ],
1181+ "columns": ["source_id"],
12891182 "isUnique": false
12901183 },
12911184 "tool_plugin_id_idx": {
12921185 "name": "tool_plugin_id_idx",
1293− "columns": [
1294− "plugin_id"
1295− ],
1186+ "columns": ["plugin_id"],
12961187 "isUnique": false
12971188 }
12981189 },
12991190 "foreignKeys": {},
13001191 "compositePrimaryKeys": {
13011192 "tool_scope_id_id_pk": {
1302− "columns": [
1303− "scope_id",
1304− "id"
1305− ],
1193+ "columns": ["scope_id", "id"],
13061194 "name": "tool_scope_id_id_pk"
13071195 }
13081196 },
@@ -1338,10 +1226,7 @@ · 29 unmodified lines
13381226 "foreignKeys": {},
13391227 "compositePrimaryKeys": {
13401228 "blob_namespace_key_pk": {
1341− "columns": [
1342− "namespace",
1343− "key"
1344− ],
1229+ "columns": ["namespace", "key"],
13451230 "name": "blob_namespace_key_pk"
13461231 }
13471232 },
apps/local/drizzle/meta/0004_snapshot.json+51−172
@@ -88,26 +88,19 @@ · 87 unmodified lines
8888 "indexes": {
8989 "connection_scope_id_idx": {
9090 "name": "connection_scope_id_idx",
91− "columns": [
92− "scope_id"
93− ],
91+ "columns": ["scope_id"],
9492 "isUnique": false
9593 },
9694 "connection_provider_idx": {
9795 "name": "connection_provider_idx",
98− "columns": [
99− "provider"
100− ],
96+ "columns": ["provider"],
10197 "isUnique": false
10298 }
10399 },
104100 "foreignKeys": {},
105101 "compositePrimaryKeys": {
106102 "connection_scope_id_id_pk": {
107− "columns": [
108− "scope_id",
109− "id"
110− ],
103+ "columns": ["scope_id", "id"],
111104 "name": "connection_scope_id_id_pk"
112105 }
113106 },
@@ -170,33 +163,24 @@ · 56 unmodified lines
170163 "indexes": {
171164 "definition_scope_id_idx": {
172165 "name": "definition_scope_id_idx",
173− "columns": [
174− "scope_id"
175− ],
166+ "columns": ["scope_id"],
176167 "isUnique": false
177168 },
178169 "definition_source_id_idx": {
179170 "name": "definition_source_id_idx",
180− "columns": [
181− "source_id"
182− ],
171+ "columns": ["source_id"],
183172 "isUnique": false
184173 },
185174 "definition_plugin_id_idx": {
186175 "name": "definition_plugin_id_idx",
187− "columns": [
188− "plugin_id"
189− ],
176+ "columns": ["plugin_id"],
190177 "isUnique": false
191178 }
192179 },
193180 "foreignKeys": {},
194181 "compositePrimaryKeys": {
195182 "definition_scope_id_id_pk": {
196− "columns": [
197− "scope_id",
198− "id"
199− ],
183+ "columns": ["scope_id", "id"],
200184 "name": "definition_scope_id_id_pk"
201185 }
202186 },
@@ -245,26 +229,19 @@ · 42 unmodified lines
245229 "indexes": {
246230 "google_discovery_binding_scope_id_idx": {
247231 "name": "google_discovery_binding_scope_id_idx",
248− "columns": [
249− "scope_id"
250− ],
232+ "columns": ["scope_id"],
251233 "isUnique": false
252234 },
253235 "google_discovery_binding_source_id_idx": {
254236 "name": "google_discovery_binding_source_id_idx",
255− "columns": [
256− "source_id"
257− ],
237+ "columns": ["source_id"],
258238 "isUnique": false
259239 }
260240 },
261241 "foreignKeys": {},
262242 "compositePrimaryKeys": {
263243 "google_discovery_binding_scope_id_id_pk": {
264− "columns": [
265− "scope_id",
266− "id"
267− ],
244+ "columns": ["scope_id", "id"],
268245 "name": "google_discovery_binding_scope_id_id_pk"
269246 }
270247 },
@@ -306,19 +283,14 @@ · 35 unmodified lines
306283 "indexes": {
307284 "google_discovery_oauth_session_scope_id_idx": {
308285 "name": "google_discovery_oauth_session_scope_id_idx",
309− "columns": [
310− "scope_id"
311− ],
286+ "columns": ["scope_id"],
312287 "isUnique": false
313288 }
314289 },
315290 "foreignKeys": {},
316291 "compositePrimaryKeys": {
317292 "google_discovery_oauth_session_scope_id_id_pk": {
318− "columns": [
319− "scope_id",
320− "id"
321− ],
293+ "columns": ["scope_id", "id"],
322294 "name": "google_discovery_oauth_session_scope_id_id_pk"
323295 }
324296 },
@@ -374,19 +346,14 @@ · 49 unmodified lines
374346 "indexes": {
375347 "google_discovery_source_scope_id_idx": {
376348 "name": "google_discovery_source_scope_id_idx",
377− "columns": [
378− "scope_id"
379− ],
349+ "columns": ["scope_id"],
380350 "isUnique": false
381351 }
382352 },
383353 "foreignKeys": {},
384354 "compositePrimaryKeys": {
385355 "google_discovery_source_scope_id_id_pk": {
386− "columns": [
387− "scope_id",
388− "id"
389− ],
356+ "columns": ["scope_id", "id"],
390357 "name": "google_discovery_source_scope_id_id_pk"
391358 }
392359 },
@@ -428,26 +395,19 @@ · 35 unmodified lines
428395 "indexes": {
429396 "graphql_operation_scope_id_idx": {
430397 "name": "graphql_operation_scope_id_idx",
431− "columns": [
432− "scope_id"
433− ],
398+ "columns": ["scope_id"],
434399 "isUnique": false
435400 },
436401 "graphql_operation_source_id_idx": {
437402 "name": "graphql_operation_source_id_idx",
438− "columns": [
439− "source_id"
440− ],
403+ "columns": ["source_id"],
441404 "isUnique": false
442405 }
443406 },
444407 "foreignKeys": {},
445408 "compositePrimaryKeys": {
446409 "graphql_operation_scope_id_id_pk": {
447− "columns": [
448− "scope_id",
449− "id"
450− ],
410+ "columns": ["scope_id", "id"],
451411 "name": "graphql_operation_scope_id_id_pk"
452412 }
453413 },
@@ -496,19 +456,14 @@ · 42 unmodified lines
496456 "indexes": {
497457 "graphql_source_scope_id_idx": {
498458 "name": "graphql_source_scope_id_idx",
499− "columns": [
500− "scope_id"
501− ],
459+ "columns": ["scope_id"],
502460 "isUnique": false
503461 }
504462 },
505463 "foreignKeys": {},
506464 "compositePrimaryKeys": {
507465 "graphql_source_scope_id_id_pk": {
508− "columns": [
509− "scope_id",
510− "id"
511− ],
466+ "columns": ["scope_id", "id"],
512467 "name": "graphql_source_scope_id_id_pk"
513468 }
514469 },
@@ -557,26 +512,19 @@ · 42 unmodified lines
557512 "indexes": {
558513 "mcp_binding_scope_id_idx": {
559514 "name": "mcp_binding_scope_id_idx",
560− "columns": [
561− "scope_id"
562− ],
515+ "columns": ["scope_id"],
563516 "isUnique": false
564517 },
565518 "mcp_binding_source_id_idx": {
566519 "name": "mcp_binding_source_id_idx",
567− "columns": [
568− "source_id"
569− ],
520+ "columns": ["source_id"],
570521 "isUnique": false
571522 }
572523 },
573524 "foreignKeys": {},
574525 "compositePrimaryKeys": {
575526 "mcp_binding_scope_id_id_pk": {
576− "columns": [
577− "scope_id",
578− "id"
579− ],
527+ "columns": ["scope_id", "id"],
580528 "name": "mcp_binding_scope_id_id_pk"
581529 }
582530 },
@@ -625,19 +573,14 @@ · 42 unmodified lines
625573 "indexes": {
626574 "mcp_oauth_session_scope_id_idx": {
627575 "name": "mcp_oauth_session_scope_id_idx",
628− "columns": [
629− "scope_id"
630− ],
576+ "columns": ["scope_id"],
631577 "isUnique": false
632578 }
633579 },
634580 "foreignKeys": {},
635581 "compositePrimaryKeys": {
636582 "mcp_oauth_session_scope_id_id_pk": {
637− "columns": [
638− "scope_id",
639− "id"
640− ],
583+ "columns": ["scope_id", "id"],
641584 "name": "mcp_oauth_session_scope_id_id_pk"
642585 }
643586 },
@@ -686,19 +629,14 @@ · 42 unmodified lines
686629 "indexes": {
687630 "mcp_source_scope_id_idx": {
688631 "name": "mcp_source_scope_id_idx",
689− "columns": [
690− "scope_id"
691− ],
632+ "columns": ["scope_id"],
692633 "isUnique": false
693634 }
694635 },
695636 "foreignKeys": {},
696637 "compositePrimaryKeys": {
697638 "mcp_source_scope_id_id_pk": {
698− "columns": [
699− "scope_id",
700− "id"
701− ],
639+ "columns": ["scope_id", "id"],
702640 "name": "mcp_source_scope_id_id_pk"
703641 }
704642 },
@@ -740,19 +678,14 @@ · 35 unmodified lines
740678 "indexes": {
741679 "openapi_oauth_session_scope_id_idx": {
742680 "name": "openapi_oauth_session_scope_id_idx",
743− "columns": [
744− "scope_id"
745− ],
681+ "columns": ["scope_id"],
746682 "isUnique": false
747683 }
748684 },
749685 "foreignKeys": {},
750686 "compositePrimaryKeys": {
751687 "openapi_oauth_session_scope_id_id_pk": {
752− "columns": [
753− "scope_id",
754− "id"
755− ],
688+ "columns": ["scope_id", "id"],
756689 "name": "openapi_oauth_session_scope_id_id_pk"
757690 }
758691 },
@@ -794,26 +727,19 @@ · 35 unmodified lines
794727 "indexes": {
795728 "openapi_operation_scope_id_idx": {
796729 "name": "openapi_operation_scope_id_idx",
797− "columns": [
798− "scope_id"
799− ],
730+ "columns": ["scope_id"],
800731 "isUnique": false
801732 },
802733 "openapi_operation_source_id_idx": {
803734 "name": "openapi_operation_source_id_idx",
804− "columns": [
805− "source_id"
806− ],
735+ "columns": ["source_id"],
807736 "isUnique": false
808737 }
809738 },
810739 "foreignKeys": {},
811740 "compositePrimaryKeys": {
812741 "openapi_operation_scope_id_id_pk": {
813− "columns": [
814− "scope_id",
815− "id"
816− ],
742+ "columns": ["scope_id", "id"],
817743 "name": "openapi_operation_scope_id_id_pk"
818744 }
819745 },
@@ -890,19 +816,14 @@ · 70 unmodified lines
890816 "indexes": {
891817 "openapi_source_scope_id_idx": {
892818 "name": "openapi_source_scope_id_idx",
893− "columns": [
894− "scope_id"
895− ],
819+ "columns": ["scope_id"],
896820 "isUnique": false
897821 }
898822 },
899823 "foreignKeys": {},
900824 "compositePrimaryKeys": {
901825 "openapi_source_scope_id_id_pk": {
902− "columns": [
903− "scope_id",
904− "id"
905− ],
826+ "columns": ["scope_id", "id"],
906827 "name": "openapi_source_scope_id_id_pk"
907828 }
908829 },
@@ -972,30 +893,22 @@ · 63 unmodified lines
972893 "indexes": {
973894 "openapi_source_binding_source_id_idx": {
974895 "name": "openapi_source_binding_source_id_idx",
975− "columns": [
976− "source_id"
977− ],
896+ "columns": ["source_id"],
978897 "isUnique": false
979898 },
980899 "openapi_source_binding_source_scope_id_idx": {
981900 "name": "openapi_source_binding_source_scope_id_idx",
982− "columns": [
983− "source_scope_id"
984− ],
901+ "columns": ["source_scope_id"],
985902 "isUnique": false
986903 },
987904 "openapi_source_binding_target_scope_id_idx": {
988905 "name": "openapi_source_binding_target_scope_id_idx",
989− "columns": [
990− "target_scope_id"
991− ],
906+ "columns": ["target_scope_id"],
992907 "isUnique": false
993908 },
994909 "openapi_source_binding_slot_idx": {
995910 "name": "openapi_source_binding_slot_idx",
996− "columns": [
997− "slot"
998− ],
911+ "columns": ["slot"],
999912 "isUnique": false
1000913 }
1001914 },
@@ -1053,33 +966,24 @@ · 51 unmodified lines
1053966 "indexes": {
1054967 "secret_scope_id_idx": {
1055968 "name": "secret_scope_id_idx",
1056− "columns": [
1057− "scope_id"
1058− ],
969+ "columns": ["scope_id"],
1059970 "isUnique": false
1060971 },
1061972 "secret_provider_idx": {
1062973 "name": "secret_provider_idx",
1063− "columns": [
1064− "provider"
1065− ],
974+ "columns": ["provider"],
1066975 "isUnique": false
1067976 },
1068977 "secret_owned_by_connection_id_idx": {
1069978 "name": "secret_owned_by_connection_id_idx",
1070− "columns": [
1071− "owned_by_connection_id"
1072− ],
979+ "columns": ["owned_by_connection_id"],
1073980 "isUnique": false
1074981 }
1075982 },
1076983 "foreignKeys": {},
1077984 "compositePrimaryKeys": {
1078985 "secret_scope_id_id_pk": {
1079− "columns": [
1080− "scope_id",
1081− "id"
1082− ],
986+ "columns": ["scope_id", "id"],
1083987 "name": "secret_scope_id_id_pk"
1084988 }
1085989 },
@@ -1173,26 +1077,19 @@ · 87 unmodified lines
11731077 "indexes": {
11741078 "source_scope_id_idx": {
11751079 "name": "source_scope_id_idx",
1176− "columns": [
1177− "scope_id"
1178− ],
1080+ "columns": ["scope_id"],
11791081 "isUnique": false
11801082 },
11811083 "source_plugin_id_idx": {
11821084 "name": "source_plugin_id_idx",
1183− "columns": [
1184− "plugin_id"
1185− ],
1085+ "columns": ["plugin_id"],
11861086 "isUnique": false
11871087 }
11881088 },
11891089 "foreignKeys": {},
11901090 "compositePrimaryKeys": {
11911091 "source_scope_id_id_pk": {
1192− "columns": [
1193− "scope_id",
1194− "id"
1195− ],
1092+ "columns": ["scope_id", "id"],
11961093 "name": "source_scope_id_id_pk"
11971094 }
11981095 },
@@ -1276,33 +1173,24 @@ · 77 unmodified lines
12761173 "indexes": {
12771174 "tool_scope_id_idx": {
12781175 "name": "tool_scope_id_idx",
1279− "columns": [
1280− "scope_id"
1281− ],
1176+ "columns": ["scope_id"],
12821177 "isUnique": false
12831178 },
12841179 "tool_source_id_idx": {
12851180 "name": "tool_source_id_idx",
1286− "columns": [
1287− "source_id"
1288− ],
1181+ "columns": ["source_id"],
12891182 "isUnique": false
12901183 },
12911184 "tool_plugin_id_idx": {
12921185 "name": "tool_plugin_id_idx",
1293− "columns": [
1294− "plugin_id"
1295− ],
1186+ "columns": ["plugin_id"],
12961187 "isUnique": false
12971188 }
12981189 },
12991190 "foreignKeys": {},
13001191 "compositePrimaryKeys": {
13011192 "tool_scope_id_id_pk": {
1302− "columns": [
1303− "scope_id",
1304− "id"
1305− ],
1193+ "columns": ["scope_id", "id"],
13061194 "name": "tool_scope_id_id_pk"
13071195 }
13081196 },
@@ -1365,20 +1253,14 @@ · 56 unmodified lines
13651253 "indexes": {
13661254 "tool_policy_scope_id_position_idx": {
13671255 "name": "tool_policy_scope_id_position_idx",
1368− "columns": [
1369− "scope_id",
1370− "position"
1371− ],
1256+ "columns": ["scope_id", "position"],
13721257 "isUnique": false
13731258 }
13741259 },
13751260 "foreignKeys": {},
13761261 "compositePrimaryKeys": {
13771262 "tool_policy_scope_id_id_pk": {
1378− "columns": [
1379− "scope_id",
1380− "id"
1381− ],
1263+ "columns": ["scope_id", "id"],
13821264 "name": "tool_policy_scope_id_id_pk"
13831265 }
13841266 },
@@ -1414,10 +1296,7 @@ · 29 unmodified lines
14141296 "foreignKeys": {},
14151297 "compositePrimaryKeys": {
14161298 "blob_namespace_key_pk": {
1417− "columns": [
1418− "namespace",
1419− "key"
1420− ],
1299+ "columns": ["namespace", "key"],
14211300 "name": "blob_namespace_key_pk"
14221301 }
14231302 },
apps/local/drizzle/meta/0005_snapshot.json+51−172
@@ -88,26 +88,19 @@ · 87 unmodified lines
8888 "indexes": {
8989 "connection_scope_id_idx": {
9090 "name": "connection_scope_id_idx",
91− "columns": [
92− "scope_id"
93− ],
91+ "columns": ["scope_id"],
9492 "isUnique": false
9593 },
9694 "connection_provider_idx": {
9795 "name": "connection_provider_idx",
98− "columns": [
99− "provider"
100− ],
96+ "columns": ["provider"],
10197 "isUnique": false
10298 }
10399 },
104100 "foreignKeys": {},
105101 "compositePrimaryKeys": {
106102 "connection_scope_id_id_pk": {
107− "columns": [
108− "scope_id",
109− "id"
110− ],
103+ "columns": ["scope_id", "id"],
111104 "name": "connection_scope_id_id_pk"
112105 }
113106 },
@@ -170,33 +163,24 @@ · 56 unmodified lines
170163 "indexes": {
171164 "definition_scope_id_idx": {
172165 "name": "definition_scope_id_idx",
173− "columns": [
174− "scope_id"
175− ],
166+ "columns": ["scope_id"],
176167 "isUnique": false
177168 },
178169 "definition_source_id_idx": {
179170 "name": "definition_source_id_idx",
180− "columns": [
181− "source_id"
182− ],
171+ "columns": ["source_id"],
183172 "isUnique": false
184173 },
185174 "definition_plugin_id_idx": {
186175 "name": "definition_plugin_id_idx",
187− "columns": [
188− "plugin_id"
189− ],
176+ "columns": ["plugin_id"],
190177 "isUnique": false
191178 }
192179 },
193180 "foreignKeys": {},
194181 "compositePrimaryKeys": {
195182 "definition_scope_id_id_pk": {
196− "columns": [
197− "scope_id",
198− "id"
199− ],
183+ "columns": ["scope_id", "id"],
200184 "name": "definition_scope_id_id_pk"
201185 }
202186 },
@@ -245,26 +229,19 @@ · 42 unmodified lines
245229 "indexes": {
246230 "google_discovery_binding_scope_id_idx": {
247231 "name": "google_discovery_binding_scope_id_idx",
248− "columns": [
249− "scope_id"
250− ],
232+ "columns": ["scope_id"],
251233 "isUnique": false
252234 },
253235 "google_discovery_binding_source_id_idx": {
254236 "name": "google_discovery_binding_source_id_idx",
255− "columns": [
256− "source_id"
257− ],
237+ "columns": ["source_id"],
258238 "isUnique": false
259239 }
260240 },
261241 "foreignKeys": {},
262242 "compositePrimaryKeys": {
263243 "google_discovery_binding_scope_id_id_pk": {
264− "columns": [
265− "scope_id",
266− "id"
267− ],
244+ "columns": ["scope_id", "id"],
268245 "name": "google_discovery_binding_scope_id_id_pk"
269246 }
270247 },
@@ -306,19 +283,14 @@ · 35 unmodified lines
306283 "indexes": {
307284 "google_discovery_oauth_session_scope_id_idx": {
308285 "name": "google_discovery_oauth_session_scope_id_idx",
309− "columns": [
310− "scope_id"
311− ],
286+ "columns": ["scope_id"],
312287 "isUnique": false
313288 }
314289 },
315290 "foreignKeys": {},
316291 "compositePrimaryKeys": {
317292 "google_discovery_oauth_session_scope_id_id_pk": {
318− "columns": [
319− "scope_id",
320− "id"
321− ],
293+ "columns": ["scope_id", "id"],
322294 "name": "google_discovery_oauth_session_scope_id_id_pk"
323295 }
324296 },
@@ -374,19 +346,14 @@ · 49 unmodified lines
374346 "indexes": {
375347 "google_discovery_source_scope_id_idx": {
376348 "name": "google_discovery_source_scope_id_idx",
377− "columns": [
378− "scope_id"
379− ],
349+ "columns": ["scope_id"],
380350 "isUnique": false
381351 }
382352 },
383353 "foreignKeys": {},
384354 "compositePrimaryKeys": {
385355 "google_discovery_source_scope_id_id_pk": {
386− "columns": [
387− "scope_id",
388− "id"
389− ],
356+ "columns": ["scope_id", "id"],
390357 "name": "google_discovery_source_scope_id_id_pk"
391358 }
392359 },
@@ -428,26 +395,19 @@ · 35 unmodified lines
428395 "indexes": {
429396 "graphql_operation_scope_id_idx": {
430397 "name": "graphql_operation_scope_id_idx",
431− "columns": [
432− "scope_id"
433− ],
398+ "columns": ["scope_id"],
434399 "isUnique": false
435400 },
436401 "graphql_operation_source_id_idx": {
437402 "name": "graphql_operation_source_id_idx",
438− "columns": [
439− "source_id"
440− ],
403+ "columns": ["source_id"],
441404 "isUnique": false
442405 }
443406 },
444407 "foreignKeys": {},
445408 "compositePrimaryKeys": {
446409 "graphql_operation_scope_id_id_pk": {
447− "columns": [
448− "scope_id",
449− "id"
450− ],
410+ "columns": ["scope_id", "id"],
451411 "name": "graphql_operation_scope_id_id_pk"
452412 }
453413 },
@@ -496,19 +456,14 @@ · 42 unmodified lines
496456 "indexes": {
497457 "graphql_source_scope_id_idx": {
498458 "name": "graphql_source_scope_id_idx",
499− "columns": [
500− "scope_id"
501− ],
459+ "columns": ["scope_id"],
502460 "isUnique": false
503461 }
504462 },
505463 "foreignKeys": {},
506464 "compositePrimaryKeys": {
507465 "graphql_source_scope_id_id_pk": {
508− "columns": [
509− "scope_id",
510− "id"
511− ],
466+ "columns": ["scope_id", "id"],
512467 "name": "graphql_source_scope_id_id_pk"
513468 }
514469 },
@@ -557,26 +512,19 @@ · 42 unmodified lines
557512 "indexes": {
558513 "mcp_binding_scope_id_idx": {
559514 "name": "mcp_binding_scope_id_idx",
560− "columns": [
561− "scope_id"
562− ],
515+ "columns": ["scope_id"],
563516 "isUnique": false
564517 },
565518 "mcp_binding_source_id_idx": {
566519 "name": "mcp_binding_source_id_idx",
567− "columns": [
568− "source_id"
569− ],
520+ "columns": ["source_id"],
570521 "isUnique": false
571522 }
572523 },
573524 "foreignKeys": {},
574525 "compositePrimaryKeys": {
575526 "mcp_binding_scope_id_id_pk": {
576− "columns": [
577− "scope_id",
578− "id"
579− ],
527+ "columns": ["scope_id", "id"],
580528 "name": "mcp_binding_scope_id_id_pk"
581529 }
582530 },
@@ -625,19 +573,14 @@ · 42 unmodified lines
625573 "indexes": {
626574 "mcp_oauth_session_scope_id_idx": {
627575 "name": "mcp_oauth_session_scope_id_idx",
628− "columns": [
629− "scope_id"
630− ],
576+ "columns": ["scope_id"],
631577 "isUnique": false
632578 }
633579 },
634580 "foreignKeys": {},
635581 "compositePrimaryKeys": {
636582 "mcp_oauth_session_scope_id_id_pk": {
637− "columns": [
638− "scope_id",
639− "id"
640− ],
583+ "columns": ["scope_id", "id"],
641584 "name": "mcp_oauth_session_scope_id_id_pk"
642585 }
643586 },
@@ -686,19 +629,14 @@ · 42 unmodified lines
686629 "indexes": {
687630 "mcp_source_scope_id_idx": {
688631 "name": "mcp_source_scope_id_idx",
689− "columns": [
690− "scope_id"
691− ],
632+ "columns": ["scope_id"],
692633 "isUnique": false
693634 }
694635 },
695636 "foreignKeys": {},
696637 "compositePrimaryKeys": {
697638 "mcp_source_scope_id_id_pk": {
698− "columns": [
699− "scope_id",
700− "id"
701− ],
639+ "columns": ["scope_id", "id"],
702640 "name": "mcp_source_scope_id_id_pk"
703641 }
704642 },
@@ -740,19 +678,14 @@ · 35 unmodified lines
740678 "indexes": {
741679 "openapi_oauth_session_scope_id_idx": {
742680 "name": "openapi_oauth_session_scope_id_idx",
743− "columns": [
744− "scope_id"
745− ],
681+ "columns": ["scope_id"],
746682 "isUnique": false
747683 }
748684 },
749685 "foreignKeys": {},
750686 "compositePrimaryKeys": {
751687 "openapi_oauth_session_scope_id_id_pk": {
752− "columns": [
753− "scope_id",
754− "id"
755− ],
688+ "columns": ["scope_id", "id"],
756689 "name": "openapi_oauth_session_scope_id_id_pk"
757690 }
758691 },
@@ -794,26 +727,19 @@ · 35 unmodified lines
794727 "indexes": {
795728 "openapi_operation_scope_id_idx": {
796729 "name": "openapi_operation_scope_id_idx",
797− "columns": [
798− "scope_id"
799− ],
730+ "columns": ["scope_id"],
800731 "isUnique": false
801732 },
802733 "openapi_operation_source_id_idx": {
803734 "name": "openapi_operation_source_id_idx",
804− "columns": [
805− "source_id"
806− ],
735+ "columns": ["source_id"],
807736 "isUnique": false
808737 }
809738 },
810739 "foreignKeys": {},
811740 "compositePrimaryKeys": {
812741 "openapi_operation_scope_id_id_pk": {
813− "columns": [
814− "scope_id",
815− "id"
816− ],
742+ "columns": ["scope_id", "id"],
817743 "name": "openapi_operation_scope_id_id_pk"
818744 }
819745 },
@@ -890,19 +816,14 @@ · 70 unmodified lines
890816 "indexes": {
891817 "openapi_source_scope_id_idx": {
892818 "name": "openapi_source_scope_id_idx",
893− "columns": [
894− "scope_id"
895− ],
819+ "columns": ["scope_id"],
896820 "isUnique": false
897821 }
898822 },
899823 "foreignKeys": {},
900824 "compositePrimaryKeys": {
901825 "openapi_source_scope_id_id_pk": {
902− "columns": [
903− "scope_id",
904− "id"
905− ],
826+ "columns": ["scope_id", "id"],
906827 "name": "openapi_source_scope_id_id_pk"
907828 }
908829 },
@@ -972,30 +893,22 @@ · 63 unmodified lines
972893 "indexes": {
973894 "openapi_source_binding_source_id_idx": {
974895 "name": "openapi_source_binding_source_id_idx",
975− "columns": [
976− "source_id"
977− ],
896+ "columns": ["source_id"],
978897 "isUnique": false
979898 },
980899 "openapi_source_binding_source_scope_id_idx": {
981900 "name": "openapi_source_binding_source_scope_id_idx",
982− "columns": [
983− "source_scope_id"
984− ],
901+ "columns": ["source_scope_id"],
985902 "isUnique": false
986903 },
987904 "openapi_source_binding_target_scope_id_idx": {
988905 "name": "openapi_source_binding_target_scope_id_idx",
989− "columns": [
990− "target_scope_id"
991− ],
906+ "columns": ["target_scope_id"],
992907 "isUnique": false
993908 },
994909 "openapi_source_binding_slot_idx": {
995910 "name": "openapi_source_binding_slot_idx",
996− "columns": [
997− "slot"
998− ],
911+ "columns": ["slot"],
999912 "isUnique": false
1000913 }
1001914 },
@@ -1053,33 +966,24 @@ · 51 unmodified lines
1053966 "indexes": {
1054967 "secret_scope_id_idx": {
1055968 "name": "secret_scope_id_idx",
1056− "columns": [
1057− "scope_id"
1058− ],
969+ "columns": ["scope_id"],
1059970 "isUnique": false
1060971 },
1061972 "secret_provider_idx": {
1062973 "name": "secret_provider_idx",
1063− "columns": [
1064− "provider"
1065− ],
974+ "columns": ["provider"],
1066975 "isUnique": false
1067976 },
1068977 "secret_owned_by_connection_id_idx": {
1069978 "name": "secret_owned_by_connection_id_idx",
1070− "columns": [
1071− "owned_by_connection_id"
1072− ],
979+ "columns": ["owned_by_connection_id"],
1073980 "isUnique": false
1074981 }
1075982 },
1076983 "foreignKeys": {},
1077984 "compositePrimaryKeys": {
1078985 "secret_scope_id_id_pk": {
1079− "columns": [
1080− "scope_id",
1081− "id"
1082− ],
986+ "columns": ["scope_id", "id"],
1083987 "name": "secret_scope_id_id_pk"
1084988 }
1085989 },
@@ -1173,26 +1077,19 @@ · 87 unmodified lines
11731077 "indexes": {
11741078 "source_scope_id_idx": {
11751079 "name": "source_scope_id_idx",
1176− "columns": [
1177− "scope_id"
1178− ],
1080+ "columns": ["scope_id"],
11791081 "isUnique": false
11801082 },
11811083 "source_plugin_id_idx": {
11821084 "name": "source_plugin_id_idx",
1183− "columns": [
1184− "plugin_id"
1185− ],
1085+ "columns": ["plugin_id"],
11861086 "isUnique": false
11871087 }
11881088 },
11891089 "foreignKeys": {},
11901090 "compositePrimaryKeys": {
11911091 "source_scope_id_id_pk": {
1192− "columns": [
1193− "scope_id",
1194− "id"
1195− ],
1092+ "columns": ["scope_id", "id"],
11961093 "name": "source_scope_id_id_pk"
11971094 }
11981095 },
@@ -1276,33 +1173,24 @@ · 77 unmodified lines
12761173 "indexes": {
12771174 "tool_scope_id_idx": {
12781175 "name": "tool_scope_id_idx",
1279− "columns": [
1280− "scope_id"
1281− ],
1176+ "columns": ["scope_id"],
12821177 "isUnique": false
12831178 },
12841179 "tool_source_id_idx": {
12851180 "name": "tool_source_id_idx",
1286− "columns": [
1287− "source_id"
1288− ],
1181+ "columns": ["source_id"],
12891182 "isUnique": false
12901183 },
12911184 "tool_plugin_id_idx": {
12921185 "name": "tool_plugin_id_idx",
1293− "columns": [
1294− "plugin_id"
1295− ],
1186+ "columns": ["plugin_id"],
12961187 "isUnique": false
12971188 }
12981189 },
12991190 "foreignKeys": {},
13001191 "compositePrimaryKeys": {
13011192 "tool_scope_id_id_pk": {
1302− "columns": [
1303− "scope_id",
1304− "id"
1305− ],
1193+ "columns": ["scope_id", "id"],
13061194 "name": "tool_scope_id_id_pk"
13071195 }
13081196 },
@@ -1365,20 +1253,14 @@ · 56 unmodified lines
13651253 "indexes": {
13661254 "tool_policy_scope_id_position_idx": {
13671255 "name": "tool_policy_scope_id_position_idx",
1368− "columns": [
1369− "scope_id",
1370− "position"
1371− ],
1256+ "columns": ["scope_id", "position"],
13721257 "isUnique": false
13731258 }
13741259 },
13751260 "foreignKeys": {},
13761261 "compositePrimaryKeys": {
13771262 "tool_policy_scope_id_id_pk": {
1378− "columns": [
1379− "scope_id",
1380− "id"
1381− ],
1263+ "columns": ["scope_id", "id"],
13821264 "name": "tool_policy_scope_id_id_pk"
13831265 }
13841266 },
@@ -1414,10 +1296,7 @@ · 29 unmodified lines
14141296 "foreignKeys": {},
14151297 "compositePrimaryKeys": {
14161298 "blob_namespace_key_pk": {
1417− "columns": [
1418− "namespace",
1419− "key"
1420− ],
1299+ "columns": ["namespace", "key"],
14211300 "name": "blob_namespace_key_pk"
14221301 }
14231302 },
apps/local/drizzle/meta/0006_snapshot.json+50−165
@@ -33,10 +33,7 @@ · 32 unmodified lines
3333 "foreignKeys": {},
3434 "compositePrimaryKeys": {
3535 "blob_namespace_key_pk": {
36− "columns": [
37− "namespace",
38− "key"
39− ],
36+ "columns": ["namespace", "key"],
4037 "name": "blob_namespace_key_pk"
4138 }
4239 },
@@ -127,26 +124,19 @@ · 84 unmodified lines
127124 "indexes": {
128125 "connection_scope_id_idx": {
129126 "name": "connection_scope_id_idx",
130− "columns": [
131− "scope_id"
132− ],
127+ "columns": ["scope_id"],
133128 "isUnique": false
134129 },
135130 "connection_provider_idx": {
136131 "name": "connection_provider_idx",
137− "columns": [
138− "provider"
139− ],
132+ "columns": ["provider"],
140133 "isUnique": false
141134 }
142135 },
143136 "foreignKeys": {},
144137 "compositePrimaryKeys": {
145138 "connection_scope_id_id_pk": {
146− "columns": [
147− "scope_id",
148− "id"
149− ],
139+ "columns": ["scope_id", "id"],
150140 "name": "connection_scope_id_id_pk"
151141 }
152142 },
@@ -209,33 +199,24 @@ · 56 unmodified lines
209199 "indexes": {
210200 "definition_scope_id_idx": {
211201 "name": "definition_scope_id_idx",
212− "columns": [
213− "scope_id"
214− ],
202+ "columns": ["scope_id"],
215203 "isUnique": false
216204 },
217205 "definition_source_id_idx": {
218206 "name": "definition_source_id_idx",
219− "columns": [
220− "source_id"
221− ],
207+ "columns": ["source_id"],
222208 "isUnique": false
223209 },
224210 "definition_plugin_id_idx": {
225211 "name": "definition_plugin_id_idx",
226− "columns": [
227− "plugin_id"
228− ],
212+ "columns": ["plugin_id"],
229213 "isUnique": false
230214 }
231215 },
232216 "foreignKeys": {},
233217 "compositePrimaryKeys": {
234218 "definition_scope_id_id_pk": {
235− "columns": [
236− "scope_id",
237− "id"
238− ],
219+ "columns": ["scope_id", "id"],
239220 "name": "definition_scope_id_id_pk"
240221 }
241222 },
@@ -284,26 +265,19 @@ · 42 unmodified lines
284265 "indexes": {
285266 "google_discovery_binding_scope_id_idx": {
286267 "name": "google_discovery_binding_scope_id_idx",
287− "columns": [
288− "scope_id"
289− ],
268+ "columns": ["scope_id"],
290269 "isUnique": false
291270 },
292271 "google_discovery_binding_source_id_idx": {
293272 "name": "google_discovery_binding_source_id_idx",
294− "columns": [
295− "source_id"
296− ],
273+ "columns": ["source_id"],
297274 "isUnique": false
298275 }
299276 },
300277 "foreignKeys": {},
301278 "compositePrimaryKeys": {
302279 "google_discovery_binding_scope_id_id_pk": {
303− "columns": [
304− "scope_id",
305− "id"
306− ],
280+ "columns": ["scope_id", "id"],
307281 "name": "google_discovery_binding_scope_id_id_pk"
308282 }
309283 },
@@ -359,19 +333,14 @@ · 49 unmodified lines
359333 "indexes": {
360334 "google_discovery_source_scope_id_idx": {
361335 "name": "google_discovery_source_scope_id_idx",
362− "columns": [
363− "scope_id"
364− ],
336+ "columns": ["scope_id"],
365337 "isUnique": false
366338 }
367339 },
368340 "foreignKeys": {},
369341 "compositePrimaryKeys": {
370342 "google_discovery_source_scope_id_id_pk": {
371− "columns": [
372− "scope_id",
373− "id"
374− ],
343+ "columns": ["scope_id", "id"],
375344 "name": "google_discovery_source_scope_id_id_pk"
376345 }
377346 },
@@ -413,26 +382,19 @@ · 35 unmodified lines
413382 "indexes": {
414383 "graphql_operation_scope_id_idx": {
415384 "name": "graphql_operation_scope_id_idx",
416− "columns": [
417− "scope_id"
418− ],
385+ "columns": ["scope_id"],
419386 "isUnique": false
420387 },
421388 "graphql_operation_source_id_idx": {
422389 "name": "graphql_operation_source_id_idx",
423− "columns": [
424− "source_id"
425− ],
390+ "columns": ["source_id"],
426391 "isUnique": false
427392 }
428393 },
429394 "foreignKeys": {},
430395 "compositePrimaryKeys": {
431396 "graphql_operation_scope_id_id_pk": {
432− "columns": [
433− "scope_id",
434− "id"
435− ],
397+ "columns": ["scope_id", "id"],
436398 "name": "graphql_operation_scope_id_id_pk"
437399 }
438400 },
@@ -495,19 +457,14 @@ · 56 unmodified lines
495457 "indexes": {
496458 "graphql_source_scope_id_idx": {
497459 "name": "graphql_source_scope_id_idx",
498− "columns": [
499− "scope_id"
500− ],
460+ "columns": ["scope_id"],
501461 "isUnique": false
502462 }
503463 },
504464 "foreignKeys": {},
505465 "compositePrimaryKeys": {
506466 "graphql_source_scope_id_id_pk": {
507− "columns": [
508− "scope_id",
509− "id"
510− ],
467+ "columns": ["scope_id", "id"],
511468 "name": "graphql_source_scope_id_id_pk"
512469 }
513470 },
@@ -556,26 +513,19 @@ · 42 unmodified lines
556513 "indexes": {
557514 "mcp_binding_scope_id_idx": {
558515 "name": "mcp_binding_scope_id_idx",
559− "columns": [
560− "scope_id"
561− ],
516+ "columns": ["scope_id"],
562517 "isUnique": false
563518 },
564519 "mcp_binding_source_id_idx": {
565520 "name": "mcp_binding_source_id_idx",
566− "columns": [
567− "source_id"
568− ],
521+ "columns": ["source_id"],
569522 "isUnique": false
570523 }
571524 },
572525 "foreignKeys": {},
573526 "compositePrimaryKeys": {
574527 "mcp_binding_scope_id_id_pk": {
575− "columns": [
576− "scope_id",
577− "id"
578− ],
528+ "columns": ["scope_id", "id"],
579529 "name": "mcp_binding_scope_id_id_pk"
580530 }
581531 },
@@ -624,19 +574,14 @@ · 42 unmodified lines
624574 "indexes": {
625575 "mcp_source_scope_id_idx": {
626576 "name": "mcp_source_scope_id_idx",
627− "columns": [
628− "scope_id"
629− ],
577+ "columns": ["scope_id"],
630578 "isUnique": false
631579 }
632580 },
633581 "foreignKeys": {},
634582 "compositePrimaryKeys": {
635583 "mcp_source_scope_id_id_pk": {
636− "columns": [
637− "scope_id",
638− "id"
639− ],
584+ "columns": ["scope_id", "id"],
640585 "name": "mcp_source_scope_id_id_pk"
641586 }
642587 },
@@ -720,33 +665,24 @@ · 77 unmodified lines
720665 "indexes": {
721666 "oauth2_session_scope_id_idx": {
722667 "name": "oauth2_session_scope_id_idx",
723− "columns": [
724− "scope_id"
725− ],
668+ "columns": ["scope_id"],
726669 "isUnique": false
727670 },
728671 "oauth2_session_plugin_id_idx": {
729672 "name": "oauth2_session_plugin_id_idx",
730− "columns": [
731− "plugin_id"
732− ],
673+ "columns": ["plugin_id"],
733674 "isUnique": false
734675 },
735676 "oauth2_session_connection_id_idx": {
736677 "name": "oauth2_session_connection_id_idx",
737− "columns": [
738− "connection_id"
739− ],
678+ "columns": ["connection_id"],
740679 "isUnique": false
741680 }
742681 },
743682 "foreignKeys": {},
744683 "compositePrimaryKeys": {
745684 "oauth2_session_scope_id_id_pk": {
746− "columns": [
747− "scope_id",
748− "id"
749− ],
685+ "columns": ["scope_id", "id"],
750686 "name": "oauth2_session_scope_id_id_pk"
751687 }
752688 },
@@ -788,26 +724,19 @@ · 35 unmodified lines
788724 "indexes": {
789725 "openapi_operation_scope_id_idx": {
790726 "name": "openapi_operation_scope_id_idx",
791− "columns": [
792− "scope_id"
793− ],
727+ "columns": ["scope_id"],
794728 "isUnique": false
795729 },
796730 "openapi_operation_source_id_idx": {
797731 "name": "openapi_operation_source_id_idx",
798− "columns": [
799− "source_id"
800− ],
732+ "columns": ["source_id"],
801733 "isUnique": false
802734 }
803735 },
804736 "foreignKeys": {},
805737 "compositePrimaryKeys": {
806738 "openapi_operation_scope_id_id_pk": {
807− "columns": [
808− "scope_id",
809− "id"
810− ],
739+ "columns": ["scope_id", "id"],
811740 "name": "openapi_operation_scope_id_id_pk"
812741 }
813742 },
@@ -891,19 +820,14 @@ · 77 unmodified lines
891820 "indexes": {
892821 "openapi_source_scope_id_idx": {
893822 "name": "openapi_source_scope_id_idx",
894− "columns": [
895− "scope_id"
896− ],
823+ "columns": ["scope_id"],
897824 "isUnique": false
898825 }
899826 },
900827 "foreignKeys": {},
901828 "compositePrimaryKeys": {
902829 "openapi_source_scope_id_id_pk": {
903− "columns": [
904− "scope_id",
905− "id"
906− ],
830+ "columns": ["scope_id", "id"],
907831 "name": "openapi_source_scope_id_id_pk"
908832 }
909833 },
@@ -973,30 +897,22 @@ · 63 unmodified lines
973897 "indexes": {
974898 "openapi_source_binding_source_id_idx": {
975899 "name": "openapi_source_binding_source_id_idx",
976− "columns": [
977− "source_id"
978− ],
900+ "columns": ["source_id"],
979901 "isUnique": false
980902 },
981903 "openapi_source_binding_source_scope_id_idx": {
982904 "name": "openapi_source_binding_source_scope_id_idx",
983− "columns": [
984− "source_scope_id"
985− ],
905+ "columns": ["source_scope_id"],
986906 "isUnique": false
987907 },
988908 "openapi_source_binding_target_scope_id_idx": {
989909 "name": "openapi_source_binding_target_scope_id_idx",
990− "columns": [
991− "target_scope_id"
992− ],
910+ "columns": ["target_scope_id"],
993911 "isUnique": false
994912 },
995913 "openapi_source_binding_slot_idx": {
996914 "name": "openapi_source_binding_slot_idx",
997− "columns": [
998− "slot"
999− ],
915+ "columns": ["slot"],
1000916 "isUnique": false
1001917 }
1002918 },
@@ -1054,33 +970,24 @@ · 51 unmodified lines
1054970 "indexes": {
1055971 "secret_scope_id_idx": {
1056972 "name": "secret_scope_id_idx",
1057− "columns": [
1058− "scope_id"
1059− ],
973+ "columns": ["scope_id"],
1060974 "isUnique": false
1061975 },
1062976 "secret_provider_idx": {
1063977 "name": "secret_provider_idx",
1064− "columns": [
1065− "provider"
1066− ],
978+ "columns": ["provider"],
1067979 "isUnique": false
1068980 },
1069981 "secret_owned_by_connection_id_idx": {
1070982 "name": "secret_owned_by_connection_id_idx",
1071− "columns": [
1072− "owned_by_connection_id"
1073− ],
983+ "columns": ["owned_by_connection_id"],
1074984 "isUnique": false
1075985 }
1076986 },
1077987 "foreignKeys": {},
1078988 "compositePrimaryKeys": {
1079989 "secret_scope_id_id_pk": {
1080− "columns": [
1081− "scope_id",
1082− "id"
1083− ],
990+ "columns": ["scope_id", "id"],
1084991 "name": "secret_scope_id_id_pk"
1085992 }
1086993 },
@@ -1174,26 +1081,19 @@ · 87 unmodified lines
11741081 "indexes": {
11751082 "source_scope_id_idx": {
11761083 "name": "source_scope_id_idx",
1177− "columns": [
1178− "scope_id"
1179− ],
1084+ "columns": ["scope_id"],
11801085 "isUnique": false
11811086 },
11821087 "source_plugin_id_idx": {
11831088 "name": "source_plugin_id_idx",
1184− "columns": [
1185− "plugin_id"
1186− ],
1089+ "columns": ["plugin_id"],
11871090 "isUnique": false
11881091 }
11891092 },
11901093 "foreignKeys": {},
11911094 "compositePrimaryKeys": {
11921095 "source_scope_id_id_pk": {
1193− "columns": [
1194− "scope_id",
1195− "id"
1196− ],
1096+ "columns": ["scope_id", "id"],
11971097 "name": "source_scope_id_id_pk"
11981098 }
11991099 },
@@ -1277,33 +1177,24 @@ · 77 unmodified lines
12771177 "indexes": {
12781178 "tool_scope_id_idx": {
12791179 "name": "tool_scope_id_idx",
1280− "columns": [
1281− "scope_id"
1282− ],
1180+ "columns": ["scope_id"],
12831181 "isUnique": false
12841182 },
12851183 "tool_source_id_idx": {
12861184 "name": "tool_source_id_idx",
1287− "columns": [
1288− "source_id"
1289− ],
1185+ "columns": ["source_id"],
12901186 "isUnique": false
12911187 },
12921188 "tool_plugin_id_idx": {
12931189 "name": "tool_plugin_id_idx",
1294− "columns": [
1295− "plugin_id"
1296− ],
1190+ "columns": ["plugin_id"],
12971191 "isUnique": false
12981192 }
12991193 },
13001194 "foreignKeys": {},
13011195 "compositePrimaryKeys": {
13021196 "tool_scope_id_id_pk": {
1303− "columns": [
1304− "scope_id",
1305− "id"
1306− ],
1197+ "columns": ["scope_id", "id"],
13071198 "name": "tool_scope_id_id_pk"
13081199 }
13091200 },
@@ -1366,20 +1257,14 @@ · 56 unmodified lines
13661257 "indexes": {
13671258 "tool_policy_scope_id_position_idx": {
13681259 "name": "tool_policy_scope_id_position_idx",
1369− "columns": [
1370− "scope_id",
1371− "position"
1372− ],
1260+ "columns": ["scope_id", "position"],
13731261 "isUnique": false
13741262 }
13751263 },
13761264 "foreignKeys": {},
13771265 "compositePrimaryKeys": {
13781266 "tool_policy_scope_id_id_pk": {
1379− "columns": [
1380− "scope_id",
1381− "id"
1382− ],
1267+ "columns": ["scope_id", "id"],
13831268 "name": "tool_policy_scope_id_id_pk"
13841269 }
13851270 },
@@ -1397,4 +1282,4 @@ · 11 unmodified lines
13971282 "internal": {
13981283 "indexes": {}
13991284 }
1400−}
1285+}
apps/local/drizzle/meta/0007_snapshot.json+95−311
@@ -33,10 +33,7 @@ · 32 unmodified lines
3333 "foreignKeys": {},
3434 "compositePrimaryKeys": {
3535 "blob_namespace_key_pk": {
36− "columns": [
37− "namespace",
38− "key"
39− ],
36+ "columns": ["namespace", "key"],
4037 "name": "blob_namespace_key_pk"
4138 }
4239 },
@@ -127,26 +124,19 @@ · 84 unmodified lines
127124 "indexes": {
128125 "connection_scope_id_idx": {
129126 "name": "connection_scope_id_idx",
130− "columns": [
131− "scope_id"
132− ],
127+ "columns": ["scope_id"],
133128 "isUnique": false
134129 },
135130 "connection_provider_idx": {
136131 "name": "connection_provider_idx",
137− "columns": [
138− "provider"
139− ],
132+ "columns": ["provider"],
140133 "isUnique": false
141134 }
142135 },
143136 "foreignKeys": {},
144137 "compositePrimaryKeys": {
145138 "connection_scope_id_id_pk": {
146− "columns": [
147− "scope_id",
148− "id"
149− ],
139+ "columns": ["scope_id", "id"],
150140 "name": "connection_scope_id_id_pk"
151141 }
152142 },
@@ -209,33 +199,24 @@ · 56 unmodified lines
209199 "indexes": {
210200 "definition_scope_id_idx": {
211201 "name": "definition_scope_id_idx",
212− "columns": [
213− "scope_id"
214− ],
202+ "columns": ["scope_id"],
215203 "isUnique": false
216204 },
217205 "definition_source_id_idx": {
218206 "name": "definition_source_id_idx",
219− "columns": [
220− "source_id"
221− ],
207+ "columns": ["source_id"],
222208 "isUnique": false
223209 },
224210 "definition_plugin_id_idx": {
225211 "name": "definition_plugin_id_idx",
226− "columns": [
227− "plugin_id"
228− ],
212+ "columns": ["plugin_id"],
229213 "isUnique": false
230214 }
231215 },
232216 "foreignKeys": {},
233217 "compositePrimaryKeys": {
234218 "definition_scope_id_id_pk": {
235− "columns": [
236− "scope_id",
237− "id"
238− ],
219+ "columns": ["scope_id", "id"],
239220 "name": "definition_scope_id_id_pk"
240221 }
241222 },
@@ -284,26 +265,19 @@ · 42 unmodified lines
284265 "indexes": {
285266 "google_discovery_binding_scope_id_idx": {
286267 "name": "google_discovery_binding_scope_id_idx",
287− "columns": [
288− "scope_id"
289− ],
268+ "columns": ["scope_id"],
290269 "isUnique": false
291270 },
292271 "google_discovery_binding_source_id_idx": {
293272 "name": "google_discovery_binding_source_id_idx",
294− "columns": [
295− "source_id"
296− ],
273+ "columns": ["source_id"],
297274 "isUnique": false
298275 }
299276 },
300277 "foreignKeys": {},
301278 "compositePrimaryKeys": {
302279 "google_discovery_binding_scope_id_id_pk": {
303− "columns": [
304− "scope_id",
305− "id"
306− ],
280+ "columns": ["scope_id", "id"],
307281 "name": "google_discovery_binding_scope_id_id_pk"
308282 }
309283 },
@@ -395,40 +369,29 @@ · 85 unmodified lines
395369 "indexes": {
396370 "google_discovery_source_scope_id_idx": {
397371 "name": "google_discovery_source_scope_id_idx",
398− "columns": [
399− "scope_id"
400− ],
372+ "columns": ["scope_id"],
401373 "isUnique": false
402374 },
403375 "google_discovery_source_auth_connection_id_idx": {
404376 "name": "google_discovery_source_auth_connection_id_idx",
405− "columns": [
406− "auth_connection_id"
407− ],
377+ "columns": ["auth_connection_id"],
408378 "isUnique": false
409379 },
410380 "google_discovery_source_auth_client_id_secret_id_idx": {
411381 "name": "google_discovery_source_auth_client_id_secret_id_idx",
412− "columns": [
413− "auth_client_id_secret_id"
414− ],
382+ "columns": ["auth_client_id_secret_id"],
415383 "isUnique": false
416384 },
417385 "google_discovery_source_auth_client_secret_secret_id_idx": {
418386 "name": "google_discovery_source_auth_client_secret_secret_id_idx",
419− "columns": [
420− "auth_client_secret_secret_id"
421− ],
387+ "columns": ["auth_client_secret_secret_id"],
422388 "isUnique": false
423389 }
424390 },
425391 "foreignKeys": {},
426392 "compositePrimaryKeys": {
427393 "google_discovery_source_scope_id_id_pk": {
428− "columns": [
429− "scope_id",
430− "id"
431− ],
394+ "columns": ["scope_id", "id"],
432395 "name": "google_discovery_source_scope_id_id_pk"
433396 }
434397 },
@@ -470,26 +433,19 @@ · 35 unmodified lines
470433 "indexes": {
471434 "graphql_operation_scope_id_idx": {
472435 "name": "graphql_operation_scope_id_idx",
473− "columns": [
474− "scope_id"
475− ],
436+ "columns": ["scope_id"],
476437 "isUnique": false
477438 },
478439 "graphql_operation_source_id_idx": {
479440 "name": "graphql_operation_source_id_idx",
480− "columns": [
481− "source_id"
482− ],
441+ "columns": ["source_id"],
483442 "isUnique": false
484443 }
485444 },
486445 "foreignKeys": {},
487446 "compositePrimaryKeys": {
488447 "graphql_operation_scope_id_id_pk": {
489− "columns": [
490− "scope_id",
491− "id"
492− ],
448+ "columns": ["scope_id", "id"],
493449 "name": "graphql_operation_scope_id_id_pk"
494450 }
495451 },
@@ -546,26 +502,19 @@ · 50 unmodified lines
546502 "indexes": {
547503 "graphql_source_scope_id_idx": {
548504 "name": "graphql_source_scope_id_idx",
549− "columns": [
550− "scope_id"
551− ],
505+ "columns": ["scope_id"],
552506 "isUnique": false
553507 },
554508 "graphql_source_auth_connection_id_idx": {
555509 "name": "graphql_source_auth_connection_id_idx",
556− "columns": [
557− "auth_connection_id"
558− ],
510+ "columns": ["auth_connection_id"],
559511 "isUnique": false
560512 }
561513 },
562514 "foreignKeys": {},
563515 "compositePrimaryKeys": {
564516 "graphql_source_scope_id_id_pk": {
565− "columns": [
566− "scope_id",
567− "id"
568− ],
517+ "columns": ["scope_id", "id"],
569518 "name": "graphql_source_scope_id_id_pk"
570519 }
571520 },
@@ -614,26 +563,19 @@ · 42 unmodified lines
614563 "indexes": {
615564 "mcp_binding_scope_id_idx": {
616565 "name": "mcp_binding_scope_id_idx",
617− "columns": [
618− "scope_id"
619− ],
566+ "columns": ["scope_id"],
620567 "isUnique": false
621568 },
622569 "mcp_binding_source_id_idx": {
623570 "name": "mcp_binding_source_id_idx",
624− "columns": [
625− "source_id"
626− ],
571+ "columns": ["source_id"],
627572 "isUnique": false
628573 }
629574 },
630575 "foreignKeys": {},
631576 "compositePrimaryKeys": {
632577 "mcp_binding_scope_id_id_pk": {
633− "columns": [
634− "scope_id",
635− "id"
636− ],
578+ "columns": ["scope_id", "id"],
637579 "name": "mcp_binding_scope_id_id_pk"
638580 }
639581 },
@@ -732,47 +674,34 @@ · 92 unmodified lines
732674 "indexes": {
733675 "mcp_source_scope_id_idx": {
734676 "name": "mcp_source_scope_id_idx",
735− "columns": [
736− "scope_id"
737− ],
677+ "columns": ["scope_id"],
738678 "isUnique": false
739679 },
740680 "mcp_source_auth_secret_id_idx": {
741681 "name": "mcp_source_auth_secret_id_idx",
742− "columns": [
743− "auth_secret_id"
744− ],
682+ "columns": ["auth_secret_id"],
745683 "isUnique": false
746684 },
747685 "mcp_source_auth_connection_id_idx": {
748686 "name": "mcp_source_auth_connection_id_idx",
749− "columns": [
750− "auth_connection_id"
751− ],
687+ "columns": ["auth_connection_id"],
752688 "isUnique": false
753689 },
754690 "mcp_source_auth_client_id_secret_id_idx": {
755691 "name": "mcp_source_auth_client_id_secret_id_idx",
756− "columns": [
757− "auth_client_id_secret_id"
758− ],
692+ "columns": ["auth_client_id_secret_id"],
759693 "isUnique": false
760694 },
761695 "mcp_source_auth_client_secret_secret_id_idx": {
762696 "name": "mcp_source_auth_client_secret_secret_id_idx",
763− "columns": [
764− "auth_client_secret_secret_id"
765− ],
697+ "columns": ["auth_client_secret_secret_id"],
766698 "isUnique": false
767699 }
768700 },
769701 "foreignKeys": {},
770702 "compositePrimaryKeys": {
771703 "mcp_source_scope_id_id_pk": {
772− "columns": [
773− "scope_id",
774− "id"
775− ],
704+ "columns": ["scope_id", "id"],
776705 "name": "mcp_source_scope_id_id_pk"
777706 }
778707 },
@@ -856,33 +785,24 @@ · 77 unmodified lines
856785 "indexes": {
857786 "oauth2_session_scope_id_idx": {
858787 "name": "oauth2_session_scope_id_idx",
859− "columns": [
860− "scope_id"
861− ],
788+ "columns": ["scope_id"],
862789 "isUnique": false
863790 },
864791 "oauth2_session_plugin_id_idx": {
865792 "name": "oauth2_session_plugin_id_idx",
866− "columns": [
867− "plugin_id"
868− ],
793+ "columns": ["plugin_id"],
869794 "isUnique": false
870795 },
871796 "oauth2_session_connection_id_idx": {
872797 "name": "oauth2_session_connection_id_idx",
873− "columns": [
874− "connection_id"
875− ],
798+ "columns": ["connection_id"],
876799 "isUnique": false
877800 }
878801 },
879802 "foreignKeys": {},
880803 "compositePrimaryKeys": {
881804 "oauth2_session_scope_id_id_pk": {
882− "columns": [
883− "scope_id",
884− "id"
885− ],
805+ "columns": ["scope_id", "id"],
886806 "name": "oauth2_session_scope_id_id_pk"
887807 }
888808 },
@@ -924,26 +844,19 @@ · 35 unmodified lines
924844 "indexes": {
925845 "openapi_operation_scope_id_idx": {
926846 "name": "openapi_operation_scope_id_idx",
927− "columns": [
928− "scope_id"
929− ],
847+ "columns": ["scope_id"],
930848 "isUnique": false
931849 },
932850 "openapi_operation_source_id_idx": {
933851 "name": "openapi_operation_source_id_idx",
934− "columns": [
935− "source_id"
936− ],
852+ "columns": ["source_id"],
937853 "isUnique": false
938854 }
939855 },
940856 "foreignKeys": {},
941857 "compositePrimaryKeys": {
942858 "openapi_operation_scope_id_id_pk": {
943− "columns": [
944− "scope_id",
945− "id"
946− ],
859+ "columns": ["scope_id", "id"],
947860 "name": "openapi_operation_scope_id_id_pk"
948861 }
949862 },
@@ -1013,19 +926,14 @@ · 63 unmodified lines
1013926 "indexes": {
1014927 "openapi_source_scope_id_idx": {
1015928 "name": "openapi_source_scope_id_idx",
1016− "columns": [
1017− "scope_id"
1018− ],
929+ "columns": ["scope_id"],
1019930 "isUnique": false
1020931 }
1021932 },
1022933 "foreignKeys": {},
1023934 "compositePrimaryKeys": {
1024935 "openapi_source_scope_id_id_pk": {
1025− "columns": [
1026− "scope_id",
1027− "id"
1028− ],
936+ "columns": ["scope_id", "id"],
1029937 "name": "openapi_source_scope_id_id_pk"
1030938 }
1031939 },
@@ -1117,44 +1025,32 @@ · 85 unmodified lines
11171025 "indexes": {
11181026 "openapi_source_binding_source_id_idx": {
11191027 "name": "openapi_source_binding_source_id_idx",
1120− "columns": [
1121− "source_id"
1122− ],
1028+ "columns": ["source_id"],
11231029 "isUnique": false
11241030 },
11251031 "openapi_source_binding_source_scope_id_idx": {
11261032 "name": "openapi_source_binding_source_scope_id_idx",
1127− "columns": [
1128− "source_scope_id"
1129− ],
1033+ "columns": ["source_scope_id"],
11301034 "isUnique": false
11311035 },
11321036 "openapi_source_binding_target_scope_id_idx": {
11331037 "name": "openapi_source_binding_target_scope_id_idx",
1134− "columns": [
1135− "target_scope_id"
1136− ],
1038+ "columns": ["target_scope_id"],
11371039 "isUnique": false
11381040 },
11391041 "openapi_source_binding_slot_idx": {
11401042 "name": "openapi_source_binding_slot_idx",
1141− "columns": [
1142− "slot"
1143− ],
1043+ "columns": ["slot"],
11441044 "isUnique": false
11451045 },
11461046 "openapi_source_binding_secret_id_idx": {
11471047 "name": "openapi_source_binding_secret_id_idx",
1148− "columns": [
1149− "secret_id"
1150− ],
1048+ "columns": ["secret_id"],
11511049 "isUnique": false
11521050 },
11531051 "openapi_source_binding_connection_id_idx": {
11541052 "name": "openapi_source_binding_connection_id_idx",
1155− "columns": [
1156− "connection_id"
1157− ],
1053+ "columns": ["connection_id"],
11581054 "isUnique": false
11591055 }
11601056 },
@@ -1212,33 +1108,24 @@ · 51 unmodified lines
12121108 "indexes": {
12131109 "secret_scope_id_idx": {
12141110 "name": "secret_scope_id_idx",
1215− "columns": [
1216− "scope_id"
1217− ],
1111+ "columns": ["scope_id"],
12181112 "isUnique": false
12191113 },
12201114 "secret_provider_idx": {
12211115 "name": "secret_provider_idx",
1222− "columns": [
1223− "provider"
1224− ],
1116+ "columns": ["provider"],
12251117 "isUnique": false
12261118 },
12271119 "secret_owned_by_connection_id_idx": {
12281120 "name": "secret_owned_by_connection_id_idx",
1229− "columns": [
1230− "owned_by_connection_id"
1231− ],
1121+ "columns": ["owned_by_connection_id"],
12321122 "isUnique": false
12331123 }
12341124 },
12351125 "foreignKeys": {},
12361126 "compositePrimaryKeys": {
12371127 "secret_scope_id_id_pk": {
1238− "columns": [
1239− "scope_id",
1240− "id"
1241− ],
1128+ "columns": ["scope_id", "id"],
12421129 "name": "secret_scope_id_id_pk"
12431130 }
12441131 },
@@ -1332,26 +1219,19 @@ · 87 unmodified lines
13321219 "indexes": {
13331220 "source_scope_id_idx": {
13341221 "name": "source_scope_id_idx",
1335− "columns": [
1336− "scope_id"
1337− ],
1222+ "columns": ["scope_id"],
13381223 "isUnique": false
13391224 },
13401225 "source_plugin_id_idx": {
13411226 "name": "source_plugin_id_idx",
1342− "columns": [
1343− "plugin_id"
1344− ],
1227+ "columns": ["plugin_id"],
13451228 "isUnique": false
13461229 }
13471230 },
13481231 "foreignKeys": {},
13491232 "compositePrimaryKeys": {
13501233 "source_scope_id_id_pk": {
1351− "columns": [
1352− "scope_id",
1353− "id"
1354− ],
1234+ "columns": ["scope_id", "id"],
13551235 "name": "source_scope_id_id_pk"
13561236 }
13571237 },
@@ -1435,33 +1315,24 @@ · 77 unmodified lines
14351315 "indexes": {
14361316 "tool_scope_id_idx": {
14371317 "name": "tool_scope_id_idx",
1438− "columns": [
1439− "scope_id"
1440− ],
1318+ "columns": ["scope_id"],
14411319 "isUnique": false
14421320 },
14431321 "tool_source_id_idx": {
14441322 "name": "tool_source_id_idx",
1445− "columns": [
1446− "source_id"
1447− ],
1323+ "columns": ["source_id"],
14481324 "isUnique": false
14491325 },
14501326 "tool_plugin_id_idx": {
14511327 "name": "tool_plugin_id_idx",
1452− "columns": [
1453− "plugin_id"
1454− ],
1328+ "columns": ["plugin_id"],
14551329 "isUnique": false
14561330 }
14571331 },
14581332 "foreignKeys": {},
14591333 "compositePrimaryKeys": {
14601334 "tool_scope_id_id_pk": {
1461− "columns": [
1462− "scope_id",
1463− "id"
1464− ],
1335+ "columns": ["scope_id", "id"],
14651336 "name": "tool_scope_id_id_pk"
14661337 }
14671338 },
@@ -1524,20 +1395,14 @@ · 56 unmodified lines
15241395 "indexes": {
15251396 "tool_policy_scope_id_position_idx": {
15261397 "name": "tool_policy_scope_id_position_idx",
1527− "columns": [
1528− "scope_id",
1529− "position"
1530− ],
1398+ "columns": ["scope_id", "position"],
15311399 "isUnique": false
15321400 }
15331401 },
15341402 "foreignKeys": {},
15351403 "compositePrimaryKeys": {
15361404 "tool_policy_scope_id_id_pk": {
1537− "columns": [
1538− "scope_id",
1539− "id"
1540− ],
1405+ "columns": ["scope_id", "id"],
15411406 "name": "tool_policy_scope_id_id_pk"
15421407 }
15431408 },
@@ -1607,33 +1472,24 @@ · 63 unmodified lines
16071472 "indexes": {
16081473 "graphql_source_header_scope_id_idx": {
16091474 "name": "graphql_source_header_scope_id_idx",
1610− "columns": [
1611− "scope_id"
1612− ],
1475+ "columns": ["scope_id"],
16131476 "isUnique": false
16141477 },
16151478 "graphql_source_header_source_id_idx": {
16161479 "name": "graphql_source_header_source_id_idx",
1617− "columns": [
1618− "source_id"
1619− ],
1480+ "columns": ["source_id"],
16201481 "isUnique": false
16211482 },
16221483 "graphql_source_header_secret_id_idx": {
16231484 "name": "graphql_source_header_secret_id_idx",
1624− "columns": [
1625− "secret_id"
1626− ],
1485+ "columns": ["secret_id"],
16271486 "isUnique": false
16281487 }
16291488 },
16301489 "foreignKeys": {},
16311490 "compositePrimaryKeys": {
16321491 "graphql_source_header_scope_id_id_pk": {
1633− "columns": [
1634− "scope_id",
1635− "id"
1636− ],
1492+ "columns": ["scope_id", "id"],
16371493 "name": "graphql_source_header_scope_id_id_pk"
16381494 }
16391495 },
@@ -1703,33 +1559,24 @@ · 63 unmodified lines
17031559 "indexes": {
17041560 "graphql_source_query_param_scope_id_idx": {
17051561 "name": "graphql_source_query_param_scope_id_idx",
1706− "columns": [
1707− "scope_id"
1708− ],
1562+ "columns": ["scope_id"],
17091563 "isUnique": false
17101564 },
17111565 "graphql_source_query_param_source_id_idx": {
17121566 "name": "graphql_source_query_param_source_id_idx",
1713− "columns": [
1714− "source_id"
1715− ],
1567+ "columns": ["source_id"],
17161568 "isUnique": false
17171569 },
17181570 "graphql_source_query_param_secret_id_idx": {
17191571 "name": "graphql_source_query_param_secret_id_idx",
1720− "columns": [
1721− "secret_id"
1722− ],
1572+ "columns": ["secret_id"],
17231573 "isUnique": false
17241574 }
17251575 },
17261576 "foreignKeys": {},
17271577 "compositePrimaryKeys": {
17281578 "graphql_source_query_param_scope_id_id_pk": {
1729− "columns": [
1730− "scope_id",
1731− "id"
1732− ],
1579+ "columns": ["scope_id", "id"],
17331580 "name": "graphql_source_query_param_scope_id_id_pk"
17341581 }
17351582 },
@@ -1799,33 +1646,24 @@ · 63 unmodified lines
17991646 "indexes": {
18001647 "openapi_source_query_param_scope_id_idx": {
18011648 "name": "openapi_source_query_param_scope_id_idx",
1802− "columns": [
1803− "scope_id"
1804− ],
1649+ "columns": ["scope_id"],
18051650 "isUnique": false
18061651 },
18071652 "openapi_source_query_param_source_id_idx": {
18081653 "name": "openapi_source_query_param_source_id_idx",
1809− "columns": [
1810− "source_id"
1811− ],
1654+ "columns": ["source_id"],
18121655 "isUnique": false
18131656 },
18141657 "openapi_source_query_param_secret_id_idx": {
18151658 "name": "openapi_source_query_param_secret_id_idx",
1816− "columns": [
1817− "secret_id"
1818− ],
1659+ "columns": ["secret_id"],
18191660 "isUnique": false
18201661 }
18211662 },
18221663 "foreignKeys": {},
18231664 "compositePrimaryKeys": {
18241665 "openapi_source_query_param_scope_id_id_pk": {
1825− "columns": [
1826− "scope_id",
1827− "id"
1828− ],
1666+ "columns": ["scope_id", "id"],
18291667 "name": "openapi_source_query_param_scope_id_id_pk"
18301668 }
18311669 },
@@ -1895,33 +1733,24 @@ · 63 unmodified lines
18951733 "indexes": {
18961734 "openapi_source_spec_fetch_header_scope_id_idx": {
18971735 "name": "openapi_source_spec_fetch_header_scope_id_idx",
1898− "columns": [
1899− "scope_id"
1900− ],
1736+ "columns": ["scope_id"],
19011737 "isUnique": false
19021738 },
19031739 "openapi_source_spec_fetch_header_source_id_idx": {
19041740 "name": "openapi_source_spec_fetch_header_source_id_idx",
1905− "columns": [
1906− "source_id"
1907− ],
1741+ "columns": ["source_id"],
19081742 "isUnique": false
19091743 },
19101744 "openapi_source_spec_fetch_header_secret_id_idx": {
19111745 "name": "openapi_source_spec_fetch_header_secret_id_idx",
1912− "columns": [
1913− "secret_id"
1914− ],
1746+ "columns": ["secret_id"],
19151747 "isUnique": false
19161748 }
19171749 },
19181750 "foreignKeys": {},
19191751 "compositePrimaryKeys": {
19201752 "openapi_source_spec_fetch_header_scope_id_id_pk": {
1921− "columns": [
1922− "scope_id",
1923− "id"
1924− ],
1753+ "columns": ["scope_id", "id"],
19251754 "name": "openapi_source_spec_fetch_header_scope_id_id_pk"
19261755 }
19271756 },
@@ -1991,33 +1820,24 @@ · 63 unmodified lines
19911820 "indexes": {
19921821 "openapi_source_spec_fetch_query_param_scope_id_idx": {
19931822 "name": "openapi_source_spec_fetch_query_param_scope_id_idx",
1994− "columns": [
1995− "scope_id"
1996− ],
1823+ "columns": ["scope_id"],
19971824 "isUnique": false
19981825 },
19991826 "openapi_source_spec_fetch_query_param_source_id_idx": {
20001827 "name": "openapi_source_spec_fetch_query_param_source_id_idx",
2001− "columns": [
2002− "source_id"
2003− ],
1828+ "columns": ["source_id"],
20041829 "isUnique": false
20051830 },
20061831 "openapi_source_spec_fetch_query_param_secret_id_idx": {
20071832 "name": "openapi_source_spec_fetch_query_param_secret_id_idx",
2008− "columns": [
2009− "secret_id"
2010− ],
1833+ "columns": ["secret_id"],
20111834 "isUnique": false
20121835 }
20131836 },
20141837 "foreignKeys": {},
20151838 "compositePrimaryKeys": {
20161839 "openapi_source_spec_fetch_query_param_scope_id_id_pk": {
2017− "columns": [
2018− "scope_id",
2019− "id"
2020− ],
1840+ "columns": ["scope_id", "id"],
20211841 "name": "openapi_source_spec_fetch_query_param_scope_id_id_pk"
20221842 }
20231843 },
@@ -2087,33 +1907,24 @@ · 63 unmodified lines
20871907 "indexes": {
20881908 "mcp_source_header_scope_id_idx": {
20891909 "name": "mcp_source_header_scope_id_idx",
2090− "columns": [
2091− "scope_id"
2092− ],
1910+ "columns": ["scope_id"],
20931911 "isUnique": false
20941912 },
20951913 "mcp_source_header_source_id_idx": {
20961914 "name": "mcp_source_header_source_id_idx",
2097− "columns": [
2098− "source_id"
2099− ],
1915+ "columns": ["source_id"],
21001916 "isUnique": false
21011917 },
21021918 "mcp_source_header_secret_id_idx": {
21031919 "name": "mcp_source_header_secret_id_idx",
2104− "columns": [
2105− "secret_id"
2106− ],
1920+ "columns": ["secret_id"],
21071921 "isUnique": false
21081922 }
21091923 },
21101924 "foreignKeys": {},
21111925 "compositePrimaryKeys": {
21121926 "mcp_source_header_scope_id_id_pk": {
2113− "columns": [
2114− "scope_id",
2115− "id"
2116− ],
1927+ "columns": ["scope_id", "id"],
21171928 "name": "mcp_source_header_scope_id_id_pk"
21181929 }
21191930 },
@@ -2183,33 +1994,24 @@ · 63 unmodified lines
21831994 "indexes": {
21841995 "mcp_source_query_param_scope_id_idx": {
21851996 "name": "mcp_source_query_param_scope_id_idx",
2186− "columns": [
2187− "scope_id"
2188− ],
1997+ "columns": ["scope_id"],
21891998 "isUnique": false
21901999 },
21912000 "mcp_source_query_param_source_id_idx": {
21922001 "name": "mcp_source_query_param_source_id_idx",
2193− "columns": [
2194− "source_id"
2195− ],
2002+ "columns": ["source_id"],
21962003 "isUnique": false
21972004 },
21982005 "mcp_source_query_param_secret_id_idx": {
21992006 "name": "mcp_source_query_param_secret_id_idx",
2200− "columns": [
2201− "secret_id"
2202− ],
2007+ "columns": ["secret_id"],
22032008 "isUnique": false
22042009 }
22052010 },
22062011 "foreignKeys": {},
22072012 "compositePrimaryKeys": {
22082013 "mcp_source_query_param_scope_id_id_pk": {
2209− "columns": [
2210− "scope_id",
2211− "id"
2212− ],
2014+ "columns": ["scope_id", "id"],
22132015 "name": "mcp_source_query_param_scope_id_id_pk"
22142016 }
22152017 },
@@ -2279,33 +2081,24 @@ · 63 unmodified lines
22792081 "indexes": {
22802082 "google_discovery_source_credential_header_scope_id_idx": {
22812083 "name": "google_discovery_source_credential_header_scope_id_idx",
2282− "columns": [
2283− "scope_id"
2284− ],
2084+ "columns": ["scope_id"],
22852085 "isUnique": false
22862086 },
22872087 "google_discovery_source_credential_header_source_id_idx": {
22882088 "name": "google_discovery_source_credential_header_source_id_idx",
2289− "columns": [
2290− "source_id"
2291− ],
2089+ "columns": ["source_id"],
22922090 "isUnique": false
22932091 },
22942092 "google_discovery_source_credential_header_secret_id_idx": {
22952093 "name": "google_discovery_source_credential_header_secret_id_idx",
2296− "columns": [
2297− "secret_id"
2298− ],
2094+ "columns": ["secret_id"],
22992095 "isUnique": false
23002096 }
23012097 },
23022098 "foreignKeys": {},
23032099 "compositePrimaryKeys": {
23042100 "google_discovery_source_credential_header_scope_id_id_pk": {
2305− "columns": [
2306− "scope_id",
2307− "id"
2308− ],
2101+ "columns": ["scope_id", "id"],
23092102 "name": "google_discovery_source_credential_header_scope_id_id_pk"
23102103 }
23112104 },
@@ -2375,33 +2168,24 @@ · 63 unmodified lines
23752168 "indexes": {
23762169 "google_discovery_source_credential_query_param_scope_id_idx": {
23772170 "name": "google_discovery_source_credential_query_param_scope_id_idx",
2378− "columns": [
2379− "scope_id"
2380− ],
2171+ "columns": ["scope_id"],
23812172 "isUnique": false
23822173 },
23832174 "google_discovery_source_credential_query_param_source_id_idx": {
23842175 "name": "google_discovery_source_credential_query_param_source_id_idx",
2385− "columns": [
2386− "source_id"
2387− ],
2176+ "columns": ["source_id"],
23882177 "isUnique": false
23892178 },
23902179 "google_discovery_source_credential_query_param_secret_id_idx": {
23912180 "name": "google_discovery_source_credential_query_param_secret_id_idx",
2392− "columns": [
2393− "secret_id"
2394− ],
2181+ "columns": ["secret_id"],
23952182 "isUnique": false
23962183 }
23972184 },
23982185 "foreignKeys": {},
23992186 "compositePrimaryKeys": {
24002187 "google_discovery_source_credential_query_param_scope_id_id_pk": {
2401− "columns": [
2402− "scope_id",
2403− "id"
2404− ],
2188+ "columns": ["scope_id", "id"],
24052189 "name": "google_discovery_source_credential_query_param_scope_id_id_pk"
24062190 }
24072191 },
apps/local/src/serve.test.ts+4−1
@@ -30,7 +30,10 @@ const startTestServer = async (): Promise<string> => { · 29 unmodified lines
3030 beforeEach(() => {
3131 clientDir = mkdtempSync(join(tmpdir(), "exec-local-serve-"));
3232 mkdirSync(join(clientDir, "assets"), { recursive: true });
33− writeFileSync(join(clientDir, "index.html"), "<!doctype html><html><body>index-shell</body></html>");
33+ writeFileSync(
34+ join(clientDir, "index.html"),
35+ "<!doctype html><html><body>index-shell</body></html>",
36+ );
3437 writeFileSync(join(clientDir, "assets", "app.js"), "console.log('ok')");
3538 });
3639
apps/local/src/serve.ts+8−6
@@ -17,12 +17,14 @@ import { getServerHandlers } from "./server/main"; · 16 unmodified lines
1717
1818 const DEFAULT_ALLOWED_HOSTS = ["localhost", "127.0.0.1", "[::1]", "::1"];
1919
20−const makeIsAllowedHost = (allowed: ReadonlySet<string>) => (request: Request): boolean => {
21− const host = request.headers.get("host");
22− if (!host) return true;
23− const hostname = host.replace(/:\d+$/, "");
24− return allowed.has(hostname);
25−};
20+const makeIsAllowedHost =
21+ (allowed: ReadonlySet<string>) =>
22+ (request: Request): boolean => {
23+ const host = request.headers.get("host");
24+ if (!host) return true;
25+ const hostname = host.replace(/:\d+$/, "");
26+ return allowed.has(hostname);
27+ };
2628
2729 // ---------------------------------------------------------------------------
2830 // Static files
apps/local/src/server/config-sync.ts+46−50
@@ -10,11 +10,7 @@ import { join } from "node:path"; · 9 unmodified lines
1010 import * as fs from "node:fs";
1111 import * as jsonc from "jsonc-parser";
1212
13−import type {
14− SourceConfig,
15− ExecutorFileConfig,
16− ConfigHeaderValue,
17−} from "@executor-js/config";
13+import type { SourceConfig, ExecutorFileConfig, ConfigHeaderValue } from "@executor-js/config";
1814 import { SECRET_REF_PREFIX } from "@executor-js/config";
1915
2016 import type { LocalExecutor } from "./executor";
@@ -57,8 +53,7 @@ const translateHeaders = ( · 36 unmodified lines
5753 // Config path resolution
5854 // ---------------------------------------------------------------------------
5955
60−export const resolveConfigPath = (scopeDir: string): string =>
61− join(scopeDir, "executor.jsonc");
56+export const resolveConfigPath = (scopeDir: string): string => join(scopeDir, "executor.jsonc");
6257
6358 // ---------------------------------------------------------------------------
6459 // Load config (sync, no Effect deps — runs at startup)
@@ -91,45 +86,53 @@ const addSourceFromConfig = ( · 26 unmodified lines
9186 const scope = executor.scopes.at(-1)!.id;
9287 switch (source.kind) {
9388 case "openapi":
94− return executor.openapi.addSpec({
95− spec: source.spec,
96− scope,
97− baseUrl: source.baseUrl,
98− namespace: source.namespace,
99− headers: translateHeaders(source.headers),
100− }).pipe(Effect.asVoid);
89+ return executor.openapi
90+ .addSpec({
91+ spec: source.spec,
92+ scope,
93+ baseUrl: source.baseUrl,
94+ namespace: source.namespace,
95+ headers: translateHeaders(source.headers),
96+ })
97+ .pipe(Effect.asVoid);
10198
10299 case "graphql":
103− return executor.graphql.addSource({
104− endpoint: source.endpoint,
105− scope,
106− namespace: source.namespace,
107− headers: translateHeaders(source.headers) as Record<string, string> | undefined,
108− }).pipe(Effect.asVoid);
100+ return executor.graphql
101+ .addSource({
102+ endpoint: source.endpoint,
103+ scope,
104+ namespace: source.namespace,
105+ headers: translateHeaders(source.headers) as Record<string, string> | undefined,
106+ })
107+ .pipe(Effect.asVoid);
109108
110109 case "mcp":
111110 if (source.transport === "stdio") {
112− return executor.mcp.addSource({
113− transport: "stdio",
111+ return executor.mcp
112+ .addSource({
113+ transport: "stdio",
114+ scope,
115+ name: source.name,
116+ command: source.command,
117+ args: source.args ? [...source.args] : undefined,
118+ env: source.env,
119+ cwd: source.cwd,
120+ namespace: source.namespace,
121+ })
122+ .pipe(Effect.asVoid);
123+ }
124+ return executor.mcp
125+ .addSource({
126+ transport: "remote",
114127 scope,
115128 name: source.name,
116− command: source.command,
117− args: source.args ? [...source.args] : undefined,
118− env: source.env,
119− cwd: source.cwd,
129+ endpoint: source.endpoint,
130+ remoteTransport: source.remoteTransport,
131+ queryParams: source.queryParams,
132+ headers: source.headers,
120133 namespace: source.namespace,
121− }).pipe(Effect.asVoid);
122− }
123− return executor.mcp.addSource({
124− transport: "remote",
125− scope,
126− name: source.name,
127− endpoint: source.endpoint,
128− remoteTransport: source.remoteTransport,
129− queryParams: source.queryParams,
130− headers: source.headers,
131− namespace: source.namespace,
132− }).pipe(Effect.asVoid);
134+ })
135+ .pipe(Effect.asVoid);
133136 }
134137 };
135138
@@ -137,10 +140,7 @@ const addSourceFromConfig = ( · 1 unmodified line
137140 * Read executor.jsonc and replay all sources into the executor.
138141 * Each source is added independently — if one fails, the rest still load.
139142 */
140−export const syncFromConfig = (
141− executor: LocalExecutor,
142− configPath: string,
143−): Effect.Effect<void> =>
143+export const syncFromConfig = (executor: LocalExecutor, configPath: string): Effect.Effect<void> =>
144144 Effect.gen(function* () {
145145 const config = loadConfigSync(configPath);
146146 if (!config?.sources?.length) {
@@ -148,9 +148,7 @@ export const syncFromConfig = ( · 1 unmodified line
148148 return;
149149 }
150150
151− console.log(
152− `[config-sync] syncing ${config.sources.length} source(s) from ${configPath}`,
153− );
151+ console.log(`[config-sync] syncing ${config.sources.length} source(s) from ${configPath}`);
154152
155153 const results = yield* Effect.forEach(
156154 config.sources,
@@ -158,11 +156,9 @@ export const syncFromConfig = ( · 1 unmodified line
158156 addSourceFromConfig(executor, source).pipe(
159157 Effect.map(() => true as const),
160158 Effect.catchCause((cause) => {
161− const ns = "namespace" in source ? source.namespace : ("name" in source ? source.name : "unknown");
162− console.warn(
163− `[config-sync] Failed to load source "${ns}":`,
164− Cause.pretty(cause),
165− );
159+ const ns =
160+ "namespace" in source ? source.namespace : "name" in source ? source.name : "unknown";
161+ console.warn(`[config-sync] Failed to load source "${ns}":`, Cause.pretty(cause));
166162 return Effect.succeed(false as const);
167163 }),
168164 ),
apps/local/src/server/db-upgrade.test.ts+16−12
@@ -156,9 +156,9 @@ describe("move-aside + fresh migrate end-to-end", () => { · 155 unmodified lines
156156 migrationsFolder: join(__dirname, "../../drizzle"),
157157 });
158158 // migrate() should have produced the new schema — source now has scope_id.
159− const cols = db
160− .prepare("PRAGMA table_info('source')")
161− .all() as ReadonlyArray<{ readonly name: string }>;
159+ const cols = db.prepare("PRAGMA table_info('source')").all() as ReadonlyArray<{
160+ readonly name: string;
161+ }>;
162162 expect(cols.some((c) => c.name === "scope_id")).toBe(true);
163163 db.close();
164164 });
@@ -169,12 +169,18 @@ describe("readLegacySecrets", () => { · 4 unmodified lines
169169 const path = join(workDir, "data.db");
170170 seed(path, PRE_SCOPE_SCHEMA);
171171 const db = new Database(path);
172− db.prepare(
173− "INSERT INTO secret (id, name, provider, created_at) VALUES (?, ?, ?, ?)",
174− ).run("sec_1", "GitHub Token", "onepassword", 1700000000);
175− db.prepare(
176− "INSERT INTO secret (id, name, provider, created_at) VALUES (?, ?, ?, ?)",
177− ).run("sec_2", "Stripe", "keychain", 1700000001);
172+ db.prepare("INSERT INTO secret (id, name, provider, created_at) VALUES (?, ?, ?, ?)").run(
173+ "sec_1",
174+ "GitHub Token",
175+ "onepassword",
176+ 1700000000,
177+ );
178+ db.prepare("INSERT INTO secret (id, name, provider, created_at) VALUES (?, ?, ?, ?)").run(
179+ "sec_2",
180+ "Stripe",
181+ "keychain",
182+ 1700000001,
183+ );
178184 db.close();
179185
180186 const rows = readLegacySecrets(path);
@@ -248,9 +254,7 @@ describe("importLegacySecrets", () => { · 67 unmodified lines
248254 it("uses INSERT OR IGNORE so a second import of the same ids is a no-op", () => {
249255 const path = join(workDir, "data.db");
250256 const db = createScopedDb(path);
251− const rows = [
252− { id: "sec_1", name: "GH", provider: "onepassword", createdAt: 1 },
253− ];
257+ const rows = [{ id: "sec_1", name: "GH", provider: "onepassword", createdAt: 1 }];
254258 importLegacySecrets(db, "scope_a", rows);
255259 // If the user's already re-registered the secret via a different
256260 // provider, the legacy row must NOT clobber it.
apps/local/src/server/db-upgrade.ts+3−3
@@ -27,9 +27,9 @@ export const isPreScopeSchema = (dbPath: string): boolean => { · 26 unmodified lines
2727 .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='source'")
2828 .get();
2929 if (!tableExists) return false;
30− const columns = db
31− .prepare("PRAGMA table_info('source')")
32− .all() as ReadonlyArray<{ readonly name: string }>;
30+ const columns = db.prepare("PRAGMA table_info('source')").all() as ReadonlyArray<{
31+ readonly name: string;
32+ }>;
3333 return !columns.some((c) => c.name === "scope_id");
3434 } finally {
3535 db.close();
apps/local/src/server/executor-schema.ts+479−370
@@ -1,396 +1,505 @@
11 import { sqliteTable, text, integer, index, primaryKey } from "drizzle-orm/sqlite-core";
22
3−export const source = sqliteTable("source", {
4− id: text('id').notNull(),
5− scope_id: text('scope_id').notNull(),
6− plugin_id: text('plugin_id').notNull(),
7− kind: text('kind').notNull(),
8− name: text('name').notNull(),
9− url: text('url'),
10− can_remove: integer('can_remove', { mode: 'boolean' }).default(true).notNull(),
11− can_refresh: integer('can_refresh', { mode: 'boolean' }).default(false).notNull(),
12− can_edit: integer('can_edit', { mode: 'boolean' }).default(false).notNull(),
13− created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(),
14− updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull()
15−}, (table) => [
16− primaryKey({ columns: [table.scope_id, table.id] }),
17− index("source_scope_id_idx").on(table.scope_id),
18− index("source_plugin_id_idx").on(table.plugin_id),
19−]);
3+export const source = sqliteTable(
4+ "source",
5+ {
6+ id: text("id").notNull(),
7+ scope_id: text("scope_id").notNull(),
8+ plugin_id: text("plugin_id").notNull(),
9+ kind: text("kind").notNull(),
10+ name: text("name").notNull(),
11+ url: text("url"),
12+ can_remove: integer("can_remove", { mode: "boolean" }).default(true).notNull(),
13+ can_refresh: integer("can_refresh", { mode: "boolean" }).default(false).notNull(),
14+ can_edit: integer("can_edit", { mode: "boolean" }).default(false).notNull(),
15+ created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(),
16+ updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
17+ },
18+ (table) => [
19+ primaryKey({ columns: [table.scope_id, table.id] }),
20+ index("source_scope_id_idx").on(table.scope_id),
21+ index("source_plugin_id_idx").on(table.plugin_id),
22+ ],
23+);
2024
21−export const tool = sqliteTable("tool", {
22− id: text('id').notNull(),
23− scope_id: text('scope_id').notNull(),
24− source_id: text('source_id').notNull(),
25− plugin_id: text('plugin_id').notNull(),
26− name: text('name').notNull(),
27− description: text('description').notNull(),
28− input_schema: text('input_schema', { mode: "json" }),
29− output_schema: text('output_schema', { mode: "json" }),
30− created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(),
31− updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull()
32−}, (table) => [
33− primaryKey({ columns: [table.scope_id, table.id] }),
34− index("tool_scope_id_idx").on(table.scope_id),
35− index("tool_source_id_idx").on(table.source_id),
36− index("tool_plugin_id_idx").on(table.plugin_id),
37−]);
25+export const tool = sqliteTable(
26+ "tool",
27+ {
28+ id: text("id").notNull(),
29+ scope_id: text("scope_id").notNull(),
30+ source_id: text("source_id").notNull(),
31+ plugin_id: text("plugin_id").notNull(),
32+ name: text("name").notNull(),
33+ description: text("description").notNull(),
34+ input_schema: text("input_schema", { mode: "json" }),
35+ output_schema: text("output_schema", { mode: "json" }),
36+ created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(),
37+ updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
38+ },
39+ (table) => [
40+ primaryKey({ columns: [table.scope_id, table.id] }),
41+ index("tool_scope_id_idx").on(table.scope_id),
42+ index("tool_source_id_idx").on(table.source_id),
43+ index("tool_plugin_id_idx").on(table.plugin_id),
44+ ],
45+);
3846
39−export const definition = sqliteTable("definition", {
40− id: text('id').notNull(),
41− scope_id: text('scope_id').notNull(),
42− source_id: text('source_id').notNull(),
43− plugin_id: text('plugin_id').notNull(),
44− name: text('name').notNull(),
45− schema: text('schema', { mode: "json" }).notNull(),
46− created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull()
47−}, (table) => [
48− primaryKey({ columns: [table.scope_id, table.id] }),
49− index("definition_scope_id_idx").on(table.scope_id),
50− index("definition_source_id_idx").on(table.source_id),
51− index("definition_plugin_id_idx").on(table.plugin_id),
52−]);
47+export const definition = sqliteTable(
48+ "definition",
49+ {
50+ id: text("id").notNull(),
51+ scope_id: text("scope_id").notNull(),
52+ source_id: text("source_id").notNull(),
53+ plugin_id: text("plugin_id").notNull(),
54+ name: text("name").notNull(),
55+ schema: text("schema", { mode: "json" }).notNull(),
56+ created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(),
57+ },
58+ (table) => [
59+ primaryKey({ columns: [table.scope_id, table.id] }),
60+ index("definition_scope_id_idx").on(table.scope_id),
61+ index("definition_source_id_idx").on(table.source_id),
62+ index("definition_plugin_id_idx").on(table.plugin_id),
63+ ],
64+);
5365
54−export const secret = sqliteTable("secret", {
55− id: text('id').notNull(),
56− scope_id: text('scope_id').notNull(),
57− name: text('name').notNull(),
58− provider: text('provider').notNull(),
59− owned_by_connection_id: text('owned_by_connection_id'),
60− created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull()
61−}, (table) => [
62− primaryKey({ columns: [table.scope_id, table.id] }),
63− index("secret_scope_id_idx").on(table.scope_id),
64− index("secret_provider_idx").on(table.provider),
65− index("secret_owned_by_connection_id_idx").on(table.owned_by_connection_id),
66−]);
66+export const secret = sqliteTable(
67+ "secret",
68+ {
69+ id: text("id").notNull(),
70+ scope_id: text("scope_id").notNull(),
71+ name: text("name").notNull(),
72+ provider: text("provider").notNull(),
73+ owned_by_connection_id: text("owned_by_connection_id"),
74+ created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(),
75+ },
76+ (table) => [
77+ primaryKey({ columns: [table.scope_id, table.id] }),
78+ index("secret_scope_id_idx").on(table.scope_id),
79+ index("secret_provider_idx").on(table.provider),
80+ index("secret_owned_by_connection_id_idx").on(table.owned_by_connection_id),
81+ ],
82+);
6783
68−export const connection = sqliteTable("connection", {
69− id: text('id').notNull(),
70− scope_id: text('scope_id').notNull(),
71− provider: text('provider').notNull(),
72− identity_label: text('identity_label'),
73− access_token_secret_id: text('access_token_secret_id').notNull(),
74− refresh_token_secret_id: text('refresh_token_secret_id'),
75− expires_at: integer('expires_at'),
76− scope: text('scope'),
77− provider_state: text('provider_state', { mode: "json" }),
78− created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(),
79− updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull()
80−}, (table) => [
81− primaryKey({ columns: [table.scope_id, table.id] }),
82− index("connection_scope_id_idx").on(table.scope_id),
83− index("connection_provider_idx").on(table.provider),
84−]);
84+export const connection = sqliteTable(
85+ "connection",
86+ {
87+ id: text("id").notNull(),
88+ scope_id: text("scope_id").notNull(),
89+ provider: text("provider").notNull(),
90+ identity_label: text("identity_label"),
91+ access_token_secret_id: text("access_token_secret_id").notNull(),
92+ refresh_token_secret_id: text("refresh_token_secret_id"),
93+ expires_at: integer("expires_at"),
94+ scope: text("scope"),
95+ provider_state: text("provider_state", { mode: "json" }),
96+ created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(),
97+ updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
98+ },
99+ (table) => [
100+ primaryKey({ columns: [table.scope_id, table.id] }),
101+ index("connection_scope_id_idx").on(table.scope_id),
102+ index("connection_provider_idx").on(table.provider),
103+ ],
104+);
85105
86−export const oauth2_session = sqliteTable("oauth2_session", {
87− id: text('id').notNull(),
88− scope_id: text('scope_id').notNull(),
89− plugin_id: text('plugin_id').notNull(),
90− strategy: text('strategy').notNull(),
91− connection_id: text('connection_id').notNull(),
92− token_scope: text('token_scope').notNull(),
93− redirect_url: text('redirect_url').notNull(),
94− payload: text('payload', { mode: "json" }).notNull(),
95− expires_at: integer('expires_at').notNull(),
96− created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull()
97−}, (table) => [
98− primaryKey({ columns: [table.scope_id, table.id] }),
99− index("oauth2_session_scope_id_idx").on(table.scope_id),
100− index("oauth2_session_plugin_id_idx").on(table.plugin_id),
101− index("oauth2_session_connection_id_idx").on(table.connection_id),
102−]);
106+export const oauth2_session = sqliteTable(
107+ "oauth2_session",
108+ {
109+ id: text("id").notNull(),
110+ scope_id: text("scope_id").notNull(),
111+ plugin_id: text("plugin_id").notNull(),
112+ strategy: text("strategy").notNull(),
113+ connection_id: text("connection_id").notNull(),
114+ token_scope: text("token_scope").notNull(),
115+ redirect_url: text("redirect_url").notNull(),
116+ payload: text("payload", { mode: "json" }).notNull(),
117+ expires_at: integer("expires_at").notNull(),
118+ created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(),
119+ },
120+ (table) => [
121+ primaryKey({ columns: [table.scope_id, table.id] }),
122+ index("oauth2_session_scope_id_idx").on(table.scope_id),
123+ index("oauth2_session_plugin_id_idx").on(table.plugin_id),
124+ index("oauth2_session_connection_id_idx").on(table.connection_id),
125+ ],
126+);
103127
104−export const tool_policy = sqliteTable("tool_policy", {
105− id: text('id').notNull(),
106− scope_id: text('scope_id').notNull(),
107− pattern: text('pattern').notNull(),
108− action: text('action').notNull(),
109− position: text('position').notNull(),
110− created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(),
111− updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull()
112−}, (table) => [
113− primaryKey({ columns: [table.scope_id, table.id] }),
114− index("tool_policy_scope_id_position_idx").on(table.scope_id, table.position),
115−]);
128+export const tool_policy = sqliteTable(
129+ "tool_policy",
130+ {
131+ id: text("id").notNull(),
132+ scope_id: text("scope_id").notNull(),
133+ pattern: text("pattern").notNull(),
134+ action: text("action").notNull(),
135+ position: text("position").notNull(),
136+ created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(),
137+ updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
138+ },
139+ (table) => [
140+ primaryKey({ columns: [table.scope_id, table.id] }),
141+ index("tool_policy_scope_id_position_idx").on(table.scope_id, table.position),
142+ ],
143+);
116144
117−export const openapi_source = sqliteTable("openapi_source", {
118− id: text('id').notNull(),
119− scope_id: text('scope_id').notNull(),
120− name: text('name').notNull(),
121− spec: text('spec').notNull(),
122− source_url: text('source_url'),
123− base_url: text('base_url'),
124− headers: text('headers', { mode: "json" }),
125− oauth2: text('oauth2', { mode: "json" })
126−}, (table) => [
127− primaryKey({ columns: [table.scope_id, table.id] }),
128− index("openapi_source_scope_id_idx").on(table.scope_id),
129−]);
145+export const openapi_source = sqliteTable(
146+ "openapi_source",
147+ {
148+ id: text("id").notNull(),
149+ scope_id: text("scope_id").notNull(),
150+ name: text("name").notNull(),
151+ spec: text("spec").notNull(),
152+ source_url: text("source_url"),
153+ base_url: text("base_url"),
154+ headers: text("headers", { mode: "json" }),
155+ oauth2: text("oauth2", { mode: "json" }),
156+ },
157+ (table) => [
158+ primaryKey({ columns: [table.scope_id, table.id] }),
159+ index("openapi_source_scope_id_idx").on(table.scope_id),
160+ ],
161+);
130162
131−export const openapi_operation = sqliteTable("openapi_operation", {
132− id: text('id').notNull(),
133− scope_id: text('scope_id').notNull(),
134− source_id: text('source_id').notNull(),
135− binding: text('binding', { mode: "json" }).notNull()
136−}, (table) => [
137− primaryKey({ columns: [table.scope_id, table.id] }),
138− index("openapi_operation_scope_id_idx").on(table.scope_id),
139− index("openapi_operation_source_id_idx").on(table.source_id),
140−]);
163+export const openapi_operation = sqliteTable(
164+ "openapi_operation",
165+ {
166+ id: text("id").notNull(),
167+ scope_id: text("scope_id").notNull(),
168+ source_id: text("source_id").notNull(),
169+ binding: text("binding", { mode: "json" }).notNull(),
170+ },
171+ (table) => [
172+ primaryKey({ columns: [table.scope_id, table.id] }),
173+ index("openapi_operation_scope_id_idx").on(table.scope_id),
174+ index("openapi_operation_source_id_idx").on(table.source_id),
175+ ],
176+);
141177
142−export const openapi_source_binding = sqliteTable("openapi_source_binding", {
143− id: text('id').primaryKey(),
144− source_id: text('source_id').notNull(),
145− source_scope_id: text('source_scope_id').notNull(),
146− target_scope_id: text('target_scope_id').notNull(),
147− slot: text('slot').notNull(),
148− kind: text({ enum: ['secret', 'connection', 'text'] }).notNull(),
149− secret_id: text('secret_id'),
150− connection_id: text('connection_id'),
151− text_value: text('text_value'),
152− created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(),
153− updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull()
154−}, (table) => [
155− index("openapi_source_binding_source_id_idx").on(table.source_id),
156− index("openapi_source_binding_source_scope_id_idx").on(table.source_scope_id),
157− index("openapi_source_binding_target_scope_id_idx").on(table.target_scope_id),
158− index("openapi_source_binding_slot_idx").on(table.slot),
159− index("openapi_source_binding_secret_id_idx").on(table.secret_id),
160− index("openapi_source_binding_connection_id_idx").on(table.connection_id),
161−]);
178+export const openapi_source_binding = sqliteTable(
179+ "openapi_source_binding",
180+ {
181+ id: text("id").primaryKey(),
182+ source_id: text("source_id").notNull(),
183+ source_scope_id: text("source_scope_id").notNull(),
184+ target_scope_id: text("target_scope_id").notNull(),
185+ slot: text("slot").notNull(),
186+ kind: text({ enum: ["secret", "connection", "text"] }).notNull(),
187+ secret_id: text("secret_id"),
188+ connection_id: text("connection_id"),
189+ text_value: text("text_value"),
190+ created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(),
191+ updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
192+ },
193+ (table) => [
194+ index("openapi_source_binding_source_id_idx").on(table.source_id),
195+ index("openapi_source_binding_source_scope_id_idx").on(table.source_scope_id),
196+ index("openapi_source_binding_target_scope_id_idx").on(table.target_scope_id),
197+ index("openapi_source_binding_slot_idx").on(table.slot),
198+ index("openapi_source_binding_secret_id_idx").on(table.secret_id),
199+ index("openapi_source_binding_connection_id_idx").on(table.connection_id),
200+ ],
201+);
162202
163−export const openapi_source_query_param = sqliteTable("openapi_source_query_param", {
164− id: text('id').notNull(),
165− scope_id: text('scope_id').notNull(),
166− source_id: text('source_id').notNull(),
167− name: text('name').notNull(),
168− kind: text({ enum: ['text', 'secret'] }).notNull(),
169− text_value: text('text_value'),
170− secret_id: text('secret_id'),
171− secret_prefix: text('secret_prefix')
172−}, (table) => [
173− primaryKey({ columns: [table.scope_id, table.id] }),
174− index("openapi_source_query_param_scope_id_idx").on(table.scope_id),
175− index("openapi_source_query_param_source_id_idx").on(table.source_id),
176− index("openapi_source_query_param_secret_id_idx").on(table.secret_id),
177−]);
203+export const openapi_source_query_param = sqliteTable(
204+ "openapi_source_query_param",
205+ {
206+ id: text("id").notNull(),
207+ scope_id: text("scope_id").notNull(),
208+ source_id: text("source_id").notNull(),
209+ name: text("name").notNull(),
210+ kind: text({ enum: ["text", "secret"] }).notNull(),
211+ text_value: text("text_value"),
212+ secret_id: text("secret_id"),
213+ secret_prefix: text("secret_prefix"),
214+ },
215+ (table) => [
216+ primaryKey({ columns: [table.scope_id, table.id] }),
217+ index("openapi_source_query_param_scope_id_idx").on(table.scope_id),
218+ index("openapi_source_query_param_source_id_idx").on(table.source_id),
219+ index("openapi_source_query_param_secret_id_idx").on(table.secret_id),
220+ ],
221+);
178222
179−export const openapi_source_spec_fetch_header = sqliteTable("openapi_source_spec_fetch_header", {
180− id: text('id').notNull(),
181− scope_id: text('scope_id').notNull(),
182− source_id: text('source_id').notNull(),
183− name: text('name').notNull(),
184− kind: text({ enum: ['text', 'secret'] }).notNull(),
185− text_value: text('text_value'),
186− secret_id: text('secret_id'),
187− secret_prefix: text('secret_prefix')
188−}, (table) => [
189− primaryKey({ columns: [table.scope_id, table.id] }),
190− index("openapi_source_spec_fetch_header_scope_id_idx").on(table.scope_id),
191− index("openapi_source_spec_fetch_header_source_id_idx").on(table.source_id),
192− index("openapi_source_spec_fetch_header_secret_id_idx").on(table.secret_id),
193−]);
223+export const openapi_source_spec_fetch_header = sqliteTable(
224+ "openapi_source_spec_fetch_header",
225+ {
226+ id: text("id").notNull(),
227+ scope_id: text("scope_id").notNull(),
228+ source_id: text("source_id").notNull(),
229+ name: text("name").notNull(),
230+ kind: text({ enum: ["text", "secret"] }).notNull(),
231+ text_value: text("text_value"),
232+ secret_id: text("secret_id"),
233+ secret_prefix: text("secret_prefix"),
234+ },
235+ (table) => [
236+ primaryKey({ columns: [table.scope_id, table.id] }),
237+ index("openapi_source_spec_fetch_header_scope_id_idx").on(table.scope_id),
238+ index("openapi_source_spec_fetch_header_source_id_idx").on(table.source_id),
239+ index("openapi_source_spec_fetch_header_secret_id_idx").on(table.secret_id),
240+ ],
241+);
194242
195−export const openapi_source_spec_fetch_query_param = sqliteTable("openapi_source_spec_fetch_query_param", {
196− id: text('id').notNull(),
197− scope_id: text('scope_id').notNull(),
198− source_id: text('source_id').notNull(),
199− name: text('name').notNull(),
200− kind: text({ enum: ['text', 'secret'] }).notNull(),
201− text_value: text('text_value'),
202− secret_id: text('secret_id'),
203− secret_prefix: text('secret_prefix')
204−}, (table) => [
205− primaryKey({ columns: [table.scope_id, table.id] }),
206− index("openapi_source_spec_fetch_query_param_scope_id_idx").on(table.scope_id),
207− index("openapi_source_spec_fetch_query_param_source_id_idx").on(table.source_id),
208− index("openapi_source_spec_fetch_query_param_secret_id_idx").on(table.secret_id),
209−]);
243+export const openapi_source_spec_fetch_query_param = sqliteTable(
244+ "openapi_source_spec_fetch_query_param",
245+ {
246+ id: text("id").notNull(),
247+ scope_id: text("scope_id").notNull(),
248+ source_id: text("source_id").notNull(),
249+ name: text("name").notNull(),
250+ kind: text({ enum: ["text", "secret"] }).notNull(),
251+ text_value: text("text_value"),
252+ secret_id: text("secret_id"),
253+ secret_prefix: text("secret_prefix"),
254+ },
255+ (table) => [
256+ primaryKey({ columns: [table.scope_id, table.id] }),
257+ index("openapi_source_spec_fetch_query_param_scope_id_idx").on(table.scope_id),
258+ index("openapi_source_spec_fetch_query_param_source_id_idx").on(table.source_id),
259+ index("openapi_source_spec_fetch_query_param_secret_id_idx").on(table.secret_id),
260+ ],
261+);
210262
211−export const mcp_source = sqliteTable("mcp_source", {
212− id: text('id').notNull(),
213− scope_id: text('scope_id').notNull(),
214− name: text('name').notNull(),
215− config: text('config', { mode: "json" }).notNull(),
216− auth_kind: text({ enum: ['none', 'header', 'oauth2'] }).default("none").notNull(),
217− auth_header_name: text('auth_header_name'),
218− auth_secret_id: text('auth_secret_id'),
219− auth_secret_prefix: text('auth_secret_prefix'),
220− auth_connection_id: text('auth_connection_id'),
221− auth_client_id_secret_id: text('auth_client_id_secret_id'),
222− auth_client_secret_secret_id: text('auth_client_secret_secret_id'),
223− created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull()
224−}, (table) => [
225− primaryKey({ columns: [table.scope_id, table.id] }),
226− index("mcp_source_scope_id_idx").on(table.scope_id),
227− index("mcp_source_auth_secret_id_idx").on(table.auth_secret_id),
228− index("mcp_source_auth_connection_id_idx").on(table.auth_connection_id),
229− index("mcp_source_auth_client_id_secret_id_idx").on(table.auth_client_id_secret_id),
230− index("mcp_source_auth_client_secret_secret_id_idx").on(table.auth_client_secret_secret_id),
231−]);
263+export const mcp_source = sqliteTable(
264+ "mcp_source",
265+ {
266+ id: text("id").notNull(),
267+ scope_id: text("scope_id").notNull(),
268+ name: text("name").notNull(),
269+ config: text("config", { mode: "json" }).notNull(),
270+ auth_kind: text({ enum: ["none", "header", "oauth2"] })
271+ .default("none")
272+ .notNull(),
273+ auth_header_name: text("auth_header_name"),
274+ auth_secret_id: text("auth_secret_id"),
275+ auth_secret_prefix: text("auth_secret_prefix"),
276+ auth_connection_id: text("auth_connection_id"),
277+ auth_client_id_secret_id: text("auth_client_id_secret_id"),
278+ auth_client_secret_secret_id: text("auth_client_secret_secret_id"),
279+ created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(),
280+ },
281+ (table) => [
282+ primaryKey({ columns: [table.scope_id, table.id] }),
283+ index("mcp_source_scope_id_idx").on(table.scope_id),
284+ index("mcp_source_auth_secret_id_idx").on(table.auth_secret_id),
285+ index("mcp_source_auth_connection_id_idx").on(table.auth_connection_id),
286+ index("mcp_source_auth_client_id_secret_id_idx").on(table.auth_client_id_secret_id),
287+ index("mcp_source_auth_client_secret_secret_id_idx").on(table.auth_client_secret_secret_id),
288+ ],
289+);
232290
233−export const mcp_source_header = sqliteTable("mcp_source_header", {
234− id: text('id').notNull(),
235− scope_id: text('scope_id').notNull(),
236− source_id: text('source_id').notNull(),
237− name: text('name').notNull(),
238− kind: text({ enum: ['text', 'secret'] }).notNull(),
239− text_value: text('text_value'),
240− secret_id: text('secret_id'),
241− secret_prefix: text('secret_prefix')
242−}, (table) => [
243− primaryKey({ columns: [table.scope_id, table.id] }),
244− index("mcp_source_header_scope_id_idx").on(table.scope_id),
245− index("mcp_source_header_source_id_idx").on(table.source_id),
246− index("mcp_source_header_secret_id_idx").on(table.secret_id),
247−]);
291+export const mcp_source_header = sqliteTable(
292+ "mcp_source_header",
293+ {
294+ id: text("id").notNull(),
295+ scope_id: text("scope_id").notNull(),
296+ source_id: text("source_id").notNull(),
297+ name: text("name").notNull(),
298+ kind: text({ enum: ["text", "secret"] }).notNull(),
299+ text_value: text("text_value"),
300+ secret_id: text("secret_id"),
301+ secret_prefix: text("secret_prefix"),
302+ },
303+ (table) => [
304+ primaryKey({ columns: [table.scope_id, table.id] }),
305+ index("mcp_source_header_scope_id_idx").on(table.scope_id),
306+ index("mcp_source_header_source_id_idx").on(table.source_id),
307+ index("mcp_source_header_secret_id_idx").on(table.secret_id),
308+ ],
309+);
248310
249−export const mcp_source_query_param = sqliteTable("mcp_source_query_param", {
250− id: text('id').notNull(),
251− scope_id: text('scope_id').notNull(),
252− source_id: text('source_id').notNull(),
253− name: text('name').notNull(),
254− kind: text({ enum: ['text', 'secret'] }).notNull(),
255− text_value: text('text_value'),
256− secret_id: text('secret_id'),
257− secret_prefix: text('secret_prefix')
258−}, (table) => [
259− primaryKey({ columns: [table.scope_id, table.id] }),
260− index("mcp_source_query_param_scope_id_idx").on(table.scope_id),
261− index("mcp_source_query_param_source_id_idx").on(table.source_id),
262− index("mcp_source_query_param_secret_id_idx").on(table.secret_id),
263−]);
311+export const mcp_source_query_param = sqliteTable(
312+ "mcp_source_query_param",
313+ {
314+ id: text("id").notNull(),
315+ scope_id: text("scope_id").notNull(),
316+ source_id: text("source_id").notNull(),
317+ name: text("name").notNull(),
318+ kind: text({ enum: ["text", "secret"] }).notNull(),
319+ text_value: text("text_value"),
320+ secret_id: text("secret_id"),
321+ secret_prefix: text("secret_prefix"),
322+ },
323+ (table) => [
324+ primaryKey({ columns: [table.scope_id, table.id] }),
325+ index("mcp_source_query_param_scope_id_idx").on(table.scope_id),
326+ index("mcp_source_query_param_source_id_idx").on(table.source_id),
327+ index("mcp_source_query_param_secret_id_idx").on(table.secret_id),
328+ ],
329+);
264330
265−export const mcp_binding = sqliteTable("mcp_binding", {
266− id: text('id').notNull(),
267− scope_id: text('scope_id').notNull(),
268− source_id: text('source_id').notNull(),
269− binding: text('binding', { mode: "json" }).notNull(),
270− created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull()
271−}, (table) => [
272− primaryKey({ columns: [table.scope_id, table.id] }),
273− index("mcp_binding_scope_id_idx").on(table.scope_id),
274− index("mcp_binding_source_id_idx").on(table.source_id),
275−]);
331+export const mcp_binding = sqliteTable(
332+ "mcp_binding",
333+ {
334+ id: text("id").notNull(),
335+ scope_id: text("scope_id").notNull(),
336+ source_id: text("source_id").notNull(),
337+ binding: text("binding", { mode: "json" }).notNull(),
338+ created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(),
339+ },
340+ (table) => [
341+ primaryKey({ columns: [table.scope_id, table.id] }),
342+ index("mcp_binding_scope_id_idx").on(table.scope_id),
343+ index("mcp_binding_source_id_idx").on(table.source_id),
344+ ],
345+);
276346
277−export const google_discovery_source = sqliteTable("google_discovery_source", {
278− id: text('id').notNull(),
279− scope_id: text('scope_id').notNull(),
280− name: text('name').notNull(),
281− config: text('config', { mode: "json" }).notNull(),
282− auth_kind: text({ enum: ['none', 'oauth2'] }).default("none").notNull(),
283− auth_connection_id: text('auth_connection_id'),
284− auth_client_id_secret_id: text('auth_client_id_secret_id'),
285− auth_client_secret_secret_id: text('auth_client_secret_secret_id'),
286− auth_scopes: text('auth_scopes', { mode: "json" }),
287− created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull(),
288− updated_at: integer('updated_at', { mode: 'timestamp_ms' }).notNull()
289−}, (table) => [
290− primaryKey({ columns: [table.scope_id, table.id] }),
291− index("google_discovery_source_scope_id_idx").on(table.scope_id),
292− index("google_discovery_source_auth_connection_id_idx").on(table.auth_connection_id),
293− index("google_discovery_source_auth_client_id_secret_id_idx").on(table.auth_client_id_secret_id),
294− index("google_discovery_source_auth_client_secret_secret_id_idx").on(table.auth_client_secret_secret_id),
295−]);
347+export const google_discovery_source = sqliteTable(
348+ "google_discovery_source",
349+ {
350+ id: text("id").notNull(),
351+ scope_id: text("scope_id").notNull(),
352+ name: text("name").notNull(),
353+ config: text("config", { mode: "json" }).notNull(),
354+ auth_kind: text({ enum: ["none", "oauth2"] })
355+ .default("none")
356+ .notNull(),
357+ auth_connection_id: text("auth_connection_id"),
358+ auth_client_id_secret_id: text("auth_client_id_secret_id"),
359+ auth_client_secret_secret_id: text("auth_client_secret_secret_id"),
360+ auth_scopes: text("auth_scopes", { mode: "json" }),
361+ created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(),
362+ updated_at: integer("updated_at", { mode: "timestamp_ms" }).notNull(),
363+ },
364+ (table) => [
365+ primaryKey({ columns: [table.scope_id, table.id] }),
366+ index("google_discovery_source_scope_id_idx").on(table.scope_id),
367+ index("google_discovery_source_auth_connection_id_idx").on(table.auth_connection_id),
368+ index("google_discovery_source_auth_client_id_secret_id_idx").on(
369+ table.auth_client_id_secret_id,
370+ ),
371+ index("google_discovery_source_auth_client_secret_secret_id_idx").on(
372+ table.auth_client_secret_secret_id,
373+ ),
374+ ],
375+);
296376
297−export const google_discovery_source_credential_header = sqliteTable("google_discovery_source_credential_header", {
298− id: text('id').notNull(),
299− scope_id: text('scope_id').notNull(),
300− source_id: text('source_id').notNull(),
301− name: text('name').notNull(),
302− kind: text({ enum: ['text', 'secret'] }).notNull(),
303− text_value: text('text_value'),
304− secret_id: text('secret_id'),
305− secret_prefix: text('secret_prefix')
306−}, (table) => [
307− primaryKey({ columns: [table.scope_id, table.id] }),
308− index("google_discovery_source_credential_header_scope_id_idx").on(table.scope_id),
309− index("google_discovery_source_credential_header_source_id_idx").on(table.source_id),
310− index("google_discovery_source_credential_header_secret_id_idx").on(table.secret_id),
311−]);
377+export const google_discovery_source_credential_header = sqliteTable(
378+ "google_discovery_source_credential_header",
379+ {
380+ id: text("id").notNull(),
381+ scope_id: text("scope_id").notNull(),
382+ source_id: text("source_id").notNull(),
383+ name: text("name").notNull(),
384+ kind: text({ enum: ["text", "secret"] }).notNull(),
385+ text_value: text("text_value"),
386+ secret_id: text("secret_id"),
387+ secret_prefix: text("secret_prefix"),
388+ },
389+ (table) => [
390+ primaryKey({ columns: [table.scope_id, table.id] }),
391+ index("google_discovery_source_credential_header_scope_id_idx").on(table.scope_id),
392+ index("google_discovery_source_credential_header_source_id_idx").on(table.source_id),
393+ index("google_discovery_source_credential_header_secret_id_idx").on(table.secret_id),
394+ ],
395+);
312396
313−export const google_discovery_source_credential_query_param = sqliteTable("google_discovery_source_credential_query_param", {
314− id: text('id').notNull(),
315− scope_id: text('scope_id').notNull(),
316− source_id: text('source_id').notNull(),
317− name: text('name').notNull(),
318− kind: text({ enum: ['text', 'secret'] }).notNull(),
319− text_value: text('text_value'),
320− secret_id: text('secret_id'),
321− secret_prefix: text('secret_prefix')
322−}, (table) => [
323− primaryKey({ columns: [table.scope_id, table.id] }),
324− index("google_discovery_source_credential_query_param_scope_id_idx").on(table.scope_id),
325− index("google_discovery_source_credential_query_param_source_id_idx").on(table.source_id),
326− index("google_discovery_source_credential_query_param_secret_id_idx").on(table.secret_id),
327−]);
397+export const google_discovery_source_credential_query_param = sqliteTable(
398+ "google_discovery_source_credential_query_param",
399+ {
400+ id: text("id").notNull(),
401+ scope_id: text("scope_id").notNull(),
402+ source_id: text("source_id").notNull(),
403+ name: text("name").notNull(),
404+ kind: text({ enum: ["text", "secret"] }).notNull(),
405+ text_value: text("text_value"),
406+ secret_id: text("secret_id"),
407+ secret_prefix: text("secret_prefix"),
408+ },
409+ (table) => [
410+ primaryKey({ columns: [table.scope_id, table.id] }),
411+ index("google_discovery_source_credential_query_param_scope_id_idx").on(table.scope_id),
412+ index("google_discovery_source_credential_query_param_source_id_idx").on(table.source_id),
413+ index("google_discovery_source_credential_query_param_secret_id_idx").on(table.secret_id),
414+ ],
415+);
328416
329−export const google_discovery_binding = sqliteTable("google_discovery_binding", {
330− id: text('id').notNull(),
331− scope_id: text('scope_id').notNull(),
332− source_id: text('source_id').notNull(),
333− binding: text('binding', { mode: "json" }).notNull(),
334− created_at: integer('created_at', { mode: 'timestamp_ms' }).notNull()
335−}, (table) => [
336− primaryKey({ columns: [table.scope_id, table.id] }),
337− index("google_discovery_binding_scope_id_idx").on(table.scope_id),
338− index("google_discovery_binding_source_id_idx").on(table.source_id),
339−]);
417+export const google_discovery_binding = sqliteTable(
418+ "google_discovery_binding",
419+ {
420+ id: text("id").notNull(),
421+ scope_id: text("scope_id").notNull(),
422+ source_id: text("source_id").notNull(),
423+ binding: text("binding", { mode: "json" }).notNull(),
424+ created_at: integer("created_at", { mode: "timestamp_ms" }).notNull(),
425+ },
426+ (table) => [
427+ primaryKey({ columns: [table.scope_id, table.id] }),
428+ index("google_discovery_binding_scope_id_idx").on(table.scope_id),
429+ index("google_discovery_binding_source_id_idx").on(table.source_id),
430+ ],
431+);
340432
341−export const graphql_source = sqliteTable("graphql_source", {
342− id: text('id').notNull(),
343− scope_id: text('scope_id').notNull(),
344− name: text('name').notNull(),
345− endpoint: text('endpoint').notNull(),
346− auth_kind: text({ enum: ['none', 'oauth2'] }).default("none").notNull(),
347− auth_connection_id: text('auth_connection_id')
348−}, (table) => [
349− primaryKey({ columns: [table.scope_id, table.id] }),
350− index("graphql_source_scope_id_idx").on(table.scope_id),
351− index("graphql_source_auth_connection_id_idx").on(table.auth_connection_id),
352−]);
433+export const graphql_source = sqliteTable(
434+ "graphql_source",
435+ {
436+ id: text("id").notNull(),
437+ scope_id: text("scope_id").notNull(),
438+ name: text("name").notNull(),
439+ endpoint: text("endpoint").notNull(),
440+ auth_kind: text({ enum: ["none", "oauth2"] })
441+ .default("none")
442+ .notNull(),
443+ auth_connection_id: text("auth_connection_id"),
444+ },
445+ (table) => [
446+ primaryKey({ columns: [table.scope_id, table.id] }),
447+ index("graphql_source_scope_id_idx").on(table.scope_id),
448+ index("graphql_source_auth_connection_id_idx").on(table.auth_connection_id),
449+ ],
450+);
353451
354−export const graphql_source_header = sqliteTable("graphql_source_header", {
355− id: text('id').notNull(),
356− scope_id: text('scope_id').notNull(),
357− source_id: text('source_id').notNull(),
358− name: text('name').notNull(),
359− kind: text({ enum: ['text', 'secret'] }).notNull(),
360− text_value: text('text_value'),
361− secret_id: text('secret_id'),
362− secret_prefix: text('secret_prefix')
363−}, (table) => [
364− primaryKey({ columns: [table.scope_id, table.id] }),
365− index("graphql_source_header_scope_id_idx").on(table.scope_id),
366− index("graphql_source_header_source_id_idx").on(table.source_id),
367− index("graphql_source_header_secret_id_idx").on(table.secret_id),
368−]);
452+export const graphql_source_header = sqliteTable(
453+ "graphql_source_header",
454+ {
455+ id: text("id").notNull(),
456+ scope_id: text("scope_id").notNull(),
457+ source_id: text("source_id").notNull(),
458+ name: text("name").notNull(),
459+ kind: text({ enum: ["text", "secret"] }).notNull(),
460+ text_value: text("text_value"),
461+ secret_id: text("secret_id"),
462+ secret_prefix: text("secret_prefix"),
463+ },
464+ (table) => [
465+ primaryKey({ columns: [table.scope_id, table.id] }),
466+ index("graphql_source_header_scope_id_idx").on(table.scope_id),
467+ index("graphql_source_header_source_id_idx").on(table.source_id),
468+ index("graphql_source_header_secret_id_idx").on(table.secret_id),
469+ ],
470+);
369471
370−export const graphql_source_query_param = sqliteTable("graphql_source_query_param", {
371− id: text('id').notNull(),
372− scope_id: text('scope_id').notNull(),
373− source_id: text('source_id').notNull(),
374− name: text('name').notNull(),
375− kind: text({ enum: ['text', 'secret'] }).notNull(),
376− text_value: text('text_value'),
377− secret_id: text('secret_id'),
378− secret_prefix: text('secret_prefix')
379−}, (table) => [
380− primaryKey({ columns: [table.scope_id, table.id] }),
381− index("graphql_source_query_param_scope_id_idx").on(table.scope_id),
382− index("graphql_source_query_param_source_id_idx").on(table.source_id),
383− index("graphql_source_query_param_secret_id_idx").on(table.secret_id),
384−]);
385−
386−export const graphql_operation = sqliteTable("graphql_operation", {
387− id: text('id').notNull(),
388− scope_id: text('scope_id').notNull(),
389− source_id: text('source_id').notNull(),
390− binding: text('binding', { mode: "json" }).notNull()
391−}, (table) => [
392− primaryKey({ columns: [table.scope_id, table.id] }),
393− index("graphql_operation_scope_id_idx").on(table.scope_id),
394− index("graphql_operation_source_id_idx").on(table.source_id),
395−]);
472+export const graphql_source_query_param = sqliteTable(
473+ "graphql_source_query_param",
474+ {
475+ id: text("id").notNull(),
476+ scope_id: text("scope_id").notNull(),
477+ source_id: text("source_id").notNull(),
478+ name: text("name").notNull(),
479+ kind: text({ enum: ["text", "secret"] }).notNull(),
480+ text_value: text("text_value"),
481+ secret_id: text("secret_id"),
482+ secret_prefix: text("secret_prefix"),
483+ },
484+ (table) => [
485+ primaryKey({ columns: [table.scope_id, table.id] }),
486+ index("graphql_source_query_param_scope_id_idx").on(table.scope_id),
487+ index("graphql_source_query_param_source_id_idx").on(table.source_id),
488+ index("graphql_source_query_param_secret_id_idx").on(table.secret_id),
489+ ],
490+);
396491
492+export const graphql_operation = sqliteTable(
493+ "graphql_operation",
494+ {
495+ id: text("id").notNull(),
496+ scope_id: text("scope_id").notNull(),
497+ source_id: text("source_id").notNull(),
498+ binding: text("binding", { mode: "json" }).notNull(),
499+ },
500+ (table) => [
501+ primaryKey({ columns: [table.scope_id, table.id] }),
502+ index("graphql_operation_scope_id_idx").on(table.scope_id),
503+ index("graphql_operation_source_id_idx").on(table.source_id),
504+ ],
505+);
apps/local/src/server/executor.ts+9−26
@@ -16,17 +16,8 @@ import { · 15 unmodified lines
1616 } from "./db-upgrade";
1717 import { migrateLegacyConnections } from "./migrate-connections";
1818
19−import {
20− Scope,
21− ScopeId,
22− type AnyPlugin,
23− collectSchemas,
24− createExecutor,
25−} from "@executor-js/sdk";
26−import {
27− makeSqliteAdapter,
28− makeSqliteBlobStore,
29−} from "@executor-js/storage-file";
19+import { Scope, ScopeId, type AnyPlugin, collectSchemas, createExecutor } from "@executor-js/sdk";
20+import { makeSqliteAdapter, makeSqliteBlobStore } from "@executor-js/storage-file";
3021 import * as NodeFileSystem from "@effect/platform-node/NodeFileSystem";
3122 import { loadPluginsFromJsonc, makeFileConfigSink } from "@executor-js/config";
3223 import * as executorSchema from "./executor-schema";
@@ -107,9 +98,7 @@ class LocalExecutorTag extends Context.Service<LocalExecutorTag, LocalExecutorBu · 74 unmodified lines
10798
10899 export type LocalExecutor = LocalExecutorBundle["executor"];
109100
110−class LocalExecutorDisposeError extends Data.TaggedError(
111− "LocalExecutorDisposeError",
112−)<{
101+class LocalExecutorDisposeError extends Data.TaggedError("LocalExecutorDisposeError")<{
113102 readonly operation: "createHandle" | "disposeExecutor" | "disposeRuntime";
114103 readonly cause: unknown;
115104 }> {}
@@ -131,13 +120,10 @@ const handleOrNull = (promise: ReturnType<typeof createExecutorHandle>) => · 15 unmodified lines
131120 Effect.runPromise(
132121 Effect.tryPromise({
133122 try: () => promise,
134− catch: (cause) =>
135− new LocalExecutorDisposeError({ operation: "createHandle", cause }),
123+ catch: (cause) => new LocalExecutorDisposeError({ operation: "createHandle", cause }),
136124 }).pipe(
137125 Effect.catch(() =>
138− Effect.succeed<Awaited<ReturnType<typeof createExecutorHandle>> | null>(
139− null,
140− ),
126+ Effect.succeed<Awaited<ReturnType<typeof createExecutorHandle>> | null>(null),
141127 ),
142128 ),
143129 );
@@ -145,7 +131,8 @@ const handleOrNull = (promise: ReturnType<typeof createExecutorHandle>) => · 1 unmodified line
145131 const createLocalExecutorLayer = () => {
146132 const { path: dbPath, legacySecrets } = resolveDbPath();
147133
148− return Layer.effect(LocalExecutorTag)(Effect.gen(function* () {
134+ return Layer.effect(LocalExecutorTag)(
135+ Effect.gen(function* () {
149136 const sqlite = yield* Effect.acquireRelease(
150137 Effect.sync(() => new Database(dbPath)),
151138 (conn) => Effect.sync(() => conn.close()),
@@ -183,9 +170,7 @@ const createLocalExecutorLayer = () => { · 31 unmodified lines
183170 // ordering. Without this, a package listed in both surfaces would
184171 // boot twice (double routes, double in-memory storage).
185172 const staticPackageNames = new Set(
186− staticPlugins
187− .map((p) => p.packageName)
188− .filter((n): n is string => !!n),
173+ staticPlugins.map((p) => p.packageName).filter((n): n is string => !!n),
189174 );
190175 const dedupedDynamic = dynamicPlugins.filter((p) => {
191176 if (p.packageName && staticPackageNames.has(p.packageName)) {
@@ -261,9 +246,7 @@ export const disposeExecutor = async (): Promise<void> => { · 69 unmodified lines
261246 const currentHandlePromise = sharedHandlePromise;
262247 sharedHandlePromise = null;
263248
264− const handle = currentHandlePromise
265− ? await handleOrNull(currentHandlePromise)
266− : null;
249+ const handle = currentHandlePromise ? await handleOrNull(currentHandlePromise) : null;
267250 if (handle) {
268251 await ignorePromiseFailure("disposeExecutor", () => handle.dispose());
269252 }
apps/local/src/server/main.ts+5−3
@@ -103,10 +103,12 @@ export const createServerHandlers = async (): Promise<ServerHandlers> => { · 102 unmodified lines
103103 return { api: apiHandler, mcp };
104104 };
105105
106−export class ServerHandlersService extends Context.Service<ServerHandlersService, ServerHandlers
107−>()("@executor-js/local/ServerHandlersService") {}
106+export class ServerHandlersService extends Context.Service<ServerHandlersService, ServerHandlers>()(
107+ "@executor-js/local/ServerHandlersService",
108+) {}
108109
109−const ServerHandlersLive = Layer.effect(ServerHandlersService)(Effect.acquireRelease(
110+const ServerHandlersLive = Layer.effect(ServerHandlersService)(
111+ Effect.acquireRelease(
110112 Effect.promise(() => createServerHandlers()),
111113 (handlers) => Effect.promise(() => closeServerHandlers(handlers)),
112114 ),
apps/local/src/server/mcp-oauth.test.ts+19−60
@@ -34,30 +34,14 @@ import { FetchHttpClient, HttpRouter, HttpServer } from "effect/unstable/http"; · 33 unmodified lines
3434 import { Effect, Layer, Option, Schema } from "effect";
3535
3636 import { addGroup, observabilityMiddleware } from "@executor-js/api";
37−import {
38− CoreHandlers,
39− ExecutionEngineService,
40− ExecutorService,
41−} from "@executor-js/api/server";
37+import { CoreHandlers, ExecutionEngineService, ExecutorService } from "@executor-js/api/server";
4238 import { createExecutionEngine } from "@executor-js/execution";
4339 import { makeQuickJsExecutor } from "@executor-js/runtime-quickjs";
44−import {
45− Scope,
46− ScopeId,
47− collectSchemas,
48− createExecutor,
49−} from "@executor-js/sdk";
50−import {
51− makeSqliteAdapter,
52− makeSqliteBlobStore,
53−} from "@executor-js/storage-file";
40+import { Scope, ScopeId, collectSchemas, createExecutor } from "@executor-js/sdk";
41+import { makeSqliteAdapter, makeSqliteBlobStore } from "@executor-js/storage-file";
5442 import { fileSecretsPlugin } from "@executor-js/plugin-file-secrets";
5543 import { mcpPlugin } from "@executor-js/plugin-mcp";
56−import {
57− McpExtensionService,
58− McpGroup,
59− McpHandlers,
60−} from "@executor-js/plugin-mcp/api";
44+import { McpExtensionService, McpGroup, McpHandlers } from "@executor-js/plugin-mcp/api";
6145
6246 import * as executorSchema from "./executor-schema";
6347 import { ErrorCaptureLive } from "./observability";
@@ -66,9 +50,10 @@ import { ErrorCaptureLive } from "./observability"; · 2 unmodified lines
6650 // the top level so `observabilityMiddleware` can land its typed-error
6751 // bridge on every endpoint.
6852 const TestApi = addGroup(McpGroup);
69−type TestApiShape = typeof TestApi extends HttpApi.HttpApi<infer _Id, infer Groups>
70− ? HttpApiClient.Client<Groups, never>
71− : never;
53+type TestApiShape =
54+ typeof TestApi extends HttpApi.HttpApi<infer _Id, infer Groups>
55+ ? HttpApiClient.Client<Groups, never>
56+ : never;
7257
7358 // ---------------------------------------------------------------------------
7459 // Fake OAuth + MCP server (mirrors the cloud test)
@@ -84,21 +69,15 @@ const RegistrationBody = Schema.Struct({ · 9 unmodified lines
8469 grant_types: Schema.optional(Schema.Array(Schema.String)),
8570 response_types: Schema.optional(Schema.Array(Schema.String)),
8671 });
87−const decodeRegistrationBody = Schema.decodeUnknownOption(
88− Schema.fromJsonString(RegistrationBody),
89−);
72+const decodeRegistrationBody = Schema.decodeUnknownOption(Schema.fromJsonString(RegistrationBody));
9073
9174 const startFakeServer = async (): Promise<FakeServer> => {
9275 const clients = new Map<string, { redirect_uris: readonly string[] }>();
93− const codes = new Map<
94− string,
95− { readonly clientId: string; readonly codeChallenge: string }
96− >();
76+ const codes = new Map<string, { readonly clientId: string; readonly codeChallenge: string }>();
9777 const accessTokens = new Map<string, { readonly refresh: string }>();
9878 const refreshTokens = new Map<string, string>();
9979 let seq = 0;
100− const next = (p: string) =>
101− `${p}_${++seq}_${randomBytes(6).toString("hex")}`;
80+ const next = (p: string) => `${p}_${++seq}_${randomBytes(6).toString("hex")}`;
10281
10382 const readBody = (req: import("node:http").IncomingMessage): Promise<string> =>
10483 new Promise((resolve, reject) => {
@@ -110,15 +89,10 @@ const startFakeServer = async (): Promise<FakeServer> => { · 5 unmodified lines
11089
11190 const server: Server = createServer(async (req, res) => {
11291 const url = new URL(req.url!, `http://${req.headers.host}`);
113− const send = (
114− status: number,
115− body: unknown,
116− headers: Record<string, string> = {},
117− ) => {
92+ const send = (status: number, body: unknown, headers: Record<string, string> = {}) => {
11893 const payload = typeof body === "string" ? body : JSON.stringify(body);
11994 res.writeHead(status, {
120− "content-type":
121− typeof body === "string" ? "text/plain" : "application/json",
95+ "content-type": typeof body === "string" ? "text/plain" : "application/json",
12296 ...headers,
12397 });
12498 res.end(payload);
@@ -162,10 +136,7 @@ const startFakeServer = async (): Promise<FakeServer> => { · 37 unmodified lines
162136 client_id: clientId,
163137 client_id_issued_at: Math.floor(Date.now() / 1000),
164138 redirect_uris: parsed.redirect_uris ?? [],
165− grant_types: parsed.grant_types ?? [
166− "authorization_code",
167− "refresh_token",
168− ],
139+ grant_types: parsed.grant_types ?? ["authorization_code", "refresh_token"],
169140 response_types: parsed.response_types ?? ["code"],
170141 token_endpoint_auth_method: "none",
171142 });
@@ -202,9 +173,7 @@ const startFakeServer = async (): Promise<FakeServer> => { · 30 unmodified lines
202173 const record = codes.get(code);
203174 if (!record) return send(400, { error: "invalid_grant" });
204175 codes.delete(code);
205− const computed = createHash("sha256")
206− .update(verifier)
207− .digest("base64url");
176+ const computed = createHash("sha256").update(verifier).digest("base64url");
208177 if (computed !== record.codeChallenge) {
209178 return send(400, { error: "invalid_grant" });
210179 }
@@ -240,9 +209,7 @@ const startFakeServer = async (): Promise<FakeServer> => { · 29 unmodified lines
240209 }
241210 });
242211
243− await new Promise<void>((resolve) =>
244− server.listen(0, "127.0.0.1", () => resolve()),
245− );
212+ await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", () => resolve()));
246213 const address = server.address() as AddressInfo;
247214
248215 return {
@@ -328,15 +295,9 @@ const startHarness = async (tmpDir: string): Promise<Harness> => { · 79 unmodified lines
328295 )) as typeof globalThis.fetch,
329296 scopeId,
330297 dispose: async () => {
298+ await Effect.runPromise(Effect.ignore(Effect.tryPromise(() => disposeHandler())));
331299 await Effect.runPromise(
332− Effect.ignore(
333− Effect.tryPromise(() => disposeHandler()),
334− ),
335− );
336− await Effect.runPromise(
337− Effect.ignore(
338− Effect.tryPromise(() => Effect.runPromise(executor.close())),
339− ),
300+ Effect.ignore(Effect.tryPromise(() => Effect.runPromise(executor.close()))),
340301 );
341302 sqlite.close();
342303 },
@@ -400,9 +361,7 @@ describe("local mcp oauth (real OAuth + MCP server)", () => { · 57 unmodified lines
400361 const redirectUrl = "http://local.test/api/mcp/oauth/callback";
401362 const scopeId = ScopeId.make(harness.scopeId);
402363
403− const run = <A, E>(
404− body: (client: TestApiShape) => Effect.Effect<A, E>,
405− ): Effect.Effect<A, E> =>
364+ const run = <A, E>(body: (client: TestApiShape) => Effect.Effect<A, E>): Effect.Effect<A, E> =>
406365 Effect.gen(function* () {
407366 const client = yield* HttpApiClient.make(TestApi, {
408367 baseUrl: TEST_BASE_URL,
apps/local/src/server/migrate-connections.test.ts+3−9
@@ -40,9 +40,7 @@ const columnNames = (db: Database, table: string): ReadonlyArray<string> => · 39 unmodified lines
4040 const MigratedMcpConfig = Schema.Struct({
4141 auth: Schema.optional(Schema.Unknown),
4242 });
43−const decodeMigratedMcpConfig = Schema.decodeUnknownSync(
44− Schema.fromJsonString(MigratedMcpConfig),
45−);
43+const decodeMigratedMcpConfig = Schema.decodeUnknownSync(Schema.fromJsonString(MigratedMcpConfig));
4644
4745 const MigratedOpenApiOAuth2 = Schema.Struct({
4846 kind: Schema.Literal("oauth2"),
@@ -130,9 +128,7 @@ describe("migrateLegacyConnections", () => { · 81 unmodified lines
130128 expect(source.auth_connection_id).toBe("mcp-oauth2-remote-mcp");
131129
132130 const ownedSecrets = db
133− .prepare(
134− "SELECT id, owned_by_connection_id FROM secret WHERE scope_id = ? ORDER BY id",
135− )
131+ .prepare("SELECT id, owned_by_connection_id FROM secret WHERE scope_id = ? ORDER BY id")
136132 .all("scope-1");
137133 expect(ownedSecrets).toEqual([
138134 {
@@ -208,9 +204,7 @@ describe("migrateLegacyConnections", () => { · 69 unmodified lines
208204 await migrateLegacyConnections(db);
209205
210206 const connection = db
211− .prepare(
212− "SELECT id, provider, access_token_secret_id FROM connection WHERE scope_id = ?",
213− )
207+ .prepare("SELECT id, provider, access_token_secret_id FROM connection WHERE scope_id = ?")
214208 .get("scope-1") as
215209 | { readonly id: string; readonly provider: string; readonly access_token_secret_id: string }
216210 | undefined;
apps/local/src/server/migrate-connections.ts+26−62
@@ -16,11 +16,7 @@ import { Database } from "bun:sqlite"; · 15 unmodified lines
1616 import { randomUUID } from "node:crypto";
1717 import { Effect, Option, Result, Schema } from "effect";
1818 import { FetchHttpClient } from "effect/unstable/http";
19−import {
20− parse as parseOpenApi,
21− resolveSpecText,
22− OAuth2Auth,
23−} from "@executor-js/plugin-openapi";
19+import { parse as parseOpenApi, resolveSpecText, OAuth2Auth } from "@executor-js/plugin-openapi";
2420 import { McpConnectionAuth } from "@executor-js/plugin-mcp";
2521
2622 // ---------------------------------------------------------------------------
@@ -51,9 +47,9 @@ const formatBoundaryError = (error: unknown): string => { · 24 unmodified lines
5147 /** Pre-flight: bail unless the drizzle migration that added the Connection
5248 * table + `secret.owned_by_connection_id` has completed. */
5349 const connectionsReady = (sqlite: Database): boolean => {
54− const secretColumns = sqlite
55− .prepare("PRAGMA table_info('secret')")
56− .all() as ReadonlyArray<{ readonly name: string }>;
50+ const secretColumns = sqlite.prepare("PRAGMA table_info('secret')").all() as ReadonlyArray<{
51+ readonly name: string;
52+ }>;
5753 if (!secretColumns.some((c) => c.name === "owned_by_connection_id")) return false;
5854 const connectionTable = sqlite
5955 .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='connection'")
@@ -103,16 +99,10 @@ const rewireSecrets = ( · 43 unmodified lines
10399 ) VALUES (?, ?, ?, ?, ?, ?)`,
104100 );
105101
106− const rows = secretIds.map(
107− (sid) => selectSecret.get(scopeId, sid) as SecretRow | undefined,
108− );
102+ const rows = secretIds.map((sid) => selectSecret.get(scopeId, sid) as SecretRow | undefined);
109103 const alreadyOwned = rows
110104 .filter((r): r is SecretRow => !!r)
111− .filter(
112− (r) =>
113− r.owned_by_connection_id !== null &&
114− r.owned_by_connection_id !== connectionId,
115− );
105+ .filter((r) => r.owned_by_connection_id !== null && r.owned_by_connection_id !== connectionId);
116106 if (alreadyOwned.length > 0) return "secret(s) already owned";
117107
118108 // Early-onboarded rows never got a `secret` routing row — pre-refactor
@@ -123,9 +113,7 @@ const rewireSecrets = ( · 4 unmodified lines
123113 const missingCount = rows.filter((r) => r === undefined).length;
124114 let fallbackProvider: string | null = null;
125115 if (missingCount > 0) {
126− const existing = selectAnySecretProvider.get(scopeId) as
127− | { provider: string }
128− | undefined;
116+ const existing = selectAnySecretProvider.get(scopeId) as { provider: string } | undefined;
129117 fallbackProvider = existing?.provider ?? "keychain";
130118 }
131119
@@ -213,9 +201,7 @@ class LegacyOpenApiOAuth2 extends Schema.Class<LegacyOpenApiOAuth2>("LegacyOpenA · 81 unmodified lines
213201 }) {}
214202
215203 const decodeOpenApiCurrent = Schema.decodeUnknownOption(OAuth2Auth);
216−const decodeOpenApiLegacy = decodeUnknownOptionAs<LegacyOpenApiOAuth2>(
217− LegacyOpenApiOAuth2,
218−);
204+const decodeOpenApiLegacy = decodeUnknownOptionAs<LegacyOpenApiOAuth2>(LegacyOpenApiOAuth2);
219205
220206 const extractAuthorizationUrl = async (
221207 rawSpec: string,
@@ -234,19 +220,18 @@ const extractAuthorizationUrl = async ( · 12 unmodified lines
234220 const spec = parsed.success as unknown;
235221 if (!isRecord(spec)) return null;
236222 const components = isRecord(spec.components) ? spec.components : null;
237− const schemes = components && isRecord(components.securitySchemes)
238− ? components.securitySchemes
239− : null;
240− const scheme = schemes && isRecord(schemes[securitySchemeName])
241− ? (schemes[securitySchemeName] as Record<string, unknown>)
242− : null;
223+ const schemes =
224+ components && isRecord(components.securitySchemes) ? components.securitySchemes : null;
225+ const scheme =
226+ schemes && isRecord(schemes[securitySchemeName])
227+ ? (schemes[securitySchemeName] as Record<string, unknown>)
228+ : null;
243229 const flows = scheme && isRecord(scheme.flows) ? scheme.flows : null;
244− const flowObj = flows && isRecord(flows.authorizationCode)
245− ? (flows.authorizationCode as Record<string, unknown>)
246− : null;
247− return flowObj && isString(flowObj.authorizationUrl)
248− ? flowObj.authorizationUrl
249− : null;
230+ const flowObj =
231+ flows && isRecord(flows.authorizationCode)
232+ ? (flows.authorizationCode as Record<string, unknown>)
233+ : null;
234+ return flowObj && isString(flowObj.authorizationUrl) ? flowObj.authorizationUrl : null;
250235 };
251236
252237 type OpenApiRow = {
@@ -266,11 +251,7 @@ const migrateOpenApi = async (sqlite: Database): Promise<void> => { · 13 unmodified lines
266251 // pointer. Pre-migration, both columns mirror each other; post-
267252 // migration, only `oauth2` is left. We read both when available and
268253 // fall back to `oauth2` so legacy data isn't silently skipped.
269− const hasInvocationConfig = columnExists(
270− sqlite,
271− "openapi_source",
272− "invocation_config",
273− );
254+ const hasInvocationConfig = columnExists(sqlite, "openapi_source", "invocation_config");
274255 const selectCols = hasInvocationConfig
275256 ? "scope_id, id, name, spec, invocation_config, oauth2"
276257 : "scope_id, id, name, spec, NULL AS invocation_config, oauth2";
@@ -283,9 +264,7 @@ const migrateOpenApi = async (sqlite: Database): Promise<void> => { · 6 unmodified lines
283264 ? sqlite.prepare(
284265 "UPDATE openapi_source SET oauth2 = ?, invocation_config = ? WHERE scope_id = ? AND id = ?",
285266 )
286− : sqlite.prepare(
287− "UPDATE openapi_source SET oauth2 = ? WHERE scope_id = ? AND id = ?",
288− );
267+ : sqlite.prepare("UPDATE openapi_source SET oauth2 = ? WHERE scope_id = ? AND id = ?");
289268
290269 for (const row of rows) {
291270 let invocation: Record<string, unknown> = {};
@@ -370,19 +349,13 @@ const migrateOpenApi = async (sqlite: Database): Promise<void> => { · 78 unmodified lines
370349 row.id,
371350 );
372351 } else {
373− updateSource.run(
374− JSON.stringify(oauth2Pointer),
375− row.scope_id,
376− row.id,
377− );
352+ updateSource.run(JSON.stringify(oauth2Pointer), row.scope_id, row.id);
378353 }
379354 });
380355 // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: bun:sqlite transaction reports rollback failures by throwing
381356 try {
382357 txn();
383− console.log(
384− `[migrate-connections] openapi ${row.scope_id}/${row.id} -> ${connectionId}`,
385− );
358+ console.log(`[migrate-connections] openapi ${row.scope_id}/${row.id} -> ${connectionId}`);
386359 } catch (err) {
387360 console.warn(
388361 `[migrate-connections] fail openapi ${row.scope_id}/${row.id}: ${formatBoundaryError(err)}`,
@@ -471,9 +444,7 @@ const migrateMcp = (sqlite: Database): void => { · 82 unmodified lines
471444
472445 const endpoint = typeof config.endpoint === "string" ? config.endpoint : null;
473446 if (!endpoint) {
474− console.warn(
475− `[migrate-connections] skip mcp ${row.scope_id}/${row.id}: endpoint missing`,
476− );
447+ console.warn(`[migrate-connections] skip mcp ${row.scope_id}/${row.id}: endpoint missing`);
477448 continue;
478449 }
479450 const connectionId = `mcp-oauth2-${row.id}`;
@@ -518,12 +489,7 @@ const migrateMcp = (sqlite: Database): void => { · 38 unmodified lines
518489 // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: bun:sqlite transaction callback must throw to roll back
519490 if (err) throw new Error(err);
520491 if (updateConfigAndAuth) {
521− updateConfigAndAuth.run(
522− JSON.stringify(nextConfig),
523− connectionId,
524− row.scope_id,
525− row.id,
526− );
492+ updateConfigAndAuth.run(JSON.stringify(nextConfig), connectionId, row.scope_id, row.id);
527493 } else {
528494 updateConfig.run(JSON.stringify(nextConfig), row.scope_id, row.id);
529495 }
@@ -531,9 +497,7 @@ const migrateMcp = (sqlite: Database): void => { · 1 unmodified line
531497 // oxlint-disable-next-line executor/no-try-catch-or-throw -- boundary: bun:sqlite transaction reports rollback failures by throwing
532498 try {
533499 txn();
534− console.log(
535− `[migrate-connections] mcp ${row.scope_id}/${row.id} -> ${connectionId}`,
536− );
500+ console.log(`[migrate-connections] mcp ${row.scope_id}/${row.id} -> ${connectionId}`);
537501 } catch (err) {
538502 console.warn(
539503 `[migrate-connections] fail mcp ${row.scope_id}/${row.id}: ${formatBoundaryError(err)}`,
apps/local/src/server/migrate-graphql-bindings.test.ts+12−23
@@ -57,9 +57,7 @@ const decodeTableInfoRows = Schema.decodeUnknownSync(Schema.Array(TableInfoRow)) · 56 unmodified lines
5757 const decodeHeaderRows = Schema.decodeUnknownSync(Schema.Array(GraphqlHeaderRow));
5858 const decodeQueryParamRow = Schema.decodeUnknownSync(GraphqlQueryParamRow);
5959 const decodeCountRow = Schema.decodeUnknownSync(CountRow);
60−const decodeHeaderIdRows = Schema.decodeUnknownSync(
61− Schema.Array(GraphqlHeaderIdRow),
62−);
60+const decodeHeaderIdRows = Schema.decodeUnknownSync(Schema.Array(GraphqlHeaderIdRow));
6361
6462 let dir: string;
6563
@@ -96,19 +94,13 @@ describe("0007_normalize_plugin_secret_refs (graphql)", () => { · 30 unmodified lines
9694 const after = new Database(dbPath, { readonly: true });
9795 const row = decodeAuthRow(
9896 after
99− .prepare(
100− "SELECT auth_kind, auth_connection_id FROM graphql_source WHERE id = ?",
101− )
97+ .prepare("SELECT auth_kind, auth_connection_id FROM graphql_source WHERE id = ?")
10298 .get("github"),
10399 );
104100 expect(row.auth_kind).toBe("oauth2");
105101 expect(row.auth_connection_id).toBe("conn-1");
106102 // Old json column is gone.
107− const cols = decodeTableInfoRows(
108− after
109− .prepare("PRAGMA table_info('graphql_source')")
110− .all(),
111− );
103+ const cols = decodeTableInfoRows(after.prepare("PRAGMA table_info('graphql_source')").all());
112104 expect(cols.some((c) => c.name === "auth")).toBe(false);
113105 expect(cols.some((c) => c.name === "headers")).toBe(false);
114106 expect(cols.some((c) => c.name === "query_params")).toBe(false);
@@ -180,9 +172,7 @@ describe("0007_normalize_plugin_secret_refs (graphql)", () => { · 65 unmodified lines
180172
181173 const paramRow = decodeQueryParamRow(
182174 after
183− .prepare(
184− "SELECT kind, secret_id FROM graphql_source_query_param WHERE source_id = ?",
185− )
175+ .prepare("SELECT kind, secret_id FROM graphql_source_query_param WHERE source_id = ?")
186176 .get("example"),
187177 );
188178 expect(paramRow).toMatchObject({ kind: "secret", secret_id: "sec-key" });
@@ -196,9 +186,12 @@ describe("0007_normalize_plugin_secret_refs (graphql)", () => { · 7 unmodified lines
196186 db.exec(PRE_0007_SQL);
197187 stampPriorMigrationsApplied(db);
198188
199− db.prepare(
200− "INSERT INTO graphql_source (scope_id, id, name, endpoint) VALUES (?, ?, ?, ?)",
201− ).run("default-scope", "bare", "Bare", "https://bare.example/graphql");
189+ db.prepare("INSERT INTO graphql_source (scope_id, id, name, endpoint) VALUES (?, ?, ?, ?)").run(
190+ "default-scope",
191+ "bare",
192+ "Bare",
193+ "https://bare.example/graphql",
194+ );
202195 db.close();
203196
204197 const drizzleDb = drizzle(new Database(dbPath));
@@ -207,9 +200,7 @@ describe("0007_normalize_plugin_secret_refs (graphql)", () => { · 2 unmodified lines
207200 const after = new Database(dbPath, { readonly: true });
208201 const row = decodeAuthRow(
209202 after
210− .prepare(
211− "SELECT auth_kind, auth_connection_id FROM graphql_source WHERE id = ?",
212− )
203+ .prepare("SELECT auth_kind, auth_connection_id FROM graphql_source WHERE id = ?")
213204 .get("bare"),
214205 );
215206 expect(row.auth_kind).toBe("none");
@@ -217,9 +208,7 @@ describe("0007_normalize_plugin_secret_refs (graphql)", () => { · 1 unmodified line
217208
218209 const headerCount = decodeCountRow(
219210 after
220− .prepare(
221− "SELECT count(*) as n FROM graphql_source_header WHERE source_id = ?",
222− )
211+ .prepare("SELECT count(*) as n FROM graphql_source_header WHERE source_id = ?")
223212 .get("bare"),
224213 ).n;
225214 expect(headerCount).toBe(0);
apps/local/src/server/migrate-openapi-bindings.test.ts+1−3
@@ -283,9 +283,7 @@ describe("0007_normalize_plugin_secret_refs (openapi)", () => { · 282 unmodified lines
283283 const after = openDatabase(dbPath, { readonly: true });
284284 const qpCount = Schema.decodeUnknownSync(CountRow)(
285285 after
286− .prepare(
287− "SELECT count(*) as n FROM openapi_source_query_param WHERE source_id = ?",
288− )
286+ .prepare("SELECT count(*) as n FROM openapi_source_query_param WHERE source_id = ?")
289287 .get("bare"),
290288 ).n;
291289 expect(qpCount).toBe(0);
apps/local/src/server/observability.ts+1−1
@@ -24,7 +24,7 @@ export const ErrorCaptureLive: Layer.Layer<ErrorCapture> = Layer.succeed( · 23 unmodified lines
2424 console.error(
2525 `[executor ${traceId}]`,
2626 // oxlint-disable-next-line executor/no-instanceof-error -- boundary: console logger preserves native Error stack output
27− squashed instanceof Error ? squashed.stack ?? squashed : squashed,
27+ squashed instanceof Error ? (squashed.stack ?? squashed) : squashed,
2828 );
2929 console.error(`[executor ${traceId}] cause:`, Cause.pretty(cause));
3030 return traceId;
apps/marketing/src/styles/global.css+3−2
@@ -218,7 +218,9 @@ body { · 217 unmodified lines
218218 text-decoration: underline;
219219 text-decoration-color: color-mix(in srgb, var(--color-teal) 35%, transparent);
220220 text-underline-offset: 3px;
221− transition: color 0.15s ease, text-decoration-color 0.15s ease;
221+ transition:
222+ color 0.15s ease,
223+ text-decoration-color 0.15s ease;
222224 }
223225
224226 .legal-prose a:hover {
@@ -230,4 +232,3 @@ body { · 5 unmodified lines
230232 color: var(--color-ink);
231233 font-weight: 600;
232234 }
233−
autumn.config.ts+64−64
@@ -2,84 +2,84 @@ import { feature, item, plan } from "atmn"; · 1 unmodified line
22
33 // Features
44 export const executions = feature({
5− id: 'executions',
6− name: 'Executions',
7− type: 'metered',
8− consumable: true,
5+ id: "executions",
6+ name: "Executions",
7+ type: "metered",
8+ consumable: true,
99 });
1010
1111 export const domainVerification = feature({
12− id: 'domain-verification',
13− name: 'Domain Verification',
14− type: 'boolean',
12+ id: "domain-verification",
13+ name: "Domain Verification",
14+ type: "boolean",
1515 });
1616
1717 export const members = feature({
18− id: 'members',
19− name: 'Members',
20− type: 'metered',
21− consumable: false,
22− archived: true,
18+ id: "members",
19+ name: "Members",
20+ type: "metered",
21+ consumable: false,
22+ archived: true,
2323 });
2424
2525 // Plans
2626 export const free = plan({
27− id: 'free',
28− name: 'Free',
29− addOn: false,
30− autoEnable: true,
31− items: [
32− item({
33− featureId: executions.id,
34− included: 10000,
35− reset: {
36− interval: 'month',
37− },
38− }),
39− ],
27+ id: "free",
28+ name: "Free",
29+ addOn: false,
30+ autoEnable: true,
31+ items: [
32+ item({
33+ featureId: executions.id,
34+ included: 10000,
35+ reset: {
36+ interval: "month",
37+ },
38+ }),
39+ ],
4040 });
4141
4242 export const freePayAsYouGo = plan({
43− id: 'free-pay-as-you-go',
44− name: 'Free Pay As You Go',
45− addOn: false,
46− autoEnable: false,
47− items: [
48− item({
49− featureId: executions.id,
50− included: 10000,
51− price: {
52− amount: 0.2,
53− billingUnits: 1000,
54− billingMethod: 'usage_based',
55− interval: 'month',
56− },
57− }),
58− ],
43+ id: "free-pay-as-you-go",
44+ name: "Free Pay As You Go",
45+ addOn: false,
46+ autoEnable: false,
47+ items: [
48+ item({
49+ featureId: executions.id,
50+ included: 10000,
51+ price: {
52+ amount: 0.2,
53+ billingUnits: 1000,
54+ billingMethod: "usage_based",
55+ interval: "month",
56+ },
57+ }),
58+ ],
5959 });
6060
6161 export const team = plan({
62− id: 'team',
63− name: 'Team',
64− addOn: false,
65− autoEnable: false,
66− price: {
67− amount: 49,
68− interval: 'month',
69− },
70− items: [
71− item({
72− featureId: executions.id,
73− included: 250000,
74− price: {
75− amount: 0.2,
76− billingUnits: 1000,
77− billingMethod: 'usage_based',
78− interval: 'month',
79− },
80− }),
81− item({
82− featureId: domainVerification.id,
83− }),
84− ],
62+ id: "team",
63+ name: "Team",
64+ addOn: false,
65+ autoEnable: false,
66+ price: {
67+ amount: 49,
68+ interval: "month",
69+ },
70+ items: [
71+ item({
72+ featureId: executions.id,
73+ included: 250000,
74+ price: {
75+ amount: 0.2,
76+ billingUnits: 1000,
77+ billingMethod: "usage_based",
78+ interval: "month",
79+ },
80+ }),
81+ item({
82+ featureId: domainVerification.id,
83+ }),
84+ ],
8585 });
examples/all-plugins/src/main.ts+3−10
@@ -141,17 +141,13 @@ const exampleOpenApiSpec = JSON.stringify({ · 140 unmodified lines
141141 get: {
142142 operationId: "items.get",
143143 tags: ["items"],
144− parameters: [
145− { name: "id", in: "path", required: true, schema: { type: "string" } },
146− ],
144+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
147145 responses: { "200": { description: "ok" } },
148146 },
149147 delete: {
150148 operationId: "items.delete",
151149 tags: ["items"],
152− parameters: [
153− { name: "id", in: "path", required: true, schema: { type: "string" } },
154− ],
150+ parameters: [{ name: "id", in: "path", required: true, schema: { type: "string" } }],
155151 responses: { "204": { description: "deleted" } },
156152 },
157153 },
@@ -400,10 +396,7 @@ const program = Effect.gen(function* () { · 242 unmodified lines
400396 console.log(" executor.keychain.isSupported:", executor.keychain.isSupported);
401397 console.log(" executor.keychain.displayName:", executor.keychain.displayName);
402398
403− console.log(
404− " executor.fileSecrets.filePath: ",
405− executor.fileSecrets.filePath,
406− );
399+ console.log(" executor.fileSecrets.filePath: ", executor.fileSecrets.filePath);
407400
408401 // executor.mcp.addSource({ connector: { kind: "remote", endpoint: "..." } });
409402 // executor.googleDiscovery.addSource({ discoveryUrl: "..." });
notes/artifacts-workflows-and-generated-ui.md+45−71
@@ -64,13 +64,7 @@ Do not make this workflow-specific. Model the generic thing as an artifact · 63 unmodified lines
6464 project, with workflows as the first consumer.
6565
6666 ```ts
67−type ArtifactKind =
68− | "workflow"
69− | "component"
70− | "ui"
71− | "template"
72− | "connector"
73− | "document";
67+type ArtifactKind = "workflow" | "component" | "ui" | "template" | "connector" | "document";
7468
7569 type ArtifactProject = {
7670 id: string;
@@ -171,18 +165,13 @@ export interface ArtifactStore { · 94 unmodified lines
171165 input: CreateArtifactProjectInput,
172166 ) => Effect.Effect<ArtifactProject, ArtifactError>;
173167
174− readonly readFile: (
175− ref: ArtifactRef,
176− path: string,
177− ) => Effect.Effect<Uint8Array, ArtifactError>;
168+ readonly readFile: (ref: ArtifactRef, path: string) => Effect.Effect<Uint8Array, ArtifactError>;
178169
179170 readonly writeFiles: (
180171 input: WriteArtifactFilesInput,
181172 ) => Effect.Effect<ArtifactCommit, ArtifactError>;
182173
183− readonly diff: (
184− input: ArtifactDiffInput,
185− ) => Effect.Effect<ArtifactDiff, ArtifactError>;
174+ readonly diff: (input: ArtifactDiffInput) => Effect.Effect<ArtifactDiff, ArtifactError>;
186175
187176 readonly forkProject: (
188177 input: ForkArtifactProjectInput,
@@ -365,10 +354,7 @@ export interface ArtifactStore { · 176 unmodified lines
365354 name: string;
366355 }) => Effect.Effect<ArtifactProject, ArtifactError>;
367356
368− readonly readFile: (
369− ref: ArtifactRef,
370− path: string,
371− ) => Effect.Effect<Uint8Array, ArtifactError>;
357+ readonly readFile: (ref: ArtifactRef, path: string) => Effect.Effect<Uint8Array, ArtifactError>;
372358
373359 readonly writeFiles: (input: {
374360 projectId: string;
@@ -422,10 +408,7 @@ Feature plugin: · 47 unmodified lines
422408 ```ts
423409 // @executor/workflows
424410 import { definePlugin, type PluginCtx } from "@executor/sdk";
425−import {
426− ArtifactStoreProtocol,
427− type ArtifactsProtocolCtx,
428−} from "@executor/artifacts";
411+import { ArtifactStoreProtocol, type ArtifactsProtocolCtx } from "@executor/artifacts";
429412
430413 type WorkflowCtx<TStore> = PluginCtx<TStore, ArtifactsProtocolCtx>;
431414
@@ -546,56 +529,47 @@ export async function summarizeTicket(input: SupportEmail) { · 114 unmodified lines
546529 E2E flow:
547530
548531 ```ts
549−it.effect(
550− "creates a code-first workflow artifact and indexes a visual graph",
551− () =>
552− Effect.gen(function* () {
553− const executor = yield* createExecutor({
554− scopes: [testScope],
555− adapter,
556− blobs,
557− plugins: [
558− artifactsLocalGitPlugin({ rootDir: tempDir }),
559− workflowsPlugin(),
560− ] as const,
561− protocols: {
562− "executor.artifacts.store": "artifacts-local-git",
532+it.effect("creates a code-first workflow artifact and indexes a visual graph", () =>
533+ Effect.gen(function* () {
534+ const executor = yield* createExecutor({
535+ scopes: [testScope],
536+ adapter,
537+ blobs,
538+ plugins: [artifactsLocalGitPlugin({ rootDir: tempDir }), workflowsPlugin()] as const,
539+ protocols: {
540+ "executor.artifacts.store": "artifacts-local-git",
541+ },
542+ });
543+
544+ const created = yield* executor.workflows.create({
545+ name: "Support triage",
546+ files: [
547+ {
548+ type: "put",
549+ path: "artifact.json",
550+ contents: JSON.stringify({
551+ schemaVersion: 1,
552+ kind: "workflow",
553+ entrypoints: ["src/workflows/support-triage.workflow.ts"],
554+ }),
555+ },
556+ {
557+ type: "put",
558+ path: "src/workflows/support-triage.workflow.ts",
559+ contents: supportTriageWorkflowSource,
563560 },
564− });
565−
566− const created = yield* executor.workflows.create({
567− name: "Support triage",
568− files: [
569− {
570− type: "put",
571− path: "artifact.json",
572− contents: JSON.stringify({
573− schemaVersion: 1,
574− kind: "workflow",
575− entrypoints: ["src/workflows/support-triage.workflow.ts"],
576− }),
577− },
578− {
579− type: "put",
580− path: "src/workflows/support-triage.workflow.ts",
581− contents: supportTriageWorkflowSource,
582− },
583− ],
584− });
585−
586− const source = yield* executor.artifacts.readFile(
587− { projectId: created.project.id, ref: created.commit.id },
588− "src/workflows/support-triage.workflow.ts",
589− );
590− expect(new TextDecoder().decode(source)).toContain('"use workflow"');
591−
592− expect(created.manifest.entrypoints).toEqual([
593− "src/workflows/support-triage.workflow.ts",
594− ]);
595− expect(created.manifest.graph.nodes.map((node) => node.kind)).toContain(
596− "step",
597− );
598− }),
561+ ],
562+ });
563+
564+ const source = yield* executor.artifacts.readFile(
565+ { projectId: created.project.id, ref: created.commit.id },
566+ "src/workflows/support-triage.workflow.ts",
567+ );
568+ expect(new TextDecoder().decode(source)).toContain('"use workflow"');
569+
570+ expect(created.manifest.entrypoints).toEqual(["src/workflows/support-triage.workflow.ts"]);
571+ expect(created.manifest.graph.nodes.map((node) => node.kind)).toContain("step");
572+ }),
599573 );
600574 ```
601575
notes/dynamic-plugin-loading-v1.md+177−187
@@ -89,7 +89,7 @@ Surveyed five plugin systems in `.reference/`: · 88 unmodified lines
8989 - **emdash** — **closest match to our setup.** Astro/Vite + React, plugins are
9090 npm packages with separate `./` and `./admin` exports, registered via
9191 import-and-call in `astro.config.mjs`. `definePlugin({ id, hooks, routes,
92− admin })` declarative shape.
92+admin })` declarative shape.
9393 - **dynamic-software** — most ambitious; Cloudflare Worker Loader for cloud
9494 isolation, iframe + postMessage RPC for UI, Proxy-based typed API client.
9595
@@ -109,10 +109,10 @@ inference flows naturally; no codegen step. · 13 unmodified lines
109109
110110 ```ts
111111 // apps/local/executor.config.ts (after)
112−import { defineExecutorConfig } from "@executor-js/sdk"
113−import { openApiPlugin } from "@executor-js/plugin-openapi"
114−import { mcpPlugin } from "@executor-js/plugin-mcp"
115−import { fileSecretsPlugin } from "@executor-js/plugin-file-secrets"
112+import { defineExecutorConfig } from "@executor-js/sdk";
113+import { openApiPlugin } from "@executor-js/plugin-openapi";
114+import { mcpPlugin } from "@executor-js/plugin-mcp";
115+import { fileSecretsPlugin } from "@executor-js/plugin-file-secrets";
116116
117117 export default defineExecutorConfig({
118118 dialect: "sqlite",
@@ -121,7 +121,7 @@ export default defineExecutorConfig({ · 2 unmodified lines
121121 mcpPlugin({ dangerouslyAllowStdioMCP: true }),
122122 fileSecretsPlugin(),
123123 ] as const,
124−})
124+});
125125 ```
126126
127127 "Dynamic" means npm-installable, not load-by-string-name. Same model as emdash.
@@ -137,12 +137,12 @@ config becomes a need. · 9 unmodified lines
137137 "type": "module",
138138 "exports": {
139139 "./server": "./dist/server.js",
140− "./client": "./dist/client.js"
140+ "./client": "./dist/client.js",
141141 },
142142 "executor": {
143143 "id": "foo",
144− "version": "0.1.0"
145− }
144+ "version": "0.1.0",
145+ },
146146 }
147147 ```
148148
@@ -171,7 +171,7 @@ Client side: separate primitive `defineClientPlugin` lives in · 22 unmodified lines
171171 React types never leak into server bundles.
172172
173173 **Layering — extension is the canonical SDK; routes/handlers is optional HTTP transport.**
174−The HTTP layer is *not a peer* of the SDK; it's a transport over it. Plugin
174+The HTTP layer is _not a peer_ of the SDK; it's a transport over it. Plugin
175175 authors should treat extension as the implementation and write handlers as
176176 thin wrappers that delegate via the `self` parameter (same pattern as
177177 `staticSources(self) => [...]` already in the codebase). This keeps:
@@ -188,7 +188,7 @@ Three plugin shapes fall out of this layering: · 10 unmodified lines
188188 utility for other plugins or scripts. CLI/embedded consumers use
189189 `executor.<id>.method()`. Vite plugin notices no `./client` and skips the
190190 plugin in the frontend bundle entirely.
191−- **Both.** Extension *and* routes/handlers *and* a `./client`. Examples:
191+- **Both.** Extension _and_ routes/handlers _and_ a `./client`. Examples:
192192 openapi, mcp, anything that needs a frontend. Routes are thin wrappers
193193 over extension methods.
194194 - **HTTP-only.** Rare — webhook receivers, OAuth callback URLs. Routes
@@ -214,7 +214,7 @@ Plugin authors can write a complete plugin importing only from · 19 unmodified lines
214214 `@executor-js/sdk` (server) and `@executor-js/sdk/client` (frontend). The SDK
215215 re-exports `Schema`, `HttpApi`, `HttpApiGroup`, `HttpApiEndpoint`,
216216 `HttpApiBuilder`, `Effect` so authors who don't want to dig into Effect
217−don't have to. Authors who *do* want Effect-native code keep importing from
217+don't have to. Authors who _do_ want Effect-native code keep importing from
218218 `effect` directly. This keeps the door open without forcing the dependency.
219219
220220 ### 6. Skip `capabilities` declaration for v1
@@ -247,7 +247,7 @@ already handles this exact pattern for secrets and works fine: · 26 unmodified lines
247247 secretProviders: (ctx) => [makeScopedProvider(...)]
248248 ```
249249
250−Each new pluggable capability gets its *own* typed field on `PluginSpec`,
250+Each new pluggable capability gets its _own_ typed field on `PluginSpec`,
251251 same shape. When artifacts lands, that's `artifactStore: () =>
252252 ArtifactStore`. If connection providers want to be modeled this way, the
253253 existing `connectionProviders` field already is.
@@ -335,24 +335,21 @@ server both import it: · 81 unmodified lines
335335
336336 ```ts
337337 // @executor-js/plugin-foo/src/shared.ts
338−import { HttpApiEndpoint, HttpApiGroup, Schema } from "@executor-js/sdk"
338+import { HttpApiEndpoint, HttpApiGroup, Schema } from "@executor-js/sdk";
339339
340340 export const Thing = Schema.Struct({
341341 id: Schema.String,
342342 name: Schema.String,
343−})
343+});
344344
345345 export const FooApi = HttpApiGroup.make("foo")
346− .add(
347− HttpApiEndpoint.get("listThings")`/things`
348− .addSuccess(Schema.Array(Thing)),
349− )
346+ .add(HttpApiEndpoint.get("listThings")`/things`.addSuccess(Schema.Array(Thing)))
350347 .add(
351348 HttpApiEndpoint.post("syncThing")`/sync/:id`
352349 .setPath(Schema.Struct({ id: Schema.String }))
353350 .addSuccess(Thing)
354351 .addError(SyncError),
355− )
352+ );
356353 ```
357354
358355 ```ts
@@ -429,49 +426,47 @@ Lives in `@executor-js/sdk/client`. Server bundles cannot import this module. · 70 unmodified lines
429426 ```ts
430427 // packages/core/sdk/src/client.ts
431428 export interface ClientPluginSpec<TId extends string = string> {
432− readonly id: TId
429+ readonly id: TId;
433430
434431 /** Pages contributed to the host's TanStack router. Mounted under
435432 * /plugins/{id}/{path}. Sidebar nav metadata declared on the route. */
436− readonly pages?: readonly PageDecl[]
433+ readonly pages?: readonly PageDecl[];
437434
438435 /** Dashboard / overview widgets the host can render in known slots. */
439− readonly widgets?: readonly WidgetDecl[]
436+ readonly widgets?: readonly WidgetDecl[];
440437
441438 /** Components the host can render in named slots (e.g., source-detail
442439 * panels, secret-picker variants). Slot names are part of the host
443440 * contract — plugin opts in by registering. */
444− readonly slots?: Record<string, SlotComponent>
441+ readonly slots?: Record<string, SlotComponent>;
445442
446443 /** Source-plugin contribution — populated by plugins that expose
447444 * `kind` rows in the core `source` table (openapi, mcp, graphql,
448445 * google-discovery). The host's sources page derives its provider
449446 * list from the union of every loaded plugin's `sourcePlugin`
450447 * via `useSourcePlugins()`. */
451− readonly sourcePlugin?: SourcePlugin
448+ readonly sourcePlugin?: SourcePlugin;
452449
453450 /** Secret-provider-plugin contribution — populated by plugins that
454451 * also ship a `secretProviders` server-side capability AND want to
455452 * expose a settings card on the host's secrets page. Surfaced via
456453 * `useSecretProviderPlugins()`. */
457− readonly secretProviderPlugin?: SecretProviderPlugin
454+ readonly secretProviderPlugin?: SecretProviderPlugin;
458455 }
459456
460457 type PageDecl = {
461− path: string // "/", "/edit/$id"
462− component: ComponentType
463− nav?: { label: string; section?: string }
464−}
458+ path: string; // "/", "/edit/$id"
459+ component: ComponentType;
460+ nav?: { label: string; section?: string };
461+};
465462
466463 type WidgetDecl = {
467− id: string
468− component: ComponentType<WidgetProps>
469− size?: "half" | "full"
470−}
464+ id: string;
465+ component: ComponentType<WidgetProps>;
466+ size?: "half" | "full";
467+};
471468
472−export const defineClientPlugin = <TId extends string>(
473− spec: ClientPluginSpec<TId>,
474−) => spec
469+export const defineClientPlugin = <TId extends string>(spec: ClientPluginSpec<TId>) => spec;
475470 ```
476471
477472 ### `createPluginAtomClient` — typed reactive client per plugin
@@ -483,24 +478,21 @@ authors write one line per atom. Same shape as the existing · 5 unmodified lines
483478
484479 ```ts
485480 // packages/core/sdk/src/client.ts (helper)
486−import { HttpApi } from "effect/unstable/httpapi"
487−import { FetchHttpClient } from "effect/unstable/http"
488−import * as AtomHttpApi from "effect/unstable/reactivity/AtomHttpApi"
481+import { HttpApi } from "effect/unstable/httpapi";
482+import { FetchHttpClient } from "effect/unstable/http";
483+import * as AtomHttpApi from "effect/unstable/reactivity/AtomHttpApi";
489484
490485 export const createPluginAtomClient = <G extends HttpApiGroup.Any>(
491486 group: G,
492487 opts: { pluginId: string },
493488 ) => {
494− const bundle = HttpApi.make(`plugin-${opts.pluginId}`).add(group)
495− return AtomHttpApi.Service<`Plugin_${string}Client`>()(
496− `Plugin_${opts.pluginId}Client`,
497− {
498− api: bundle,
499− httpClient: FetchHttpClient.layer,
500− baseUrl: `/_executor/plugins/${opts.pluginId}`,
501− },
502− )
503−}
489+ const bundle = HttpApi.make(`plugin-${opts.pluginId}`).add(group);
490+ return AtomHttpApi.Service<`Plugin_${string}Client`>()(`Plugin_${opts.pluginId}Client`, {
491+ api: bundle,
492+ httpClient: FetchHttpClient.layer,
493+ baseUrl: `/_executor/plugins/${opts.pluginId}`,
494+ });
495+};
504496 ```
505497
506498 Plugin author writes:
@@ -513,39 +505,34 @@ import { · 6 unmodified lines
513505 useAtomValue,
514506 useAtomSet,
515507 AsyncResult,
516−} from "@executor-js/sdk/client"
517−import { FooApi } from "./shared"
508+} from "@executor-js/sdk/client";
509+import { FooApi } from "./shared";
518510
519−const FooClient = createPluginAtomClient(FooApi, { pluginId: "foo" })
511+const FooClient = createPluginAtomClient(FooApi, { pluginId: "foo" });
520512
521513 export const fooThingsAtom = FooClient.query("foo", "listThings", {
522514 timeToLive: "30 seconds",
523515 reactivityKeys: ["foo:things"],
524−})
516+});
525517
526−export const fooSync = FooClient.mutation("foo", "syncThing")
518+export const fooSync = FooClient.mutation("foo", "syncThing");
527519
528520 const FooPage = () => {
529− const things = useAtomValue(fooThingsAtom)
530− const doSync = useAtomSet(fooSync, { mode: "promise" })
521+ const things = useAtomValue(fooThingsAtom);
522+ const doSync = useAtomSet(fooSync, { mode: "promise" });
531523
532524 return AsyncResult.match(things, {
533525 onInitial: () => <Skeleton />,
534526 onFailure: () => <p>Failed to load</p>,
535− onSuccess: ({ value }) => (
536− <Table
537− rows={value}
538− onSync={(id) => doSync({ path: { id } })}
539− />
540− ),
541− })
542−}
527+ onSuccess: ({ value }) => <Table rows={value} onSync={(id) => doSync({ path: { id } })} />,
528+ });
529+};
543530
544531 export default defineClientPlugin({
545532 id: "foo" as const,
546533 pages: [{ path: "/", component: FooPage, nav: { label: "Foo" } }],
547534 widgets: [{ id: "foo-status", component: FooStatus, size: "half" }],
548−})
535+});
549536 ```
550537
551538 Type inference: `FooClient.query("foo", "listThings", ...)` is fully typed
@@ -608,15 +595,17 @@ Single config, two consumers. · 56 unmodified lines
608595
609596 ```ts
610597 // apps/local/src/server/executor.ts (after)
611−import config from "../../executor.config.ts"
612−
613−const executor = yield* createExecutor({
614− scopes: [scope],
615− adapter,
616− blobs,
617− plugins: config.plugins,
618− onElicitation: "accept-all",
619−})
598+import config from "../../executor.config.ts";
599+
600+const executor =
601+ yield *
602+ createExecutor({
603+ scopes: [scope],
604+ adapter,
605+ blobs,
606+ plugins: config.plugins,
607+ onElicitation: "accept-all",
608+ });
620609 ```
621610
622611 The plugins are already configured (factory called) by the time
@@ -631,18 +620,16 @@ export default function executorVite(): Plugin { · 8 unmodified lines
631620 return {
632621 name: "executor-plugins",
633622 resolveId(id) {
634− if (id === "virtual:executor/plugins-client") return "\0" + id
623+ if (id === "virtual:executor/plugins-client") return "\0" + id;
635624 },
636625 async load(id) {
637− if (id !== "\0virtual:executor/plugins-client") return
638− const config = await loadExecutorConfig()
639− const imports = config.plugins
640− .map((p, i) => `import p${i} from "${p.id}/client"`)
641− .join("\n")
642− const list = config.plugins.map((_, i) => `p${i}`).join(", ")
643− return `${imports}\nexport const plugins = [${list}]`
626+ if (id !== "\0virtual:executor/plugins-client") return;
627+ const config = await loadExecutorConfig();
628+ const imports = config.plugins.map((p, i) => `import p${i} from "${p.id}/client"`).join("\n");
629+ const list = config.plugins.map((_, i) => `p${i}`).join(", ");
630+ return `${imports}\nexport const plugins = [${list}]`;
644631 },
645− }
632+ };
646633 }
647634 ```
648635
@@ -655,11 +642,11 @@ file — `__root.tsx` is the only place that touches · 6 unmodified lines
655642
656643 ```tsx
657644 // apps/local/src/routes/__root.tsx
658−import { createRootRoute } from "@tanstack/react-router"
659−import { ExecutorProvider } from "@executor-js/react/api/provider"
660−import { ExecutorPluginsProvider } from "@executor-js/sdk/client"
661−import { plugins as clientPlugins } from "virtual:executor/plugins-client"
662−import { Shell } from "../web/shell"
645+import { createRootRoute } from "@tanstack/react-router";
646+import { ExecutorProvider } from "@executor-js/react/api/provider";
647+import { ExecutorPluginsProvider } from "@executor-js/sdk/client";
648+import { plugins as clientPlugins } from "virtual:executor/plugins-client";
649+import { Shell } from "../web/shell";
663650
664651 export const Route = createRootRoute({
665652 component: () => (
@@ -669,15 +656,15 @@ export const Route = createRootRoute({ · 3 unmodified lines
669656 </ExecutorPluginsProvider>
670657 </ExecutorProvider>
671658 ),
672−})
659+});
673660 ```
674661
675662 ```tsx
676663 // e.g. inside packages/react/src/pages/sources.tsx
677−import { useSourcePlugins } from "@executor-js/sdk/client"
664+import { useSourcePlugins } from "@executor-js/sdk/client";
678665
679666 export function SourcesPage() {
680− const sourcePlugins = useSourcePlugins()
667+ const sourcePlugins = useSourcePlugins();
681668 // ...
682669 }
683670 ```
@@ -701,49 +688,48 @@ needs a Vite restart (not a hot update — config changed). · 17 unmodified lines
701688
702689 ```ts
703690 // src/shared.ts — only @executor-js/sdk imports, no raw effect imports
704−import { HttpApiEndpoint, HttpApiGroup, Schema } from "@executor-js/sdk"
691+import { HttpApiEndpoint, HttpApiGroup, Schema } from "@executor-js/sdk";
705692
706693 export const Greeting = Schema.Struct({
707694 message: Schema.String,
708695 count: Schema.Number,
709−})
710−export type Greeting = typeof Greeting.Type
696+});
697+export type Greeting = typeof Greeting.Type;
711698
712699 export const ExampleApi = HttpApiGroup.make("example").add(
713700 HttpApiEndpoint.post("greet", "/greet", {
714701 payload: Schema.Struct({ name: Schema.String }),
715702 success: Greeting,
716703 }),
717−)
704+);
718705 ```
719706
720707 ```ts
721708 // src/server.ts
722−import {
723− Context, definePlugin, Effect, HttpApi, HttpApiBuilder,
724−} from "@executor-js/sdk"
725−import { ExampleApi } from "./shared"
709+import { Context, definePlugin, Effect, HttpApi, HttpApiBuilder } from "@executor-js/sdk";
710+import { ExampleApi } from "./shared";
726711
727−const ExampleApiBundle = HttpApi.make("example").add(ExampleApi)
712+const ExampleApiBundle = HttpApi.make("example").add(ExampleApi);
728713
729714 interface ExampleExtension {
730715 readonly greet: (
731716 name: string,
732− ) => Effect.Effect<{ readonly message: string; readonly count: number }>
717+ ) => Effect.Effect<{ readonly message: string; readonly count: number }>;
733718 }
734719
735720 export class ExampleExtensionService extends Context.Service<
736− ExampleExtensionService, ExampleExtension,
721+ ExampleExtensionService,
722+ ExampleExtension
737723 >()("ExampleExtensionService") {}
738724
739725 const ExampleHandlers = HttpApiBuilder.group(ExampleApiBundle, "example", (h) =>
740726 h.handle("greet", ({ payload }) =>
741727 Effect.gen(function* () {
742− const ext = yield* ExampleExtensionService
743− return yield* ext.greet(payload.name)
728+ const ext = yield* ExampleExtensionService;
729+ return yield* ext.greet(payload.name);
744730 }),
745731 ),
746−)
732+);
747733
748734 export const examplePlugin = definePlugin(() => ({
749735 id: "example" as const,
@@ -754,57 +740,57 @@ export const examplePlugin = definePlugin(() => ({ · 4 unmodified lines
754740 extension: (ctx): ExampleExtension => ({
755741 greet: (name: string) =>
756742 Effect.sync(() => {
757− ctx.storage.count += 1
743+ ctx.storage.count += 1;
758744 return {
759745 message: `hello ${name}`,
760746 count: ctx.storage.count,
761− }
747+ };
762748 }),
763749 }),
764750
765751 routes: () => ExampleApi,
766752 handlers: () => ExampleHandlers,
767753 extensionService: ExampleExtensionService,
768−}))
754+}));
769755
770−export default examplePlugin
756+export default examplePlugin;
771757 ```
772758
773759 ```tsx
774760 // src/client.tsx
775−import {
776− defineClientPlugin,
777− createPluginAtomClient,
778− useAtomSet,
779−} from "@executor-js/sdk/client"
780−import { useState } from "react"
781−import { ExampleApi } from "./shared"
761+import { defineClientPlugin, createPluginAtomClient, useAtomSet } from "@executor-js/sdk/client";
762+import { useState } from "react";
763+import { ExampleApi } from "./shared";
782764
783−const ExampleClient = createPluginAtomClient(ExampleApi, { pluginId: "example" })
765+const ExampleClient = createPluginAtomClient(ExampleApi, { pluginId: "example" });
784766
785−const greetAtom = ExampleClient.mutation("example", "greet")
767+const greetAtom = ExampleClient.mutation("example", "greet");
786768
787769 const ExamplePage = () => {
788− const [name, setName] = useState("world")
789− const [result, setResult] = useState<string>()
790− const doGreet = useAtomSet(greetAtom, { mode: "promise" })
770+ const [name, setName] = useState("world");
771+ const [result, setResult] = useState<string>();
772+ const doGreet = useAtomSet(greetAtom, { mode: "promise" });
791773
792774 return (
793775 <div>
794776 <input value={name} onChange={(e) => setName(e.target.value)} />
795− <button onClick={async () => {
796− const g = await doGreet({ payload: { name } })
797− setResult(`${g.message} (#${g.count})`)
798− }}>Greet</button>
777+ <button
778+ onClick={async () => {
779+ const g = await doGreet({ payload: { name } });
780+ setResult(`${g.message} (#${g.count})`);
781+ }}
782+ >
783+ Greet
784+ </button>
799785 {result && <pre>{result}</pre>}
800786 </div>
801− )
802−}
787+ );
788+};
803789
804790 export default defineClientPlugin({
805791 id: "example" as const,
806792 pages: [{ path: "/", component: ExamplePage, nav: { label: "Example" } }],
807−})
793+});
808794 ```
809795
810796 ```jsonc
@@ -814,13 +800,13 @@ export default defineClientPlugin({ · 3 unmodified lines
814800 "type": "module",
815801 "exports": {
816802 "./server": "./dist/server.js",
817− "./client": "./dist/client.js"
803+ "./client": "./dist/client.js",
818804 },
819805 "executor": { "id": "example", "version": "0.1.0" },
820806 "peerDependencies": {
821807 "@executor-js/sdk": "workspace:*",
822− "react": "catalog:"
823− }
808+ "react": "catalog:",
809+ },
824810 }
825811 ```
826812
@@ -828,8 +814,8 @@ User adds it: · 1 unmodified line
828814
829815 ```ts
830816 // apps/local/executor.config.ts
831−import { examplePlugin } from "@executor-js/plugin-example/server"
832−plugins: [/* ... */, examplePlugin()]
817+import { examplePlugin } from "@executor-js/plugin-example/server";
818+plugins: [, /* ... */ examplePlugin()];
833819 ```
834820
835821 ## SDK-only plugin example
@@ -852,81 +838,82 @@ file with no `./client` export, no `routes`, no `handlers`. · 16 unmodified lines
852838 "type": "module",
853839 "exports": { "./server": "./dist/server.js" },
854840 "executor": { "id": "rateLimiter", "version": "0.1.0" },
855− "peerDependencies": { "@executor-js/sdk": "workspace:*" }
841+ "peerDependencies": { "@executor-js/sdk": "workspace:*" },
856842 }
857843 ```
858844
859845 ```ts
860846 // src/server.ts
861−import { definePlugin, defineSchema, Effect, Schema } from "@executor-js/sdk"
847+import { definePlugin, defineSchema, Effect, Schema } from "@executor-js/sdk";
862848
863849 interface RateLimiterConfig {
864− defaultLimit?: number
850+ defaultLimit?: number;
865851 }
866852
867−class RateLimitExceeded extends Schema.TaggedError<RateLimitExceeded>()(
868− "RateLimitExceeded",
869− { key: Schema.String, retryAfterMs: Schema.Number },
870−) {}
871−
872−export const rateLimiterPlugin = definePlugin(
873− (opts: RateLimiterConfig = {}) => ({
874− id: "rateLimiter" as const,
875−
876− schema: defineSchema({
877− rate_buckets: {
878− fields: {
879− key: { type: "string", primary: true },
880− tokens: { type: "number" },
881− updated_at: { type: "number" },
882− },
853+class RateLimitExceeded extends Schema.TaggedError<RateLimitExceeded>()("RateLimitExceeded", {
854+ key: Schema.String,
855+ retryAfterMs: Schema.Number,
856+}) {}
857+
858+export const rateLimiterPlugin = definePlugin((opts: RateLimiterConfig = {}) => ({
859+ id: "rateLimiter" as const,
860+
861+ schema: defineSchema({
862+ rate_buckets: {
863+ fields: {
864+ key: { type: "string", primary: true },
865+ tokens: { type: "number" },
866+ updated_at: { type: "number" },
883867 },
884− }),
868+ },
869+ }),
885870
886− storage: ({ adapter }) => ({
887− adapter,
888− defaultLimit: opts.defaultLimit ?? 60,
889− }),
871+ storage: ({ adapter }) => ({
872+ adapter,
873+ defaultLimit: opts.defaultLimit ?? 60,
874+ }),
890875
891− extension: (ctx) => ({
892− check: (key: string, cost = 1) =>
893− Effect.gen(function* () {
894− const bucket = yield* readOrCreateBucket(ctx.storage, key)
895− const refilled = refill(bucket, ctx.storage.defaultLimit)
896− if (refilled.tokens < cost) {
897− return yield* new RateLimitExceeded({
898− key,
899− retryAfterMs: estimateRetry(refilled),
900− })
901− }
902− yield* writeBucket(ctx.storage, key, refilled.tokens - cost)
903− return { allowed: true, remaining: refilled.tokens - cost }
904− }),
876+ extension: (ctx) => ({
877+ check: (key: string, cost = 1) =>
878+ Effect.gen(function* () {
879+ const bucket = yield* readOrCreateBucket(ctx.storage, key);
880+ const refilled = refill(bucket, ctx.storage.defaultLimit);
881+ if (refilled.tokens < cost) {
882+ return yield* new RateLimitExceeded({
883+ key,
884+ retryAfterMs: estimateRetry(refilled),
885+ });
886+ }
887+ yield* writeBucket(ctx.storage, key, refilled.tokens - cost);
888+ return { allowed: true, remaining: refilled.tokens - cost };
889+ }),
905890
906− reset: (key: string) =>
907− Effect.tryPromise(() =>
908− ctx.storage.adapter.delete({
909− model: "rate_buckets",
910− where: [["key", "=", key]],
911− }),
912− ),
913− }),
891+ reset: (key: string) =>
892+ Effect.tryPromise(() =>
893+ ctx.storage.adapter.delete({
894+ model: "rate_buckets",
895+ where: [["key", "=", key]],
896+ }),
897+ ),
914898 }),
915−)
899+}));
916900 ```
917901
918902 Pure programmatic consumption — works identically from CLI, tests,
919903 embedded library use, or another plugin's `extension`:
920904
921905 ```ts
922−const result = yield* executor.rateLimiter.check("user-123", 5)
906+const result = yield * executor.rateLimiter.check("user-123", 5);
923907 // ^? { allowed: true; remaining: number }
924908
925−yield* executor.rateLimiter.check("user-123", 1000).pipe(
926− Effect.catchTag("RateLimitExceeded", (err) =>
927− Effect.log(`hit limit on ${err.key}, retry in ${err.retryAfterMs}ms`),
928− ),
929−)
909+yield *
910+ executor.rateLimiter
911+ .check("user-123", 1000)
912+ .pipe(
913+ Effect.catchTag("RateLimitExceeded", (err) =>
914+ Effect.log(`hit limit on ${err.key}, retry in ${err.retryAfterMs}ms`),
915+ ),
916+ );
930917 ```
931918
932919 What the host does with this plugin:
@@ -1000,6 +987,7 @@ Build order — all shipped. · 67 unmodified lines
1000987 ## References
1001988
1002989 **Reference research (pre-design):**
990+
1003991 - emdash: `astro.config.mjs` import-and-call pattern, `package.json` exports
1004992 for separate admin entry — see `.reference/emdash/demos/plugins-demo/`.
1005993 - dynamic-software: PluginManifest + capability declarations idea (deferred);
@@ -1008,6 +996,7 @@ Build order — all shipped. · 2 unmodified lines
1008996 when we add capability enforcement).
1009997
1010998 **Shipped code:**
999+
10111000 - Plugin contract: `packages/core/sdk/src/plugin.ts` (`PluginSpec`,
10121001 `definePlugin`).
10131002 - Client contract: `packages/core/sdk/src/client.ts`
@@ -1030,6 +1019,7 @@ Build order — all shipped. · 16 unmodified lines
10301019 follows the same per-capability-typed-field pattern.
10311020
10321021 **Adjacent plans:**
1022+
10331023 - `notes/artifacts-workflows-and-generated-ui.md` — the artifacts/workflows
10341024 plan. Uses "protocol" terminology that we're not adopting; read its
10351025 "ArtifactStoreProtocol" as "the typed interface that goes into the
notes/mcp-conn-pool.md+9−7
@@ -4,7 +4,7 @@ · 3 unmodified lines
44
55 The MCP plugin **already pools connections within a session DO**. Production
66 telemetry attributing 17% of `plugin.mcp.connection.acquire` calls to a
7−fresh `plugin.mcp.connection.handshake` represents *cold-start sessions*,
7+fresh `plugin.mcp.connection.handshake` represents _cold-start sessions_,
88 not redundant in-session handshakes. No structural change is required;
99 this PR ships a strict regression test that pins the existing contract
1010 plus a `plugin.mcp.cache_hit` span attribute so future telemetry can
@@ -68,11 +68,12 @@ mutating the cache key per call) trips immediately in CI. · 57 unmodified lines
6868 ## Re-reading the production trace
6969
7070 Trace `b7102047bed975da461c0519d1251de4`:
71+
7172 - `mcp.plugin.resolve_connector` 2.72s
72−- `plugin.mcp.connection.acquire` 2.72s
73+- `plugin.mcp.connection.acquire` 2.72s
7374 - `plugin.mcp.connection.handshake` 2.52s
74−- `executor.storage.transaction` 1.02s (token persist)
75−- `plugin.mcp.client.call_tool` 1.48s
75+- `executor.storage.transaction` 1.02s (token persist)
76+- `plugin.mcp.client.call_tool` 1.48s
7677
7778 Span nesting plus `resolve_connector ≈ acquire ≈ handshake` durations is
7879 the cache-miss path: `resolveConnector` only runs (and emits its span)
@@ -80,11 +81,12 @@ when the cache lookup actually invokes the lookup closure, which only · 1 unmodified line
8081 happens on a miss.
8182
8283 8h aggregate:
84+
8385 - `plugin.mcp.connection.handshake` count 4
84−- `plugin.mcp.connection.acquire` count 24
85−- `mcp.plugin.resolve_connector` count 4
86+- `plugin.mcp.connection.acquire` count 24
87+- `mcp.plugin.resolve_connector` count 4
8688
87−Acquire count is 6× handshake count. The cache *is* hitting on 20 of 24
89+Acquire count is 6× handshake count. The cache _is_ hitting on 20 of 24
8890 calls. The 4 misses are cold-start sessions (each MCP session DO's first
8991 tool call, plus any session where the connection went stale and was
9092 invalidated by the retry path). Six tool calls per session is consistent
notes/old/connections-migration.md+1−1
@@ -21,7 +21,7 @@ to preserve auth state across the upgrade. · 20 unmodified lines
2121
2222 1. `INSERT INTO connection` with the new pointer's `connectionId`,
2323 `provider = "openapi:oauth2"`, and `provider_state` = `{flow,
24− tokenUrl, clientIdSecretId, clientSecretSecretId, scopes}` lifted
24+tokenUrl, clientIdSecretId, clientSecretSecretId, scopes}` lifted
2525 from the legacy row. `access_token_secret_id` + `refresh_token_secret_id`
2626 reuse the legacy secret ids — tokens themselves never move, only
2727 ownership links flip.
notes/old/error-handling.md+4−4
@@ -87,7 +87,7 @@ Owns the translation, the opaque wire schema, and the capture service. · 86 unmodified lines
8787
8888 ```ts
8989 interface ErrorCaptureShape {
90− readonly captureException: (cause: Cause<unknown>) => Effect<string>
90+ readonly captureException: (cause: Cause<unknown>) => Effect<string>;
9191 }
9292 ```
9393
@@ -130,7 +130,7 @@ Plugin authors write normal Effect code. Their extension method error · 36 unmodified lines
130130 unions look like:
131131
132132 ```ts
133−Effect.Effect<X, MyPluginTypedError | StorageError, never>
133+Effect.Effect<X, MyPluginTypedError | StorageError, never>;
134134 ```
135135
136136 Where `MyPluginTypedError` is the union of their own
@@ -152,9 +152,9 @@ class McpGroup extends HttpApiGroup.make("mcp") · 15 unmodified lines
152152 // …
153153 .addError(InternalError)
154154 .addError(McpOAuthError)
155− .addError(McpConnectionError)
155+ .addError(McpConnectionError) {
156156 // …
157−{}
157+}
158158 ```
159159
160160 No per-endpoint `addError`. The framework encodes each tagged error by
notes/old/errored-sources.md+2−1
@@ -54,7 +54,7 @@ source: { · 53 unmodified lines
5454 Effect.catchAll((e) => {
5555 const message = e instanceof Error ? e.message : String(e);
5656 return executor.sources.recordError({ source, message }).pipe(Effect.asVoid);
57−})
57+});
5858 ```
5959
6060 `executor.sources.recordError` is a new core method that writes a
@@ -76,6 +76,7 @@ a special case — current `removeSource` already works regardless of · 15 unmodified lines
7676 ## Scope
7777
7878 Not for the scope-refactor PR. Separate change:
79+
7980 - Schema migration adds two columns.
8081 - `executor.sources.recordError` on the core SDK.
8182 - Wire up in config-sync.
notes/old/indexes.md+1−1
@@ -25,7 +25,7 @@ Tables affected (all drop candidates): · 24 unmodified lines
2525 - `workos_vault_metadata_scope_id_idx`
2626
2727 Root cause: `core-schema.ts` and plugin schemas mark `scope_id: {
28−index: true }`. The drizzle generator should probably *skip* emitting
28+index: true }`. The drizzle generator should probably _skip_ emitting
2929 a single-column index on a column that's already the leftmost column
3030 of a composite primary key. Fix in two places:
3131
notes/old/mcp-testing.md+6−6
@@ -5,10 +5,10 @@ deleting one silently drops coverage that the others can't replace. · 4 unmodified lines
55
66 ## Suites at a glance
77
8−| file | pool | drives | what it proves |
9−|---|---|---|---|
10−| `apps/cloud/src/mcp-session.e2e.node.test.ts` | node | `InMemoryTransport` + SDK `Client` | engine + plugin wiring; schema drift; elicitation semantics |
11−| `apps/cloud/src/mcp-flow.test.ts` | workerd (vitest-pool-workers) | `SELF.fetch` + hand-rolled JSON-RPC | edge HTTP pipeline that does not require a live multi-request DO session |
8+| file | pool | drives | what it proves |
9+| ----------------------------------------------- | ----------------------------- | --------------------------------------------------- | --------------------------------------------------------------------------------------------- |
10+| `apps/cloud/src/mcp-session.e2e.node.test.ts` | node | `InMemoryTransport` + SDK `Client` | engine + plugin wiring; schema drift; elicitation semantics |
11+| `apps/cloud/src/mcp-flow.test.ts` | workerd (vitest-pool-workers) | `SELF.fetch` + hand-rolled JSON-RPC | edge HTTP pipeline that does not require a live multi-request DO session |
1212 | `apps/cloud/src/mcp-miniflare.e2e.node.test.ts` | node + miniflare-on-real-port | SDK `Client` + real `StreamableHTTPClientTransport` | the long-lived-socket DO runtime actually works end-to-end; elicitation round-trips real HTTP |
1313
1414 ## The workerd cross-request I/O wall
@@ -86,12 +86,12 @@ Pattern used in `mcp-miniflare.e2e.node.test.ts`: · 71 unmodified lines
8686 3. Read the bound port from `HttpServer.HttpServer.address` (TcpAddress tag)
8787 inside a `Layer.effect` that produces the test service.
8888 4. Generate the spec with `OpenApi.fromApi(api)` and inject
89− `servers: [{ url: `http://127.0.0.1:${port}` }]` before `JSON.stringify`.
89+ `servers: [{ url: `http://127.0.0.1:${port}` }]`before`JSON.stringify`.
9090 5. SDK Client advertises `capabilities: { elicitation: { form: {} } }` and
9191 registers `client.setRequestHandler(ElicitRequestSchema, ...)` that returns
9292 `{ action: "accept", content: {} }`.
9393 6. Call `execute` with code that (a) calls `tools.openapi.addSource({ spec,
94− namespace })`, (b) invokes the POST operation.
94+namespace })`, (b) invokes the POST operation.
9595
9696 **Tool id format inside the `execute` sandbox.**
9797 `tools.<sourceId>.<group>.<operation>` for openapi. The Effect
notes/old/pluggable-storage.md+45−37
@@ -35,7 +35,9 @@ export interface CoreStore { · 34 unmodified lines
3535
3636 // definitions
3737 readonly putDefinitions: (defs: readonly StoredDefinition[]) => Effect.Effect<void, Error>;
38− readonly listDefinitionsBySource: (sourceId: string) => Effect.Effect<readonly StoredDefinition[], Error>;
38+ readonly listDefinitionsBySource: (
39+ sourceId: string,
40+ ) => Effect.Effect<readonly StoredDefinition[], Error>;
3941 readonly removeDefinitionsBySource: (sourceId: string) => Effect.Effect<void, Error>;
4042
4143 // secrets (routing rows; provider-specific data lives in provider)
@@ -45,10 +47,9 @@ export interface CoreStore { · 3 unmodified lines
4547 readonly removeSecret: (id: string) => Effect.Effect<void, Error>;
4648 }
4749
48−export const makeDefaultCoreStore = (deps: {
49− adapter: DBAdapter;
50− scope: Scope;
51−}): CoreStore => { /* typedAdapter<CoreSchema> wiring, one place */ };
50+export const makeDefaultCoreStore = (deps: { adapter: DBAdapter; scope: Scope }): CoreStore => {
51+ /* typedAdapter<CoreSchema> wiring, one place */
52+};
5253 ```
5354
5455 ## Wiring — defaults passed in deps, force the spread
@@ -70,9 +71,7 @@ export interface ExecutorConfig<TPlugins> { · 15 unmodified lines
7071
7172 // Inside createExecutor:
7273 const defaults = makeDefaultCoreStore({ adapter, scope });
73−const core = config.coreStorage
74− ? config.coreStorage({ adapter, scope, defaults })
75− : defaults;
74+const core = config.coreStorage ? config.coreStorage({ adapter, scope, defaults }) : defaults;
7675 // Executor now calls core.putTools(...), core.removeSource(...), etc.
7776 // No more adapter.create({ model: "tool", ... }) scattered through executor.ts.
7877 ```
@@ -83,7 +82,7 @@ const core = config.coreStorage · 4 unmodified lines
8382 with no type help.
8483 - Anyone implementing from scratch (test doubles, a non-drizzle backend)
8584 gets no squigglies when they miss a method.
86−- Defaults-in-deps gets "only write what you override" ergonomics *and*
85+- Defaults-in-deps gets "only write what you override" ergonomics _and_
8786 keeps the full `CoreStore` return type enforced.
8887
8988 ## What it unlocks
@@ -91,30 +90,39 @@ const core = config.coreStorage · 1 unmodified line
9190 **Algolia sidecar — no string-match, just method overrides:**
9291
9392 ```ts
94−const executor = yield* createExecutor({
95− scope, adapter, blobs, plugins,
96−
97− coreStorage: ({ defaults }) => ({
98− ...defaults,
99− putTools: (tools) =>
100− Effect.gen(function* () {
101− yield* defaults.putTools(tools);
102− yield* Effect.promise(() => algolia.saveObjects({
103− indexName: `tools_${scope.id}`,
104− objects: tools.map((t) => ({ objectID: t.id, ...t })),
105− }));
106− }),
107− removeToolsBySource: (sourceId) =>
108− Effect.gen(function* () {
109− const affected = yield* defaults.listTools();
110− yield* defaults.removeToolsBySource(sourceId);
111− yield* Effect.promise(() => algolia.deleteObjects({
112− indexName: `tools_${scope.id}`,
113− objectIDs: affected.filter((t) => t.sourceId === sourceId).map((t) => t.id),
114− }));
115− }),
116− }),
117−});
93+const executor =
94+ yield *
95+ createExecutor({
96+ scope,
97+ adapter,
98+ blobs,
99+ plugins,
100+
101+ coreStorage: ({ defaults }) => ({
102+ ...defaults,
103+ putTools: (tools) =>
104+ Effect.gen(function* () {
105+ yield* defaults.putTools(tools);
106+ yield* Effect.promise(() =>
107+ algolia.saveObjects({
108+ indexName: `tools_${scope.id}`,
109+ objects: tools.map((t) => ({ objectID: t.id, ...t })),
110+ }),
111+ );
112+ }),
113+ removeToolsBySource: (sourceId) =>
114+ Effect.gen(function* () {
115+ const affected = yield* defaults.listTools();
116+ yield* defaults.removeToolsBySource(sourceId);
117+ yield* Effect.promise(() =>
118+ algolia.deleteObjects({
119+ indexName: `tools_${scope.id}`,
120+ objectIDs: affected.filter((t) => t.sourceId === sourceId).map((t) => t.id),
121+ }),
122+ );
123+ }),
124+ }),
125+ });
118126 ```
119127
120128 Test doubles become trivial — either hand-roll the whole `CoreStore`
@@ -128,13 +136,13 @@ The factory name leaks. Rename to the defaults-in-deps shape: · 7 unmodified lines
128136
129137 ```ts
130138 // before
131−storage: (deps) => makeDefaultOpenapiStore(deps)
139+storage: (deps) => makeDefaultOpenapiStore(deps);
132140
133141 // after
134−storage: ({ defaults }) => defaults // no-op, identical to leaving off `storage:`
142+storage: ({ defaults }) => defaults; // no-op, identical to leaving off `storage:`
135143
136144 // override one method
137−storage: ({ defaults }) => ({ ...defaults, upsertSource: myUpsert })
145+storage: ({ defaults }) => ({ ...defaults, upsertSource: myUpsert });
138146 ```
139147
140148 Mechanical sweep across openapi / mcp / graphql / workos-vault plugins.
@@ -152,6 +160,6 @@ Not intellectually hard, but a broad diff: · 11 unmodified lines
152160 factory renamed to follow the `defaults` contract.
153161 - Docs/readme updates showing the new override pattern.
154162
155−Ideally lands *before* pluggable search — the search sidecar story
163+Ideally lands _before_ pluggable search — the search sidecar story
156164 depends on this refactor to avoid the `if (model === "tool")` antipattern
157165 I'd otherwise be stuck with.
notes/old/promise-sdk-typed-errors.md+3−1
@@ -6,7 +6,7 @@ · 5 unmodified lines
66 `Effect.runPromise(...)`. That works but it's lossy at the Promise
77 boundary:
88
9−- The promise's *rejection type* is `unknown` (or `any` after
9+- The promise's _rejection type_ is `unknown` (or `any` after
1010 `try/catch`). Consumers have to `instanceof InternalError` /
1111 `err._tag === "..."` to discriminate, even though the underlying
1212 Effect had a fully typed error union.
@@ -43,12 +43,14 @@ Do we expose Effect's `Exit` / `Cause` directly to promise consumers, · 30 unmodified lines
4343 or wrap them in a promise-native `Result<A, E>`?
4444
4545 **Expose `Exit`/`Cause`:**
46+
4647 - Pros: one fewer abstraction, no translation layer to maintain, cause
4748 preserves parallel/sequential composition.
4849 - Cons: consumers depend on `effect`'s API even though they're using a
4950 "Promise" SDK — partly defeats the abstraction.
5051
5152 **Wrap in a `Result<A, E>`:**
53+
5254 - Pros: `@executor-js/sdk/promise` consumers don't import `effect` at all,
5355 surface stays small.
5456 - Cons: another type to learn / document; loses some Cause structure
notes/old/rls.md+3−3
@@ -54,8 +54,8 @@ And app-side, once per request inside a transaction (so `SET LOCAL` · 53 unmodified lines
5454 takes):
5555
5656 ```ts
57−yield* sql`SET LOCAL app.scope_chain = ${toPgArray(chain)}`;
58−yield* sql`SET LOCAL app.write_scope = ${writeScope}`;
57+yield * sql`SET LOCAL app.scope_chain = ${toPgArray(chain)}`;
58+yield * sql`SET LOCAL app.write_scope = ${writeScope}`;
5959 ```
6060
6161 `current_setting('…', true)` returns null when the setting is missing,
@@ -69,7 +69,7 @@ scope sees zero rows, not an error that might be handled into a 500. · 7 unmodified lines
6969 tables is whatever `executor-schema.ts` exports with a `scope_id`
7070 column — same set the scope adapter already knows about.
7171 2. Add an Effect layer that wraps the DbService to issue the two `SET
72− LOCAL`s at connection acquire. `postgres.js`'s `sql.begin` already
72+LOCAL`s at connection acquire. `postgres.js`'s `sql.begin` already
7373 gives us the transaction boundary.
7474 3. Leave `BYPASSRLS` off the worker's role so we actually get the
7575 enforcement; grant it on the role used by the migration / admin
notes/old/scoped-source-auth.md+2−7
@@ -20,7 +20,7 @@ The common primitive is not "org auth" or "user auth". It is an ordered · 19 unmodified lines
2020 scope stack:
2121
2222 ```ts
23−[user, workspace, org]
23+[user, workspace, org];
2424 ```
2525
2626 Rows still belong to exactly one scope. Resolution walks the stack
@@ -68,12 +68,7 @@ For example: · 41 unmodified lines
6868
6969 ```ts
7070 oauth2: {
71− flow: "clientCredentials",
72− tokenUrl,
73− clientIdSlot,
74− clientSecretSlot,
75− connectionSlot,
76− scopes
71+ flow: ("clientCredentials", tokenUrl, clientIdSlot, clientSecretSlot, connectionSlot, scopes);
7772 }
7873 ```
7974
notes/old/scopes.md+6−6
@@ -10,15 +10,15 @@ scopes by construction. · 9 unmodified lines
1010
1111 - **SDK** — `createExecutor({ scope: Scope, adapter, blobs, plugins })`.
1212 One `Scope` per executor instance. `scopeAdapter(rootAdapter, {...},
13− schema)` wraps the adapter before it reaches plugin storage or the
13+schema)` wraps the adapter before it reaches plugin storage or the
1414 core-table writers. Every read on a scoped table gets `where scope_id
15− = scope.id` ANDed in; every write gets `scope_id = scope.id` stamped
15+= scope.id` ANDed in; every write gets `scope_id = scope.id` stamped
1616 into the payload. Tx handles passed into transaction callbacks are
1717 also wrapped, so nested writes stay inside the same scope.
1818
1919 - **Plugins** — see a plain `DBAdapter`. They do not know or care about
2020 scope. Every plugin schema that wants isolation declares `scope_id:
21− { type: "string", required: true, index: true }`. Forgetting the
21+{ type: "string", required: true, index: true }`. Forgetting the
2222 column means the adapter passes the plugin's reads/writes through
2323 unscoped (documented failure mode, not silent) — tests catch the
2424 concrete cases we care about.
@@ -44,8 +44,8 @@ sources, tools, secrets, and plugin-owned source detail lookups. · 19 unmodified lines
4444
4545 ```ts
4646 interface ScopeContext {
47− readonly read: readonly string[]; // precedence-ordered, innermost first
48− readonly write: string; // exactly one scope for writes
47+ readonly read: readonly string[]; // precedence-ordered, innermost first
48+ readonly write: string; // exactly one scope for writes
4949 }
5050 ```
5151
@@ -91,7 +91,7 @@ Writes land in exactly one scope chosen by the caller. · 39 unmodified lines
9191 there. When a user invokes Gmail, secret resolution walks the scope
9292 stack (user → workspace → org) and only finds a token if that user
9393 personally oauthed. Policy on the source row — `auth_scope_mandate:
94− "user"` — forces this: secret lookup refuses to return
94+"user"` — forces this: secret lookup refuses to return
9595 workspace-level tokens even if one existed.
9696
9797 - **Local global + per-folder.** `~/.executor/global` holds the outer
notes/old/search.md+6−2
@@ -9,7 +9,7 @@ whatever) without baking any of it into the core. · 8 unmodified lines
99
1010 1. `executor.tools.list()` — `SELECT * FROM tool WHERE scope_id = ?`.
1111 2. In JS, for every tool: normalize + tokenize `id / sourceId / name /
12− description`, score per field with weights, apply coverage + exact-
12+description`, score per field with weights, apply coverage + exact-
1313 phrase boosts, filter, sort, slice.
1414
1515 O(N) per query over the scope. Fine at hundreds of tools, painful past
@@ -74,11 +74,13 @@ type. Same pattern as plugin `storage:` options and the future `coreStorage:`. · 58 unmodified lines
7474 ## Override shapes
7575
7676 **Replace entirely:**
77+
7778 ```ts
7879 search: ({ listTools }) => algoliaSearch({ listTools }),
7980 ```
8081
8182 **Decorate — mix in logging/timing:**
83+
8284 ```ts
8385 search: ({ defaults }) => ({
8486 query: (q) => Effect.gen(function* () {
@@ -91,6 +93,7 @@ search: ({ defaults }) => ({ · 6 unmodified lines
9193 ```
9294
9395 **Hybrid — try Algolia, fall back to local:**
96+
9497 ```ts
9598 search: ({ listTools, defaults }) => ({
9699 query: (q) =>
@@ -109,6 +112,7 @@ it's a storage concern. Handled by wrapping the core store (see · 12 unmodified lines
109112 delegate to the default and mirror to the external index.
110113
111114 The two halves are independent:
115+
112116 - Indexing only (manual reindex): override `coreStorage`, leave `search`
113117 as default.
114118 - Search only (some other populates the index): override `search`, leave
@@ -122,7 +126,7 @@ scorer: · 7 unmodified lines
122126 1. **Projection on `tools.list`** — expose a `select` option so search
123127 doesn't pull `input_schema` / `output_schema`. Biggest per-query win.
124128 2. **Postgres FTS** — `tsvector` generated column on `(name, description,
125− id, source_id)`, GIN index, use `websearch_to_tsquery`. Native
129+id, source_id)`, GIN index, use `websearch_to_tsquery`. Native
126130 ranking, fuzzy via `pg_trgm`. Would replace the in-memory scorer for
127131 the cloud app.
128132 3. **Per-scope LRU** — cache scored results; invalidate on source
notes/old/storage-migration.md+23−15
@@ -3,11 +3,13 @@ · 2 unmodified lines
33 ## What was done
44
55 ### 1. DBSchema alignment with better-auth
6+
67 - Vendored better-auth's `BetterAuthPluginDBSchema` pattern into `storage-core/src/schema.ts`
78 - Made `modelName` optional (falls back to the key), renamed `disableMigrations` → `disableMigration`, dropped `order`
89 - Removed explicit `modelName` from all plugin schemas (core-schema, openapi, mcp, graphql, google-discovery, workos-vault) — keys already match table names
910
1011 ### 2. @executor-js/cli package (packages/core/cli/)
12+
1113 - Mirrors better-auth's CLI approach: load config → collect plugin schemas → generate drizzle TS
1214 - `executor generate --config ./executor.config.ts --output ./src/services/executor-schema.ts`
1315 - Generator ported from better-auth's drizzle generator, adapted for our DBSchema
@@ -15,11 +17,13 @@ · 1 unmodified line
1517 - Uses jiti for config loading (supports TS configs)
1618
1719 ### 3. apps/cloud wired up
20+
1821 - `executor.config.ts` defines plugins with stub credentials (only used for schema shape)
1922 - `drizzle.config.ts` points at both `schema.ts` (cloud tables) and `executor-schema.ts` (executor tables)
2023 - Fresh `drizzle/0000_initial.sql` with all tables (cloud + core + plugin + blob), indexes, FKs
2124
2225 ### 4. apps/local wired up
26+
2327 - `executor.config.ts` defines plugins + dialect sqlite
2428 - `drizzle.config.ts` for drizzle-kit generate/push
2529 - Generated `executor-schema.ts` with all sqlite tables + blob table
@@ -27,6 +31,7 @@ · 1 unmodified line
2731 - At startup: `migrate(db, { migrationsFolder })` from `drizzle-orm/bun-sqlite/migrator` applies pending migrations before constructing the adapter
2832
2933 ### 5. Adapter DDL removed
34+
3035 - `makeSqliteAdapter` and `makePostgresAdapter` no longer run DDL — they return `DBAdapter` directly (not `Effect.Effect<DBAdapter>`)
3136 - `makeSqliteBlobStore` and `makePostgresBlobStore` no longer run DDL — they return `BlobStore` directly
3237 - `buildCreateTableStatements` deleted from `storage-file/compile.ts`
@@ -39,6 +44,7 @@ · 6 unmodified lines
3944 The `@executor-js/config` package already maintains `executor.jsonc` as a source of truth for source configurations. The `config-store.ts` decorator intercepts `putSource`/`removeSource` on each plugin and mirrors to this file. So for existing users, `executor.jsonc` already has everything.
4045
4146 ### Local app migration
47+
4248 1. On first run, `migrate()` creates all tables from the initial migration
4349 2. Add a **sync-from-config step** after executor creation:
4450 - `loadConfig(configPath)` reads `executor.jsonc`
@@ -47,28 +53,30 @@ The `@executor-js/config` package already maintains `executor.jsonc` as a source · 2 unmodified lines
4753 3. Secrets survive untouched — they live in keychain/1password/file-secrets, not in the DB. The `secret` routing table gets re-populated when sources are added.
4854
4955 ### What won't survive (acceptable losses)
56+
5057 - Tool invocation history / blobs (old KV format)
5158 - OAuth sessions (ephemeral, users re-auth)
5259 - Stale source state (re-fetching is actually a benefit)
5360
5461 ### Cloud migration
62+
5563 - Only ~2 users, just drop old tables and re-add manually
5664 - Or write a one-off SQL migration
5765
5866 ## Key files
5967
60−| File | Role |
61−|------|------|
62−| `packages/core/cli/src/commands/generate.ts` | CLI generate command |
63−| `packages/core/cli/src/generators/drizzle.ts` | Drizzle schema generator |
64−| `packages/core/sdk/src/config.ts` | `defineExecutorConfig` helper |
65−| `packages/core/config/src/schema.ts` | `executor.jsonc` schema |
66−| `packages/core/config/src/config-store.ts` | Plugin store → config file decorator |
67−| `packages/core/config/src/load.ts` | Config loading |
68−| `apps/cloud/executor.config.ts` | Cloud config for CLI |
69−| `apps/cloud/src/services/executor-schema.ts` | Generated drizzle schema |
70−| `apps/cloud/drizzle/0000_*.sql` | Fresh migration |
71−| `apps/local/executor.config.ts` | Local config for CLI |
72−| `apps/local/src/server/executor-schema.ts` | Generated drizzle schema |
73−| `apps/local/drizzle/0000_*.sql` | Fresh migration |
74−| `apps/local/src/server/executor.ts` | Local executor bootstrap with `migrate()` |
68+| File | Role |
69+| --------------------------------------------- | ----------------------------------------- |
70+| `packages/core/cli/src/commands/generate.ts` | CLI generate command |
71+| `packages/core/cli/src/generators/drizzle.ts` | Drizzle schema generator |
72+| `packages/core/sdk/src/config.ts` | `defineExecutorConfig` helper |
73+| `packages/core/config/src/schema.ts` | `executor.jsonc` schema |
74+| `packages/core/config/src/config-store.ts` | Plugin store → config file decorator |
75+| `packages/core/config/src/load.ts` | Config loading |
76+| `apps/cloud/executor.config.ts` | Cloud config for CLI |
77+| `apps/cloud/src/services/executor-schema.ts` | Generated drizzle schema |
78+| `apps/cloud/drizzle/0000_*.sql` | Fresh migration |
79+| `apps/local/executor.config.ts` | Local config for CLI |
80+| `apps/local/src/server/executor-schema.ts` | Generated drizzle schema |
81+| `apps/local/drizzle/0000_*.sql` | Fresh migration |
82+| `apps/local/src/server/executor.ts` | Local executor bootstrap with `migrate()` |
notes/research/plugins/manifest-schema.md+92−94
@@ -72,24 +72,24 @@ The manifest should likely be a JSON-serializable object with these top-level se · 71 unmodified lines
7272
7373 ```ts
7474 type PluginManifest = {
75− schemaVersion: string
76− id: string
77− name: string
78− version: string
79− description?: string
80− publisher?: PluginPublisher
81− license?: string
82− homepage?: string
83− repository?: string
84− categories?: PluginCategory[]
85− compatibility: PluginCompatibility
86− entrypoints: PluginEntrypoints
87− capabilities?: PluginCapabilities
88− extensions?: PluginExtensions
89− storage?: PluginStorageDeclaration
90− package?: PluginPackageDeclaration
91− metadata?: Record<string, unknown>
92−}
75+ schemaVersion: string;
76+ id: string;
77+ name: string;
78+ version: string;
79+ description?: string;
80+ publisher?: PluginPublisher;
81+ license?: string;
82+ homepage?: string;
83+ repository?: string;
84+ categories?: PluginCategory[];
85+ compatibility: PluginCompatibility;
86+ entrypoints: PluginEntrypoints;
87+ capabilities?: PluginCapabilities;
88+ extensions?: PluginExtensions;
89+ storage?: PluginStorageDeclaration;
90+ package?: PluginPackageDeclaration;
91+ metadata?: Record<string, unknown>;
92+};
9393 ```
9494
9595 This is a planning type, not a final API.
@@ -135,9 +135,7 @@ The install surface should support package sources rather than assuming one cust · 39 unmodified lines
135135 User-facing install config can stay simple:
136136
137137 ```ts
138−type PluginInstallSpec =
139− | string
140− | [string, Record<string, unknown>]
138+type PluginInstallSpec = string | [string, Record<string, unknown>];
141139 ```
142140
143141 The string identifies the package source. Examples might include `@executor/openapi`, `github:owner/repo`, `https://github.com/owner/repo.git`, or `./plugins/my-plugin`. Tuple options are installation configuration, not manifest data.
@@ -146,10 +144,10 @@ Resolved install metadata should be stored separately from the manifest. It shou · 2 unmodified lines
146144
147145 ```ts
148146 type PluginPackageDeclaration = {
149− source?: "npm" | "git" | "github" | "local" | "registry"
150− packageName?: string
151− repository?: string
152−}
147+ source?: "npm" | "git" | "github" | "local" | "registry";
148+ packageName?: string;
149+ repository?: string;
150+};
153151 ```
154152
155153 This `package` block is optional planning shape, not final schema. For npm packages, standard `package.json` fields may already provide most of this information.
@@ -160,12 +158,12 @@ Compatibility should be explicit and checked before plugin code is loaded. · 4 unmodified lines
160158
161159 ```ts
162160 type PluginCompatibility = {
163− executor: string
164− runtimeApi?: string
165− platforms?: PluginPlatform[]
166−}
161+ executor: string;
162+ runtimeApi?: string;
163+ platforms?: PluginPlatform[];
164+};
167165
168−type PluginPlatform = "local" | "cloud"
166+type PluginPlatform = "local" | "cloud";
169167 ```
170168
171169 `executor` should probably be a semver range for supported Executor versions.
@@ -182,27 +180,27 @@ Entrypoints describe executable code or assets the host may load. · 10 unmodified lines
182180
183181 ```ts
184182 type PluginEntrypoints = {
185− main?: PluginCodeEntrypoint
186− sdk?: PluginCodeEntrypoint
187− source?: PluginCodeEntrypoint
188− secretStore?: PluginCodeEntrypoint
189− apiGroup?: PluginCodeEntrypoint
190− apiHandlers?: PluginCodeEntrypoint
191− routeHandlers?: Record<string, PluginCodeEntrypoint>
192− backgroundServices?: Record<string, PluginCodeEntrypoint>
193− reactSource?: PluginUiEntrypoint
194− reactSecretProvider?: PluginUiEntrypoint
195− ui?: PluginUiEntrypoint
196− adminUi?: PluginUiEntrypoint
197−}
183+ main?: PluginCodeEntrypoint;
184+ sdk?: PluginCodeEntrypoint;
185+ source?: PluginCodeEntrypoint;
186+ secretStore?: PluginCodeEntrypoint;
187+ apiGroup?: PluginCodeEntrypoint;
188+ apiHandlers?: PluginCodeEntrypoint;
189+ routeHandlers?: Record<string, PluginCodeEntrypoint>;
190+ backgroundServices?: Record<string, PluginCodeEntrypoint>;
191+ reactSource?: PluginUiEntrypoint;
192+ reactSecretProvider?: PluginUiEntrypoint;
193+ ui?: PluginUiEntrypoint;
194+ adminUi?: PluginUiEntrypoint;
195+};
198196
199197 type PluginCodeEntrypoint = {
200− module: string
201− export?: string
202− runtime?: PluginRuntimeTarget
203−}
198+ module: string;
199+ export?: string;
200+ runtime?: PluginRuntimeTarget;
201+};
204202
205−type PluginRuntimeTarget = "worker" | "isolate" | "host-trusted"
203+type PluginRuntimeTarget = "worker" | "isolate" | "host-trusted";
206204 ```
207205
208206 The exact shape may change, but the manifest should avoid requiring the host to import a plugin just to discover what code exists.
@@ -237,21 +235,21 @@ Example planning shape: · 28 unmodified lines
237235 ```ts
238236 type PluginLayerDeclaration = {
239237 sdk?: {
240− pluginId: string
241− entrypoint: keyof PluginEntrypoints
242− }
238+ pluginId: string;
239+ entrypoint: keyof PluginEntrypoints;
240+ };
243241 api?: {
244− requiresSdkPlugin: string
245− groupEntrypoint?: keyof PluginEntrypoints
246− handlersEntrypoint?: keyof PluginEntrypoints
247− }
242+ requiresSdkPlugin: string;
243+ groupEntrypoint?: keyof PluginEntrypoints;
244+ handlersEntrypoint?: keyof PluginEntrypoints;
245+ };
248246 react?: Array<{
249− kind: "source" | "secret-provider" | "settings" | "admin"
250− requiresSdkPlugin?: string
251− requiresApi?: boolean
252− entrypoint: keyof PluginEntrypoints
253− }>
254−}
247+ kind: "source" | "secret-provider" | "settings" | "admin";
248+ requiresSdkPlugin?: string;
249+ requiresApi?: boolean;
250+ entrypoint: keyof PluginEntrypoints;
251+ }>;
252+};
255253 ```
256254
257255 This is planning shape, not final schema. The important requirement is that hosts can opt into layers independently while still validating that the selected layers are compatible.
@@ -264,13 +262,13 @@ Extensions declare what the plugin contributes at a product level. · 6 unmodified lines
264262
265263 ```ts
266264 type PluginExtensions = {
267− sources?: SourceExtensionDeclaration[]
268− secretStores?: SecretStoreExtensionDeclaration[]
269− tools?: ToolExtensionDeclaration[]
270− routes?: RouteExtensionDeclaration[]
271− uiSurfaces?: UiSurfaceDeclaration[]
272− backgroundServices?: BackgroundServiceDeclaration[]
273−}
265+ sources?: SourceExtensionDeclaration[];
266+ secretStores?: SecretStoreExtensionDeclaration[];
267+ tools?: ToolExtensionDeclaration[];
268+ routes?: RouteExtensionDeclaration[];
269+ uiSurfaces?: UiSurfaceDeclaration[];
270+ backgroundServices?: BackgroundServiceDeclaration[];
271+};
274272 ```
275273
276274 Some extension declarations may be fully static. Others may be partial declarations that are completed by plugin registration at load time.
@@ -283,16 +281,16 @@ Capabilities declare requested access. Grants are separate host state. · 6 unmodified lines
283281
284282 ```ts
285283 type PluginCapabilities = {
286− tools?: ToolCapabilityDeclaration[]
287− sources?: SourceCapabilityDeclaration[]
288− secrets?: SecretCapabilityDeclaration[]
289− network?: NetworkCapabilityDeclaration[]
290− storage?: StorageCapabilityDeclaration[]
291− scopes?: ScopeCapabilityDeclaration[]
292− filesystem?: FilesystemCapabilityDeclaration[]
293− execution?: ExecutionCapabilityDeclaration[]
294− host?: HostCapabilityDeclaration[]
295−}
284+ tools?: ToolCapabilityDeclaration[];
285+ sources?: SourceCapabilityDeclaration[];
286+ secrets?: SecretCapabilityDeclaration[];
287+ network?: NetworkCapabilityDeclaration[];
288+ storage?: StorageCapabilityDeclaration[];
289+ scopes?: ScopeCapabilityDeclaration[];
290+ filesystem?: FilesystemCapabilityDeclaration[];
291+ execution?: ExecutionCapabilityDeclaration[];
292+ host?: HostCapabilityDeclaration[];
293+};
296294 ```
297295
298296 Capabilities should be deny-by-default. If a capability is missing, the runtime should deny that access.
@@ -344,11 +342,11 @@ The manifest should let plugins declare storage needs separately from storage gr · 45 unmodified lines
344342 ```ts
345343 type PluginStorageDeclaration = {
346344 collections?: Array<{
347− name: string
348− kind: "kv" | "document" | "sql"
349− description?: string
350− }>
351−}
345+ name: string;
346+ kind: "kv" | "document" | "sql";
347+ description?: string;
348+ }>;
349+};
352350 ```
353351
354352 Storage declarations help the host pre-create, migrate, display, or approve plugin storage.
@@ -364,18 +362,18 @@ Executor does not need a custom publishing flow immediately. npm, GitHub/git, an · 9 unmodified lines
364362 ```ts
365363 type PluginArtifacts = {
366364 files?: Array<{
367− path: string
368− size?: number
369− sha256?: string
370− kind?: "code" | "asset" | "manifest" | "source-map"
371− }>
365+ path: string;
366+ size?: number;
367+ sha256?: string;
368+ kind?: "code" | "asset" | "manifest" | "source-map";
369+ }>;
372370 bundle?: {
373− format: "esm"
374− main: string
375− size?: number
376− sha256?: string
377− }
378−}
371+ format: "esm";
372+ main: string;
373+ size?: number;
374+ sha256?: string;
375+ };
376+};
379377 ```
380378
381379 Cloud and local do not need to store cached artifacts the same way, but they should agree on immutable identity and verification where possible.
packages/core/api/src/connections/api.ts+6−15
@@ -1,12 +1,7 @@
11 import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi";
22 import { Schema } from "effect";
33
4−import {
5− ConnectionId,
6− ConnectionInUseError,
7− ScopeId,
8− Usage,
9−} from "@executor-js/sdk";
4+import { ConnectionId, ConnectionInUseError, ScopeId, Usage } from "@executor-js/sdk";
105
116 import { InternalError } from "../observability";
127
@@ -54,13 +49,9 @@ export const ConnectionsApi = HttpApiGroup.make("connections") · 41 unmodified lines
5449 }),
5550 )
5651 .add(
57− HttpApiEndpoint.get(
58− "usages",
59− "/scopes/:scopeId/connections/:connectionId/usages",
60− {
61− params: ConnectionParams,
62− success: Schema.Array(Usage),
63− error: InternalError,
64− },
65− ),
52+ HttpApiEndpoint.get("usages", "/scopes/:scopeId/connections/:connectionId/usages", {
53+ params: ConnectionParams,
54+ success: Schema.Array(Usage),
55+ error: InternalError,
56+ }),
6657 );
packages/core/api/src/handlers/connections.ts+27−30
@@ -18,35 +18,32 @@ const refToResponse = (ref: ConnectionRef) => ({ · 17 unmodified lines
1818 updatedAt: ref.updatedAt.getTime(),
1919 });
2020
21−export const ConnectionsHandlers = HttpApiBuilder.group(
22− ExecutorApi,
23− "connections",
24− (handlers) =>
25− handlers
26− .handle("list", () =>
27− capture(
28− Effect.gen(function* () {
29− const executor = yield* ExecutorService;
30− const refs = yield* executor.connections.list();
31− return refs.map(refToResponse);
32− }),
33− ),
34− )
35− .handle("remove", ({ params: path }) =>
36− capture(
37− Effect.gen(function* () {
38− const executor = yield* ExecutorService;
39− yield* executor.connections.remove(path.connectionId);
40− return { removed: true };
41− }),
42− ),
43− )
44− .handle("usages", ({ params: path }) =>
45− capture(
46− Effect.gen(function* () {
47− const executor = yield* ExecutorService;
48− return yield* executor.connections.usages(path.connectionId);
49− }),
50− ),
21+export const ConnectionsHandlers = HttpApiBuilder.group(ExecutorApi, "connections", (handlers) =>
22+ handlers
23+ .handle("list", () =>
24+ capture(
25+ Effect.gen(function* () {
26+ const executor = yield* ExecutorService;
27+ const refs = yield* executor.connections.list();
28+ return refs.map(refToResponse);
29+ }),
5130 ),
31+ )
32+ .handle("remove", ({ params: path }) =>
33+ capture(
34+ Effect.gen(function* () {
35+ const executor = yield* ExecutorService;
36+ yield* executor.connections.remove(path.connectionId);
37+ return { removed: true };
38+ }),
39+ ),
40+ )
41+ .handle("usages", ({ params: path }) =>
42+ capture(
43+ Effect.gen(function* () {
44+ const executor = yield* ExecutorService;
45+ return yield* executor.connections.usages(path.connectionId);
46+ }),
47+ ),
48+ ),
5249 );
packages/core/api/src/handlers/executions.ts+46−42
@@ -9,60 +9,64 @@ import { capture, captureEngineError } from "@executor-js/api"; · 8 unmodified lines
99 export const ExecutionsHandlers = HttpApiBuilder.group(ExecutorApi, "executions", (handlers) =>
1010 handlers
1111 .handle("execute", ({ payload }) =>
12− capture(Effect.gen(function* () {
13− const engine = yield* ExecutionEngineService;
14− const outcome = yield* captureEngineError(engine.executeWithPause(payload.code));
12+ capture(
13+ Effect.gen(function* () {
14+ const engine = yield* ExecutionEngineService;
15+ const outcome = yield* captureEngineError(engine.executeWithPause(payload.code));
1516
16− if (outcome.status === "completed") {
17− const formatted = formatExecuteResult(outcome.result);
17+ if (outcome.status === "completed") {
18+ const formatted = formatExecuteResult(outcome.result);
19+ return {
20+ status: "completed" as const,
21+ text: formatted.text,
22+ structured: formatted.structured,
23+ isError: formatted.isError,
24+ };
25+ }
26+
27+ const formatted = formatPausedExecution(outcome.execution);
1828 return {
19− status: "completed" as const,
29+ status: "paused" as const,
2030 text: formatted.text,
2131 structured: formatted.structured,
22− isError: formatted.isError,
2332 };
24− }
25−
26− const formatted = formatPausedExecution(outcome.execution);
27− return {
28− status: "paused" as const,
29− text: formatted.text,
30− structured: formatted.structured,
31− };
32− })),
33+ }),
34+ ),
3335 )
3436 .handle("resume", ({ params: path, payload }) =>
35− capture(Effect.gen(function* () {
36− const engine = yield* ExecutionEngineService;
37− const result = yield* captureEngineError(
38− engine.resume(path.executionId, {
39− action: payload.action,
40− content: payload.content as Record<string, unknown> | undefined,
41− }),
42− );
37+ capture(
38+ Effect.gen(function* () {
39+ const engine = yield* ExecutionEngineService;
40+ const result = yield* captureEngineError(
41+ engine.resume(path.executionId, {
42+ action: payload.action,
43+ content: payload.content as Record<string, unknown> | undefined,
44+ }),
45+ );
4346
44− if (!result) {
45− return yield* Effect.fail({
46− _tag: "ExecutionNotFoundError" as const,
47− executionId: path.executionId,
48− });
49− }
47+ if (!result) {
48+ return yield* Effect.fail({
49+ _tag: "ExecutionNotFoundError" as const,
50+ executionId: path.executionId,
51+ });
52+ }
5053
51− if (result.status === "completed") {
52− const formatted = formatExecuteResult(result.result);
54+ if (result.status === "completed") {
55+ const formatted = formatExecuteResult(result.result);
56+ return {
57+ text: formatted.text,
58+ structured: formatted.structured,
59+ isError: formatted.isError,
60+ };
61+ }
62+
63+ const formatted = formatPausedExecution(result.execution);
5364 return {
5465 text: formatted.text,
5566 structured: formatted.structured,
56− isError: formatted.isError,
67+ isError: false,
5768 };
58− }
59−
60− const formatted = formatPausedExecution(result.execution);
61− return {
62− text: formatted.text,
63− structured: formatted.structured,
64− isError: false,
65− };
66− })),
69+ }),
70+ ),
6771 ),
6872 );
packages/core/api/src/handlers/oauth.ts+2−1
@@ -67,7 +67,8 @@ const toPopupErrorMessage = (error: unknown): string => { · 66 unmodified lines
6767 if (message) return message;
6868
6969 const sessionNotFound = decodeOAuthSessionNotFoundError(error);
70− if (Option.isSome(sessionNotFound)) return `OAuth session not found: ${sessionNotFound.value.sessionId}`;
70+ if (Option.isSome(sessionNotFound))
71+ return `OAuth session not found: ${sessionNotFound.value.sessionId}`;
7172 return "Authentication failed";
7273 };
7374
packages/core/api/src/handlers/scope.ts+20−18
@@ -7,23 +7,25 @@ import { capture } from "@executor-js/api"; · 6 unmodified lines
77
88 export const ScopeHandlers = HttpApiBuilder.group(ExecutorApi, "scope", (handlers) =>
99 handlers.handle("info", () =>
10− capture(Effect.gen(function* () {
11− const executor = yield* ExecutorService;
12− // `id` / `name` / `dir` continue to point at the outermost scope so
13− // existing clients keep their source writes org/workspace-scoped.
14− // `stack` exposes the full innermost-first scope stack so the UI can
15− // deliberately target per-user secret writes when binding credentials.
16− const scope = executor.scopes.at(-1)!;
17− return {
18− id: scope.id,
19− name: scope.name,
20− dir: scope.name,
21− stack: executor.scopes.map((entry) => ({
22− id: entry.id,
23− name: entry.name,
24− dir: entry.name,
25− })),
26− };
27− })),
10+ capture(
11+ Effect.gen(function* () {
12+ const executor = yield* ExecutorService;
13+ // `id` / `name` / `dir` continue to point at the outermost scope so
14+ // existing clients keep their source writes org/workspace-scoped.
15+ // `stack` exposes the full innermost-first scope stack so the UI can
16+ // deliberately target per-user secret writes when binding credentials.
17+ const scope = executor.scopes.at(-1)!;
18+ return {
19+ id: scope.id,
20+ name: scope.name,
21+ dir: scope.name,
22+ stack: executor.scopes.map((entry) => ({
23+ id: entry.id,
24+ name: entry.name,
25+ dir: entry.name,
26+ })),
27+ };
28+ }),
29+ ),
2830 ),
2931 );
packages/core/api/src/handlers/secrets.ts+42−32
@@ -17,45 +17,55 @@ const refToResponse = (ref: SecretRef) => ({ · 16 unmodified lines
1717 export const SecretsHandlers = HttpApiBuilder.group(ExecutorApi, "secrets", (handlers) =>
1818 handlers
1919 .handle("list", () =>
20− capture(Effect.gen(function* () {
21− const executor = yield* ExecutorService;
22− const refs = yield* executor.secrets.list();
23− return refs.map(refToResponse);
24− })),
20+ capture(
21+ Effect.gen(function* () {
22+ const executor = yield* ExecutorService;
23+ const refs = yield* executor.secrets.list();
24+ return refs.map(refToResponse);
25+ }),
26+ ),
2527 )
2628 .handle("status", ({ params: path }) =>
27− capture(Effect.gen(function* () {
28− const executor = yield* ExecutorService;
29− const status = yield* executor.secrets.status(path.secretId);
30− return { secretId: path.secretId, status };
31− })),
29+ capture(
30+ Effect.gen(function* () {
31+ const executor = yield* ExecutorService;
32+ const status = yield* executor.secrets.status(path.secretId);
33+ return { secretId: path.secretId, status };
34+ }),
35+ ),
3236 )
3337 .handle("set", ({ params: path, payload }) =>
34− capture(Effect.gen(function* () {
35− const executor = yield* ExecutorService;
36− const ref = yield* executor.secrets.set(
37− new SetSecretInput({
38− id: payload.id,
39− scope: path.scopeId,
40− name: payload.name,
41− value: payload.value,
42− provider: payload.provider,
43− }),
44− );
45− return refToResponse(ref);
46− })),
38+ capture(
39+ Effect.gen(function* () {
40+ const executor = yield* ExecutorService;
41+ const ref = yield* executor.secrets.set(
42+ new SetSecretInput({
43+ id: payload.id,
44+ scope: path.scopeId,
45+ name: payload.name,
46+ value: payload.value,
47+ provider: payload.provider,
48+ }),
49+ );
50+ return refToResponse(ref);
51+ }),
52+ ),
4753 )
4854 .handle("remove", ({ params: path }) =>
49− capture(Effect.gen(function* () {
50− const executor = yield* ExecutorService;
51− yield* executor.secrets.remove(path.secretId);
52− return { removed: true };
53− })),
55+ capture(
56+ Effect.gen(function* () {
57+ const executor = yield* ExecutorService;
58+ yield* executor.secrets.remove(path.secretId);
59+ return { removed: true };
60+ }),
61+ ),
5462 )
5563 .handle("usages", ({ params: path }) =>
56− capture(Effect.gen(function* () {
57− const executor = yield* ExecutorService;
58− return yield* executor.secrets.usages(path.secretId);
59− })),
64+ capture(
65+ Effect.gen(function* () {
66+ const executor = yield* ExecutorService;
67+ return yield* executor.secrets.usages(path.secretId);
68+ }),
69+ ),
6070 ),
6171 );
packages/core/api/src/handlers/sources.ts+69−59
@@ -9,72 +9,82 @@ import { capture } from "@executor-js/api"; · 8 unmodified lines
99 export const SourcesHandlers = HttpApiBuilder.group(ExecutorApi, "sources", (handlers) =>
1010 handlers
1111 .handle("list", () =>
12− capture(Effect.gen(function* () {
13− const executor = yield* ExecutorService;
14− const sources = yield* executor.sources.list();
15− return sources.map((s) => ({
16− id: s.id,
17− scopeId: s.scopeId ? ScopeId.make(s.scopeId) : undefined,
18− name: s.name,
19− kind: s.kind,
20− url: s.url,
21− runtime: s.runtime,
22− canRemove: s.canRemove,
23− canRefresh: s.canRefresh,
24− canEdit: s.canEdit,
25− }));
26− })),
12+ capture(
13+ Effect.gen(function* () {
14+ const executor = yield* ExecutorService;
15+ const sources = yield* executor.sources.list();
16+ return sources.map((s) => ({
17+ id: s.id,
18+ scopeId: s.scopeId ? ScopeId.make(s.scopeId) : undefined,
19+ name: s.name,
20+ kind: s.kind,
21+ url: s.url,
22+ runtime: s.runtime,
23+ canRemove: s.canRemove,
24+ canRefresh: s.canRefresh,
25+ canEdit: s.canEdit,
26+ }));
27+ }),
28+ ),
2729 )
2830 .handle("remove", ({ params: path }) =>
29− capture(Effect.gen(function* () {
30− const executor = yield* ExecutorService;
31− yield* executor.sources.remove(path.sourceId);
32− return { removed: true };
33− })),
31+ capture(
32+ Effect.gen(function* () {
33+ const executor = yield* ExecutorService;
34+ yield* executor.sources.remove(path.sourceId);
35+ return { removed: true };
36+ }),
37+ ),
3438 )
3539 .handle("refresh", ({ params: path }) =>
36− capture(Effect.gen(function* () {
37− const executor = yield* ExecutorService;
38− yield* executor.sources.refresh(path.sourceId);
39− return { refreshed: true };
40− })),
40+ capture(
41+ Effect.gen(function* () {
42+ const executor = yield* ExecutorService;
43+ yield* executor.sources.refresh(path.sourceId);
44+ return { refreshed: true };
45+ }),
46+ ),
4147 )
4248 .handle("tools", ({ params: path }) =>
43− capture(Effect.gen(function* () {
44− const executor = yield* ExecutorService;
45− // Source detail is a management view — include policy-blocked
46− // tools so users can see and unblock them from the same place
47− // they review the source's other tools. Annotations are loaded
48− // so the UI can show the plugin's default approval state for
49− // tools that have no user policy override.
50− const tools = yield* executor.tools.list({
51− sourceId: path.sourceId,
52− includeAnnotations: true,
53− includeBlocked: true,
54− });
55− return tools.map((t) => ({
56− id: ToolId.make(t.id),
57− pluginId: t.pluginId,
58− sourceId: t.sourceId,
59− name: t.name,
60− description: t.description,
61− mayElicit: t.annotations?.mayElicit,
62− requiresApproval: t.annotations?.requiresApproval,
63− approvalDescription: t.annotations?.approvalDescription,
64− }));
65− })),
49+ capture(
50+ Effect.gen(function* () {
51+ const executor = yield* ExecutorService;
52+ // Source detail is a management view — include policy-blocked
53+ // tools so users can see and unblock them from the same place
54+ // they review the source's other tools. Annotations are loaded
55+ // so the UI can show the plugin's default approval state for
56+ // tools that have no user policy override.
57+ const tools = yield* executor.tools.list({
58+ sourceId: path.sourceId,
59+ includeAnnotations: true,
60+ includeBlocked: true,
61+ });
62+ return tools.map((t) => ({
63+ id: ToolId.make(t.id),
64+ pluginId: t.pluginId,
65+ sourceId: t.sourceId,
66+ name: t.name,
67+ description: t.description,
68+ mayElicit: t.annotations?.mayElicit,
69+ requiresApproval: t.annotations?.requiresApproval,
70+ approvalDescription: t.annotations?.approvalDescription,
71+ }));
72+ }),
73+ ),
6674 )
6775 .handle("detect", ({ payload }) =>
68− capture(Effect.gen(function* () {
69− const executor = yield* ExecutorService;
70− const results = yield* executor.sources.detect(payload.url);
71− return results.map((r) => ({
72− kind: r.kind,
73− confidence: r.confidence,
74− endpoint: r.endpoint,
75− name: r.name,
76− namespace: r.namespace,
77− }));
78− })),
76+ capture(
77+ Effect.gen(function* () {
78+ const executor = yield* ExecutorService;
79+ const results = yield* executor.sources.detect(payload.url);
80+ return results.map((r) => ({
81+ kind: r.kind,
82+ confidence: r.confidence,
83+ endpoint: r.endpoint,
84+ name: r.name,
85+ namespace: r.namespace,
86+ }));
87+ }),
88+ ),
7989 ),
8090 );
packages/core/api/src/handlers/tools.ts+32−28
@@ -9,35 +9,39 @@ import { capture } from "@executor-js/api"; · 8 unmodified lines
99 export const ToolsHandlers = HttpApiBuilder.group(ExecutorApi, "tools", (handlers) =>
1010 handlers
1111 .handle("list", () =>
12− capture(Effect.gen(function* () {
13− const executor = yield* ExecutorService;
14− // Tools page is a management view — include policy-blocked tools
15− // (so users can unblock them) and load annotations so the row can
16− // show the plugin's default approval state when no user rule
17− // matches. Mirrors the per-source `sources.tools` handler.
18− const tools = yield* executor.tools.list({
19− includeAnnotations: true,
20− includeBlocked: true,
21− });
22− return tools.map((t) => ({
23− id: ToolId.make(t.id),
24− pluginId: t.pluginId,
25− sourceId: t.sourceId,
26− name: t.name,
27− description: t.description,
28− mayElicit: t.annotations?.mayElicit,
29− requiresApproval: t.annotations?.requiresApproval,
30− }));
31− })),
12+ capture(
13+ Effect.gen(function* () {
14+ const executor = yield* ExecutorService;
15+ // Tools page is a management view — include policy-blocked tools
16+ // (so users can unblock them) and load annotations so the row can
17+ // show the plugin's default approval state when no user rule
18+ // matches. Mirrors the per-source `sources.tools` handler.
19+ const tools = yield* executor.tools.list({
20+ includeAnnotations: true,
21+ includeBlocked: true,
22+ });
23+ return tools.map((t) => ({
24+ id: ToolId.make(t.id),
25+ pluginId: t.pluginId,
26+ sourceId: t.sourceId,
27+ name: t.name,
28+ description: t.description,
29+ mayElicit: t.annotations?.mayElicit,
30+ requiresApproval: t.annotations?.requiresApproval,
31+ }));
32+ }),
33+ ),
3234 )
3335 .handle("schema", ({ params: path }) =>
34− capture(Effect.gen(function* () {
35− const executor = yield* ExecutorService;
36− const schema = yield* executor.tools.schema(path.toolId);
37− if (schema === null) {
38− return yield* new ToolNotFoundError({ toolId: path.toolId });
39− }
40− return schema;
41− })),
36+ capture(
37+ Effect.gen(function* () {
38+ const executor = yield* ExecutorService;
39+ const schema = yield* executor.tools.schema(path.toolId);
40+ if (schema === null) {
41+ return yield* new ToolNotFoundError({ toolId: path.toolId });
42+ }
43+ return schema;
44+ }),
45+ ),
4246 ),
4347 );
packages/core/api/src/oauth-popup.test.ts+6−4
@@ -172,10 +172,12 @@ describe("runOAuthCallback", () => { · 171 unmodified lines
172172 class DomainError extends Data.TaggedError("DomainError")<{
173173 readonly message: string;
174174 }> {}
175− const isDomainError = Schema.is(Schema.Struct({
176− _tag: Schema.Literal("DomainError"),
177− message: Schema.String,
178− }));
175+ const isDomainError = Schema.is(
176+ Schema.Struct({
177+ _tag: Schema.Literal("DomainError"),
178+ message: Schema.String,
179+ }),
180+ );
179181 const html = await Effect.runPromise(
180182 runOAuthCallback<GoogleAuth, DomainError, never>({
181183 complete: () => Effect.fail(new DomainError({ message: "Code expired" })),
packages/core/api/src/oauth-popup.ts+1−4
@@ -12,10 +12,7 @@ · 11 unmodified lines
1212
1313 import { Cause, Effect } from "effect";
1414
15−import {
16− OAUTH_POPUP_MESSAGE_TYPE,
17− type OAuthPopupResult,
18−} from "@executor-js/sdk";
15+import { OAUTH_POPUP_MESSAGE_TYPE, type OAuthPopupResult } from "@executor-js/sdk";
1916
2017 export { OAUTH_POPUP_MESSAGE_TYPE, isOAuthPopupResult } from "@executor-js/sdk";
2118 export type { OAuthPopupResult } from "@executor-js/sdk";
packages/core/api/src/oauth/api.ts+1−3
@@ -36,9 +36,7 @@ const ProbePayload = Schema.Struct({ · 35 unmodified lines
3636 const ProbeResponse = Schema.Struct({
3737 resourceMetadata: Schema.NullOr(Schema.Record(Schema.String, Schema.Unknown)),
3838 resourceMetadataUrl: Schema.NullOr(Schema.String),
39− authorizationServerMetadata: Schema.NullOr(
40− Schema.Record(Schema.String, Schema.Unknown),
41− ),
39+ authorizationServerMetadata: Schema.NullOr(Schema.Record(Schema.String, Schema.Unknown)),
4240 authorizationServerMetadataUrl: Schema.NullOr(Schema.String),
4341 authorizationServerUrl: Schema.NullOr(Schema.String),
4442 supportsDynamicRegistration: Schema.Boolean,
packages/core/api/src/observability.test.ts+15−18
@@ -20,9 +20,7 @@ const makeRecorder = (traceId = "trace-xyz") => · 19 unmodified lines
2020 ErrorCapture,
2121 ErrorCapture.of({
2222 captureException: (cause) =>
23− Ref.update(seen, (prev) => [...prev, cause]).pipe(
24− Effect.as(traceId),
25− ),
23+ Ref.update(seen, (prev) => [...prev, cause]).pipe(Effect.as(traceId)),
2624 }),
2725 );
2826 return { layer, seen };
@@ -58,17 +56,19 @@ describe("capture", () => { · 29 unmodified lines
5856 }),
5957 );
6058
61− it.effect("UniqueViolationError dies (becomes a defect — plugins should catchTag before returning)", () =>
62− Effect.gen(function* () {
63− const err = new UniqueViolationError({ model: "thing" });
64− const exit = yield* Effect.exit(capture(Effect.fail(err)));
65− expect(Exit.isFailure(exit)).toBe(true);
66− if (!Exit.isFailure(exit)) return;
67− const defect = Cause.findDefect(exit.cause);
68− expect(Result.isSuccess(defect) ? defect.success : undefined).toBeInstanceOf(
69− UniqueViolationError,
70− );
71− }),
59+ it.effect(
60+ "UniqueViolationError dies (becomes a defect — plugins should catchTag before returning)",
61+ () =>
62+ Effect.gen(function* () {
63+ const err = new UniqueViolationError({ model: "thing" });
64+ const exit = yield* Effect.exit(capture(Effect.fail(err)));
65+ expect(Exit.isFailure(exit)).toBe(true);
66+ if (!Exit.isFailure(exit)) return;
67+ const defect = Cause.findDefect(exit.cause);
68+ expect(Result.isSuccess(defect) ? defect.success : undefined).toBeInstanceOf(
69+ UniqueViolationError,
70+ );
71+ }),
7272 );
7373
7474 it.effect("non-storage typed failures pass through unchanged", () =>
@@ -76,10 +76,7 @@ describe("capture", () => { · 1 unmodified line
7676 class DomainError {
7777 readonly _tag = "DomainError" as const;
7878 }
79− const eff = Effect.fail(new DomainError()) as Effect.Effect<
80− never,
81− DomainError
82− >;
79+ const eff = Effect.fail(new DomainError()) as Effect.Effect<never, DomainError>;
8380 const result = yield* Effect.flip(capture(eff));
8481 expect(result).toBeInstanceOf(DomainError);
8582 }),
packages/core/api/src/observability.ts+22−33
@@ -51,19 +51,16 @@ export interface ErrorCaptureShape { · 50 unmodified lines
5151 * can later look up. Implementations (Sentry, console, etc.) decide
5252 * how to persist it.
5353 */
54− readonly captureException: (
55− cause: Cause.Cause<unknown>,
56− ) => Effect.Effect<string>;
54+ readonly captureException: (cause: Cause.Cause<unknown>) => Effect.Effect<string>;
5755 }
5856
5957 export class ErrorCapture extends Context.Service<ErrorCapture, ErrorCaptureShape>()(
6058 "@executor-js/api/ErrorCapture",
6159 ) {
6260 /** No-op — used where capture isn't wired. Traces back as empty string. */
63− static readonly NoOp: Layer.Layer<ErrorCapture> = Layer.succeed(
64− ErrorCapture,
65− { captureException: () => Effect.succeed("") },
66− );
61+ static readonly NoOp: Layer.Layer<ErrorCapture> = Layer.succeed(ErrorCapture, {
62+ captureException: () => Effect.succeed(""),
63+ });
6764 }
6865
6966 // Resolve ErrorCapture with a no-op fallback. Keeps the caller's R channel
@@ -71,9 +68,7 @@ export class ErrorCapture extends Context.Service<ErrorCapture, ErrorCaptureShap · 1 unmodified line
7168 // typecheck; if it's there, we use it; if not, trace ids are empty.
7269 const resolveCapture = Effect.serviceOption(ErrorCapture).pipe(
7370 Effect.map((opt) =>
74− Option.isSome(opt)
75− ? opt.value
76− : ({ captureException: () => Effect.succeed("") } as const),
71+ Option.isSome(opt) ? opt.value : ({ captureException: () => Effect.succeed("") } as const),
7772 ),
7873 );
7974
@@ -102,9 +97,7 @@ export const capture = <A, E, R>( · 22 unmodified lines
10297 Effect.catchTag("StorageError", (err) =>
10398 resolveCapture.pipe(
10499 Effect.flatMap((c) => c.captureException(Cause.fail(err))),
105− Effect.flatMap((traceId) =>
106− Effect.fail(new InternalError({ traceId })),
107− ),
100+ Effect.flatMap((traceId) => Effect.fail(new InternalError({ traceId }))),
108101 ),
109102 ),
110103 ) as Effect.Effect<A, Exclude<E, StorageFailure> | InternalError, R>;
@@ -131,10 +124,8 @@ export const captureEngineError = <A, R>( · 20 unmodified lines
131124 ? Effect.fail(err)
132125 : resolveCapture.pipe(
133126 Effect.flatMap((c) => c.captureException(Cause.fail(err))),
134− Effect.flatMap((traceId) =>
135− Effect.fail(new InternalError({ traceId })),
136− ),
137− ),
127+ Effect.flatMap((traceId) => Effect.fail(new InternalError({ traceId }))),
128+ ),
138129 ),
139130 );
140131
@@ -160,20 +151,18 @@ export class ObservabilityMiddleware extends HttpApiMiddleware.Service<Observabi · 19 unmodified lines
160151 export const observabilityMiddleware = <Id extends string, Groups extends HttpApiGroup.Any>(
161152 _api: HttpApi.HttpApi<Id, Groups>,
162153 ): Layer.Layer<ObservabilityMiddleware> =>
163− Layer.succeed(
164− ObservabilityMiddleware,
165− (httpApp) =>
166− Effect.catchCause(httpApp, (cause) =>
167− Effect.gen(function* () {
168− const defect = Cause.findDefect(cause);
169− if (Result.isFailure(defect)) {
170− return yield* Effect.failCause(cause);
171− }
172− const c = yield* resolveCapture;
173− const traceId = yield* c.captureException(cause);
174− return HttpServerResponse.jsonUnsafe(new InternalError({ traceId }), {
175− status: 500,
176− });
177− }),
178− ),
154+ Layer.succeed(ObservabilityMiddleware, (httpApp) =>
155+ Effect.catchCause(httpApp, (cause) =>
156+ Effect.gen(function* () {
157+ const defect = Cause.findDefect(cause);
158+ if (Result.isFailure(defect)) {
159+ return yield* Effect.failCause(cause);
160+ }
161+ const c = yield* resolveCapture;
162+ const traceId = yield* c.captureException(cause);
163+ return HttpServerResponse.jsonUnsafe(new InternalError({ traceId }), {
164+ status: 500,
165+ });
166+ }),
167+ ),
179168 );
packages/core/api/src/plugin-routes.ts+18−16
@@ -40,8 +40,9 @@ import { CoreExecutorApi } from "./api"; · 39 unmodified lines
4040 // eslint-disable-next-line @typescript-eslint/no-explicit-any
4141 type ExtractServiceId<S> = S extends Context.Service<infer Id, any> ? Id : never;
4242
43−export type PluginExtensionServices<TPlugins extends readonly AnyPlugin[]> =
44− ExtractServiceId<NonNullable<TPlugins[number]["extensionService"]>>;
43+export type PluginExtensionServices<TPlugins extends readonly AnyPlugin[]> = ExtractServiceId<
44+ NonNullable<TPlugins[number]["extensionService"]>
45+>;
4546
4647 /** Extract the precise `HttpApiGroup` type carried by a plugin's
4748 * `routes()` field. Plugins without a `routes()` field contribute
@@ -52,8 +53,9 @@ type ExtractPluginGroup<P> = P extends { readonly routes?: () => infer G } · 4 unmodified lines
5253
5354 /** Union of every plugin's contributed group — combined with the core
5455 * executor groups to type `composePluginApi(plugins)` precisely. */
55−export type PluginGroups<TPlugins extends readonly AnyPlugin[]> =
56− ExtractPluginGroup<TPlugins[number]>;
56+export type PluginGroups<TPlugins extends readonly AnyPlugin[]> = ExtractPluginGroup<
57+ TPlugins[number]
58+>;
5759
5860 /** Group identities baked into `CoreExecutorApi` (tools, sources, secrets,
5961 * …). Extracted via inference so adding a core group flows through
@@ -64,8 +66,10 @@ type CoreGroups = · 4 unmodified lines
6466
6567 /** Result of `composePluginApi(plugins)` — the core API extended with
6668 * every plugin group from `TPlugins`. */
67−export type ComposedExecutorApi<TPlugins extends readonly AnyPlugin[]> =
68− HttpApi.HttpApi<"executor", CoreGroups | PluginGroups<TPlugins>>;
69+export type ComposedExecutorApi<TPlugins extends readonly AnyPlugin[]> = HttpApi.HttpApi<
70+ "executor",
71+ CoreGroups | PluginGroups<TPlugins>
72+>;
6973
7074 // eslint-disable-next-line @typescript-eslint/no-explicit-any
7175 type AnyLayer = Layer.Layer<any, any, any>;
@@ -73,18 +77,18 @@ type AnyLayer = Layer.Layer<any, any, any>; · 1 unmodified line
7377 // Use the field accessor + NonNullable rather than `extends { handlers: ... }`
7478 // because the spec marks `handlers` optional (`handlers?:`); the conditional
7579 // form would fail the match because the field type includes `undefined`.
76−type ExtractHandlerLayer<P> = NonNullable<
77− P extends { readonly handlers?: infer F } ? F : never
78−> extends () => infer L
79− ? L
80− : never;
80+type ExtractHandlerLayer<P> =
81+ NonNullable<P extends { readonly handlers?: infer F } ? F : never> extends () => infer L
82+ ? L
83+ : never;
8184
8285 // Compute the union of every plugin's handler-Layer type. Each plugin's
8386 // `handlers()` returns a specific `Layer<Group, never, ExtensionService>`;
8487 // we union them so `Layer.mergeAll(...)`'s output type can be extracted
8588 // without erasing per-plugin requirements.
86−type PluginHandlerLayers<TPlugins extends readonly AnyPlugin[]> =
87− ExtractHandlerLayer<TPlugins[number]>;
89+type PluginHandlerLayers<TPlugins extends readonly AnyPlugin[]> = ExtractHandlerLayer<
90+ TPlugins[number]
91+>;
8892
8993 // Distribute over the union of handler layers to extract each channel
9094 // individually, then re-pack into a single `Layer<UnionROut, UnionE,
@@ -174,9 +178,7 @@ export const composePluginHandlers = <TPlugins extends readonly AnyPlugin[]>( · 83 unmodified lines
174178 * `*ExtensionService` Tags) so the host's `HttpRouter.middleware`
175179 * recognises them as per-request requires.
176180 */
177−export const composePluginHandlerLayer = <
178− TPlugins extends readonly AnyPlugin[],
179−>(
181+export const composePluginHandlerLayer = <TPlugins extends readonly AnyPlugin[]>(
180182 plugins: TPlugins,
181183 ): MergedHandlerLayer<TPlugins> => {
182184 const layers = plugins.flatMap((p) => (p.handlers ? [p.handlers()] : []));
packages/core/api/src/secrets/api.ts+2−6
@@ -48,12 +48,8 @@ const SetSecretPayload = Schema.Struct({ · 47 unmodified lines
4848 // ---------------------------------------------------------------------------
4949
5050 const SecretNotFound = SecretNotFoundError.annotate({ httpApiStatus: 404 });
51−const SecretResolution = SecretResolutionError.annotate(
52− { httpApiStatus: 500 },
53−);
54−const SecretOwnedByConnection = SecretOwnedByConnectionError.annotate(
55− { httpApiStatus: 409 },
56−);
51+const SecretResolution = SecretResolutionError.annotate({ httpApiStatus: 500 });
52+const SecretOwnedByConnection = SecretOwnedByConnectionError.annotate({ httpApiStatus: 409 });
5753 const SecretInUse = SecretInUseError.annotate({ httpApiStatus: 409 });
5854
5955 // ---------------------------------------------------------------------------
packages/core/api/src/sources/api.ts+2−8
@@ -1,10 +1,6 @@
11 import { HttpApiEndpoint, HttpApiGroup } from "effect/unstable/httpapi";
22 import { Schema } from "effect";
3−import {
4− ScopeId,
5− SourceRemovalNotAllowedError,
6− ToolId,
7−} from "@executor-js/sdk";
3+import { ScopeId, SourceRemovalNotAllowedError, ToolId } from "@executor-js/sdk";
84
95 import { InternalError } from "../observability";
106
@@ -68,9 +64,7 @@ const DetectResultResponse = Schema.Struct({ · 57 unmodified lines
6864 // Error schemas with HTTP status annotations
6965 // ---------------------------------------------------------------------------
7066
71−const SourceRemovalNotAllowed = SourceRemovalNotAllowedError.annotate(
72− { httpApiStatus: 409 },
73−);
67+const SourceRemovalNotAllowed = SourceRemovalNotAllowedError.annotate({ httpApiStatus: 409 });
7468
7569 // ---------------------------------------------------------------------------
7670 // Group
packages/core/api/src/tools/api.ts+1−3
@@ -31,9 +31,7 @@ const ToolSchemaResponse = Schema.Struct({ · 30 unmodified lines
3131 id: ToolId,
3232 inputTypeScript: Schema.optional(Schema.String),
3333 outputTypeScript: Schema.optional(Schema.String),
34− typeScriptDefinitions: Schema.optional(
35− Schema.Record(Schema.String, Schema.String),
36− ),
34+ typeScriptDefinitions: Schema.optional(Schema.Record(Schema.String, Schema.String)),
3735 inputSchema: Schema.optional(Schema.Unknown),
3836 outputSchema: Schema.optional(Schema.Unknown),
3937 });
packages/core/cli/package.json+1−1
@@ -12,13 +12,13 @@ · 11 unmodified lines
1212 "url": "git+https://github.com/RhysSullivan/executor.git",
1313 "directory": "packages/core/cli"
1414 },
15− "type": "module",
1615 "bin": {
1716 "executor": "./dist/index.js"
1817 },
1918 "files": [
2019 "dist"
2120 ],
21+ "type": "module",
2222 "exports": {
2323 ".": "./src/index.ts"
2424 },
packages/core/cli/src/commands/generate.ts+4−18
@@ -6,11 +6,7 @@ import { collectSchemas } from "@executor-js/sdk/core"; · 5 unmodified lines
66 import { getConfig } from "../utils/get-config.js";
77 import { generateDrizzleSchema } from "../generators/drizzle.js";
88
9−async function generateAction(opts: {
10− cwd: string;
11− config?: string;
12− output?: string;
13−}) {
9+async function generateAction(opts: { cwd: string; config?: string; output?: string }) {
1410 const cwd = path.resolve(opts.cwd);
1511 if (!existsSync(cwd)) {
1612 console.error(`The directory "${cwd}" does not exist.`);
@@ -55,17 +51,7 @@ async function generateAction(opts: { · 38 unmodified lines
5551
5652 export const generate = new Command("generate")
5753 .description("Generate a drizzle schema file from the executor config")
58− .option(
59− "-c, --cwd <cwd>",
60− "the working directory",
61− process.cwd(),
62− )
63− .option(
64− "--config <config>",
65− "path to the executor config file",
66− )
67− .option(
68− "--output <output>",
69− "output file path for the generated schema",
70− )
54+ .option("-c, --cwd <cwd>", "the working directory", process.cwd())
55+ .option("--config <config>", "path to the executor config file")
56+ .option("--output <output>", "output file path for the generated schema")
7157 .action(generateAction);
packages/core/cli/src/generators/drizzle.ts+62−104
@@ -15,14 +15,9 @@ import type { SchemaGenerator } from "./types.js"; · 14 unmodified lines
1515
1616 type Dialect = "pg" | "sqlite" | "mysql";
1717
18−const getModelName = (key: string, def: DBSchema[string]): string =>
19− def.modelName ?? key;
20−
21−const getType = (
22− name: string,
23− field: DBFieldAttribute,
24− dialect: Dialect,
25−): string => {
18+const getModelName = (key: string, def: DBSchema[string]): string => def.modelName ?? key;
19+
20+const getType = (name: string, field: DBFieldAttribute, dialect: Dialect): string => {
2621 if (field.references?.field === "id") {
2722 return `text('${name}')`;
2823 }
@@ -39,9 +34,7 @@ const getType = ( · 10 unmodified lines
3934 }[dialect];
4035 }
4136 // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: generator rejects invalid schema input through the SchemaGenerator promise API
42− throw new TypeError(
43− `Invalid field type for field ${name}`,
44− );
37+ throw new TypeError(`Invalid field type for field ${name}`);
4538 }
4639
4740 const typeMap: Record<string, Record<Dialect, string>> = {
@@ -65,12 +58,8 @@ const getType = ( · 17 unmodified lines
6558 },
6659 number: {
6760 sqlite: `integer('${name}')`,
68− pg: field.bigint
69− ? `bigint('${name}', { mode: 'number' })`
70− : `integer('${name}')`,
71− mysql: field.bigint
72− ? `bigint('${name}', { mode: 'number' })`
73− : `int('${name}')`,
61+ pg: field.bigint ? `bigint('${name}', { mode: 'number' })` : `integer('${name}')`,
62+ mysql: field.bigint ? `bigint('${name}', { mode: 'number' })` : `int('${name}')`,
7463 },
7564 date: {
7665 sqlite: `integer('${name}', { mode: 'timestamp_ms' })`,
@@ -99,9 +88,7 @@ const getType = ( · 22 unmodified lines
9988 const dbTypeMap = typeMap[type];
10089 if (!dbTypeMap) {
10190 // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: generator rejects unsupported schema input through the SchemaGenerator promise API
102− throw new Error(
103− `Unsupported field type '${field.type}' for field '${name}'.`,
104− );
91+ throw new Error(`Unsupported field type '${field.type}' for field '${name}'.`);
10592 }
10693 return dbTypeMap[dialect];
10794 };
@@ -110,11 +97,7 @@ const getType = ( · 2 unmodified lines
11097 // Generator
11198 // ---------------------------------------------------------------------------
11299
113−export const generateDrizzleSchema: SchemaGenerator = async ({
114− schema,
115− dialect,
116− file,
117−}) => {
100+export const generateDrizzleSchema: SchemaGenerator = async ({ schema, dialect, file }) => {
118101 const filePath = file || "./executor-schema.ts";
119102 const fileExist = existsSync(filePath);
120103
@@ -159,69 +142,62 @@ export const generateDrizzleSchema: SchemaGenerator = async ({ · 38 unmodified lines
159142 }
160143
161144 const fieldLines = Object.entries(fields)
162− .filter(([fieldName]) => fieldName !== "id")
163− .map(([fieldName, attr]) => {
164− const physical = attr.fieldName ?? fieldName;
165−
166− const isToolPolicyCompositeField =
167− tableKey === "tool_policy" &&
168− (physical === "scope_id" || physical === "position");
169−
170− if (attr.index && !attr.unique && !isToolPolicyCompositeField) {
171− extras.push({
172− kind: "index",
173− name: `${tableKey}_${physical}_idx`,
174− on: physical,
175− });
176− } else if (attr.index && attr.unique) {
177− extras.push({
178− kind: "uniqueIndex",
179− name: `${tableKey}_${physical}_uidx`,
180− on: physical,
181− });
182− }
145+ .filter(([fieldName]) => fieldName !== "id")
146+ .map(([fieldName, attr]) => {
147+ const physical = attr.fieldName ?? fieldName;
148+
149+ const isToolPolicyCompositeField =
150+ tableKey === "tool_policy" && (physical === "scope_id" || physical === "position");
151+
152+ if (attr.index && !attr.unique && !isToolPolicyCompositeField) {
153+ extras.push({
154+ kind: "index",
155+ name: `${tableKey}_${physical}_idx`,
156+ on: physical,
157+ });
158+ } else if (attr.index && attr.unique) {
159+ extras.push({
160+ kind: "uniqueIndex",
161+ name: `${tableKey}_${physical}_uidx`,
162+ on: physical,
163+ });
164+ }
165+
166+ let col = getType(physical, attr, dialect);
183167
184− let col = getType(physical, attr, dialect);
185−
186− if (
187− attr.defaultValue !== null &&
188− typeof attr.defaultValue !== "undefined"
189− ) {
190− if (typeof attr.defaultValue === "function") {
191− if (
192− attr.type === "date" &&
193− attr.defaultValue.toString().includes("new Date()")
194− ) {
195− if (dialect === "sqlite") {
196− col += `.default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)`;
197− } else {
198− col += `.defaultNow()`;
168+ if (attr.defaultValue !== null && typeof attr.defaultValue !== "undefined") {
169+ if (typeof attr.defaultValue === "function") {
170+ if (attr.type === "date" && attr.defaultValue.toString().includes("new Date()")) {
171+ if (dialect === "sqlite") {
172+ col += `.default(sql\`(cast(unixepoch('subsecond') * 1000 as integer))\`)`;
173+ } else {
174+ col += `.defaultNow()`;
175+ }
199176 }
177+ } else if (typeof attr.defaultValue === "string") {
178+ col += `.default("${attr.defaultValue}")`;
179+ } else {
180+ col += `.default(${attr.defaultValue})`;
200181 }
201− } else if (typeof attr.defaultValue === "string") {
202− col += `.default("${attr.defaultValue}")`;
203− } else {
204− col += `.default(${attr.defaultValue})`;
205182 }
206− }
207183
208− if (attr.onUpdate && attr.type === "date") {
209− if (typeof attr.onUpdate === "function") {
210− col += `.$onUpdate(${attr.onUpdate})`;
184+ if (attr.onUpdate && attr.type === "date") {
185+ if (typeof attr.onUpdate === "function") {
186+ col += `.$onUpdate(${attr.onUpdate})`;
187+ }
211188 }
212− }
213189
214− return `${physical}: ${col}${attr.required !== false ? ".notNull()" : ""}${
215− attr.unique ? ".unique()" : ""
216− }${
217− attr.references
218− ? `.references(()=> ${attr.references.model}.${attr.references.field ?? "id"}, { onDelete: '${
219− attr.references.onDelete || "cascade"
220− }' })`
221− : ""
222− }`;
223− })
224− .join(",\n ");
190+ return `${physical}: ${col}${attr.required !== false ? ".notNull()" : ""}${
191+ attr.unique ? ".unique()" : ""
192+ }${
193+ attr.references
194+ ? `.references(()=> ${attr.references.model}.${attr.references.field ?? "id"}, { onDelete: '${
195+ attr.references.onDelete || "cascade"
196+ }' })`
197+ : ""
198+ }`;
199+ })
200+ .join(",\n ");
225201
226202 if (tableKey === "tool_policy") {
227203 extras.push({
@@ -339,12 +315,7 @@ export const generateDrizzleSchema: SchemaGenerator = async ({ · 111 unmodified lines
339315 const hasMany = manyRelations.length > 0;
340316
341317 if (hasOne || hasMany) {
342− const destructured = [
343− hasOne ? "one" : "",
344− hasMany ? "many" : "",
345− ]
346− .filter(Boolean)
347− .join(", ");
318+ const destructured = [hasOne ? "one" : "", hasMany ? "many" : ""].filter(Boolean).join(", ");
348319
349320 const body = [
350321 ...singleRelations
@@ -353,9 +324,7 @@ export const generateDrizzleSchema: SchemaGenerator = async ({ · 2 unmodified lines
353324 (r) =>
354325 ` ${r.key}: one(${r.model}, {\n fields: [${r.reference!.field}],\n references: [${r.reference!.references}],\n })`,
355326 ),
356− ...manyRelations.map(
357− ({ key, model }) => ` ${key}: many(${model})`,
358− ),
327+ ...manyRelations.map(({ key, model }) => ` ${key}: many(${model})`),
359328 ].join(",\n");
360329
361330 const block = `export const ${modelName}Relations = relations(${modelName}, ({ ${destructured} }) => ({
@@ -378,13 +347,7 @@ ${body} · 16 unmodified lines
378347 // Import generation — only emit what's actually used
379348 // ---------------------------------------------------------------------------
380349
381−function generateImport({
382− dialect,
383− schema,
384−}: {
385− dialect: Dialect;
386− schema: DBSchema;
387−}) {
350+function generateImport({ dialect, schema }: { dialect: Dialect; schema: DBSchema }) {
388351 const rootImports: string[] = [];
389352 const coreImports: string[] = [];
390353
@@ -403,10 +366,7 @@ function generateImport({ · 12 unmodified lines
403366 if (field.bigint) hasBigint = true;
404367 if (field.type === "json") hasJson = true;
405368 if (field.type === "boolean") hasBoolean = true;
406− if (
407− (field.type === "number" && !field.bigint) ||
408− field.type === "number[]"
409− ) {
369+ if ((field.type === "number" && !field.bigint) || field.type === "number[]") {
410370 hasNumber = true;
411371 }
412372 if (field.type === "date") hasDate = true;
@@ -482,9 +442,7 @@ function generateImport({ · 69 unmodified lines
482442 // produce relation blocks (see relationsString generation).
483443 if (hasReferences) rootImports.push("relations");
484444
485− const filteredCore = coreImports
486− .map((x) => x.trim())
487− .filter((x) => x !== "");
445+ const filteredCore = coreImports.map((x) => x.trim()).filter((x) => x !== "");
488446
489447 // Deduplicate
490448 const uniqueCore = [...new Set(filteredCore)];
packages/core/cli/src/generators/index.ts+1−4
@@ -7,10 +7,7 @@ const generators: Record<string, SchemaGenerator> = { · 6 unmodified lines
77 drizzle: generateDrizzleSchema,
88 };
99
10−export const generateSchema = (
11− adapter: string,
12− ...args: Parameters<SchemaGenerator>
13−) => {
10+export const generateSchema = (adapter: string, ...args: Parameters<SchemaGenerator>) => {
1411 const generator = generators[adapter];
1512 if (!generator) {
1613 // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: synchronous CLI generator registry rejects unsupported adapter names
packages/core/cli/tsup.config.ts+1−6
@@ -8,10 +8,5 @@ export default defineConfig({ · 7 unmodified lines
88 dts: false,
99 sourcemap: true,
1010 clean: true,
11− external: [
12− /^effect/,
13− /^@effect\//,
14− "commander",
15− "jiti",
16− ],
11+ external: [/^effect/, /^@effect\//, "commander", "jiti"],
1712 });
packages/core/config/src/index.ts+1−5
@@ -25,8 +25,4 @@ export { · 24 unmodified lines
2525 } from "./write";
2626
2727 export type { ConfigFileSink, ConfigFileSinkOptions } from "./sink";
28−export {
29− makeFileConfigSink,
30− headerToConfigValue,
31− headersToConfigValues,
32−} from "./sink";
28+export { makeFileConfigSink, headerToConfigValue, headersToConfigValues } from "./sink";
packages/core/config/src/load-plugins.test.ts+2−6
@@ -25,9 +25,7 @@ interface FixturePlugin { · 24 unmodified lines
2525 readonly __optionsReceived: Record<string, unknown> | null;
2626 }
2727
28−const asFixturePlugins = (
29− plugins: readonly AnyPlugin[] | null,
30−): readonly FixturePlugin[] => {
28+const asFixturePlugins = (plugins: readonly AnyPlugin[] | null): readonly FixturePlugin[] => {
3129 // Fixture servers return shapes wider than AnyPlugin (they carry
3230 // __optionsReceived for test assertions); narrow once here.
3331 // oxlint-disable-next-line executor/no-double-cast
@@ -71,9 +69,7 @@ describe("loadPluginsFromJsonc", () => { · 37 unmodified lines
7169
7270 it("throws on JSONC parse errors", async () => {
7371 const file = writeJsonc("parse-error", `{ "plugins": [`);
74− await expect(loadPluginsFromJsonc({ path: file })).rejects.toThrow(
75− /failed to parse/i,
76− );
72+ await expect(loadPluginsFromJsonc({ path: file })).rejects.toThrow(/failed to parse/i);
7773 });
7874
7975 it("throws with a helpful message when the package can't be resolved", async () => {
packages/core/config/src/load-plugins.ts+3−8
@@ -66,8 +66,7 @@ export interface LoadPluginsFromJsoncOptions { · 65 unmodified lines
6666 */
6767 export const loadPluginsFromJsonc = async (
6868 options: LoadPluginsFromJsoncOptions,
69−): Promise<readonly AnyPlugin[] | null> =>
70− Effect.runPromise(loadPluginsFromJsoncEffect(options));
69+): Promise<readonly AnyPlugin[] | null> => Effect.runPromise(loadPluginsFromJsoncEffect(options));
7170
7271 const loadPluginsFromJsoncEffect = (
7372 options: LoadPluginsFromJsoncOptions,
@@ -122,9 +121,7 @@ const loadPluginsFromJsoncEffect = ( · 48 unmodified lines
122121 // require.resolve is anchored to the jsonc's directory so plugin
123122 // packages resolve from the host app's `node_modules` regardless of
124123 // CWD.
125− const require = createRequire(
126− isAbsolute(path) ? path : resolvePath(fromDir, "_anchor.js"),
127− );
124+ const require = createRequire(isAbsolute(path) ? path : resolvePath(fromDir, "_anchor.js"));
128125
129126 const loaded: AnyPlugin[] = [];
130127 for (const entry of entries) {
@@ -146,9 +143,7 @@ const loadPluginsFromJsoncEffect = ( · 15 unmodified lines
146143 message: `[load-plugins] failed to import "${serverEntry}" from ${resolved}.`,
147144 cause,
148145 }),
149− })) as
150− | { default?: LooseConfiguredPlugin }
151− | LooseConfiguredPlugin;
146+ })) as { default?: LooseConfiguredPlugin } | LooseConfiguredPlugin;
152147 const factory = (
153148 typeof mod === "function" ? mod : (mod.default ?? null)
154149 ) as LooseConfiguredPlugin | null;
packages/core/config/src/sink.ts+2−6
@@ -22,9 +22,7 @@ import { addSourceToConfig, removeSourceFromConfig } from "./write"; · 21 unmodified lines
2222 // refs) into the config file's `secret-public-ref:<id>` string form.
2323 type PluginHeaderValue = string | { secretId: string; prefix?: string };
2424
25−export const headerToConfigValue = (
26− value: PluginHeaderValue,
27−): ConfigHeaderValue => {
25+export const headerToConfigValue = (value: PluginHeaderValue): ConfigHeaderValue => {
2826 if (typeof value === "string") return value;
2927 const ref = `${SECRET_REF_PREFIX}${value.secretId}`;
3028 return value.prefix ? { value: ref, prefix: value.prefix } : ref;
@@ -55,9 +53,7 @@ const defaultOnError = (op: "upsert" | "remove", err: unknown): void => { · 24 unmodified lines
5553 console.warn(`[config-sink] ${op} failed`, err);
5654 };
5755
58−export const makeFileConfigSink = (
59− options: ConfigFileSinkOptions,
60−): ConfigFileSink => {
56+export const makeFileConfigSink = (options: ConfigFileSinkOptions): ConfigFileSink => {
6157 const { path, fsLayer, onError = defaultOnError } = options;
6258
6359 return {
packages/core/execution/src/description.test.ts+32−42
@@ -56,54 +56,44 @@ const slackPlugin = definePlugin(() => ({ · 55 unmodified lines
5656 }));
5757
5858 describe("buildExecuteDescription", () => {
59− it.effect(
60− "renders real source ids as namespaces (sorted) through the real executor flow",
61− () =>
62− Effect.gen(function* () {
63− // Intentionally register in non-alphabetical order — the formatter
64− // is expected to sort by source id.
65− const executor = yield* createExecutor(
66− makeTestConfig({ plugins: [slackPlugin(), githubPlugin()] as const }),
67− );
59+ it.effect("renders real source ids as namespaces (sorted) through the real executor flow", () =>
60+ Effect.gen(function* () {
61+ // Intentionally register in non-alphabetical order — the formatter
62+ // is expected to sort by source id.
63+ const executor = yield* createExecutor(
64+ makeTestConfig({ plugins: [slackPlugin(), githubPlugin()] as const }),
65+ );
6866
69− const description = yield* buildExecuteDescription(executor);
67+ const description = yield* buildExecuteDescription(executor);
7068
71− // Stable anchor from the workflow preamble.
72− expect(description).toContain(
73− "Execute TypeScript in a sandboxed runtime",
74− );
75− // The namespaces section header.
76− expect(description).toContain("## Available namespaces");
77− // Each source renders with its ACTUAL id (not pluginId / name / UUID).
78− expect(description).toContain("`github` — GitHub");
79− expect(description).toContain("`slack` — Slack Workspace");
80− // And the plugin ids must NOT leak into the namespace list.
81− expect(description).not.toContain("`github-plugin`");
82− expect(description).not.toContain("`slack-plugin`");
69+ // Stable anchor from the workflow preamble.
70+ expect(description).toContain("Execute TypeScript in a sandboxed runtime");
71+ // The namespaces section header.
72+ expect(description).toContain("## Available namespaces");
73+ // Each source renders with its ACTUAL id (not pluginId / name / UUID).
74+ expect(description).toContain("`github` — GitHub");
75+ expect(description).toContain("`slack` — Slack Workspace");
76+ // And the plugin ids must NOT leak into the namespace list.
77+ expect(description).not.toContain("`github-plugin`");
78+ expect(description).not.toContain("`slack-plugin`");
8379
84− // Sort order: `github` before `slack`.
85− const githubIdx = description.indexOf("`github`");
86− const slackIdx = description.indexOf("`slack`");
87− expect(githubIdx).toBeGreaterThan(-1);
88− expect(slackIdx).toBeGreaterThan(-1);
89− expect(githubIdx).toBeLessThan(slackIdx);
90− }),
80+ // Sort order: `github` before `slack`.
81+ const githubIdx = description.indexOf("`github`");
82+ const slackIdx = description.indexOf("`slack`");
83+ expect(githubIdx).toBeGreaterThan(-1);
84+ expect(slackIdx).toBeGreaterThan(-1);
85+ expect(githubIdx).toBeLessThan(slackIdx);
86+ }),
9187 );
9288
93− it.effect(
94− "omits the Available namespaces section when no plugins register sources",
95− () =>
96− Effect.gen(function* () {
97− const executor = yield* createExecutor(
98− makeTestConfig({ plugins: [] as const }),
99− );
89+ it.effect("omits the Available namespaces section when no plugins register sources", () =>
90+ Effect.gen(function* () {
91+ const executor = yield* createExecutor(makeTestConfig({ plugins: [] as const }));
10092
101− const description = yield* buildExecuteDescription(executor);
93+ const description = yield* buildExecuteDescription(executor);
10294
103− expect(description).toContain(
104− "Execute TypeScript in a sandboxed runtime",
105− );
106− expect(description).not.toContain("## Available namespaces");
107− }),
95+ expect(description).toContain("Execute TypeScript in a sandboxed runtime");
96+ expect(description).not.toContain("## Available namespaces");
97+ }),
10898 );
10999 });
packages/core/execution/src/description.ts+5−7
@@ -10,13 +10,11 @@ import type { Executor, Source } from "@executor-js/sdk/core"; · 9 unmodified lines
1010 */
1111 export const buildExecuteDescription = (executor: Executor): Effect.Effect<string> =>
1212 Effect.gen(function* () {
13− const sources: readonly Source[] = yield* executor.sources
14− .list()
15− .pipe(
16− // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: ExecutionEngine.getDescription currently exposes no error channel; engine typed-error widening is covered separately
17− Effect.orDie,
18− Effect.withSpan("executor.sources.list"),
19− );
13+ const sources: readonly Source[] = yield* executor.sources.list().pipe(
14+ // oxlint-disable-next-line executor/no-effect-escape-hatch -- boundary: ExecutionEngine.getDescription currently exposes no error channel; engine typed-error widening is covered separately
15+ Effect.orDie,
16+ Effect.withSpan("executor.sources.list"),
17+ );
2018
2119 const description = yield* Effect.sync(() => formatDescription(sources)).pipe(
2220 Effect.withSpan("schema.compile.description", {
packages/core/execution/src/engine.test.ts+2−4
@@ -20,13 +20,11 @@ class FakeRuntimeError extends Data.TaggedError("FakeRuntimeError")<{ · 19 unmodified lines
2020 }> {}
2121
2222 const failingExecutor: CodeExecutor<FakeRuntimeError> = {
23− execute: () =>
24− Effect.fail(new FakeRuntimeError({ message: "Unexpected token ':'" })),
23+ execute: () => Effect.fail(new FakeRuntimeError({ message: "Unexpected token ':'" })),
2524 };
2625
2726 const succeedingExecutor: CodeExecutor<FakeRuntimeError> = {
28− execute: () =>
29− Effect.succeed({ result: "ok", logs: [] } satisfies ExecuteResult),
27+ execute: () => Effect.succeed({ result: "ok", logs: [] } satisfies ExecuteResult),
3028 };
3129
3230 const emptyPlugin = definePlugin(() => ({
packages/core/execution/src/promise.ts+4−12
@@ -30,13 +30,9 @@ import { · 29 unmodified lines
3030 type ResumeResponse,
3131 } from "./engine";
3232
33−export type ElicitationHandler = (
34− ctx: ElicitationContext,
35−) => Promise<ElicitationResponse>;
33+export type ElicitationHandler = (ctx: ElicitationContext) => Promise<ElicitationResponse>;
3634
37−export type ExecutionEngineConfig<
38− E extends Cause.YieldableError = CodeExecutionError,
39−> = {
35+export type ExecutionEngineConfig<E extends Cause.YieldableError = CodeExecutionError> = {
4036 readonly executor: PromiseExecutor;
4137 readonly codeExecutor: CodeExecutor<E>;
4238 };
@@ -64,9 +60,7 @@ const fromPromise = <A>(try_: () => Promise<A>): Effect.Effect<A> => · 21 unmodified lines
6460 type EffectInvokeOptions = Parameters<EffectExecutor["tools"]["invoke"]>[2];
6561 type PromiseInvokeOptions = Parameters<PromiseExecutor["tools"]["invoke"]>[2];
6662
67−const toPromiseInvokeOptions = (
68− options: EffectInvokeOptions,
69−): PromiseInvokeOptions => {
63+const toPromiseInvokeOptions = (options: EffectInvokeOptions): PromiseInvokeOptions => {
7064 const onElicitation = options?.onElicitation;
7165 if (!onElicitation) return undefined;
7266 if (onElicitation === "accept-all") return { onElicitation };
@@ -154,9 +148,7 @@ export const toPromiseExecutionEngine = <E extends Cause.YieldableError>( · 81 unmodified lines
154148 getDescription: () => Effect.runPromise(engine.getDescription),
155149 });
156150
157−export const createExecutionEngine = <
158− E extends Cause.YieldableError = CodeExecutionError,
159−>(
151+export const createExecutionEngine = <E extends Cause.YieldableError = CodeExecutionError>(
160152 config: ExecutionEngineConfig<E>,
161153 ): ExecutionEngine =>
162154 toPromiseExecutionEngine(
packages/core/execution/src/tool-invoker.test.ts+9−18
@@ -133,9 +133,7 @@ const errorPlugin = definePlugin(() => ({ · 132 unmodified lines
133133 }));
134134
135135 const makeSearchExecutor = () =>
136− createExecutor(
137− makeTestConfig({ plugins: [githubPlugin(), crmPlugin()] as const }),
138− );
136+ createExecutor(makeTestConfig({ plugins: [githubPlugin(), crmPlugin()] as const }));
139137
140138 describe("tool discovery", () => {
141139 it.effect("ranks matches using ids, namespaces, camelCase names, and descriptions", () =>
@@ -155,9 +153,7 @@ describe("tool discovery", () => { · 13 unmodified lines
155153
156154 const crmMatches = yield* searchTools(executor, "crm create contact", 5);
157155 expect(crmMatches.items[0]?.path).toBe("crm.createContact");
158− expect(crmMatches.items[0]?.score ?? 0).toBeGreaterThan(
159− crmMatches.items[1]?.score ?? 0,
160− );
156+ expect(crmMatches.items[0]?.score ?? 0).toBeGreaterThan(crmMatches.items[1]?.score ?? 0);
161157 }),
162158 );
163159
@@ -218,9 +214,7 @@ describe("tool discovery", () => { · 54 unmodified lines
218214 const githubOnly = yield* searchTools(executor, "list", 5, {
219215 namespace: "github",
220216 });
221− expect(githubOnly.items.map((match) => match.path)).toEqual([
222− "github.listRepositoryIssues",
223− ]);
217+ expect(githubOnly.items.map((match) => match.path)).toEqual(["github.listRepositoryIssues"]);
224218
225219 const crmOnly = yield* searchTools(executor, "list", 5, {
226220 namespace: "crm",
@@ -426,16 +420,12 @@ describe("tool discovery", () => { · 199 unmodified lines
426420
427421 it.effect("converts message-bearing tool error results into execution errors", () =>
428422 Effect.gen(function* () {
429− const executor = yield* createExecutor(
430− makeTestConfig({ plugins: [errorPlugin()] as const }),
431− );
423+ const executor = yield* createExecutor(makeTestConfig({ plugins: [errorPlugin()] as const }));
432424 const invoker = makeExecutorToolInvoker(executor, {
433425 invokeOptions: { onElicitation: acceptAll },
434426 });
435427
436− const error = yield* Effect.flip(
437− invoker.invoke({ path: "records.queryRows", args: {} }),
438− );
428+ const error = yield* Effect.flip(invoker.invoke({ path: "records.queryRows", args: {} }));
439429
440430 expect(error).toEqual(
441431 expect.objectContaining({
@@ -552,10 +542,11 @@ describe("pause/resume with multiple elicitations", () => { · 110 unmodified lines
552542
553543 // `execution.fiber` is on `InternalPausedExecution`; the exported
554544 // `PausedExecution` type doesn't carry it. Cast to read.
555− const pausedWithFiber = (value: unknown): {
545+ const pausedWithFiber = (
546+ value: unknown,
547+ ): {
556548 readonly fiber: Fiber.Fiber<unknown, unknown>;
557− } =>
558− value as { readonly fiber: Fiber.Fiber<unknown, unknown> };
549+ } => value as { readonly fiber: Fiber.Fiber<unknown, unknown> };
559550 const sandboxFiber = pausedWithFiber(paused1.execution).fiber;
560551 const exitProbe = await Effect.runPromise(
561552 Effect.race(
packages/core/execution/src/tool-invoker.ts+6−4
@@ -60,9 +60,7 @@ type ToolResultEnvelope = { · 59 unmodified lines
6060 };
6161
6262 const isToolResultEnvelope = (value: unknown): value is ToolResultEnvelope =>
63− value !== null &&
64− typeof value === "object" &&
65− ("error" in value || "data" in value);
63+ value !== null && typeof value === "object" && ("error" in value || "data" in value);
6664
6765 const hasToolResultError = (
6866 value: ToolResultEnvelope,
@@ -129,7 +127,11 @@ export const makeExecutorToolInvoker = ( · 60 unmodified lines
129127
130128 const isElicitationDeclinedError = (
131129 value: unknown,
132−): value is { readonly _tag: "ElicitationDeclinedError"; readonly toolId: string; readonly action: "cancel" | "decline" } =>
130+): value is {
131+ readonly _tag: "ElicitationDeclinedError";
132+ readonly toolId: string;
133+ readonly action: "cancel" | "decline";
134+} =>
133135 Predicate.isTagged(value, "ElicitationDeclinedError") &&
134136 value !== null &&
135137 typeof value === "object" &&
packages/core/sdk/README.md+21−29
@@ -83,35 +83,27 @@ interface MemorySecretsConfig { · 82 unmodified lines
8383
8484 // definePlugin takes a factory and returns a configured-plugin function
8585 // that the consumer calls with options.
86−export const memorySecretsPlugin = definePlugin(
87− (options?: MemorySecretsConfig) => {
88− const map = new Map<string, string>(
89− Object.entries(options?.initial ?? {}),
90− );
91− const provider: SecretProvider = {
92− key: "memory",
93− writable: true,
94− get: (id: string) => Effect.sync(() => map.get(id) ?? null),
95− has: (id: string) => Effect.sync(() => map.has(id)),
96− set: (id: string, value: string) =>
97− Effect.sync(() => void map.set(id, value)),
98− delete: (id: string) => Effect.sync(() => map.delete(id)),
99− list: () =>
100− Effect.sync(() =>
101− Array.from(map.keys()).map((k) => ({ id: k, name: k })),
102− ),
103− };
104−
105− return {
106− id: "memorySecrets" as const,
107− storage: () => ({}),
108− extension: () => ({
109− label: "in-memory secrets",
110− }),
111− secretProviders: () => [provider],
112− };
113− },
114−);
86+export const memorySecretsPlugin = definePlugin((options?: MemorySecretsConfig) => {
87+ const map = new Map<string, string>(Object.entries(options?.initial ?? {}));
88+ const provider: SecretProvider = {
89+ key: "memory",
90+ writable: true,
91+ get: (id: string) => Effect.sync(() => map.get(id) ?? null),
92+ has: (id: string) => Effect.sync(() => map.has(id)),
93+ set: (id: string, value: string) => Effect.sync(() => void map.set(id, value)),
94+ delete: (id: string) => Effect.sync(() => map.delete(id)),
95+ list: () => Effect.sync(() => Array.from(map.keys()).map((k) => ({ id: k, name: k }))),
96+ };
97+
98+ return {
99+ id: "memorySecrets" as const,
100+ storage: () => ({}),
101+ extension: () => ({
102+ label: "in-memory secrets",
103+ }),
104+ secretProviders: () => [provider],
105+ };
106+});
115107
116108 // End users compose the executor exactly the same way as above —
117109 // the plugin's extension is reachable as `executor.memorySecrets`.
packages/core/sdk/package.json+12−12
@@ -53,8 +53,18 @@ · 52 unmodified lines
5353 "dependencies": {
5454 "@executor-js/storage-core": "workspace:*",
5555 "effect": "catalog:",
56− "oauth4webapi": "^3.8.5",
57− "fractional-indexing": "^3.2.0"
56+ "fractional-indexing": "^3.2.0",
57+ "oauth4webapi": "^3.8.5"
58+ },
59+ "devDependencies": {
60+ "@effect/atom-react": "catalog:",
61+ "@effect/vitest": "catalog:",
62+ "@types/node": "catalog:",
63+ "@types/react": "catalog:",
64+ "react": "catalog:",
65+ "tsup": "catalog:",
66+ "typescript": "catalog:",
67+ "vitest": "catalog:"
5868 },
5969 "peerDependencies": {
6070 "@effect/atom-react": "catalog:",
@@ -67,15 +77,5 @@ · 6 unmodified lines
6777 "react": {
6878 "optional": true
6979 }
70− },
71− "devDependencies": {
72− "@effect/atom-react": "catalog:",
73− "@effect/vitest": "catalog:",
74− "@types/node": "catalog:",
75− "@types/react": "catalog:",
76− "react": "catalog:",
77− "tsup": "catalog:",
78− "typescript": "catalog:",
79− "vitest": "catalog:"
8080 }
8181 }
packages/core/sdk/src/blob.test.ts+2−6
@@ -75,9 +75,7 @@ describe("pluginBlobStore", () => { · 74 unmodified lines
7575 Effect.gen(function* () {
7676 const store = makeInMemoryBlobStore();
7777 const plugin = pluginBlobStore(store, ["inner", "outer"], "my-plugin");
78− const err = yield* plugin
79− .put("k", "v", { scope: "not-in-stack" })
80− .pipe(Effect.flip);
78+ const err = yield* plugin.put("k", "v", { scope: "not-in-stack" }).pipe(Effect.flip);
8179 expect(err).toBeInstanceOf(StorageError);
8280 expect(err).toMatchObject({
8381 message: expect.stringContaining("not in the"),
@@ -91,9 +89,7 @@ describe("pluginBlobStore", () => { · 7 unmodified lines
9189 Effect.gen(function* () {
9290 const store = makeInMemoryBlobStore();
9391 const plugin = pluginBlobStore(store, ["inner"], "my-plugin");
94− const err = yield* plugin
95− .delete("k", { scope: "not-in-stack" })
96− .pipe(Effect.flip);
92+ const err = yield* plugin.delete("k", { scope: "not-in-stack" }).pipe(Effect.flip);
9793 expect(err).toBeInstanceOf(StorageError);
9894 }),
9995 );
packages/core/sdk/src/blob.ts+4−16
@@ -21,10 +21,7 @@ import { Effect } from "effect"; · 20 unmodified lines
2121 import { StorageError } from "@executor-js/storage-core";
2222
2323 export interface BlobStore {
24− readonly get: (
25− namespace: string,
26− key: string,
27− ) => Effect.Effect<string | null, StorageError>;
24+ readonly get: (namespace: string, key: string) => Effect.Effect<string | null, StorageError>;
2825 /** Multi-namespace lookup for a single key. Backends issue one query
2926 * (`WHERE namespace IN (...) AND key = ?`) and return the hits keyed
3027 * by namespace — the caller applies its own precedence. Lets
@@ -39,14 +36,8 @@ export interface BlobStore { · 8 unmodified lines
3936 key: string,
4037 value: string,
4138 ) => Effect.Effect<void, StorageError>;
42− readonly delete: (
43− namespace: string,
44− key: string,
45− ) => Effect.Effect<void, StorageError>;
46− readonly has: (
47− namespace: string,
48− key: string,
49− ) => Effect.Effect<boolean, StorageError>;
39+ readonly delete: (namespace: string, key: string) => Effect.Effect<void, StorageError>;
40+ readonly has: (namespace: string, key: string) => Effect.Effect<boolean, StorageError>;
5041 }
5142
5243 export interface PluginBlobStore {
@@ -69,10 +60,7 @@ export interface PluginBlobStore { · 16 unmodified lines
6960 readonly has: (key: string) => Effect.Effect<boolean, StorageError>;
7061 }
7162
72−const assertScope = (
73− scope: string,
74− scopes: readonly string[],
75−): Effect.Effect<void, StorageError> =>
63+const assertScope = (scope: string, scopes: readonly string[]): Effect.Effect<void, StorageError> =>
7664 scopes.includes(scope)
7765 ? Effect.void
7866 : Effect.fail(
packages/core/sdk/src/client.ts+8−20
@@ -35,12 +35,7 @@ export * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; · 34 unmodified lines
3535 export * as Atom from "effect/unstable/reactivity/Atom";
3636 export * as AtomHttpApi from "effect/unstable/reactivity/AtomHttpApi";
3737
38−export {
39− useAtomValue,
40− useAtomSet,
41− useAtomMount,
42− useAtomRefresh,
43−} from "@effect/atom-react";
38+export { useAtomValue, useAtomSet, useAtomMount, useAtomRefresh } from "@effect/atom-react";
4439
4540 // ---------------------------------------------------------------------------
4641 // defineClientPlugin — declarative spec for the frontend half of a plugin.
@@ -219,14 +214,11 @@ export const createPluginAtomClient = < · 172 unmodified lines
219214 const { baseUrl = "/api" } = options;
220215 const pluginId = group.identifier;
221216 const bundle = HttpApi.make(`plugin-${pluginId}`).add(group);
222− return AtomHttpApi.Service<`Plugin_${G["identifier"]}Client`>()(
223− `Plugin_${pluginId}Client`,
224− {
225− api: bundle,
226− httpClient: FetchHttpClient.layer,
227− baseUrl,
228− },
229− );
217+ return AtomHttpApi.Service<`Plugin_${G["identifier"]}Client`>()(`Plugin_${pluginId}Client`, {
218+ api: bundle,
219+ httpClient: FetchHttpClient.layer,
220+ baseUrl,
221+ });
230222 };
231223
232224 // ---------------------------------------------------------------------------
@@ -250,9 +242,7 @@ interface ExecutorPluginsContextValue { · 17 unmodified lines
250242 readonly secretProviderPlugins: readonly SecretProviderPlugin[];
251243 }
252244
253−const ExecutorPluginsContext = createContext<
254− ExecutorPluginsContextValue | null
255−>(null);
245+const ExecutorPluginsContext = createContext<ExecutorPluginsContextValue | null>(null);
256246 ExecutorPluginsContext.displayName = "ExecutorPluginsContext";
257247
258248 export interface ExecutorPluginsProviderProps {
@@ -286,9 +276,7 @@ const usePluginsCtx = (hookName: string): ExecutorPluginsContextValue => { · 27 unmodified lines
286276 const ctx = useContext(ExecutorPluginsContext);
287277 if (!ctx) {
288278 // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: React hook invariant
289− throw new Error(
290− `${hookName} must be called inside an <ExecutorPluginsProvider>.`,
291− );
279+ throw new Error(`${hookName} must be called inside an <ExecutorPluginsProvider>.`);
292280 }
293281 return ctx;
294282 };
packages/core/sdk/src/connections.test.ts+358−422
@@ -162,9 +162,7 @@ describe("connections", () => { · 161 unmodified lines
162162 }),
163163 )
164164 .pipe(Effect.flip);
165− expect(
166− Predicate.isTagged(err, "ConnectionProviderNotRegisteredError"),
167− ).toBe(true);
165+ expect(Predicate.isTagged(err, "ConnectionProviderNotRegisteredError")).toBe(true);
168166 }),
169167 );
170168
@@ -200,54 +198,52 @@ describe("connections", () => { · 29 unmodified lines
200198 }),
201199 );
202200
203− it.effect(
204− "secrets.list hides connection-owned secrets but surfaces bare ones",
205− () =>
206− Effect.gen(function* () {
207− const { provider } = makeConnectionProvider({ key: "spotify" });
208− const executor = yield* createExecutor(
209− makeTestConfig({
210− plugins: [memorySecretsPlugin(), connPlugin(provider)] as const,
211− }),
212− );
201+ it.effect("secrets.list hides connection-owned secrets but surfaces bare ones", () =>
202+ Effect.gen(function* () {
203+ const { provider } = makeConnectionProvider({ key: "spotify" });
204+ const executor = yield* createExecutor(
205+ makeTestConfig({
206+ plugins: [memorySecretsPlugin(), connPlugin(provider)] as const,
207+ }),
208+ );
213209
214− yield* executor.secrets.set(
215− new SetSecretInput({
216− id: sid("bare-api"),
217− scope: scpid("test-scope"),
218− name: "bare API key",
219− value: "bare",
220− }),
221− );
210+ yield* executor.secrets.set(
211+ new SetSecretInput({
212+ id: sid("bare-api"),
213+ scope: scpid("test-scope"),
214+ name: "bare API key",
215+ value: "bare",
216+ }),
217+ );
222218
223− yield* executor.connections.create(
224− new CreateConnectionInput({
225− id: cid("conn-1"),
226− scope: scpid("test-scope"),
227− provider: "spotify",
228− identityLabel: null,
229− accessToken: new TokenMaterial({
230− secretId: sid("conn-1.access"),
231− name: "access",
232− value: "a",
233− }),
234− refreshToken: new TokenMaterial({
235− secretId: sid("conn-1.refresh"),
236− name: "refresh",
237− value: "r",
238− }),
239− expiresAt: null,
240− oauthScope: null,
241− providerState: null,
219+ yield* executor.connections.create(
220+ new CreateConnectionInput({
221+ id: cid("conn-1"),
222+ scope: scpid("test-scope"),
223+ provider: "spotify",
224+ identityLabel: null,
225+ accessToken: new TokenMaterial({
226+ secretId: sid("conn-1.access"),
227+ name: "access",
228+ value: "a",
242229 }),
243− );
230+ refreshToken: new TokenMaterial({
231+ secretId: sid("conn-1.refresh"),
232+ name: "refresh",
233+ value: "r",
234+ }),
235+ expiresAt: null,
236+ oauthScope: null,
237+ providerState: null,
238+ }),
239+ );
244240
245− const list = yield* executor.secrets.list();
246− const ids = list.map((s) => String(s.id));
247− expect(ids).toContain("bare-api");
248− expect(ids).not.toContain("conn-1.access");
249− expect(ids).not.toContain("conn-1.refresh");
250− }),
241+ const list = yield* executor.secrets.list();
242+ const ids = list.map((s) => String(s.id));
243+ expect(ids).toContain("bare-api");
244+ expect(ids).not.toContain("conn-1.access");
245+ expect(ids).not.toContain("conn-1.refresh");
246+ }),
251247 );
252248
253249 it.effect(
@@ -279,72 +275,55 @@ describe("connections", () => { · 25 unmodified lines
279275 }),
280276 );
281277
282− const err = yield* executor.secrets
283− .remove("conn-1.access")
284− .pipe(Effect.flip);
285− expect(Predicate.isTagged(err, "SecretOwnedByConnectionError")).toBe(
286− true,
287− );
278+ const err = yield* executor.secrets.remove("conn-1.access").pipe(Effect.flip);
279+ expect(Predicate.isTagged(err, "SecretOwnedByConnectionError")).toBe(true);
288280 }),
289281 );
290282
291− it.effect(
292− "connections.remove cascades through providers and deletes the core row",
293− () =>
294− Effect.gen(function* () {
295− const secretProvider = makeMemoryProvider();
296− const { provider } = makeConnectionProvider({ key: "spotify" });
297− const executor = yield* createExecutor(
298− makeTestConfig({
299− plugins: [
300− memorySecretsPlugin(secretProvider),
301− connPlugin(provider),
302− ] as const,
303− }),
304− );
283+ it.effect("connections.remove cascades through providers and deletes the core row", () =>
284+ Effect.gen(function* () {
285+ const secretProvider = makeMemoryProvider();
286+ const { provider } = makeConnectionProvider({ key: "spotify" });
287+ const executor = yield* createExecutor(
288+ makeTestConfig({
289+ plugins: [memorySecretsPlugin(secretProvider), connPlugin(provider)] as const,
290+ }),
291+ );
305292
306− yield* executor.connections.create(
307− new CreateConnectionInput({
308− id: cid("conn-1"),
309− scope: scpid("test-scope"),
310− provider: "spotify",
311− identityLabel: null,
312− accessToken: new TokenMaterial({
313− secretId: sid("conn-1.access"),
314− name: "access",
315− value: "access-v1",
316− }),
317− refreshToken: new TokenMaterial({
318− secretId: sid("conn-1.refresh"),
319− name: "refresh",
320− value: "refresh-v1",
321− }),
322− expiresAt: null,
323− oauthScope: null,
324− providerState: null,
293+ yield* executor.connections.create(
294+ new CreateConnectionInput({
295+ id: cid("conn-1"),
296+ scope: scpid("test-scope"),
297+ provider: "spotify",
298+ identityLabel: null,
299+ accessToken: new TokenMaterial({
300+ secretId: sid("conn-1.access"),
301+ name: "access",
302+ value: "access-v1",
325303 }),
326− );
304+ refreshToken: new TokenMaterial({
305+ secretId: sid("conn-1.refresh"),
306+ name: "refresh",
307+ value: "refresh-v1",
308+ }),
309+ expiresAt: null,
310+ oauthScope: null,
311+ providerState: null,
312+ }),
313+ );
327314
328− // Pre-check: backing provider holds the tokens.
329− expect(
330− yield* secretProvider.get!("conn-1.access", "test-scope"),
331− ).toBe("access-v1");
332− expect(
333− yield* secretProvider.get!("conn-1.refresh", "test-scope"),
334− ).toBe("refresh-v1");
335−
336− yield* executor.connections.remove("conn-1");
337−
338− // Connection row gone.
339− expect(yield* executor.connections.get("conn-1")).toBeNull();
340− // Backing secret values gone from the provider.
341− expect(
342− yield* secretProvider.get!("conn-1.access", "test-scope"),
343− ).toBeNull();
344− expect(
345− yield* secretProvider.get!("conn-1.refresh", "test-scope"),
346− ).toBeNull();
347− }),
315+ // Pre-check: backing provider holds the tokens.
316+ expect(yield* secretProvider.get!("conn-1.access", "test-scope")).toBe("access-v1");
317+ expect(yield* secretProvider.get!("conn-1.refresh", "test-scope")).toBe("refresh-v1");
318+
319+ yield* executor.connections.remove("conn-1");
320+
321+ // Connection row gone.
322+ expect(yield* executor.connections.get("conn-1")).toBeNull();
323+ // Backing secret values gone from the provider.
324+ expect(yield* secretProvider.get!("conn-1.access", "test-scope")).toBeNull();
325+ expect(yield* secretProvider.get!("conn-1.refresh", "test-scope")).toBeNull();
326+ }),
348327 );
349328
350329 it.effect("accessToken returns the stored value when not near expiry", () =>
@@ -407,10 +386,7 @@ describe("connections", () => { · 56 unmodified lines
407386 });
408387 const executor = yield* createExecutor(
409388 makeTestConfig({
410− plugins: [
411− memorySecretsPlugin(secretProvider),
412− connPlugin(provider),
413− ] as const,
389+ plugins: [memorySecretsPlugin(secretProvider), connPlugin(provider)] as const,
414390 }),
415391 );
416392
@@ -445,12 +421,10 @@ describe("connections", () => { · 28 unmodified lines
445421 expect(calls[0]!.providerState).toEqual({ rotation: "fresh" });
446422
447423 // Backing secrets got rewritten at the same ids.
448− expect(
449− yield* secretProvider.get!("conn-1.access", "test-scope"),
450− ).toBe("rotated-refresh-v1");
451− expect(
452− yield* secretProvider.get!("conn-1.refresh", "test-scope"),
453− ).toBe("refresh-v2");
424+ expect(yield* secretProvider.get!("conn-1.access", "test-scope")).toBe(
425+ "rotated-refresh-v1",
426+ );
427+ expect(yield* secretProvider.get!("conn-1.refresh", "test-scope")).toBe("refresh-v2");
454428
455429 const got = yield* executor.connections.get("conn-1");
456430 expect(got?.providerState).toEqual({ rotation: "bumped" });
@@ -458,110 +432,105 @@ describe("connections", () => { · 1 unmodified line
458432 }),
459433 );
460434
461− it.effect(
462− "accessToken dedupes concurrent refreshes into a single provider call",
463− () =>
464− Effect.gen(function* () {
465− // A gated refresh provider. Every concurrent caller that lands
466− // inside the skew window must converge on the single pending
467− // refresh instead of hitting the token endpoint N times. The
468− // `entered` Deferred signals that the leader fiber is parked
469− // inside `refresh`; we only release the `gate` once every
470− // caller has had a chance to register.
471− const gate = yield* Deferred.make<void>();
472− const entered = yield* Deferred.make<void>();
473− const calls: ConnectionRefreshInput[] = [];
474− let responseCounter = 0;
475− const provider: ConnectionProvider = {
476− key: "spotify",
477− refresh: (
478− input,
479− ): Effect.Effect<ConnectionRefreshResult, ConnectionRefreshError> =>
480− Effect.gen(function* () {
481− calls.push(input);
482− const n = ++responseCounter;
483− yield* Deferred.succeed(entered, undefined as void);
484− // Block the leader inside `refresh` until the test
485− // releases the gate. Any other fiber that concurrently
486− // calls `accessToken` must observe the in-flight
487− // Deferred instead of entering this handler.
488− yield* Deferred.await(gate);
489− return {
490− accessToken: `rotated-${n}`,
491− refreshToken: `refresh-${n}`,
492− expiresAt: Date.now() + 3_600_000,
493− oauthScope: "user-read",
494− providerState: null,
495− };
496− }),
497− };
435+ it.effect("accessToken dedupes concurrent refreshes into a single provider call", () =>
436+ Effect.gen(function* () {
437+ // A gated refresh provider. Every concurrent caller that lands
438+ // inside the skew window must converge on the single pending
439+ // refresh instead of hitting the token endpoint N times. The
440+ // `entered` Deferred signals that the leader fiber is parked
441+ // inside `refresh`; we only release the `gate` once every
442+ // caller has had a chance to register.
443+ const gate = yield* Deferred.make<void>();
444+ const entered = yield* Deferred.make<void>();
445+ const calls: ConnectionRefreshInput[] = [];
446+ let responseCounter = 0;
447+ const provider: ConnectionProvider = {
448+ key: "spotify",
449+ refresh: (input): Effect.Effect<ConnectionRefreshResult, ConnectionRefreshError> =>
450+ Effect.gen(function* () {
451+ calls.push(input);
452+ const n = ++responseCounter;
453+ yield* Deferred.succeed(entered, undefined as void);
454+ // Block the leader inside `refresh` until the test
455+ // releases the gate. Any other fiber that concurrently
456+ // calls `accessToken` must observe the in-flight
457+ // Deferred instead of entering this handler.
458+ yield* Deferred.await(gate);
459+ return {
460+ accessToken: `rotated-${n}`,
461+ refreshToken: `refresh-${n}`,
462+ expiresAt: Date.now() + 3_600_000,
463+ oauthScope: "user-read",
464+ providerState: null,
465+ };
466+ }),
467+ };
498468
499− const executor = yield* createExecutor(
500− makeTestConfig({
501− plugins: [memorySecretsPlugin(), connPlugin(provider)] as const,
502− }),
503− );
469+ const executor = yield* createExecutor(
470+ makeTestConfig({
471+ plugins: [memorySecretsPlugin(), connPlugin(provider)] as const,
472+ }),
473+ );
504474
505− yield* executor.connections.create(
506− new CreateConnectionInput({
507− id: cid("conn-1"),
508− scope: scpid("test-scope"),
509− provider: "spotify",
510− identityLabel: null,
511− accessToken: new TokenMaterial({
512− secretId: sid("conn-1.access"),
513− name: "access",
514− value: "stale",
515− }),
516− refreshToken: new TokenMaterial({
517− secretId: sid("conn-1.refresh"),
518− name: "refresh",
519− value: "refresh-v1",
520− }),
521− // Expired so every caller enters the refresh branch.
522− expiresAt: Date.now() - 1_000,
523− oauthScope: null,
524− providerState: null,
475+ yield* executor.connections.create(
476+ new CreateConnectionInput({
477+ id: cid("conn-1"),
478+ scope: scpid("test-scope"),
479+ provider: "spotify",
480+ identityLabel: null,
481+ accessToken: new TokenMaterial({
482+ secretId: sid("conn-1.access"),
483+ name: "access",
484+ value: "stale",
525485 }),
526− );
486+ refreshToken: new TokenMaterial({
487+ secretId: sid("conn-1.refresh"),
488+ name: "refresh",
489+ value: "refresh-v1",
490+ }),
491+ // Expired so every caller enters the refresh branch.
492+ expiresAt: Date.now() - 1_000,
493+ oauthScope: null,
494+ providerState: null,
495+ }),
496+ );
527497
528− // Kick off the leader first and wait for it to park inside
529− // `refresh`. Any subsequent caller is guaranteed to see the
530− // in-flight Deferred the leader just registered.
531− const leaderFiber = yield* Effect.forkDetach(
532− executor.connections.accessToken("conn-1"),
533− { startImmediately: true },
534− );
535− yield* Deferred.await(entered);
498+ // Kick off the leader first and wait for it to park inside
499+ // `refresh`. Any subsequent caller is guaranteed to see the
500+ // in-flight Deferred the leader just registered.
501+ const leaderFiber = yield* Effect.forkDetach(executor.connections.accessToken("conn-1"), {
502+ startImmediately: true,
503+ });
504+ yield* Deferred.await(entered);
536505
537− const followerFibers = yield* Effect.forEach(
538− [1, 2, 3, 4],
539− () =>
540− Effect.forkDetach(executor.connections.accessToken("conn-1"), {
541− startImmediately: true,
542− }),
543− { concurrency: "unbounded" },
544− );
545− yield* Effect.forEach([1, 2, 3, 4], () => Effect.yieldNow);
506+ const followerFibers = yield* Effect.forEach(
507+ [1, 2, 3, 4],
508+ () =>
509+ Effect.forkDetach(executor.connections.accessToken("conn-1"), {
510+ startImmediately: true,
511+ }),
512+ { concurrency: "unbounded" },
513+ );
514+ yield* Effect.forEach([1, 2, 3, 4], () => Effect.yieldNow);
546515
547− // Every follower is queued on the leader's Deferred. Release
548− // the gate — the leader resolves, waiters wake up with the
549− // same token, no extra `refresh` is invoked.
550− yield* Deferred.succeed(gate, undefined as void);
516+ // Every follower is queued on the leader's Deferred. Release
517+ // the gate — the leader resolves, waiters wake up with the
518+ // same token, no extra `refresh` is invoked.
519+ yield* Deferred.succeed(gate, undefined as void);
551520
552− const leaderResult = yield* Fiber.join(leaderFiber);
553− const followerResults = yield* Effect.all(
554− followerFibers.map((f) => Fiber.await(f)),
555− { concurrency: "unbounded" },
556− );
557− expect(leaderResult).toBe("rotated-1");
558− for (const result of followerResults) {
559− expect(Exit.isSuccess(result)).toBe(true);
560− if (!Exit.isSuccess(result)) continue;
561− expect(result.value).toBe("rotated-1");
562− }
563− expect(calls).toHaveLength(1);
564− }),
521+ const leaderResult = yield* Fiber.join(leaderFiber);
522+ const followerResults = yield* Effect.all(
523+ followerFibers.map((f) => Fiber.await(f)),
524+ { concurrency: "unbounded" },
525+ );
526+ expect(leaderResult).toBe("rotated-1");
527+ for (const result of followerResults) {
528+ expect(Exit.isSuccess(result)).toBe(true);
529+ if (!Exit.isSuccess(result)) continue;
530+ expect(result.value).toBe("rotated-1");
531+ }
532+ expect(calls).toHaveLength(1);
533+ }),
565534 );
566535
567536 it.effect(
@@ -609,12 +578,8 @@ describe("connections", () => { · 41 unmodified lines
609578 }),
610579 );
611580
612− const flipped = yield* executor.connections
613− .accessToken("conn-1")
614− .pipe(Effect.flip);
615− expect(
616− Predicate.isTagged(flipped, "ConnectionReauthRequiredError"),
617− ).toBe(true);
581+ const flipped = yield* executor.connections.accessToken("conn-1").pipe(Effect.flip);
582+ expect(Predicate.isTagged(flipped, "ConnectionReauthRequiredError")).toBe(true);
618583 if (!Predicate.isTagged(flipped, "ConnectionReauthRequiredError")) {
619584 return;
620585 }
@@ -623,57 +588,53 @@ describe("connections", () => { · 2 unmodified lines
623588 }),
624589 );
625590
626− it.effect(
627− "accessToken preserves ConnectionRefreshError for non-reauth failures",
628− () =>
629− Effect.gen(function* () {
630− // Transient failure path — the provider failed but not with a
631− // terminal RFC 6749 code. The SDK must keep it as-is so
632− // callers can tell "retry later" from "prompt for sign-in".
633− const provider: ConnectionProvider = {
634− key: "spotify",
635− refresh: (input) =>
636− Effect.fail(
637− new ConnectionRefreshError({
638− connectionId: input.connectionId,
639− message: "network flake",
640− }),
641− ),
642− };
591+ it.effect("accessToken preserves ConnectionRefreshError for non-reauth failures", () =>
592+ Effect.gen(function* () {
593+ // Transient failure path — the provider failed but not with a
594+ // terminal RFC 6749 code. The SDK must keep it as-is so
595+ // callers can tell "retry later" from "prompt for sign-in".
596+ const provider: ConnectionProvider = {
597+ key: "spotify",
598+ refresh: (input) =>
599+ Effect.fail(
600+ new ConnectionRefreshError({
601+ connectionId: input.connectionId,
602+ message: "network flake",
603+ }),
604+ ),
605+ };
643606
644− const executor = yield* createExecutor(
645− makeTestConfig({
646− plugins: [memorySecretsPlugin(), connPlugin(provider)] as const,
647− }),
648− );
607+ const executor = yield* createExecutor(
608+ makeTestConfig({
609+ plugins: [memorySecretsPlugin(), connPlugin(provider)] as const,
610+ }),
611+ );
649612
650− yield* executor.connections.create(
651− new CreateConnectionInput({
652− id: cid("conn-1"),
653− scope: scpid("test-scope"),
654− provider: "spotify",
655− identityLabel: null,
656− accessToken: new TokenMaterial({
657− secretId: sid("conn-1.access"),
658− name: "access",
659− value: "stale",
660− }),
661− refreshToken: new TokenMaterial({
662− secretId: sid("conn-1.refresh"),
663− name: "refresh",
664− value: "refresh-v1",
665− }),
666− expiresAt: Date.now() - 1_000,
667− oauthScope: null,
668− providerState: null,
613+ yield* executor.connections.create(
614+ new CreateConnectionInput({
615+ id: cid("conn-1"),
616+ scope: scpid("test-scope"),
617+ provider: "spotify",
618+ identityLabel: null,
619+ accessToken: new TokenMaterial({
620+ secretId: sid("conn-1.access"),
621+ name: "access",
622+ value: "stale",
669623 }),
670− );
624+ refreshToken: new TokenMaterial({
625+ secretId: sid("conn-1.refresh"),
626+ name: "refresh",
627+ value: "refresh-v1",
628+ }),
629+ expiresAt: Date.now() - 1_000,
630+ oauthScope: null,
631+ providerState: null,
632+ }),
633+ );
671634
672− const err = yield* executor.connections
673− .accessToken("conn-1")
674− .pipe(Effect.flip);
675− expect(Predicate.isTagged(err, "ConnectionRefreshError")).toBe(true);
676− }),
635+ const err = yield* executor.connections.accessToken("conn-1").pipe(Effect.flip);
636+ expect(Predicate.isTagged(err, "ConnectionRefreshError")).toBe(true);
637+ }),
677638 );
678639
679640 it.effect(
@@ -705,116 +666,97 @@ describe("connections", () => { · 25 unmodified lines
705666 }),
706667 );
707668
708− const err = yield* executor.connections
709− .accessToken("conn-1")
710− .pipe(Effect.flip);
711− expect(
712− Predicate.isTagged(err, "ConnectionRefreshNotSupportedError"),
713− ).toBe(true);
669+ const err = yield* executor.connections.accessToken("conn-1").pipe(Effect.flip);
670+ expect(Predicate.isTagged(err, "ConnectionRefreshNotSupportedError")).toBe(true);
714671 }),
715672 );
716673
717− it.effect(
718− "accessToken fails with ConnectionNotFoundError for an unknown id",
719− () =>
720− Effect.gen(function* () {
721− const executor = yield* createExecutor(
722− makeTestConfig({
723− plugins: [memorySecretsPlugin()] as const,
724− }),
725− );
674+ it.effect("accessToken fails with ConnectionNotFoundError for an unknown id", () =>
675+ Effect.gen(function* () {
676+ const executor = yield* createExecutor(
677+ makeTestConfig({
678+ plugins: [memorySecretsPlugin()] as const,
679+ }),
680+ );
726681
727− const err = yield* executor.connections
728− .accessToken("does-not-exist")
729− .pipe(Effect.flip);
730− expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true);
731− }),
682+ const err = yield* executor.connections.accessToken("does-not-exist").pipe(Effect.flip);
683+ expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true);
684+ }),
732685 );
733686
734− it.effect(
735− "updateTokens writes new values but does not rotate secret ids",
736− () =>
737− Effect.gen(function* () {
738− const secretProvider = makeMemoryProvider();
739− const { provider } = makeConnectionProvider({ key: "spotify" });
740− const executor = yield* createExecutor(
741− makeTestConfig({
742− plugins: [
743− memorySecretsPlugin(secretProvider),
744− connPlugin(provider),
745− ] as const,
746− }),
747− );
687+ it.effect("updateTokens writes new values but does not rotate secret ids", () =>
688+ Effect.gen(function* () {
689+ const secretProvider = makeMemoryProvider();
690+ const { provider } = makeConnectionProvider({ key: "spotify" });
691+ const executor = yield* createExecutor(
692+ makeTestConfig({
693+ plugins: [memorySecretsPlugin(secretProvider), connPlugin(provider)] as const,
694+ }),
695+ );
748696
749− yield* executor.connections.create(
750− new CreateConnectionInput({
751− id: cid("conn-1"),
752− scope: scpid("test-scope"),
753− provider: "spotify",
754− identityLabel: null,
755− accessToken: new TokenMaterial({
756− secretId: sid("conn-1.access"),
757− name: "access",
758− value: "v1",
759− }),
760− refreshToken: new TokenMaterial({
761− secretId: sid("conn-1.refresh"),
762− name: "refresh",
763− value: "r1",
764− }),
765− expiresAt: null,
766− oauthScope: null,
767− providerState: null,
697+ yield* executor.connections.create(
698+ new CreateConnectionInput({
699+ id: cid("conn-1"),
700+ scope: scpid("test-scope"),
701+ provider: "spotify",
702+ identityLabel: null,
703+ accessToken: new TokenMaterial({
704+ secretId: sid("conn-1.access"),
705+ name: "access",
706+ value: "v1",
768707 }),
769− );
770−
771− const updated = yield* executor.connections.updateTokens(
772− new UpdateConnectionTokensInput({
773− id: cid("conn-1"),
774− accessToken: "v2",
775− refreshToken: "r2",
776− expiresAt: 1_700_000_000_000,
777− oauthScope: "new-scope",
778− providerState: { rotation: "next" },
708+ refreshToken: new TokenMaterial({
709+ secretId: sid("conn-1.refresh"),
710+ name: "refresh",
711+ value: "r1",
779712 }),
780− );
713+ expiresAt: null,
714+ oauthScope: null,
715+ providerState: null,
716+ }),
717+ );
781718
782− expect(updated.accessTokenSecretId).toBe(sid("conn-1.access"));
783− expect(updated.refreshTokenSecretId).toBe(sid("conn-1.refresh"));
784− expect(updated.expiresAt).toBe(1_700_000_000_000);
785− expect(updated.oauthScope).toBe("new-scope");
786− expect(updated.providerState).toEqual({ rotation: "next" });
787−
788− expect(
789− yield* secretProvider.get!("conn-1.access", "test-scope"),
790− ).toBe("v2");
791− expect(
792− yield* secretProvider.get!("conn-1.refresh", "test-scope"),
793− ).toBe("r2");
794− }),
719+ const updated = yield* executor.connections.updateTokens(
720+ new UpdateConnectionTokensInput({
721+ id: cid("conn-1"),
722+ accessToken: "v2",
723+ refreshToken: "r2",
724+ expiresAt: 1_700_000_000_000,
725+ oauthScope: "new-scope",
726+ providerState: { rotation: "next" },
727+ }),
728+ );
729+
730+ expect(updated.accessTokenSecretId).toBe(sid("conn-1.access"));
731+ expect(updated.refreshTokenSecretId).toBe(sid("conn-1.refresh"));
732+ expect(updated.expiresAt).toBe(1_700_000_000_000);
733+ expect(updated.oauthScope).toBe("new-scope");
734+ expect(updated.providerState).toEqual({ rotation: "next" });
735+
736+ expect(yield* secretProvider.get!("conn-1.access", "test-scope")).toBe("v2");
737+ expect(yield* secretProvider.get!("conn-1.refresh", "test-scope")).toBe("r2");
738+ }),
795739 );
796740
797− it.effect(
798− "updateTokens fails with ConnectionNotFoundError for unknown id",
799− () =>
800− Effect.gen(function* () {
801− const { provider } = makeConnectionProvider({ key: "spotify" });
802− const executor = yield* createExecutor(
803− makeTestConfig({
804− plugins: [memorySecretsPlugin(), connPlugin(provider)] as const,
805− }),
806− );
741+ it.effect("updateTokens fails with ConnectionNotFoundError for unknown id", () =>
742+ Effect.gen(function* () {
743+ const { provider } = makeConnectionProvider({ key: "spotify" });
744+ const executor = yield* createExecutor(
745+ makeTestConfig({
746+ plugins: [memorySecretsPlugin(), connPlugin(provider)] as const,
747+ }),
748+ );
807749
808− const err = yield* executor.connections
809− .updateTokens(
810− new UpdateConnectionTokensInput({
811− id: cid("nope"),
812− accessToken: "x",
813− }),
814− )
815− .pipe(Effect.flip);
816− expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true);
817− }),
750+ const err = yield* executor.connections
751+ .updateTokens(
752+ new UpdateConnectionTokensInput({
753+ id: cid("nope"),
754+ accessToken: "x",
755+ }),
756+ )
757+ .pipe(Effect.flip);
758+ expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true);
759+ }),
818760 );
819761
820762 it.effect("setIdentityLabel updates the label", () =>
@@ -850,21 +792,19 @@ describe("connections", () => { · 29 unmodified lines
850792 }),
851793 );
852794
853− it.effect(
854− "setIdentityLabel fails with ConnectionNotFoundError for unknown id",
855− () =>
856− Effect.gen(function* () {
857− const executor = yield* createExecutor(
858− makeTestConfig({
859− plugins: [memorySecretsPlugin()] as const,
860− }),
861− );
795+ it.effect("setIdentityLabel fails with ConnectionNotFoundError for unknown id", () =>
796+ Effect.gen(function* () {
797+ const executor = yield* createExecutor(
798+ makeTestConfig({
799+ plugins: [memorySecretsPlugin()] as const,
800+ }),
801+ );
862802
863− const err = yield* executor.connections
864− .setIdentityLabel("does-not-exist", "x")
865− .pipe(Effect.flip);
866− expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true);
867− }),
803+ const err = yield* executor.connections
804+ .setIdentityLabel("does-not-exist", "x")
805+ .pipe(Effect.flip);
806+ expect(Predicate.isTagged(err, "ConnectionNotFoundError")).toBe(true);
807+ }),
868808 );
869809
870810 it.effect("providers() returns every registered connection provider key", () =>
@@ -936,8 +876,7 @@ const makeLayeredConnExecutors = () => · 65 unmodified lines
936876 describe("connections — multi-scope behaviour", () => {
937877 it.effect("get picks the innermost-scope row when the same id exists at two scopes", () =>
938878 Effect.gen(function* () {
939− const { execOuter, execInner, outerId, innerId } =
940− yield* makeLayeredConnExecutors();
879+ const { execOuter, execInner, outerId, innerId } = yield* makeLayeredConnExecutors();
941880
942881 yield* execOuter.connections.create(
943882 new CreateConnectionInput({
@@ -991,52 +930,49 @@ describe("connections — multi-scope behaviour", () => { · 47 unmodified lines
991930 }),
992931 );
993932
994− it.effect(
995− "remove at the inner scope does not wipe the outer-scope connection",
996− () =>
997− Effect.gen(function* () {
998− const { execOuter, execInner, outerId, innerId } =
999− yield* makeLayeredConnExecutors();
933+ it.effect("remove at the inner scope does not wipe the outer-scope connection", () =>
934+ Effect.gen(function* () {
935+ const { execOuter, execInner, outerId, innerId } = yield* makeLayeredConnExecutors();
1000936
1001− yield* execOuter.connections.create(
1002− new CreateConnectionInput({
1003− id: cid("shared"),
1004− scope: outerId,
1005− provider: "spotify",
1006− identityLabel: "outer",
1007− accessToken: new TokenMaterial({
1008− secretId: sid("shared.access.outer"),
1009− name: "access",
1010− value: "outer-access",
1011− }),
1012− refreshToken: null,
1013− expiresAt: null,
1014− oauthScope: null,
1015− providerState: null,
937+ yield* execOuter.connections.create(
938+ new CreateConnectionInput({
939+ id: cid("shared"),
940+ scope: outerId,
941+ provider: "spotify",
942+ identityLabel: "outer",
943+ accessToken: new TokenMaterial({
944+ secretId: sid("shared.access.outer"),
945+ name: "access",
946+ value: "outer-access",
1016947 }),
1017− );
1018− yield* execInner.connections.create(
1019− new CreateConnectionInput({
1020− id: cid("shared"),
1021− scope: innerId,
1022− provider: "spotify",
1023− identityLabel: "inner",
1024− accessToken: new TokenMaterial({
1025− secretId: sid("shared.access.inner"),
1026− name: "access",
1027− value: "inner-access",
1028− }),
1029− refreshToken: null,
1030− expiresAt: null,
1031− oauthScope: null,
1032− providerState: null,
948+ refreshToken: null,
949+ expiresAt: null,
950+ oauthScope: null,
951+ providerState: null,
952+ }),
953+ );
954+ yield* execInner.connections.create(
955+ new CreateConnectionInput({
956+ id: cid("shared"),
957+ scope: innerId,
958+ provider: "spotify",
959+ identityLabel: "inner",
960+ accessToken: new TokenMaterial({
961+ secretId: sid("shared.access.inner"),
962+ name: "access",
963+ value: "inner-access",
1033964 }),
1034− );
965+ refreshToken: null,
966+ expiresAt: null,
967+ oauthScope: null,
968+ providerState: null,
969+ }),
970+ );
1035971
1036− yield* execInner.connections.remove("shared");
972+ yield* execInner.connections.remove("shared");
1037973
1038− const outerStill = yield* execOuter.connections.get("shared");
1039− expect(outerStill?.identityLabel).toBe("outer");
1040− }),
974+ const outerStill = yield* execOuter.connections.get("shared");
975+ expect(outerStill?.identityLabel).toBe("outer");
976+ }),
1041977 );
1042978 });
packages/core/sdk/src/connections.ts+1−3
@@ -87,9 +87,7 @@ export class CreateConnectionInput extends Schema.Class<CreateConnectionInput>( · 86 unmodified lines
8787 // the SDK rethrows it from `ctx.connections.accessToken` callers.
8888 // ---------------------------------------------------------------------------
8989
90−export class ConnectionRefreshError extends Data.TaggedError(
91− "ConnectionRefreshError",
92−)<{
90+export class ConnectionRefreshError extends Data.TaggedError("ConnectionRefreshError")<{
9391 readonly connectionId: ConnectionId;
9492 readonly message: string;
9593 /**
packages/core/sdk/src/core-schema.ts+6−17
@@ -10,10 +10,7 @@ · 9 unmodified lines
1010 // dynamic (runtime-registered) rows.
1111 // ---------------------------------------------------------------------------
1212
13−import type {
14− DBSchema,
15− InferDBFieldsOutput,
16−} from "@executor-js/storage-core";
13+import type { DBSchema, InferDBFieldsOutput } from "@executor-js/storage-core";
1714
1815 export const coreSchema = {
1916 source: {
@@ -209,25 +206,18 @@ export type CoreSchema = typeof coreSchema; · 189 unmodified lines
209206 export type SourceRow = InferDBFieldsOutput<CoreSchema["source"]["fields"]> &
210207 Record<string, unknown>;
211208
212−export type ToolRow = InferDBFieldsOutput<CoreSchema["tool"]["fields"]> &
213− Record<string, unknown>;
209+export type ToolRow = InferDBFieldsOutput<CoreSchema["tool"]["fields"]> & Record<string, unknown>;
214210
215−export type DefinitionRow = InferDBFieldsOutput<
216− CoreSchema["definition"]["fields"]
217−> &
211+export type DefinitionRow = InferDBFieldsOutput<CoreSchema["definition"]["fields"]> &
218212 Record<string, unknown>;
219213
220214 export type SecretRow = InferDBFieldsOutput<CoreSchema["secret"]["fields"]> &
221215 Record<string, unknown>;
222216
223−export type ConnectionRow = InferDBFieldsOutput<
224− CoreSchema["connection"]["fields"]
225−> &
217+export type ConnectionRow = InferDBFieldsOutput<CoreSchema["connection"]["fields"]> &
226218 Record<string, unknown>;
227219
228−export type ToolPolicyRow = InferDBFieldsOutput<
229− CoreSchema["tool_policy"]["fields"]
230−> &
220+export type ToolPolicyRow = InferDBFieldsOutput<CoreSchema["tool_policy"]["fields"]> &
231221 Record<string, unknown>;
232222
233223 // ---------------------------------------------------------------------------
@@ -251,8 +241,7 @@ export const TOOL_POLICY_ACTIONS = [ · 17 unmodified lines
251241 ] as const satisfies readonly ToolPolicyAction[];
252242
253243 export const isToolPolicyAction = (value: unknown): value is ToolPolicyAction =>
254− typeof value === "string" &&
255− (TOOL_POLICY_ACTIONS as readonly string[]).includes(value);
244+ typeof value === "string" && (TOOL_POLICY_ACTIONS as readonly string[]).includes(value);
256245
257246 // ---------------------------------------------------------------------------
258247 // Tool annotations — default-policy metadata the executor consults
packages/core/sdk/src/error-handling.test.ts+4−11
@@ -43,8 +43,7 @@ import { Scope } from "./scope"; · 42 unmodified lines
4343 // ---------------------------------------------------------------------------
4444
4545 const makeFailingAdapter = (failure: StorageFailure): DBAdapter => {
46− const fail = () =>
47− Effect.fail(failure) as Effect.Effect<never, StorageFailure, never>;
46+ const fail = () => Effect.fail(failure) as Effect.Effect<never, StorageFailure, never>;
4847 return {
4948 id: "failing",
5049 create: fail as DBAdapter["create"],
@@ -91,9 +90,7 @@ describe("typed-error edge model — SDK", () => { · 40 unmodified lines
9190 message: "backend lost its mind",
9291 cause: driverCause,
9392 });
94− const executor = yield* createExecutor(
95− baseConfig(makeFailingAdapter(failure)),
96− );
93+ const executor = yield* createExecutor(baseConfig(makeFailingAdapter(failure)));
9794
9895 const result = yield* executor.tools.list().pipe(Effect.flip);
9996 expect(result).toBeInstanceOf(StorageError);
@@ -140,9 +137,7 @@ describe("typed-error edge model — SDK", () => { · 40 unmodified lines
140137 model: err.model ?? null,
141138 }),
142139 ),
143− Effect.map((r) =>
144− "caught" in r ? r : { caught: false as const, model: null },
145− ),
140+ Effect.map((r) => ("caught" in r ? r : { caught: false as const, model: null })),
146141 ),
147142 }),
148143 extension: (ctx) => ({
@@ -172,9 +167,7 @@ describe("typed-error edge model — SDK", () => { · 23 unmodified lines
172167 // type-check (R would leak) nor run (would crash at construction).
173168 const executor = yield* createExecutor(
174169 baseConfig(
175− makeFailingAdapter(
176− new StorageError({ message: "doesn't matter", cause: undefined }),
177− ),
170+ makeFailingAdapter(new StorageError({ message: "doesn't matter", cause: undefined })),
178171 ),
179172 );
180173 // Sanity: the executor surface exists and methods are callable.
packages/core/sdk/src/errors.ts+4−7
@@ -32,13 +32,10 @@ export class PluginNotLoadedError extends Schema.TaggedErrorClass<PluginNotLoade · 31 unmodified lines
3232 * the plugin only declares static tools and this one's id matched
3333 * dynamically somehow. Shouldn't happen in practice; guards against
3434 * programmer error. */
35−export class NoHandlerError extends Schema.TaggedErrorClass<NoHandlerError>()(
36− "NoHandlerError",
37− {
38− toolId: ToolId,
39− pluginId: Schema.String,
40− },
41−) {}
35+export class NoHandlerError extends Schema.TaggedErrorClass<NoHandlerError>()("NoHandlerError", {
36+ toolId: ToolId,
37+ pluginId: Schema.String,
38+}) {}
4239
4340 /** Tool invocation was rejected because a workspace `tool_policy` rule
4441 * with `action: "block"` matched. `pattern` is the matched policy
packages/core/sdk/src/executor.test.ts+190−276
@@ -7,11 +7,7 @@ import type { DBAdapter, Where } from "@executor-js/storage-core"; · 6 unmodified lines
77 import { makeInMemoryBlobStore } from "./blob";
88 import { CreateConnectionInput, TokenMaterial } from "./connections";
99 import { collectSchemas, createExecutor } from "./executor";
10−import {
11− ElicitationResponse,
12− FormElicitation,
13− UrlElicitation,
14−} from "./elicitation";
10+import { ElicitationResponse, FormElicitation, UrlElicitation } from "./elicitation";
1511 import { defineSchema, definePlugin } from "./plugin";
1612 import { SetSecretInput } from "./secrets";
1713 import { makeTestConfig } from "./testing";
@@ -29,10 +25,7 @@ class TestPluginError extends Data.TaggedError("TestPluginError")<{ · 11 unmodified lines
2925 readonly message: string;
3026 }> {}
3127
32−const recordFindMany = (
33− adapter: DBAdapter,
34− calls: FindManyCall[],
35−): DBAdapter => ({
28+const recordFindMany = (adapter: DBAdapter, calls: FindManyCall[]): DBAdapter => ({
3629 ...adapter,
3730 findMany: (data) => {
3831 calls.push({ model: data.model, where: data.where });
@@ -244,9 +237,7 @@ const opaqueRouteSecretsPlugin = definePlugin(() => ({ · 205 unmodified lines
244237 describe("createExecutor", () => {
245238 it.effect("invokes a static tool via the in-memory pool", () =>
246239 Effect.gen(function* () {
247− const executor = yield* createExecutor(
248− makeTestConfig({ plugins: [testPlugin()] as const }),
249− );
240+ const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const }));
250241 const result = yield* executor.tools.invoke(
251242 "test.control.echo",
252243 { text: "hi" },
@@ -258,9 +249,7 @@ describe("createExecutor", () => { · 5 unmodified lines
258249
259250 it.effect("lists static tools alongside dynamic ones", () =>
260251 Effect.gen(function* () {
261− const executor = yield* createExecutor(
262− makeTestConfig({ plugins: [testPlugin()] as const }),
263− );
252+ const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const }));
264253 yield* executor.test.addThing("thing1", "hello");
265254
266255 const tools = yield* executor.tools.list();
@@ -273,9 +262,7 @@ describe("createExecutor", () => { · 6 unmodified lines
273262
274263 it.effect("filters tools by query", () =>
275264 Effect.gen(function* () {
276− const executor = yield* createExecutor(
277− makeTestConfig({ plugins: [testPlugin()] as const }),
278− );
265+ const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const }));
279266 yield* executor.test.addThing("thing1", "hello");
280267
281268 const tools = yield* executor.tools.list({ query: "echo" });
@@ -298,24 +285,17 @@ describe("createExecutor", () => { · 16 unmodified lines
298285 findManyCalls.length = 0;
299286 const tools = yield* executor.tools.list({ sourceId: "thing1" });
300287
301− expect(tools.map((t) => t.id).sort()).toEqual([
302− "thing1.read",
303− "thing1.write",
304− ]);
288+ expect(tools.map((t) => t.id).sort()).toEqual(["thing1.read", "thing1.write"]);
305289 const toolRead = findManyCalls.find((call) => call.model === "tool");
306290 expect(toolRead?.where).toEqual(
307− expect.arrayContaining([
308− expect.objectContaining({ field: "source_id", value: "thing1" }),
309− ]),
291+ expect.arrayContaining([expect.objectContaining({ field: "source_id", value: "thing1" })]),
310292 );
311293 }),
312294 );
313295
314296 it.effect("can list tools without resolving dynamic annotations", () =>
315297 Effect.gen(function* () {
316− const executor = yield* createExecutor(
317− makeTestConfig({ plugins: [testPlugin()] as const }),
318− );
298+ const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const }));
319299 yield* executor.test.addThing("thing1", "hello");
320300 testAnnotationResolveCount = 0;
321301
@@ -325,19 +305,14 @@ describe("createExecutor", () => { · 3 unmodified lines
325305 });
326306
327307 expect(testAnnotationResolveCount).toBe(0);
328− expect(tools.map((t) => t.id).sort()).toEqual([
329− "thing1.read",
330− "thing1.write",
331− ]);
308+ expect(tools.map((t) => t.id).sort()).toEqual(["thing1.read", "thing1.write"]);
332309 expect(tools.every((tool) => tool.annotations === undefined)).toBe(true);
333310 }),
334311 );
335312
336313 it.effect("invokes a dynamic tool through plugin.invokeTool", () =>
337314 Effect.gen(function* () {
338− const executor = yield* createExecutor(
339− makeTestConfig({ plugins: [testPlugin()] as const }),
340− );
315+ const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const }));
341316 yield* executor.test.addThing("thing1", "hello");
342317
343318 const result = yield* executor.tools.invoke(
@@ -351,9 +326,7 @@ describe("createExecutor", () => { · 7 unmodified lines
351326
352327 it.effect("enforces tool annotations before invoking", () =>
353328 Effect.gen(function* () {
354− const executor = yield* createExecutor(
355− makeTestConfig({ plugins: [testPlugin()] as const }),
356− );
329+ const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const }));
357330 yield* executor.test.addThing("thing1", "hello");
358331
359332 // requiresApproval: true → declined → ElicitationDeclinedError
@@ -362,8 +335,7 @@ describe("createExecutor", () => { · 2 unmodified lines
362335 "thing1.write",
363336 { value: "updated" },
364337 {
365− onElicitation: () =>
366− Effect.succeed(new ElicitationResponse({ action: "decline" })),
338+ onElicitation: () => Effect.succeed(new ElicitationResponse({ action: "decline" })),
367339 },
368340 )
369341 .pipe(Effect.flip);
@@ -381,9 +353,7 @@ describe("createExecutor", () => { · 11 unmodified lines
381353
382354 it.effect("sources.list unions static runtime sources and dynamic ones", () =>
383355 Effect.gen(function* () {
384− const executor = yield* createExecutor(
385− makeTestConfig({ plugins: [testPlugin()] as const }),
386− );
356+ const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const }));
387357 yield* executor.test.addThing("thing1", "hello");
388358
389359 const sources = yield* executor.sources.list();
@@ -444,12 +414,8 @@ describe("createExecutor", () => { · 54 unmodified lines
444414
445415 it.effect("rejects remove of a static source", () =>
446416 Effect.gen(function* () {
447− const executor = yield* createExecutor(
448− makeTestConfig({ plugins: [testPlugin()] as const }),
449− );
450− const err = yield* executor.sources
451− .remove("test.control")
452− .pipe(Effect.flip);
417+ const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const }));
418+ const err = yield* executor.sources.remove("test.control").pipe(Effect.flip);
453419 expect(Predicate.isTagged(err, "SourceRemovalNotAllowedError")).toBe(true);
454420 }),
455421 );
@@ -546,9 +512,7 @@ describe("createExecutor", () => { · 90 unmodified lines
546512
547513 it.effect("ctx.transaction commits all nested writes on success", () =>
548514 Effect.gen(function* () {
549− const executor = yield* createExecutor(
550− makeTestConfig({ plugins: [testPlugin()] as const }),
551− );
515+ const executor = yield* createExecutor(makeTestConfig({ plugins: [testPlugin()] as const }));
552516 // addThing wraps storage + sources.register in ctx.transaction.
553517 yield* executor.test.addThing("thing1", "hello");
554518
@@ -560,13 +524,9 @@ describe("createExecutor", () => { · 5 unmodified lines
560524 expect(tools).toContain("thing1.write");
561525
562526 // plugin storage row committed too
563− expect(
564− yield* executor.tools.invoke(
565− "thing1.read",
566− {},
567− { onElicitation: "accept-all" },
568− ),
569− ).toBe("hello");
527+ expect(yield* executor.tools.invoke("thing1.read", {}, { onElicitation: "accept-all" })).toBe(
528+ "hello",
529+ );
570530 }),
571531 );
572532
@@ -612,9 +572,7 @@ describe("createExecutor", () => { · 39 unmodified lines
612572 makeTestConfig({ plugins: [rollbackPlugin()] as const }),
613573 );
614574
615− const result = yield* executor.rollback
616− .doFailingTx()
617− .pipe(Effect.result);
575+ const result = yield* executor.rollback.doFailingTx().pipe(Effect.result);
618576 expect(Result.isFailure(result)).toBe(true);
619577
620578 // Plugin storage row must not persist.
@@ -684,18 +642,12 @@ describe("createExecutor", () => { · 63 unmodified lines
684642 }),
685643 );
686644
687− const leaked = yield* executor.secrets
688− .get("conn-owned.access_token")
689− .pipe(Effect.flip);
690− expect(Predicate.isTagged(leaked, "SecretOwnedByConnectionError")).toBe(
691− true,
692− );
645+ const leaked = yield* executor.secrets.get("conn-owned.access_token").pipe(Effect.flip);
646+ expect(Predicate.isTagged(leaked, "SecretOwnedByConnectionError")).toBe(true);
693647
694648 const status = yield* executor.secrets.status("conn-owned.access_token");
695649 expect(status).toBe("missing");
696− const visibleIds = (yield* executor.secrets.list()).map(
697− (s) => String(s.id),
698− );
650+ const visibleIds = (yield* executor.secrets.list()).map((s) => String(s.id));
699651 expect(visibleIds).not.toContain("conn-owned.access_token");
700652
701653 const token = yield* executor.connections.accessToken("conn-owned");
@@ -764,9 +716,7 @@ describe("createExecutor", () => { · 62 unmodified lines
764716 close: () => Effect.sync(() => void closed++),
765717 }));
766718
767− const executor = yield* createExecutor(
768− makeTestConfig({ plugins: [closeable()] as const }),
769− );
719+ const executor = yield* createExecutor(makeTestConfig({ plugins: [closeable()] as const }));
770720
771721 yield* executor.close();
772722 expect(closed).toBe(1);
@@ -812,18 +762,14 @@ describe("createExecutor", () => { · 39 unmodified lines
812762 ],
813763 }));
814764
815− const executor = yield* createExecutor(
816− makeTestConfig({ plugins: [loginPlugin()] as const }),
817− );
765+ const executor = yield* createExecutor(makeTestConfig({ plugins: [loginPlugin()] as const }));
818766
819767 const result = yield* executor.tools.invoke(
820768 "login.ctl.signIn",
821769 {},
822770 {
823771 onElicitation: (ctx) => {
824− expect(Predicate.isTagged(ctx.request, "FormElicitation")).toBe(
825− true,
826− );
772+ expect(Predicate.isTagged(ctx.request, "FormElicitation")).toBe(true);
827773 return Effect.succeed(
828774 new ElicitationResponse({
829775 action: "accept",
@@ -872,18 +818,14 @@ describe("createExecutor", () => { · 42 unmodified lines
872818 ],
873819 }));
874820
875− const executor = yield* createExecutor(
876− makeTestConfig({ plugins: [oauthPlugin()] as const }),
877− );
821+ const executor = yield* createExecutor(makeTestConfig({ plugins: [oauthPlugin()] as const }));
878822
879823 const result = yield* executor.tools.invoke(
880824 "oauth.ctl.connect",
881825 {},
882826 {
883827 onElicitation: (ctx) => {
884− expect(Predicate.isTagged(ctx.request, "UrlElicitation")).toBe(
885− true,
886− );
828+ expect(Predicate.isTagged(ctx.request, "UrlElicitation")).toBe(true);
887829 return Effect.succeed(
888830 new ElicitationResponse({
889831 action: "accept",
@@ -932,9 +874,7 @@ describe("createExecutor", () => { · 42 unmodified lines
932874 ],
933875 }));
934876
935− const executor = yield* createExecutor(
936− makeTestConfig({ plugins: [elicitOnly()] as const }),
937− );
877+ const executor = yield* createExecutor(makeTestConfig({ plugins: [elicitOnly()] as const }));
938878
939879 const action = yield* executor.tools.invoke(
940880 "elicit.ctl.ask",
@@ -985,10 +925,7 @@ describe("createExecutor", () => { · 44 unmodified lines
985925 }),
986926 );
987927
988− const result = yield* executor.rotate.rotate(
989− "DB_PASSWORD",
990− "correct-horse-battery-staple",
991− );
928+ const result = yield* executor.rotate.rotate("DB_PASSWORD", "correct-horse-battery-staple");
992929 expect(result).toEqual({
993930 oldValue: "hunter2",
994931 newValue: "correct-horse-battery-staple",
@@ -1348,8 +1285,7 @@ describe("cross-scope write preservation (SDK)", () => { · 353 unmodified lines
13481285 "secrets.set at the inner scope does not wipe an outer-scope row with the same id",
13491286 () =>
13501287 Effect.gen(function* () {
1351− const { execOuter, execInner, outerId, innerId } =
1352− yield* makeLayeredExecutors();
1288+ const { execOuter, execInner, outerId, innerId } = yield* makeLayeredExecutors();
13531289
13541290 // Admin-equivalent writes the org-wide secret at the outer scope.
13551291 yield* execInner.secrets.set(
@@ -1384,9 +1320,7 @@ describe("cross-scope write preservation (SDK)", () => { · 28 unmodified lines
13841320 // are the shared, observable source of truth.
13851321 const outerRefs = yield* execOuter.secrets.list();
13861322 expect(outerRefs.map((r) => r.id)).toContain("api-token");
1387− expect(outerRefs.find((r) => r.id === "api-token")?.scopeId).toBe(
1388− outerId,
1389− );
1323+ expect(outerRefs.find((r) => r.id === "api-token")?.scopeId).toBe(outerId);
13901324
13911325 // Inner executor's list is de-duplicated by id (innermost wins),
13921326 // so we only expect one ref for `api-token` — pinned at the
@@ -1530,40 +1464,35 @@ const makeMarkerExecutors = () => · 137 unmodified lines
15301464 });
15311465
15321466 describe("cross-scope read precedence + remove isolation (SDK)", () => {
1533− it.effect(
1534− "secrets.remove at the inner scope does not wipe outer-scope row with same id",
1535− () =>
1536− Effect.gen(function* () {
1537− const { execOuter, execInner, outerId, innerId } =
1538− yield* makeLayeredExecutors();
1467+ it.effect("secrets.remove at the inner scope does not wipe outer-scope row with same id", () =>
1468+ Effect.gen(function* () {
1469+ const { execOuter, execInner, outerId, innerId } = yield* makeLayeredExecutors();
15391470
1540− yield* execInner.secrets.set(
1541− new SetSecretInput({
1542− id: SecretId.make("api-token"),
1543− scope: outerId,
1544− name: "Org default",
1545− value: "org-default",
1546− }),
1547− );
1548− yield* execInner.secrets.set(
1549− new SetSecretInput({
1550− id: SecretId.make("api-token"),
1551− scope: innerId,
1552− name: "Personal override",
1553− value: "personal-override",
1554− }),
1555− );
1471+ yield* execInner.secrets.set(
1472+ new SetSecretInput({
1473+ id: SecretId.make("api-token"),
1474+ scope: outerId,
1475+ name: "Org default",
1476+ value: "org-default",
1477+ }),
1478+ );
1479+ yield* execInner.secrets.set(
1480+ new SetSecretInput({
1481+ id: SecretId.make("api-token"),
1482+ scope: innerId,
1483+ name: "Personal override",
1484+ value: "personal-override",
1485+ }),
1486+ );
15561487
1557− // Inner caller removes — should only drop the inner override.
1558− yield* execInner.secrets.remove("api-token");
1488+ // Inner caller removes — should only drop the inner override.
1489+ yield* execInner.secrets.remove("api-token");
15591490
1560− // Outer-only executor must still see its org-scope row.
1561− const outerRefs = yield* execOuter.secrets.list();
1562− expect(outerRefs.map((r) => r.id)).toContain("api-token");
1563− expect(outerRefs.find((r) => r.id === "api-token")?.scopeId).toBe(
1564− outerId,
1565− );
1566− }),
1491+ // Outer-only executor must still see its org-scope row.
1492+ const outerRefs = yield* execOuter.secrets.list();
1493+ expect(outerRefs.map((r) => r.id)).toContain("api-token");
1494+ expect(outerRefs.find((r) => r.id === "api-token")?.scopeId).toBe(outerId);
1495+ }),
15671496 );
15681497
15691498 it.effect(
@@ -1584,33 +1513,30 @@ describe("cross-scope read precedence + remove isolation (SDK)", () => { · 14 unmodified lines
15841513 }),
15851514 );
15861515
1587− it.effect(
1588− "ctx.core.sources.unregister at the inner scope does not wipe outer-scope row",
1589− () =>
1590− Effect.gen(function* () {
1591− const { execOuter, execInner } = yield* makeLayeredExecutors();
1516+ it.effect("ctx.core.sources.unregister at the inner scope does not wipe outer-scope row", () =>
1517+ Effect.gen(function* () {
1518+ const { execOuter, execInner } = yield* makeLayeredExecutors();
15921519
1593− yield* execOuter.tenant.addSource("shared");
1594− yield* execInner.tenant.addSource("shared");
1520+ yield* execOuter.tenant.addSource("shared");
1521+ yield* execInner.tenant.addSource("shared");
15951522
1596− // Plugin-owned unregister path (ctx.core.sources.unregister) fires
1597− // via a dedicated extension method. We drive it by calling
1598− // `sources.remove` — which routes through the same deleteSourceById
1599− // helper — but the real regression is the findOne-before-delete
1600− // picking the wrong scope's row. The outer row must survive.
1601− yield* execInner.sources.remove("shared");
1523+ // Plugin-owned unregister path (ctx.core.sources.unregister) fires
1524+ // via a dedicated extension method. We drive it by calling
1525+ // `sources.remove` — which routes through the same deleteSourceById
1526+ // helper — but the real regression is the findOne-before-delete
1527+ // picking the wrong scope's row. The outer row must survive.
1528+ yield* execInner.sources.remove("shared");
16021529
1603− const outerSources = yield* execOuter.sources.list();
1604− expect(outerSources.filter((s) => s.id === "shared")).toHaveLength(1);
1605− }),
1530+ const outerSources = yield* execOuter.sources.list();
1531+ expect(outerSources.filter((s) => s.id === "shared")).toHaveLength(1);
1532+ }),
16061533 );
16071534
16081535 it.effect(
16091536 "tools.invoke picks the innermost tool when the same tool id exists at two scopes",
16101537 () =>
16111538 Effect.gen(function* () {
1612− const { execOuter, execInner, outerId, innerId } =
1613− yield* makeMarkerExecutors();
1539+ const { execOuter, execInner, outerId, innerId } = yield* makeMarkerExecutors();
16141540
16151541 yield* execOuter.marker.register("shared", "outer");
16161542 yield* execInner.marker.register("shared", "inner");
@@ -1637,153 +1563,141 @@ describe("cross-scope read precedence + remove isolation (SDK)", () => { · 20 unmodified lines
16371563 }),
16381564 );
16391565
1640− it.effect(
1641− "tools.schema — innermost shadow returns the inner description",
1642− () =>
1643− Effect.gen(function* () {
1644− const { execOuter, execInner } = yield* makeMarkerExecutors();
1566+ it.effect("tools.schema — innermost shadow returns the inner description", () =>
1567+ Effect.gen(function* () {
1568+ const { execOuter, execInner } = yield* makeMarkerExecutors();
16451569
1646− yield* execOuter.marker.register("shared", "outer-desc");
1647− yield* execInner.marker.register("shared", "inner-desc");
1570+ yield* execOuter.marker.register("shared", "outer-desc");
1571+ yield* execInner.marker.register("shared", "inner-desc");
16481572
1649− const schema = yield* execInner.tools.schema("shared.t");
1650− expect(schema?.description).toBe("inner-desc");
1651− }),
1573+ const schema = yield* execInner.tools.schema("shared.t");
1574+ expect(schema?.description).toBe("inner-desc");
1575+ }),
16521576 );
16531577
1654− it.effect(
1655− "tools.list dedupes by id, keeping the innermost row",
1656− () =>
1657− Effect.gen(function* () {
1658− const { execOuter, execInner } = yield* makeMarkerExecutors();
1578+ it.effect("tools.list dedupes by id, keeping the innermost row", () =>
1579+ Effect.gen(function* () {
1580+ const { execOuter, execInner } = yield* makeMarkerExecutors();
16591581
1660− yield* execOuter.marker.register("shared", "outer-desc");
1661− yield* execInner.marker.register("shared", "inner-desc");
1582+ yield* execOuter.marker.register("shared", "outer-desc");
1583+ yield* execInner.marker.register("shared", "inner-desc");
16621584
1663− const tools = yield* execInner.tools.list();
1664− const shared = tools.filter((t) => t.id === "shared.t");
1665− expect(shared).toHaveLength(1);
1666− expect(shared[0]?.description).toBe("inner-desc");
1667− }),
1585+ const tools = yield* execInner.tools.list();
1586+ const shared = tools.filter((t) => t.id === "shared.t");
1587+ expect(shared).toHaveLength(1);
1588+ expect(shared[0]?.description).toBe("inner-desc");
1589+ }),
16681590 );
16691591
1670− it.effect(
1671− "sources.list dedupes by id, keeping the innermost row",
1672− () =>
1673− Effect.gen(function* () {
1674− const { execOuter, execInner } = yield* makeMarkerExecutors();
1592+ it.effect("sources.list dedupes by id, keeping the innermost row", () =>
1593+ Effect.gen(function* () {
1594+ const { execOuter, execInner } = yield* makeMarkerExecutors();
16751595
1676− yield* execOuter.marker.register("shared", "outer-name");
1677− yield* execInner.marker.register("shared", "inner-name");
1596+ yield* execOuter.marker.register("shared", "outer-name");
1597+ yield* execInner.marker.register("shared", "inner-name");
16781598
1679− const sources = yield* execInner.sources.list();
1680− const shared = sources.filter((s) => s.id === "shared");
1681− expect(shared).toHaveLength(1);
1682− expect(shared[0]?.name).toBe("inner-name");
1683− }),
1599+ const sources = yield* execInner.sources.list();
1600+ const shared = sources.filter((s) => s.id === "shared");
1601+ expect(shared).toHaveLength(1);
1602+ expect(shared[0]?.name).toBe("inner-name");
1603+ }),
16841604 );
16851605
1686− it.effect(
1687− "tools.definitions dedupes by (source_id, name), keeping the innermost row",
1688− () =>
1689− Effect.gen(function* () {
1690− const { execOuter, execInner } = yield* makeLayeredExecutors();
1691−
1692− // Register inner first, outer second. Without precedence-aware
1693− // dedup, a naive "iterate rows, last-one-wins" map would end up
1694− // keyed to the outer description just because outer was inserted
1695− // into the store last.
1696− yield* execInner["tenant-defs"].addDefs("S", {
1697− Shared: { type: "string", description: "inner" },
1698− });
1699− yield* execOuter["tenant-defs"].addDefs("S", {
1700− Shared: { type: "string", description: "outer" },
1701− });
1606+ it.effect("tools.definitions dedupes by (source_id, name), keeping the innermost row", () =>
1607+ Effect.gen(function* () {
1608+ const { execOuter, execInner } = yield* makeLayeredExecutors();
1609+
1610+ // Register inner first, outer second. Without precedence-aware
1611+ // dedup, a naive "iterate rows, last-one-wins" map would end up
1612+ // keyed to the outer description just because outer was inserted
1613+ // into the store last.
1614+ yield* execInner["tenant-defs"].addDefs("S", {
1615+ Shared: { type: "string", description: "inner" },
1616+ });
1617+ yield* execOuter["tenant-defs"].addDefs("S", {
1618+ Shared: { type: "string", description: "outer" },
1619+ });
17021620
1703− const defs = yield* execInner.tools.definitions();
1704− const shared = defs.S?.Shared as { description?: string } | undefined;
1705− expect(shared?.description).toBe("inner");
1706− }),
1621+ const defs = yield* execInner.tools.definitions();
1622+ const shared = defs.S?.Shared as { description?: string } | undefined;
1623+ expect(shared?.description).toBe("inner");
1624+ }),
17071625 );
17081626
1709− it.effect(
1710− "tools.schema attaches innermost $defs when shadowed across scopes",
1711− () =>
1712− Effect.gen(function* () {
1713− // Source id "S" with a tool that references $defs/Shared, plus a
1714− // definition "Shared" registered at both scopes. Schema's attached
1715− // $defs should come from the inner scope.
1716− const plugins = [
1717− tenantPlugin(),
1718− tenantPluginWithDefs(),
1719− definePlugin(() => ({
1720− id: "ref" as const,
1721− storage: () => ({}),
1722− extension: (ctx) => ({
1723− register: (sourceId: string) =>
1724− ctx.transaction(
1725− ctx.core.sources.register({
1726− id: sourceId,
1727− scope: ctx.scopes[0]!.id,
1728− kind: "ref",
1729− name: sourceId,
1730− canRemove: true,
1731− tools: [
1732− {
1733− name: "use",
1734− description: "uses $defs/Shared",
1735− inputSchema: {
1736− type: "object",
1737− properties: { x: { $ref: "#/$defs/Shared" } },
1738− },
1627+ it.effect("tools.schema attaches innermost $defs when shadowed across scopes", () =>
1628+ Effect.gen(function* () {
1629+ // Source id "S" with a tool that references $defs/Shared, plus a
1630+ // definition "Shared" registered at both scopes. Schema's attached
1631+ // $defs should come from the inner scope.
1632+ const plugins = [
1633+ tenantPlugin(),
1634+ tenantPluginWithDefs(),
1635+ definePlugin(() => ({
1636+ id: "ref" as const,
1637+ storage: () => ({}),
1638+ extension: (ctx) => ({
1639+ register: (sourceId: string) =>
1640+ ctx.transaction(
1641+ ctx.core.sources.register({
1642+ id: sourceId,
1643+ scope: ctx.scopes[0]!.id,
1644+ kind: "ref",
1645+ name: sourceId,
1646+ canRemove: true,
1647+ tools: [
1648+ {
1649+ name: "use",
1650+ description: "uses $defs/Shared",
1651+ inputSchema: {
1652+ type: "object",
1653+ properties: { x: { $ref: "#/$defs/Shared" } },
17391654 },
1740− ],
1741− }),
1742− ),
1743− }),
1744− }))(),
1745− ] as const;
1746− const schema = collectSchemas(plugins);
1747− const adapter = makeMemoryAdapter({ schema });
1748− const blobs = makeInMemoryBlobStore();
1749−
1750− const outerId = ScopeId.make("org");
1751− const innerId = ScopeId.make("user-org:u1:org");
1752− const execOuter = yield* createExecutor({
1753− scopes: [
1754− new Scope({ id: outerId, name: "outer", createdAt: new Date() }),
1755− ],
1756− adapter,
1757− blobs,
1758− plugins,
1759− onElicitation: "accept-all",
1760− });
1761− const execInner = yield* createExecutor({
1762− scopes: [
1763− new Scope({ id: innerId, name: "inner", createdAt: new Date() }),
1764− new Scope({ id: outerId, name: "outer", createdAt: new Date() }),
1765− ],
1766− adapter,
1767− blobs,
1768− plugins,
1769− onElicitation: "accept-all",
1770− });
1655+ },
1656+ ],
1657+ }),
1658+ ),
1659+ }),
1660+ }))(),
1661+ ] as const;
1662+ const schema = collectSchemas(plugins);
1663+ const adapter = makeMemoryAdapter({ schema });
1664+ const blobs = makeInMemoryBlobStore();
17711665
1772− yield* execOuter.ref.register("S");
1773− yield* execInner.ref.register("S");
1666+ const outerId = ScopeId.make("org");
1667+ const innerId = ScopeId.make("user-org:u1:org");
1668+ const execOuter = yield* createExecutor({
1669+ scopes: [new Scope({ id: outerId, name: "outer", createdAt: new Date() })],
1670+ adapter,
1671+ blobs,
1672+ plugins,
1673+ onElicitation: "accept-all",
1674+ });
1675+ const execInner = yield* createExecutor({
1676+ scopes: [
1677+ new Scope({ id: innerId, name: "inner", createdAt: new Date() }),
1678+ new Scope({ id: outerId, name: "outer", createdAt: new Date() }),
1679+ ],
1680+ adapter,
1681+ blobs,
1682+ plugins,
1683+ onElicitation: "accept-all",
1684+ });
17741685
1775− yield* execInner["tenant-defs"].addDefs("S", {
1776− Shared: { type: "string", description: "inner" },
1777− });
1778− yield* execOuter["tenant-defs"].addDefs("S", {
1779− Shared: { type: "string", description: "outer" },
1780− });
1686+ yield* execOuter.ref.register("S");
1687+ yield* execInner.ref.register("S");
17811688
1782− const view = yield* execInner.tools.schema("S.use");
1783− const input = view?.inputSchema as
1784− | { $defs?: { Shared?: { description?: string } } }
1785− | undefined;
1786− expect(input?.$defs?.Shared?.description).toBe("inner");
1787− }),
1689+ yield* execInner["tenant-defs"].addDefs("S", {
1690+ Shared: { type: "string", description: "inner" },
1691+ });
1692+ yield* execOuter["tenant-defs"].addDefs("S", {
1693+ Shared: { type: "string", description: "outer" },
1694+ });
1695+
1696+ const view = yield* execInner.tools.schema("S.use");
1697+ const input = view?.inputSchema as
1698+ | { $defs?: { Shared?: { description?: string } } }
1699+ | undefined;
1700+ expect(input?.$defs?.Shared?.description).toBe("inner");
1701+ }),
17881702 );
17891703 });
packages/core/sdk/src/index.ts+1−5
@@ -121,11 +121,7 @@ export { · 120 unmodified lines
121121 } from "./secret-backed-value";
122122
123123 // Usage tracking — secret/connection refs across plugins
124−export {
125− Usage,
126− type UsagesForSecretInput,
127− type UsagesForConnectionInput,
128−} from "./usages";
124+export { Usage, type UsagesForSecretInput, type UsagesForConnectionInput } from "./usages";
129125
130126 // Connections
131127 export {
packages/core/sdk/src/oauth-discovery.test.ts+34−66
@@ -15,23 +15,19 @@ const DcrRequestBody = Schema.Struct({ · 14 unmodified lines
1515 redirect_uris: Schema.Array(Schema.String),
1616 token_endpoint_auth_method: Schema.String,
1717 });
18−const decodeDcrRequestBody = Schema.decodeUnknownSync(
19− Schema.fromJsonString(DcrRequestBody),
20−);
18+const decodeDcrRequestBody = Schema.decodeUnknownSync(Schema.fromJsonString(DcrRequestBody));
2119
2220 const installFetchRouter = (
2321 handlers: readonly { match: (url: string) => boolean; handle: Handler }[],
2422 ): { calls: Array<{ url: string; init: RequestInit }> } => {
2523 const calls: Array<{ url: string; init: RequestInit }> = [];
26− globalThis.fetch = vi
27− .fn()
28− .mockImplementation(async (url: string, init: RequestInit = {}) => {
29− calls.push({ url, init });
30− for (const h of handlers) {
31− if (h.match(url)) return h.handle(url, init);
32− }
33− return new Response(null, { status: 404 });
34− }) as typeof fetch;
24+ globalThis.fetch = vi.fn().mockImplementation(async (url: string, init: RequestInit = {}) => {
25+ calls.push({ url, init });
26+ for (const h of handlers) {
27+ if (h.match(url)) return h.handle(url, init);
28+ }
29+ return new Response(null, { status: 404 });
30+ }) as typeof fetch;
3531 return { calls };
3632 };
3733
@@ -45,14 +41,11 @@ describe("discoverProtectedResourceMetadata", () => { · 7 unmodified lines
4541 it("fetches RFC 9728 well-known metadata on the resource's origin", async () => {
4642 installFetchRouter([
4743 {
48− match: (u) =>
49− u ===
50− "https://api.example.com/.well-known/oauth-protected-resource/graphql",
44+ match: (u) => u === "https://api.example.com/.well-known/oauth-protected-resource/graphql",
5145 handle: () => new Response(null, { status: 404 }),
5246 },
5347 {
54− match: (u) =>
55− u === "https://api.example.com/.well-known/oauth-protected-resource",
48+ match: (u) => u === "https://api.example.com/.well-known/oauth-protected-resource",
5649 handle: () =>
5750 new Response(
5851 JSON.stringify({
@@ -69,18 +62,14 @@ describe("discoverProtectedResourceMetadata", () => { · 10 unmodified lines
6962 discoverProtectedResourceMetadata("https://api.example.com/graphql"),
7063 );
7164 expect(result).not.toBeNull();
72− expect(result!.metadata.authorization_servers?.[0]).toBe(
73− "https://api.example.com",
74− );
65+ expect(result!.metadata.authorization_servers?.[0]).toBe("https://api.example.com");
7566 expect(result!.metadataUrl).toBe(
7667 "https://api.example.com/.well-known/oauth-protected-resource",
7768 );
7869 });
7970
8071 it("returns null when every well-known candidate 404s", async () => {
81− installFetchRouter([
82− { match: () => true, handle: () => new Response(null, { status: 404 }) },
83− ]);
72+ installFetchRouter([{ match: () => true, handle: () => new Response(null, { status: 404 }) }]);
8473
8574 const result = await Effect.runPromise(
8675 discoverProtectedResourceMetadata("https://api.example.com/graphql"),
@@ -117,13 +106,11 @@ describe("discoverAuthorizationServerMetadata", () => { · 30 unmodified lines
117106 it("falls back to openid-configuration when oauth-authorization-server is absent", async () => {
118107 installFetchRouter([
119108 {
120− match: (u) =>
121− u === "https://as.example.com/.well-known/oauth-authorization-server",
109+ match: (u) => u === "https://as.example.com/.well-known/oauth-authorization-server",
122110 handle: () => new Response(null, { status: 404 }),
123111 },
124112 {
125− match: (u) =>
126− u === "https://as.example.com/.well-known/openid-configuration",
113+ match: (u) => u === "https://as.example.com/.well-known/openid-configuration",
127114 handle: () =>
128115 new Response(
129116 JSON.stringify({
@@ -142,9 +129,7 @@ describe("discoverAuthorizationServerMetadata", () => { · 12 unmodified lines
142129 discoverAuthorizationServerMetadata("https://as.example.com"),
143130 );
144131 expect(result).not.toBeNull();
145− expect(result!.metadata.token_endpoint).toBe(
146− "https://as.example.com/token",
147− );
132+ expect(result!.metadata.token_endpoint).toBe("https://as.example.com/token");
148133 expect(result!.metadataUrl.endsWith("openid-configuration")).toBe(true);
149134 });
150135
@@ -159,9 +144,7 @@ describe("discoverAuthorizationServerMetadata", () => { · 8 unmodified lines
159144 }),
160145 },
161146 ]);
162− const exit = await Effect.runPromiseExit(
163− discoverAuthorizationServerMetadata("https://as"),
164− );
147+ const exit = await Effect.runPromiseExit(discoverAuthorizationServerMetadata("https://as"));
165148 expect(Exit.isFailure(exit)).toBe(true);
166149 });
167150 });
@@ -260,11 +243,13 @@ describe("registerDynamicClient", () => { · 92 unmodified lines
260243 if (!Exit.isFailure(exit)) return;
261244 const reason = exit.cause.reasons.find(Cause.isFailReason);
262245 const error = reason?.error;
263− expect(error).toEqual(expect.objectContaining({
264− _tag: "OAuthDiscoveryError",
265− status: 400,
266− message: expect.stringMatching(/invalid_client_metadata/),
267− }));
246+ expect(error).toEqual(
247+ expect.objectContaining({
248+ _tag: "OAuthDiscoveryError",
249+ status: 400,
250+ message: expect.stringMatching(/invalid_client_metadata/),
251+ }),
252+ );
268253 });
269254 });
270255
@@ -279,14 +264,11 @@ describe("beginDynamicAuthorization", () => { · 8 unmodified lines
279264 installFetchRouter([
280265 {
281266 match: (u) =>
282− u ===
283− "https://backboard.railway.com/.well-known/oauth-protected-resource/graphql/v2",
267+ u === "https://backboard.railway.com/.well-known/oauth-protected-resource/graphql/v2",
284268 handle: () => new Response(null, { status: 404 }),
285269 },
286270 {
287− match: (u) =>
288− u ===
289− "https://backboard.railway.com/.well-known/oauth-protected-resource",
271+ match: (u) => u === "https://backboard.railway.com/.well-known/oauth-protected-resource",
290272 handle: () =>
291273 new Response(
292274 JSON.stringify({
@@ -304,18 +286,14 @@ describe("beginDynamicAuthorization", () => { · 11 unmodified lines
304286 ),
305287 },
306288 {
307− match: (u) =>
308− u ===
309− "https://backboard.railway.com/.well-known/oauth-authorization-server",
289+ match: (u) => u === "https://backboard.railway.com/.well-known/oauth-authorization-server",
310290 handle: () =>
311291 new Response(
312292 JSON.stringify({
313293 issuer: "https://backboard.railway.com",
314− authorization_endpoint:
315− "https://backboard.railway.com/oauth/auth",
294+ authorization_endpoint: "https://backboard.railway.com/oauth/auth",
316295 token_endpoint: "https://backboard.railway.com/oauth/token",
317− registration_endpoint:
318− "https://backboard.railway.com/oauth/register",
296+ registration_endpoint: "https://backboard.railway.com/oauth/register",
319297 scopes_supported: [
320298 "openid",
321299 "profile",
@@ -356,27 +334,19 @@ describe("beginDynamicAuthorization", () => { · 34 unmodified lines
356334 );
357335
358336 const url = new URL(result.authorizationUrl);
359− expect(url.origin + url.pathname).toBe(
360− "https://backboard.railway.com/oauth/auth",
361− );
337+ expect(url.origin + url.pathname).toBe("https://backboard.railway.com/oauth/auth");
362338 expect(url.searchParams.get("client_id")).toBe("dyn-client-42");
363− expect(url.searchParams.get("redirect_uri")).toBe(
364− "https://app.example/cb",
365− );
339+ expect(url.searchParams.get("redirect_uri")).toBe("https://app.example/cb");
366340 expect(url.searchParams.get("response_type")).toBe("code");
367341 expect(url.searchParams.get("code_challenge_method")).toBe("S256");
368342 expect(url.searchParams.get("state")).toBe("state-xyz");
369343 expect(result.codeVerifier.length).toBeGreaterThanOrEqual(43);
370− expect(result.state.authorizationServerUrl).toBe(
371− "https://backboard.railway.com",
372− );
344+ expect(result.state.authorizationServerUrl).toBe("https://backboard.railway.com");
373345 expect(result.state.authorizationServerMetadata.token_endpoint).toBe(
374346 "https://backboard.railway.com/oauth/token",
375347 );
376348 expect(result.state.clientInformation.client_id).toBe("dyn-client-42");
377− expect(result.state.resourceMetadata?.resource).toBe(
378− "https://backboard.railway.com",
379− );
349+ expect(result.state.resourceMetadata?.resource).toBe("https://backboard.railway.com");
380350 });
381351
382352 it("skips discovery + DCR when previousState is provided", async () => {
@@ -422,8 +392,7 @@ describe("beginDynamicAuthorization", () => { · 39 unmodified lines
422392 new Response(
423393 JSON.stringify({
424394 issuer: "https://legacy.example.com",
425− authorization_endpoint:
426− "https://legacy.example.com/authorize",
395+ authorization_endpoint: "https://legacy.example.com/authorize",
427396 token_endpoint: "https://legacy.example.com/token",
428397 code_challenge_methods_supported: ["plain"],
429398 response_types_supported: ["code"],
@@ -454,8 +423,7 @@ describe("beginDynamicAuthorization", () => { · 24 unmodified lines
454423 new Response(
455424 JSON.stringify({
456425 issuer: "https://static.example.com",
457− authorization_endpoint:
458− "https://static.example.com/authorize",
426+ authorization_endpoint: "https://static.example.com/authorize",
459427 token_endpoint: "https://static.example.com/token",
460428 code_challenge_methods_supported: ["S256"],
461429 response_types_supported: ["code"],
packages/core/sdk/src/oauth-helpers.test.ts+10−11
@@ -109,7 +109,10 @@ describe("buildAuthorizationUrl", () => { · 108 unmodified lines
109109
110110 it("preserves pre-existing query params on the authorization URL", () => {
111111 const url = new URL(
112− buildAuthorizationUrl({ ...baseInput, authorizationUrl: "https://example.com/auth?tenant=acme" }),
112+ buildAuthorizationUrl({
113+ ...baseInput,
114+ authorizationUrl: "https://example.com/auth?tenant=acme",
115+ }),
113116 );
114117 expect(url.searchParams.get("tenant")).toBe("acme");
115118 expect(url.searchParams.get("client_id")).toBe("client-123");
@@ -124,12 +127,10 @@ type FetchArgs = { url: string; init: RequestInit }; · 8 unmodified lines
124127
125128 const captureFetch = (response: Response): { calls: FetchArgs[] } => {
126129 const calls: FetchArgs[] = [];
127− globalThis.fetch = vi
128− .fn()
129− .mockImplementation(async (url: string, init: RequestInit) => {
130− calls.push({ url, init });
131− return response;
132− }) as typeof fetch;
130+ globalThis.fetch = vi.fn().mockImplementation(async (url: string, init: RequestInit) => {
131+ calls.push({ url, init });
132+ return response;
133+ }) as typeof fetch;
133134 return { calls };
134135 };
135136
@@ -138,10 +139,8 @@ const originalFetch = globalThis.fetch; · 2 unmodified lines
138139 const jwtPart = (value: unknown): string =>
139140 Buffer.from(JSON.stringify(value)).toString("base64url");
140141
141−const unsignedJwt = (
142− claims: Record<string, unknown>,
143− alg = "RS256",
144−): string => `${jwtPart({ alg, typ: "JWT" })}.${jwtPart(claims)}.sig`;
142+const unsignedJwt = (claims: Record<string, unknown>, alg = "RS256"): string =>
143+ `${jwtPart({ alg, typ: "JWT" })}.${jwtPart(claims)}.sig`;
145144
146145 describe("exchangeAuthorizationCode", () => {
147146 afterEach(() => {
packages/core/sdk/src/oauth-helpers.ts+19−46
@@ -61,8 +61,7 @@ export const OAUTH2_DEFAULT_TIMEOUT_MS = 20_000; · 60 unmodified lines
6161 // PKCE (RFC 7636) — straight delegation to `oauth4webapi`
6262 // ---------------------------------------------------------------------------
6363
64−export const createPkceCodeVerifier = (): string =>
65− oauth.generateRandomCodeVerifier();
64+export const createPkceCodeVerifier = (): string => oauth.generateRandomCodeVerifier();
6665
6766 export const createPkceCodeChallenge = (verifier: string): Promise<string> =>
6867 oauth.calculatePKCECodeChallenge(verifier);
@@ -165,9 +164,7 @@ const asFromTokenUrlAndIssuer = ( · 96 unmodified lines
165164 return options.idTokenSigningAlgValuesSupported
166165 ? {
167166 ...withIssuer,
168− id_token_signing_alg_values_supported: [
169− ...options.idTokenSigningAlgValuesSupported,
170− ],
167+ id_token_signing_alg_values_supported: [...options.idTokenSigningAlgValuesSupported],
171168 }
172169 : withIssuer;
173170 };
@@ -194,9 +191,7 @@ const oauth4webapiRequestOptions = ( · 20 unmodified lines
194191 signal: AbortSignal.timeout(timeoutMs ?? OAUTH2_DEFAULT_TIMEOUT_MS),
195192 };
196193 if (isLoopbackHttpUrl(targetUrl)) {
197− (options as { [oauth.allowInsecureRequests]?: boolean })[
198− oauth.allowInsecureRequests
199− ] = true;
194+ (options as { [oauth.allowInsecureRequests]?: boolean })[oauth.allowInsecureRequests] = true;
200195 }
201196 return options;
202197 };
@@ -211,9 +206,7 @@ const pickClientAuth = ( · 8 unmodified lines
211206 : oauth.ClientSecretPost(clientSecret);
212207 };
213208
214−const tokenResponseFrom = (
215− r: oauth.TokenEndpointResponse,
216−): OAuth2TokenResponse => ({
209+const tokenResponseFrom = (r: oauth.TokenEndpointResponse): OAuth2TokenResponse => ({
217210 access_token: r.access_token,
218211 token_type: r.token_type,
219212 refresh_token: r.refresh_token,
@@ -227,15 +220,14 @@ const tokenResponseFrom = ( · 7 unmodified lines
227220 // its claims against the AS metadata and rejects mismatches we don't care
228221 // about. Strip the field before delegation.
229222 const stripIdToken = async (response: Response): Promise<Response> => {
230− const body = await response.clone().json().then(
231− (value: unknown) => value,
232− () => null,
233− );
234− if (
235− !body ||
236− typeof body !== "object" ||
237− !("id_token" in (body as Record<string, unknown>))
238− ) {
223+ const body = await response
224+ .clone()
225+ .json()
226+ .then(
227+ (value: unknown) => value,
228+ () => null,
229+ );
230+ if (!body || typeof body !== "object" || !("id_token" in (body as Record<string, unknown>))) {
239231 return response;
240232 }
241233 const { id_token: _ignored, ...rest } = body as Record<string, unknown>;
@@ -252,11 +244,7 @@ const processTokenEndpointResponse = async ( · 10 unmodified lines
252244 response: Response,
253245 ): Promise<OAuth2TokenResponse> =>
254246 tokenResponseFrom(
255− await oauth.processGenericTokenEndpointResponse(
256− as,
257− client,
258− await stripIdToken(response),
259− ),
247+ await oauth.processGenericTokenEndpointResponse(as, client, await stripIdToken(response)),
260248 );
261249
262250 // ---------------------------------------------------------------------------
@@ -282,14 +270,10 @@ export const exchangeAuthorizationCode = ( · 19 unmodified lines
282270 Effect.tryPromise({
283271 try: async () => {
284272 const as = asFromTokenUrlAndIssuer(input.tokenUrl, input.issuerUrl, {
285− idTokenSigningAlgValuesSupported:
286− input.idTokenSigningAlgValuesSupported,
273+ idTokenSigningAlgValuesSupported: input.idTokenSigningAlgValuesSupported,
287274 });
288275 const client: oauth.Client = { client_id: input.clientId };
289− const clientAuth = pickClientAuth(
290− input.clientSecret,
291− input.clientAuth ?? "body",
292− );
276+ const clientAuth = pickClientAuth(input.clientSecret, input.clientAuth ?? "body");
293277 // `authorizationCodeGrantRequest` requires its `callbackParameters`
294278 // to have been returned from `validateAuthResponse`. Our public API
295279 // takes the `code` directly (the UI already validated `state` by
@@ -334,10 +318,7 @@ export const exchangeClientCredentials = ( · 38 unmodified lines
334318 try: async () => {
335319 const as = asFromTokenUrl(input.tokenUrl);
336320 const client: oauth.Client = { client_id: input.clientId };
337− const clientAuth = pickClientAuth(
338− input.clientSecret,
339− input.clientAuth ?? "body",
340− );
321+ const clientAuth = pickClientAuth(input.clientSecret, input.clientAuth ?? "body");
341322 const params = new URLSearchParams();
342323 if (input.scopes && input.scopes.length > 0) {
343324 params.set("scope", input.scopes.join(input.scopeSeparator ?? " "));
@@ -349,11 +330,7 @@ export const exchangeClientCredentials = ( · 5 unmodified lines
349330 params,
350331 oauth4webapiRequestOptions(input.tokenUrl, input.timeoutMs),
351332 );
352− const result = await oauth.processClientCredentialsResponse(
353− as,
354− client,
355− response,
356− );
333+ const result = await oauth.processClientCredentialsResponse(as, client, response);
357334 return tokenResponseFrom(result);
358335 },
359336 catch: toOAuth2Error,
@@ -382,14 +359,10 @@ export const refreshAccessToken = ( · 22 unmodified lines
382359 Effect.tryPromise({
383360 try: async () => {
384361 const as = asFromTokenUrlAndIssuer(input.tokenUrl, input.issuerUrl, {
385− idTokenSigningAlgValuesSupported:
386− input.idTokenSigningAlgValuesSupported,
362+ idTokenSigningAlgValuesSupported: input.idTokenSigningAlgValuesSupported,
387363 });
388364 const client: oauth.Client = { client_id: input.clientId };
389− const clientAuth = pickClientAuth(
390− input.clientSecret,
391− input.clientAuth ?? "body",
392− );
365+ const clientAuth = pickClientAuth(input.clientSecret, input.clientAuth ?? "body");
393366 const additionalParameters =
394367 input.scopes && input.scopes.length > 0
395368 ? new URLSearchParams({
packages/core/sdk/src/oauth-popup-types.ts+1−3
@@ -22,9 +22,7 @@ export type OAuthPopupResult<TAuth> = · 21 unmodified lines
2222 readonly error: string;
2323 };
2424
25−export const isOAuthPopupResult = <TAuth>(
26− value: unknown,
27−): value is OAuthPopupResult<TAuth> =>
25+export const isOAuthPopupResult = <TAuth>(value: unknown): value is OAuthPopupResult<TAuth> =>
2826 typeof value === "object" &&
2927 value !== null &&
3028 (value as { type?: unknown }).type === OAUTH_POPUP_MESSAGE_TYPE;
packages/core/sdk/src/oauth.ts+12−29
@@ -69,15 +69,12 @@ export const OAuthAuthorizationCodeStrategy = Schema.Struct({ · 68 unmodified lines
6969 scopeSeparator: Schema.optional(Schema.String),
7070 /** Provider-specific params injected at authorization URL build time
7171 * (Google's `access_type=offline`, `prompt=consent`, ...). */
72− extraAuthorizationParams: Schema.optional(
73− Schema.Record(Schema.String, Schema.String),
74− ),
72+ extraAuthorizationParams: Schema.optional(Schema.Record(Schema.String, Schema.String)),
7573 /** `"body"` (default) sends client creds in the form body; `"basic"`
7674 * uses HTTP Basic auth. Stripe-style servers require basic. */
7775 clientAuth: Schema.optional(Schema.Literals(["body", "basic"])),
7876 });
79−export type OAuthAuthorizationCodeStrategy =
80− typeof OAuthAuthorizationCodeStrategy.Type;
77+export type OAuthAuthorizationCodeStrategy = typeof OAuthAuthorizationCodeStrategy.Type;
8178
8279 /** RFC 6749 §4.4 client credentials — no user redirect, no PKCE. Used
8380 * for server-to-server integrations where the plugin has both
@@ -92,8 +89,7 @@ export const OAuthClientCredentialsStrategy = Schema.Struct({ · 8 unmodified lines
9289 scopeSeparator: Schema.optional(Schema.String),
9390 clientAuth: Schema.optional(Schema.Literals(["body", "basic"])),
9491 });
95−export type OAuthClientCredentialsStrategy =
96− typeof OAuthClientCredentialsStrategy.Type;
92+export type OAuthClientCredentialsStrategy = typeof OAuthClientCredentialsStrategy.Type;
9793
9894 /** Tagged union of every start-time strategy shape. A new strategy (e.g.
9995 * device-code) is added here; the service's start/complete routes on
@@ -121,9 +117,7 @@ export const OAuthProviderState = Schema.Union([ · 21 unmodified lines
121117 issuerUrl: Schema.optional(Schema.NullOr(Schema.String)),
122118 authorizationServerUrl: Schema.optional(Schema.NullOr(Schema.String)),
123119 authorizationServerMetadataUrl: Schema.NullOr(Schema.String),
124− idTokenSigningAlgValuesSupported: Schema.optional(
125− Schema.Array(Schema.String),
126− ),
120+ idTokenSigningAlgValuesSupported: Schema.optional(Schema.Array(Schema.String)),
127121 /** DCR-minted client_id. Embedded inline (not a secret) — DCR
128122 * clients are public-ish by design; the secret part (if the AS
129123 * issued one) is a separate secret row. */
@@ -266,21 +260,15 @@ export interface OAuthCompleteResult { · 136 unmodified lines
266260 // capable plugin group `.addError(OAuthStartError)` etc. and the HTTP
267261 // edge renders them with the annotated status.
268262
269−export class OAuthProbeError extends Schema.TaggedErrorClass<OAuthProbeError>()(
270− "OAuthProbeError",
271− {
272− message: Schema.String,
273− },
274−) {
263+export class OAuthProbeError extends Schema.TaggedErrorClass<OAuthProbeError>()("OAuthProbeError", {
264+ message: Schema.String,
265+}) {
275266 static annotations = { httpApiStatus: 400 };
276267 }
277268
278−export class OAuthStartError extends Schema.TaggedErrorClass<OAuthStartError>()(
279− "OAuthStartError",
280− {
281− message: Schema.String,
282− },
283−) {
269+export class OAuthStartError extends Schema.TaggedErrorClass<OAuthStartError>()("OAuthStartError", {
270+ message: Schema.String,
271+}) {
284272 static annotations = { httpApiStatus: 400 };
285273 }
286274
@@ -312,9 +300,7 @@ export class OAuthSessionNotFoundError extends Schema.TaggedErrorClass<OAuthSess · 25 unmodified lines
312300 // ---------------------------------------------------------------------------
313301
314302 export interface OAuthService {
315− readonly probe: (
316− input: OAuthProbeInput,
317− ) => Effect.Effect<OAuthProbeResult, OAuthProbeError>;
303+ readonly probe: (input: OAuthProbeInput) => Effect.Effect<OAuthProbeResult, OAuthProbeError>;
318304 readonly start: (
319305 input: OAuthStartInput,
320306 ) => Effect.Effect<OAuthStartResult, OAuthStartError | StorageFailure>;
@@ -326,10 +312,7 @@ export interface OAuthService { · 5 unmodified lines
326312 >;
327313 /** Drop an in-flight session without completing — used when the
328314 * user cancels the popup or the source is deleted mid-onboarding. */
329− readonly cancel: (
330− sessionId: string,
331− tokenScope?: string,
332− ) => Effect.Effect<void, StorageFailure>;
315+ readonly cancel: (sessionId: string, tokenScope?: string) => Effect.Effect<void, StorageFailure>;
333316 }
334317
335318 // ---------------------------------------------------------------------------
packages/core/sdk/src/plugin.ts+25−105
@@ -1,11 +1,6 @@
11 import type { Context, Effect, Layer } from "effect";
22 import type { HttpApiGroup } from "effect/unstable/httpapi";
3−import type {
4− DBAdapter,
5− DBSchema,
6− StorageFailure,
7− TypedAdapter,
8−} from "@executor-js/storage-core";
3+import type { DBAdapter, DBSchema, StorageFailure, TypedAdapter } from "@executor-js/storage-core";
94
105 import type { PluginBlobStore } from "./blob";
116 import type {
@@ -15,12 +10,7 @@ import type { · 3 unmodified lines
1510 CreateConnectionInput,
1611 UpdateConnectionTokensInput,
1712 } from "./connections";
18−import type {
19− DefinitionsInput,
20− SourceInput,
21− ToolAnnotations,
22− ToolRow,
23−} from "./core-schema";
13+import type { DefinitionsInput, SourceInput, ToolAnnotations, ToolRow } from "./core-schema";
2414 import type { SourceDetectionResult } from "./types";
2515 import type {
2616 ElicitationDeclinedError,
@@ -70,9 +60,7 @@ export interface StorageDeps<TSchema extends DBSchema | undefined = undefined> { · 43 unmodified lines
7060 * `@executor-js/api` `withCapture`) is the one place that
7161 * translates it to the opaque `InternalError({ traceId })`.
7262 */
73− readonly adapter: TSchema extends DBSchema
74− ? TypedAdapter<TSchema, StorageFailure>
75− : DBAdapter;
63+ readonly adapter: TSchema extends DBSchema ? TypedAdapter<TSchema, StorageFailure> : DBAdapter;
7664 readonly blobs: PluginBlobStore;
7765 }
7866
@@ -114,12 +102,8 @@ export interface PluginCtx<TStore = unknown> { · 35 unmodified lines
114102
115103 readonly core: {
116104 readonly sources: {
117− readonly register: (
118− input: SourceInput,
119− ) => Effect.Effect<void, StorageFailure>;
120− readonly unregister: (
121− sourceId: string,
122− ) => Effect.Effect<void, StorageFailure>;
105+ readonly register: (input: SourceInput) => Effect.Effect<void, StorageFailure>;
106+ readonly unregister: (sourceId: string) => Effect.Effect<void, StorageFailure>;
123107 readonly update: (input: {
124108 readonly id: string;
125109 readonly scope: string;
@@ -134,9 +118,7 @@ export interface PluginCtx<TStore = unknown> { · 8 unmodified lines
134118 * same `ctx.transaction` as `sources.register` for atomicity.
135119 * Replaces any existing defs for the given sourceId. */
136120 readonly definitions: {
137− readonly register: (
138− input: DefinitionsInput,
139− ) => Effect.Effect<void, StorageFailure>;
121+ readonly register: (input: DefinitionsInput) => Effect.Effect<void, StorageFailure>;
140122 };
141123 };
142124
@@ -157,9 +139,7 @@ export interface PluginCtx<TStore = unknown> { · 14 unmodified lines
157139 * `executor.secrets.set` on the host surface, but OAuth2 refresh
158140 * and one-shot token capture from plugin-owned flows need it here
159141 * too. Same routing rules as the host-level setter. */
160− readonly set: (
161− input: SetSecretInput,
162− ) => Effect.Effect<SecretRef, StorageFailure>;
142+ readonly set: (input: SetSecretInput) => Effect.Effect<SecretRef, StorageFailure>;
163143 /** Delete a secret from its pinned provider and the core table.
164144 * Rejects with `SecretOwnedByConnectionError` if the row is owned
165145 * by a connection — callers must go through `connections.remove`
@@ -168,10 +148,7 @@ export interface PluginCtx<TStore = unknown> { · 2 unmodified lines
168148 * the user to detach the listed sources first. */
169149 readonly remove: (
170150 id: string,
171− ) => Effect.Effect<
172− void,
173− SecretOwnedByConnectionError | SecretInUseError | StorageFailure
174− >;
151+ ) => Effect.Effect<void, SecretOwnedByConnectionError | SecretInUseError | StorageFailure>;
175152 };
176153
177154 /** Connections — product-level sign-in state. Owns backing secret
@@ -180,22 +157,14 @@ export interface PluginCtx<TStore = unknown> { · 2 unmodified lines
180157 * fresh token (the SDK handles refresh via the registered provider
181158 * keyed by `connection.provider`). */
182159 readonly connections: {
183− readonly get: (
184− id: string,
185− ) => Effect.Effect<ConnectionRef | null, StorageFailure>;
160+ readonly get: (id: string) => Effect.Effect<ConnectionRef | null, StorageFailure>;
186161 readonly list: () => Effect.Effect<readonly ConnectionRef[], StorageFailure>;
187162 readonly create: (
188163 input: CreateConnectionInput,
189− ) => Effect.Effect<
190− ConnectionRef,
191− ConnectionProviderNotRegisteredError | StorageFailure
192− >;
164+ ) => Effect.Effect<ConnectionRef, ConnectionProviderNotRegisteredError | StorageFailure>;
193165 readonly updateTokens: (
194166 input: UpdateConnectionTokensInput,
195− ) => Effect.Effect<
196− ConnectionRef,
197− ConnectionNotFoundError | StorageFailure
198− >;
167+ ) => Effect.Effect<ConnectionRef, ConnectionNotFoundError | StorageFailure>;
199168 readonly setIdentityLabel: (
200169 id: string,
201170 label: string | null,
@@ -217,9 +186,7 @@ export interface PluginCtx<TStore = unknown> { · 15 unmodified lines
217186 /** Refuses with `ConnectionInUseError` if any plugin reports the
218187 * connection as in use. Caller surfaces the `usages` list to the
219188 * user. */
220− readonly remove: (
221− id: string,
222− ) => Effect.Effect<void, ConnectionInUseError | StorageFailure>;
189+ readonly remove: (id: string) => Effect.Effect<void, ConnectionInUseError | StorageFailure>;
223190 };
224191
225192 /** Shared OAuth service. Plugins use this to probe/start/complete OAuth
@@ -230,9 +197,7 @@ export interface PluginCtx<TStore = unknown> { · 4 unmodified lines
230197 * adapter's transaction method. Use this in extension methods that
231198 * need atomicity across plugin storage writes AND core source/tool
232199 * registration. */
233− readonly transaction: <A, E>(
234− effect: Effect.Effect<A, E>,
235− ) => Effect.Effect<A, E | StorageFailure>;
200+ readonly transaction: <A, E>(effect: Effect.Effect<A, E>) => Effect.Effect<A, E | StorageFailure>;
236201 }
237202
238203 // ---------------------------------------------------------------------------
@@ -265,9 +230,7 @@ export interface StaticToolDecl<TStore = unknown> { · 26 unmodified lines
265230 * Inline because static tools have no plugin storage to resolve from;
266231 * the plugin author literally writes this at definition time. */
267232 readonly annotations?: ToolAnnotations;
268− readonly handler: (
269− input: StaticToolHandlerInput<TStore>,
270− ) => Effect.Effect<unknown, unknown>;
233+ readonly handler: (input: StaticToolHandlerInput<TStore>) => Effect.Effect<unknown, unknown>;
271234 }
272235
273236 export interface StaticSourceDecl<TStore = unknown> {
@@ -388,9 +351,7 @@ export interface PluginSpec< · 114 unmodified lines
388351 * Handlers close over `self` via the closure, so a control tool
389352 * that delegates to the plugin's real API is a one-liner:
390353 * `({ args }) => self.addSpec(args)`. */
391− readonly staticSources?: (
392− self: NoInfer<TExtension>,
393− ) => readonly StaticSourceDecl<TStore>[];
354+ readonly staticSources?: (self: NoInfer<TExtension>) => readonly StaticSourceDecl<TStore>[];
394355
395356 /** HttpApiGroup contributed by this plugin. Composed into the host's
396357 * `HttpApi` via the `addGroup` helper at runtime. The host mounts
@@ -446,9 +407,7 @@ export interface PluginSpec< · 49 unmodified lines
446407 * map doesn't have the toolId. The plugin reads its own enrichment
447408 * via `ctx.storage` and returns the result. Optional — plugins with
448409 * only static tools can omit it. */
449− readonly invokeTool?: (
450− input: InvokeToolInput<TStore>,
451− ) => Effect.Effect<unknown, unknown>;
410+ readonly invokeTool?: (input: InvokeToolInput<TStore>) => Effect.Effect<unknown, unknown>;
452411
453412 /** Bulk resolve annotations (requiresApproval, approvalDescription,
454413 * mayElicit) for a set of tool rows under a single source. Called
@@ -497,13 +456,9 @@ export interface PluginSpec< · 42 unmodified lines
497456 * by this plugin. Plugin-side cleanup only; the executor deletes
498457 * the core source/tool rows after this callback returns, inside
499458 * the same transaction. */
500− readonly removeSource?: (
501− input: SourceLifecycleInput<TStore>,
502− ) => Effect.Effect<void, unknown>;
459+ readonly removeSource?: (input: SourceLifecycleInput<TStore>) => Effect.Effect<void, unknown>;
503460
504− readonly refreshSource?: (
505− input: SourceLifecycleInput<TStore>,
506− ) => Effect.Effect<void, unknown>;
461+ readonly refreshSource?: (input: SourceLifecycleInput<TStore>) => Effect.Effect<void, unknown>;
507462
508463 /** URL autodetection hook. When the user pastes a URL in the
509464 * onboarding UI, `executor.sources.detect(url)` fans out to every
@@ -526,9 +481,7 @@ export interface PluginSpec< · 16 unmodified lines
526481 readonly secretProviders?:
527482 | readonly SecretProvider[]
528483 | ((ctx: PluginCtx<TStore>) => readonly SecretProvider[])
529− | ((
530− ctx: PluginCtx<TStore>,
531− ) => Effect.Effect<readonly SecretProvider[]>);
484+ | ((ctx: PluginCtx<TStore>) => Effect.Effect<readonly SecretProvider[]>);
532485
533486 /** Connection providers contributed by this plugin. Same registration
534487 * shape as `secretProviders`. Each provider's `key` is what
@@ -538,9 +491,7 @@ export interface PluginSpec< · 3 unmodified lines
538491 readonly connectionProviders?:
539492 | readonly ConnectionProvider[]
540493 | ((ctx: PluginCtx<TStore>) => readonly ConnectionProvider[])
541− | ((
542− ctx: PluginCtx<TStore>,
543− ) => Effect.Effect<readonly ConnectionProvider[]>);
494+ | ((ctx: PluginCtx<TStore>) => Effect.Effect<readonly ConnectionProvider[]>);
544495
545496 readonly close?: () => Effect.Effect<void, unknown>;
546497 }
@@ -558,15 +509,7 @@ export interface Plugin< · 11 unmodified lines
558509 // eslint-disable-next-line @typescript-eslint/no-explicit-any
559510 THandlersLayer extends Layer.Layer<any, any, any> = any,
560511 TGroup extends HttpApiGroup.Any = HttpApiGroup.Any,
561−> extends PluginSpec<
562− TId,
563− TExtension,
564− TStore,
565− TSchema,
566− TExtensionService,
567− THandlersLayer,
568− TGroup
569− > {}
512+> extends PluginSpec<TId, TExtension, TStore, TSchema, TExtensionService, THandlersLayer, TGroup> {}
570513
571514 // ---------------------------------------------------------------------------
572515 // definePlugin — factory-returning-spec. Options from the author factory
@@ -589,15 +532,7 @@ export type ConfiguredPlugin< · 16 unmodified lines
589532 options?: TOptions & {
590533 readonly storage?: (deps: StorageDeps<TSchema>) => TStore;
591534 },
592−) => Plugin<
593− TId,
594− TExtension,
595− TStore,
596− TSchema,
597− TExtensionService,
598− THandlersLayer,
599− TGroup
600−>;
535+) => Plugin<TId, TExtension, TStore, TSchema, TExtensionService, THandlersLayer, TGroup>;
601536
602537 // eslint-disable-next-line @typescript-eslint/ban-types
603538 export function definePlugin<
@@ -614,15 +549,7 @@ export function definePlugin< · 10 unmodified lines
614549 >(
615550 authorFactory: (
616551 options?: TOptions,
617− ) => PluginSpec<
618− TId,
619− TExtension,
620− TStore,
621− TSchema,
622− TExtensionService,
623− THandlersLayer,
624− TGroup
625− >,
552+ ) => PluginSpec<TId, TExtension, TStore, TSchema, TExtensionService, THandlersLayer, TGroup>,
626553 ): ConfiguredPlugin<
627554 TId,
628555 TExtension,
@@ -643,9 +570,7 @@ export function definePlugin< · 14 unmodified lines
643570 } = options ?? {};
644571
645572 const hasAuthorOptions = Object.keys(rest).length > 0;
646− const spec = authorFactory(
647− hasAuthorOptions ? (rest as TOptions) : undefined,
648− );
573+ const spec = authorFactory(hasAuthorOptions ? (rest as TOptions) : undefined);
649574
650575 return {
651576 ...spec,
@@ -664,12 +589,7 @@ export function definePlugin< · 12 unmodified lines
664589 export type AnyPlugin = Plugin<string>;
665590
666591 export type PluginExtensions<TPlugins extends readonly AnyPlugin[]> = {
667− readonly [P in TPlugins[number] as P["id"]]: P extends Plugin<
668− string,
669− infer TExt
670− >
671− ? TExt
672− : never;
592+ readonly [P in TPlugins[number] as P["id"]]: P extends Plugin<string, infer TExt> ? TExt : never;
673593 };
674594
675595 /** Lightweight projection of a secret entry as returned by `ctx.secrets.list`. */
packages/core/sdk/src/policies.test.ts+74−83
@@ -5,10 +5,7 @@ import { generateKeyBetween } from "fractional-indexing"; · 4 unmodified lines
55 import type { ToolPolicyRow } from "./core-schema";
66 import { PolicyId } from "./ids";
77 import { createExecutor } from "./executor";
8−import {
9− ElicitationResponse,
10− type ElicitationHandler,
11−} from "./elicitation";
8+import { ElicitationResponse, type ElicitationHandler } from "./elicitation";
129 import {
1310 effectivePolicyFromSorted,
1411 isValidPattern,
@@ -149,13 +146,8 @@ describe("resolveToolPolicy", () => { · 134 unmodified lines
149146 ROW("outer", "vercel.*", "block", "a0", "org"),
150147 ROW("inner", "vercel.dns.create", "approve", "a0", "user"),
151148 ];
152− const rank = (row: Pick<ToolPolicyRow, "scope_id">) =>
153− row.scope_id === "user" ? 0 : 1;
154− const result = resolveToolPolicy(
155− "vercel.dns.create",
156− policies,
157− rank,
158− );
149+ const rank = (row: Pick<ToolPolicyRow, "scope_id">) => (row.scope_id === "user" ? 0 : 1);
150+ const result = resolveToolPolicy("vercel.dns.create", policies, rank);
159151 expect(result?.action).toBe("approve");
160152 expect(result?.policyId).toBe("inner");
161153 });
@@ -167,18 +159,12 @@ describe("resolveToolPolicy", () => { · 5 unmodified lines
167159 // refetch and reorder math sees colliding neighbor keys.
168160 const a = resolveToolPolicy(
169161 "vercel.dns.create",
170− [
171− ROW("z", "vercel.dns.*", "block", "a0"),
172− ROW("a", "vercel.dns.*", "approve", "a0"),
173− ],
162+ [ROW("z", "vercel.dns.*", "block", "a0"), ROW("a", "vercel.dns.*", "approve", "a0")],
174163 flatRank,
175164 );
176165 const b = resolveToolPolicy(
177166 "vercel.dns.create",
178− [
179− ROW("a", "vercel.dns.*", "approve", "a0"),
180− ROW("z", "vercel.dns.*", "block", "a0"),
181− ],
167+ [ROW("a", "vercel.dns.*", "approve", "a0"), ROW("z", "vercel.dns.*", "block", "a0")],
182168 flatRank,
183169 );
184170 // Same input rows in different order, same winner — id "a" sorts
@@ -189,11 +175,11 @@ describe("resolveToolPolicy", () => { · 4 unmodified lines
189175 });
190176
191177 describe("effectivePolicyFromSorted", () => {
192− const POL = (
193− id: string,
194− pattern: string,
195− action: "approve" | "require_approval" | "block",
196− ) => ({ id: PolicyId.make(id), pattern, action });
178+ const POL = (id: string, pattern: string, action: "approve" | "require_approval" | "block") => ({
179+ id: PolicyId.make(id),
180+ pattern,
181+ action,
182+ });
197183
198184 it("returns user policy when one matches", () => {
199185 const result = effectivePolicyFromSorted(
@@ -218,9 +204,10 @@ describe("effectivePolicyFromSorted", () => { · 18 unmodified lines
218204 });
219205
220206 it("falls back to plugin default approve when annotation is false/undefined", () => {
221− expect(
222− effectivePolicyFromSorted("vercel.dns.create", [], false),
223− ).toEqual({ action: "approve", source: "plugin-default" });
207+ expect(effectivePolicyFromSorted("vercel.dns.create", [], false)).toEqual({
208+ action: "approve",
209+ source: "plugin-default",
210+ });
224211 expect(effectivePolicyFromSorted("vercel.dns.create", [])).toEqual({
225212 action: "approve",
226213 source: "plugin-default",
@@ -342,10 +329,7 @@ describe("executor.policies", () => { · 115 unmodified lines
342329 expect(second.position < first.position).toBe(true);
343330
344331 const rules = yield* executor.policies.list();
345− expect(rules.map((r) => r.pattern)).toEqual([
346− "vercel.delete",
347− "vercel.*",
348− ]);
332+ expect(rules.map((r) => r.pattern)).toEqual(["vercel.delete", "vercel.*"]);
349333 }),
350334 );
351335
@@ -425,34 +409,27 @@ describe("executor.policies", () => { · 73 unmodified lines
425409 }),
426410 );
427411
428− it.effect(
429− "consecutive creates produce strictly increasing-precedence keys",
430− () =>
431− Effect.gen(function* () {
432− const executor = yield* setupExecutor();
433− const created: string[] = [];
434− for (const pattern of ["a.*", "b.*", "c.*", "d.*"]) {
435− const row = yield* executor.policies.create({
436− scope: "test-scope",
437− pattern,
438− action: "approve",
439− });
440− created.push(row.position);
441− }
442− // Each new key sorts strictly above the previous (lower lex
443− // order = higher precedence). No collisions.
444− for (let i = 1; i < created.length; i++) {
445− expect(created[i]! < created[i - 1]!).toBe(true);
446− }
447− // List order matches insertion-reverse.
448− const rules = yield* executor.policies.list();
449− expect(rules.map((r) => r.pattern)).toEqual([
450− "d.*",
451− "c.*",
452− "b.*",
453− "a.*",
454− ]);
455− }),
412+ it.effect("consecutive creates produce strictly increasing-precedence keys", () =>
413+ Effect.gen(function* () {
414+ const executor = yield* setupExecutor();
415+ const created: string[] = [];
416+ for (const pattern of ["a.*", "b.*", "c.*", "d.*"]) {
417+ const row = yield* executor.policies.create({
418+ scope: "test-scope",
419+ pattern,
420+ action: "approve",
421+ });
422+ created.push(row.position);
423+ }
424+ // Each new key sorts strictly above the previous (lower lex
425+ // order = higher precedence). No collisions.
426+ for (let i = 1; i < created.length; i++) {
427+ expect(created[i]! < created[i - 1]!).toBe(true);
428+ }
429+ // List order matches insertion-reverse.
430+ const rules = yield* executor.policies.list();
431+ expect(rules.map((r) => r.pattern)).toEqual(["d.*", "c.*", "b.*", "a.*"]);
432+ }),
456433 );
457434
458435 it.effect("remove deletes the row", () =>
@@ -521,14 +498,10 @@ describe("blocked tools", () => { · 62 unmodified lines
521498 pattern: "vercel.*",
522499 action: "block",
523500 });
524− const result = yield* Effect.result(
525− executor.tools.invoke("vercel.delete", {}),
526− );
501+ const result = yield* Effect.result(executor.tools.invoke("vercel.delete", {}));
527502 expect(Result.isFailure(result)).toBe(true);
528503 if (!Result.isFailure(result)) return;
529− expect(Predicate.isTagged("ToolBlockedError")(result.failure)).toBe(
530− true,
531− );
504+ expect(Predicate.isTagged("ToolBlockedError")(result.failure)).toBe(true);
532505 if (!Predicate.isTagged("ToolBlockedError")(result.failure)) return;
533506 expect(result.failure.pattern).toBe("vercel.*");
534507 }),
@@ -545,9 +518,13 @@ describe("approve / require_approval interaction with annotations", () => { · 10 unmodified lines
545518 action: "approve",
546519 });
547520 const calls = { count: 0 };
548− const result = yield* executor.tools.invoke("vercel.delete", {}, {
549− onElicitation: recordingHandler(calls),
550− });
521+ const result = yield* executor.tools.invoke(
522+ "vercel.delete",
523+ {},
524+ {
525+ onElicitation: recordingHandler(calls),
526+ },
527+ );
551528 expect(calls.count).toBe(0);
552529 expect(result).toEqual({ ran: "vercel.delete" });
553530 }),
@@ -562,9 +539,13 @@ describe("approve / require_approval interaction with annotations", () => { · 8 unmodified lines
562539 action: "require_approval",
563540 });
564541 const calls = { count: 0 };
565− yield* executor.tools.invoke("vercel.deploy", {}, {
566− onElicitation: recordingHandler(calls),
567− });
542+ yield* executor.tools.invoke(
543+ "vercel.deploy",
544+ {},
545+ {
546+ onElicitation: recordingHandler(calls),
547+ },
548+ );
568549 expect(calls.count).toBe(1);
569550 }),
570551 );
@@ -578,15 +559,17 @@ describe("approve / require_approval interaction with annotations", () => { · 7 unmodified lines
578559 action: "require_approval",
579560 });
580561 const result = yield* Effect.result(
581− executor.tools.invoke("vercel.deploy", {}, {
582− onElicitation: decliningHandler,
583− }),
562+ executor.tools.invoke(
563+ "vercel.deploy",
564+ {},
565+ {
566+ onElicitation: decliningHandler,
567+ },
568+ ),
584569 );
585570 expect(Result.isFailure(result)).toBe(true);
586571 if (!Result.isFailure(result)) return;
587− expect(
588− Predicate.isTagged("ElicitationDeclinedError")(result.failure),
589− ).toBe(true);
572+ expect(Predicate.isTagged("ElicitationDeclinedError")(result.failure)).toBe(true);
590573 }),
591574 );
592575
@@ -597,16 +580,24 @@ describe("approve / require_approval interaction with annotations", () => { · 4 unmodified lines
597580 // marks any tool whose name contains "delete" as requiring
598581 // approval, so the prompt should fire.
599582 const calls = { count: 0 };
600− yield* executor.tools.invoke("vercel.delete", {}, {
601− onElicitation: recordingHandler(calls),
602− });
583+ yield* executor.tools.invoke(
584+ "vercel.delete",
585+ {},
586+ {
587+ onElicitation: recordingHandler(calls),
588+ },
589+ );
603590 expect(calls.count).toBe(1);
604591
605592 // vercel.deploy has no plugin-required approval and no policy,
606593 // so no prompt.
607− yield* executor.tools.invoke("vercel.deploy", {}, {
608− onElicitation: recordingHandler(calls),
609− });
594+ yield* executor.tools.invoke(
595+ "vercel.deploy",
596+ {},
597+ {
598+ onElicitation: recordingHandler(calls),
599+ },
600+ );
610601 expect(calls.count).toBe(1);
611602 }),
612603 );
packages/core/sdk/src/policies.ts+2−8
@@ -184,9 +184,7 @@ export const resolveToolPolicy = ( · 183 unmodified lines
184184 // policies in evaluation order from the API.
185185 // ---------------------------------------------------------------------------
186186
187−const liftPlugin = (
188− defaultRequiresApproval: boolean | undefined,
189−): EffectivePolicy =>
187+const liftPlugin = (defaultRequiresApproval: boolean | undefined): EffectivePolicy =>
190188 defaultRequiresApproval
191189 ? { action: "require_approval", source: "plugin-default" }
192190 : { action: "approve", source: "plugin-default" };
@@ -245,8 +243,4 @@ export const rowToToolPolicy = (row: ToolPolicyRow): ToolPolicy => ({ · 52 unmodified lines
245243 // inputs with effect/Schema.
246244 // ---------------------------------------------------------------------------
247245
248−export const ToolPolicyActionSchema = Schema.Literals([
249− "approve",
250− "require_approval",
251− "block",
252−]);
246+export const ToolPolicyActionSchema = Schema.Literals(["approve", "require_approval", "block"]);
packages/core/sdk/src/promise-executor.ts+22−33
@@ -40,25 +40,24 @@ import { Scope } from "./scope"; · 39 unmodified lines
4040 // off a returned ref still gets the branded type for use as an opaque token.
4141 // ---------------------------------------------------------------------------
4242
43−type Unbrand<T> = T extends Brand.Brand<string>
44− ? string
45− : T extends readonly (infer U)[]
46− ? readonly Unbrand<U>[]
47− : T extends ReadonlyMap<infer K, infer V>
48− ? ReadonlyMap<Unbrand<K>, Unbrand<V>>
49− : T extends ReadonlySet<infer U>
50− ? ReadonlySet<Unbrand<U>>
51− : T extends Date
52− ? T
53− : T extends (...args: infer A) => infer R
54− ? (...args: { [I in keyof A]: Unbrand<A[I]> }) => Unbrand<R>
55− : T extends object
56− ? { readonly [K in keyof T]: Unbrand<T[K]> }
57− : T;
58−
59−export type Promisified<T> = T extends (
60− ...args: infer A
61−) => Effect.Effect<infer R, infer _E>
43+type Unbrand<T> =
44+ T extends Brand.Brand<string>
45+ ? string
46+ : T extends readonly (infer U)[]
47+ ? readonly Unbrand<U>[]
48+ : T extends ReadonlyMap<infer K, infer V>
49+ ? ReadonlyMap<Unbrand<K>, Unbrand<V>>
50+ : T extends ReadonlySet<infer U>
51+ ? ReadonlySet<Unbrand<U>>
52+ : T extends Date
53+ ? T
54+ : T extends (...args: infer A) => infer R
55+ ? (...args: { [I in keyof A]: Unbrand<A[I]> }) => Unbrand<R>
56+ : T extends object
57+ ? { readonly [K in keyof T]: Unbrand<T[K]> }
58+ : T;
59+
60+export type Promisified<T> = T extends (...args: infer A) => Effect.Effect<infer R, infer _E>
6261 ? (...args: { [I in keyof A]: Unbrand<A[I]> }) => Promise<R>
6362 : T extends readonly unknown[]
6463 ? T
@@ -103,10 +102,7 @@ const isPlainObject = (v: unknown): v is Record<string | symbol, unknown> => · 38 unmodified lines
103102 const promisifyDeep = <T>(value: T): Promisified<T> => {
104103 if (typeof value === "function") {
105104 return ((...args: unknown[]) => {
106− const result = (value as (...a: unknown[]) => unknown).apply(
107− undefined,
108− args,
109− );
105+ const result = (value as (...a: unknown[]) => unknown).apply(undefined, args);
110106 if (Effect.isEffect(result)) {
111107 return Effect.runPromise(result as Effect.Effect<unknown, unknown>);
112108 }
@@ -121,10 +117,7 @@ const promisifyDeep = <T>(value: T): Promisified<T> => { · 8 unmodified lines
121117 const v = Reflect.get(target, prop, receiver);
122118 if (typeof v === "function") {
123119 return (...args: unknown[]) => {
124− const result = (v as (...a: unknown[]) => unknown).apply(
125− target,
126− args,
127− );
120+ const result = (v as (...a: unknown[]) => unknown).apply(target, args);
128121 if (Effect.isEffect(result)) {
129122 return Effect.runPromise(result as Effect.Effect<unknown, unknown>);
130123 }
@@ -143,9 +136,7 @@ const promisifyDeep = <T>(value: T): Promisified<T> => { · 12 unmodified lines
143136 // construct an executor with just `{ plugins: [...] }`.
144137 // ---------------------------------------------------------------------------
145138
146−export const createExecutor = async <
147− const TPlugins extends readonly AnyPlugin[] = [],
148−>(
139+export const createExecutor = async <const TPlugins extends readonly AnyPlugin[] = []>(
149140 config: ExecutorConfig<TPlugins>,
150141 ): Promise<Executor<TPlugins>> => {
151142 const plugins = (config?.plugins ?? []) as TPlugins;
@@ -183,9 +174,7 @@ export const createExecutor = async < · 31 unmodified lines
183174 // get the tagged error as the rejected value. See
184175 // notes/promise-sdk-typed-errors.md for the planned `runPromiseExit`
185176 // rewrite that exposes the full error union to consumers.
186− const effectExecutor = await Effect.runPromise(
187− createEffectExecutor(effectConfig),
188− );
177+ const effectExecutor = await Effect.runPromise(createEffectExecutor(effectConfig));
189178
190179 return promisifyDeep(effectExecutor) as Executor<TPlugins>;
191180 };
packages/core/sdk/src/promise.test.ts+2−4
@@ -33,8 +33,7 @@ const echoPlugin = definePlugin(() => ({ · 32 unmodified lines
3333 },
3434 ],
3535 extension: () => ({
36− greet: (name: string) =>
37− Effect.succeed(`hello, ${name}`) as Effect.Effect<string, never>,
36+ greet: (name: string) => Effect.succeed(`hello, ${name}`) as Effect.Effect<string, never>,
3837 }),
3938 }));
4039
@@ -110,8 +109,7 @@ describe("promise/createExecutor", () => { · 69 unmodified lines
110109 "ap.ctl.go",
111110 {},
112111 {
113− onElicitation: () =>
114− Effect.succeed({ action: "decline" as const }) as any,
112+ onElicitation: () => Effect.succeed({ action: "decline" as const }) as any,
115113 },
116114 ),
117115 ).rejects.toMatchObject({
packages/core/sdk/src/promise.ts+1−5
@@ -51,11 +51,7 @@ export { · 50 unmodified lines
5151
5252 // File-config helper for the CLI. Plain typed-object factory with no
5353 // Effect in its signature, so it's safe to live on the Promise surface.
54−export {
55− defineExecutorConfig,
56− type ExecutorCliConfig,
57− type ExecutorDialect,
58−} from "./config";
54+export { defineExecutorConfig, type ExecutorCliConfig, type ExecutorDialect } from "./config";
5955
6056 // Error tags — Promise callers handle these via .catch().
6157 export {
packages/core/sdk/src/schema-types.ts+0−1
@@ -398,4 +398,3 @@ export const buildToolTypeScriptPreview = (input: { · 397 unmodified lines
398398 : {}),
399399 };
400400 };
401−
packages/core/sdk/src/scoped-adapter.test.ts+2−6
@@ -179,16 +179,12 @@ describe("scopeAdapter — read isolation", () => { · 178 unmodified lines
179179 it.effect("caller-supplied scope_id filter is stripped (can't bypass isolation)", () =>
180180 Effect.gen(function* () {
181181 const inner = makeMemoryAdapter({ schema });
182− yield* typedAdapter<typeof schema>(
183− scopeAdapter(inner, { scopes: ["c"] }, schema),
184− ).create({
182+ yield* typedAdapter<typeof schema>(scopeAdapter(inner, { scopes: ["c"] }, schema)).create({
185183 model: "thing",
186184 data: { id: "t-hidden", scope_id: "c", value: "secret" },
187185 });
188186
189− const reader = typedAdapter<typeof schema>(
190− scopeAdapter(inner, { scopes: ["a"] }, schema),
191− );
187+ const reader = typedAdapter<typeof schema>(scopeAdapter(inner, { scopes: ["a"] }, schema));
192188 // Attempt to bypass by explicitly filtering for scope_id "c".
193189 const rows = yield* reader.findMany({
194190 model: "thing",
packages/core/sdk/src/scoped-adapter.ts+7−34
@@ -49,10 +49,7 @@ const collectScopedModels = (schema: DBSchema): Set<string> => { · 48 unmodified lines
4949 return out;
5050 };
5151
52−const withScopeRead = (
53− where: readonly Where[] | undefined,
54− ctx: ScopeContext,
55−): Where[] => {
52+const withScopeRead = (where: readonly Where[] | undefined, ctx: ScopeContext): Where[] => {
5653 const base = (where ?? []).filter((w) => w.field !== SCOPE_FIELD);
5754 const callerScope = (where ?? []).find((w) => w.field === SCOPE_FIELD);
5855
@@ -123,11 +120,7 @@ const wrapTxMethods = ( · 64 unmodified lines
123120 create: (data) =>
124121 isScoped(data.model)
125122 ? Effect.flatMap(
126− assertScopedWrite(
127− data.model,
128− data.data as Record<string, unknown>,
129− ctx,
130− ),
123+ assertScopedWrite(data.model, data.data as Record<string, unknown>, ctx),
131124 () => inner.create(data),
132125 )
133126 : inner.create(data),
@@ -136,11 +129,7 @@ const wrapTxMethods = ( · 2 unmodified lines
136129 ? Effect.flatMap(
137130 Effect.all(
138131 data.data.map((row) =>
139− assertScopedWrite(
140− data.model,
141− row as Record<string, unknown>,
142− ctx,
143− ),
132+ assertScopedWrite(data.model, row as Record<string, unknown>, ctx),
144133 ),
145134 ),
146135 () => inner.createMany(data),
@@ -168,11 +157,7 @@ const wrapTxMethods = ( · 21 unmodified lines
168157 // rows you can read. That's sufficient for isolation; we
169158 // don't need to force-stamp on update.
170159 (data.update as Record<string, unknown>)[SCOPE_FIELD] !== undefined
171− ? assertScopedWrite(
172− data.model,
173− data.update as Record<string, unknown>,
174− ctx,
175− )
160+ ? assertScopedWrite(data.model, data.update as Record<string, unknown>, ctx)
176161 : Effect.void,
177162 () =>
178163 inner.update({
@@ -185,11 +170,7 @@ const wrapTxMethods = ( · 6 unmodified lines
185170 isScoped(data.model)
186171 ? Effect.flatMap(
187172 (data.update as Record<string, unknown>)[SCOPE_FIELD] !== undefined
188− ? assertScopedWrite(
189− data.model,
190− data.update as Record<string, unknown>,
191− ctx,
192− )
173+ ? assertScopedWrite(data.model, data.update as Record<string, unknown>, ctx)
193174 : Effect.void,
194175 () =>
195176 inner.updateMany({
@@ -209,22 +190,14 @@ const wrapTxMethods = ( · 13 unmodified lines
209190 };
210191 };
211192
212−export const scopeAdapter = (
213− inner: DBAdapter,
214− ctx: ScopeContext,
215− schema: DBSchema,
216−): DBAdapter => {
193+export const scopeAdapter = (inner: DBAdapter, ctx: ScopeContext, schema: DBSchema): DBAdapter => {
217194 const scopedModels = collectScopedModels(schema);
218195 const tx = wrapTxMethods(inner, ctx, scopedModels);
219196 return {
220197 ...tx,
221198 transaction: (callback) =>
222199 inner.transaction((rawTrx) => {
223− const scopedTrx: DBTransactionAdapter = wrapTxMethods(
224− rawTrx,
225− ctx,
226− scopedModels,
227− );
200+ const scopedTrx: DBTransactionAdapter = wrapTxMethods(rawTrx, ctx, scopedModels);
228201 return callback(scopedTrx);
229202 }),
230203 createSchema: inner.createSchema,
packages/core/sdk/src/secrets.ts+5−20
@@ -30,31 +30,18 @@ export interface SecretProvider { · 29 unmodified lines
3030 * failed, etc.) surface as `StorageFailure` — the executor treats
3131 * a provider call the same as a DB call; `StorageError` is captured
3232 * at the HTTP edge to `InternalError`, `UniqueViolationError` dies. */
33− readonly get: (
34− id: string,
35− scope: string,
36− ) => Effect.Effect<string | null, StorageFailure>;
33+ readonly get: (id: string, scope: string) => Effect.Effect<string | null, StorageFailure>;
3734 /** Check whether a provider has a backing value without returning it.
3835 * Providers that can answer this cheaply should implement it so
3936 * stale core routing rows don't appear as selectable secrets. */
40− readonly has?: (
41− id: string,
42− scope: string,
43− ) => Effect.Effect<boolean, StorageFailure>;
37+ readonly has?: (id: string, scope: string) => Effect.Effect<boolean, StorageFailure>;
4438 /** Set a secret value at a named scope. Only called on writable
4539 * providers. Providers that partition by scope use this arg to
4640 * decide where to write; flat providers ignore it. */
47− readonly set?: (
48− id: string,
49− value: string,
50− scope: string,
51− ) => Effect.Effect<void, StorageFailure>;
41+ readonly set?: (id: string, value: string, scope: string) => Effect.Effect<void, StorageFailure>;
5242 /** Delete a secret at a named scope. Only called on writable providers.
5343 * Returns true if something was deleted. */
54− readonly delete?: (
55− id: string,
56− scope: string,
57− ) => Effect.Effect<boolean, StorageFailure>;
44+ readonly delete?: (id: string, scope: string) => Effect.Effect<boolean, StorageFailure>;
5845 /** Enumerate known secret entries. Optional — not all backends can
5946 * enumerate (env-backed providers, for example). */
6047 readonly list?: () => Effect.Effect<
@@ -90,9 +77,7 @@ export class SecretRef extends Schema.Class<SecretRef>("SecretRef")({ · 29 unmodified lines
9077 // OAuth token exchange writes to the innermost per-user scope.
9178 // ---------------------------------------------------------------------------
9279
93−export class SetSecretInput extends Schema.Class<SetSecretInput>(
94− "SetSecretInput",
95−)({
80+export class SetSecretInput extends Schema.Class<SetSecretInput>("SetSecretInput")({
9681 id: SecretId,
9782 /** Scope id to own this secret. Must be one of the executor's
9883 * configured scopes. */
packages/core/sdk/src/testing.ts+8−11
@@ -16,21 +16,18 @@ import { Scope } from "./scope"; · 15 unmodified lines
1616 // need multi-scope behavior can pass `scopes` explicitly.
1717 // ---------------------------------------------------------------------------
1818
19−export const makeTestConfig = <
20− const TPlugins extends readonly AnyPlugin[] = [],
21−>(options?: {
19+export const makeTestConfig = <const TPlugins extends readonly AnyPlugin[] = []>(options?: {
2220 readonly scopeName?: string;
2321 readonly scopes?: readonly Scope[];
2422 readonly plugins?: TPlugins;
2523 }): ExecutorConfig<TPlugins> => {
26− const scopes =
27− options?.scopes ?? [
28− new Scope({
29− id: ScopeId.make("test-scope"),
30− name: options?.scopeName ?? "test",
31− createdAt: new Date(),
32− }),
33− ];
24+ const scopes = options?.scopes ?? [
25+ new Scope({
26+ id: ScopeId.make("test-scope"),
27+ name: options?.scopeName ?? "test",
28+ createdAt: new Date(),
29+ }),
30+ ];
3431
3532 const schema = collectSchemas(options?.plugins ?? []);
3633
packages/core/sdk/src/types.ts+1−3
@@ -65,9 +65,7 @@ export class ToolSchema extends Schema.Class<ToolSchema>("ToolSchema")({ · 64 unmodified lines
6565 outputSchema: Schema.optional(Schema.Unknown),
6666 inputTypeScript: Schema.optional(Schema.String),
6767 outputTypeScript: Schema.optional(Schema.String),
68− typeScriptDefinitions: Schema.optional(
69− Schema.Record(Schema.String, Schema.String),
70− ),
68+ typeScriptDefinitions: Schema.optional(Schema.Record(Schema.String, Schema.String)),
7169 }) {}
7270
7371 // ---------------------------------------------------------------------------
packages/core/storage-core/LICENSE.md+21−20
@@ -6,26 +6,27 @@ Modifications were made to remove authentication-specific types and to · 5 unmodified lines
66 adapt the interfaces to the executor project.
77
88 Vendored files:
9− - src/adapter.ts — ported from packages/core/src/db/adapter/index.ts
10− and types.ts (DBAdapter interface, Where DSL, CustomAdapter,
11− DBAdapterFactoryConfig). Promise → Effect conversion, auth-specific
12− model names stripped.
13− - src/schema.ts — ported from packages/core/src/db/type.ts
14− (DBSchema, DBFieldAttribute, InferDB*).
15− - src/factory.ts — ported from packages/core/src/db/adapter/factory.ts
16− (createAdapterFactory). Promise → Effect. Stripped auth-specific
17− concerns: BetterAuthOptions generic, numeric serial ids, joins,
18− telemetry spans, logger, plural model resolution. Matches our
19− simpler CustomAdapter + DBAdapterFactoryConfig shape.
20− - src/testing/memory.ts — ported from
21− packages/memory-adapter/src/memory-adapter.ts (memoryAdapter).
22− Promise → Effect. Piped through our createAdapter.
23− - storage-drizzle/src/adapter.ts — ported from
24− packages/drizzle-adapter/src/drizzle-adapter.ts (drizzleAdapter).
25− Promise → Effect. Takes an explicit tables map instead of reading
26− db._.fullSchema. Piped through our createAdapter.
27−
28−-------------------------------------------------------------------------------
9+
10+- src/adapter.ts — ported from packages/core/src/db/adapter/index.ts
11+ and types.ts (DBAdapter interface, Where DSL, CustomAdapter,
12+ DBAdapterFactoryConfig). Promise → Effect conversion, auth-specific
13+ model names stripped.
14+- src/schema.ts — ported from packages/core/src/db/type.ts
15+ (DBSchema, DBFieldAttribute, InferDB\*).
16+- src/factory.ts — ported from packages/core/src/db/adapter/factory.ts
17+ (createAdapterFactory). Promise → Effect. Stripped auth-specific
18+ concerns: BetterAuthOptions generic, numeric serial ids, joins,
19+ telemetry spans, logger, plural model resolution. Matches our
20+ simpler CustomAdapter + DBAdapterFactoryConfig shape.
21+- src/testing/memory.ts — ported from
22+ packages/memory-adapter/src/memory-adapter.ts (memoryAdapter).
23+ Promise → Effect. Piped through our createAdapter.
24+- storage-drizzle/src/adapter.ts — ported from
25+ packages/drizzle-adapter/src/drizzle-adapter.ts (drizzleAdapter).
26+ Promise → Effect. Takes an explicit tables map instead of reading
27+ db.\_.fullSchema. Piped through our createAdapter.
28+
29+---
2930
3031 The MIT License (MIT)
3132 Copyright (c) 2024 - present, Bereket Engida
packages/core/storage-core/README.md+1−5
@@ -42,11 +42,7 @@ Or grab typed query helpers for an existing schema — purely a type-level · 41 unmodified lines
4242 view, no runtime cost:
4343
4444 ```ts
45−import {
46− typedAdapter,
47− type DBAdapter,
48− type DBSchema,
49−} from "@executor-js/storage-core";
45+import { typedAdapter, type DBAdapter, type DBSchema } from "@executor-js/storage-core";
5046
5147 const schema = {
5248 secrets: {
packages/core/storage-core/package.json+6−6
@@ -53,6 +53,12 @@ · 52 unmodified lines
5353 "@standard-schema/spec": "^1.0.0",
5454 "effect": "catalog:"
5555 },
56+ "devDependencies": {
57+ "@effect/vitest": "catalog:",
58+ "tsup": "catalog:",
59+ "typescript": "catalog:",
60+ "vitest": "catalog:"
61+ },
5662 "peerDependencies": {
5763 "@effect/vitest": "catalog:",
5864 "vitest": "catalog:"
@@ -64,11 +70,5 @@ · 5 unmodified lines
6470 "vitest": {
6571 "optional": true
6672 }
67− },
68− "devDependencies": {
69− "@effect/vitest": "catalog:",
70− "tsup": "catalog:",
71− "typescript": "catalog:",
72− "vitest": "catalog:"
7373 }
7474 }
packages/core/storage-core/src/adapter.ts+2−7
@@ -192,9 +192,7 @@ export type DBAdapter = { · 191 unmodified lines
192192 * "does my adapter support JSON?") without having to thread the config
193193 * manually. Populated by `createAdapter`.
194194 */
195− options?:
196− | ({ adapterConfig: DBAdapterFactoryConfig } & CustomAdapter["options"])
197− | undefined;
195+ options?: ({ adapterConfig: DBAdapterFactoryConfig } & CustomAdapter["options"]) | undefined;
198196 };
199197
200198 // ---------------------------------------------------------------------------
@@ -252,10 +250,7 @@ export interface CustomAdapter { · 51 unmodified lines
252250 join?: JoinConfig | undefined;
253251 }) => Effect.Effect<T[], StorageFailure>;
254252
255− delete: (data: {
256− model: string;
257− where: CleanedWhere[];
258− }) => Effect.Effect<void, StorageFailure>;
253+ delete: (data: { model: string; where: CleanedWhere[] }) => Effect.Effect<void, StorageFailure>;
259254
260255 deleteMany: (data: {
261256 model: string;
packages/core/storage-core/src/errors.ts+1−3
@@ -35,8 +35,6 @@ export class StorageError extends Data.TaggedError("StorageError")<{ · 34 unmodified lines
3535 * driver message) stays internal because plugin code rarely needs it
3636 * and surfacing it leaks backend specifics.
3737 */
38−export class UniqueViolationError extends Data.TaggedError(
39− "UniqueViolationError",
40−)<{
38+export class UniqueViolationError extends Data.TaggedError("UniqueViolationError")<{
4139 readonly model?: string;
4240 }> {}
packages/core/storage-core/src/memory.test.ts+1−4
@@ -11,10 +11,7 @@ import { Effect } from "effect"; · 10 unmodified lines
1111
1212 import type { DBAdapter } from "./adapter";
1313 import { makeMemoryAdapter } from "./testing/memory";
14−import {
15− conformanceSchema,
16− runAdapterConformance,
17−} from "./testing/conformance";
14+import { conformanceSchema, runAdapterConformance } from "./testing/conformance";
1815
1916 const withAdapter = <A, E>(
2017 fn: (adapter: DBAdapter) => Effect.Effect<A, E>,
packages/core/storage-core/src/schema.ts+10−28
@@ -41,16 +41,13 @@ export type InferDBValueType<T extends DBFieldType> = T extends "string" · 40 unmodified lines
4141 ? T[number]
4242 : never;
4343
44−export type InferDBFieldOutput<T extends DBFieldAttribute> =
45− T["returned"] extends false
46− ? never
47− : T["required"] extends false
48− ? InferDBValueType<T["type"]> | undefined | null
49− : InferDBValueType<T["type"]>;
44+export type InferDBFieldOutput<T extends DBFieldAttribute> = T["returned"] extends false
45+ ? never
46+ : T["required"] extends false
47+ ? InferDBValueType<T["type"]> | undefined | null
48+ : InferDBValueType<T["type"]>;
5049
51−export type InferDBFieldInput<T extends DBFieldAttribute> = InferDBValueType<
52− T["type"]
53−>;
50+export type InferDBFieldInput<T extends DBFieldAttribute> = InferDBValueType<T["type"]>;
5451
5552 export type InferDBFieldsInput<Field> =
5653 Field extends Record<infer Key, DBFieldAttribute>
@@ -70,19 +67,13 @@ export type InferDBFieldsInput<Field> = · 13 unmodified lines
7067 }
7168 : {};
7269
73−export type InferDBFieldsOutput<
74− Fields extends Record<string, DBFieldAttribute>,
75−> =
70+export type InferDBFieldsOutput<Fields extends Record<string, DBFieldAttribute>> =
7671 Fields extends Record<infer Key, DBFieldAttribute>
7772 ? {
7873 [K in Key as Fields[K]["returned"] extends false
7974 ? never
8075 : Fields[K]["required"] extends false
81− ? Fields[K]["defaultValue"] extends
82− | boolean
83− | string
84− | number
85− | Date
76+ ? Fields[K]["defaultValue"] extends boolean | string | number | Date
8677 ? K
8778 : never
8879 : K]: InferDBFieldOutput<Fields[K]>;
@@ -90,11 +81,7 @@ export type InferDBFieldsOutput< · 1 unmodified line
9081 [K in Key as Fields[K]["returned"] extends false
9182 ? never
9283 : Fields[K]["required"] extends false
93− ? Fields[K]["defaultValue"] extends
94− | boolean
95− | string
96− | number
97− | Date
84+ ? Fields[K]["defaultValue"] extends boolean | string | number | Date
9885 ? never
9986 : K
10087 : never]?: InferDBFieldOutput<Fields[K]> | null;
@@ -131,12 +118,7 @@ export type DBFieldAttributeConfig = { · 30 unmodified lines
131118 model: string;
132119 field: string;
133120 /** @default "cascade" */
134− onDelete?:
135− | "no action"
136− | "restrict"
137− | "cascade"
138− | "set null"
139− | "set default";
121+ onDelete?: "no action" | "restrict" | "cascade" | "set null" | "set default";
140122 }
141123 | undefined;
142124 unique?: boolean | undefined;
packages/core/storage-core/src/typed.ts+4−14
@@ -18,20 +18,12 @@ · 17 unmodified lines
1818 import type { Effect } from "effect";
1919
2020 import type { DBAdapter, StorageFailure, Where } from "./adapter";
21−import type {
22− DBSchema,
23− InferDBFieldsInput,
24− InferDBFieldsOutput,
25−} from "./schema";
21+import type { DBSchema, InferDBFieldsInput, InferDBFieldsOutput } from "./schema";
2622
27−type RowInput<S extends DBSchema, M extends keyof S> = InferDBFieldsInput<
28− S[M]["fields"]
29−> &
23+type RowInput<S extends DBSchema, M extends keyof S> = InferDBFieldsInput<S[M]["fields"]> &
3024 Record<string, unknown>;
3125
32−type RowOutput<S extends DBSchema, M extends keyof S> = InferDBFieldsOutput<
33− S[M]["fields"]
34−> &
26+type RowOutput<S extends DBSchema, M extends keyof S> = InferDBFieldsOutput<S[M]["fields"]> &
3527 Record<string, unknown>;
3628
3729 /**
@@ -103,9 +95,7 @@ export interface TypedAdapter<S extends DBSchema, E = StorageFailure> { · 65 unmodified lines
10395 * this is a typed re-export of the adapter's methods. Pass the schema as
10496 * a type parameter; the adapter argument is the normal untyped one.
10597 */
106−export const typedAdapter = <S extends DBSchema>(
107− adapter: DBAdapter,
108−): TypedAdapter<S> => ({
98+export const typedAdapter = <S extends DBSchema>(adapter: DBAdapter): TypedAdapter<S> => ({
10999 raw: adapter,
110100 create: adapter.create as TypedAdapter<S>["create"],
111101 createMany: adapter.createMany as TypedAdapter<S>["createMany"],
packages/core/storage-drizzle/src/adapter.test.ts+4−11
@@ -10,8 +10,7 @@ import { StorageError, UniqueViolationError } from "@executor-js/storage-core"; · 9 unmodified lines
1010
1111 import { isTransientStorageError } from "./adapter";
1212
13−const transientMsg =
14− "[storage-drizzle] findOne select failed: Network connection lost.";
13+const transientMsg = "[storage-drizzle] findOne select failed: Network connection lost.";
1514
1615 describe("isTransientStorageError", () => {
1716 it.for([
@@ -20,9 +19,7 @@ describe("isTransientStorageError", () => { · 2 unmodified lines
2019 "[storage-drizzle] insert returning failed: ECONNRESET",
2120 "[storage-drizzle] findMany select failed: Connection terminated unexpectedly",
2221 ])("returns true for %s", (message) => {
23− expect(
24− isTransientStorageError(new StorageError({ message, cause: null })),
25− ).toBe(true);
22+ expect(isTransientStorageError(new StorageError({ message, cause: null }))).toBe(true);
2623 });
2724
2825 it("returns false for unique violation", () => {
@@ -52,9 +49,7 @@ describe("transient retry policy", () => { · 23 unmodified lines
5249 const result = yield* Effect.suspend(() => {
5350 calls++;
5451 if (calls < 3) {
55− return Effect.fail(
56− new StorageError({ message: transientMsg, cause: null }),
57− );
52+ return Effect.fail(new StorageError({ message: transientMsg, cause: null }));
5853 }
5954 return Effect.succeed("ok");
6055 }).pipe(Effect.retry(retryPolicy));
@@ -70,9 +65,7 @@ describe("transient retry policy", () => { · 9 unmodified lines
7065 const exit = yield* Effect.exit(
7166 Effect.suspend(() => {
7267 calls++;
73− return Effect.fail(
74− new StorageError({ message: transientMsg, cause: null }),
75− );
68+ return Effect.fail(new StorageError({ message: transientMsg, cause: null }));
7669 }).pipe(Effect.retry(retryPolicy)),
7770 );
7871
packages/core/storage-file/src/adapter.ts+2−6
@@ -28,16 +28,12 @@ export interface MakeSqliteAdapterOptions { · 27 unmodified lines
2828 readonly generateId?: () => string;
2929 }
3030
31−export const makeSqliteAdapter = (
32− options: MakeSqliteAdapterOptions,
33−): DBAdapter =>
31+export const makeSqliteAdapter = (options: MakeSqliteAdapterOptions): DBAdapter =>
3432 drizzleAdapter({
3533 db: options.db,
3634 schema: options.schema,
3735 provider: "sqlite",
3836 adapterId: options.adapterId ?? "sqlite",
3937 supportsTransaction: true,
40− customIdGenerator: options.generateId
41− ? () => options.generateId!()
42− : undefined,
38+ customIdGenerator: options.generateId ? () => options.generateId!() : undefined,
4339 });
packages/core/storage-file/src/blob-store.ts+5−27
@@ -41,21 +41,14 @@ const wrapErr = · 40 unmodified lines
4141 cause,
4242 });
4343
44−export const makeSqliteBlobStore = (
45− options: MakeSqliteBlobStoreOptions,
46−): BlobStore => ({
44+export const makeSqliteBlobStore = (options: MakeSqliteBlobStoreOptions): BlobStore => ({
4745 get: (namespace, key) =>
4846 Effect.try({
4947 try: () =>
5048 options.db
5149 .select({ value: blobTable.value })
5250 .from(blobTable)
53− .where(
54− and(
55− eq(blobTable.namespace, namespace),
56− eq(blobTable.key, key),
57− ),
58− )
51+ .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key)))
5952 .limit(1)
6053 .all() as ReadonlyArray<{ value: string }>,
6154 catch: wrapErr("get"),
@@ -72,12 +65,7 @@ export const makeSqliteBlobStore = ( · 10 unmodified lines
7265 value: blobTable.value,
7366 })
7467 .from(blobTable)
75− .where(
76− and(
77− inArray(blobTable.namespace, [...namespaces]),
78− eq(blobTable.key, key),
79− ),
80− )
68+ .where(and(inArray(blobTable.namespace, [...namespaces]), eq(blobTable.key, key)))
8169 .all() as ReadonlyArray<{ namespace: string; value: string }>,
8270 catch: wrapErr("getMany"),
8371 }).pipe(
@@ -107,12 +95,7 @@ export const makeSqliteBlobStore = ( · 23 unmodified lines
10795 try: () =>
10896 options.db
10997 .delete(blobTable)
110− .where(
111− and(
112− eq(blobTable.namespace, namespace),
113− eq(blobTable.key, key),
114− ),
115− )
98+ .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key)))
11699 .run(),
117100 catch: wrapErr("delete"),
118101 }).pipe(Effect.asVoid),
@@ -123,12 +106,7 @@ export const makeSqliteBlobStore = ( · 4 unmodified lines
123106 options.db
124107 .select({ one: drizzleSql<number>`1`.as("one") })
125108 .from(blobTable)
126− .where(
127− and(
128− eq(blobTable.namespace, namespace),
129− eq(blobTable.key, key),
130− ),
131− )
109+ .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key)))
132110 .limit(1)
133111 .all() as ReadonlyArray<{ one: number }>,
134112 catch: wrapErr("has"),
packages/core/storage-file/src/index.test.ts+3−16
@@ -2,19 +2,10 @@ import { Effect } from "effect"; · 1 unmodified line
22 import Database from "better-sqlite3";
33 import { drizzle } from "drizzle-orm/better-sqlite3";
44 import { relations, sql } from "drizzle-orm";
5−import {
6− sqliteTable,
7− text,
8− real,
9− integer,
10− getTableConfig,
11−} from "drizzle-orm/sqlite-core";
5+import { sqliteTable, text, real, integer, getTableConfig } from "drizzle-orm/sqlite-core";
126
137 import type { DBAdapter } from "@executor-js/storage-core";
14−import {
15− conformanceSchema,
16− runAdapterConformance,
17−} from "@executor-js/storage-core/testing";
8+import { conformanceSchema, runAdapterConformance } from "@executor-js/storage-core/testing";
189
1910 import { makeSqliteAdapter } from "./index";
2011
@@ -91,11 +82,7 @@ const bootstrapTables = ( · 70 unmodified lines
9182 if (col.isUnique) parts.push("UNIQUE");
9283 return parts.join(" ");
9384 });
94− db.run(
95− sql.raw(
96− `CREATE TABLE IF NOT EXISTS "${config.name}" (${cols.join(", ")})`,
97− ),
98− );
85+ db.run(sql.raw(`CREATE TABLE IF NOT EXISTS "${config.name}" (${cols.join(", ")})`));
9986 } catch {
10087 // Not a table (e.g. relations) — skip
10188 }
packages/core/storage-postgres/src/adapter.ts+2−6
@@ -25,16 +25,12 @@ export interface MakePostgresAdapterOptions { · 24 unmodified lines
2525 readonly generateId?: () => string;
2626 }
2727
28−export const makePostgresAdapter = (
29− options: MakePostgresAdapterOptions,
30−): DBAdapter =>
28+export const makePostgresAdapter = (options: MakePostgresAdapterOptions): DBAdapter =>
3129 drizzleAdapter({
3230 db: options.db,
3331 schema: options.schema,
3432 provider: "pg",
3533 adapterId: options.adapterId ?? "postgres",
3634 supportsTransaction: true,
37− customIdGenerator: options.generateId
38− ? () => options.generateId!()
39− : undefined,
35+ customIdGenerator: options.generateId ? () => options.generateId!() : undefined,
4036 });
packages/core/storage-postgres/src/blob-store.ts+5−27
@@ -42,21 +42,14 @@ const wrapErr = · 41 unmodified lines
4242 cause,
4343 });
4444
45−export const makePostgresBlobStore = (
46− options: MakePostgresBlobStoreOptions,
47−): BlobStore => ({
45+export const makePostgresBlobStore = (options: MakePostgresBlobStoreOptions): BlobStore => ({
4846 get: (namespace, key) =>
4947 Effect.tryPromise({
5048 try: async () => {
5149 const rows = await options.db
5250 .select({ value: blobTable.value })
5351 .from(blobTable)
54− .where(
55− and(
56− eq(blobTable.namespace, namespace),
57− eq(blobTable.key, key),
58− ),
59− )
52+ .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key)))
6053 .limit(1);
6154 return rows[0]?.value ?? null;
6255 },
@@ -73,12 +66,7 @@ export const makePostgresBlobStore = ( · 10 unmodified lines
7366 value: blobTable.value,
7467 })
7568 .from(blobTable)
76− .where(
77− and(
78− inArray(blobTable.namespace, [...namespaces]),
79− eq(blobTable.key, key),
80− ),
81− );
69+ .where(and(inArray(blobTable.namespace, [...namespaces]), eq(blobTable.key, key)));
8270 const out = new Map<string, string>();
8371 for (const row of rows) out.set(row.namespace, row.value);
8472 return out as ReadonlyMap<string, string>;
@@ -103,12 +91,7 @@ export const makePostgresBlobStore = ( · 18 unmodified lines
10391 try: async () => {
10492 await options.db
10593 .delete(blobTable)
106− .where(
107− and(
108− eq(blobTable.namespace, namespace),
109− eq(blobTable.key, key),
110− ),
111− );
94+ .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key)));
11295 },
11396 catch: wrapErr("delete"),
11497 }),
@@ -118,12 +101,7 @@ export const makePostgresBlobStore = ( · 3 unmodified lines
118101 const rows = await options.db
119102 .select({ key: blobTable.key })
120103 .from(blobTable)
121− .where(
122− and(
123− eq(blobTable.namespace, namespace),
124− eq(blobTable.key, key),
125− ),
126− )
104+ .where(and(eq(blobTable.namespace, namespace), eq(blobTable.key, key)))
127105 .limit(1);
128106 return rows.length > 0;
129107 },
packages/core/storage-postgres/src/index.test.ts+3−8
@@ -22,18 +22,13 @@ import { · 21 unmodified lines
2222 } from "drizzle-orm/pg-core";
2323
2424 import type { DBAdapter, StorageFailure } from "@executor-js/storage-core";
25−import {
26− conformanceSchema,
27− runAdapterConformance,
28−} from "@executor-js/storage-core/testing";
25+import { conformanceSchema, runAdapterConformance } from "@executor-js/storage-core/testing";
2926
3027 import { makePostgresAdapter } from "./index";
3128
3229 const url = "postgresql://postgres:postgres@127.0.0.1:5435/postgres";
3330
34−class PostgresTestDatabaseError extends Data.TaggedError(
35− "PostgresTestDatabaseError",
36−)<{
31+class PostgresTestDatabaseError extends Data.TaggedError("PostgresTestDatabaseError")<{
3732 readonly message: string;
3833 readonly cause: unknown;
3934 }> {}
@@ -153,7 +148,7 @@ const resetTables = Effect.gen(function* () { · 113 unmodified lines
153148 try: () =>
154149 sql`DROP TABLE IF EXISTS "source", "tag", "source_tag", "with_defaults", "blob" CASCADE`.then(
155150 () => undefined,
156− ),
151+ ),
157152 catch: (cause) =>
158153 new PostgresTestDatabaseError({
159154 message: "failed to reset postgres conformance tables",
packages/core/storage-postgres/src/index.ts+1−4
@@ -5,9 +5,6 @@ · 4 unmodified lines
55 // wrapper around @executor-js/storage-drizzle.
66 // ---------------------------------------------------------------------------
77
8−export {
9− makePostgresAdapter,
10− type MakePostgresAdapterOptions,
11−} from "./adapter";
8+export { makePostgresAdapter, type MakePostgresAdapterOptions } from "./adapter";
129
1310 export { makePostgresBlobStore, blobTable } from "./blob-store";
packages/core/vite-plugin/package.json+3−3
@@ -44,9 +44,6 @@ · 43 unmodified lines
4444 "jiti": "^2.6.1",
4545 "jsonc-parser": "^3.3.1"
4646 },
47− "peerDependencies": {
48− "vite": "catalog:"
49− },
5047 "devDependencies": {
5148 "@effect/vitest": "catalog:",
5249 "@types/node": "catalog:",
@@ -54,5 +51,8 @@ · 1 unmodified line
5451 "typescript": "catalog:",
5552 "vite": "catalog:",
5653 "vitest": "catalog:"
54+ },
55+ "peerDependencies": {
56+ "vite": "catalog:"
5757 }
5858 }
packages/core/vite-plugin/src/index.ts+8−23
@@ -56,10 +56,7 @@ const readJsoncPlugins = (path: string): readonly string[] => { · 55 unmodified lines
5656 }
5757 };
5858
59−const tryResolveClient = (
60− packageName: string,
61− fromDir: string,
62−): string | null => {
59+const tryResolveClient = (packageName: string, fromDir: string): string | null => {
6360 const require = createRequire(resolvePath(fromDir, "_anchor.js"));
6461 try {
6562 return require.resolve(`${packageName}/client`);
@@ -83,9 +80,7 @@ interface ExecutorVitePluginOptions { · 17 unmodified lines
8380 readonly jsoncPath?: string;
8481 }
8582
86−export default function executorVitePlugin(
87− options: ExecutorVitePluginOptions = {},
88−): Plugin {
83+export default function executorVitePlugin(options: ExecutorVitePluginOptions = {}): Plugin {
8984 let projectRoot: string = process.cwd();
9085 let resolvedConfigPath: string | null = null;
9186 let resolvedJsoncPath: string | null = null;
@@ -93,13 +88,9 @@ export default function executorVitePlugin( · 1 unmodified line
9388
9489 const resolveConfigPath = (): string | null => {
9590 if (resolvedConfigPath) return resolvedConfigPath;
96− const candidates = options.configPath
97− ? [options.configPath]
98− : DEFAULT_CONFIG_CANDIDATES;
91+ const candidates = options.configPath ? [options.configPath] : DEFAULT_CONFIG_CANDIDATES;
9992 for (const candidate of candidates) {
100− const abs = isAbsolute(candidate)
101− ? candidate
102− : resolvePath(projectRoot, candidate);
93+ const abs = isAbsolute(candidate) ? candidate : resolvePath(projectRoot, candidate);
10394 if (existsSync(abs)) {
10495 resolvedConfigPath = abs;
10596 return abs;
@@ -110,13 +101,9 @@ export default function executorVitePlugin( · 4 unmodified lines
110101
111102 const resolveJsoncPath = (): string | null => {
112103 if (resolvedJsoncPath) return resolvedJsoncPath;
113− const candidates = options.jsoncPath
114− ? [options.jsoncPath]
115− : DEFAULT_JSONC_CANDIDATES;
104+ const candidates = options.jsoncPath ? [options.jsoncPath] : DEFAULT_JSONC_CANDIDATES;
116105 for (const candidate of candidates) {
117− const abs = isAbsolute(candidate)
118− ? candidate
119− : resolvePath(projectRoot, candidate);
106+ const abs = isAbsolute(candidate) ? candidate : resolvePath(projectRoot, candidate);
120107 if (existsSync(abs)) {
121108 resolvedJsoncPath = abs;
122109 return abs;
@@ -212,8 +199,7 @@ export default function executorVitePlugin( · 89 unmodified lines
212199 }
213200
214201 cachedSource =
215− `${lines.join("\n")}\n` +
216− `export const plugins = [${exportExpressions.join(", ")}];\n`;
202+ `${lines.join("\n")}\n` + `export const plugins = [${exportExpressions.join(", ")}];\n`;
217203 return cachedSource;
218204 };
219205
@@ -235,8 +221,7 @@ export default function executorVitePlugin( · 15 unmodified lines
235221 const configPath = resolveConfigPath();
236222 const jsoncPath = resolveJsoncPath();
237223 const isWatched =
238− (configPath && ctx.file === configPath) ||
239− (jsoncPath && ctx.file === jsoncPath);
224+ (configPath && ctx.file === configPath) || (jsoncPath && ctx.file === jsoncPath);
240225 if (!isWatched) return undefined;
241226 cachedSource = null;
242227 const mod = ctx.server.moduleGraph.getModuleById(RESOLVED_ID);
packages/hosts/mcp/src/stdio-integration.test.ts+35−33
@@ -11,39 +11,41 @@ const cliEntry = resolve(repoRoot, "apps/cli/src/main.ts"); · 10 unmodified lines
1111 const testScope = resolve(repoRoot, "apps/local");
1212
1313 describe("MCP stdio integration", () => {
14− it.effect("execute tool returns result over stdio transport", () =>
15− Effect.gen(function* () {
16− // Fresh temp dir so the test doesn't migrate against the developer's
17− // real ~/.executor/data.db.
18− const dataDir = mkdtempSync(join(tmpdir(), "executor-mcp-test-"));
19−
20− const transport = new StdioClientTransport({
21− command: "bun",
22− args: ["run", cliEntry, "mcp", "--scope", testScope],
23− env: { ...process.env, EXECUTOR_DATA_DIR: dataDir },
24− });
25−
26− const client = new Client({ name: "test-client", version: "1.0.0" }, { capabilities: {} });
27−
28− yield* Effect.acquireRelease(
29− Effect.promise(() => client.connect(transport)),
30− () => Effect.promise(() => transport.close()),
31− );
32−
33− const { tools } = yield* Effect.promise(() => client.listTools());
34− expect(tools.map((t) => t.name)).toContain("execute");
35−
36− const result = yield* Effect.promise(() =>
37− client.callTool({
38− name: "execute",
39− arguments: { code: "return 2+2" },
40− }),
41− );
42−
43− const text = (result.content as Array<{ type: string; text: string }>)[0]?.text;
44− expect(text).toContain("4");
45− expect(result.isError).toBeFalsy();
46− }).pipe(Effect.scoped),
14+ it.effect(
15+ "execute tool returns result over stdio transport",
16+ () =>
17+ Effect.gen(function* () {
18+ // Fresh temp dir so the test doesn't migrate against the developer's
19+ // real ~/.executor/data.db.
20+ const dataDir = mkdtempSync(join(tmpdir(), "executor-mcp-test-"));
21+
22+ const transport = new StdioClientTransport({
23+ command: "bun",
24+ args: ["run", cliEntry, "mcp", "--scope", testScope],
25+ env: { ...process.env, EXECUTOR_DATA_DIR: dataDir },
26+ });
27+
28+ const client = new Client({ name: "test-client", version: "1.0.0" }, { capabilities: {} });
29+
30+ yield* Effect.acquireRelease(
31+ Effect.promise(() => client.connect(transport)),
32+ () => Effect.promise(() => transport.close()),
33+ );
34+
35+ const { tools } = yield* Effect.promise(() => client.listTools());
36+ expect(tools.map((t) => t.name)).toContain("execute");
37+
38+ const result = yield* Effect.promise(() =>
39+ client.callTool({
40+ name: "execute",
41+ arguments: { code: "return 2+2" },
42+ }),
43+ );
44+
45+ const text = (result.content as Array<{ type: string; text: string }>)[0]?.text;
46+ expect(text).toContain("4");
47+ expect(result.isError).toBeFalsy();
48+ }).pipe(Effect.scoped),
4749 { timeout: 30_000 },
4850 );
4951 });
packages/kernel/core/README.md+1−5
@@ -17,11 +17,7 @@ npm install @executor-js/codemode-core · 16 unmodified lines
1717 Implement a runtime that satisfies `CodeExecutor`:
1818
1919 ```ts
20−import type {
21− CodeExecutor,
22− ExecuteResult,
23− SandboxToolInvoker,
24−} from "@executor-js/codemode-core";
20+import type { CodeExecutor, ExecuteResult, SandboxToolInvoker } from "@executor-js/codemode-core";
2521 import { Effect } from "effect";
2622
2723 export const makeMyRuntime = (): CodeExecutor => ({
packages/kernel/core/src/code-recovery.test.ts+6−13
@@ -57,7 +57,9 @@ describe("recoverExecutionBody", () => { · 56 unmodified lines
5757 });
5858
5959 it("strips markdown code fences", () => {
60− expect(recoverExecutionBody("```js\nasync () => 42\n```")).toBe(wrapCallableBody("async () => 42"));
60+ expect(recoverExecutionBody("```js\nasync () => 42\n```")).toBe(
61+ wrapCallableBody("async () => 42"),
62+ );
6163 });
6264
6365 it("strips typescript fences", () => {
@@ -121,13 +123,7 @@ describe("recoverExecutionBody", () => { · 57 unmodified lines
121123 });
122124
123125 it("extracts code from prose wrapped fences", () => {
124− const code = [
125− "Sure, here's the code:",
126− "",
127− "```ts",
128− "async () => 42",
129− "```",
130− ].join("\n");
126+ const code = ["Sure, here's the code:", "", "```ts", "async () => 42", "```"].join("\n");
131127
132128 expect(recoverExecutionBody(code)).toBe(wrapCallableBody("async () => 42"));
133129 });
@@ -142,10 +138,7 @@ describe("recoverExecutionBody", () => { · 8 unmodified lines
142138 });
143139
144140 it("handles block comments before export default", () => {
145− const code = [
146− "/* generated by the model */",
147− "export default async () => 42",
148− ].join("\n");
141+ const code = ["/* generated by the model */", "export default async () => 42"].join("\n");
149142
150143 expect(recoverExecutionBody(code)).toBe(wrapCallableBody("async () => 42"));
151144 });
@@ -199,7 +192,7 @@ describe("recoverExecutionBody", () => { · 47 unmodified lines
199192 " return { count: matches.length, top };",
200193 "} catch (error) {",
201194 " return {",
202− ' error: error instanceof Error ? error.message : String(error),',
195+ " error: error instanceof Error ? error.message : String(error),",
203196 " query,",
204197 " };",
205198 "}",
packages/kernel/core/src/code-recovery.ts+14−10
@@ -1,8 +1,7 @@
11 import { parse } from "@babel/parser";
22
33 const FENCED_CODE_BLOCK = /```(?:[^\n`]*)?\s*\n([\s\S]*?)```/i;
4−const FUNCTION_DECLARATION =
5− /^(?:async\s+)?function(?:\s+([a-zA-Z_$][a-zA-Z0-9_$]*))?\s*\(/;
4+const FUNCTION_DECLARATION = /^(?:async\s+)?function(?:\s+([a-zA-Z_$][a-zA-Z0-9_$]*))?\s*\(/;
65 const CALLABLE_ERROR = "Code must evaluate to a function";
76
87 const extractCandidateSource = (code: string): string => {
@@ -47,7 +46,9 @@ const unwrapExpression = (expression: { type: string; expression?: unknown }): u · 38 unmodified lines
4746 case "TSTypeAssertion":
4847 case "TSNonNullExpression":
4948 case "TSInstantiationExpression":
50− return expression.expression ? unwrapExpression(expression.expression as { type: string }) : expression;
49+ return expression.expression
50+ ? unwrapExpression(expression.expression as { type: string })
51+ : expression;
5152 default:
5253 return expression;
5354 }
@@ -76,11 +77,11 @@ const renderExportDefaultBody = ( · 22 unmodified lines
7677 };
7778
7879 type ExportDefaultDeclarationNode = {
79− type: string;
80− start?: number | null;
81− end?: number | null;
82− id?: { name?: string | null } | null;
83− expression?: unknown;
80+ type: string;
81+ start?: number | null;
82+ end?: number | null;
83+ id?: { name?: string | null } | null;
84+ expression?: unknown;
8485 };
8586
8687 const renderParsedBody = (source: string): string => {
@@ -99,10 +100,13 @@ const renderParsedBody = (source: string): string => { · 12 unmodified lines
99100
100101 switch (statement.type) {
101102 case "ExpressionStatement": {
102− const expression = unwrapExpression(statement.expression as { type: string; expression?: unknown }) as {
103+ const expression = unwrapExpression(
104+ statement.expression as { type: string; expression?: unknown },
105+ ) as {
103106 type?: string;
104107 };
105− return expression?.type === "ArrowFunctionExpression" || expression?.type === "FunctionExpression"
108+ return expression?.type === "ArrowFunctionExpression" ||
109+ expression?.type === "FunctionExpression"
106110 ? wrapCallableBody(source)
107111 : source;
108112 }
packages/kernel/core/src/strip-types.test.ts+4−2
@@ -6,7 +6,7 @@ describe("stripTypeScript", () => { · 5 unmodified lines
66 it("removes variable type annotations", () => {
77 const out = stripTypeScript('const x: string = "hello"; return x;');
88 expect(out).not.toContain(": string");
9− expect(out).toContain('const x');
9+ expect(out).toContain("const x");
1010 expect(out).toContain('"hello"');
1111 });
1212
@@ -30,7 +30,9 @@ describe("stripTypeScript", () => { · 17 unmodified lines
3030 });
3131
3232 it("removes interface declarations", () => {
33− const out = stripTypeScript("interface User { name: string; } const u = { name: 'a' }; return u;");
33+ const out = stripTypeScript(
34+ "interface User { name: string; } const u = { name: 'a' }; return u;",
35+ );
3436 expect(out).not.toContain("interface User");
3537 expect(out).not.toContain(": string");
3638 expect(() => new Function(out)).not.toThrow();
packages/kernel/runtime-deno-subprocess/src/index.test.ts+19−17
@@ -167,23 +167,25 @@ describe.skipIf(!isDenoAvailable())("runtime-deno-subprocess", () => { · 166 unmodified lines
167167 );
168168
169169 // Skipped in CI and on Windows — outbound HTTPS may be blocked by firewall/policy
170− it.effect.skipIf(process.env["CI"] === "true" || process.platform === "win32")("network access can be allowed via permissions", () =>
171− Effect.gen(function* () {
172− const executor = makeDenoSubprocessExecutor({
173− permissions: {
174− allowNet: true,
175− },
176− });
177− const toolInvoker = makeTestInvoker({});
178−
179− const output = yield* executor.execute(
180− ['const res = await fetch("https://example.com");', "return res.status;"].join("\n"),
181− toolInvoker,
182− );
183−
184− expect(output.result).toBe(200);
185− expect(output.error).toBeUndefined();
186− }),
170+ it.effect.skipIf(process.env["CI"] === "true" || process.platform === "win32")(
171+ "network access can be allowed via permissions",
172+ () =>
173+ Effect.gen(function* () {
174+ const executor = makeDenoSubprocessExecutor({
175+ permissions: {
176+ allowNet: true,
177+ },
178+ });
179+ const toolInvoker = makeTestInvoker({});
180+
181+ const output = yield* executor.execute(
182+ ['const res = await fetch("https://example.com");', "return res.status;"].join("\n"),
183+ toolInvoker,
184+ );
185+
186+ expect(output.result).toBe(200);
187+ expect(output.error).toBeUndefined();
188+ }),
187189 );
188190
189191 it.effect("multiple sequential tool calls work correctly", () =>
packages/kernel/runtime-deno-subprocess/src/index.ts+1−3
@@ -92,9 +92,7 @@ const defaultDenoExecutable = (): string => { · 91 unmodified lines
9292 const isWindows = process.platform === "win32";
9393 const home = (process.env.HOME || process.env.USERPROFILE)?.trim();
9494 if (home) {
95− const installedPath = isWindows
96− ? `${home}\\.deno\\bin\\deno.exe`
97− : `${home}/.deno/bin/deno`;
95+ const installedPath = isWindows ? `${home}\\.deno\\bin\\deno.exe` : `${home}/.deno/bin/deno`;
9896 const result = spawnSync(installedPath, ["--version"], {
9997 stdio: "ignore",
10098 timeout: 5000,
packages/kernel/runtime-quickjs/README.md+5−5
@@ -33,11 +33,11 @@ const engine = createExecutionEngine({ · 32 unmodified lines
3333
3434 ### Options
3535
36−| Option | Default | Description |
37−| ------------------- | ---------- | -------------------------------------------- |
38−| `timeoutMs` | `5_000` | Max wall-clock time per execution |
39−| `memoryLimitBytes` | `64 * 1MB` | Max memory the VM can allocate |
40−| `maxStackSizeBytes` | `1 * 1MB` | Max call-stack depth |
36+| Option | Default | Description |
37+| ------------------- | ---------- | --------------------------------- |
38+| `timeoutMs` | `5_000` | Max wall-clock time per execution |
39+| `memoryLimitBytes` | `64 * 1MB` | Max memory the VM can allocate |
40+| `maxStackSizeBytes` | `1 * 1MB` | Max call-stack depth |
4141
4242 ### Swapping the QuickJS build
4343
packages/plugins/example/package.json+8−8
@@ -52,14 +52,6 @@ · 51 unmodified lines
5252 "dependencies": {
5353 "@executor-js/sdk": "workspace:*"
5454 },
55− "peerDependencies": {
56− "react": "catalog:"
57− },
58− "peerDependenciesMeta": {
59− "react": {
60− "optional": true
61− }
62− },
6355 "devDependencies": {
6456 "@effect/vitest": "catalog:",
6557 "@types/node": "catalog:",
@@ -69,5 +61,13 @@ · 3 unmodified lines
6961 "tsup": "catalog:",
7062 "typescript": "catalog:",
7163 "vitest": "catalog:"
64+ },
65+ "peerDependencies": {
66+ "react": "catalog:"
67+ },
68+ "peerDependenciesMeta": {
69+ "react": {
70+ "optional": true
71+ }
7272 }
7373 }
packages/plugins/example/src/client.tsx+2−8
@@ -11,11 +11,7 @@ · 10 unmodified lines
1111 // ---------------------------------------------------------------------------
1212
1313 import { useState } from "react";
14−import {
15− defineClientPlugin,
16− createPluginAtomClient,
17− useAtomSet,
18−} from "@executor-js/sdk/client";
14+import { defineClientPlugin, createPluginAtomClient, useAtomSet } from "@executor-js/sdk/client";
1915
2016 import { ExampleApi } from "./shared";
2117
@@ -68,9 +64,7 @@ function ExamplePage() { · 46 unmodified lines
6864 </button>
6965 </div>
7066 {result && (
71− <pre style={{ marginTop: "1rem", padding: "0.5rem", background: "#f5f5f5" }}>
72− {result}
73− </pre>
67+ <pre style={{ marginTop: "1rem", padding: "0.5rem", background: "#f5f5f5" }}>{result}</pre>
7468 )}
7569 </div>
7670 );
packages/plugins/example/src/server.ts+1−7
@@ -11,13 +11,7 @@ · 10 unmodified lines
1111 // React and other browser-only deps live in `./client` — never here.
1212 // ---------------------------------------------------------------------------
1313
14−import {
15− Context,
16− definePlugin,
17− Effect,
18− HttpApi,
19− HttpApiBuilder,
20−} from "@executor-js/sdk";
14+import { Context, definePlugin, Effect, HttpApi, HttpApiBuilder } from "@executor-js/sdk";
2115
2216 import { ExampleApi } from "./shared";
2317
packages/plugins/google-discovery/src/api/handlers.test.ts+20−26
@@ -53,9 +53,7 @@ const WebHandler = Effect.acquireRelease( · 52 unmodified lines
5353 Layer.provide(observabilityMiddleware(Api)),
5454 Layer.provide(UnusedExecutor),
5555 Layer.provide(UnusedExecutionEngine),
56− Layer.provide(
57− Layer.succeed(GoogleDiscoveryExtensionService, failingExtension),
58− ),
56+ Layer.provide(Layer.succeed(GoogleDiscoveryExtensionService, failingExtension)),
5957 Layer.provideMerge(HttpServer.layerServices),
6058 Layer.provideMerge(Layer.succeed(HttpRouter.RouterConfig)({ maxParamLength: 1000 })),
6159 ),
@@ -98,9 +96,7 @@ describe("GoogleDiscoveryHandlers", () => { · 36 unmodified lines
9896 Layer.provide(observabilityMiddleware(Api)),
9997 Layer.provide(UnusedExecutor),
10098 Layer.provide(UnusedExecutionEngine),
101− Layer.provide(
102− Layer.succeed(GoogleDiscoveryExtensionService, extension),
103− ),
99+ Layer.provide(Layer.succeed(GoogleDiscoveryExtensionService, extension)),
104100 Layer.provideMerge(HttpServer.layerServices),
105101 Layer.provideMerge(Layer.succeed(HttpRouter.RouterConfig)({ maxParamLength: 1000 })),
106102 ),
@@ -130,27 +126,25 @@ describe("GoogleDiscoveryHandlers", () => { · 23 unmodified lines
130126 }),
131127 );
132128
133− it.effect(
134− "defect-returning methods produce an opaque InternalError, no leakage",
135− () =>
136− Effect.gen(function* () {
137− const web = yield* WebHandler;
138− const response = yield* Effect.promise(() =>
139− web.handler(
140− new Request("http://localhost/scopes/scope_1/google-discovery/probe", {
141− method: "POST",
142− headers: { "content-type": "application/json" },
143− body: JSON.stringify({
144− discoveryUrl: "https://example.googleapis.com/$discovery/rest?version=v1",
145− }),
129+ it.effect("defect-returning methods produce an opaque InternalError, no leakage", () =>
130+ Effect.gen(function* () {
131+ const web = yield* WebHandler;
132+ const response = yield* Effect.promise(() =>
133+ web.handler(
134+ new Request("http://localhost/scopes/scope_1/google-discovery/probe", {
135+ method: "POST",
136+ headers: { "content-type": "application/json" },
137+ body: JSON.stringify({
138+ discoveryUrl: "https://example.googleapis.com/$discovery/rest?version=v1",
146139 }),
147− HandlerContext,
148− ),
149− );
140+ }),
141+ HandlerContext,
142+ ),
143+ );
150144
151− expect(response.status).toBe(500);
152− const body = yield* Effect.promise(() => response.text());
153− expect(body).not.toContain("Not implemented");
154− }),
145+ expect(response.status).toBe(500);
146+ const body = yield* Effect.promise(() => response.text());
147+ expect(body).not.toContain("Not implemented");
148+ }),
155149 );
156150 });
packages/plugins/google-discovery/src/api/handlers.ts+4−5
@@ -2,10 +2,7 @@ import { HttpApiBuilder } from "effect/unstable/httpapi"; · 1 unmodified line
22 import { Context, Effect } from "effect";
33
44 import { addGroup, capture } from "@executor-js/api";
5−import type {
6− GoogleDiscoveryAddSourceInput,
7− GoogleDiscoveryPluginExtension,
8−} from "../sdk/plugin";
5+import type { GoogleDiscoveryAddSourceInput, GoogleDiscoveryPluginExtension } from "../sdk/plugin";
96 import { GoogleDiscoveryStoredSourceSchema } from "../sdk/stored-source";
107 import { GoogleDiscoveryGroup } from "./group";
118
@@ -20,7 +17,9 @@ import { GoogleDiscoveryGroup } from "./group"; · 8 unmodified lines
2017 // `.addError(InternalError)` on the group — no per-handler translation.
2118 // ---------------------------------------------------------------------------
2219
23−export class GoogleDiscoveryExtensionService extends Context.Service<GoogleDiscoveryExtensionService, GoogleDiscoveryPluginExtension
20+export class GoogleDiscoveryExtensionService extends Context.Service<
21+ GoogleDiscoveryExtensionService,
22+ GoogleDiscoveryPluginExtension
2423 >()("GoogleDiscoveryExtensionService") {}
2524
2625 // ---------------------------------------------------------------------------
packages/plugins/google-discovery/src/react/client.ts+3−4
@@ -2,7 +2,6 @@ import { createPluginAtomClient } from "@executor-js/sdk/client"; · 1 unmodified line
22 import { getBaseUrl } from "@executor-js/react/api/base-url";
33 import { GoogleDiscoveryGroup } from "../api/group";
44
5−export const GoogleDiscoveryClient = createPluginAtomClient(
6− GoogleDiscoveryGroup,
7− { baseUrl: getBaseUrl() },
8−);
5+export const GoogleDiscoveryClient = createPluginAtomClient(GoogleDiscoveryGroup, {
6+ baseUrl: getBaseUrl(),
7+});
packages/plugins/google-discovery/src/react/index.ts+1−4
@@ -1,6 +1,3 @@
11 export { googleDiscoverySourcePlugin } from "./source-plugin";
22 export { GoogleDiscoveryClient } from "./client";
3−export {
4− addGoogleDiscoverySource,
5− probeGoogleDiscovery,
6−} from "./atoms";
3+export { addGoogleDiscoverySource, probeGoogleDiscovery } from "./atoms";
packages/plugins/google-discovery/src/sdk/document.ts+22−22
@@ -22,9 +22,7 @@ const LowercaseText = Schema.String.pipe( · 21 unmodified lines
2222 );
2323 const TextOption = Schema.OptionFromOptional(TrimmedString).pipe(
2424 Schema.decode({
25− decode: SchemaGetter.transform((value) =>
26− Option.filter(value, (text) => text.length > 0),
27− ),
25+ decode: SchemaGetter.transform((value) => Option.filter(value, (text) => text.length > 0)),
2826 encode: SchemaGetter.transform((value) => value),
2927 }),
3028 Schema.withDecodingDefaultType(Effect.succeed(Option.none())),
@@ -48,9 +46,7 @@ const DiscoveryParameterLocationInput = Schema.optional( · 17 unmodified lines
4846 const DiscoveryDefaultValue = Schema.Union([Schema.String, Schema.Number, Schema.Boolean]);
4947
5048 const DiscoverySchemaModel = Schema.Struct({
51− type: Schema.optional(
52− LowercaseText,
53− ),
49+ type: Schema.optional(LowercaseText),
5450 description: TextOption,
5551 properties: UnknownRecordWithDefault,
5652 items: Schema.optional(Schema.Unknown),
@@ -65,9 +61,7 @@ const DiscoverySchemaModel = Schema.Struct({ · 8 unmodified lines
6561 type DiscoverySchema = typeof DiscoverySchemaModel.Type;
6662
6763 const DiscoveryParameterModel = Schema.Struct({
68− type: Schema.optional(
69− LowercaseText,
70− ),
64+ type: Schema.optional(LowercaseText),
7165 description: TextOption,
7266 properties: UnknownRecordWithDefault,
7367 items: Schema.optional(Schema.Unknown),
@@ -446,7 +440,9 @@ const manifestMethodFromMethod = (input: { · 372 unmodified lines
446440 hasBody: requestRef !== undefined,
447441 }),
448442 inputSchema: Option.fromNullishOr(buildInputSchema({ parameters, requestRef })),
449− outputSchema: Option.fromNullishOr(responseRef ? { $ref: schemaRef(responseRef) } : undefined),
443+ outputSchema: Option.fromNullishOr(
444+ responseRef ? { $ref: schemaRef(responseRef) } : undefined,
445+ ),
450446 scopes: method.scopes ?? [],
451447 });
452448 });
@@ -497,20 +493,24 @@ export const extractGoogleDiscoveryManifest = Effect.fn("GoogleDiscovery.extract · 44 unmodified lines
497493 ),
498494 );
499495
500− const topLevelMethods = yield* Effect.forEach(Object.values(document.methods ?? {}), (rawMethod) =>
501− manifestMethodFromMethod({
502− service,
503− rawMethod,
504− globalParameters: document.parameters ?? {},
505− }),
496+ const topLevelMethods = yield* Effect.forEach(
497+ Object.values(document.methods ?? {}),
498+ (rawMethod) =>
499+ manifestMethodFromMethod({
500+ service,
501+ rawMethod,
502+ globalParameters: document.parameters ?? {},
503+ }),
506504 );
507505
508− const nestedMethods = yield* Effect.forEach(Object.values(document.resources ?? {}), (rawResource) =>
509− collectMethods({
510− service,
511− rawResource,
512− globalParameters: document.parameters ?? {},
513− }),
506+ const nestedMethods = yield* Effect.forEach(
507+ Object.values(document.resources ?? {}),
508+ (rawResource) =>
509+ collectMethods({
510+ service,
511+ rawResource,
512+ globalParameters: document.parameters ?? {},
513+ }),
514514 );
515515
516516 return new GoogleDiscoveryManifest({
packages/plugins/graphql/src/api/group.ts+6−13
@@ -3,10 +3,7 @@ import { Schema } from "effect"; · 2 unmodified lines
33 import { ScopeId } from "@executor-js/sdk/core";
44 import { InternalError } from "@executor-js/api";
55
6−import {
7− GraphqlIntrospectionError,
8− GraphqlExtractionError,
9−} from "../sdk/errors";
6+import { GraphqlIntrospectionError, GraphqlExtractionError } from "../sdk/errors";
107 import { GraphqlSourceAuth, HeaderValue } from "../sdk/types";
118
129 // StoredGraphqlSource shape as an HTTP response schema. Kept local to the
@@ -72,12 +69,8 @@ const AddSourceResponse = Schema.Struct({ · 59 unmodified lines
7269 // Errors with HTTP status
7370 // ---------------------------------------------------------------------------
7471
75−const IntrospectionError = GraphqlIntrospectionError.annotate(
76− { httpApiStatus: 400 },
77−);
78−const ExtractionError = GraphqlExtractionError.annotate(
79− { httpApiStatus: 400 },
80−);
72+const IntrospectionError = GraphqlIntrospectionError.annotate({ httpApiStatus: 400 });
73+const ExtractionError = GraphqlExtractionError.annotate({ httpApiStatus: 400 });
8174
8275 // ---------------------------------------------------------------------------
8376 // Group
@@ -119,6 +112,6 @@ export const GraphqlGroup = HttpApiGroup.make("graphql") · 35 unmodified lines
119112 success: UpdateSourceResponse,
120113 error: GraphqlErrors,
121114 }),
122− )
123− // Plugin domain errors carry their own HTTP status (4xx);
124− // `InternalError` is the shared opaque 500 translated at the HTTP edge.
115+ );
116+// Plugin domain errors carry their own HTTP status (4xx);
117+// `InternalError` is the shared opaque 500 translated at the HTTP edge.
packages/plugins/graphql/src/api/handlers.ts+50−62
@@ -2,11 +2,7 @@ import { HttpApiBuilder } from "effect/unstable/httpapi"; · 1 unmodified line
22 import { Context, Effect } from "effect";
33
44 import { addGroup, capture } from "@executor-js/api";
5−import type {
6− GraphqlPluginExtension,
7− HeaderValue,
8− GraphqlUpdateSourceInput,
9−} from "../sdk/plugin";
5+import type { GraphqlPluginExtension, HeaderValue, GraphqlUpdateSourceInput } from "../sdk/plugin";
106 import { GraphqlGroup } from "./group";
117
128 // ---------------------------------------------------------------------------
@@ -20,7 +16,10 @@ import { GraphqlGroup } from "./group"; · 7 unmodified lines
2016 // `.addError(InternalError)` on the group — no per-handler translation.
2117 // ---------------------------------------------------------------------------
2218
23−export class GraphqlExtensionService extends Context.Service<GraphqlExtensionService, GraphqlPluginExtension>()("GraphqlExtensionService") {}
19+export class GraphqlExtensionService extends Context.Service<
20+ GraphqlExtensionService,
21+ GraphqlPluginExtension
22+>()("GraphqlExtensionService") {}
2423
2524 // ---------------------------------------------------------------------------
2625 // Composed API — core + graphql group
@@ -38,61 +37,50 @@ const ExecutorApiWithGraphql = addGroup(GraphqlGroup); · 11 unmodified lines
3837 // by the API-level observability middleware.
3938 // ---------------------------------------------------------------------------
4039
41−export const GraphqlHandlers = HttpApiBuilder.group(
42− ExecutorApiWithGraphql,
43− "graphql",
44− (handlers) =>
45− handlers
46− .handle("addSource", ({ params: path, payload }) =>
47− capture(
48− Effect.gen(function* () {
49− const ext = yield* GraphqlExtensionService;
50− const result = yield* ext.addSource({
51− endpoint: payload.endpoint,
52− scope: path.scopeId,
53− name: payload.name,
54− introspectionJson: payload.introspectionJson,
55− namespace: payload.namespace,
56− headers: payload.headers as
57− | Record<string, HeaderValue>
58− | undefined,
59− queryParams: payload.queryParams as
60− | Record<string, HeaderValue>
61− | undefined,
62− auth: payload.auth,
63− });
64− return {
65− toolCount: result.toolCount,
66− namespace: result.namespace,
67− };
68− }),
69− ),
70− )
71− .handle("getSource", ({ params: path }) =>
72− capture(
73− Effect.gen(function* () {
74− const ext = yield* GraphqlExtensionService;
75− return yield* ext.getSource(path.namespace, path.scopeId);
76− }),
77− ),
78− )
79− .handle("updateSource", ({ params: path, payload }) =>
80− capture(
81− Effect.gen(function* () {
82− const ext = yield* GraphqlExtensionService;
83− yield* ext.updateSource(path.namespace, path.scopeId, {
84− name: payload.name,
85− endpoint: payload.endpoint,
86− headers: payload.headers as
87− | Record<string, HeaderValue>
88− | undefined,
89− queryParams: payload.queryParams as
90− | Record<string, HeaderValue>
91− | undefined,
92− auth: payload.auth,
93− } as GraphqlUpdateSourceInput);
94− return { updated: true };
95− }),
96− ),
40+export const GraphqlHandlers = HttpApiBuilder.group(ExecutorApiWithGraphql, "graphql", (handlers) =>
41+ handlers
42+ .handle("addSource", ({ params: path, payload }) =>
43+ capture(
44+ Effect.gen(function* () {
45+ const ext = yield* GraphqlExtensionService;
46+ const result = yield* ext.addSource({
47+ endpoint: payload.endpoint,
48+ scope: path.scopeId,
49+ name: payload.name,
50+ introspectionJson: payload.introspectionJson,
51+ namespace: payload.namespace,
52+ headers: payload.headers as Record<string, HeaderValue> | undefined,
53+ queryParams: payload.queryParams as Record<string, HeaderValue> | undefined,
54+ auth: payload.auth,
55+ });
56+ return {
57+ toolCount: result.toolCount,
58+ namespace: result.namespace,
59+ };
60+ }),
9761 ),
62+ )
63+ .handle("getSource", ({ params: path }) =>
64+ capture(
65+ Effect.gen(function* () {
66+ const ext = yield* GraphqlExtensionService;
67+ return yield* ext.getSource(path.namespace, path.scopeId);
68+ }),
69+ ),
70+ )
71+ .handle("updateSource", ({ params: path, payload }) =>
72+ capture(
73+ Effect.gen(function* () {
74+ const ext = yield* GraphqlExtensionService;
75+ yield* ext.updateSource(path.namespace, path.scopeId, {
76+ name: payload.name,
77+ endpoint: payload.endpoint,
78+ headers: payload.headers as Record<string, HeaderValue> | undefined,
79+ queryParams: payload.queryParams as Record<string, HeaderValue> | undefined,
80+ auth: payload.auth,
81+ } as GraphqlUpdateSourceInput);
82+ return { updated: true };
83+ }),
84+ ),
85+ ),
9886 );
packages/plugins/graphql/src/react/GraphqlSignInButton.tsx+2−1
@@ -25,7 +25,8 @@ export default function GraphqlSignInButton(props: { sourceId: string }) { · 24 unmodified lines
2525 popupName: "graphql-oauth",
2626 });
2727
28− const source = AsyncResult.isSuccess(sourceResult) && sourceResult.value ? sourceResult.value : null;
28+ const source =
29+ AsyncResult.isSuccess(sourceResult) && sourceResult.value ? sourceResult.value : null;
2930 const oauth2 = source?.auth.kind === "oauth2" ? source.auth : null;
3031 const connections = AsyncResult.isSuccess(connectionsResult) ? connectionsResult.value : null;
3132 const isConnected =
packages/plugins/graphql/src/react/defaults.ts+1−2
@@ -3,5 +3,4 @@ import { · 2 unmodified lines
33 type HttpCredentialsState,
44 } from "@executor-js/react/plugins/http-credentials";
55
6−export const initialGraphqlCredentials = (): HttpCredentialsState =>
7− emptyHttpCredentials();
6+export const initialGraphqlCredentials = (): HttpCredentialsState => emptyHttpCredentials();
packages/plugins/graphql/src/sdk/introspect.ts+1−3
@@ -226,9 +226,7 @@ export const introspect = Effect.fn("GraphQL.introspect")(function* ( · 225 unmodified lines
226226 }
227227
228228 const raw = yield* response.json.pipe(
229− Effect.tapCause((cause) =>
230− Effect.logError("graphql introspection JSON parse failed", cause),
231− ),
229+ Effect.tapCause((cause) => Effect.logError("graphql introspection JSON parse failed", cause)),
232230 Effect.mapError(
233231 () =>
234232 new GraphqlIntrospectionError({
packages/plugins/graphql/src/sdk/invoke.ts+1−2
@@ -20,8 +20,7 @@ export const resolveHeaders = ( · 19 unmodified lines
2020 );
2121 return resolveSecretBackedMap({
2222 values: headers,
23− getSecret: (secretId) =>
24− secrets.get(secretId).pipe(Effect.catch(() => Effect.succeed(null))),
23+ getSecret: (secretId) => secrets.get(secretId).pipe(Effect.catch(() => Effect.succeed(null))),
2524 missing: "drop",
2625 onMissing: (name) =>
2726 new GraphqlInvocationError({
packages/plugins/graphql/src/sdk/types.ts+7−9
@@ -64,15 +64,13 @@ export type QueryParamValue = typeof QueryParamValue.Type; · 63 unmodified lines
6464 // Source auth
6565 // ---------------------------------------------------------------------------
6666
67−export const GraphqlSourceAuth = Schema.Union(
68− [
69− Schema.Struct({ kind: Schema.Literal("none") }),
70− Schema.Struct({
71− kind: Schema.Literal("oauth2"),
72− connectionId: Schema.String,
73− }),
74− ],
75−);
67+export const GraphqlSourceAuth = Schema.Union([
68+ Schema.Struct({ kind: Schema.Literal("none") }),
69+ Schema.Struct({
70+ kind: Schema.Literal("oauth2"),
71+ connectionId: Schema.String,
72+ }),
73+]);
7674 export type GraphqlSourceAuth = typeof GraphqlSourceAuth.Type;
7775
7876 export class InvocationConfig extends Schema.Class<InvocationConfig>("InvocationConfig")({
packages/plugins/keychain/src/index.test.ts+3−1
@@ -62,7 +62,9 @@ describe("keychain plugin", () => { · 61 unmodified lines
6262 const resolved = yield* executor.secrets.get(testId);
6363 expect(resolved).toBe("keychain-test-value");
6464 }).pipe(
65− Effect.ensuring(executor.secrets.remove(testId).pipe(Effect.orElseSucceed(() => undefined))),
65+ Effect.ensuring(
66+ executor.secrets.remove(testId).pipe(Effect.orElseSucceed(() => undefined)),
67+ ),
6668 );
6769 }),
6870 );
packages/plugins/keychain/src/index.ts+24−37
@@ -1,10 +1,6 @@
11 import { Effect } from "effect";
22
3−import {
4− definePlugin,
5− type PluginCtx,
6− type SecretProvider,
7−} from "@executor-js/sdk/core";
3+import { definePlugin, type PluginCtx, type SecretProvider } from "@executor-js/sdk/core";
84
95 import {
106 deletePassword,
@@ -59,8 +55,7 @@ export type KeychainExtension = ReturnType<typeof makeKeychainExtension>; · 48 unmodified lines
5955 const scopedServiceName = (
6056 ctx: PluginCtx<unknown>,
6157 options: KeychainPluginConfig | undefined,
62−): string =>
63− `${resolveServiceName(options?.serviceName)}/${ctx.scopes[0]!.id}`;
58+): string => `${resolveServiceName(options?.serviceName)}/${ctx.scopes[0]!.id}`;
6459
6560 const makeKeychainExtension = (
6661 ctx: PluginCtx<unknown>,
@@ -83,34 +78,26 @@ const makeKeychainExtension = ( · 16 unmodified lines
8378 };
8479 };
8580
86−export const keychainPlugin = definePlugin(
87− (options?: KeychainPluginConfig) => ({
88− id: "keychain" as const,
89− storage: () => ({}),
90−
91− extension: (ctx): KeychainExtension => makeKeychainExtension(ctx, options),
92−
93− secretProviders: (ctx): Effect.Effect<readonly SecretProvider[]> =>
94− Effect.gen(function* () {
95− const serviceName = scopedServiceName(ctx, options);
96− const reachable = yield* setPassword(
97− serviceName,
98− PROBE_ACCOUNT,
99− PROBE_VALUE,
100− ).pipe(
101− Effect.andThen(
102− deletePassword(serviceName, PROBE_ACCOUNT).pipe(
103− Effect.catch(() => Effect.void),
104− ),
105− ),
106− Effect.as(true),
107− Effect.catch(() =>
108− Effect.logWarning(
109− "keychain unavailable, skipping provider registration",
110− ).pipe(Effect.as(false)),
81+export const keychainPlugin = definePlugin((options?: KeychainPluginConfig) => ({
82+ id: "keychain" as const,
83+ storage: () => ({}),
84+
85+ extension: (ctx): KeychainExtension => makeKeychainExtension(ctx, options),
86+
87+ secretProviders: (ctx): Effect.Effect<readonly SecretProvider[]> =>
88+ Effect.gen(function* () {
89+ const serviceName = scopedServiceName(ctx, options);
90+ const reachable = yield* setPassword(serviceName, PROBE_ACCOUNT, PROBE_VALUE).pipe(
91+ Effect.andThen(
92+ deletePassword(serviceName, PROBE_ACCOUNT).pipe(Effect.catch(() => Effect.void)),
93+ ),
94+ Effect.as(true),
95+ Effect.catch(() =>
96+ Effect.logWarning("keychain unavailable, skipping provider registration").pipe(
97+ Effect.as(false),
11198 ),
112− );
113− return reachable ? [makeKeychainProvider(serviceName)] : [];
114− }),
115− }),
116−);
99+ ),
100+ );
101+ return reachable ? [makeKeychainProvider(serviceName)] : [];
102+ }),
103+}));
packages/plugins/mcp/src/api/group.ts+10−11
@@ -179,14 +179,13 @@ export const McpGroup = HttpApiGroup.make("mcp") · 178 unmodified lines
179179 success: UpdateSourceResponse,
180180 error: [InternalError, McpConnectionError, McpToolDiscoveryError],
181181 }),
182− )
183− // Errors declared once at the group level — every endpoint inherits.
184− // Plugin domain errors carry their own HttpApiSchema status (4xx);
185− // `InternalError` is the shared opaque 500 translated at the HTTP
186− // edge by `withCapture`. We only list errors an MCP *group*
187− // endpoint can surface: `McpInvocationError` is thrown inside
188− // `invokeTool` which is reached via the core `tools.invoke`
189− // endpoint, not any MCP-group endpoint, so it doesn't belong here.
190− // OAuth errors live on the shared `/oauth/*` group in `@executor-js/api`
191− // now — the MCP group only declares its own plugin-domain errors.
192−;
182+ );
183+// Errors declared once at the group level — every endpoint inherits.
184+// Plugin domain errors carry their own HttpApiSchema status (4xx);
185+// `InternalError` is the shared opaque 500 translated at the HTTP
186+// edge by `withCapture`. We only list errors an MCP *group*
187+// endpoint can surface: `McpInvocationError` is thrown inside
188+// `invokeTool` which is reached via the core `tools.invoke`
189+// endpoint, not any MCP-group endpoint, so it doesn't belong here.
190+// OAuth errors live on the shared `/oauth/*` group in `@executor-js/api`
191+// now — the MCP group only declares its own plugin-domain errors.
packages/plugins/mcp/src/api/handlers.test.ts+16−18
@@ -69,25 +69,23 @@ const McpConnectionErrorResponse = Schema.Struct({ · 68 unmodified lines
6969 });
7070
7171 describe("McpHandlers", () => {
72− it.effect(
73− "defect-returning methods produce an opaque InternalError, no leakage",
74− () =>
75− Effect.gen(function* () {
76− const web = yield* WebHandler;
77− const response = yield* Effect.promise(() =>
78− (web.handler as (request: Request) => Promise<Response>)(
79− new Request("http://localhost/scopes/scope_1/mcp/probe", {
80− method: "POST",
81− headers: { "content-type": "application/json" },
82− body: JSON.stringify({ endpoint: "https://example.com/mcp" }),
83− }),
84− ),
85− );
72+ it.effect("defect-returning methods produce an opaque InternalError, no leakage", () =>
73+ Effect.gen(function* () {
74+ const web = yield* WebHandler;
75+ const response = yield* Effect.promise(() =>
76+ (web.handler as (request: Request) => Promise<Response>)(
77+ new Request("http://localhost/scopes/scope_1/mcp/probe", {
78+ method: "POST",
79+ headers: { "content-type": "application/json" },
80+ body: JSON.stringify({ endpoint: "https://example.com/mcp" }),
81+ }),
82+ ),
83+ );
8684
87− expect(response.status).toBe(500);
88− const body = yield* Effect.promise(() => response.text());
89− expect(body).not.toContain("Not implemented");
90− }),
85+ expect(response.status).toBe(500);
86+ const body = yield* Effect.promise(() => response.text());
87+ expect(body).not.toContain("Not implemented");
88+ }),
9189 );
9290
9391 it.effect("domain MCP connection errors are encoded as 400 responses", () =>
packages/plugins/mcp/src/api/handlers.ts+3−2
@@ -20,8 +20,9 @@ import { McpGroup } from "./group"; · 19 unmodified lines
2020 // (no `Captured<>` inversion).
2121 // ---------------------------------------------------------------------------
2222
23−export class McpExtensionService extends Context.Service<McpExtensionService, McpPluginExtension
24−>()("McpExtensionService") {}
23+export class McpExtensionService extends Context.Service<McpExtensionService, McpPluginExtension>()(
24+ "McpExtensionService",
25+) {}
2526
2627 // ---------------------------------------------------------------------------
2728 // Composed API
packages/plugins/mcp/src/react/index.ts+1−6
@@ -1,9 +1,4 @@
11 export { mcpSourcePlugin, createMcpSourcePlugin } from "./source-plugin";
22 export type { McpSourcePluginOptions } from "./source-plugin";
33 export { McpClient } from "./client";
4−export {
5− probeMcpEndpoint,
6− addMcpSource,
7− removeMcpSource,
8− refreshMcpSource,
9−} from "./atoms";
4+export { probeMcpEndpoint, addMcpSource, removeMcpSource, refreshMcpSource } from "./atoms";
packages/plugins/mcp/src/react/source-plugin.tsx+1−3
@@ -23,9 +23,7 @@ export interface McpSourcePluginOptions { · 22 unmodified lines
2323 readonly allowStdio?: boolean;
2424 }
2525
26−export const createMcpSourcePlugin = (
27− options?: McpSourcePluginOptions,
28−): SourcePlugin => {
26+export const createMcpSourcePlugin = (options?: McpSourcePluginOptions): SourcePlugin => {
2927 const allowStdio = options?.allowStdio ?? false;
3028
3129 const AddWithFlag: ComponentType<AddProps> = (props) => (
packages/plugins/mcp/src/sdk/connection-pool.test.ts+26−44
@@ -163,11 +163,7 @@ describe("MCP connection pooling (regression)", () => { · 162 unmodified lines
163163 // First invoke is necessarily a cache miss — it cold-handshakes the
164164 // pooled connection. Every subsequent invoke must hit the cache and
165165 // produce zero new MCP server sessions.
166− yield* executor.tools.invoke(
167− echo.id,
168− { value: "1" },
169− { onElicitation: "accept-all" },
170− );
166+ yield* executor.tools.invoke(echo.id, { value: "1" }, { onElicitation: "accept-all" });
171167 const sessionsAfterFirstInvoke = server.sessionCount();
172168 expect(sessionsAfterFirstInvoke).toBe(sessionsAfterAddSource + 1);
173169
@@ -185,44 +181,30 @@ describe("MCP connection pooling (regression)", () => { · 11 unmodified lines
185181 }),
186182 );
187183
188− it.effect(
189− "different tools on the same source share the cached connection",
190− () =>
191− Effect.gen(function* () {
192− const server = yield* serveMcpServer;
193− const executor = yield* makeTestExecutor(server.url);
194− const tools = yield* executor.tools.list();
195− const echo = tools.find((t) => t.name === "echo")!;
196− const echo2 = tools.find((t) => t.name === "echo2")!;
197− expect(echo).toBeDefined();
198− expect(echo2).toBeDefined();
199−
200− const sessionsAfterAddSource = server.sessionCount();
201−
202− // Cold handshake on first call.
203− yield* executor.tools.invoke(
204− echo.id,
205− { value: "a" },
206− { onElicitation: "accept-all" },
207− );
208− const baseline = server.sessionCount();
209− expect(baseline).toBe(sessionsAfterAddSource + 1);
210−
211− // Different tool on the SAME source — same cache key, same conn.
212− yield* executor.tools.invoke(
213− echo2.id,
214− { value: "b" },
215− { onElicitation: "accept-all" },
216− );
217− expect(server.sessionCount()).toBe(baseline);
218−
219− // Back to the first tool — still pooled.
220− yield* executor.tools.invoke(
221− echo.id,
222− { value: "c" },
223− { onElicitation: "accept-all" },
224− );
225− expect(server.sessionCount()).toBe(baseline);
226− }),
184+ it.effect("different tools on the same source share the cached connection", () =>
185+ Effect.gen(function* () {
186+ const server = yield* serveMcpServer;
187+ const executor = yield* makeTestExecutor(server.url);
188+ const tools = yield* executor.tools.list();
189+ const echo = tools.find((t) => t.name === "echo")!;
190+ const echo2 = tools.find((t) => t.name === "echo2")!;
191+ expect(echo).toBeDefined();
192+ expect(echo2).toBeDefined();
193+
194+ const sessionsAfterAddSource = server.sessionCount();
195+
196+ // Cold handshake on first call.
197+ yield* executor.tools.invoke(echo.id, { value: "a" }, { onElicitation: "accept-all" });
198+ const baseline = server.sessionCount();
199+ expect(baseline).toBe(sessionsAfterAddSource + 1);
200+
201+ // Different tool on the SAME source — same cache key, same conn.
202+ yield* executor.tools.invoke(echo2.id, { value: "b" }, { onElicitation: "accept-all" });
203+ expect(server.sessionCount()).toBe(baseline);
204+
205+ // Back to the first tool — still pooled.
206+ yield* executor.tools.invoke(echo.id, { value: "c" }, { onElicitation: "accept-all" });
207+ expect(server.sessionCount()).toBe(baseline);
208+ }),
227209 );
228210 });
packages/plugins/mcp/src/sdk/cross-user-isolation.test.ts+2−6
@@ -53,10 +53,7 @@ const makeSharedOrgExecutors = () => · 52 unmodified lines
5353
5454 const makeFor = (innerId: ScopeId) =>
5555 createExecutor({
56− scopes: [
57− new Scope({ id: innerId, name: "Personal", createdAt: new Date() }),
58− orgScope,
59− ],
56+ scopes: [new Scope({ id: innerId, name: "Personal", createdAt: new Date() }), orgScope],
6057 adapter,
6158 blobs,
6259 plugins,
@@ -142,8 +139,7 @@ describe("MCP cross-user isolation within the same org", () => { · 79 unmodified lines
142139
143140 it.effect("A sees A's source, B sees org's, neither sees the other's inner", () =>
144141 Effect.gen(function* () {
145− const { execA, execB, aInnerId, bInnerId, orgScopeId } =
146− yield* makeSharedOrgExecutors();
142+ const { execA, execB, aInnerId, bInnerId, orgScopeId } = yield* makeSharedOrgExecutors();
147143
148144 yield* seedSource(execA.mcp.addSource, {
149145 scope: aInnerId,
packages/plugins/mcp/src/sdk/elicitation.test.ts+1−2
@@ -145,8 +145,7 @@ describe("MCP elicitation (end-to-end)", () => { · 144 unmodified lines
145145 gatedEcho.id,
146146 { value: "nope" },
147147 {
148− onElicitation: () =>
149− Effect.succeed(new ElicitationResponse({ action: "decline" })),
148+ onElicitation: () => Effect.succeed(new ElicitationResponse({ action: "decline" })),
150149 },
151150 );
152151
packages/plugins/mcp/src/sdk/per-user-auth-isolation.test.ts+136−146
@@ -103,72 +103,69 @@ const failureError = <E>(exit: Exit.Exit<unknown, E>): E | undefined => · 102 unmodified lines
103103 const isToolInvocationError = (error: unknown): error is ToolInvocationError =>
104104 Predicate.isTagged(error, "ToolInvocationError");
105105
106−const createAuthRecordingServer: Effect.Effect<TestServer, Error, never> =
107− Effect.callback<TestServer, Error>((resume) => {
108− const transports = new Map<string, StreamableHTTPServerTransport>();
109− const recorded: RecordedRequest[] = [];
110−
111− const httpServer = http.createServer(async (req, res) => {
112− recorded.push({
113− authorization: req.headers["authorization"] as string | undefined,
114− });
106+const createAuthRecordingServer: Effect.Effect<TestServer, Error, never> = Effect.callback<
107+ TestServer,
108+ Error
109+>((resume) => {
110+ const transports = new Map<string, StreamableHTTPServerTransport>();
111+ const recorded: RecordedRequest[] = [];
112+
113+ const httpServer = http.createServer(async (req, res) => {
114+ recorded.push({
115+ authorization: req.headers["authorization"] as string | undefined,
116+ });
115117
116− const sessionId = req.headers["mcp-session-id"] as string | undefined;
117− if (sessionId) {
118− const transport = transports.get(sessionId);
119− if (!transport) {
120− res.writeHead(404);
121− res.end("Session not found");
122− return;
123− }
124− await transport.handleRequest(req, res);
118+ const sessionId = req.headers["mcp-session-id"] as string | undefined;
119+ if (sessionId) {
120+ const transport = transports.get(sessionId);
121+ if (!transport) {
122+ res.writeHead(404);
123+ res.end("Session not found");
125124 return;
126125 }
127−
128− const mcpServer = new McpServer(
129− { name: "iso-test", version: "1.0.0" },
130− { capabilities: {} },
131− );
132− mcpServer.registerTool(
133− "whoami",
134− {
135− description: "Echoes a marker — used to prove the invoke reached the server",
136− inputSchema: { marker: z.string() },
137− },
138− async ({ marker }: { marker: string }) => ({
139− content: [{ type: "text" as const, text: `ok:${marker}` }],
140− }),
141− );
142−
143− const transport = new StreamableHTTPServerTransport({
144− sessionIdGenerator: () => crypto.randomUUID(),
145− onsessioninitialized: (sid) => {
146− transports.set(sid, transport);
147− },
148− });
149− await mcpServer.connect(transport);
150126 await transport.handleRequest(req, res);
151− });
127+ return;
128+ }
129+
130+ const mcpServer = new McpServer({ name: "iso-test", version: "1.0.0" }, { capabilities: {} });
131+ mcpServer.registerTool(
132+ "whoami",
133+ {
134+ description: "Echoes a marker — used to prove the invoke reached the server",
135+ inputSchema: { marker: z.string() },
136+ },
137+ async ({ marker }: { marker: string }) => ({
138+ content: [{ type: "text" as const, text: `ok:${marker}` }],
139+ }),
140+ );
152141
153− httpServer.listen(0, () => {
154− const addr = httpServer.address();
155− const port = typeof addr === "object" && addr ? addr.port : 0;
156− resume(
157− Effect.succeed({
158− url: `http://127.0.0.1:${port}`,
159− httpServer,
160− recorded: () => recorded,
161− }),
162− );
142+ const transport = new StreamableHTTPServerTransport({
143+ sessionIdGenerator: () => crypto.randomUUID(),
144+ onsessioninitialized: (sid) => {
145+ transports.set(sid, transport);
146+ },
163147 });
148+ await mcpServer.connect(transport);
149+ await transport.handleRequest(req, res);
164150 });
165151
166−const serveMcpServer = Effect.acquireRelease(
167− createAuthRecordingServer,
168− ({ httpServer }) =>
169− Effect.sync(() => {
170− httpServer.close();
171− }),
152+ httpServer.listen(0, () => {
153+ const addr = httpServer.address();
154+ const port = typeof addr === "object" && addr ? addr.port : 0;
155+ resume(
156+ Effect.succeed({
157+ url: `http://127.0.0.1:${port}`,
158+ httpServer,
159+ recorded: () => recorded,
160+ }),
161+ );
162+ });
163+});
164+
165+const serveMcpServer = Effect.acquireRelease(createAuthRecordingServer, ({ httpServer }) =>
166+ Effect.sync(() => {
167+ httpServer.close();
168+ }),
172169 );
173170
174171 // ---------------------------------------------------------------------------
@@ -181,8 +178,7 @@ const USER_A = ScopeId.make("user-a"); · 6 unmodified lines
181178 const USER_B = ScopeId.make("user-b");
182179 const ORG = ScopeId.make("org");
183180
184−const scope = (id: ScopeId, name: string): Scope =>
185− new Scope({ id, name, createdAt: new Date() });
181+const scope = (id: ScopeId, name: string): Scope => new Scope({ id, name, createdAt: new Date() });
186182
187183 const makeLayeredMcpExecutors = () =>
188184 Effect.gen(function* () {
@@ -290,9 +286,7 @@ describe("per-user MCP auth isolation", () => { · 101 unmodified lines
290286 content: [{ type: "text", text: "ok:from-user-a" }],
291287 });
292288 const userARequests = server.recorded().slice(recordedBeforeUserA);
293− expect(
294− userARequests.some((r) => r.authorization === "Bearer token-user-a"),
295− ).toBe(true);
289+ expect(userARequests.some((r) => r.authorization === "Bearer token-user-a")).toBe(true);
296290
297291 // Snapshot the recorded-auth slice we'll check for the user-B
298292 // run — "token-user-a" must not appear in any request AFTER
@@ -344,95 +338,91 @@ describe("per-user MCP auth isolation", () => { · 45 unmodified lines
344338 // must fail rather than silently fall through to any other scope's
345339 // value. This also locks the contract that the MCP plugin doesn't
346340 // quietly downgrade a missing secret to "no auth" on the transport.
347− it.effect(
348− "header source: unauthenticated user B cannot invoke via a per-user secret",
349− () =>
350− Effect.gen(function* () {
351− const server = yield* serveMcpServer;
352− const { execUserA, execUserB } = yield* makeLayeredMcpExecutors();
341+ it.effect("header source: unauthenticated user B cannot invoke via a per-user secret", () =>
342+ Effect.gen(function* () {
343+ const server = yield* serveMcpServer;
344+ const { execUserA, execUserB } = yield* makeLayeredMcpExecutors();
345+
346+ const SECRET = SecretId.make("shared-mcp-token");
347+
348+ // User A plants their personal token at their per-user scope
349+ // FIRST — so the subsequent addSource's discovery call can
350+ // resolve it through user A's stack.
351+ yield* execUserA.secrets.set(
352+ new SetSecretInput({
353+ id: SECRET,
354+ scope: USER_A,
355+ name: "User A MCP token",
356+ value: "token-user-a-header",
357+ }),
358+ );
353359
354− const SECRET = SecretId.make("shared-mcp-token");
360+ yield* execUserA.mcp.addSource({
361+ transport: "remote",
362+ scope: ORG,
363+ name: "Shared MCP (header)",
364+ endpoint: server.url,
365+ namespace: "iso_header",
366+ auth: {
367+ kind: "header",
368+ headerName: "Authorization",
369+ secretId: SECRET,
370+ prefix: "Bearer ",
371+ },
372+ });
355373
356− // User A plants their personal token at their per-user scope
357− // FIRST — so the subsequent addSource's discovery call can
358− // resolve it through user A's stack.
359− yield* execUserA.secrets.set(
360− new SetSecretInput({
361− id: SECRET,
362− scope: USER_A,
363− name: "User A MCP token",
364− value: "token-user-a-header",
365− }),
366− );
374+ // User A sanity invoke — server sees A's token. Asserting on
375+ // the recorded header here guards against false-positive for
376+ // the negative check below (e.g. if the plugin silently dropped
377+ // auth, both invocations would fail for unrelated reasons).
378+ const userATools = yield* execUserA.tools.list();
379+ const whoamiForA = userATools.find((t) => t.name === "whoami")!;
380+
381+ const recordedBeforeUserA = server.recorded().length;
382+ const userAResult = yield* execUserA.tools.invoke(
383+ whoamiForA.id,
384+ { marker: "user-a-header" },
385+ { onElicitation: "accept-all" },
386+ );
387+ expect(userAResult).toMatchObject({
388+ content: [{ type: "text", text: "ok:user-a-header" }],
389+ });
390+ const userARequests = server.recorded().slice(recordedBeforeUserA);
391+ expect(userARequests.some((r) => r.authorization === "Bearer token-user-a-header")).toBe(
392+ true,
393+ );
367394
368− yield* execUserA.mcp.addSource({
369− transport: "remote",
370− scope: ORG,
371− name: "Shared MCP (header)",
372− endpoint: server.url,
373− namespace: "iso_header",
374− auth: {
375− kind: "header",
376− headerName: "Authorization",
377− secretId: SECRET,
378− prefix: "Bearer ",
379− },
380− });
395+ const recordedBeforeUserB = server.recorded().length;
381396
382− // User A sanity invoke — server sees A's token. Asserting on
383− // the recorded header here guards against false-positive for
384− // the negative check below (e.g. if the plugin silently dropped
385− // auth, both invocations would fail for unrelated reasons).
386− const userATools = yield* execUserA.tools.list();
387− const whoamiForA = userATools.find((t) => t.name === "whoami")!;
397+ // User B has no personal token. The fall-through lookup walks
398+ // [userB, org] — neither scope has the secret row, so the
399+ // resolver must return null and the invoke must fail.
400+ const userBTools = yield* execUserB.tools.list();
401+ const whoamiForB = userBTools.find((t) => t.name === "whoami")!;
388402
389− const recordedBeforeUserA = server.recorded().length;
390− const userAResult = yield* execUserA.tools.invoke(
391− whoamiForA.id,
392− { marker: "user-a-header" },
403+ const userBResult = yield* Effect.exit(
404+ execUserB.tools.invoke(
405+ whoamiForB.id,
406+ { marker: "user-b-header" },
393407 { onElicitation: "accept-all" },
394− );
395− expect(userAResult).toMatchObject({
396− content: [{ type: "text", text: "ok:user-a-header" }],
397− });
398− const userARequests = server.recorded().slice(recordedBeforeUserA);
399− expect(
400− userARequests.some(
401− (r) => r.authorization === "Bearer token-user-a-header",
402− ),
403− ).toBe(true);
404−
405− const recordedBeforeUserB = server.recorded().length;
406−
407− // User B has no personal token. The fall-through lookup walks
408− // [userB, org] — neither scope has the secret row, so the
409− // resolver must return null and the invoke must fail.
410− const userBTools = yield* execUserB.tools.list();
411− const whoamiForB = userBTools.find((t) => t.name === "whoami")!;
412−
413− const userBResult = yield* Effect.exit(
414− execUserB.tools.invoke(
415− whoamiForB.id,
416− { marker: "user-b-header" },
417− { onElicitation: "accept-all" },
418− ),
419− );
420−
421− expect(Exit.isFailure(userBResult)).toBe(true);
422− // tools.invoke wraps plugin failures in ToolInvocationError
423− // with the original error carried on `cause`. Pin the exact
424− // inner tag — a regression that swapped the "no connection
425− // found" check for a silent no-auth fallback would either
426− // succeed outright (leaking) or surface a different tag here.
427− const outer = failureError(userBResult);
428− expect(isToolInvocationError(outer)).toBe(true);
429− const inner = isToolInvocationError(outer) ? outer.cause : undefined;
430− expect(Predicate.isTagged(inner, "McpConnectionError")).toBe(true);
408+ ),
409+ );
431410
432− const afterUserB = server.recorded().slice(recordedBeforeUserB);
433− for (const req of afterUserB) {
434− expect(req.authorization).not.toBe("Bearer token-user-a-header");
435− }
436− }),
411+ expect(Exit.isFailure(userBResult)).toBe(true);
412+ // tools.invoke wraps plugin failures in ToolInvocationError
413+ // with the original error carried on `cause`. Pin the exact
414+ // inner tag — a regression that swapped the "no connection
415+ // found" check for a silent no-auth fallback would either
416+ // succeed outright (leaking) or surface a different tag here.
417+ const outer = failureError(userBResult);
418+ expect(isToolInvocationError(outer)).toBe(true);
419+ const inner = isToolInvocationError(outer) ? outer.cause : undefined;
420+ expect(Predicate.isTagged(inner, "McpConnectionError")).toBe(true);
421+
422+ const afterUserB = server.recorded().slice(recordedBeforeUserB);
423+ for (const req of afterUserB) {
424+ expect(req.authorization).not.toBe("Bearer token-user-a-header");
425+ }
426+ }),
437427 );
438428 });
packages/plugins/mcp/src/sdk/probe-shape.test.ts+3−4
@@ -129,10 +129,9 @@ describe("probeMcpEndpointShape", () => { · 128 unmodified lines
129129 }),
130130 { status: 400, headers: { "content-type": "application/json" } },
131131 );
132− const result = yield* probeMcpEndpointShape(
133− "https://backboard.railway.com/graphql/v2",
134− { fetch: stubFetch(response) },
135− );
132+ const result = yield* probeMcpEndpointShape("https://backboard.railway.com/graphql/v2", {
133+ fetch: stubFetch(response),
134+ });
136135 expect(result.kind).toBe("not-mcp");
137136 }),
138137 );
packages/plugins/mcp/src/sdk/probe-shape.ts+1−2
@@ -133,8 +133,7 @@ export const probeMcpEndpointShape = ( · 132 unmodified lines
133133 }
134134 return {
135135 kind: "not-mcp",
136− reason:
137− "401 without Bearer WWW-Authenticate — not an MCP auth challenge",
136+ reason: "401 without Bearer WWW-Authenticate — not an MCP auth challenge",
138137 } as const;
139138 }
140139
packages/plugins/mcp/src/sdk/stdio-connector.ts+1−3
@@ -26,8 +26,6 @@ export const createStdioTransport = (config: StdioTransportConfig) => · 25 unmodified lines
2626 new StdioClientTransport({
2727 command: config.command,
2828 args: config.args ? [...config.args] : undefined,
29− env: config.env
30− ? ({ ...process.env, ...config.env } as Record<string, string>)
31− : undefined,
29+ env: config.env ? ({ ...process.env, ...config.env } as Record<string, string>) : undefined,
3230 cwd: config.cwd,
3331 });
packages/plugins/onepassword/src/api/handlers.ts+38−26
@@ -16,7 +16,9 @@ import { OnePasswordGroup } from "./group"; · 15 unmodified lines
1616 // `.addError(InternalError)` on the group — no per-handler translation.
1717 // ---------------------------------------------------------------------------
1818
19−export class OnePasswordExtensionService extends Context.Service<OnePasswordExtensionService, OnePasswordExtension
19+export class OnePasswordExtensionService extends Context.Service<
20+ OnePasswordExtensionService,
21+ OnePasswordExtension
2022 >()("OnePasswordExtensionService") {}
2123
2224 // ---------------------------------------------------------------------------
@@ -41,38 +43,48 @@ export const OnePasswordHandlers = HttpApiBuilder.group( · 18 unmodified lines
4143 (handlers) =>
4244 handlers
4345 .handle("getConfig", () =>
44− capture(Effect.gen(function* () {
45− const ext = yield* OnePasswordExtensionService;
46− return yield* ext.getConfig();
47− })),
46+ capture(
47+ Effect.gen(function* () {
48+ const ext = yield* OnePasswordExtensionService;
49+ return yield* ext.getConfig();
50+ }),
51+ ),
4852 )
4953 .handle("configure", ({ payload }) =>
50− capture(Effect.gen(function* () {
51− const ext = yield* OnePasswordExtensionService;
52− yield* ext.configure(payload);
53− })),
54+ capture(
55+ Effect.gen(function* () {
56+ const ext = yield* OnePasswordExtensionService;
57+ yield* ext.configure(payload);
58+ }),
59+ ),
5460 )
5561 .handle("removeConfig", () =>
56− capture(Effect.gen(function* () {
57− const ext = yield* OnePasswordExtensionService;
58− yield* ext.removeConfig();
59− })),
62+ capture(
63+ Effect.gen(function* () {
64+ const ext = yield* OnePasswordExtensionService;
65+ yield* ext.removeConfig();
66+ }),
67+ ),
6068 )
6169 .handle("status", () =>
62− capture(Effect.gen(function* () {
63− const ext = yield* OnePasswordExtensionService;
64− return yield* ext.status();
65− })),
70+ capture(
71+ Effect.gen(function* () {
72+ const ext = yield* OnePasswordExtensionService;
73+ return yield* ext.status();
74+ }),
75+ ),
6676 )
6777 .handle("listVaults", ({ query: urlParams }) =>
68− capture(Effect.gen(function* () {
69− const ext = yield* OnePasswordExtensionService;
70− const auth =
71− urlParams.authKind === "desktop-app"
72− ? { kind: "desktop-app" as const, accountName: urlParams.account }
73− : { kind: "service-account" as const, tokenSecretId: urlParams.account };
74− const vaults = yield* ext.listVaults(auth);
75− return { vaults: [...vaults] };
76− })),
78+ capture(
79+ Effect.gen(function* () {
80+ const ext = yield* OnePasswordExtensionService;
81+ const auth =
82+ urlParams.authKind === "desktop-app"
83+ ? { kind: "desktop-app" as const, accountName: urlParams.account }
84+ : { kind: "service-account" as const, tokenSecretId: urlParams.account };
85+ const vaults = yield* ext.listVaults(auth);
86+ return { vaults: [...vaults] };
87+ }),
88+ ),
7789 ),
7890 );
packages/plugins/openapi/src/api/group.ts+7−12
@@ -166,15 +166,10 @@ export const OpenApiGroup = HttpApiGroup.make("openapi") · 165 unmodified lines
166166 }),
167167 )
168168 .add(
169− HttpApiEndpoint.post(
170− "removeSourceBinding",
171− "/scopes/:scopeId/openapi/source-bindings/remove",
172− {
173− params: ScopeIdParam,
174− payload: RemoveBindingPayload,
175− success: Schema.Struct({ removed: Schema.Boolean }),
176− error: DomainErrors,
177− },
178− ),
179− )
180−;
169+ HttpApiEndpoint.post("removeSourceBinding", "/scopes/:scopeId/openapi/source-bindings/remove", {
170+ params: ScopeIdParam,
171+ payload: RemoveBindingPayload,
172+ success: Schema.Struct({ removed: Schema.Boolean }),
173+ error: DomainErrors,
174+ }),
175+ );
packages/plugins/openapi/src/api/handlers.ts+3−1
@@ -24,7 +24,9 @@ import { OpenApiGroup } from "./group"; · 23 unmodified lines
2424 // `.addError(InternalError)` on the group — no per-handler translation.
2525 // ---------------------------------------------------------------------------
2626
27−export class OpenApiExtensionService extends Context.Service<OpenApiExtensionService, OpenApiPluginExtension
27+export class OpenApiExtensionService extends Context.Service<
28+ OpenApiExtensionService,
29+ OpenApiPluginExtension
2830 >()("OpenApiExtensionService") {}
2931
3032 // ---------------------------------------------------------------------------
packages/plugins/openapi/src/react/AddOpenApiSource.tsx+113−113
@@ -401,29 +401,29 @@ export default function AddOpenApiSource(props: { · 400 unmodified lines
401401 const result = exit.value;
402402 setPreview(result);
403403
404− const firstServer = result.servers[0];
405− if (firstServer) {
406− setSelectedServerIndex(0);
407− setVariableSelections(defaultSelectionsFor(firstServer));
408− setCustomBaseUrl("");
409− } else {
410− setSelectedServerIndex(-1);
411− setVariableSelections({});
412− setCustomBaseUrl("");
413− }
404+ const firstServer = result.servers[0];
405+ if (firstServer) {
406+ setSelectedServerIndex(0);
407+ setVariableSelections(defaultSelectionsFor(firstServer));
408+ setCustomBaseUrl("");
409+ } else {
410+ setSelectedServerIndex(-1);
411+ setVariableSelections({});
412+ setCustomBaseUrl("");
413+ }
414414
415− const firstPreset = result.headerPresets[0];
416− if (firstPreset) {
417− setStrategy({ kind: "header", presetIndex: 0 });
418− setCustomHeaders(entriesFromSpecPreset(firstPreset));
419− } else {
420− // No header presets — default to "custom" so the headers editor is
421− // visible immediately. Specs with no `security` block (e.g. Microsoft
422− // Graph) would otherwise leave the user staring at just the
423− // Authentication heading with no way to add headers.
424− setStrategy({ kind: "custom" });
425− setCustomHeaders([]);
426− }
415+ const firstPreset = result.headerPresets[0];
416+ if (firstPreset) {
417+ setStrategy({ kind: "header", presetIndex: 0 });
418+ setCustomHeaders(entriesFromSpecPreset(firstPreset));
419+ } else {
420+ // No header presets — default to "custom" so the headers editor is
421+ // visible immediately. Specs with no `security` block (e.g. Microsoft
422+ // Graph) would otherwise leave the user staring at just the
423+ // Authentication heading with no way to add headers.
424+ setStrategy({ kind: "custom" });
425+ setCustomHeaders([]);
426+ }
427427 setAnalyzing(false);
428428 };
429429
@@ -490,126 +490,126 @@ export default function AddOpenApiSource(props: { · 60 unmodified lines
490490 const tokenUrl = resolveOAuthUrl(selectedOAuth2Preset.tokenUrl, resolvedBaseUrl);
491491
492492 if (selectedOAuth2Preset.flow === "clientCredentials") {
493− // RFC 6749 §4.4: no user-interactive consent step. The client_secret
494− // is mandatory; the backend exchanges tokens inline and returns a
495− // completed OAuth2Auth we can attach to the source directly.
496− if (!oauth2ClientSecretSecretId) {
497− setOauth2Error("client_credentials requires a client secret");
498− return;
499− }
500− setStartingOAuth(true);
501− const connectionId = openApiOAuthConnectionId(resolvedSourceId, selectedOAuth2Preset.flow);
493+ // RFC 6749 §4.4: no user-interactive consent step. The client_secret
494+ // is mandatory; the backend exchanges tokens inline and returns a
495+ // completed OAuth2Auth we can attach to the source directly.
496+ if (!oauth2ClientSecretSecretId) {
497+ setOauth2Error("client_credentials requires a client secret");
498+ return;
499+ }
500+ setStartingOAuth(true);
501+ const connectionId = openApiOAuthConnectionId(resolvedSourceId, selectedOAuth2Preset.flow);
502+ const exit = await doStartOAuth({
503+ params: { scopeId },
504+ payload: {
505+ endpoint: tokenUrl,
506+ redirectUrl: tokenUrl,
507+ connectionId,
508+ tokenScope: scopeId,
509+ strategy: {
510+ kind: "client-credentials",
511+ tokenEndpoint: tokenUrl,
512+ clientIdSecretId: oauth2ClientIdSecretId,
513+ clientSecretSecretId: oauth2ClientSecretSecretId,
514+ scopes: [...oauth2SelectedScopes],
515+ },
516+ pluginId: "openapi",
517+ identityLabel: `${displayName} OAuth`,
518+ },
519+ });
520+ setStartingOAuth(false);
521+ if (Exit.isFailure(exit)) {
522+ setOauth2Error(errorMessageFromExit(exit, "Failed to start OAuth"));
523+ return;
524+ }
525+ const response = exit.value;
526+ if (!response.completedConnection) {
527+ setOauth2Error("client_credentials flow did not mint a connection");
528+ return;
529+ }
530+ setOauth2AuthState({
531+ fingerprint: selectedOAuth2Fingerprint,
532+ auth: new OAuth2Auth({
533+ kind: "oauth2",
534+ connectionId: response.completedConnection.connectionId,
535+ securitySchemeName: selectedOAuth2Preset.securitySchemeName,
536+ flow: "clientCredentials",
537+ tokenUrl,
538+ authorizationUrl: null,
539+ clientIdSecretId: oauth2ClientIdSecretId,
540+ clientSecretSecretId: oauth2ClientSecretSecretId,
541+ scopes: [...oauth2SelectedScopes],
542+ }),
543+ });
544+ setOauth2Error(null);
545+ return;
546+ }
547+
548+ const authorizationUrl = resolveOAuthUrl(
549+ Option.getOrElse(selectedOAuth2Preset.authorizationUrl, () => ""),
550+ resolvedBaseUrl,
551+ );
552+ const issuerUrl = inferOAuthIssuerUrl(authorizationUrl);
553+
554+ await oauth.openAuthorization({
555+ run: async () => {
502556 const exit = await doStartOAuth({
503557 params: { scopeId },
504558 payload: {
505− endpoint: tokenUrl,
506− redirectUrl: tokenUrl,
507− connectionId,
559+ endpoint: authorizationUrl,
560+ connectionId: openApiOAuthConnectionId(resolvedSourceId, selectedOAuth2Preset.flow),
508561 tokenScope: scopeId,
562+ redirectUrl: oauth2RedirectUrl,
509563 strategy: {
510− kind: "client-credentials",
564+ kind: "authorization-code",
565+ authorizationEndpoint: authorizationUrl,
511566 tokenEndpoint: tokenUrl,
567+ issuerUrl,
512568 clientIdSecretId: oauth2ClientIdSecretId,
513− clientSecretSecretId: oauth2ClientSecretSecretId,
569+ clientSecretSecretId: oauth2ClientSecretSecretId ?? null,
514570 scopes: [...oauth2SelectedScopes],
515571 },
516572 pluginId: "openapi",
517573 identityLabel: `${displayName} OAuth`,
518574 },
519575 });
520− setStartingOAuth(false);
521576 if (Exit.isFailure(exit)) {
522− setOauth2Error(errorMessageFromExit(exit, "Failed to start OAuth"));
523− return;
577+ // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: OAuth popup API represents start failure by rejecting run()
578+ throw new Error(errorMessageFromExit(exit, "Failed to start OAuth"));
524579 }
525580 const response = exit.value;
526− if (!response.completedConnection) {
527− setOauth2Error("client_credentials flow did not mint a connection");
528− return;
581+ if (response.authorizationUrl === null) {
582+ // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: OAuth popup API represents start failure by rejecting run()
583+ throw new Error("Unexpected response flow from server");
529584 }
585+ return {
586+ sessionId: response.sessionId,
587+ authorizationUrl: response.authorizationUrl,
588+ };
589+ },
590+ onSuccess: (result) => {
530591 setOauth2AuthState({
531592 fingerprint: selectedOAuth2Fingerprint,
532593 auth: new OAuth2Auth({
533594 kind: "oauth2",
534− connectionId: response.completedConnection.connectionId,
595+ connectionId: result.connectionId,
535596 securitySchemeName: selectedOAuth2Preset.securitySchemeName,
536− flow: "clientCredentials",
597+ flow: "authorizationCode",
537598 tokenUrl,
538− authorizationUrl: null,
599+ authorizationUrl,
600+ issuerUrl,
539601 clientIdSecretId: oauth2ClientIdSecretId,
540602 clientSecretSecretId: oauth2ClientSecretSecretId,
541603 scopes: [...oauth2SelectedScopes],
542604 }),
543605 });
544606 setOauth2Error(null);
545− return;
546− }
547−
548− const authorizationUrl = resolveOAuthUrl(
549− Option.getOrElse(selectedOAuth2Preset.authorizationUrl, () => ""),
550− resolvedBaseUrl,
551− );
552− const issuerUrl = inferOAuthIssuerUrl(authorizationUrl);
553−
554− await oauth.openAuthorization({
555− run: async () => {
556− const exit = await doStartOAuth({
557− params: { scopeId },
558− payload: {
559− endpoint: authorizationUrl,
560− connectionId: openApiOAuthConnectionId(resolvedSourceId, selectedOAuth2Preset.flow),
561− tokenScope: scopeId,
562− redirectUrl: oauth2RedirectUrl,
563− strategy: {
564− kind: "authorization-code",
565− authorizationEndpoint: authorizationUrl,
566− tokenEndpoint: tokenUrl,
567− issuerUrl,
568− clientIdSecretId: oauth2ClientIdSecretId,
569− clientSecretSecretId: oauth2ClientSecretSecretId ?? null,
570− scopes: [...oauth2SelectedScopes],
571− },
572− pluginId: "openapi",
573− identityLabel: `${displayName} OAuth`,
574− },
575− });
576− if (Exit.isFailure(exit)) {
577− // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: OAuth popup API represents start failure by rejecting run()
578− throw new Error(errorMessageFromExit(exit, "Failed to start OAuth"));
579− }
580− const response = exit.value;
581− if (response.authorizationUrl === null) {
582− // oxlint-disable-next-line executor/no-try-catch-or-throw, executor/no-error-constructor -- boundary: OAuth popup API represents start failure by rejecting run()
583− throw new Error("Unexpected response flow from server");
584− }
585− return {
586− sessionId: response.sessionId,
587− authorizationUrl: response.authorizationUrl,
588− };
589− },
590− onSuccess: (result) => {
591− setOauth2AuthState({
592− fingerprint: selectedOAuth2Fingerprint,
593− auth: new OAuth2Auth({
594− kind: "oauth2",
595− connectionId: result.connectionId,
596− securitySchemeName: selectedOAuth2Preset.securitySchemeName,
597− flow: "authorizationCode",
598− tokenUrl,
599− authorizationUrl,
600− issuerUrl,
601− clientIdSecretId: oauth2ClientIdSecretId,
602− clientSecretSecretId: oauth2ClientSecretSecretId,
603− scopes: [...oauth2SelectedScopes],
604− }),
605− });
606− setOauth2Error(null);
607− },
608− onError: (message) => {
609− setStartingOAuth(false);
610− setOauth2Error(message);
611− },
612− });
607+ },
608+ onError: (message) => {
609+ setStartingOAuth(false);
610+ setOauth2Error(message);
611+ },
612+ });
613613 }, [
614614 selectedOAuth2Preset,
615615 oauth2ClientIdSecretId,
packages/plugins/openapi/src/react/OpenApiSourceSummary.tsx+1−3
@@ -87,9 +87,7 @@ export default function OpenApiSourceSummary(props: { · 86 unmodified lines
8787 }
8888 const connections = AsyncResult.isSuccess(connectionsResult) ? connectionsResult.value : [];
8989 const liveConnectionIds = new Set(connections.map((connection) => connection.id));
90− const scopeRanks = new Map(
91− scopeStack.map((scope, index) => [scope.id, index] as const),
92− );
90+ const scopeRanks = new Map(scopeStack.map((scope, index) => [scope.id, index] as const));
9391 const credentialTargetScope = userScope;
9492 const missing = missingCredentialLabels(source, bindings, credentialTargetScope, scopeRanks, {
9593 liveConnectionIds,
packages/plugins/openapi/src/sdk/client-credentials-oauth.test.ts+43−37
@@ -8,7 +8,13 @@ · 7 unmodified lines
88
99 import { afterEach, expect, layer } from "@effect/vitest";
1010 import { Effect, Layer, Schema } from "effect";
11−import { HttpApi, HttpApiBuilder, HttpApiEndpoint, HttpApiGroup, OpenApi } from "effect/unstable/httpapi";
11+import {
12+ HttpApi,
13+ HttpApiBuilder,
14+ HttpApiEndpoint,
15+ HttpApiGroup,
16+ OpenApi,
17+} from "effect/unstable/httpapi";
1218 import { FetchHttpClient, HttpRouter, HttpServer, HttpServerRequest } from "effect/unstable/http";
1319 import * as NodeHttpServer from "@effect/platform-node/NodeHttpServer";
1420
@@ -91,36 +97,40 @@ const mockClientCredentialsFetch = (args: { · 76 unmodified lines
9197 readonly expiresIn?: number;
9298 }) => {
9399 let callIndex = 0;
94− globalThis.fetch = Object.assign(async (_input: RequestInfo | URL, init?: RequestInit) => {
95− const url = typeof _input === "string" ? _input : _input.toString();
96− if (!url.includes("token.example.com")) {
97− return originalFetch(_input, init);
98− }
99− const bodyText =
100− init?.body instanceof URLSearchParams
101− ? init.body.toString()
102− : typeof init?.body === "string"
103− ? init.body
104− : "";
105− const params = new URLSearchParams(bodyText);
106− args.calls.push({
107− grantType: params.get("grant_type"),
108− clientId: params.get("client_id"),
109− clientSecret: params.get("client_secret"),
110− scope: params.get("scope"),
111− });
112− const token = args.accessTokens[Math.min(callIndex, args.accessTokens.length - 1)] ?? "unknown";
113− callIndex += 1;
114− const body: Record<string, unknown> = {
115− access_token: token,
116− token_type: "Bearer",
117− };
118− if (typeof args.expiresIn === "number") body.expires_in = args.expiresIn;
119− return new Response(JSON.stringify(body), {
120− status: 200,
121− headers: { "content-type": "application/json" },
122− });
123− }, { preconnect: originalFetch.preconnect });
100+ globalThis.fetch = Object.assign(
101+ async (_input: RequestInfo | URL, init?: RequestInit) => {
102+ const url = typeof _input === "string" ? _input : _input.toString();
103+ if (!url.includes("token.example.com")) {
104+ return originalFetch(_input, init);
105+ }
106+ const bodyText =
107+ init?.body instanceof URLSearchParams
108+ ? init.body.toString()
109+ : typeof init?.body === "string"
110+ ? init.body
111+ : "";
112+ const params = new URLSearchParams(bodyText);
113+ args.calls.push({
114+ grantType: params.get("grant_type"),
115+ clientId: params.get("client_id"),
116+ clientSecret: params.get("client_secret"),
117+ scope: params.get("scope"),
118+ });
119+ const token =
120+ args.accessTokens[Math.min(callIndex, args.accessTokens.length - 1)] ?? "unknown";
121+ callIndex += 1;
122+ const body: Record<string, unknown> = {
123+ access_token: token,
124+ token_type: "Bearer",
125+ };
126+ if (typeof args.expiresIn === "number") body.expires_in = args.expiresIn;
127+ return new Response(JSON.stringify(body), {
128+ status: 200,
129+ headers: { "content-type": "application/json" },
130+ });
131+ },
132+ { preconnect: originalFetch.preconnect },
133+ );
124134 };
125135
126136 afterEach(() => {
@@ -306,17 +316,13 @@ layer(TestLayer)("OpenAPI client_credentials OAuth", (it) => { · 179 unmodified lines
306316
307317 // Access-token secret is owned by the connection and filtered
308318 // out of the user-facing secret list.
309− const userSecretIds = new Set(
310− (yield* userExec.secrets.list()).map((s) => String(s.id)),
311− );
319+ const userSecretIds = new Set((yield* userExec.secrets.list()).map((s) => String(s.id)));
312320 expect(userSecretIds).toContain("petstore_client_id");
313321 expect(userSecretIds).toContain("petstore_client_secret");
314322 expect(userSecretIds).not.toContain(`${auth.connectionId}.access_token`);
315323
316324 // Admin scope sees neither alice's connection nor her token.
317− const adminSecretIds = new Set(
318− (yield* adminExec.secrets.list()).map((s) => String(s.id)),
319− );
325+ const adminSecretIds = new Set((yield* adminExec.secrets.list()).map((s) => String(s.id)));
320326 expect(adminSecretIds).not.toContain(`${auth.connectionId}.access_token`);
321327 }),
322328 );
packages/plugins/openapi/src/sdk/credential-status.test.ts+1−6
@@ -84,12 +84,7 @@ describe("OpenAPI credential status", () => { · 83 unmodified lines
8484 it("does not treat personal bindings as satisfying org-level credential status", () => {
8585 expect(
8686 missingCredentialLabels(source, bindings(userScope, allSlots), orgScope, scopeRanks),
87− ).toEqual([
88− "Authorization",
89− "Client ID",
90− "Client Secret",
91− "OAuth client connection",
92− ]);
87+ ).toEqual(["Authorization", "Client ID", "Client Secret", "OAuth client connection"]);
9388 });
9489
9590 it("prefers the personal binding over a shared org binding", () => {
packages/plugins/openapi/src/sdk/credential-status.ts+5−21
@@ -33,15 +33,9 @@ export const effectiveBindingForScope = ( · 32 unmodified lines
3333 ): BindingRowForCredentialStatus | null =>
3434 rows
3535 .filter(
36− (row) =>
37− row.slot === slot &&
38− scopeRank(ranks, row.scopeId) >= scopeRank(ranks, targetScope),
36+ (row) => row.slot === slot && scopeRank(ranks, row.scopeId) >= scopeRank(ranks, targetScope),
3937 )
40− .sort(
41− (a, b) =>
42− scopeRank(ranks, a.scopeId) -
43− scopeRank(ranks, b.scopeId),
44− )[0] ?? null;
38+ .sort((a, b) => scopeRank(ranks, a.scopeId) - scopeRank(ranks, b.scopeId))[0] ?? null;
4539
4640 const hasSecretBinding = (
4741 rows: readonly BindingRowForCredentialStatus[],
@@ -59,9 +53,7 @@ const hasConnectionBinding = ( · 11 unmodified lines
5953 ) => {
6054 const binding = effectiveBindingForScope(rows, slot, targetScope, ranks);
6155 if (binding?.value.kind !== "connection") return false;
62− return liveConnectionIds
63− ? liveConnectionIds.has(binding.value.connectionId)
64− : true;
56+ return liveConnectionIds ? liveConnectionIds.has(binding.value.connectionId) : true;
6557 };
6658
6759 const effectiveClientSecretSlot = (oauth2: {
@@ -106,17 +98,9 @@ export function missingCredentialLabels( · 38 unmodified lines
10698 }
10799
108100 if (
109− !hasConnectionBinding(
110− bindings,
111− oauth2.connectionSlot,
112− targetScope,
113− ranks,
114− liveConnectionIds,
115− )
101+ !hasConnectionBinding(bindings, oauth2.connectionSlot, targetScope, ranks, liveConnectionIds)
116102 ) {
117− missing.push(
118− oauth2.flow === "clientCredentials" ? "OAuth client connection" : "OAuth sign-in",
119− );
103+ missing.push(oauth2.flow === "clientCredentials" ? "OAuth client connection" : "OAuth sign-in");
120104 }
121105
122106 return missing;
packages/plugins/openapi/src/sdk/extract.ts+2−6
@@ -124,9 +124,7 @@ const extractRequestBody = ( · 123 unmodified lines
124124 contentType: mediaType,
125125 schema: Option.fromNullishOr(media.schema),
126126 encoding: Option.fromNullishOr(
127− buildEncodingRecord(
128− (media as { encoding?: Record<string, unknown> }).encoding,
129− ),
127+ buildEncodingRecord((media as { encoding?: Record<string, unknown> }).encoding),
130128 ),
131129 }),
132130 );
@@ -246,9 +244,7 @@ const extractServers = (doc: ParsedDocument): ServerInfo[] => · 113 unmodified lines
246244 new ServerVariable({
247245 default: String(v.default),
248246 enum:
249− enumValues && enumValues.length > 0
250− ? Option.some(enumValues)
251− : Option.none(),
247+ enumValues && enumValues.length > 0 ? Option.some(enumValues) : Option.none(),
252248 description: Option.fromNullishOr(v.description),
253249 }),
254250 ],
packages/plugins/openapi/src/sdk/form-urlencoded-body.test.ts+52−58
@@ -36,14 +36,12 @@ const memoryProvider: SecretProvider = (() => { · 35 unmodified lines
3636 return {
3737 key: "memory",
3838 writable: true,
39− get: (id, scope) =>
40− Effect.sync(() => store.get(`${scope}\u0000${id}`) ?? null),
39+ get: (id, scope) => Effect.sync(() => store.get(`${scope}\u0000${id}`) ?? null),
4140 set: (id, value, scope) =>
4241 Effect.sync(() => {
4342 store.set(`${scope}\u0000${id}`, value);
4443 }),
45− delete: (id, scope) =>
46− Effect.sync(() => store.delete(`${scope}\u0000${id}`)),
44+ delete: (id, scope) => Effect.sync(() => store.delete(`${scope}\u0000${id}`)),
4745 list: () => Effect.sync(() => []),
4846 };
4947 })();
@@ -61,31 +59,29 @@ type Captured = { · 11 unmodified lines
6159
6260 const startEchoServer = () =>
6361 Effect.acquireRelease(
64− Effect.callback<{ baseUrl: string; captured: Captured; close: () => void }>(
65− (resume) => {
66− const captured: Captured = { contentType: "", body: "" };
67− const server = createServer((req, res) => {
68− const chunks: Buffer[] = [];
69− req.on("data", (c: Buffer) => chunks.push(c));
70− req.on("end", () => {
71− captured.contentType = req.headers["content-type"] ?? "";
72− captured.body = Buffer.concat(chunks).toString("utf8");
73− res.writeHead(200, { "content-type": "application/json" });
74− res.end(JSON.stringify({ ok: true }));
75− });
62+ Effect.callback<{ baseUrl: string; captured: Captured; close: () => void }>((resume) => {
63+ const captured: Captured = { contentType: "", body: "" };
64+ const server = createServer((req, res) => {
65+ const chunks: Buffer[] = [];
66+ req.on("data", (c: Buffer) => chunks.push(c));
67+ req.on("end", () => {
68+ captured.contentType = req.headers["content-type"] ?? "";
69+ captured.body = Buffer.concat(chunks).toString("utf8");
70+ res.writeHead(200, { "content-type": "application/json" });
71+ res.end(JSON.stringify({ ok: true }));
7672 });
77− server.listen(0, "127.0.0.1", () => {
78− const port = (server.address() as AddressInfo).port;
79− resume(
80− Effect.succeed({
81− baseUrl: `http://127.0.0.1:${port}`,
82− captured,
83− close: () => server.close(),
84− }),
85− );
86− });
87− },
88− ),
73+ });
74+ server.listen(0, "127.0.0.1", () => {
75+ const port = (server.address() as AddressInfo).port;
76+ resume(
77+ Effect.succeed({
78+ baseUrl: `http://127.0.0.1:${port}`,
79+ captured,
80+ close: () => server.close(),
81+ }),
82+ );
83+ });
84+ }),
8985 (s) => Effect.sync(() => s.close()),
9086 );
9187
@@ -130,40 +126,38 @@ const formSpec = JSON.stringify({ · 38 unmodified lines
130126 });
131127
132128 describe("OpenAPI non-JSON request body serialization", () => {
133− it.effect(
134− "form-urlencoded object body is properly encoded (no '[object Object]')",
135− () =>
136− Effect.gen(function* () {
137− const { baseUrl, captured } = yield* startEchoServer();
129+ it.effect("form-urlencoded object body is properly encoded (no '[object Object]')", () =>
130+ Effect.gen(function* () {
131+ const { baseUrl, captured } = yield* startEchoServer();
138132
139− const executor = yield* createExecutor(
140− makeTestConfig({
141− plugins: [
142− openApiPlugin({ httpClientLayer: FetchHttpClient.layer }),
143− memorySecretsPlugin(),
144− ] as const,
145− }),
146− );
133+ const executor = yield* createExecutor(
134+ makeTestConfig({
135+ plugins: [
136+ openApiPlugin({ httpClientLayer: FetchHttpClient.layer }),
137+ memorySecretsPlugin(),
138+ ] as const,
139+ }),
140+ );
147141
148− yield* executor.openapi.addSpec({
149− spec: formSpec,
150− scope: TEST_SCOPE,
151− namespace: "form",
152− baseUrl,
153− });
142+ yield* executor.openapi.addSpec({
143+ spec: formSpec,
144+ scope: TEST_SCOPE,
145+ namespace: "form",
146+ baseUrl,
147+ });
154148
155− yield* executor.tools.invoke(
156− "form.forms.submit",
157− { body: { name: "Acme", email: "a@b.com" } },
158− autoApprove,
159− );
149+ yield* executor.tools.invoke(
150+ "form.forms.submit",
151+ { body: { name: "Acme", email: "a@b.com" } },
152+ autoApprove,
153+ );
160154
161− expect(captured.contentType).toBe("application/x-www-form-urlencoded");
162− expect(captured.body).not.toBe("[object Object]");
155+ expect(captured.contentType).toBe("application/x-www-form-urlencoded");
156+ expect(captured.body).not.toBe("[object Object]");
163157
164− const parsed = new URLSearchParams(captured.body);
165− expect(parsed.get("name")).toBe("Acme");
166− expect(parsed.get("email")).toBe("a@b.com");
167− }),
158+ const parsed = new URLSearchParams(captured.body);
159+ expect(parsed.get("name")).toBe("Acme");
160+ expect(parsed.get("email")).toBe("a@b.com");
161+ }),
168162 );
169163 });
packages/plugins/openapi/src/sdk/index.test.ts+4−3
@@ -195,9 +195,10 @@ describe("OpenAPI plugin", () => { · 194 unmodified lines
195195
196196 const vars = Option.getOrThrow(server.variables);
197197 expect(vars.region!.default).toBe("us");
198− expect(
199− Option.getOrElse(vars.region!.enum, () => [] as readonly string[]),
200− ).toEqual(["us", "de"]);
198+ expect(Option.getOrElse(vars.region!.enum, () => [] as readonly string[])).toEqual([
199+ "us",
200+ "de",
201+ ]);
201202 expect(Option.getOrElse(vars.region!.description, () => "")).toBe(
202203 "The data-storage-location for an organization",
203204 );
packages/plugins/openapi/src/sdk/invoke.ts+7−20
@@ -191,9 +191,7 @@ const isXmlContentType = (ct: string | null | undefined): boolean => { · 190 unmodified lines
191191 const normalized = normalizeContentType(ct);
192192 if (!normalized) return false;
193193 return (
194− normalized === "application/xml" ||
195− normalized === "text/xml" ||
196− normalized.endsWith("+xml")
194+ normalized === "application/xml" || normalized === "text/xml" || normalized.endsWith("+xml")
197195 );
198196 };
199197
@@ -291,8 +289,7 @@ const serializeFormUrlEncoded = ( · 91 unmodified lines
291289 parts.push(`${encKey}=${encodeFormValue(v, allowReserved)}`);
292290 }
293291 } else {
294− const sep =
295− style === "spaceDelimited" ? " " : style === "pipeDelimited" ? "|" : ",";
292+ const sep = style === "spaceDelimited" ? " " : style === "pipeDelimited" ? "|" : ",";
296293 parts.push(
297294 `${encKey}=${encodeFormValue(
298295 raw.map((v) => (typeof v === "object" ? JSON.stringify(v) : String(v))).join(sep),
@@ -313,18 +310,13 @@ const serializeFormUrlEncoded = ( · 14 unmodified lines
313310 // `%5B` / `%5D`. Matches swagger-client's behaviour and remains
314311 // accepted by common server-side parsers (qs, Rails, etc.).
315312 parts.push(
316− `${encodeURIComponent(`${key}[${subkey}]`)}=${encodeFormValue(
317− subval,
318− allowReserved,
319− )}`,
313+ `${encodeURIComponent(`${key}[${subkey}]`)}=${encodeFormValue(subval, allowReserved)}`,
320314 );
321315 }
322316 } else if (explode) {
323317 // form + explode=true on object: sub-keys become top-level fields.

40 more lines aren’t shown here.

packages/plugins/openapi/src/sdk/multi-scope-bearer.test.ts+406−424

This diff is large: its 901 lines aren’t shown here.

packages/plugins/openapi/src/sdk/multi-scope-oauth.test.ts+71−57

This diff is large: its 165 lines aren’t shown here.

packages/plugins/openapi/src/sdk/non-json-body.test.ts+29−53

This diff is large: its 136 lines aren’t shown here.

packages/plugins/openapi/src/sdk/openapi-utils.ts+2−7

This diff is large: its 23 lines aren’t shown here.

packages/plugins/openapi/src/sdk/plugin.test.ts+101−119

This diff is large: its 306 lines aren’t shown here.

packages/plugins/openapi/src/sdk/preview-oauth2.test.ts+1−3

This diff is large: its 11 lines aren’t shown here.

packages/plugins/openapi/src/sdk/preview.ts+4−10

This diff is large: its 35 lines aren’t shown here.

packages/plugins/openapi/tsconfig.json+1−4

This diff is large: its 10 lines aren’t shown here.

packages/plugins/workos-vault/src/react/WorkOSVaultSettings.tsx+2−2

This diff is large: its 11 lines aren’t shown here.

packages/plugins/workos-vault/src/sdk/plugin.ts+26−27

This diff is large: its 66 lines aren’t shown here.

packages/plugins/workos-vault/src/sdk/secret-store.test.ts+102−131

This diff is large: its 303 lines aren’t shown here.

packages/plugins/workos-vault/src/sdk/secret-store.ts+26−49

This diff is large: its 140 lines aren’t shown here.

packages/plugins/workos-vault/src/sdk/testing.ts+11−31

This diff is large: its 97 lines aren’t shown here.

packages/react/src/api/reactivity-keys.tsx+2−8

This diff is large: its 24 lines aren’t shown here.

packages/react/src/components/calendar.tsx+1−4

This diff is large: its 12 lines aren’t shown here.

packages/react/src/components/chart.tsx+1−4

This diff is large: its 12 lines aren’t shown here.

packages/react/src/components/code-block.tsx+1−3

This diff is large: its 11 lines aren’t shown here.

packages/react/src/components/markdown.tsx+3−1

This diff is large: its 11 lines aren’t shown here.

packages/react/src/components/schema-explorer.tsx+1−5

This diff is large: its 13 lines aren’t shown here.

packages/react/src/components/tool-tree.tsx+5−9

This diff is large: its 31 lines aren’t shown here.

packages/react/src/hooks/use-policy-actions.ts+7−6

This diff is large: its 20 lines aren’t shown here.

packages/react/src/pages/secrets.tsx+3−13

This diff is large: its 37 lines aren’t shown here.

packages/react/src/plugins/headers-list.tsx+1−3

This diff is large: its 11 lines aren’t shown here.

packages/react/src/plugins/namespace.ts+1−3

This diff is large: its 9 lines aren’t shown here.

packages/react/src/plugins/secret-id.test.ts+2−8

This diff is large: its 23 lines aren’t shown here.

packages/react/src/plugins/secret-id.tsx+1−4

This diff is large: its 12 lines aren’t shown here.

packages/react/src/plugins/source-identity.tsx+1−5

This diff is large: its 13 lines aren’t shown here.

scripts/check-release-notes.ts+2−8

This diff is large: its 23 lines aren’t shown here.

scripts/oxlint-plugin-executor/rules/no-redundant-primitive-cast.js+2−9

This diff is large: its 25 lines aren’t shown here.

scripts/oxlint-plugin-executor/rules/no-unsupported-effect-api.js+1−2

This diff is large: its 10 lines aren’t shown here.

scripts/oxlint-plugin-executor/utils.js+3−3

This diff is large: its 20 lines aren’t shown here.

scripts/publish-packages.ts+2−6

This diff is large: its 22 lines aren’t shown here.

tests/daemon-bootstrap.test.ts+30−23

This diff is large: its 61 lines aren’t shown here.

tests/daemon-state.test.ts+2−1

This diff is large: its 10 lines aren’t shown here.

tests/presets-reachable.test.ts+2−1

This diff is large: its 10 lines aren’t shown here.

tests/release-bootstrap-smoke.test.ts+118−144

This diff is large: its 283 lines aren’t shown here.

tests/source-identity.test.ts+1−4

This diff is large: its 11 lines aren’t shown here.

tests/tools-cli.test.ts+10−6

This diff is large: its 40 lines aren’t shown here.