@open-everywhere/create 0.8.1 → 0.9.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.
package/README.md CHANGED
@@ -91,7 +91,8 @@ This CLI starts on Node 18 or newer, so `--check` can report an old Node. The ap
91
91
  Node 24, so a scaffold on an older Node exits non-zero and writes nothing.
92
92
 
93
93
  The scaffolded app sends anonymous usage telemetry when it runs, ON by default. Its `README.md`
94
- says how to turn it off (`OE_TELEMETRY=off` on the deployment), and the full disclosure is
94
+ says how to turn it off (`telemetry: { enabled: false }` in `agent/settings/settings.ts`, or
95
+ `DO_NOT_TRACK=1` on the deployment), and the full disclosure is
95
96
  [Telemetry](https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/telemetry.md).
96
97
  This CLI itself sends none.
97
98
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-everywhere/create",
3
- "version": "0.8.1",
3
+ "version": "0.9.0",
4
4
  "license": "Apache-2.0",
5
5
  "type": "module",
6
6
  "description": "npx scaffold and setup check for Open Everywhere — one step engine, reported by --check for an agent-led setup.",
@@ -46,8 +46,8 @@
46
46
  "test:env-pipe": "tsx scripts/env-pipe-test.ts"
47
47
  },
48
48
  "dependencies": {
49
- "@open-everywhere/core": "0.8.1",
50
- "@open-everywhere/provider-slack": "0.8.1",
49
+ "@open-everywhere/core": "0.9.0",
50
+ "@open-everywhere/provider-slack": "0.9.0",
51
51
  "tsx": "^4.23.9"
52
52
  },
