@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 +2 -1
- package/package.json +3 -3
- package/src/app-memory.ts +25 -0
- package/src/steps/build-deploy.ts +1 -1
- package/src/steps/database.ts +9 -6
- package/src/steps/env-checklist.ts +3 -3
- package/src/steps/slack.ts +13 -13
- package/src/steps/verify.ts +14 -2
- package/template/.env.example +18 -15
- package/template/AGENTS.md +9 -2
- package/template/Dockerfile +1 -1
- package/template/README.md +7 -5
- package/template/agent/access/access-requests.ts +19 -18
- package/template/agent/access/bundles.ts +1 -1
- package/template/agent/access/connector-credentials.ts +6 -4
- package/template/agent/access/mcp-servers.ts +5 -3
- package/template/agent/memory/config/domains.ts +1 -1
- package/template/agent/personal-browser/policy.ts +3 -2
- package/template/agent/sandbox/sandbox.ts +2 -2
- package/template/claude/skills/quickstart-open-everywhere/SKILL.md +58 -25
- package/template/docker-compose.yml +9 -4
- package/template/scripts/ops-mcp.ts +1 -0
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 (`
|
|
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.
|
|
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.
|
|
50
|
-
"@open-everywhere/provider-slack": "0.
|
|
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
|
],
|
package/src/steps/database.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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 ||
|
|
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
|
|
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
|
|
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
|
],
|
package/src/steps/slack.ts
CHANGED
|
@@ -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.
|
|
8
|
-
//
|
|
9
|
-
//
|
|
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
|
-
// 🔴
|
|
15
|
-
// secrets
|
|
16
|
-
//
|
|
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
|
|
44
|
-
"
|
|
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
|
|
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
|
|
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
|
-
"
|
|
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).",
|
package/src/steps/verify.ts
CHANGED
|
@@ -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"
|
|
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
|
],
|
package/template/.env.example
CHANGED
|
@@ -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.
|
|
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
|
-
#
|
|
39
|
-
#
|
|
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
|
-
#
|
|
47
|
-
#
|
|
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
|
-
#
|
|
59
|
-
# GITHUB_APP_PRIVATE_KEY=
|
|
60
|
-
#
|
|
61
|
-
#
|
|
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) ---
|
package/template/AGENTS.md
CHANGED
|
@@ -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
|
|
28
|
-
`
|
|
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
|
package/template/Dockerfile
CHANGED
|
@@ -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, `
|
|
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.
|
package/template/README.md
CHANGED
|
@@ -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,
|
|
45
|
-
the Vercel project's environment or the
|
|
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
|
|
120
|
-
`
|
|
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
|
|
6
|
-
// politely and the flow is simply OFF — nothing else changes. Both
|
|
7
|
-
//
|
|
8
|
-
//
|
|
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
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
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:
|
|
40
|
-
configRepo:
|
|
41
|
-
configBase
|
|
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`: `
|
|
5
|
-
//
|
|
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
|
-
|
|
55
|
-
|
|
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
|
|
9
|
-
//
|
|
10
|
-
//
|
|
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
|
-
//
|
|
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
|
|
24
|
-
// (
|
|
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 +
|
|
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
|
|
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
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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,
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
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 `
|
|
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
|
|
193
|
-
Neon integration already set `DATABASE_URL` there, and that one value resolves both stores — so
|
|
194
|
-
there is no `OE_OPERATIONAL_PROVIDER`
|
|
195
|
-
|
|
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`.
|
|
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
|
|
293
|
-
Slack Connect user, a Discord user
|
|
294
|
-
|
|
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
|
-
|
|
314
|
-
|
|
315
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
#
|
|
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();
|