@open-everywhere/adapters 0.1.5 โ†’ 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/NOTICE +9 -9
  2. package/README.md +9 -1
  3. package/agent/browser/sandbox.ts +27 -5
  4. package/agent/instrumentation.ts +95 -36
  5. package/agent/model/approval-judge.ts +1 -1
  6. package/agent/oe-oauth/config.ts +1 -1
  7. package/agent/oe-oauth/token-vault.ts +3 -2
  8. package/agent/register-defaults.ts +276 -0
  9. package/agent/sandbox/vercel-lifecycle.ts +22 -1
  10. package/dist/agent/browser/sandbox.d.ts +8 -2
  11. package/dist/agent/browser/sandbox.d.ts.map +1 -1
  12. package/dist/agent/browser/sandbox.js +19 -2
  13. package/dist/agent/browser/sandbox.js.map +1 -1
  14. package/dist/agent/instrumentation.d.ts +24 -4
  15. package/dist/agent/instrumentation.d.ts.map +1 -1
  16. package/dist/agent/instrumentation.js +83 -34
  17. package/dist/agent/instrumentation.js.map +1 -1
  18. package/dist/agent/model/approval-judge.d.ts +1 -1
  19. package/dist/agent/model/approval-judge.js +1 -1
  20. package/dist/agent/oe-oauth/config.d.ts +1 -1
  21. package/dist/agent/oe-oauth/config.d.ts.map +1 -1
  22. package/dist/agent/oe-oauth/token-vault.d.ts.map +1 -1
  23. package/dist/agent/oe-oauth/token-vault.js +4 -2
  24. package/dist/agent/oe-oauth/token-vault.js.map +1 -1
  25. package/dist/agent/register-defaults.d.ts +76 -0
  26. package/dist/agent/register-defaults.d.ts.map +1 -0
  27. package/dist/agent/register-defaults.js +186 -0
  28. package/dist/agent/register-defaults.js.map +1 -0
  29. package/dist/agent/sandbox/vercel-lifecycle.d.ts.map +1 -1
  30. package/dist/agent/sandbox/vercel-lifecycle.js +20 -1
  31. package/dist/agent/sandbox/vercel-lifecycle.js.map +1 -1
  32. package/package.json +10 -5
package/NOTICE CHANGED
@@ -12,28 +12,28 @@ Apache License, Version 2.0.
12
12
 
13
13
  Modifications to eve
14
14
 
15
- The @open-everywhere/core package distributes patches/eve+0.54.3.patch. That
16
- patch file is a modification of eve 0.54.3. Vercel licenses eve 0.54.3 under the
17
- Apache License, Version 2.0. The copyright notice of eve 0.54.3 reads
15
+ The @open-everywhere/core package distributes patches/eve+0.71.0.patch. That
16
+ patch file is a modification of eve 0.71.0. Vercel licenses eve 0.71.0 under the
17
+ Apache License, Version 2.0. The copyright notice of eve 0.71.0 reads
18
18
  "Copyright 2026 Vercel, Inc. and contributors". The Open Everywhere authors made
19
19
  the changes in the patch file.
20
20
 
21
- The patch modifies six files of eve 0.54.3:
21
+ The patch modifies four files of eve 0.71.0:
22
22
 
23
- dist/src/context/dynamic-tool-lifecycle.js
24
23
  dist/src/execution/web-fetch/request.js
25
24
  dist/src/harness/tool-loop.js
26
25
  dist/src/internal/authored-package-boundary.js
27
26
  dist/src/protocol/message.d.ts
28
- dist/src/tasks/session-index.js
29
27
 
30
28
  Open Everywhere does not ship the eve package. eve is a peer dependency, so the
31
- consumer installs eve 0.54.3 from the registry. The patch file carries the
29
+ consumer installs eve 0.71.0 from the registry. The patch file carries the
32
30
  modified portions of eve. The oe-eve-patch command, which
33
31
  @open-everywhere/core ships, applies them to the eve that the consumer
34
- installed. The command writes no file outside the consumer's own project.
32
+ installed. The command patches no file outside the consumer's own project. To
33
+ restore an eve that holds an older Open Everywhere patch, it also writes to npm's
34
+ cache and to a temporary directory.
35
35
 
36
- The attribution notice of eve 0.54.3 follows, as section 4(d) of the Apache
36
+ The attribution notice of eve 0.71.0 follows, as section 4(d) of the Apache
37
37
  License, Version 2.0 requires. The notices for eve's bundled code-mode runtime
38
38
  are not reproduced, because the patch modifies no part of that runtime.
39
39
 
package/README.md CHANGED
@@ -17,10 +17,18 @@ It implements core's host seams against concrete services:
17
17
  Core defines these seams and ships no host of its own, so a different host is a different adapters
18
18
  package rather than a fork of the runtime.
19
19
 
