Unified · Split
| @@ -12,7 +12,7 @@ import { · 11 unmodified lines | |||
| 12 | } from "@executor-js/api/server"; | 12 | } from "@executor-js/api/server"; |
| 13 | import { makeDynamicWorkerExecutor } from "@executor-js/runtime-dynamic-worker"; | 13 | import { makeDynamicWorkerExecutor } from "@executor-js/runtime-dynamic-worker"; |
| 14 | import { makeQuickJsExecutor } from "@executor-js/runtime-quickjs"; | 14 | import { makeQuickJsExecutor } from "@executor-js/runtime-quickjs"; |
| 15 | import { env } from "cloudflare:workers"; | 15 | import { env, waitUntil } from "cloudflare:workers"; |
| 16 | 16 | ||
| 17 | import type { CloudflareConfig } from "./config"; | 17 | import type { CloudflareConfig } from "./config"; |
| 18 | import { makeCloudflarePlugins } from "./plugins"; | 18 | import { makeCloudflarePlugins } from "./plugins"; |
| @@ -56,6 +56,12 @@ export const makeCloudflareHostConfig = (config: CloudflareConfig): Layer.Layer< · 37 unmodified lines | |||
| 56 | allowLocalNetwork: config.allowLocalNetwork, | 56 | allowLocalNetwork: config.allowLocalNetwork, |
| 57 | webBaseUrl: config.webBaseUrl, | 57 | webBaseUrl: config.webBaseUrl, |
| 58 | oauthCallbackPath: "/api/oauth/callback", | 58 | oauthCallbackPath: "/api/oauth/callback", |
| 59 | // A Worker, so a tools read's speculative catalog refresh needs `waitUntil` | ||
| 60 | // to survive the response at all. Unlike cloud's Hyperdrive plane there is | ||
| 61 | // nothing to drain it before: the storage here is a D1 binding read | ||
| 62 | // straight off `env`, with no pool and no close finalizer, so a detached | ||
| 63 | // batch still has everything it needs after the response is written. | ||
| 64 | deferToolSync: (task) => Effect.sync(() => waitUntil(Effect.runPromise(task))), | ||
| 59 | }); | 65 | }); |
| 60 | 66 | ||
| 61 | /** | 67 | /** |
| @@ -1,4 +1,4 @@ | |||
| 1 | import { Layer } from "effect"; | 1 | import { Effect, Layer } from "effect"; |
| 2 | 2 | ||
| 3 | import { | 3 | import { |
| 4 | CodeExecutorProvider, | 4 | CodeExecutorProvider, |
| @@ -60,6 +60,13 @@ export const SelfHostHostConfig: Layer.Layer<HostConfig> = Layer.sync(HostConfig · 55 unmodified lines | |||
| 60 | event.kind === "added" ? "integration_added" : "integration_removed", | 60 | event.kind === "added" ? "integration_added" : "integration_removed", |
| 61 | { plugin_key: event.pluginKey }, | 61 | { plugin_key: event.pluginKey }, |
| 62 | ), | 62 | ), |
| 63 | // Self-host is one long-lived process over one long-lived libSQL handle, so | ||
| 64 | // a speculative catalog refresh just goes to a detached fiber: the handle | ||
| 65 | // it needs outlives every request, and there is no isolate to keep alive. | ||
| 66 | // Detached, not `forkScoped` — the request scope this is called from closes | ||
| 67 | // as soon as the response is written, which is precisely what the work has | ||
| 68 | // to outlive. The task is total by contract, so nothing escapes the fork. | ||
| 69 | deferToolSync: (task) => Effect.asVoid(Effect.forkDetach(task)), | ||
| 63 | }; | 70 | }; |
| 64 | }); | 71 | }); |
| 65 | 72 | ||
| @@ -201,6 +201,11 @@ const createLocalExecutorLayer = (options: LocalExecutorOptions = {}) => { · 200 unmodified lines | |||
| 201 | event.kind === "added" ? "integration_added" : "integration_removed", | 201 | event.kind === "added" ? "integration_added" : "integration_removed", |
| 202 | { plugin_key: event.pluginKey }, | 202 | { plugin_key: event.pluginKey }, |
| 203 | ), | 203 | ), |
| 204 | // The daemon owns its sqlite handle for its whole lifetime, so a tools | ||
| 205 | // read's speculative catalog refresh goes to a detached fiber and the | ||
| 206 | // read answers without waiting on an MCP server's handshake. The task | ||
| 207 | // is total by contract, so nothing escapes the fork. | ||
| 208 | deferToolSync: (task) => Effect.asVoid(Effect.forkDetach(task)), | ||
| 204 | onElicitation: "accept-all", | 209 | onElicitation: "accept-all", |
| 205 | oauthEndpointUrlPolicy: { allowHttp: true }, | 210 | oauthEndpointUrlPolicy: { allowHttp: true }, |
| 206 | // EXPLICIT OAuth callback — the daemon serves the v2 `/api/oauth/callback` | 211 | // EXPLICIT OAuth callback — the daemon serves the v2 `/api/oauth/callback` |