@open-everywhere/create 0.1.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +9 -9
- package/README.md +16 -5
- package/bin/create-open-everywhere.js +4 -0
- package/package.json +3 -3
- package/src/app-naming.ts +47 -0
- package/src/cli.ts +47 -10
- package/src/deploy-status.ts +84 -0
- package/src/engine.ts +9 -4
- package/src/env-file.ts +11 -9
- package/src/scaffold.ts +21 -0
- package/src/steps/database.ts +4 -3
- package/src/steps/env-checklist.ts +28 -9
- package/src/steps/slack.ts +18 -5
- package/template/.env.example +10 -1
- package/template/AGENTS.md +17 -3
- package/template/README.md +33 -2
- package/template/agent/access/bundles.ts +9 -0
- package/template/agent/access/pre-approval-rules.ts +81 -0
- package/template/agent/instructions.md +6 -4
- package/template/agent/instrumentation/otel.ts +13 -0
- package/template/agent/instrumentation/otlp.ts +4 -0
- package/template/agent/lib/register-adapters.ts +21 -282
- package/template/agent/sandbox/sandbox.ts +127 -73
- package/template/agent/spend/checkpoints.ts +4 -2
- package/template/claude/skills/quickstart-open-everywhere/SKILL.md +59 -22
- package/template/claude/skills/upgrade-open-everywhere/SKILL.md +40 -14
- package/template/package.json +1 -1
- package/template/roster.lock.json +7 -1
- package/template/scripts/goldens/roster-plan.txt +156 -0
- package/template/scripts/ops-mcp.ts +16 -0
- package/template/agent/instrumentation.ts +0 -6
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import { defineSandbox } from "eve/sandbox";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
1
|
+
import { defineSandbox, type RuntimeSandboxSession, type SandboxEnvironment, type SandboxSession } from "eve/sandbox";
|
|
2
|
+
import { VercelSandbox } from "eve/sandbox/vercel";
|
|
3
|
+
import { JustBashSandbox } from "eve/sandbox/just-bash";
|
|
4
|
+
import { DockerSandbox } from "eve/sandbox/docker";
|
|
5
5
|
import { bindFrom, type BindableContext } from "@open-everywhere/core/agent/identity/binding.js";
|
|
6
6
|
import { resolvePartition } from "@open-everywhere/core/agent/memory/partition.js";
|
|
7
7
|
import { sandboxPresetFor } from "@open-everywhere/core/agent/roles/role.js";
|
|
8
8
|
import { getConfig } from "@open-everywhere/core/agent/access/config.js";
|
|
9
9
|
import { onVercel } from "@open-everywhere/core/agent/access/platform.js";
|
|
10
|
-
import { agentBrowserSandboxHooks } from "@open-everywhere/adapters/agent/browser/sandbox.js";
|
|
11
|
-
import {
|
|
10
|
+
import { agentBrowserSandboxHooks, type BakeableSandbox } from "@open-everywhere/adapters/agent/browser/sandbox.js";
|
|
11
|
+
import { registerSandboxFirewall } from "@open-everywhere/core/agent/sandbox/network-policy.js";
|
|
12
12
|
import { resolveSelfHostBackendKind, selfHostBackendBootLog } from "./backend-select.js";
|
|
13
|
+
// The KIND type has one home — core's adapter-api barrel — so the backend that runs and the backend
|
|
14
|
+
// that stops cannot disagree about its spelling (see ./backend-select.ts's header).
|
|
15
|
+
import type { SelfHostBackendKind } from "@open-everywhere/core/agent/adapter-api.js";
|
|
13
16
|
import {
|
|
14
17
|
resolveSandboxRegionPlan,
|
|
15
18
|
vercelSandboxCreateOptions,
|
|
@@ -148,22 +151,67 @@ const VERCEL_CREATE_OPTIONS = vercelSandboxCreateOptions({
|
|
|
148
151
|
// ── Agent-browser PRE-BAKE (the in-sandbox browser plane, OPE-43 — flag-gated) ──
|
|
149
152
|
// When browser.enabled: true with at least one row in the host declaration
|
|
150
153
|
// (agent/access/browser-hosts.ts, registered through lib/register-config.ts) (and only on the
|
|
151
|
-
// Vercel backend), this
|
|
152
|
-
//
|
|
153
|
-
//
|
|
154
|
-
//
|
|
155
|
-
//
|
|
156
|
-
// fail-closed matrix live in the adapter (@open-everywhere/adapters/agent/browser/sandbox.js).
|
|
154
|
+
// Vercel backend), this bakes the agent-browser CLI + Chromium into the sandbox TEMPLATE —
|
|
155
|
+
// sessions boot warm, and nothing is ever fetched mid-session (the runtime firewall wouldn't allow
|
|
156
|
+
// it; the vendor tools additionally run with autoInstall bound off). Unarmed it is {}, and the
|
|
157
|
+
// environment below carries NO `prepare` at all. Rationale + fail-closed matrix live in the adapter
|
|
158
|
+
// (@open-everywhere/adapters/agent/browser/sandbox.js).
|
|
157
159
|
const AGENT_BROWSER_BAKE = agentBrowserSandboxHooks();
|
|
158
160
|
|
|
159
|
-
|
|
161
|
+
// 🔴 eve 0.69.0 TOOK `revalidationKey` AWAY, AND THE TEMPLATE KEY IS NOW THE ENVIRONMENT'S OWN HASH
|
|
162
|
+
// (OPE-2247). Through 0.68.x the bake contributed `{ revalidationKey, bootstrap }` to the sandbox
|
|
163
|
+
// definition, and bumping the agent-browser pin changed that key, which rebuilt the template. 0.69.0
|
|
164
|
+
// replaced both: setup is the environment's `prepare`, and a template is keyed by
|
|
165
|
+
// `sha256(stableSerialize(environment options))` — which serialises a function by its SOURCE TEXT
|
|
166
|
+
// (`shared/sandbox-environment.js`), not by the values it closes over. So ARMING or DISARMING the
|
|
167
|
+
// bake rebuilds the template (the `prepare` key appears or disappears), and EDITING this file's
|
|
168
|
+
// prepare body rebuilds it, but bumping the agent-browser version alone does NOT: the pin lives
|
|
169
|
+
// inside the vendor installer, behind a closure, where the hash cannot see it. The consequence is a
|
|
170
|
+
// stale warm template after a pin bump, not an unfenced session. Re-deriving a revalidation input the
|
|
171
|
+
// hash can see belongs with the browser plane, not with this port — it is handed to the orchestrator
|
|
172
|
+
// as a follow-up rather than guessed at here, because every field of these options is forwarded to
|
|
173
|
+
// Vercel's own `Sandbox.create`, so inventing a carrier key is a change to the create call.
|
|
174
|
+
const AGENT_BROWSER_PREPARE =
|
|
175
|
+
AGENT_BROWSER_BAKE.bootstrap === undefined
|
|
176
|
+
? {}
|
|
177
|
+
: {
|
|
178
|
+
// `installAgentBrowser` reads only `run` off the session it is given, verified in the vendor
|
|
179
|
+
// bytes: `sandbox.id` appears once in `@agent-browser/eve/dist/sandbox/sandbox.js`, inside
|
|
180
|
+
// `runAgentBrowser` (the TOOL path), never on the install path. eve 0.69.0's `prepare` hands
|
|
181
|
+
// over a `SandboxSession`, which carries `run` and no `id` — and `BakeableSandbox.id` is
|
|
182
|
+
// optional for exactly that reason, so this is a widening to the adapter's own shape rather
|
|
183
|
+
// than a cast that hides a missing field.
|
|
184
|
+
prepare: async (sandbox: SandboxSession): Promise<void> => {
|
|
185
|
+
const bootstrap = AGENT_BROWSER_BAKE.bootstrap;
|
|
186
|
+
if (bootstrap === undefined) return;
|
|
187
|
+
await bootstrap({ use: async () => sandbox satisfies BakeableSandbox });
|
|
188
|
+
},
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
// ── The three provider ENVIRONMENTS (eve 0.64.0 + 0.66.0 shape) ──
|
|
192
|
+
// eve replaced the object-form `defineSandbox({ backend, … })` with environments a selector opens.
|
|
193
|
+
// The environment is also the capability carrier: only a Vercel session takes a full
|
|
194
|
+
// `SandboxNetworkPolicy`, Docker takes the coarse `"allow-all"` / `"deny-all"` alone, and just-bash
|
|
195
|
+
// takes none because its policy is fixed at creation. Core's firewall sites reach whichever one is
|
|
196
|
+
// live through `registerSandboxFirewall` below, because they cannot import this file.
|
|
197
|
+
const VERCEL_ENVIRONMENT = VercelSandbox.environment({ ...VERCEL_CREATE_OPTIONS, ...AGENT_BROWSER_PREPARE });
|
|
198
|
+
|
|
199
|
+
interface SelfHostSandbox {
|
|
200
|
+
readonly kind: SelfHostBackendKind;
|
|
201
|
+
/** The environment itself, for the named `environment` export eve's compiler requires. */
|
|
202
|
+
readonly environment: SandboxEnvironment<object | undefined, SandboxSession>;
|
|
203
|
+
/** Opening is a closure because each provider's open options differ, and the union of two
|
|
204
|
+
* environments has no callable `open` signature. */
|
|
205
|
+
readonly open: () => Promise<RuntimeSandboxSession>;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function selfHostEnvironment(): SelfHostSandbox {
|
|
160
209
|
// Fail-loud selection: unknown OE_SANDBOX_BACKEND throws here at boot (never a silent fallback).
|
|
161
210
|
const kind = resolveSelfHostBackendKind();
|
|
162
211
|
const bootLog = selfHostBackendBootLog(kind);
|
|
163
212
|
if (bootLog.warn !== undefined) console.warn(bootLog.warn);
|
|
164
|
-
console.info(bootLog.info);
|
|
165
|
-
//
|
|
166
|
-
// hatch" deployment looks healthy until the first wake, when every session silently aborts.
|
|
213
|
+
console.info(bootLog.info);
|
|
214
|
+
// ── EGRESS POSTURE (self-host only) ──
|
|
167
215
|
const egressLog = selfHostEgressBootLog();
|
|
168
216
|
if (egressLog.warn !== undefined) console.warn(egressLog.warn);
|
|
169
217
|
console.info(egressLog.info);
|
|
@@ -176,67 +224,73 @@ function selfHostBackend() {
|
|
|
176
224
|
// readable file (the compose stack mounts one; a bare self-host run does not), and the timer is
|
|
177
225
|
// unref'd, so nothing about this can keep a process alive or take one down.
|
|
178
226
|
startEgressDenialPoller();
|
|
179
|
-
if (kind === "justbash")
|
|
227
|
+
if (kind === "justbash") {
|
|
228
|
+
const environment = JustBashSandbox.environment({});
|
|
229
|
+
registerSandboxFirewall({ carries: "none", backend: "justbash" });
|
|
230
|
+
return { kind, environment, open: async () => await environment.open() };
|
|
231
|
+
}
|
|
180
232
|
const image = getSettings().sandbox.image;
|
|
181
|
-
|
|
233
|
+
const environment = image ? DockerSandbox.environment({ image }) : DockerSandbox.environment({});
|
|
234
|
+
registerSandboxFirewall({ carries: "coarse", environment });
|
|
235
|
+
return { kind, environment, open: async () => await environment.open() };
|
|
182
236
|
}
|
|
183
237
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
238
|
+
// Resolved at module load, exactly as the backend ternary was: one boot decision, one set of logs,
|
|
239
|
+
// and the registration core's firewall sites read must be in place before the first turn.
|
|
240
|
+
const SELF_HOST = ON_VERCEL ? undefined : selfHostEnvironment();
|
|
241
|
+
if (ON_VERCEL) registerSandboxFirewall({ carries: "full", environment: VERCEL_ENVIRONMENT });
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* The environment eve's COMPILER reads, and it must be exactly one (`compiler/normalize-sandbox.js`:
|
|
245
|
+
* "Sandbox must export an environment"). eve records its `provider` name and this export's name in
|
|
246
|
+
* the compiled binding, so the built app is specialised to the backend resolved HERE, at module load —
|
|
247
|
+
* which is what the old `backend:` ternary already did, so nothing about that is new. The selector
|
|
248
|
+
* below opens the same environment; this export is how the build learns which provider that is.
|
|
249
|
+
*/
|
|
250
|
+
export const environment: SandboxEnvironment<object | undefined, SandboxSession> =
|
|
251
|
+
SELF_HOST === undefined ? (VERCEL_ENVIRONMENT as SandboxEnvironment<object | undefined, SandboxSession>) : SELF_HOST.environment;
|
|
252
|
+
|
|
253
|
+
export default defineSandbox(async (context) => {
|
|
187
254
|
// ── Per-domain/role sandbox SIZING (item 26 stretch) ──
|
|
188
255
|
// A domain (or the role preset it adopts) may declare `sandbox: { vcpus: N }`
|
|
189
|
-
// (core memory/domain.ts SandboxPreset); this
|
|
190
|
-
// eve forwards `
|
|
191
|
-
//
|
|
192
|
-
//
|
|
193
|
-
//
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
// whose backend DOES accept `{ resources }`. Type the sized open explicitly. The resolved
|
|
206
|
-
// session's `.id` is eve's own session key — see created-sandbox-registry.ts for why that is
|
|
207
|
-
// the same value the Vercel binding names the sandbox with.
|
|
208
|
-
const useSized = use as unknown as (options?: { resources: { vcpus: number } }) => Promise<{ id: string }>;
|
|
209
|
-
let options: { resources: { vcpus: number } } | undefined;
|
|
210
|
-
try {
|
|
211
|
-
// SessionContext structurally satisfies BindableContext (session.auth carries the
|
|
212
|
-
// platform attributes); a ctx with no resolvable identity falls through the
|
|
213
|
-
// binding bridge or resolves no domain — either way: no preset, default size.
|
|
214
|
-
const bindable = ctx as BindableContext;
|
|
215
|
-
const preset = sandboxPresetFor(bindFrom(bindable), resolvePartition(bindable), getConfig().loadDomains());
|
|
216
|
-
const vcpus = preset?.vcpus;
|
|
217
|
-
if (typeof vcpus === "number" && Number.isFinite(vcpus) && vcpus > 0) {
|
|
218
|
-
options = { resources: { vcpus: Math.floor(vcpus) } };
|
|
219
|
-
}
|
|
220
|
-
} catch (err) {
|
|
221
|
-
console.warn(`[open-everywhere:sandbox] vcpus preset resolution failed — using default size (${String(err)})`);
|
|
222
|
-
}
|
|
223
|
-
// OPE-722: record the ACTUAL vendor sandbox name eve just opened for this session, so the
|
|
224
|
-
// turn-end stop hook (backend-stop.ts) can find it directly instead of depending on
|
|
225
|
-
// Sandbox.list's tag index for a sandbox THIS turn just created — a hardening against the
|
|
226
|
-
// same-turn creation race OPE-722 reported, regardless of whether the vendor's tag index is
|
|
227
|
-
// ever actually stale (see backend-stop.ts's createVercelList doc comment for the full
|
|
228
|
-
// account). `recordCreatedSandbox` is documented to never throw, so no extra try/catch layer
|
|
229
|
-
// is needed here — it fails as loud as `recordCreatedSandbox`'s own contract allows.
|
|
230
|
-
const record = (session: { id: string }): void => recordCreatedSandbox(ctx.session.id, session.id);
|
|
231
|
-
try {
|
|
232
|
-
record(await useSized(options));
|
|
233
|
-
} catch (err) {
|
|
234
|
-
// The sized open failed (e.g. the update was rejected for this plan/region):
|
|
235
|
-
// fall back to the default size rather than blocking the turn. A bare use()
|
|
236
|
-
// failure is a real sandbox outage — let that one propagate.
|
|
237
|
-
if (options === undefined) throw err;
|
|
238
|
-
console.warn(`[open-everywhere:sandbox] applying vcpus=${options.resources.vcpus} failed — retrying default size (${String(err)})`);
|
|
239
|
-
record(await useSized());
|
|
256
|
+
// (core memory/domain.ts SandboxPreset); this selector applies it when it opens the session —
|
|
257
|
+
// eve forwards `resources` to the Vercel SDK's create/update, so the size change lands on the
|
|
258
|
+
// session VM. FAIL-OPEN end to end (sizing is a tuning knob, the turn must never die for it):
|
|
259
|
+
// identity/config resolution errors → default size; a rejected sized open retries unsized.
|
|
260
|
+
// Per-session vcpu sizing is a Vercel-only knob, so every other backend opens straight through.
|
|
261
|
+
if (SELF_HOST !== undefined) return await SELF_HOST.open();
|
|
262
|
+
let options: { resources: { vcpus: number } } | undefined;
|
|
263
|
+
try {
|
|
264
|
+
// SandboxSelectorContext structurally satisfies BindableContext (session.auth carries the
|
|
265
|
+
// platform attributes); a context with no resolvable identity falls through the binding bridge
|
|
266
|
+
// or resolves no domain — either way: no preset, default size.
|
|
267
|
+
const bindable = context as unknown as BindableContext;
|
|
268
|
+
const preset = sandboxPresetFor(bindFrom(bindable), resolvePartition(bindable), getConfig().loadDomains());
|
|
269
|
+
const vcpus = preset?.vcpus;
|
|
270
|
+
if (typeof vcpus === "number" && Number.isFinite(vcpus) && vcpus > 0) {
|
|
271
|
+
options = { resources: { vcpus: Math.floor(vcpus) } };
|
|
240
272
|
}
|
|
241
|
-
}
|
|
273
|
+
} catch (err) {
|
|
274
|
+
console.warn(`[open-everywhere:sandbox] vcpus preset resolution failed — using default size (${String(err)})`);
|
|
275
|
+
}
|
|
276
|
+
// 🔴 OPE-722's CREATED-SANDBOX RECORD IS GONE, BECAUSE eve 0.69.0 NO LONGER EXPOSES THE NAME
|
|
277
|
+
// (OPE-2247). The old `onSession` hook's `use()` resolved to an object carrying `.id` — the vendor
|
|
278
|
+
// sandbox name — and this file recorded it so the turn-end stop hook could reach the sandbox with a
|
|
279
|
+
// primary-key `Sandbox.get` rather than `Sandbox.list`'s tag index. On 0.69.0 neither
|
|
280
|
+
// `SandboxSession` nor `RuntimeSandboxSession` carries an `id`, so there is nothing to record and no
|
|
281
|
+
// authored surface that can learn it. The stop hook degrades to exactly the pre-OPE-722 path:
|
|
282
|
+
// `created-sandbox-registry.ts` documents an absent record as "falls back to the vendor's tag
|
|
283
|
+
// index, exactly as before this fix", so stop-after-turn still works and the narrow create-then-stop
|
|
284
|
+
// race OPE-722 hardened against is open again. Re-deriving a name the hook can use is handed to the
|
|
285
|
+
// orchestrator as a follow-up; it is not guessable from these bytes.
|
|
286
|
+
if (options === undefined) return await VERCEL_ENVIRONMENT.open();
|
|
287
|
+
try {
|
|
288
|
+
return await VERCEL_ENVIRONMENT.open(options);
|
|
289
|
+
} catch (err) {
|
|
290
|
+
// The sized open failed (e.g. the size was rejected for this plan/region): fall back to the
|
|
291
|
+
// default size rather than blocking the turn. A bare open() failure is a real sandbox outage —
|
|
292
|
+
// that one propagates, from the call below.
|
|
293
|
+
console.warn(`[open-everywhere:sandbox] applying vcpus=${options.resources.vcpus} failed — retrying default size (${String(err)})`);
|
|
294
|
+
return await VERCEL_ENVIRONMENT.open();
|
|
295
|
+
}
|
|
242
296
|
});
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
// scope is over its monthly total. A checkpoint is softer and per-session — when a single
|
|
9
9
|
// conversation gets expensive it PAUSES only CONSEQUENTIAL actions (PRs, memory writes, skill/
|
|
10
10
|
// routine creation, …) and posts a "keep going?" card. Reads and replies are never blocked, and a
|
|
11
|
-
// member's approval lets the agent resume
|
|
12
|
-
//
|
|
11
|
+
// member's approval lets the agent resume. On a live session the approval lasts until the window
|
|
12
|
+
// rotates or `approvals.checkpointTtlHours` (default 24 h) lapses, whichever comes first. On a
|
|
13
|
+
// durable job's session it does not lapse: the agent asks again once it has spent that much again.
|
|
14
|
+
// It's a runaway-session circuit-breaker, not a spend ceiling.
|
|
13
15
|
//
|
|
14
16
|
// EMPTY BY DEFAULT — with nothing declared here, nothing is checkpointed (Open Everywhere never
|
|
15
17
|
// invents a threshold). This file is the seam a DB/dashboard would swap behind, same as
|
|
@@ -22,9 +22,13 @@ that repository ships no `env:pipe` script. Everything else is the same.
|
|
|
22
22
|
- The ops MCP tools check and scaffold. **You** run install, build and deploy commands in the
|
|
23
23
|
user's shell: show the command, get a yes, then run it. Never run a deploy, create a cloud
|
|
24
24
|
resource, or write a secret without explicit confirmation.
|
|
25
|
-
- **Never ask the user to paste a secret into the chat, and never echo or
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
- **Never ask the user to paste a secret into the chat, never pass one to a tool, and never echo or
|
|
26
|
+
commit one.** A tool call is part of your transcript. The user types each secret into their OWN
|
|
27
|
+
terminal with `npx oe env set <NAME>`: it reads the value from a hidden prompt and writes it to the
|
|
28
|
+
gitignored `.env.local`. You only ever handle the NAME: `scaffold_env { secretNames: [...] }`
|
|
29
|
+
returns the command per name and whether the file holds a value yet. `scaffold_env { values }`
|
|
30
|
+
takes non-secret config only, and its schema refuses a secret name. Mint the credential-store key
|
|
31
|
+
with `scaffold_credential_key`, which never prints it.
|
|
28
32
|
- **Never propose a local run as a setup step.** `npm run dev` is the development loop AFTER the
|
|
29
33
|
deploy. A local run has no public HTTPS URL for Slack to reach and keeps its state in a different
|
|
30
34
|
store, so it proves little about the deployment. See "Develop locally" below.
|
|
@@ -32,9 +36,17 @@ that repository ships no `env:pipe` script. Everything else is the same.
|
|
|
32
36
|
`OE_CREDENTIAL_ENCRYPTION_KEY`, `SLACK_BOT_TOKEN`, `SLACK_SIGNING_SECRET` and
|
|
33
37
|
`GITHUB_WEBHOOK_SECRET` from its **environment**: the Slack bundle and the GitHub webhook check
|
|
34
38
|
never read the credential store. So these go in `.env.local`, and then into the deployment's own
|
|
35
|
-
environment. `secret_set` is for a credential whose `broker` is `"db"` (and
|
|
36
|
-
`NOTION_WEBHOOK_SECRET`)
|
|
37
|
-
|
|
39
|
+
environment. `secret_set { names: [...] }` is for a credential whose `broker` is `"db"` (and
|
|
40
|
+
`NOTION_WEBHOOK_SECRET`): it moves each named value from `.env.local` into the store, so the user
|
|
41
|
+
runs `npx oe env set <NAME>` first. It needs `DATABASE_URL` and the key in `.env.local` too.
|
|
42
|
+
Nothing reads a stored secret back out (`secret_list` returns names only).
|
|
43
|
+
- **If an ops tool answers "the app now runs @open-everywhere/core X; this ops server runs Y"**, the
|
|
44
|
+
server predates an upgrade. Ask the user to restart it (`/mcp`, then reconnect
|
|
45
|
+
`open-everywhere-ops`) and call again. Never work around it: a stale server serves old code. A
|
|
46
|
+
server whose core predates this check cannot refuse: after an upgrade, an input-validation error
|
|
47
|
+
that names `values` or `secrets`, a `scaffold_env` reply with no `secrets` list for the
|
|
48
|
+
`secretNames` you sent, or "Tool … not found", means the same thing. Restart it. Never fall back
|
|
49
|
+
to passing a value.
|
|
38
50
|
- Every command runs from the app root.
|
|
39
51
|
|
|
40
52
|
## Step 1 — ask which route
|
|
@@ -53,11 +65,26 @@ that repository ships no `env:pipe` script. Everything else is the same.
|
|
|
53
65
|
Nothing beyond these two is supported. For any other host, read the contract and follow the Docker
|
|
54
66
|
route; do not invent a recipe.
|
|
55
67
|
|
|
68
|
+
## Tell the user about telemetry, before the first deploy
|
|
69
|
+
|
|
70
|
+
**The deployed app sends anonymous usage telemetry, and it is ON by default.** Tell the user that
|
|
71
|
+
before the first deploy, and ask whether they want it. Do not decide for them. It sends event names,
|
|
72
|
+
enums, coarse buckets, public model ids and the app version, with a random per-install id
|
|
73
|
+
(`service.instance.id`). It never sends message or memory content, org names, workspace, channel,
|
|
74
|
+
user or session ids, or env values. The full list
|
|
75
|
+
of events is `https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/telemetry.md`.
|
|
76
|
+
|
|
77
|
+
To turn it off, `OE_TELEMETRY=off` (or `DO_NOT_TRACK=1`) must be set on the DEPLOYMENT: a value in
|
|
78
|
+
`.env.local` reaches only a local run. On the Vercel route, at step 9:
|
|
79
|
+
`printf off | npx vercel env add OE_TELEMETRY production`. On the Docker route, put it in the
|
|
80
|
+
container's environment. Every boot prints a line that says whether telemetry is on.
|
|
81
|
+
|
|
56
82
|
## Status, at every step
|
|
57
83
|
|
|
58
84
|
- `npx @open-everywhere/create --check --json` — the step list as data: `satisfied`, `detail`, and a
|
|
59
85
|
`next` command per unsatisfied step. It changes nothing. Pass `--route docker` on the Docker
|
|
60
|
-
route, and `--host https://<host>` once a deployment exists
|
|
86
|
+
route, and `--host https://<host>` once a deployment exists: the env step then also reads the
|
|
87
|
+
deployment's own verdicts from its public status route. In a clone of the repository, run
|
|
61
88
|
`node packages/create/bin/create-open-everywhere.js --check --json` instead.
|
|
62
89
|
- The `preflight` tool — the env verdict per name. Pass `target: "vercel"` or `target: "docker"`.
|
|
63
90
|
|
|
@@ -74,18 +101,21 @@ tool. The same step list, written for a human, is
|
|
|
74
101
|
3. **Database.** `npx vercel install neon` provisions a Neon database and connects it to the linked
|
|
75
102
|
project, which sets `DATABASE_URL` there. (`vercel install` is an alias of
|
|
76
103
|
`vercel integration add`; the Marketplace slug is `neon`.) The dashboard path — project →
|
|
77
|
-
**Storage → Neon → Connect** — does the same thing.
|
|
104
|
+
**Storage → Neon → Connect** — does the same thing. The integration can also enable **Neon
|
|
105
|
+
Auth** (`NEON_AUTH_BASE_URL`, `VITE_NEON_AUTH_URL`). Open Everywhere does not use it: leave it
|
|
106
|
+
off, or ignore those two variables. Then get the same value into `.env.local`
|
|
78
107
|
for the operator tools, either with `npx vercel env pull` (it writes the project's environment
|
|
79
108
|
into `.env.local`, overwriting it, so pull before you add anything local) or by asking the user
|
|
80
|
-
|
|
109
|
+
to run `npx oe env set DATABASE_URL` and paste the connection string at its prompt. **That one value is the whole
|
|
81
110
|
step:** it resolves both the operational store and the memory backing to postgres on its own, so
|
|
82
111
|
set no provider selector. A selector pointing AWAY from postgres is the only thing that breaks
|
|
83
112
|
it.
|
|
84
113
|
4. **Key.** `scaffold_credential_key`.
|
|
85
114
|
5. **Slack app.** Call `surface_manifest { surface: "slack" }` and hand the user the manifest and
|
|
86
115
|
the steps it returns. Before the first deploy the manifest keeps `YOUR-DEPLOY-URL`. The user
|
|
87
|
-
creates and installs the app, then
|
|
88
|
-
|
|
116
|
+
creates and installs the app, then runs `npx oe env set SLACK_BOT_TOKEN` and
|
|
117
|
+
`npx oe env set SLACK_SIGNING_SECRET` in their own terminal. Confirm with
|
|
118
|
+
`scaffold_env { secretNames: ["SLACK_BOT_TOKEN", "SLACK_SIGNING_SECRET"] }`: both read `present: true`.
|
|
89
119
|
6. **Vouch the workspace.** Ask for the team id (`T…`: the segment after `/client/` in the
|
|
90
120
|
workspace URL at app.slack.com). In `agent/identity/surfaces.ts` add
|
|
91
121
|
`defineSurface({ key: "slack/<team-id>", trust: "internal" })`.
|
|
@@ -96,7 +126,8 @@ tool. The same step list, written for a human, is
|
|
|
96
126
|
9. **Deployment env.** `.env.local` never ships, so the deployment reads only what you set ON it.
|
|
97
127
|
The names are `OE_CREDENTIAL_ENCRYPTION_KEY`, `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET`. The
|
|
98
128
|
Neon integration already set `DATABASE_URL` there, and that one value resolves both stores — so
|
|
99
|
-
there is no `OE_OPERATIONAL_PROVIDER` or `OE_MEMORY_PROVIDER` to push.
|
|
129
|
+
there is no `OE_OPERATIONAL_PROVIDER` or `OE_MEMORY_PROVIDER` to push. Add `OE_TELEMETRY` (value
|
|
130
|
+
`off`) only when the user chose to turn telemetry off (see the telemetry section above).
|
|
100
131
|
Confirm each command first, and **never let a value reach your own output.**
|
|
101
132
|
- **In a scaffolded app:** `npm run -s env:pipe -- <NAME> | npx vercel env add <NAME> production`.
|
|
102
133
|
Always pipe `env:pipe` straight into `vercel env add`, as written. Never run it bare: your
|
|
@@ -127,11 +158,12 @@ public HTTPS URL, Postgres, a sandbox backend, egress control, a model route, an
|
|
|
127
158
|
Then:
|
|
128
159
|
|
|
129
160
|
1. **Install and build.** `npm install`, then `npm run build`.
|
|
130
|
-
2. **Postgres.** Provision one the container can reach
|
|
131
|
-
one value resolves both stores to postgres;
|
|
161
|
+
2. **Postgres.** Provision one the container can reach. The user runs `npx oe env set DATABASE_URL`
|
|
162
|
+
and pastes its connection string at the prompt. That one value resolves both stores to postgres;
|
|
163
|
+
set no provider selector.
|
|
132
164
|
3. **Key.** `scaffold_credential_key`.
|
|
133
165
|
4. **Model route.** There is no OIDC off Vercel, so the user supplies `AI_GATEWAY_API_KEY` (Vercel
|
|
134
|
-
→ AI Gateway → API Keys issues one for any host)
|
|
166
|
+
→ 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
|
|
135
167
|
an **adapter swap in app code**
|
|
136
168
|
(`https://github.com/open-everywhere/open-everywhere/blob/main/docs/extending/adapter-model.md`),
|
|
137
169
|
which `--check` cannot see: that step stays `missing` on the Docker route either way, so read it
|
|
@@ -147,9 +179,9 @@ Then:
|
|
|
147
179
|
|
|
148
180
|
## Develop locally
|
|
149
181
|
|
|
150
|
-
Only when the user asks to change or debug the agent — never as a setup step.
|
|
151
|
-
`AI_GATEWAY_API_KEY`
|
|
152
|
-
then `npm run dev`. A local run keeps its state in an embedded database under
|
|
182
|
+
Only when the user asks to change or debug the agent — never as a setup step. The user creates
|
|
183
|
+
`AI_GATEWAY_API_KEY` (Vercel → AI Gateway → API Keys) and writes it with
|
|
184
|
+
`npx oe env set AI_GATEWAY_API_KEY`, then `npm run dev`. A local run keeps its state in an embedded database under
|
|
153
185
|
`.open-everywhere-data/`, and Slack cannot reach it without a public tunnel.
|
|
154
186
|
|
|
155
187
|
## Notes
|
|
@@ -161,7 +193,12 @@ then `npm run dev`. A local run keeps its state in an embedded database under
|
|
|
161
193
|
- The deployment's own identity (`deploymentIdentity`) is code in `agent/identity/surfaces.ts`, not
|
|
162
194
|
env.
|
|
163
195
|
- `@open-everywhere/create` has no interactive wizard and sends no telemetry. It scaffolds
|
|
164
|
-
(`npx @open-everywhere/create <dir>`) and reports status (`--check`). Nothing else.
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
196
|
+
(`npx @open-everywhere/create <dir>`) and reports status (`--check`). Nothing else. The deployed
|
|
197
|
+
app is a different matter: its telemetry is on by default (see the telemetry section above).
|
|
198
|
+
- 🔴 **Without `--host`, `--check` reads `.env.local` and your shell, never the deployment's own
|
|
199
|
+
environment.** With `--host`, the env step also reads the deployment's status route, which reports
|
|
200
|
+
`DATABASE_URL` and the credential key (its `secrets` row), the store selection and the model route.
|
|
201
|
+
The env step does not judge the Slack secrets, so even a "9/9 satisfied" is not proof that the
|
|
202
|
+
deployment holds `SLACK_BOT_TOKEN` and `SLACK_SIGNING_SECRET`. The same status document's
|
|
203
|
+
`surfaces.slack.credentials` reports whether both are present: read it with
|
|
204
|
+
`curl -s https://<host>/eve/v1/oe/status`. Slack's **Verified** is what proves the signing secret.
|
|
@@ -66,15 +66,31 @@ Those are decisions, not steps.
|
|
|
66
66
|
|
|
67
67
|
`--plan` also prints a **template drift** report. It compares every file the scaffold copied into
|
|
68
68
|
this app with the TARGET release's `@open-everywhere/create` template, and lists each one that
|
|
69
|
-
differs, with a one-line size of its diff. An upgrade changes only `package.json
|
|
69
|
+
differs, with a one-line size of its diff. An upgrade changes only `package.json` and
|
|
70
|
+
`package-lock.json`, so a scaffolded
|
|
70
71
|
file stays as the old template wrote it. A stale copy can typecheck and build and still break the
|
|
71
72
|
deployment: the 0.1.2 `agent/lib/register-adapters.ts` registers no Postgres client on a bare
|
|
72
73
|
`DATABASE_URL`, so a Vercel+Neon deployment of 0.1.4 fails at its first write.
|
|
73
74
|
|
|
75
|
+
`agent/lib/register-adapters.ts` is the exception once it is current. The current template's copy
|
|
76
|
+
is ONE call, `registerDefaultAdapters()` from `@open-everywhere/adapters`, plus a `customAdapters`
|
|
77
|
+
hook. The adapter wiring, the store selection rules and the floors live in that package, so the
|
|
78
|
+
package upgrade carries them, and the file needs no hand-merge. Sync it ONCE, from a copy that wires
|
|
79
|
+
the adapters by hand to the one-call copy: move any adapter the user registered by hand into the
|
|
80
|
+
`customAdapters` hook. After that, a SYNC line for this file is a comment or hook difference only.
|
|
81
|
+
|
|
74
82
|
| Line | What to do |
|
|
75
83
|
| --- | --- |
|
|
76
84
|
| SYNC | a template-owned file — wiring, a script, `AGENTS.md`, `README.md`, `.env.example`, a skill. Plan to copy the target's file over this one, after you read the diff. |
|
|
77
85
|
| config | the app's own config-as-code. A difference is normal once the app is configured. Merge the template's change by hand only if the user wants it. |
|
|
86
|
+
| DELETE | a release up to the target removed this file from the template, and the app still has it. Plan to delete it. eve 0.69 and later refuse `agent/instrumentation.ts` at build. |
|
|
87
|
+
| app-owned | `.oe-template-ignore` names the file, so the app owns it. Nothing to do. |
|
|
88
|
+
|
|
89
|
+
`.oe-template-ignore` at the app root lists the template files the app removed or owns on purpose:
|
|
90
|
+
one app-relative path per line, a trailing `/` for a directory, `#` for a comment. The report prints
|
|
91
|
+
nothing for an ignored file the app does not have, and one `app-owned` line for one it has. Add a
|
|
92
|
+
path only when the user says the app owns that file. Never add one to silence a SYNC line you have
|
|
93
|
+
not read.
|
|
78
94
|
|
|
79
95
|
The report never writes a file. If it says the drift check did not run, compare the files with the
|
|
80
96
|
target's template by hand. An installed core without the report prints none: `oe upgrade` runs the
|
|
@@ -92,7 +108,11 @@ In order, and it stops at the first step that fails:
|
|
|
92
108
|
1. bump every `@open-everywhere/*` package to the target, renaming a dependency on the retired
|
|
93
109
|
unscoped `create-open-everywhere` to `@open-everywhere/create` as it goes;
|
|
94
110
|
2. pin `eve` to the version the target core declares as its peer;
|
|
95
|
-
3. `npm install` — its `postinstall` runs `oe-eve-patch`, which the app refuses to boot without
|
|
111
|
+
3. `npm install` — its `postinstall` runs `oe-eve-patch`, which the app refuses to boot without.
|
|
112
|
+
Before it, the run drops the `package-lock.json` entries of `eve`, of every
|
|
113
|
+
`@open-everywhere/*` package and of every package that peers on one of them, so npm resolves
|
|
114
|
+
that set again. If the install still stops with ERESOLVE (a core at 0.2.1 or earlier does not
|
|
115
|
+
drop them), run `rm package-lock.json && npm install`, then run `oe upgrade` again;
|
|
96
116
|
4. run the config codemods the target's notes name, and print the migrations that are yours;
|
|
97
117
|
5. `oe build --accept` — this rewrites `roster.lock.json`;
|
|
98
118
|
6. `npm run typecheck`;
|
|
@@ -129,7 +149,7 @@ codemod. Do not delete the marker to get past this refusal.
|
|
|
129
149
|
|
|
130
150
|
```
|
|
131
151
|
git diff roster.lock.json
|
|
132
|
-
git diff package.json
|
|
152
|
+
git diff package.json package-lock.json
|
|
133
153
|
git status
|
|
134
154
|
```
|
|
135
155
|
|
|
@@ -142,9 +162,13 @@ Then run the app's own checks once more if anything above was fixed by hand:
|
|
|
142
162
|
|
|
143
163
|
Then act on the template drift report. A core that carries it prints it again after the install;
|
|
144
164
|
if this run printed no "Template drift" heading, run `npx oe upgrade --to <target> --plan` now to get it. For
|
|
145
|
-
each
|
|
165
|
+
each DELETE line, delete the file. If the user says the app owns it, keep it and name it in
|
|
166
|
+
`.oe-template-ignore`. For each SYNC line, read the diff against the installed template, for example
|
|
146
167
|
`diff node_modules/@open-everywhere/create/template/agent/lib/register-adapters.ts agent/lib/register-adapters.ts`.
|
|
147
|
-
Keep any edit the user made on purpose, and copy the template's file otherwise.
|
|
168
|
+
Keep any edit the user made on purpose, and copy the template's file otherwise. For
|
|
169
|
+
`agent/lib/register-adapters.ts`, the user's edits belong in the `customAdapters` hook of the
|
|
170
|
+
template's one-call copy, never in a hand-wired copy: a hand-wired copy does not get the product's
|
|
171
|
+
wiring changes on the next upgrade. A file under the
|
|
148
172
|
template's `claude/` directory lands in the app's `.claude/`, and the template's `gitignore` is the
|
|
149
173
|
app's `.gitignore`. Name every synced file in the pull request.
|
|
150
174
|
|
|
@@ -161,7 +185,7 @@ Commit on a branch, never on the default branch. The body carries:
|
|
|
161
185
|
- what changed in `roster.lock.json`, in your own words;
|
|
162
186
|
- **the env names the deployment needs before this merges**, names only;
|
|
163
187
|
- the manual steps, and who does each one.
|
|
164
|
-
- the template files you synced, and each SYNC line you left as it is, with the reason.
|
|
188
|
+
- the template files you synced or deleted, and each SYNC or DELETE line you left as it is, with the reason.
|
|
165
189
|
|
|
166
190
|
Then stop. The admin reviews the lock diff and merges. The merge deploys.
|
|
167
191
|
|
|
@@ -169,21 +193,23 @@ Then stop. The admin reviews the lock diff and merges. The merge deploys.
|
|
|
169
193
|
|
|
170
194
|
A green build is not a working deployment.
|
|
171
195
|
|
|
172
|
-
**
|
|
173
|
-
JSON route that reports the deployment's version, whether the
|
|
174
|
-
|
|
175
|
-
answers, and the Slack posture.
|
|
176
|
-
|
|
177
|
-
|
|
196
|
+
**Read the status route, `GET /eve/v1/oe/status`.** It ships from core 0.1.4. It is an
|
|
197
|
+
unauthenticated, non-sensitive JSON route that reports the deployment's version, whether the
|
|
198
|
+
database is reachable, the sandbox backend, whether egress is enforced, whether the model route
|
|
199
|
+
answers, and the Slack posture. A deployment can hide it on purpose, with a roster exclusion or an
|
|
200
|
+
edge rule (`docs/deploy/contract.md` § Hiding the status route). A 404 or a 403 there can be that
|
|
201
|
+
choice, not a fault: ask the operator before you call it broken. Never read some other route as if
|
|
202
|
+
it were this one. When the route answers, report each field, in this order:
|
|
178
203
|
|
|
179
204
|
1. **version** — it reads the target. If it still reads the old version, the deploy has not
|
|
180
205
|
finished, or it failed. Nothing below is meaningful until this matches.
|
|
181
|
-
2. **database reachable
|
|
206
|
+
2. **database reachable** (the `postgres` verdict).
|
|
182
207
|
3. **sandbox backend**, and **egress enforced**.
|
|
183
208
|
4. **model route answers.**
|
|
184
209
|
5. **Slack** — credentials present, workspace installed, events verified.
|
|
185
210
|
|
|
186
|
-
**
|
|
211
|
+
**When the route does not answer**, on a core older than 0.1.4 or on a deployment that hides it,
|
|
212
|
+
confirm what you can and say plainly what you could not:
|
|
187
213
|
|
|
188
214
|
- the deploy finished, from the host's own deployment log;
|
|
189
215
|
- `npx @open-everywhere/create --check --json --host https://<host>` — its `build-deploy` step
|
package/template/package.json
CHANGED
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"@open-everywhere/provider-notion": "{{OE_VERSION}}",
|
|
32
32
|
"@open-everywhere/provider-slack": "{{OE_VERSION}}",
|
|
33
33
|
"@open-everywhere/provider-teams": "{{OE_VERSION}}",
|
|
34
|
-
"eve": "0.
|
|
34
|
+
"eve": "0.71.0",
|
|
35
35
|
"just-bash": "^3.1.0"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"hook/eve-patch-gate": "mounted",
|
|
40
40
|
"hook/fire-carriage": "mounted",
|
|
41
41
|
"hook/fire-toolset": "mounted",
|
|
42
|
+
"hook/held-question": "mounted",
|
|
42
43
|
"hook/job-posture": "mounted",
|
|
43
44
|
"hook/lean-seat": "mounted",
|
|
44
45
|
"hook/learn": "mounted",
|
|
@@ -69,10 +70,13 @@
|
|
|
69
70
|
"tool/add_label": "mounted",
|
|
70
71
|
"tool/add_routine_rule": "mounted",
|
|
71
72
|
"tool/agent_version": "mounted",
|
|
73
|
+
"tool/ask_question": "mounted",
|
|
74
|
+
"tool/ask_room": "mounted",
|
|
72
75
|
"tool/assign": "mounted",
|
|
73
76
|
"tool/aws": "mounted",
|
|
74
77
|
"tool/browser_tools": "mounted",
|
|
75
78
|
"tool/cancel_routine": "mounted",
|
|
79
|
+
"tool/cancel_work": "mounted",
|
|
76
80
|
"tool/capture_events": "mounted",
|
|
77
81
|
"tool/check_pr_status": "mounted",
|
|
78
82
|
"tool/checkout_repo": "mounted",
|
|
@@ -118,6 +122,7 @@
|
|
|
118
122
|
"tool/read_channel": "mounted",
|
|
119
123
|
"tool/read_docs": "mounted",
|
|
120
124
|
"tool/read_file_upload": "mounted",
|
|
125
|
+
"tool/read_github_issue": "mounted",
|
|
121
126
|
"tool/read_routine_transcript": "mounted",
|
|
122
127
|
"tool/read_thread": "mounted",
|
|
123
128
|
"tool/read_thread_seat": "mounted",
|
|
@@ -151,7 +156,8 @@
|
|
|
151
156
|
"tool/unlink_identity": "mounted",
|
|
152
157
|
"tool/update_status": "mounted",
|
|
153
158
|
"tool/usage_report": "mounted",
|
|
154
|
-
"tool/walk_pr": "mounted"
|
|
159
|
+
"tool/walk_pr": "mounted",
|
|
160
|
+
"tool/work": "mounted"
|
|
155
161
|
}
|
|
156
162
|
},
|
|
157
163
|
"@open-everywhere/provider-github": {
|