@open-everywhere/create 0.6.0 → 0.8.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 +6 -4
- package/package.json +3 -3
- package/src/scaffold.ts +2 -2
- package/src/steps/build-deploy.ts +2 -2
- package/template/.dockerignore +35 -0
- package/template/AGENTS.md +7 -0
- package/template/Dockerfile +60 -0
- package/template/README.md +13 -0
- package/template/agent/access/a2a-inbound.ts +69 -0
- package/template/agent/agent.ts +25 -8
- package/template/agent/google-chat/policy.ts +34 -0
- package/template/agent/instructions.md +35 -20
- package/template/agent/lib/register-config.ts +20 -1
- package/template/agent/meetings/policy.ts +2 -4
- package/template/agent/memory/config/domains/org.ts +2 -2
- package/template/agent/model/models.ts +17 -0
- package/template/agent/model/stacks.ts +23 -6
- package/template/agent/routines/collectors.ts +28 -0
- package/template/agent/settings/settings.ts +21 -2
- package/template/agent/spend/model-pricing.ts +3 -1
- package/template/claude/skills/quickstart-open-everywhere/SKILL.md +96 -9
- package/template/claude/skills/upgrade-open-everywhere/SKILL.md +17 -4
- package/template/deploy/egress-proxy/Dockerfile +30 -0
- package/template/deploy/egress-proxy/control/acl.ts +228 -0
- package/template/deploy/egress-proxy/control/handlers.ts +174 -0
- package/template/deploy/egress-proxy/control/helper.cjs +108 -0
- package/template/deploy/egress-proxy/control/server.ts +122 -0
- package/template/deploy/egress-proxy/entrypoint.sh +62 -0
- package/template/deploy/egress-proxy/gen-ca.sh +42 -0
- package/template/deploy/egress-proxy/install-ca.sh +39 -0
- package/template/deploy/egress-proxy/squid.conf +93 -0
- package/template/deploy/egress-proxy/tier-b.conf +42 -0
- package/template/docker-compose.yml +135 -0
- package/template/gitignore +4 -0
- package/template/package.json +5 -3
- package/template/roster.config.ts +2 -2
- package/template/roster.lock.json +14 -1
- package/template/scripts/goldens/roster-plan.txt +13 -0
- package/template/scripts/model-config-check.ts +81 -0
- package/template/scripts/ops-mcp.ts +1 -1
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
registerDefaultAgentModelId,
|
|
8
8
|
} from "@open-everywhere/core/agent/model/default-model.js";
|
|
9
9
|
import { getSettings } from "@open-everywhere/core/agent/settings/settings.js";
|
|
10
|
+
import { viaProblem, type ModelTargets } from "@open-everywhere/core/agent/model/targets.js";
|
|
11
|
+
import { registerBareModelRoutes } from "@open-everywhere/core/agent/model/bare-model-via.js";
|
|
10
12
|
import { SETTINGS_OVERRIDES } from "../settings/settings.js";
|
|
11
13
|
import { resolveStack, resolveTranscription, type ModelRole, type ModelRoleSpec, type ModelStack } from "./stacks.js";
|
|
12
14
|
|
|
@@ -19,6 +21,10 @@ const STACK: ModelStack = resolveStack(STACK_ALIAS, {});
|
|
|
19
21
|
|
|
20
22
|
// A deploy can retarget the primary (`models.force` / `models.primary` in agent/settings/settings.ts).
|
|
21
23
|
// Core owns that precedence, so read the primary back from it.
|
|
24
|
+
// Every role's `via` names a `models.targets` entry (OPE-2556). Fail LOUD at config load: an unknown target would
|
|
25
|
+
// otherwise fail every call of that role at run time.
|
|
26
|
+
assertStackVia(STACK, MODEL_SETTINGS.targets);
|
|
27
|
+
|
|
22
28
|
registerDefaultAgentModelId(STACK.primary.id);
|
|
23
29
|
|
|
24
30
|
export const MODELS = {
|
|
@@ -29,6 +35,9 @@ export const MODELS = {
|
|
|
29
35
|
judge: STACK.judge,
|
|
30
36
|
} as const satisfies Record<string, ModelRoleSpec>;
|
|
31
37
|
|
|
38
|
+
// A bare model id (a declared subagent's `model`) goes through the target of the role that names it, else the primary's.
|
|
39
|
+
registerBareModelRoutes(Object.values(MODELS));
|
|
40
|
+
|
|
32
41
|
/** The alias of the stack this process resolved — for `/oe status`. */
|
|
33
42
|
export const MODEL_STACK = STACK_ALIAS;
|
|
34
43
|
/** The meeting bot's speech-to-text role: a primary and a fallback route (stacks.ts TRANSCRIPTION). */
|
|
@@ -38,6 +47,8 @@ export const PRIMARY_MODEL = MODELS.primary.id;
|
|
|
38
47
|
export const UTILITY_MODEL = MODELS.utility.id;
|
|
39
48
|
export const VISION_MODEL = MODELS.vision.id;
|
|
40
49
|
export const SUPERVISOR_MODEL = MODELS.supervisor.id;
|
|
50
|
+
/** The supervisor role's `models.targets` entry (OPE-2556): the org domain's supervisor route carries it. */
|
|
51
|
+
export const SUPERVISOR_VIA = MODELS.supervisor.via;
|
|
41
52
|
export const JUDGE_MODEL = MODELS.judge.id;
|
|
42
53
|
export const PRIMARY_MODEL_CONTEXT_WINDOW_TOKENS = MODELS.primary.contextWindowTokens;
|
|
43
54
|
|
|
@@ -62,3 +73,9 @@ export function allowListForStack(stack: ModelStack, extra: readonly ModelPolicy
|
|
|
62
73
|
export function modelAllowList(extra: readonly ModelPolicyAllowEntry[] = []): ModelPolicyAllowEntry[] {
|
|
63
74
|
return allowListForStack(MODELS, extra);
|
|
64
75
|
}
|
|
76
|
+
|
|
77
|
+
/** Throw when a stack role's `via` names no `models.targets` entry. Exported for tests. */
|
|
78
|
+
export function assertStackVia(stack: ModelStack, targets: ModelTargets): void {
|
|
79
|
+
const bad = (Object.keys(stack) as ModelRole[]).flatMap((role) => viaProblem(stack[role].via, targets, `stack role "${role}"`) ?? []);
|
|
80
|
+
if (bad.length > 0) throw new Error(`open-everywhere: ${bad.join("; ")}`);
|
|
81
|
+
}
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
// judge — the auto-approval judge.
|
|
10
10
|
//
|
|
11
11
|
// Every id is an AI Gateway model id. Add a stack by adding an entry, and give each new id a rate in
|
|
12
|
-
// agent/spend/model-pricing.ts, or a USD spend cap cannot see its cost.
|
|
12
|
+
// agent/spend/model-pricing.ts, or a USD spend cap cannot see its cost. A role's `via` names the
|
|
13
|
+
// `models.targets` entry its calls go through: the European stack uses the `mistral` target in
|
|
14
|
+
// agent/settings/settings.ts (only the Mistral provider, zero retention).
|
|
13
15
|
|
|
14
16
|
import type { ModelTier } from "@open-everywhere/core/agent/memory/domain.js";
|
|
15
17
|
import type { TranscriptionRoleSpec } from "@open-everywhere/adapters/agent/model/transcription-routes.js";
|
|
@@ -21,17 +23,19 @@ export interface ModelRoleSpec {
|
|
|
21
23
|
readonly tier: ModelTier;
|
|
22
24
|
/** The model's context window. eve compacts at `agent.compactionThresholdPercent` of it. */
|
|
23
25
|
readonly contextWindowTokens: number;
|
|
26
|
+
/** The `models.targets` entry this role's calls go through (OPE-2556). Absent: `"gateway"`. An unknown name fails config load. */
|
|
27
|
+
readonly via?: string;
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export type ModelStack = Readonly<Record<ModelRole, ModelRoleSpec>>;
|
|
27
31
|
|
|
28
32
|
export const STACKS = {
|
|
29
33
|
"chinese-open-weights": {
|
|
30
|
-
primary: { id: "deepseek/deepseek-v4-flash
|
|
31
|
-
utility: { id: "deepseek/deepseek-v4-flash
|
|
34
|
+
primary: { id: "deepseek/deepseek-v4.1-flash", tier: "high", contextWindowTokens: 1_000_000 },
|
|
35
|
+
utility: { id: "deepseek/deepseek-v4.1-flash", tier: "low", contextWindowTokens: 1_000_000 },
|
|
32
36
|
vision: { id: "alibaba/qwen3-vl-instruct", tier: "low", contextWindowTokens: 131_072 },
|
|
33
|
-
supervisor: { id: "deepseek/deepseek-v4-flash
|
|
34
|
-
judge: { id: "deepseek/deepseek-v4-flash
|
|
37
|
+
supervisor: { id: "deepseek/deepseek-v4.1-flash", tier: "low", contextWindowTokens: 1_000_000 },
|
|
38
|
+
judge: { id: "deepseek/deepseek-v4.1-flash", tier: "low", contextWindowTokens: 1_000_000 },
|
|
35
39
|
},
|
|
36
40
|
"american-closed": {
|
|
37
41
|
primary: { id: "openai/gpt-6-luna", tier: "high", contextWindowTokens: 1_050_000 },
|
|
@@ -40,6 +44,14 @@ export const STACKS = {
|
|
|
40
44
|
supervisor: { id: "openai/gpt-6-luna", tier: "low", contextWindowTokens: 1_050_000 },
|
|
41
45
|
judge: { id: "openai/gpt-6-luna", tier: "low", contextWindowTokens: 1_050_000 },
|
|
42
46
|
},
|
|
47
|
+
// Mistral Small 4 (Apache 2.0) in every role, vision included, served by Mistral with zero retention.
|
|
48
|
+
"european-open-weights": {
|
|
49
|
+
primary: { id: "mistral/mistral-small", tier: "high", contextWindowTokens: 262_144, via: "mistral" },
|
|
50
|
+
utility: { id: "mistral/mistral-small", tier: "low", contextWindowTokens: 262_144, via: "mistral" },
|
|
51
|
+
vision: { id: "mistral/mistral-small", tier: "low", contextWindowTokens: 262_144, via: "mistral" },
|
|
52
|
+
supervisor: { id: "mistral/mistral-small", tier: "low", contextWindowTokens: 262_144, via: "mistral" },
|
|
53
|
+
judge: { id: "mistral/mistral-small", tier: "low", contextWindowTokens: 262_144, via: "mistral" },
|
|
54
|
+
},
|
|
43
55
|
} as const satisfies Record<string, ModelStack>;
|
|
44
56
|
|
|
45
57
|
export type StackAlias = keyof typeof STACKS;
|
|
@@ -78,7 +90,7 @@ export function resolveStack(alias: string, overrides: StackOverrides = {}): Mod
|
|
|
78
90
|
// Speech to text for the meeting bot: a primary and a fallback route per stack, on two different providers, so
|
|
79
91
|
// one provider outage falls through to the other. A route names its transport too: the AI Gateway, DeepInfra
|
|
80
92
|
// (key: `oe secret set DEEPINFRA_API_KEY`), or your own OpenAI-compatible speech server. These defaults are
|
|
81
|
-
// pending a bake-off (`
|
|
93
|
+
// pending a bake-off (the `bakeoff:stt` script of the Open Everywhere repository).
|
|
82
94
|
export const TRANSCRIPTION = {
|
|
83
95
|
"chinese-open-weights": {
|
|
84
96
|
primary: { via: "deepinfra", id: "Qwen/Qwen3-ASR-1.7B" },
|
|
@@ -88,6 +100,11 @@ export const TRANSCRIPTION = {
|
|
|
88
100
|
primary: { via: "gateway", id: "microsoft/mai-transcribe-2" },
|
|
89
101
|
fallback: { via: "gateway", id: "spacexai/grok-stt" },
|
|
90
102
|
},
|
|
103
|
+
// Mistral's Voxtral on your own speech server, then Qwen3-ASR on a second one: audio stays on your machines.
|
|
104
|
+
"european-open-weights": {
|
|
105
|
+
primary: { via: "self-hosted", id: "mistralai/Voxtral-Mini-3B-2507", baseUrl: "http://voxtral:8000/v1" },
|
|
106
|
+
fallback: { via: "self-hosted", id: "Qwen/Qwen3-ASR-1.7B", baseUrl: "http://qwen3-asr:8000/v1" },
|
|
107
|
+
},
|
|
91
108
|
} as const satisfies Record<StackAlias, TranscriptionRoleSpec>;
|
|
92
109
|
|
|
93
110
|
/** Fully local: your own speech server only. For a Docker deployment with no cloud model. */
|
|
@@ -65,6 +65,28 @@
|
|
|
65
65
|
// "merged is not live" case from the OPE-151 audit, and it is the one where getting it backwards is
|
|
66
66
|
// most expensive: telling a customer their bug is fixed because a deploy read timed out.
|
|
67
67
|
//
|
|
68
|
+
// ── The chief-of-staff watch (OPE-2422) ───────────────────────────────────────────────────────
|
|
69
|
+
//
|
|
70
|
+
// The PERSONAL WATCH reads each person's own sources on a schedule and messages them in their DM when it finds
|
|
71
|
+
// something that needs them, such as two meetings that overlap. A source is one of the collectors above, read
|
|
72
|
+
// AS the person (the reader receives `forPrincipal`), plus the detector that reads its body:
|
|
73
|
+
//
|
|
74
|
+
// ROUTINE_COLLECTORS: [{
|
|
75
|
+
// name: "calendar.next-3-days",
|
|
76
|
+
// tool: "gcal__list_events", // the person's own calendar connector
|
|
77
|
+
// args: { timeMin: "now", timeMax: "+3d", singleEvents: true },
|
|
78
|
+
// describe: "the person's own calendar for the next three days",
|
|
79
|
+
// personal: true, // read AS one person; the watch names only these
|
|
80
|
+
// }],
|
|
81
|
+
// PERSONAL_WATCH: { sources: [{ collector: "calendar.next-3-days", detect: "calendar-conflicts" }] },
|
|
82
|
+
//
|
|
83
|
+
// 🔴 ON BY DEFAULT FOR EVERY MEMBER once a source is declared here: each member of the organisation who talks to
|
|
84
|
+
// the agent in their own DM is watched at `less`, and turns it to `more` or `none` with `/oe watch` or the Home tab.
|
|
85
|
+
// With no source declared, the watch makes no read and sends nothing. The boot refuses a source whose collector is
|
|
86
|
+
// not `personal: true`, and a personal read reaches only a reader registered with
|
|
87
|
+
// `registerRoutineCollectorReader(reader, { personal: true })` — a reader that promises to read as `forPrincipal`
|
|
88
|
+
// with that person's own sign-in, and to throw when it cannot (routines/collector.ts).
|
|
89
|
+
|
|
68
90
|
// ── THE READER SEAM ────────────────────────────────────────────────────────────────────────────
|
|
69
91
|
//
|
|
70
92
|
// 🔴 A COLLECTOR ONLY RUNS WHERE A READER IS REGISTERED, AND CORE SHIPS NONE. The dispatcher tick is
|
|
@@ -77,6 +99,7 @@
|
|
|
77
99
|
|
|
78
100
|
import { registerRoutineCollectors, type RoutineCollectorDef } from "@open-everywhere/core/agent/routines/collector.js";
|
|
79
101
|
import { registerRoutineGates, type RoutineGateDef } from "@open-everywhere/core/agent/routines/gate.js";
|
|
102
|
+
import { registerPersonalWatch, type PersonalWatchDecl } from "@open-everywhere/core/agent/personal-watch/watch.js";
|
|
80
103
|
|
|
81
104
|
/** The reads this deployment lets a routine run before its fire. Empty by default — declare one
|
|
82
105
|
* here and it becomes nameable by `create_routine`, and by nothing else. */
|
|
@@ -85,5 +108,10 @@ export const ROUTINE_COLLECTORS: readonly RoutineCollectorDef[] = [];
|
|
|
85
108
|
/** The conditions this deployment lets a routine fire behind. Empty by default. */
|
|
86
109
|
export const ROUTINE_GATES: readonly RoutineGateDef[] = [];
|
|
87
110
|
|
|
111
|
+
/** The chief-of-staff watch's sources. Empty by default: the watch reads nothing until one is declared. */
|
|
112
|
+
export const PERSONAL_WATCH: PersonalWatchDecl = { sources: [] };
|
|
113
|
+
|
|
88
114
|
registerRoutineCollectors(ROUTINE_COLLECTORS);
|
|
89
115
|
registerRoutineGates(ROUTINE_GATES);
|
|
116
|
+
// After the collectors: a watch source names a declared collector, and the registration refuses one it cannot find.
|
|
117
|
+
registerPersonalWatch(PERSONAL_WATCH);
|
|
@@ -9,8 +9,11 @@
|
|
|
9
9
|
//
|
|
10
10
|
// Examples:
|
|
11
11
|
// identity: { name: "Ada" }, // the name the agent answers to
|
|
12
|
-
// models: { stack: "chinese-open-weights" }, // every model role at once
|
|
13
|
-
//
|
|
12
|
+
// models: { stack: "chinese-open-weights" }, // every model role at once (or "european-open-weights")
|
|
13
|
+
// models: { dataPolicy: { zeroRetention: true } }, // every model call: only zero-retention providers
|
|
14
|
+
// models: { targets: { gateway: { kind: "vercel-ai-gateway", providers: { only: ["openai", "azure"] } } } }, // only these providers
|
|
15
|
+
// After a change to `models`, run `npm run check:model-config`: it names a model no provider can serve.
|
|
16
|
+
// sandbox: { selfHostBackend: "docker" }, // a per-session container; needs a Docker daemon at build time
|
|
14
17
|
//
|
|
15
18
|
// Every behaviour choice that used to be an env var lives here too (OPE-2411): the model pins
|
|
16
19
|
// (`models.*`), the admin control plane (`control.*`), the directory sync (`directory.*`), AWS
|
|
@@ -31,4 +34,20 @@ export const SETTINGS_OVERRIDES = defineSettings({
|
|
|
31
34
|
// refuses: no browser tool mounts, no host opens, and the boot logs one line that names the
|
|
32
35
|
// platform. Set `enabled: false` to turn the browser off, and to silence that line off Vercel.
|
|
33
36
|
browser: { enabled: true },
|
|
37
|
+
// The sandbox backend off Vercel. eve provisions the sandbox template at BUILD time, on this
|
|
38
|
+
// backend, so it decides where `npm run build` works: `justbash` needs nothing, and `docker` needs
|
|
39
|
+
// a running Docker daemon at build time. The image (Dockerfile) builds with no daemon, so the
|
|
40
|
+
// Docker route needs `justbash`, and so does a local build on a machine without Docker.
|
|
41
|
+
// justbash shares the app process's filesystem: development-grade isolation. On Vercel this line
|
|
42
|
+
// has no effect: the Vercel Sandbox always runs there.
|
|
43
|
+
sandbox: { selfHostBackend: "justbash" },
|
|
44
|
+
// Where model calls go. `gateway` is the shipped target. `mistral` is the European stack's route
|
|
45
|
+
// (`european-open-weights` in agent/model/stacks.ts): only the `mistral` provider, and every call
|
|
46
|
+
// demands zero retention. A role with no `via` uses `gateway`, so this entry changes no other call.
|
|
47
|
+
models: {
|
|
48
|
+
targets: {
|
|
49
|
+
gateway: { kind: "vercel-ai-gateway" },
|
|
50
|
+
mistral: { kind: "vercel-ai-gateway", providers: { only: ["mistral"] }, dataPolicy: { zeroRetention: true } },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
34
53
|
});
|
|
@@ -11,11 +11,13 @@ import { defineModelPricing, assertModelPricingValid, type ModelPricingTable } f
|
|
|
11
11
|
|
|
12
12
|
const pricing: ModelPricingTable = defineModelPricing({
|
|
13
13
|
// chinese-open-weights: primary, utility, supervisor and judge.
|
|
14
|
-
"deepseek/deepseek-v4-flash
|
|
14
|
+
"deepseek/deepseek-v4.1-flash": { inPerMtok: 0.3, outPerMtok: 1.2 },
|
|
15
15
|
// chinese-open-weights: vision.
|
|
16
16
|
"alibaba/qwen3-vl-instruct": { inPerMtok: 0.2, outPerMtok: 0.88 },
|
|
17
17
|
// american-closed: vision.
|
|
18
18
|
"google/gemini-3.8-flash": { inPerMtok: 0.75, outPerMtok: 3.75 },
|
|
19
|
+
// european-open-weights: every role.
|
|
20
|
+
"mistral/mistral-small": { inPerMtok: 0.15, outPerMtok: 0.6 },
|
|
19
21
|
});
|
|
20
22
|
|
|
21
23
|
assertModelPricingValid(pricing);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: quickstart-open-everywhere
|
|
3
|
-
description: Set up, configure or deploy an Open Everywhere app — from a fresh scaffold to an agent that answers in Slack. Use when the user wants to set up Open Everywhere, deploy it, or connect Slack, Notion or GitHub. Two routes are supported, Vercel and Docker, and step 1 is to ask which. Drives the open-everywhere-ops MCP server (preflight, surface_manifest, scaffold_env, scaffold_credential_key, secret_set/list/rm)
|
|
3
|
+
description: Set up, configure or deploy an Open Everywhere app — from a fresh scaffold to an agent that answers in Slack. Use when the user wants to set up Open Everywhere, deploy it, or connect Slack, Notion or GitHub. Two routes are supported, Vercel and Docker, and step 1 is to ask which. Drives the open-everywhere-ops MCP server (preflight, surface_manifest, surface_app_create, scaffold_env, scaffold_credential_key, secret_set/list/rm, sovereignty), the `@open-everywhere/create --check --json` status report and the `npm run check:model-config` model check.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Quickstart Open Everywhere
|
|
@@ -79,6 +79,10 @@ To turn it off, `OE_TELEMETRY=off` (or `DO_NOT_TRACK=1`) must be set on the DEPL
|
|
|
79
79
|
`printf off | npx vercel env add OE_TELEMETRY production`. On the Docker route, put it in the
|
|
80
80
|
container's environment. Every boot prints a line that says whether telemetry is on.
|
|
81
81
|
|
|
82
|
+
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`
|
|
84
|
+
command run by hand, or an app scaffolded before 0.8.0, read the eve section of the page above.
|
|
85
|
+
|
|
82
86
|
## Status, at every step
|
|
83
87
|
|
|
84
88
|
- `npx @open-everywhere/create --check --json` — the step list as data: `satisfied`, `detail`, and a
|
|
@@ -92,6 +96,56 @@ Re-run both after each step. They are the only answer to "what is left to do": t
|
|
|
92
96
|
tool. The same step list, written for a human, is
|
|
93
97
|
`https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/setup-steps.md`.
|
|
94
98
|
|
|
99
|
+
## Check the model config, after every model setting
|
|
100
|
+
|
|
101
|
+
Run `npm run check:model-config` after the model settings are written, and before the deploy that
|
|
102
|
+
carries them. The Vercel route runs it at its build step, and the Docker route at its model route
|
|
103
|
+
step. Run it again after each change to `models` in `agent/settings/settings.ts` (`dataPolicy`,
|
|
104
|
+
`targets`, `force`), to the stack in `agent/model/stacks.ts`, or to a model route of a domain or a
|
|
105
|
+
role. The command is the same in both homes. For each model, it reads the public list of endpoints
|
|
106
|
+
from the model's target: the AI Gateway, unless `models.targets` names another URL. So it needs the
|
|
107
|
+
network and no credential. It changes nothing.
|
|
108
|
+
|
|
109
|
+
Read the result to the user in plain words:
|
|
110
|
+
|
|
111
|
+
- **Exit 0, no `✗` line and no `?` line.** Each model the app can use has a provider that meets
|
|
112
|
+
every demand. Go on.
|
|
113
|
+
- **A `?` line that says "did not answer", "answered HTTP" or "not in the expected shape".** The
|
|
114
|
+
check did not run for that model. This is not a green. Fix the network, then run it again. A
|
|
115
|
+
target URL that "does not serve the endpoints read" (a relay) cannot be checked here: say so.
|
|
116
|
+
- **A `?` line that says a `providers.only` name "serves none of the checked models".** The name is
|
|
117
|
+
most likely a typo. Show it to the user.
|
|
118
|
+
- **A `?` line that says "does not say whether".** The gateway does not publish that fact, so the
|
|
119
|
+
demand is unknown. Name the model and the demand to the user, then go on.
|
|
120
|
+
- **Exit 1, a `✗` line.** Each line names one demand and one model. Read it to the user:
|
|
121
|
+
- `region eu: no provider for deepseek/deepseek-v4.1-flash` (or `zeroRetention`, `noTraining`,
|
|
122
|
+
`providers.only`): no provider meets the demand. Under it every call of that model fails
|
|
123
|
+
closed: the call errors, and it never goes to a provider outside the demand. The choices are a
|
|
124
|
+
stack whose models meet the demand, a wider `providers.only` list, or fewer rows in
|
|
125
|
+
`models.dataPolicy` or in the target's own `dataPolicy`.
|
|
126
|
+
- `models.dataPolicy and target <name> name two different regions`: no call can meet both.
|
|
127
|
+
Keep one region.
|
|
128
|
+
- `the gateway does not know this model`: correct the model id in the stack or the route.
|
|
129
|
+
- `models.targets names no such target`: add the target to `models.targets`, or correct the
|
|
130
|
+
`via` name.
|
|
131
|
+
|
|
132
|
+
**Do not deploy on this red.** Let the user pick the fix, then run the check again. One role
|
|
133
|
+
differs: transcription tries the fallback route that `agent/model/stacks.ts` names when the
|
|
134
|
+
primary route fails. So a red on the primary transcription model sends the audio to the fallback
|
|
135
|
+
route. Check that route too.
|
|
136
|
+
|
|
137
|
+
After the deploy, `--check --host` reads the same check in the `model-route` row of the status
|
|
138
|
+
route. That row names no model, so on a `no` there, run `npm run check:model-config` for the names.
|
|
139
|
+
An app scaffolded before 0.8.0 has no `check:model-config` script: its upgrade notes say how to add it.
|
|
140
|
+
|
|
141
|
+
## Tell the user where their data lives
|
|
142
|
+
|
|
143
|
+
Before the first deploy, offer the data map once: `npx oe sovereignty --host vercel` (or
|
|
144
|
+
`--host docker`), or the `sovereignty` ops tool. For each piece of data it says where it lives,
|
|
145
|
+
which region, and which company could be compelled to hand it over. It reads the app's own config
|
|
146
|
+
and changes nothing. Give the user the short version. Run it again after a change to the model
|
|
147
|
+
settings or the store.
|
|
148
|
+
|
|
95
149
|
## The Vercel route
|
|
96
150
|
|
|
97
151
|
1. **Install.** If `node_modules` is missing: `npm install`. Its `postinstall` runs `oe-eve-patch`,
|
|
@@ -132,7 +186,8 @@ tool. The same step list, written for a human, is
|
|
|
132
186
|
`agent/memory/config/domains/org.ts` set `admins: ["slack/<team-id>/<member-id>"]`. Offer to
|
|
133
187
|
commit both edits. They take effect on the next deploy, so make them before it.
|
|
134
188
|
Then OFFER the optional window step below. Do not make it for the user unasked.
|
|
135
|
-
8. **Build.** `npm run build`. It must pass.
|
|
189
|
+
8. **Build.** `npm run build`. It must pass. Then `npm run check:model-config`, and read it as
|
|
190
|
+
"Check the model config" above says. A `✗` line stops the route here.
|
|
136
191
|
9. **Deployment env.** `.env.local` never ships, so the deployment reads only what you set ON it.
|
|
137
192
|
The names are `OE_CREDENTIAL_ENCRYPTION_KEY`, `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET`. The
|
|
138
193
|
Neon integration already set `DATABASE_URL` there, and that one value resolves both stores — so
|
|
@@ -170,20 +225,28 @@ Then:
|
|
|
170
225
|
1. **Install and build.** `npm install`, then `npm run build`.
|
|
171
226
|
2. **Postgres.** Provision one the container can reach. The user runs `npx oe env set DATABASE_URL`
|
|
172
227
|
and pastes its connection string at the prompt. That one value resolves both stores to postgres;
|
|
173
|
-
set no provider selector.
|
|
228
|
+
set no provider selector. The compose stack of step 6 also carries a Postgres of its own.
|
|
174
229
|
3. **Key.** `scaffold_credential_key`.
|
|
175
230
|
4. **Model route.** There is no OIDC off Vercel, so the user supplies `AI_GATEWAY_API_KEY` (Vercel
|
|
176
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
|
|
177
232
|
an **adapter swap in app code**
|
|
178
233
|
(`https://github.com/open-everywhere/open-everywhere/blob/main/docs/extending/adapter-model.md`),
|
|
179
234
|
which `--check` cannot see: that step stays `missing` on the Docker route either way, so read it
|
|
180
|
-
and move on rather than chasing a green it cannot give.
|
|
235
|
+
and move on rather than chasing a green it cannot give. On the gateway, run
|
|
236
|
+
`npm run check:model-config` once the model settings are written, and read it as "Check the
|
|
237
|
+
model config" above says. A `✗` line stops the route here.
|
|
181
238
|
5. **Slack app.** Exactly as steps 5–7 of the Vercel route.
|
|
182
|
-
6. **Run it.**
|
|
183
|
-
|
|
184
|
-
`
|
|
185
|
-
|
|
186
|
-
|
|
239
|
+
6. **Run it.** The app ships its own `Dockerfile` and `docker-compose.yml`: the image, the egress
|
|
240
|
+
proxy (`deploy/egress-proxy/`), Postgres and its WebSocket bridge. On the server, the user puts
|
|
241
|
+
the contract's environment in `.env`, with `OE_EGRESS_PROXY_TOKEN` (`openssl rand -hex 32`) and
|
|
242
|
+
a `POSTGRES_PASSWORD`, then runs `docker compose up --build`. The compose file sets
|
|
243
|
+
`DATABASE_URL` to its own `postgres` service. For the managed Postgres of step 2 instead, put
|
|
244
|
+
its `DATABASE_URL` in `.env`, then delete the app's `DATABASE_URL` and `OE_NEON_WS_PROXY` lines
|
|
245
|
+
and the `postgres` and `neon-wsproxy` services from the compose file. Put a TLS-terminating
|
|
246
|
+
reverse proxy in front that forwards `/eve/` and `/.well-known/workflow/`.
|
|
247
|
+
Tell the user plainly: this stack runs the `justbash` sandbox backend, so the agent's shell
|
|
248
|
+
shares the app process's filesystem and environment, every `.env` value included. That is
|
|
249
|
+
development grade, and it does not meet the contract's `sandbox-backend` requirement.
|
|
187
250
|
7. **Prove it, then the Slack URLs and the hello.** As steps 11–13 of the Vercel route, with
|
|
188
251
|
`--route docker --host https://<host>`.
|
|
189
252
|
|
|
@@ -233,6 +296,30 @@ person in chat cannot add one.
|
|
|
233
296
|
With no server declared, nothing changes: the build writes no connection for one, and only the
|
|
234
297
|
shipped connections (Notion) stay mounted.
|
|
235
298
|
|
|
299
|
+
## Optional: connect GitHub
|
|
300
|
+
|
|
301
|
+
Offer this step after the first deploy, when the user wants the agent to work in GitHub. The
|
|
302
|
+
GitHub App is one click: the ops server generates its manifest, and GitHub hands the App's keys
|
|
303
|
+
back to a page on this computer, so the user copies nothing.
|
|
304
|
+
|
|
305
|
+
1. Call `surface_app_create { surface: "github", baseUrl: "https://<host>" }`. Pass
|
|
306
|
+
`owner: "<org login>"` to create the App under an organization. The default App also answers
|
|
307
|
+
@mentions on issues and pull requests; `variant: "source-control"` creates one for checkout,
|
|
308
|
+
pull requests and ticket actions only.
|
|
309
|
+
2. Give the user the `openUrl` from the reply. It is a local page: they open it in a browser on
|
|
310
|
+
this computer. GitHub opens with the App filled in. They check the name (GitHub App names are
|
|
311
|
+
unique, so they change it if GitHub says it is taken) and confirm.
|
|
312
|
+
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.
|
|
316
|
+
5. Only after that redeploy or restart, ask the user to open the install link and install the App
|
|
317
|
+
on the repositories the agent works in. The install sends one `installation` delivery, and the
|
|
318
|
+
deployment learns the installation from it. Before the redeploy the deployment does not hold the
|
|
319
|
+
App's webhook secret, so it refuses that delivery and never learns the installation.
|
|
320
|
+
|
|
321
|
+
The tool never returns a value, and it refuses to overwrite names `.env.local` already holds.
|
|
322
|
+
|
|
236
323
|
## Develop locally
|
|
237
324
|
|
|
238
325
|
Only when the user asks to change or debug the agent — never as a setup step. The user creates
|
|
@@ -37,7 +37,7 @@ npm view @open-everywhere/core version
|
|
|
37
37
|
**Read the installed one, not the pin.** `oe upgrade` writes the pins before it installs, so an
|
|
38
38
|
upgrade that stopped part way leaves a manifest that names a version the app is not running.
|
|
39
39
|
|
|
40
|
-
The
|
|
40
|
+
The eleven packages publish in lockstep, so one version answers for all of them. Agree the target
|
|
41
41
|
with the user before you go on. Taking the latest is the normal choice.
|
|
42
42
|
|
|
43
43
|
## 2. Read the notes between the two versions
|
|
@@ -55,13 +55,19 @@ nothing. Each entry names:
|
|
|
55
55
|
| CONFIG | a change to this app's own config files. A codemod applies some; the rest are yours. |
|
|
56
56
|
| ENV + / ENV - | an env NAME the deployment starts or stops needing. The admin sets the value. |
|
|
57
57
|
| MANUAL | a step outside the files — a dashboard change, a URL to re-paste, a key to rotate. |
|
|
58
|
+
| DEFAULT | behaviour the release turns on with no config change. It applies at the next deploy. The line says which setting turns it off, if one does. |
|
|
58
59
|
|
|
59
60
|
The installed copy of the record cannot know about a release published after it, so `--plan`
|
|
60
61
|
says plainly which crossed versions it has no entry for. The full window is printed again in
|
|
61
62
|
step 3, after the install puts the target's own copy on disk. The same text is in
|
|
62
63
|
`node_modules/@open-everywhere/core/UPGRADING.md`.
|
|
63
64
|
|
|
64
|
-
|
|
65
|
+
The installed core prints no DEFAULT line for the target. To see the target's new defaults before
|
|
66
|
+
the install, run the target's own `oe`:
|
|
67
|
+
`npx -p @open-everywhere/core@<target> oe upgrade --plan --to <target>`.
|
|
68
|
+
|
|
69
|
+
**Stop and ask the user** when the window holds a breaking change, an env change, a manual step or
|
|
70
|
+
a new default.
|
|
65
71
|
Those are decisions, not steps.
|
|
66
72
|
|
|
67
73
|
`--plan` also prints a **template drift** report. It compares every file the scaffold copied into
|
|
@@ -116,7 +122,12 @@ In order, and it stops at the first step that fails:
|
|
|
116
122
|
4. run the config codemods the target's notes name, and print the migrations that are yours;
|
|
117
123
|
5. `oe build --accept` — this rewrites `roster.lock.json`;
|
|
118
124
|
6. `npm run typecheck`;
|
|
119
|
-
7. `npm run build`.
|
|
125
|
+
7. `npm run build`. eve prepares the sandbox template at build time. Off Vercel an app whose
|
|
126
|
+
`sandbox.selfHostBackend` is `docker` needs a running Docker daemon for that (this template
|
|
127
|
+
declares `justbash`, which needs nothing). When the build fails, `docker version` does not
|
|
128
|
+
answer, and the app's `build` script ends in `eve build`, a core at 0.8.0 or later builds
|
|
129
|
+
again with `npm run build -- --skip-sandbox-prewarm` and says so in one line. eve says that output may not be deployable, so do not start or deploy
|
|
130
|
+
it. A Vercel deploy builds again on Vercel.
|
|
120
131
|
|
|
121
132
|
A failed step prints which one and leaves the tree where it stopped. Fix the cause and run
|
|
122
133
|
`oe upgrade` again: it RESUMES, from whichever step stopped it.
|
|
@@ -158,7 +169,8 @@ thing the agent can do. If a line surprises you, say so to the user before the p
|
|
|
158
169
|
is the review surface, and it is the only one.
|
|
159
170
|
|
|
160
171
|
Then run the app's own checks once more if anything above was fixed by hand:
|
|
161
|
-
`npm run typecheck && npm run build`.
|
|
172
|
+
`npm run typecheck && npm run build`. If the build stops because no Docker daemon runs, add
|
|
173
|
+
`-- --skip-sandbox-prewarm` to the build, as `oe upgrade` does, and do not deploy that output.
|
|
162
174
|
|
|
163
175
|
Then act on the template drift report. A core that carries it prints it again after the install;
|
|
164
176
|
if this run printed no "Template drift" heading, run `npx oe upgrade --to <target> --plan` now to get it. For
|
|
@@ -185,6 +197,7 @@ Commit on a branch, never on the default branch. The body carries:
|
|
|
185
197
|
- what changed in `roster.lock.json`, in your own words;
|
|
186
198
|
- **the env names the deployment needs before this merges**, names only;
|
|
187
199
|
- the manual steps, and who does each one.
|
|
200
|
+
- each new default, and the setting the user chose for it, or that no setting turns it off.
|
|
188
201
|
- the template files you synced or deleted, and each SYNC or DELETE line you left as it is, with the reason.
|
|
189
202
|
|
|
190
203
|
Then stop. The admin reviews the lock diff and merges. The merge deploys.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Open Everywhere egress proxy — Squid (Tier A/B) + the Node policy control service.
|
|
2
|
+
# The SOLE container with a WAN route in docker-compose.yml; the app reaches the internet
|
|
3
|
+
# only by proxying through here. See deploy/egress-proxy/README.md.
|
|
4
|
+
#
|
|
5
|
+
# Base on node:24-alpine (NOT plain alpine, whose `nodejs` package is too old for
|
|
6
|
+
# `--experimental-strip-types`). Node 24 runs the TypeScript control service directly via
|
|
7
|
+
# native type stripping — no build step. node:24-alpine puts node at /usr/local/bin; the
|
|
8
|
+
# squid external_acl helper references /usr/bin/node, so we symlink it below.
|
|
9
|
+
FROM node:24-alpine
|
|
10
|
+
|
|
11
|
+
# squid = the forward proxy + ssl_bump; openssl = CA generation for Tier B; wget = the
|
|
12
|
+
# entrypoint health probe. (nodejs comes from the base image.)
|
|
13
|
+
RUN apk add --no-cache squid openssl wget ca-certificates \
|
|
14
|
+
&& ln -sf "$(command -v node)" /usr/bin/node \
|
|
15
|
+
&& mkdir -p /opt/oe-egress/control /var/lib/squid /opt/oe-egress/ca
|
|
16
|
+
|
|
17
|
+
WORKDIR /opt/oe-egress
|
|
18
|
+
|
|
19
|
+
# Config + scripts + control service (kept alongside so the entrypoint copies a writable
|
|
20
|
+
# squid.conf and can drop the Tier B overlay in conditionally).
|
|
21
|
+
COPY squid.conf tier-b.conf gen-ca.sh install-ca.sh entrypoint.sh ./
|
|
22
|
+
COPY control/ ./control/
|
|
23
|
+
RUN chmod +x gen-ca.sh install-ca.sh entrypoint.sh
|
|
24
|
+
|
|
25
|
+
# 3128 = the forward proxy the app points HTTPS_PROXY at. The control service (8080) is
|
|
26
|
+
# reachable on the internal network so the app can POST policy; the ACL helper hits it on
|
|
27
|
+
# loopback. Not exposed to the host.
|
|
28
|
+
EXPOSE 3128 8080
|
|
29
|
+
|
|
30
|
+
ENTRYPOINT ["/bin/sh", "/opt/oe-egress/entrypoint.sh"]
|