53
53
  "devDependencies": {
@@ -0,0 +1,25 @@
1
+ // The app's `memory.provider` setting, read from its own settings file (OPE-2650). The selector was
2
+ // the OE_MEMORY_PROVIDER variable, so the database step read it from the env. It is a setting now,
3
+ // and this CLI cannot register the app's settings, so the step reads the one value where the app
4
+ // declares it: `agent/settings/settings.ts`, the `SETTINGS_OVERRIDES` export.
5
+ //
6
+ // FAIL-SOFT, the same rule as `app-naming.ts`: no file, no dependencies or a throw reads as "no
7
+ // selector declared", which is the shipped value, so `DATABASE_URL` decides. A DYNAMIC import at
8
+ // check time keeps the CLI's static graph free of the runtime (scripts/boot-free-test.ts).
9
+
10
+ import { existsSync } from "node:fs";
11
+ import { join } from "node:path";
12
+ import { pathToFileURL } from "node:url";
13
+
14
+ /** The app's `memory.provider`, trimmed, or `""` when it declares none. Never throws. */
15
+ export async function readAppMemoryProvider(cwd: string): Promise<string> {
16
+ const file = join(cwd, "agent", "settings", "settings.ts");
17
+ if (!existsSync(file)) return "";
18
+ try {
19
+ const mod = (await import(pathToFileURL(file).href)) as { SETTINGS_OVERRIDES?: { memory?: Record<string, unknown> } };
20
+ const provider = mod.SETTINGS_OVERRIDES?.memory?.["provider"];
21
+ return typeof provider === "string" ? provider.trim() : "";
22
+ } catch {
23
+ return "";
24
+ }
25
+ }
@@ -69,7 +69,7 @@ export const buildDeployStep: SetupStep = {
69
69
  "`npm run build` (= `oe build`, then `eve build`) — must be green.",
70
70
  vercel
71
71
  ? "Deploy is a SEPARATE, explicit step: `npx vercel deploy --prod` (confirm the scope and team first). A `git push` does NOT deploy."
72
- : "Deploy is a SEPARATE, explicit step: on your server, put the contract's environment in `.env` (with `OE_EGRESS_PROXY_TOKEN`), then run `docker compose up --build`. The app's `Dockerfile` builds the image, and `docker-compose.yml` puts it behind the egress proxy with Postgres. This stack runs the `justbash` sandbox backend: the agent's shell shares the app process's filesystem and environment. That is development grade, and it does not meet the contract's `sandbox-backend` requirement. See https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/contract.md.",
72
+ : "Deploy is a SEPARATE, explicit step: on your server, put the contract's environment in `.env` (with `OE_EGRESS_PROXY_TOKEN`), declare `egress: { proxyUrl: \"http://oe-egress-proxy:8080\", denialLog: \"/var/log/oe-egress/denied.log\" }` in `agent/settings/settings.ts`, then run `docker compose up --build`. The app's `Dockerfile` builds the image, and `docker-compose.yml` puts it behind the egress proxy with Postgres. This stack runs the `justbash` sandbox backend: the agent's shell shares the app process's filesystem and environment. That is development grade, and it does not meet the contract's `sandbox-backend` requirement. See https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/contract.md.",
73
73
  "Then re-run `npx @open-everywhere/create --check --host https://<host>`: it probes the deployed Slack route.",
74
74
  "The probe passes on the route's own `401 unauthorized`, which proves the function booted and the route is mounted. It does NOT prove the deployment holds SLACK_SIGNING_SECRET: the route answers the same without it. Only Event Subscriptions showing **Verified** in the Slack console proves that.",
75
75
  ],
@@ -21,11 +21,12 @@ import { routeOf, type SetupRoute, type SetupStep, type StepContext } from "../e
21
21
  import { resolveOperationalSelection } from "@open-everywhere/core/agent/operational/selection.js";
22
22
  import { resolveMemoryProviderId } from "@open-everywhere/core/agent/memory/backing-selection.js";
23
23
  import { OWNER_URL_ENV, RUNTIME_ROLE_NAME } from "@open-everywhere/core/agent/ops/two-role.js";
24
+ import { readAppMemoryProvider } from "../app-memory.js";
24
25
 
25
- /** Which stores this env resolves to postgres, by the runtime's own two rules. */
26
- function postgresResolution(env: NodeJS.ProcessEnv): { operational: boolean; memory: boolean } {
26
+ /** Which stores this env and the app's `memory.provider` resolve to postgres, by the runtime's own two rules. */
27
+ function postgresResolution(env: NodeJS.ProcessEnv, memoryProvider: string): { operational: boolean; memory: boolean } {
27
28
  const op = resolveOperationalSelection(env);
28
- return { operational: op.id === "postgres" && op.floor === "none", memory: resolveMemoryProviderId(env) === "postgres" };
29
+ return { operational: op.id === "postgres" && op.floor === "none", memory: resolveMemoryProviderId(env, memoryProvider) === "postgres" };
29
30
  }
30
31
 
31
32
  /** The next action for an unsatisfied database step, per route. */
@@ -40,10 +41,12 @@ function nextFor(route: SetupRoute): string {
40
41
  export const databaseStep: SetupStep = {
41
42
  id: "database",
42
43
  title: "Database (Postgres)",
43
- check(ctx: StepContext) {
44
+ async check(ctx: StepContext) {
44
45
  const env = mergedEnv(ctx.cwd, ctx.env) as NodeJS.ProcessEnv;
45
46
  const route = routeOf(ctx);
46
- const resolved = postgresResolution(env);
47
+ // The memory selector is the app's `memory.provider` setting since OPE-2650, not an env var.
48
+ const memoryProvider = await readAppMemoryProvider(ctx.cwd);
49
+ const resolved = postgresResolution(env, memoryProvider);
47
50
  if (isSet(env.DATABASE_URL) && resolved.operational && resolved.memory) {
48
51
  return {
49
52
  satisfied: true,
@@ -54,7 +57,7 @@ export const databaseStep: SetupStep = {
54
57
  }
55
58
  const pointedAway = [
56
59
  ...(resolved.operational || !isSet(env.OE_OPERATIONAL_PROVIDER) ? [] : ["OE_OPERATIONAL_PROVIDER"]),
57
- ...(resolved.memory || !isSet(env.OE_MEMORY_PROVIDER) ? [] : ["OE_MEMORY_PROVIDER"]),
60
+ ...(resolved.memory || memoryProvider === "" ? [] : ["memory.provider in agent/settings/settings.ts"]),
58
61
  ];
59
62
  return {
60
63
  satisfied: false,
@@ -62,10 +62,10 @@ export const envChecklistStep: SetupStep = {
62
62
  "Both routes need two values: DATABASE_URL and OE_CREDENTIAL_ENCRYPTION_KEY. DATABASE_URL resolves BOTH stores to postgres on its own (OPE-2230), so there is no provider selector to set — a selector pointing away from postgres is the only thing that breaks it.",
63
63
  "Mint OE_CREDENTIAL_ENCRYPTION_KEY with the ops tool `scaffold_credential_key`: it writes a base64 32-byte AES-256 key into .env.local and never prints it. The encrypted credential store fails closed on every read and write without it.",
64
64
  route === "docker"
65
- ? "The Docker route also needs a model route: AI_GATEWAY_API_KEY. There is no OIDC off Vercel. Running a different model backend is an adapter swap in app code, not an env setting — https://github.com/open-everywhere/open-everywhere/blob/main/docs/extending/adapter-model.md."
65
+ ? "The Docker route also needs a model route: AI_GATEWAY_API_KEY, an operator secret (OPE-2650) — store it with `npx oe secret set AI_GATEWAY_API_KEY`. There is no OIDC off Vercel. Running a different model backend is an adapter swap in app code, not an env setting — https://github.com/open-everywhere/open-everywhere/blob/main/docs/extending/adapter-model.md."
66
66
  : "The Vercel route needs NO AI_GATEWAY_API_KEY: a deployed project authenticates to the AI Gateway with OIDC.",
67
- "Each chat surface adds its own names — Slack reads SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET from the environment.",
68
- "Not needed on a deployment: OE_PUBLIC_BASE_URL (only for a custom domain) and OE_INTAKE_PAGE_SIGNING_KEY (the deployment mints it into the credential store).",
67
+ "Each chat surface adds its own secrets — Slack's SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET are operator secrets (OPE-2650): store them with `npx oe secret set <NAME>`, not in the deployment's environment.",
68
+ "Not needed on a deployment: OE_PUBLIC_BASE_URL (only for a custom domain) and OE_INTAKE_PAGE_SIGNING_KEY (not an env var since 0.9.0: the deployment mints it into the credential store).",
69
69
  "Run `npx @open-everywhere/create --check --json`, or the ops tool `preflight`, for the live verdict per name. Both read .env.local and the shell only.",
70
70
  "Set the values on the deployment instead (the Vercel dashboard, say)? Add `--host https://<host>`: the step then also reads the deployment's own verdicts from its public `/eve/v1/oe/status` route.",
71
71
  ],
@@ -4,16 +4,16 @@
4
4
  //
5
5
  // The manifest itself lives in the Slack bundle (OPE-1603) — @open-everywhere/provider-slack/
6
6
  // agent/app-manifest.js — so this step and the ops MCP tool `surface_manifest` print the SAME
7
- // document. The bundle resolves SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET ENV-FIRST (the block
8
- // below has the second home), so an operator who sets them by hand puts both in `.env.local` and
9
- // then on the deployment.
7
+ // document. SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET are OPERATOR SECRETS since OPE-2650: an
8
+ // operator who sets them by hand stores both with `npx oe secret set`, and the env var of the same
9
+ // name is the on-ramp, read only when nothing is stored (the block below has the `/setup` home).
10
10
  //
11
11
  // REQUIRED ON BOTH ROUTES (OPE-2233). The old `local` exemption is gone with the local goal: a
12
12
  // deployment with no chat surface configured answers nobody, whichever route it took.
13
13
  //
14
- // 🔴 THERE ARE TWO HOMES SINCE OPE-2231, AND THIS CHECK READS ONE. The bundle resolves both
15
- // secrets ENV-FIRST and then from the deployment's own encrypted credential store, which is where
16
- // a `/setup` run puts them — so a deployment configured that way is CORRECT and this step still
14
+ // 🔴 THE SECRETS LIVE IN THE STORE, AND THIS CHECK READS ONLY THE ON-RAMP. The bundle resolves both
15
+ // as operator secrets (the stored value, else the env var of the same name), then from the value a
16
+ // `/setup` run stored — so a deployment configured either way is CORRECT and this step still
17
17
  // reports it unsatisfied, because `.env.local` and the shell are all it can read. That makes the
18
18
  // wording load-bearing: the detail says "missing from .env.local and this shell", never a bare
19
19
  // "missing", and it names `/setup` and the status endpoint as the places that can see the store.
@@ -40,21 +40,21 @@ export const slackStep: SetupStep = {
40
40
  return {
41
41
  satisfied: true,
42
42
  detail:
43
- "SLACK_BOT_TOKEN + SLACK_SIGNING_SECRET set in the environment. 🔴 Setting these two makes the deployment IGNORE anything " +
44
- "/setup stored, and CLOSES /setup: the resolver is env-first, so landing that page cannot change which Slack app a working " +
45
- "deployment speaks as",
43
+ "SLACK_BOT_TOKEN + SLACK_SIGNING_SECRET set in the environment — the operator-secret on-ramp. For the deployment, store both with " +
44
+ "`npx oe secret set`. 🔴 Either home makes the deployment IGNORE anything /setup stored, and CLOSES /setup: the operator " +
45
+ "secret comes first, so landing that page cannot change which Slack app a working deployment speaks as",
46
46
  };
47
47
  }
48
48
  return {
49
49
  satisfied: false,
50
50
  detail:
51
51
  `missing from .env.local and this shell: ${missing.join(", ")}. This check reads those two homes only — a deployment ` +
52
- "configured by /setup keeps both secrets in its own encrypted credential store, and reports unsatisfied here while working " +
53
- `correctly. The status endpoint and /setup itself are what can see the store. ${missNote(routeOf(ctx))}`,
52
+ "that stored both with `npx oe secret set`, or configured by /setup, keeps them in its own encrypted credential store, and " +
53
+ `reports unsatisfied here while working correctly. The status endpoint, \`npx oe secret list\` and /setup itself are what can see the store. ${missNote(routeOf(ctx))}`,
54
54
  next:
55
55
  'open https://<host>/setup once the deployment is up (it creates and installs the Slack app from one pasted configuration ' +
56
56
  'token, with no env var to set), or create the app from the manifest the ops tool `surface_manifest { surface: "slack" }` ' +
57
- "returns, then have the person run `npx oe env set SLACK_BOT_TOKEN` and `npx oe env set SLACK_SIGNING_SECRET` in their own terminal",
57
+ "returns, then have the person run `npx oe secret set SLACK_BOT_TOKEN` and `npx oe secret set SLACK_SIGNING_SECRET` in their own terminal (value on stdin)",
58
58
  };
59
59
  },
60
60
  guide(ctx: StepContext) {
@@ -85,7 +85,7 @@ export const slackStep: SetupStep = {
85
85
  : "upload your own icon, or the project mark"
86
86
  }, ${PROJECT_MARK_URL}. Slack's manifest has no icon field, so this step stays by hand.`,
87
87
  "Basic Information → App Credentials → copy the Signing Secret = SLACK_SIGNING_SECRET.",
88
- "Write both with `npx oe env set SLACK_BOT_TOKEN` and `npx oe env set SLACK_SIGNING_SECRET`, run in your own terminal: each reads the value from a hidden prompt. Never paste a secret into the chat, and never pass one to an ops tool.",
88
+ "Store both as operator secrets with `npx oe secret set SLACK_BOT_TOKEN` and `npx oe secret set SLACK_SIGNING_SECRET`, run in your own terminal: each reads the value from stdin and writes the deployment's encrypted credential store. For a local run only, `npx oe env set <NAME>` puts the value in .env.local instead. Never paste a secret into the chat, and never pass one to an ops tool.",
89
89
  "THE SHORT WAY, once the deployment is up: open https://<host>/setup. It creates the Slack app from this same manifest with one pasted Slack app configuration token, installs it, and keeps every credential in the deployment's own encrypted store — no env var to set. This check cannot see that store, so it still reports this step unsatisfied afterwards; the status endpoint can.",
90
90
  "The manifest's URLs say YOUR-DEPLOY-URL until you deploy. Slack cannot verify Event Subscriptions before a deploy that holds SLACK_SIGNING_SECRET is up. After the deploy, paste the manifest again with your host (App Manifest → Save Changes), then retry the verification under Event Subscriptions.",
91
91
  "Note two ids for the trust edits: the workspace (team) id T… (the segment after /client/ in the workspace URL at https://app.slack.com), and your own member id U… (your Slack profile → ⋮ More → Copy member ID).",
@@ -22,7 +22,17 @@ import { envChecklistStep } from "./env-checklist.js";
22
22
  * both stores to postgres on its own, so naming them here would send an operator to set two
23
23
  * variables that change nothing — the exact fix list that ticket removed.
24
24
  */
25
- const DEPLOYMENT_ENV_NAMES = ["DATABASE_URL", "OE_CREDENTIAL_ENCRYPTION_KEY", "SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"] as const;
25
+ const DEPLOYMENT_ENV_NAMES = ["DATABASE_URL", "OE_CREDENTIAL_ENCRYPTION_KEY"] as const;
26
+
27
+ /**
28
+ * What the deployment's encrypted credential STORE must hold. The Slack pair is an operator secret
29
+ * since OPE-2650: `npx oe secret set <NAME>` writes it, with the same DATABASE_URL and key the
30
+ * deployment reads, so it needs no env push. A `/setup` run is the other way to put both there.
31
+ */
32
+ const DEPLOYMENT_SECRET_NAMES = ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"] as const;
33
+
34
+ /** The one command that stores each operator secret, value on stdin. */
35
+ const secretSetCommands = (names: readonly string[]): string => names.map((n) => `\`npx oe secret set ${n}\``).join(" and ");
26
36
 
27
37
  /**
28
38
  * The go/no-go rule, split out from `check()` so it can be tested on its own. That split is
@@ -97,6 +107,7 @@ export const verifyStep: SetupStep = {
97
107
  next:
98
108
  "set the deployment's OWN environment — this check never reads it: " +
99
109
  `${DEPLOYMENT_ENV_NAMES.join(", ")} (${routeOf(ctx) === "docker" ? "the container's environment" : "the Vercel project's Production environment"}). ` +
110
+ `Store the Slack operator secrets in its credential store (${secretSetCommands(DEPLOYMENT_SECRET_NAMES)}, value on stdin), or run /setup. ` +
100
111
  "Then confirm the Slack app's Event Subscriptions shows the request URL as Verified (that proves SLACK_SIGNING_SECRET on the deployment; this check cannot), and send the app a direct message",
101
112
  };
102
113
  },
@@ -106,11 +117,12 @@ export const verifyStep: SetupStep = {
106
117
  summary: "Before going live, run a real task in a pilot channel and walk the setup runbook's safety checklist (access bundles, egress enforcement, spend caps).",
107
118
  instructions: [
108
119
  `🔴 THIS CHECK NEVER READS THE DEPLOYMENT'S OWN ENVIRONMENT. Every step above reads .env.local and your shell. \`.env.local\` is gitignored and never ships, so the deployment reads only what you set ON it: ${DEPLOYMENT_ENV_NAMES.join(", ")}. A full house here says nothing about any of them.`,
120
+ `The Slack bot token and signing secret are operator secrets (OPE-2650): store them in the deployment's credential store with ${secretSetCommands(DEPLOYMENT_SECRET_NAMES)}, run in your own terminal with the same DATABASE_URL and OE_CREDENTIAL_ENCRYPTION_KEY in .env.local. No env push is needed for them.`,
109
121
  vercel
110
122
  ? "Set each one for Production without printing it: `npm run -s env:pipe -- <NAME> | npx vercel env add <NAME> production`. Always pipe it straight into `vercel env add`; never run `env:pipe` bare, because a shell that is not a terminal prints the value. The Neon integration already set DATABASE_URL there, and it selects postgres for both stores on its own — no provider selector to push. A variable added after a deploy reaches the functions only on the NEXT deploy."
111
123
  : "Set each one in the container's environment (a compose file, your orchestrator's secret store — not a tracked file). DATABASE_URL selects postgres for both stores on its own. A variable added after the container starts reaches the app only on the next start.",
112
124
  "Mint OE_CREDENTIAL_ENCRYPTION_KEY with the ops tool `scaffold_credential_key`, which writes it to .env.local and never prints it. Without it the encrypted credential store fails closed on every read and write.",
113
- "Not needed on a deployment: the two store selectors (DATABASE_URL resolves both), OE_PUBLIC_BASE_URL (only for a custom domain) and OE_INTAKE_PAGE_SIGNING_KEY (the deployment mints it into the credential store). On the Vercel route, AI_GATEWAY_API_KEY is not needed either — OIDC covers it.",
125
+ "Not needed on a deployment: the two store selectors (DATABASE_URL resolves both), OE_PUBLIC_BASE_URL (only for a custom domain) and OE_INTAKE_PAGE_SIGNING_KEY (not an env var since 0.9.0: the deployment mints it into the credential store). On the Vercel route, AI_GATEWAY_API_KEY is not needed either — OIDC covers it. On the Docker route it is an operator secret: `npx oe secret set AI_GATEWAY_API_KEY`.",
114
126
  "Send the app a direct message in Slack, then @mention it in a channel you invited it to.",
115
127
  "Safety checklist: https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/setup-runbook.md#safety-checklist-the-anti-footgun-list",
116
128
  ],
@@ -10,7 +10,8 @@
10
10
  # A deployed VERCEL project authenticates to the gateway with OIDC and needs no key. Everything
11
11
  # else does: a local run, and the DOCKER route, where there is no OIDC. Running a different model
12
12
  # backend instead is an adapter swap in app code, not an env setting (docs/extending/adapter-model.md).
13
- # Create a key at: Vercel dashboard -> AI Gateway -> API Keys.
13
+ # Create a key at: Vercel dashboard -> AI Gateway -> API Keys. For a local run, set it here. For a
14
+ # deployment, store it as an operator secret: `npx oe secret set AI_GATEWAY_API_KEY` (value on stdin).
14
15
  AI_GATEWAY_API_KEY=
15
16
 
16
17
  # --- The operational store, memory and the credential store ---
@@ -23,10 +24,10 @@ AI_GATEWAY_API_KEY=
23
24
  # database under .open-everywhere-data/.
24
25
  DATABASE_URL=
25
26
  # 🔴 NOT REQUIRED (OPE-2230). A present DATABASE_URL resolves BOTH stores to postgres on its own, so
26
- # there is no selector to set and nothing to push to the deployment. These two are an OVERRIDE only
27
- # — and a selector pointing AWAY from postgres is the one thing that breaks the resolution.
27
+ # there is no selector to set and nothing to push to the deployment. This one is an OVERRIDE only
28
+ # — and a selector pointing AWAY from postgres is the one thing that breaks the resolution. The
29
+ # memory selector is the `memory.provider` setting in agent/settings/settings.ts (0.9.0).
28
30
  # OE_OPERATIONAL_PROVIDER=postgres
29
- # OE_MEMORY_PROVIDER=postgres
30
31
  # A base64-encoded 32-byte AES-256 key that encrypts stored credentials. The deployment and the
31
32
  # operator tools need the same value. This command appends a fresh one to .env.local without printing it:
32
33
  # node -e "console.log('OE_CREDENTIAL_ENCRYPTION_KEY=' + require('crypto').randomBytes(32).toString('base64'))" >> .env.local
@@ -35,30 +36,32 @@ DATABASE_URL=
35
36
  # OE_PUBLIC_BASE_URL=https://agent.example.com
36
37
  #
37
38
  # After DATABASE_URL and the key are set, `npx oe secret set <NAME>` (value on stdin) stores a secret
38
- # whose credential names `broker: "db"` in the encrypted store. The Slack variables and
39
- # GITHUB_WEBHOOK_SECRET below are read from the ENVIRONMENT only, so they stay environment variables.
39
+ # in the encrypted store. The surface credentials below (the Slack, GitHub, Discord and Linear tokens
40
+ # and signing secrets) and AI_GATEWAY_API_KEY are OPERATOR SECRETS: a stored value wins, and the
41
+ # variable of the same name here is read only when nothing is stored. For a deployment, store them;
42
+ # a line here only reaches a local run.
40
43
 
41
44
  # --- Telemetry (https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/telemetry.md) ---
42
45
  # The deployed app sends ANONYMOUS USAGE TELEMETRY, ON BY DEFAULT: event names, enums, coarse
43
46
  # buckets, public model ids and the app version, with a random per-install id (service.instance.id).
44
47
  # Never message or memory content, org names, workspace, channel, user or session ids, or env
45
- # values. The page above lists every event. To turn it
46
- # off, set this ON THE DEPLOYMENT (the Vercel project or the container's environment): a value here
47
- # only reaches a local run, because .env.local never ships. DO_NOT_TRACK=1 does the same.
48
- # OE_TELEMETRY=off
48
+ # values. The page above lists every event. To turn it off, declare
49
+ # `telemetry: { enabled: false }` in agent/settings/settings.ts and deploy: the setting ships with the
50
+ # app. DO_NOT_TRACK=1 in the deployment's environment does the same. OE_TELEMETRY is gone (0.9.0).
49
51
 
50
52
  # --- Slack (https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/surfaces/slack.md) ---
53
+ # Both are operator secrets: `npx oe secret set SLACK_BOT_TOKEN` and `npx oe secret set SLACK_SIGNING_SECRET`.
51
54
  # Slack app -> OAuth & Permissions -> Bot User OAuth Token (xoxb-…).
52
55
  SLACK_BOT_TOKEN=
53
56
  # Slack app -> Basic Information -> Signing Secret. Verifies every inbound event.
54
57
  SLACK_SIGNING_SECRET=
55
58
 
56
59
  # --- GitHub (https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/surfaces/github.md) ---
57
- # The GitHub App that receives webhooks and replies as the app.
58
- # GITHUB_APP_ID=
59
- # GITHUB_APP_PRIVATE_KEY=
60
- # GITHUB_APP_SLUG=
61
- # Verifies inbound deliveries. If it is unset, the route refuses every delivery.
60
+ # The GitHub App that receives webhooks and replies as the app. Which App it is (`github.appId`,
61
+ # `github.appSlug`, `github.conversation`) is in agent/settings/settings.ts.
62
+ # GITHUB_APP_PRIVATE_KEY= (an operator secret: `npx oe secret set GITHUB_APP_PRIVATE_KEY`)
63
+ # Verifies inbound deliveries. If it is unset, the route refuses every delivery. An operator secret:
64
+ # `npx oe secret set GITHUB_WEBHOOK_SECRET`.
62
65
  # GITHUB_WEBHOOK_SECRET=
63
66
 
64
67
  # --- Notion (https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/surfaces/notion.md) ---
@@ -24,8 +24,9 @@ Setup is yours to drive, with the user's consent at every command. Use the
24
24
  - **Tell the user about telemetry before the first deploy.** The deployed app sends anonymous usage
25
25
  telemetry, and it is ON by default: event names, enums, coarse buckets, public model ids and the
26
26
  app version, with a random per-install id (`service.instance.id`). It never sends message or
27
- memory content, org names, workspace, channel, user or session ids, or env values. To turn it off, they set `OE_TELEMETRY=off` (or
28
- `DO_NOT_TRACK=1`) on the deployment itself. The full list of events is
27
+ memory content, org names, workspace, channel, user or session ids, or env values. To turn it off, they declare
28
+ `telemetry: { enabled: false }` in `agent/settings/settings.ts` and deploy, or set `DO_NOT_TRACK=1`
29
+ on the deployment itself. The full list of events is
29
30
  `https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/telemetry.md`. Ask which
30
31
  they want, and do not decide for them.
31
32
  - `npx @open-everywhere/create --check --json` is the status report: every step, with `satisfied`,
@@ -80,6 +81,12 @@ Setup is yours to drive, with the user's consent at every command. Use the
80
81
  - Never commit a secret, a token, an `.env` value or a credential. Secrets go in `.env.local`
81
82
  (gitignored), and the deployment reads them from its own environment. A credential whose
82
83
  `broker` is `"db"` goes in the encrypted credential store instead (`npx oe secret set <NAME>`).
84
+ The secret values of the Slack, Discord, GitHub and Linear surfaces are operator secrets: the
85
+ Slack bot token and signing secret, the GitHub App private key and `LINEAR_CLIENT_SECRET`, for
86
+ example. Store each with `npx oe secret set <NAME>`. An id or a public value (`LINEAR_CLIENT_ID`,
87
+ `DISCORD_PUBLIC_KEY`) is not an operator secret, so do not move it to the store. The Teams app
88
+ password and a Linear app slot's `LINEAR_APP_<SLOT>_*` values stay in the environment. Where `agent/access/connector-credentials.ts`
89
+ names one (the Slack `work-token` and `chat-token` ids), it stamps it `broker: "db"`.
83
90
  - Upgrade every `@open-everywhere/*` package together, to one version. Keep `eve` at the exact
84
91
  version `@open-everywhere/core` declares as its peer. A `devDependencies` key that still reads
85
92
  `create-open-everywhere` is replaced by `@open-everywhere/create`, not bumped: the unscoped name is
@@ -13,7 +13,7 @@ WORKDIR /app
13
13
  # the eve version, the platform, a random installation id and a salted hash of the git remote URL or
14
14
  # of the working directory to telemetry.vercel.com. This image turns it OFF:
15
15
  # `EVE_TELEMETRY_DISABLED=1` is eve's switch, and both stages set it. Open Everywhere's own telemetry
16
- # has its own switch, `OE_TELEMETRY` (see README.md § Telemetry).
16
+ # has its own switch, the `telemetry.enabled` setting (see README.md § Telemetry).
17
17
  ENV EVE_TELEMETRY_DISABLED=1
18
18
 
19
19
  # git: a dependency or the `git` memory backing can need it at build time.
@@ -41,9 +41,9 @@ step also reads the deployment's own verdicts, so values set only in the Vercel
41
41
  The deployed app sends **anonymous usage telemetry, ON by default**: event names, enums, coarse
42
42
  buckets, public model ids and the app version. Each event carries a random per-install id
43
43
  (`service.instance.id`). It never sends message or memory content, org names, workspace, channel,
44
- user or session ids, or env values. **To turn it off, set `OE_TELEMETRY=off`** (or `DO_NOT_TRACK=1`) on the deployment:
45
- the Vercel project's environment or the container's. `.env.local` alone does not do it, because it
46
- never ships. Every event and attribute is listed in the
44
+ user or session ids, or env values. **To turn it off, declare `telemetry: { enabled: false }`** in `agent/settings/settings.ts`
45
+ and deploy. Or set `DO_NOT_TRACK=1` on the deployment: the Vercel project's environment or the
46
+ container's. `.env.local` alone does not do it, because it never ships. Every event and attribute is listed in the
47
47
  [telemetry disclosure](https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/telemetry.md).
48
48
  The `@open-everywhere/create` CLI sends none.
49
49
 
@@ -116,8 +116,10 @@ Notion and GitHub are in the roster. Each one needs its own setup before it answ
116
116
  - **Notion** — create an internal integration, subscribe its webhook to
117
117
  `https://<host>/eve/v1/notion`, and put the pages it may act on in a memory domain's `reach`. See the
118
118
  [Notion guide](https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/surfaces/notion.md).
119
- - **GitHub** — create a GitHub App, point its webhook at `https://<host>/eve/v1/github`, and set
120
- `GITHUB_WEBHOOK_SECRET`. See the
119
+ - **GitHub** — create a GitHub App, point its webhook at `https://<host>/eve/v1/github`, and store
120
+ its private key and webhook secret as operator secrets (`npx oe secret set GITHUB_APP_PRIVATE_KEY`,
121
+ `npx oe secret set GITHUB_WEBHOOK_SECRET`), and set `github: { appId, appSlug, conversation: true }`
122
+ in `agent/settings/settings.ts`. See the
121
123
  [GitHub guide](https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/surfaces/github.md).
122
124
 
123
125
  Discord, Microsoft Teams and Linear are installed too. To add one, add it under `providers` in
@@ -2,19 +2,20 @@
2
2
  // knobs for the widen-slow path: request card → deterministic codemod → bot-PR →
3
3
  // ADMIN MERGES IN GITHUB (that merge is the approval; there is no admin card).
4
4
  //
5
- // FAIL-CLOSED: with either env var below unset, the `request_access` tool refuses
6
- // politely and the flow is simply OFF — nothing else changes. Both values are
7
- // operator-specific (a private channel id, YOUR fork's repo), so they live in env,
8
- // never in this committed file (AGENTS.md security rules).
5
+ // FAIL-CLOSED: with `accessRequests.configRepo` or `accessRequests.adminChannel` unset, the
6
+ // `request_access` tool refuses politely and the flow is simply OFF — nothing else changes. Both
7
+ // values are settings (`agent/settings/settings.ts`, OPE-2650), not environment variables: which
8
+ // repository holds this deployment's config and which room hears about each pull request are tracked
9
+ // facts of the deployment, changed by pull request like every other one.
9
10
  //
10
- // OE_ACCESS_ADMIN_CHANNEL Slack channel id that hears "PR opened — merge to grant".
11
- // OE_ACCESS_CONFIG_REPO "owner/repo" of THIS deployment's config repo (the one
12
- // the grants overlay lives in). The GitHub App needs write on it.
13
- // OE_ACCESS_CONFIG_BASE Base branch (default "main").
14
- // OE_ACCESS_GRANTS_PATH Overlay path (default "agent/access/grants.generated.ts").
15
- // OE_WEBHOOK_HOOKS_PATH Webhook-declaration overlay path (default
16
- // "agent/webhooks/hooks.generated.ts") — the `declare_webhook` flow (OPE-208)
17
- // rides this same repo/branch/credential, only the file differs.
11
+ // accessRequests.adminChannel the channel that hears "PR opened — merge to grant".
12
+ // accessRequests.configRepo "owner/repo" of THIS deployment's config repo (the one the grants
13
+ // overlay lives in). The GitHub App needs write on it.
14
+ // accessRequests.configBase the base branch (shipped "main").
15
+ //
16
+ // The overlay PATHS (`grantsPath`, `hooksPath`, `agentsPath`, `bindingsPath` and the rest) are fields
17
+ // of the object below. Each one is unset here, so core uses its documented default. A fork that moves
18
+ // an overlay sets the field in this file.
18
19
  //
19
20
  // Two BEHAVIOUR choices are declared below, not in env (OPE-2411): `dryRun` (was OE_ACCESS_DRY_RUN=1,
20
21
  // the e2e/test mode where a tap validates and reports, with no branch and no PR) and
@@ -33,13 +34,15 @@ import { loadBaseWebhooks } from "../webhooks/hooks.js";
33
34
  import { MCP_SERVERS } from "./mcp-servers.js";
34
35
  import { loadBaseDomains, loadBaseSilos, loadDomains } from "../memory/config/domains.js";
35
36
  import { loadGroups } from "../identity/groups.js";
37
+ import { getSettings } from "@open-everywhere/core/agent/settings/settings.js";
36
38
 
37
39
  export function loadAccessRequests(): AccessRequestsConfig | null {
40
+ // Read on every call, never at module load: the settings register after this module's body runs.
41
+ const { adminChannel, configRepo, configBase } = getSettings().accessRequests;
38
42
  return {
39
- adminChannel: process.env.OE_ACCESS_ADMIN_CHANNEL,
40
- configRepo: process.env.OE_ACCESS_CONFIG_REPO,
41
- configBase: process.env.OE_ACCESS_CONFIG_BASE || undefined,
42
- grantsPath: process.env.OE_ACCESS_GRANTS_PATH || undefined,
43
+ adminChannel: adminChannel || undefined,
44
+ configRepo: configRepo || undefined,
45
+ configBase,
43
46
  dryRun: false,
44
47
  credentialBindingLive: false,
45
48
  baseBundles: loadBaseBundles,
@@ -60,7 +63,6 @@ export function loadAccessRequests(): AccessRequestsConfig | null {
60
63
  // The `declare_webhook` PR path (OPE-208) rides this SAME config repo, branch and bot
61
64
  // credential — only the machine-written file differs. `baseWebhooks` is the code-declared
62
65
  // baseline a candidate overlay is validated against before any PR opens.
63
- hooksPath: process.env.OE_WEBHOOK_HOOKS_PATH || undefined,
64
66
  baseWebhooks: loadBaseWebhooks,
65
67
  // The declared MCP servers, so `propose_mcp_server` validates a candidate entry against them (OPE-2420).
66
68
  baseMcpServers: () => MCP_SERVERS,
@@ -69,7 +71,6 @@ export function loadAccessRequests(): AccessRequestsConfig | null {
69
71
  // the code-declared agents baseline (empty — agents live only in the overlay); `baseDomains` /
70
72
  // `baseSilos` are the code-only memory baseline a candidate overlay is validated against (ring ②)
71
73
  // before any PR opens. `agentsPath` defaults to "agent/memory/config/agents.generated.ts".
72
- agentsPath: process.env.OE_AGENTS_PATH || undefined,
73
74
  baseAgents: emptyAgentsFile,
74
75
  baseDomains: loadBaseDomains,
75
76
  baseSilos: loadBaseSilos,
@@ -65,7 +65,7 @@ const bindings: Binding[] = [
65
65
  // The machine-owned overlays: access requests the agent raised and an admin approved by merging a
66
66
  // pull request (grants.generated.ts), and agent-authored domains (agents.generated.ts).
67
67
  // The app-declared MCP servers (./mcp-servers.ts, OPE-2414): each server's personal credential, granted in
68
- // every person's OWN DM (the built-in `oe:everyone` group, or the groups the entry names). Never a channel.
68
+ // every INTERNAL person's OWN DM (the built-in `oe:everyone` group, or the groups the entry names). Never a channel.
69
69
  bundles.push(...mcpServerBundles(MCP_SERVERS));
70
70
  bindings.push(...mcpServerBindings(MCP_SERVERS));
71
71
  // The domain rules people asked for from approval cards and an admin merged (./domain-rules.ts, OPE-2456): each
@@ -1,8 +1,8 @@
1
1
  // Connector credential catalogues — HOW each connector authenticates.
2
2
  //
3
3
  // One catalogue per connector. Each credential names its type, where its secret lives
4
- // (`secretRef` + `broker`: `env` reads an environment variable by NAME, `db` reads the encrypted
5
- // credential store), and the endpoints it may be presented to. A bundle grant
4
+ // (`secretRef` + `broker`: `db` reads the encrypted credential store, `env` reads an environment
5
+ // variable by NAME), and the endpoints it may be presented to. A bundle grant
6
6
  // (agent/access/bundles.ts) picks a credential by `id`. A credential nobody grants is inert.
7
7
  //
8
8
  // The secret VALUES never live in this file — only the names they are stored under.
@@ -51,8 +51,10 @@ const catalogues: ConnectorCredentials[] = [
51
51
  connector: "slack",
52
52
  allows: ["api-key"],
53
53
  credentials: [
54
- { id: "work-token", type: "api-key", secretRef: "SLACK_BOT_TOKEN", broker: "env", intake: false },
55
- { id: "chat-token", type: "api-key", secretRef: "SLACK_BOT_TOKEN", broker: "env", intake: false },
54
+ // The bot token is an operator secret (`npx oe secret set SLACK_BOT_TOKEN`), so both ids name it
55
+ // in the credential store, under the same name the Slack surface reads.
56
+ { id: "work-token", type: "api-key", secretRef: "SLACK_BOT_TOKEN", broker: "db", intake: false },
57
+ { id: "chat-token", type: "api-key", secretRef: "SLACK_BOT_TOKEN", broker: "db", intake: false },
56
58
  ],
57
59
  }),
58
60
  ];
@@ -5,9 +5,11 @@
5
5
  // • In a DM, every person reaches the server AS THEMSELVES. Their first call posts a "Connect my
6
6
  // account" card; they sign in with their own account once, and their token reaches only what
7
7
  // that account can see. This needs `connectors.userPrincipal`, which ships on.
8
- // • 🔴 "Every person" means every account that can DM the agent, an external one included (a Slack
9
- // Connect user, a Discord user who shares a server with the bot). Where external people can DM the
10
- // agent, narrow the entry with `people: { groups: [...] }` (agent/identity/groups.ts).
8
+ // • 🔴 "Every person" means every INTERNAL person: a DM partner the platform or this deployment's
9
+ // trust declaration confirms as a member (OPE-2415). An external account (a Slack Connect user, a
10
+ // guest, a Discord user who shares a server with the bot but whom nothing vouches) is not in it. To
11
+ // give the server to a named external person, list them in a person-group (agent/identity/groups.ts)
12
+ // and name that group in `people: { groups: [...] }`. To narrow the server, name groups the same way.
11
13
  // • In a channel, nothing changes until an admin grants the server's `workspace-oauth` credential
12
14
  // (the org's own account) on a bundle in bundles.ts. A personal login never reaches a channel.
13
15
  //
@@ -51,7 +51,7 @@ import { loadDomainModules } from "./domains/index.js";
51
51
 
52
52
  const silos: MemorySilo[] = [
53
53
  // The workspace's shared brain. Postgres-backed (matches the deploy:
54
- // OE_MEMORY_PROVIDER=postgres + Neon DATABASE_URL). Referenced by the `org`
54
+ // a Neon DATABASE_URL selects postgres, or `memory.provider`). Referenced by the `org`
55
55
  // domain below, so every internal channel writes to / reads from one brain.
56
56
  defineMemorySilo({ name: "org-brain", provider: "postgres" }),
57
57
 
@@ -20,8 +20,9 @@
20
20
  // 🔴 NOTHING HERE IS A SECRET.
21
21
  //
22
22
  // To turn it on: build and publish the browser image (the manual's Personal browser page says how), set
23
- // `box.image`, and set `enabled: true`. The watch page also needs the page signing key
24
- // (OE_INTAKE_PAGE_SIGNING_KEY) and a public URL, which the credential intake page already uses.
23
+ // `box.image`, and set `enabled: true`. The watch page also needs the credential vault
24
+ // (OE_CREDENTIAL_ENCRYPTION_KEY), where its page signing key mints, and a public URL. The credential
25
+ // intake page needs the same two.
25
26
 
26
27
  import { DEFAULT_PERSONAL_BROWSER_POLICY, registerPersonalBrowserPolicy, type PersonalBrowserPolicy } from "@open-everywhere/core/agent/personal-browser/policy.js";
27
28
 
@@ -107,7 +107,7 @@ const SNAPSHOT_EXPIRATION_MS = SANDBOX_SETTINGS.snapshotExpirationMs; // 7 days
107
107
  // 🔴 Its session egress is NOT behind the oe-egress-proxy sidecar (OPE-2407). eve starts the
108
108
  // session container with no `--network` flag, so it joins the daemon's default bridge network
109
109
  // (a direct WAN route, no proxy env), and eve's docker options expose no network field. eve's
110
- // docker firewall takes only "allow-all" / "deny-all". So docker + OE_EGRESS_PROXY_URL REFUSES
110
+ // docker firewall takes only "allow-all" / "deny-all". So docker + `egress.proxyUrl` REFUSES
111
111
  // TO BOOT (selfHostEgressBootLog's `fatal`): the proxy would record "enforced" for a session it
112
112
  // never sees. Without the proxy, a no-reach session gets "deny-all" (applied) and a session that
113
113
  // needs a host allow-list aborts at session.started (fail-closed).
@@ -231,7 +231,7 @@ function selfHostEnvironment(): SelfHostSandbox {
231
231
  // the permission-homeostasis widening loop can see this plane at all. Here because this is the
232
232
  // ONE off-Vercel boot path that already resolves the egress posture — the same reason the boot
233
233
  // log above lives here — and because starting an interval anywhere the Vercel branch could reach
234
- // would put a timer in a lambda. A no-op returning undefined unless OE_EGRESS_DENIAL_LOG names a
234
+ // would put a timer in a lambda. A no-op returning undefined unless `egress.denialLog` names a
235
235
  // readable file (the compose stack mounts one; a bare self-host run does not), and the timer is
236
236
  // unref'd, so nothing about this can keep a process alive or take one down.
237
237
  startEgressDenialPoller();
@@ -32,14 +32,35 @@ that repository ships no `env:pipe` script. Everything else is the same.
32
32
  - **Never propose a local run as a setup step.** `npm run dev` is the development loop AFTER the
33
33
  deploy. A local run has no public HTTPS URL for Slack to reach and keeps its state in a different
34
34
  store, so it proves little about the deployment. See "Develop locally" below.
35
- - **Where each secret lives.** The deployment reads `DATABASE_URL`,
36
- `OE_CREDENTIAL_ENCRYPTION_KEY`, `SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET` and
37
- `GITHUB_WEBHOOK_SECRET` from its **environment**: the Slack bundle and the GitHub webhook check
38
- never read the credential store. So these go in `.env.local`, and then into the deployment's own
39
- environment. `secret_set { names: [...] }` is for a credential whose `broker` is `"db"` (and
40
- `NOTION_WEBHOOK_SECRET`): it moves each named value from `.env.local` into the store, so the user
41
- runs `npx oe env set <NAME>` first. It needs `DATABASE_URL` and the key in `.env.local` too.
42
- Nothing reads a stored secret back out (`secret_list` returns names only).
35
+ - **Where each secret lives.** The deployment reads `DATABASE_URL` and
36
+ `OE_CREDENTIAL_ENCRYPTION_KEY` from its **environment**: they open the credential store. Every
37
+ other secret lives IN the store. The surface credentials (`SLACK_BOT_TOKEN`,
38
+ `SLACK_SIGNING_SECRET`, `GITHUB_WEBHOOK_SECRET`, `GITHUB_APP_PRIVATE_KEY`, `DISCORD_BOT_TOKEN`, the
39
+ Linear tokens and secrets, `AI_GATEWAY_API_KEY`) are **operator secrets** since OPE-2650: a stored
40
+ value wins, and an env var of the same name is only the on-ramp. `secret_set { names: [...] }`
41
+ moves each named value from `.env.local` into the store, so the user runs `npx oe env set <NAME>`
42
+ first. It needs `DATABASE_URL` and the key in `.env.local` too. `scaffold_env` marks an operator
43
+ secret with `operatorSecret: true`. Nothing reads a stored secret back out (`secret_list` returns
44
+ names only).
45
+ - **A value with no local copy.** Sometimes the deployment holds an operator secret as a host env
46
+ var, and the user cannot read the value back. On Vercel a SENSITIVE env var reads back empty:
47
+ `vercel env pull` writes `[SENSITIVE]`. Then `npx oe env set` has no value to take, and
48
+ `secret_set` has nothing to move. Do not tell the user to set a value they cannot read. The
49
+ deployment copies it itself. The user runs, in their own terminal,
50
+ `npx oe secret adopt SLACK_BOT_TOKEN --url https://<deployment>` with a token from the
51
+ deployment's `OE_CONTROL_INJECT_TOKENS` family on stdin. The command calls the deployment's
52
+ `POST /control/v1/secrets/adopt` route, and that route answers 404 while the family is empty. If
53
+ the user holds no token from it, they mint one and store its `<name>:<sha256 hex>` entry with
54
+ `npx oe secret set OE_CONTROL_INJECT_TOKENS` first, then wait 30 seconds for a running instance
55
+ to read the new row. An injection token can write operator-secret rows, so treat it as a
56
+ credential-write secret. The deployment must run 0.9.0 or later. The reply gives each name as
57
+ `stored`, `already-stored` or `no-env-value`, and never the value. A stored row stays unless the
58
+ command passes `--overwrite`. Never pass `--overwrite` with `OE_CONTROL_INJECT_TOKENS` after the
59
+ user stored it to open this door: the env list then replaces their entry. Only operator-secret
60
+ names are accepted, never `DATABASE_URL` or `OE_CREDENTIAL_ENCRYPTION_KEY`. After a name reports
61
+ `stored`, tell the user to delete that env var on the host. The control MCP write
62
+ `adopt_env_secrets { "org": "<org>", "names": ["SLACK_BOT_TOKEN"] }` runs the same code, but its
63
+ plane answers 404 on a default deployment, so name the command.
43
64
  - **If an ops tool answers "the app now runs @open-everywhere/core X; this ops server runs Y"**, the
44
65
  server predates an upgrade. Ask the user to restart it (`/mcp`, then reconnect
45
66
  `open-everywhere-ops`) and call again. Never work around it: a stale server serves old code. A
@@ -74,13 +95,13 @@ enums, coarse buckets, public model ids and the app version, with a random per-i
74
95
  user or session ids, or env values. The full list
75
96
  of events is `https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/telemetry.md`.
76
97
 
77
- To turn it off, `OE_TELEMETRY=off` (or `DO_NOT_TRACK=1`) must be set on the DEPLOYMENT: a value in
78
- `.env.local` reaches only a local run. On the Vercel route, at step 9:
79
- `printf off | npx vercel env add OE_TELEMETRY production`. On the Docker route, put it in the
80
- container's environment. Every boot prints a line that says whether telemetry is on.
98
+ To turn it off, declare `telemetry: { enabled: false }` in `agent/settings/settings.ts`, commit it,
99
+ and deploy: the setting ships with the app, on both routes. `DO_NOT_TRACK=1` in the deployment's
100
+ own environment also turns it off. The `OE_TELEMETRY` variable is gone (0.9.0) and does nothing.
101
+ The deployed app prints a line that says whether telemetry is on, before it sends anything.
81
102
 
82
103
  eve's own CLI telemetry is separate: Open Everywhere sets `EVE_TELEMETRY_DISABLED=1` where it
83
- starts eve (its npm scripts and `Dockerfile`), and `OE_TELEMETRY` does not change it. For an `eve`
104
+ starts eve (its npm scripts and `Dockerfile`), and `telemetry.enabled` does not change it. For an `eve`
84
105
  command run by hand, or an app scaffolded before 0.8.0, read the eve section of the page above.
85
106
 
86
107
  ## Status, at every step
@@ -170,6 +191,8 @@ settings or the store.
170
191
  creates and installs the app, then runs `npx oe env set SLACK_BOT_TOKEN` and
171
192
  `npx oe env set SLACK_SIGNING_SECRET` in their own terminal. Confirm with
172
193
  `scaffold_env { secretNames: ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"] }`: both read `present: true`.
194
+ Both are operator secrets, so then call `secret_set { names: ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"] }`:
195
+ it moves them into the deployment's credential store, and they need no env push.
173
196
  **App icon.** With no `identity.name` set, the agent presents as the project, so its default
174
197
  avatar is the project mark. Unless the user set `identity.name` and chose their own icon, tell
175
198
  them to upload
@@ -189,10 +212,11 @@ settings or the store.
189
212
  8. **Build.** `npm run build`. It must pass. Then `npm run check:model-config`, and read it as
190
213
  "Check the model config" above says. A `✗` line stops the route here.
191
214
  9. **Deployment env.** `.env.local` never ships, so the deployment reads only what you set ON it.
192
- The names are `OE_CREDENTIAL_ENCRYPTION_KEY`, `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET`. The
193
- Neon integration already set `DATABASE_URL` there, and that one value resolves both stores — so
194
- there is no `OE_OPERATIONAL_PROVIDER` or `OE_MEMORY_PROVIDER` to push. Add `OE_TELEMETRY` (value
195
- `off`) only when the user chose to turn telemetry off (see the telemetry section above).
215
+ The name is `OE_CREDENTIAL_ENCRYPTION_KEY`. The Slack pair is not on this list: step 5 stored it
216
+ in the credential store with `secret_set`. The Neon integration already set `DATABASE_URL` there, and that one value resolves both stores — so
217
+ there is no `OE_OPERATIONAL_PROVIDER` to push, and `memory.provider` is a setting in Git. When the
218
+ user chose to turn telemetry off, commit `telemetry: { enabled: false }` before the deploy (see
219
+ the telemetry section above); there is no variable to push for it.
196
220
  Confirm each command first, and **never let a value reach your own output.**
197
221
  - **In a scaffolded app:** `npm run -s env:pipe -- <NAME> | npx vercel env add <NAME> production`.
198
222
  Always pipe `env:pipe` straight into `vercel env add`, as written. Never run it bare: your
@@ -228,7 +252,9 @@ Then:
228
252
  set no provider selector. The compose stack of step 6 also carries a Postgres of its own.
229
253
  3. **Key.** `scaffold_credential_key`.
230
254
  4. **Model route.** There is no OIDC off Vercel, so the user supplies `AI_GATEWAY_API_KEY` (Vercel
231
- → AI Gateway → API Keys issues one for any host) with `npx oe env set AI_GATEWAY_API_KEY`. Running a different model backend instead is
255
+ → AI Gateway → API Keys issues one for any host) with `npx oe env set AI_GATEWAY_API_KEY`. It is
256
+ an operator secret: once Postgres is reachable from this machine, `secret_set { names: ["AI_GATEWAY_API_KEY"] }`
257
+ stores it, and the container's `.env` line is only the on-ramp. Running a different model backend instead is
232
258
  an **adapter swap in app code**
233
259
  (`https://github.com/open-everywhere/open-everywhere/blob/main/docs/extending/adapter-model.md`),
234
260
  which `--check` cannot see: that step stays `missing` on the Docker route either way, so read it
@@ -289,9 +315,10 @@ person in chat cannot add one.
289
315
  posts a "Connect my account" card, and they sign in with their own account once. A channel
290
316
  gets nothing until an admin grants the server's `workspace-oauth` credential (the org's own
291
317
  account) on a bundle in `agent/access/bundles.ts`.
292
- 5. Say who "each person" is: every account that can DM the agent, an external one included (a
293
- Slack Connect user, a Discord user who shares a server with the bot). If external people can
294
- DM the agent, offer to narrow the entry with `people: { groups: ["<group>"] }`.
318
+ 5. Say who "each person" is: every INTERNAL person who can DM the agent. An external account (a
319
+ Slack Connect user, a Slack guest, a Discord user whom the deployment does not vouch) does not
320
+ get the server. To give it to a named external person, or to narrow it, offer
321
+ `people: { groups: ["<group>"] }` with a group that lists those people.
295
322
 
296
323
  With no server declared, nothing changes: the build writes no connection for one, and only the
297
324
  shipped connections (Notion) stay mounted.
@@ -310,15 +337,21 @@ back to a page on this computer, so the user copies nothing.
310
337
  this computer. GitHub opens with the App filled in. They check the name (GitHub App names are
311
338
  unique, so they change it if GitHub says it is taken) and confirm.
312
339
  3. Call `surface_app_create { surface: "github", action: "status" }`. `state: "done"` lists the
313
- env names written to `.env.local` and the App's install link. Keep the link for step 5.
314
- 4. On the Vercel route, push each name in `written` exactly as in step 9, then redeploy. On the
315
- Docker route, add each one to the server's `.env` (Docker step 6), then restart the stack.
340
+ secret names written to `.env.local`, the App's public values as `settings`, and the App's
341
+ install link. Keep the link for step 5.
342
+ 4. Write each entry of `settings` into the `github` group of `agent/settings/settings.ts`, for
343
+ example `github: { appId: "123", appSlug: "acme-agent", appClientId: "Iv1.…", botUserId: "456",
344
+ conversation: true }`. Keep every other `github` key the file already sets. These are public
345
+ values, so they go in Git. `conversation` is `true` only for the default App: it lets @mentions
346
+ start a conversation. Then store each name in `written` (`GITHUB_APP_PRIVATE_KEY`,
347
+ `GITHUB_WEBHOOK_SECRET`) with `secret_set`, because they are operator secrets, and redeploy with
348
+ the settings change. On the Docker route, the redeploy is a restart of the stack.
316
349
  5. Only after that redeploy or restart, ask the user to open the install link and install the App
317
350
  on the repositories the agent works in. The install sends one `installation` delivery, and the
318
351
  deployment learns the installation from it. Before the redeploy the deployment does not hold the
319
352
  App's webhook secret, so it refuses that delivery and never learns the installation.
320
353
 
321
- The tool never returns a value, and it refuses to overwrite names `.env.local` already holds.
354
+ The tool never returns a secret, and it refuses to overwrite names `.env.local` already holds.
322
355
 
323
356
  ## Develop locally
324
357
 
@@ -64,11 +64,16 @@ services:
64
64
  # the session's allow list. justbash shares this process's filesystem: development-grade
65
65
  # isolation. The app refuses to boot here on `"docker"`: eve starts docker session containers
66
66
  # on the daemon's default network, outside this proxy.
67
- OE_EGRESS_PROXY_URL: "http://oe-egress-proxy:8080" # the control endpoint for the policy push
67
+ # 🔴 THE APP'S EGRESS WIRING IS SETTINGS, NOT ENV (0.9.0, OPE-2650). Declare it in
68
+ # agent/settings/settings.ts, or the app runs without the proxy and a session that needs a host
69
+ # aborts (fail-closed):
70
+ # egress: { proxyUrl: "http://oe-egress-proxy:8080", denialLog: "/var/log/oe-egress/denied.log" }
71
+ # The token below is the operator secret OE_EGRESS_PROXY_TOKEN. The app reads it from this
72
+ # environment as the secret's migration on-ramp (with a one-time notice), or from the db
73
+ # credential store after `oe secret set OE_EGRESS_PROXY_TOKEN`. The proxy reads its own copy above.
68
74
  OE_EGRESS_PROXY_TOKEN: "${OE_EGRESS_PROXY_TOKEN:?set OE_EGRESS_PROXY_TOKEN in .env — the app's policy push is rejected without it (same value as the proxy)}"
69
- # The proxy's denial log: a refused host becomes an `egress.denied` audit row. Unset it to turn
70
- # the reader off.
71
- OE_EGRESS_DENIAL_LOG: "${OE_EGRESS_DENIAL_LOG:-/var/log/oe-egress/denied.log}"
75
+ # `egress.denialLog` (above) reads the proxy's denial log: a refused host becomes an
76
+ # `egress.denied` audit row. Leave it out of the settings to turn the reader off.
72
77
  # Tier B: trust the MITM CA for git, npm and curl (no effect on Tier A).
73
78
  NODE_EXTRA_CA_CERTS: "/opt/oe-egress/ca/oe-egress-ca.crt"
74
79
  GIT_SSL_CAINFO: "/opt/oe-egress/ca/oe-egress-ca.crt"
@@ -42,6 +42,7 @@ registerGithubOps();
42
42
  // Added a surface to roster.config.ts? Register its ops arm too:
43
43
  // import { registerTeamsOps } from "@open-everywhere/provider-teams/agent/ops.js";
44
44
  // import { registerDiscordOps } from "@open-everywhere/provider-discord/agent/ops.js";
45
+ // import { registerLinearOps } from "@open-everywhere/provider-linear/agent/ops.js";
45
46
  registerSurfaceTrustPreflight();
46
47
 
47
48
  const server = buildOpsServer();