20
+ ## Boot wiring
21
+
22
+ `registerDefaultAdapters()` (`@open-everywhere/adapters/agent/register-defaults.js`) wires every
23
+ default adapter, the app's selected provider bundles and the operational store, with the store
24
+ selection rules and the floors. An app's `agent/lib/register-adapters.ts` calls it once and adds
25
+ its own adapters in the `customAdapters` hook. See
26
+ [the app's adapter wiring](https://github.com/open-everywhere/open-everywhere/blob/main/docs/extending/adapter-model.md#the-apps-adapter-wiring).
27
+
20
28
  ## Install
21
29
 
22
30
  ```sh
23
- npm i @open-everywhere/core @open-everywhere/adapters eve@0.54.3
31
+ npm i @open-everywhere/core @open-everywhere/adapters eve@0.71.0
24
32
  npx oe-eve-patch
25
33
  ```
26
34
 
@@ -1,16 +1,32 @@
1
1
  // The agent-browser sandbox pre-bake โ€” Vercel-only, and mandatory: a session sits behind the deny-all egress
2
- // firewall, so no mid-turn install can work. eve freezes `revalidationKey` at BUILD, so arm it there too.
2
+ // firewall, so no mid-turn install can work, so the install has to happen at BUILD.
3
+ //
4
+ // ๐Ÿ”ด eve 0.69.0 REMOVED `revalidationKey` (OPE-2247), so the key below reaches nothing. The sandbox
5
+ // definition used to pass it to eve, which froze it into the template identity; 0.69.0 keys a template
6
+ // by `sha256(stableSerialize(environment options))` instead, and the app now passes the bake as the
7
+ // environment's `prepare` (`agent/sandbox/sandbox.ts`). `stableSerialize` renders a function as its
8
+ // SOURCE TEXT, so arming or disarming the bake re-keys the template and editing the prepare body
9
+ // re-keys it, but a change to the agent-browser PIN alone does not โ€” the pin lives inside the vendor
10
+ // installer, behind a closure the hash cannot see. The key is kept, unused, because it is still the
11
+ // honest answer to "what would re-key this bake" and the re-derivation needs it; see the ๐Ÿ”ด note in
12
+ // `agent/sandbox/sandbox.ts`, which carries the whole account and the follow-up.
3
13
 
4
- import { agentBrowserRevalidationKey, installAgentBrowser } from "@agent-browser/eve/sandbox";
14
+ import { agentBrowserRevalidationKey, installAgentBrowser, type EveSandboxSession } from "@agent-browser/eve/sandbox";
5
15
  import { agentBrowserEnabled, loadAgentBrowserConfig } from "./config.js";
6
16
 
7
- /** Minimal structural sandbox the installer needs (matches @agent-browser/eve's EveSandboxSession). */
17
+ /** Minimal structural sandbox the installer needs. `id` is OPTIONAL deliberately: eve 0.69.0's
18
+ * `prepare` hands over a `SandboxSession`, which carries no `id` at all, and `installAgentBrowser`
19
+ * reads only `run` โ€” `sandbox.id` appears once in the vendor bundle, inside `runAgentBrowser` (the
20
+ * TOOL path). Keeping it required would make the caller's cast load-bearing and hide the day a real
21
+ * `id` dependency comes back. @agent-browser/eve's own `EveSandboxSession` still requires it, so the
22
+ * one cast at the `installAgentBrowser` boundary stays and is commented there. */
8
23
  export interface BakeableSandbox {
9
- readonly id: string;
24
+ readonly id?: string;
10
25
  run(options: { readonly command: string }): PromiseLike<{ readonly exitCode?: number; readonly stderr?: string; readonly stdout?: string }>;
11
26
  }
12
27
 
13
28
  export interface AgentBrowserSandboxHooks {
29
+ /** ๐Ÿ”ด DEAD SINCE eve 0.69.0 (OPE-2247) โ€” nothing reads it. See this file's header. */
14
30
  readonly revalidationKey: () => string;
15
31
  readonly bootstrap: (input: { use: () => Promise<BakeableSandbox> }) => Promise<void>;
16
32
  }
@@ -20,7 +36,13 @@ export interface AgentBrowserSandboxHooks {
20
36
  * vendor installer: system deps โ†’ `npm install -g agent-browser@<pinned>` โ†’ `agent-browser install`).
21
37
  */
22
38
  export function agentBrowserSandboxHooks(
23
- install: (sandbox: BakeableSandbox) => Promise<unknown> = (sandbox) => installAgentBrowser(sandbox),
39
+ // ๐Ÿ”ด THE ONE CAST, AND IT IS HERE ON PURPOSE. The vendor's `EveSandboxSession` requires an `id`;
40
+ // `installAgentBrowser` never reads one (verified in the vendor bytes: `sandbox.id` appears once, in
41
+ // `runAgentBrowser`, which is the TOOL path), and eve 0.69.0's `prepare` has no `id` to give. So the
42
+ // widening sits at THIS boundary, where the mismatch actually is, rather than at the caller โ€” which
43
+ // is what makes `BakeableSandbox.id` optional honest instead of a hole: the day the installer does
44
+ // read an `id`, this line is the one that has to be argued with.
45
+ install: (sandbox: BakeableSandbox) => Promise<unknown> = (sandbox) => installAgentBrowser(sandbox as EveSandboxSession),
24
46
  ): AgentBrowserSandboxHooks | Record<string, never> {
25
47
  if (!agentBrowserEnabled()) return {};
26
48
  // ๐Ÿ”ด ONE ROAD, NOT TWO (OPE-1793). The platform refusal lives in the config loader, which every
@@ -1,42 +1,101 @@
1
- // OpenTelemetry tracing through eve's instrumentation seam; the vendor SDK is why this lives in adapters.
2
- // A provider registers only when a collector is configured, and NO model input or output reaches a span.
3
- // eve's `defineInstrumentation` accepts `recordInputs` and `recordOutputs`; it normalises each as
4
- // `=== true`, so an absent field reads FALSE. This adapter sets neither, so enabling model content
5
- // takes action โ€” suppressing it does not. A fork that sets them true still meets eve's audience
6
- // ceiling (`applyAudienceCeiling`, eve `shared/instrumentation-content.js`), which forces both off
7
- // for every channel whose audience is not `public` (an `unknown` audience records only inside eve's
8
- // own dev environment).
9
-
10
- import { defineInstrumentation } from "eve/instrumentation";
11
- import { registerOTel } from "@vercel/otel";
1
+ // OpenTelemetry through eve's own pipeline; the vendor SDK (`@vercel/otel` exporters) is why this lives in adapters.
2
+ //
3
+ // eve 0.71 owns the OpenTelemetry lifecycle: it registers ONE tracer provider for the process, from
4
+ // the providers in `agent/instrumentation/`. On Vercel preview and production it also seeds its Agent
5
+ // Runs destination. A second `registerOTel` call kills the boot ("another runtime already owns the
6
+ // global tracer provider", OPE-2262), so this module never registers anything itself. It declares:
7
+ //
8
+ // - `default` (the app's `otel` slot): the process-wide trace policy, plus the boot `setup`.
9
+ // The policy emits every trace with NO model input and NO model output, for every audience in
10
+ // every environment. Without it, eve records content for public conversations and in
11
+ // development. A destination cannot restore what this ceiling removes.
12
+ // - `otlpDestination` (the app's `otlp` slot): the consumer's OTLP collector, from the standard
13
+ // `OTEL_EXPORTER_OTLP_*` variables, as an eve destination. With no endpoint it is disabled.
14
+ //
15
+ // `scripts/instrumentation-hosted-boot-test.ts` boots both slots through eve's registration path
16
+ // with the env Vercel creates.
17
+
18
+ import { disableInstrumentation } from "eve/instrumentation";
19
+ import { otel, otelIntegration, type OtelDeclaration, type SpanExporter } from "eve/instrumentation/otel";
20
+ import { OTLPHttpJsonTraceExporter, OTLPHttpProtoTraceExporter } from "@vercel/otel";
12
21
  import { installLogShipping, initTelemetry } from "@open-everywhere/core/agent/adapter-api.js";
13
22
 
23
+ /** Metadata only: emit the span, record neither content direction. */
24
+ const METADATA_ONLY = { emit: true, recordInputs: false, recordOutputs: false } as const;
25
+
14
26
  /**
15
- * A collector is configured when a standard OTLP endpoint env var is set, or when running on Vercel, where
16
- * `registerOTel` auto-wires the built-in collector. Neither holds locally or in CI.
27
+ * Boot side effects. eve awaits every provider's `setup` before it loads the server entry.
28
+ * `OtelDeclaration` is an eve `InstrumentationProvider`, so it carries `setup` like any other.
17
29
  */
18
- function collectorConfigured(): boolean {
19
- return Boolean(
20
- process.env.OTEL_EXPORTER_OTLP_ENDPOINT ||
21
- process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ||
22
- process.env.VERCEL,
23
- );
30
+ function setup(): void {
31
+ // Structured log shipping (vendor-neutral HTTP ingest; core seam). Its own env gate
32
+ // (OE_LOG_SHIP_URL); console is untouched when that is unset.
33
+ installLogShipping();
34
+ // Anonymous usage telemetry (default ON; core seam, own env gate โ€” OE_TELEMETRY/DO_NOT_TRACK).
35
+ // Prints the boot disclosure line before anything is transmitted. Fire-and-forget: boot never waits.
36
+ void initTelemetry();
24
37
  }
25
38
 
26
- export default defineInstrumentation({
27
- setup({ agentName }) {
28
- // Structured log shipping (vendor-neutral HTTP ingest; core seam). Independent of
29
- // the OTel collector check below: it has its own env gate (OE_LOG_SHIP_URL) and is
30
- // a full no-op โ€” console untouched โ€” when that is unset.
31
- installLogShipping();
32
- // Anonymous usage telemetry (default ON; core seam, own env gate โ€” OE_TELEMETRY/
33
- // DO_NOT_TRACK). Prints the boot disclosure line before anything is transmitted;
34
- // fire-and-forget (this callback is synchronous, must never block boot).
35
- initTelemetry();
36
- if (!collectorConfigured()) return;
37
- // serviceName = the agent name from `defineAgent`, per Eve's guidance. Exporter
38
- // wiring (endpoint, headers, protocol) comes from the standard `OTEL_EXPORTER_OTLP_*`
39
- // env vars that `@vercel/otel` reads.
40
- registerOTel({ serviceName: agentName });
41
- },
42
- });
39
+ const declaration: OtelDeclaration = { ...otel({ tracePolicy: () => METADATA_ONLY }), setup };
40
+ export default declaration;
41
+
42
+ /** The OTLP traces protocols `@vercel/otel` exports over HTTP. */
43
+ type OtlpProtocol = "http/protobuf" | "http/json";
44
+
45
+ /** The OTLP target the standard env vars name, or `undefined` when no endpoint is set. */
46
+ export interface OtlpTarget {
47
+ readonly url: string;
48
+ readonly protocol: OtlpProtocol;
49
+ readonly headers: Readonly<Record<string, string>>;
50
+ }
51
+
52
+ /**
53
+ * Parse an `OTEL_EXPORTER_OTLP_*HEADERS` value: comma-separated `key=value` pairs, each value
54
+ * percent-encoded (OpenTelemetry SDK environment specification). A pair without `=` is skipped.
55
+ */
56
+ export function parseOtlpHeaders(raw: string | undefined): Record<string, string> {
57
+ const headers: Record<string, string> = {};
58
+ for (const pair of (raw ?? "").split(",")) {
59
+ const at = pair.indexOf("=");
60
+ if (at <= 0) continue;
61
+ const key = pair.slice(0, at).trim();
62
+ const value = pair.slice(at + 1).trim();
63
+ if (key === "") continue;
64
+ try {
65
+ headers[key] = decodeURIComponent(value);
66
+ } catch {
67
+ headers[key] = value;
68
+ }
69
+ }
70
+ return headers;
71
+ }
72
+
73
+ /**
74
+ * Resolve the OTLP traces target with the standard precedence: the traces-specific variable over the
75
+ * general one, and the general endpoint gets `/v1/traces` appended. Only the HTTP protocols exist
76
+ * here; `grpc` or an unknown value falls back to `http/protobuf` with a warning.
77
+ */
78
+ export function resolveOtlpTarget(env: NodeJS.ProcessEnv = process.env): OtlpTarget | undefined {
79
+ const tracesEndpoint = env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT?.trim();
80
+ const endpoint = env.OTEL_EXPORTER_OTLP_ENDPOINT?.trim();
81
+ const url = tracesEndpoint ? tracesEndpoint : endpoint ? `${endpoint.replace(/\/+$/, "")}/v1/traces` : undefined;
82
+ if (url === undefined) return undefined;
83
+ const requested = (env.OTEL_EXPORTER_OTLP_TRACES_PROTOCOL ?? env.OTEL_EXPORTER_OTLP_PROTOCOL ?? "http/protobuf").trim();
84
+ let protocol: OtlpProtocol = "http/protobuf";
85
+ if (requested === "http/json") protocol = "http/json";
86
+ else if (requested !== "http/protobuf") {
87
+ console.warn(`[open-everywhere:instrumentation] OTLP traces protocol "${requested}" is not supported โ€” exporting with http/protobuf`);
88
+ }
89
+ const headers = { ...parseOtlpHeaders(env.OTEL_EXPORTER_OTLP_HEADERS), ...parseOtlpHeaders(env.OTEL_EXPORTER_OTLP_TRACES_HEADERS) };
90
+ return { url, protocol, headers };
91
+ }
92
+
93
+ function otlpExporter(target: OtlpTarget): SpanExporter {
94
+ const config = { url: target.url, headers: { ...target.headers } };
95
+ return target.protocol === "http/json" ? new OTLPHttpJsonTraceExporter(config) : new OTLPHttpProtoTraceExporter(config);
96
+ }
97
+
98
+ const OTLP_TARGET = resolveOtlpTarget();
99
+
100
+ /** The consumer's OTLP collector as an eve destination, or disabled when no endpoint is set. */
101
+ export const otlpDestination = OTLP_TARGET === undefined ? disableInstrumentation() : otelIntegration({ traceExporter: otlpExporter(OTLP_TARGET) });
@@ -50,7 +50,7 @@ function gatewayApprovalJudgeCompletion(defaults: GatewayRoutedDefaults): Approv
50
50
 
51
51
  /**
52
52
  * Build and register the gateway judge transport at boot. Without it the judge answers `defer` everywhere;
53
- * registration IS the transport opt-in, and arming is separate (approvals.judgeMode, default off).
53
+ * registration IS the transport opt-in, and arming is separate (approvals.judgeMode, shipped enforce).
54
54
  */
55
55
  export function registerGatewayApprovalJudge(defaults: GatewayRoutedDefaults): void {
56
56
  registerApprovalJudgeCompletion(gatewayApprovalJudgeCompletion(defaults));
@@ -32,7 +32,7 @@ export interface OeOauthDiscoveryProviderConfig extends OeOauthProviderConfigBas
32
32
  readonly mode: typeof OE_OAUTH_MODE_DISCOVERY;
33
33
  /** The hosted-MCP server URL โ€” discovery base + RFC 8707 resource indicator. */
34
34
  readonly resource: string;
35
- /** The `client_name` sent at Dynamic Client Registration (default "Open Everywhere"). */
35
+ /** The `client_name` sent at Dynamic Client Registration (default: the deployment's configured name, `agentDisplayName()`). */
36
36
  readonly clientName?: string;
37
37
  }
38
38
 
@@ -2,7 +2,7 @@
2
2
  // encrypted KV. Refresh is single-flight in-process AND cross-process, because the provider ROTATES tokens.
3
3
 
4
4
  import type { EncryptedKvVault } from "@open-everywhere/core/agent/adapter-api.js";
5
- import { encryptedKvVault, getSecretVia, OE_OAUTH_CALLBACK_PATH } from "@open-everywhere/core/agent/adapter-api.js";
5
+ import { agentDisplayName, encryptedKvVault, getSecretVia, OE_OAUTH_CALLBACK_PATH } from "@open-everywhere/core/agent/adapter-api.js";
6
6
  import { isStaticConfidential, OE_OAUTH_MODE_STATIC, type OeOauthProviderConfig, type OeOauthStaticProviderConfig } from "./config.js";
7
7
  import {
8
8
  discoverEndpoints,
@@ -250,7 +250,8 @@ export function makeOeOauthTokenVault(providers: ReadonlyMap<string, OeOauthProv
250
250
  const endpoints = await discoverEndpoints(cfg.resource, doFetch);
251
251
  const { clientId } = await registerClient(
252
252
  endpoints.registrationEndpoint,
253
- { clientName: cfg.clientName ?? "Open Everywhere", redirectUri, ...(cfg.scopes ? { scopes: cfg.scopes } : {}) },
253
+ // The OAuth consent screen names the client: the deployment's configured name (OPE-2334).
254
+ { clientName: cfg.clientName ?? agentDisplayName(), redirectUri, ...(cfg.scopes ? { scopes: cfg.scopes } : {}) },
254
255
  doFetch,
255
256
  );
256
257
  const record: DcrClientRecord = {
@@ -0,0 +1,276 @@
1
+ // THE DEFAULT ADAPTER WIRING (OPE-2301) โ€” `registerDefaultAdapters()`, the boot call every app makes
2
+ // from its `agent/lib/register-adapters.ts`.
3
+ //
4
+ // ๐Ÿ”ด WHY THIS IS PRODUCT CODE AND NOT A TEMPLATE FILE. The wiring used to live in each app, as a
5
+ // copy of the template's `agent/lib/register-adapters.ts`. `npx @open-everywhere/create` copies the
6
+ // template once, and an upgrade touches only `package.json`, so every upgrade had to hand-merge the
7
+ // copy. It drifted twice: a 0.1.2 copy registered no Postgres client on a bare `DATABASE_URL`
8
+ // (OPE-2230), and the test-bed's copy broke the 0.1.5 build with a boot refusal. The selection rules
9
+ // and the floors below are product logic, not consumer config. Here they ship with the package, and
10
+ // an upgrade picks them up. The app's file is one call plus an optional `customAdapters` hook.
11
+ //
12
+ // ๐Ÿ”ด STATIC IMPORTS ON PURPOSE. Eve bundles each authored module and its import graph into ONE
13
+ // rolldown chunk. A literal dynamic `import("โ€ฆ")` of an internal module code-splits into a second
14
+ // chunk and trips Eve's "expected one authored module" invariant. Laziness is kept where it matters:
15
+ // `registerNeon()` builds the `@neondatabase/serverless` driver through an inner variable-specifier
16
+ // import, and only when it is CALLED (the Neon gate below).
17
+ //
18
+ // ๐Ÿ”ด THE APP'S INPUTS ARE ARGUMENTS. The selection table (`providers.generated.ts`, `oe build`
19
+ // output) and the memory config are app files, so the app passes them in. This module names no
20
+ // provider bundle: it drives the table it receives.
21
+
22
+ import { registerOeOauth } from "./oe-oauth/register.js";
23
+ import { oeOauthEnabled, loadOeOauthConfig } from "./oe-oauth/config.js";
24
+ import { registerAgentBrowser } from "./browser/register.js";
25
+ import { agentBrowserEnabled, loadAgentBrowserConfig } from "./browser/config.js";
26
+ import { registerAwsAccess } from "./aws/register.js";
27
+ import { awsAccessEnabled } from "./aws/config.js";
28
+ import { registerDirectoryAdapter } from "./directory/register.js";
29
+ import { registerGatewayRouteProbe } from "./model/gateway-probe.js";
30
+ import { registerNeon } from "./postgres/neon.js";
31
+ import { vercelBlobPutter } from "./blob/hosting.js";
32
+ import {
33
+ registerDbCredentialBroker,
34
+ CREDENTIAL_KEY_ENV,
35
+ registerGitHubConfigPrProvider,
36
+ setBlobPutter,
37
+ registerAuditDropCounter,
38
+ emitTelemetry,
39
+ getOperationalStore,
40
+ bootVerifyAuditChainTail,
41
+ bootBackfillLinkTenants,
42
+ runBootNotices,
43
+ registerBootNotice,
44
+ announceJudgeWithoutRules,
45
+ registerOperationalPgliteLazy,
46
+ resolveOperationalSelection,
47
+ resolveMemoryProviderId,
48
+ onVercel,
49
+ declareProviderSelection,
50
+ assertConnectorMemoryGrants,
51
+ applySubtreeSurfaceClaims,
52
+ reconcileSurfaceCommands,
53
+ type Domain,
54
+ type DomainMemoryGrants,
55
+ type MemorySilo,
56
+ type OperationalFloor,
57
+ } from "@open-everywhere/core/agent/adapter-api.js";
58
+
59
+ /** One row of the app's generated selection table (`agent/lib/providers.generated.ts`). */
60
+ export interface SelectedProviderBundle {
61
+ readonly id: string;
62
+ readonly register: () => void;
63
+ }
64
+
65
+ export interface DefaultAdapterOptions {
66
+ /** The app's `SELECTED_PROVIDERS` table. Its ids are the declared selection. */
67
+ readonly providers: readonly SelectedProviderBundle[];
68
+ /**
69
+ * The app's memory config, for the prefetch invariant. Each one is CALLED at the check, after the
70
+ * provider bundles register, so pass the loaders, not their results.
71
+ */
72
+ readonly memory: {
73
+ readonly domains: () => readonly Domain[];
74
+ readonly silos: () => readonly MemorySilo[];
75
+ readonly grants: (domain: Domain) => DomainMemoryGrants | null;
76
+ };
77
+ /**
78
+ * THE HOOK FOR CUSTOM ADAPTERS. It runs once, after every default adapter and every selected
79
+ * provider bundle registers, and before the boot checks that read those registries (the memory
80
+ * grants, the subtree claims), the store wiring and the slash-command reconcile. So a custom
81
+ * registration here replaces a default one, and the same boot checks see it.
82
+ * The store selection is NOT a hook point: the Neon gate and the PGlite floor are product rules.
83
+ */
84
+ readonly customAdapters?: () => void | Promise<void>;
85
+ }
86
+
87
+ /** What the store wiring decided for one env. */
88
+ export interface StoreWiring {
89
+ readonly operational: { readonly id: string; readonly floor: OperationalFloor };
90
+ /** Did the Neon gate open (a Postgres backing is selected AND `DATABASE_URL` is set)? */
91
+ readonly neon: boolean;
92
+ /** Is the embedded PGlite floor armed? */
93
+ readonly pgliteFloor: boolean;
94
+ }
95
+
96
+ /** What one `registerDefaultAdapters()` call wired. */
97
+ export interface DefaultAdapterWiring {
98
+ readonly providers: readonly string[];
99
+ readonly oeOauth: boolean;
100
+ readonly dbCredentialBroker: boolean;
101
+ readonly agentBrowser: boolean;
102
+ readonly awsAccess: boolean;
103
+ readonly customAdapters: boolean;
104
+ readonly store: StoreWiring;
105
+ }
106
+
107
+ /**
108
+ * The store decision, pure. ๐Ÿ”ด THE SELECTION IS THE RUNTIME'S OWN RULE, NOT A SECOND ENV TEST
109
+ * (OPE-2230). Both selectors default to postgres when `DATABASE_URL` is present. A hand-written
110
+ * `OE_MEMORY_PROVIDER === "postgres"` test refuses to build the driver for exactly the deployment
111
+ * the default exists for: the Neon Marketplace integration sets `DATABASE_URL` and nothing else.
112
+ *
113
+ * The PGlite floor (OPE-113) arms when the operational selection resolves to it (no provider named,
114
+ * no `DATABASE_URL`). It stays off on Vercel, where the floor is per-instance and ephemeral, unless
115
+ * `OE_ALLOW_EPHEMERAL_OPERATIONAL=1` names the single-instance escape. The Neon gate never opens on
116
+ * the floor's branch, so the two never both register.
117
+ */
118
+ export function decideStoreWiring(env: NodeJS.ProcessEnv, vercel: boolean): StoreWiring {
119
+ const operational = resolveOperationalSelection(env);
120
+ const postgresSelected =
121
+ resolveMemoryProviderId(env) === "postgres" || (operational.id === "postgres" && operational.floor === "none");
122
+ return {
123
+ operational,
124
+ neon: postgresSelected && Boolean(env.DATABASE_URL),
125
+ pgliteFloor: operational.floor === "pglite" && (!vercel || env.OE_ALLOW_EPHEMERAL_OPERATIONAL === "1"),
126
+ };
127
+ }
128
+
129
+ let WIRED: DefaultAdapterWiring | undefined;
130
+ let STARTED: Promise<DefaultAdapterWiring> | undefined;
131
+
132
+ /** What `registerDefaultAdapters()` wired in this process, or `undefined` before it runs. */
133
+ export function defaultAdapterWiring(): DefaultAdapterWiring | undefined {
134
+ return WIRED;
135
+ }
136
+
137
+ /**
138
+ * Wire every default adapter, the selected provider bundles and the operational store. Call it from
139
+ * the app's `agent/lib/register-adapters.ts`, with `await` at module top level.
140
+ * The wiring runs ONCE per process. A second run would declare the provider selection again (which
141
+ * clears the gate's admitted and refused records), and run every bundle's register(), the boot
142
+ * checks and the slash-command reconcile a second time.
143
+ * ๐Ÿ”ด A SECOND CALL RETURNS THE FIRST CALL'S RESULT; IT DOES NOT THROW. eve 0.71's authored-module
144
+ * loader (`internal/authored-module-loader.js`) bundles and evaluates `agent/agent.ts` more than once
145
+ * in one `eve build` process, and each evaluation runs `register-adapters.ts` again. This module and
146
+ * core stay external, so the second evaluation shares the registries the first one filled. A throw
147
+ * here failed that evaluation and the build (OPE-2346).
148
+ */
149
+ export function registerDefaultAdapters(options: DefaultAdapterOptions): Promise<DefaultAdapterWiring> {
150
+ STARTED ??= wireDefaultAdapters(options);
151
+ return STARTED;
152
+ }
153
+
154
+ async function wireDefaultAdapters(options: DefaultAdapterOptions): Promise<DefaultAdapterWiring> {
155
+
156
+ // PROVIDER SELECTION โ€” declared FIRST, before any bundle registers (OPE-116 stage 5). Core takes the
157
+ // ids as opaque strings, and every platform-keyed registration seam then refuses anything outside
158
+ // them. A row that lands ahead of the declaration cannot be retracted (core warns if one does).
159
+ const providers = declareProviderSelection(options.providers.map((p) => p.id));
160
+
161
+ // Connector-plane user-auth flow (OPE-88): own-rolled OAuth, registered only when the master flag
162
+ // OE_OAUTH_ENABLED is armed AND a provider config block is present. Otherwise no flow registers and
163
+ // core's OAuth mint paths fail closed. There is no vendor fallback.
164
+ const oeOauthCfg = oeOauthEnabled() ? loadOeOauthConfig() : undefined;
165
+ if (oeOauthCfg) registerOeOauth({ providers: oeOauthCfg });
166
+
167
+ // The "db" credential broker (task #113): registered iff its AES key exists. Without the key the
168
+ // broker fails closed on store and read, so registering it would falsely offer the "Set API key"
169
+ // chat intake, which keys off the broker being REGISTERED.
170
+ const dbCredentialBroker = Boolean(process.env[CREDENTIAL_KEY_ENV]);
171
+ if (dbCredentialBroker) registerDbCredentialBroker();
172
+
173
+ // The agent-browser toolkit (OPE-43). The platform gate is inside loadAgentBrowserConfig
174
+ // (OPE-1793): off Vercel the config is `undefined` and nothing registers.
175
+ // registerAgentBrowser can still refuse (returns false, loud) when the vendor binding did not take.
176
+ const browserCfg = agentBrowserEnabled() ? loadAgentBrowserConfig() : undefined;
177
+ const agentBrowser = browserCfg ? registerAgentBrowser(browserCfg) : false;
178
+
179
+ // The AWS-access layer: only when OE_AWS_ENABLED is armed. Flag-off โ‡’ core's AWS mint and exec
180
+ // paths fail closed.
181
+ const awsAccess = awsAccessEnabled();
182
+ if (awsAccess) registerAwsAccess();
183
+
184
+ // The identity-DIRECTORY plane: a no-op unless OE_DIRECTORY_PROVIDER names an adapter, and LOUD
185
+ // when it names one whose config is incomplete.
186
+ registerDirectoryAdapter();
187
+
188
+ // The GitHub config-repo PR mechanism, the default ConfigPrProvider (OPE-86). Unconditional and
189
+ // harmless when unused. A self-host swaps in its own provider from `customAdapters`.
190
+ registerGitHubConfigPrProvider();
191
+
192
+ // THE PROVIDER BUNDLES: one boot call per SELECTED bundle, straight from the generated table.
193
+ for (const provider of options.providers) {
194
+ provider.register();
195
+ }
196
+
197
+ // The deployment-status surface's model probe (OPE-2230). It registers a function, and nothing
198
+ // calls it until somebody reads `GET /eve/v1/oe/status`.
199
+ registerGatewayRouteProbe();
200
+
201
+ // The audit drop counter (audit-wedge WS-5). audit.ts cannot import telemetry.ts (a module-init
202
+ // cycle, and the one-chunk invariant), so this boot module injects the emitter.
203
+ registerAuditDropCounter((action, platform) => emitTelemetry("oe.audit.dropped", { "oe.action": action, "oe.platform": platform }));
204
+
205
+ // The Vercel Blob putter for the HTML-artifact hosting seam. Harmless when unused: the hosting
206
+ // path runs only for an org that opted in, and the putter fails loud without its OIDC env.
207
+ setBlobPutter(vercelBlobPutter);
208
+
209
+ // THE APP'S HOOK, after every default registration and before every check that reads them.
210
+ const customAdapters = options.customAdapters !== undefined;
211
+ if (options.customAdapters) await options.customAdapters();
212
+
213
+ // THE PREFETCH INVARIANT (OPE-390), fail-LOUD. It runs after the bundles register, because a
214
+ // connector-owned memory capability is DECLARED by its bundle's register(). It refuses a domain
215
+ // whose memory names a connector-owned silo the domain holds no connector grant for.
216
+ assertConnectorMemoryGrants(options.memory.domains(), options.memory.silos(), options.memory.grants);
217
+
218
+ // THE SUBTREE CLAIM (OPE-539), for the same boot-order reason: the platform that honours
219
+ // `subtree: true` declares so from its own bundle's register().
220
+ applySubtreeSurfaceClaims();
221
+
222
+ // The judge-without-rules line (OPE-2400): `approvals.judgeMode: "enforce"` with no bound
223
+ // pre-approval rule behaves like `off`, so say so once. A boot notice, so it logs once per process,
224
+ // in the process that serves, and a failure costs one log line, never the boot.
225
+ registerBootNotice("approval-judge-no-rules", announceJudgeWithoutRules);
226
+
227
+ const store = decideStoreWiring(process.env, onVercel());
228
+
229
+ // Neon (hosted Postgres) is LIVE-ONLY: registerNeon() builds the driver only when called here.
230
+ if (store.neon) await registerNeon();
231
+
232
+ // The embedded PGlite floor, armed LAZILY (OPE-391). The store registers now, but the lock, the
233
+ // WASM boot and the schema init happen on the first QUERY. `eve start` evaluates the app module
234
+ // in the CLI parent and again in the server child, and an eager arm made the parent the lock
235
+ // holder. The boot store checks ride along as the first-open callback, so they run once, in the
236
+ // process that uses the store.
237
+ if (store.pgliteFloor) registerOperationalPgliteLazy(undefined, runBootStoreChecks);
238
+ else await runBootStoreChecks();
239
+
240
+ // SLASH COMMANDS REGISTER FROM THE DEPLOYMENT ITSELF (OPE-1687). The app declares WHICH ROOMS
241
+ // (`declareSurfaceCommandScope`); each selected bundle declared HOW in the loop above. AWAITED, and
242
+ // it never throws: a lambda can freeze the moment it answers, and a floating promise would be
243
+ // cancelled on the cold start that had the work to do. The reconciler owns the latency bound (a
244
+ // short timeout per vendor call, no 429 backoff, one budget). `eve start` evaluates the app module
245
+ // twice, so a local start reconciles twice. A reconcile WRITES when the process holds the surface's
246
+ // credentials and the declared rooms differ from this checkout.
247
+ await reconcileSurfaceCommands();
248
+
249
+ WIRED = { providers, oeOauth: oeOauthCfg !== undefined, dbCredentialBroker, agentBrowser, awsAccess, customAdapters, store };
250
+ return WIRED;
251
+ }
252
+
253
+ // The boot-time store checks, in one place so they run either eagerly (hosted and sqlite paths, where
254
+ // the store is live the moment it registers) or on the PGlite floor's FIRST OPEN. Availability over
255
+ // integrity: none of them throws.
256
+ // 1. The audit hash-chain tail-check (OPE-52): one loud warn on any break.
257
+ // 2. The identity-link tenant backfill (OPE-309): fills blank `tenant` columns only.
258
+ // 3. The registered boot notices (OPE-2231): a selected bundle registers a notice from its own
259
+ // register() call, and this drains the registry. The drain catches each notice separately.
260
+ async function runBootStoreChecks(): Promise<void> {
261
+ try {
262
+ await bootVerifyAuditChainTail(getOperationalStore());
263
+ } catch {
264
+ /* store not resolvable โ€” its first real use raises the reason loudly; skip the check */
265
+ }
266
+ try {
267
+ await bootBackfillLinkTenants();
268
+ } catch {
269
+ /* store not resolvable โ€” see above; bootBackfillLinkTenants swallows everything else */
270
+ }
271
+ try {
272
+ await runBootNotices();
273
+ } catch {
274
+ /* unreachable โ€” runBootNotices catches per notice; see core boot/notices.ts */
275
+ }
276
+ }
@@ -1,8 +1,18 @@
1
1
  // Stop-after-turn sandbox lifecycle โ€” this file owns the DECISION, backend-stop.ts the mechanics. It exists
2
2
  // because a session VM is billed for its whole idle tail; every failure is fail-soft to the `timeout` fuse.
3
+ //
4
+ // ๐Ÿ”ด ON A WORK SESSION THE RULE IS "STOP WHEN IDLE", NOT "STOP AFTER THE TURN" (OPE-2341, placement ยง2.9).
5
+ // In the three-tier shape the WORK SESSION (W) owns the conversation's sandbox, and every job of the
6
+ // conversation runs in it. A W turn can end while W still owes a job: a job forwarded and not yet
7
+ // started, or a task whose settlement has not run yet. The session-local live set does not see the
8
+ // first, so the old rule stopped the VM under a job that was about to run in it. So on W the stop also
9
+ // needs W's DURABLE ledger to owe nothing (core channels/work-generation.ts `workSessionSandboxState`).
10
+ // The rule for every other session is unchanged: a seat has no sandbox of its own, and a two-tier seat
11
+ // keeps the stop after its turn. `timeoutMs` stays the fuse, so on W it is the longest a JOB may hold
12
+ // the VM, not the longest turn.
3
13
 
4
14
  import { defineHook, type HookContext } from "eve/hooks";
5
- import { bindFrom, domainKeyOf, resolvePartition, appendUsageEvent, usageForSession, recordSpend, periodOf, estimateSandboxCostUsd, routineIdOf, turnSurfaceMode, getSettings, isSubagentChildContext, liveTaskIds, subagentParentSessionId, type BindableContext } from "@open-everywhere/core/agent/adapter-api.js";
15
+ import { bindFrom, domainKeyOf, resolvePartition, appendUsageEvent, usageForSession, recordSpend, periodOf, estimateSandboxCostUsd, routineIdOf, turnSurfaceMode, getSettings, isSubagentChildContext, liveTaskIds, subagentParentSessionId, workSessionSandboxState, type BindableContext } from "@open-everywhere/core/agent/adapter-api.js";
6
16
  import { resolveSandboxStopper, shouldStop, type SandboxListing, type SandboxStopper } from "./backend-stop.js";
7
17
 
8
18
  // Re-exported so the one "is this sandbox up?" predicate and the one listing shape stay
@@ -122,6 +132,17 @@ async function onTurnEnd(ctx: HookContext, outcome: string): Promise<void> {
122
132
  );
123
133
  return;
124
134
  }
135
+ // ๐Ÿ”ด OPE-2341 โ€” A WORK SESSION STOPS ITS SANDBOX ONLY WHEN IDLE (see the file header). Fail-closed:
136
+ // an unreadable ledger reads as busy, and the VM then idles to its fuse instead of dying under a job.
137
+ const work = await workSessionSandboxState(ctx as unknown as BindableContext);
138
+ if (work.work && !work.idle) {
139
+ log(
140
+ `turn.${outcome}: SKIPPED the stop โ€” this work session is not idle` +
141
+ (work.owed.length > 0 ? `: it still owes ${work.owed.length} job(s) or task(s) [${work.owed.join(", ")}]` : ": its ledger could not be read") +
142
+ "; the turn end that finds it idle sweeps it",
143
+ );
144
+ return;
145
+ }
125
146
  const stopped = await stopSessionSandboxes(ctx.session.id, stopper);
126
147
  if (stopped.length > 0) {
127
148
  log(`turn.${outcome}: stopped ${stopped.map((s) => s.name).join(", ")} (auto-snapshot; next turn auto-resumes)`);
@@ -1,6 +1,11 @@
1
- /** Minimal structural sandbox the installer needs (matches @agent-browser/eve's EveSandboxSession). */
1
+ /** Minimal structural sandbox the installer needs. `id` is OPTIONAL deliberately: eve 0.69.0's
2
+ * `prepare` hands over a `SandboxSession`, which carries no `id` at all, and `installAgentBrowser`
3
+ * reads only `run` โ€” `sandbox.id` appears once in the vendor bundle, inside `runAgentBrowser` (the
4
+ * TOOL path). Keeping it required would make the caller's cast load-bearing and hide the day a real
5
+ * `id` dependency comes back. @agent-browser/eve's own `EveSandboxSession` still requires it, so the
6
+ * one cast at the `installAgentBrowser` boundary stays and is commented there. */
2
7
  export interface BakeableSandbox {
3
- readonly id: string;
8
+ readonly id?: string;
4
9
  run(options: {
5
10
  readonly command: string;
6
11
  }): PromiseLike<{
@@ -10,6 +15,7 @@ export interface BakeableSandbox {
10
15
  }>;
11
16
  }
12
17
  export interface AgentBrowserSandboxHooks {
18
+ /** ๐Ÿ”ด DEAD SINCE eve 0.69.0 (OPE-2247) โ€” nothing reads it. See this file's header. */
13
19
  readonly revalidationKey: () => string;
14
20
  readonly bootstrap: (input: {
15
21
  use: () => Promise<BakeableSandbox>;
@@ -1 +1 @@
1
- {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../agent/browser/sandbox.ts"],"names":[],"mappings":"AAMA,uGAAuG;AACvG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,GAAG,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,WAAW,CAAC;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7I;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,eAAe,EAAE,MAAM,MAAM,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvF;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,OAAO,GAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,OAAO,CAA6C,GAClG,wBAAwB,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAelD"}
1
+ {"version":3,"file":"sandbox.d.ts","sourceRoot":"","sources":["../../../agent/browser/sandbox.ts"],"names":[],"mappings":"AAgBA;;;;;mFAKmF;AACnF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,CAAC;IACrB,GAAG,CAAC,OAAO,EAAE;QAAE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,WAAW,CAAC;QAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC7I;AAED,MAAM,WAAW,wBAAwB;IACvC,sFAAsF;IACtF,QAAQ,CAAC,eAAe,EAAE,MAAM,MAAM,CAAC;IACvC,QAAQ,CAAC,SAAS,EAAE,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,OAAO,CAAC,eAAe,CAAC,CAAA;KAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACvF;AAED;;;GAGG;AACH,wBAAgB,wBAAwB,CAOtC,OAAO,GAAE,CAAC,OAAO,EAAE,eAAe,KAAK,OAAO,CAAC,OAAO,CAAkE,GACvH,wBAAwB,GAAG,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,CAelD"}