@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.
Files changed (40) hide show
  1. package/README.md +6 -4
  2. package/package.json +3 -3
  3. package/src/scaffold.ts +2 -2
  4. package/src/steps/build-deploy.ts +2 -2
  5. package/template/.dockerignore +35 -0
  6. package/template/AGENTS.md +7 -0
  7. package/template/Dockerfile +60 -0
  8. package/template/README.md +13 -0
  9. package/template/agent/access/a2a-inbound.ts +69 -0
  10. package/template/agent/agent.ts +25 -8
  11. package/template/agent/google-chat/policy.ts +34 -0
  12. package/template/agent/instructions.md +35 -20
  13. package/template/agent/lib/register-config.ts +20 -1
  14. package/template/agent/meetings/policy.ts +2 -4
  15. package/template/agent/memory/config/domains/org.ts +2 -2
  16. package/template/agent/model/models.ts +17 -0
  17. package/template/agent/model/stacks.ts +23 -6
  18. package/template/agent/routines/collectors.ts +28 -0
  19. package/template/agent/settings/settings.ts +21 -2
  20. package/template/agent/spend/model-pricing.ts +3 -1
  21. package/template/claude/skills/quickstart-open-everywhere/SKILL.md +96 -9
  22. package/template/claude/skills/upgrade-open-everywhere/SKILL.md +17 -4
  23. package/template/deploy/egress-proxy/Dockerfile +30 -0
  24. package/template/deploy/egress-proxy/control/acl.ts +228 -0
  25. package/template/deploy/egress-proxy/control/handlers.ts +174 -0
  26. package/template/deploy/egress-proxy/control/helper.cjs +108 -0
  27. package/template/deploy/egress-proxy/control/server.ts +122 -0
  28. package/template/deploy/egress-proxy/entrypoint.sh +62 -0
  29. package/template/deploy/egress-proxy/gen-ca.sh +42 -0
  30. package/template/deploy/egress-proxy/install-ca.sh +39 -0
  31. package/template/deploy/egress-proxy/squid.conf +93 -0
  32. package/template/deploy/egress-proxy/tier-b.conf +42 -0
  33. package/template/docker-compose.yml +135 -0
  34. package/template/gitignore +4 -0
  35. package/template/package.json +5 -3
  36. package/template/roster.config.ts +2 -2
  37. package/template/roster.lock.json +14 -1
  38. package/template/scripts/goldens/roster-plan.txt +13 -0
  39. package/template/scripts/model-config-check.ts +81 -0
  40. package/template/scripts/ops-mcp.ts +1 -1
@@ -0,0 +1,62 @@
1
+ #!/bin/sh
2
+ # Entrypoint for the oe-egress-proxy container: start the policy control service, wire the
3
+ # tier (A default / B if OE_EGRESS_MITM=1), then exec Squid in the foreground.
4
+ set -eu
5
+
6
+ CONTROL_PORT="${OE_EGRESS_CONTROL_PORT:-8080}"
7
+ MITM="${OE_EGRESS_MITM:-0}"
8
+ SQUID_CONF="/etc/squid/squid.conf"
9
+
10
+ echo "[oe-egress] starting (MITM=$MITM) ..."
11
+
12
+ # ── Control service (loopback + app-net): holds per-source policy, answers ACL callouts ──
13
+ # Node 24 runs the TypeScript directly (native type stripping) — no build step, no tsx.
14
+ node --experimental-strip-types /opt/oe-egress/control/server.ts &
15
+ CONTROL_PID=$!
16
+
17
+ # Wait for the control service to accept connections before Squid starts firing callouts.
18
+ i=0
19
+ while [ $i -lt 50 ]; do
20
+ if wget -q -O /dev/null "http://127.0.0.1:${CONTROL_PORT}/healthz" 2>/dev/null; then break; fi
21
+ i=$((i + 1)); sleep 0.2
22
+ done
23
+
24
+ # ── Fresh, writable copy of the config (the image ships it read-only) ──
25
+ cp /opt/oe-egress/squid.conf "$SQUID_CONF"
26
+
27
+ if [ "$MITM" = "1" ]; then
28
+ echo "[oe-egress] Tier B (MITM) enabled — generating CA + initialising cert DB."
29
+ sh /opt/oe-egress/gen-ca.sh
30
+ # Augment the forward-proxy listener with ssl-bump so CONNECTs can be decrypted.
31
+ sed -i \
32
+ "s#^http_port 3128\$#http_port 3128 ssl-bump generate-host-certificates=on dynamic_cert_mem_cache_size=4MB tls-cert=${OE_EGRESS_CA_DIR:-/opt/oe-egress/ca}/oe-egress-ca.crt tls-key=${OE_EGRESS_CA_DIR:-/opt/oe-egress/ca}/oe-egress-ca.key#" \
33
+ "$SQUID_CONF"
34
+ # Append the Tier B overlay directly (no conf.d include — see squid.conf).
35
+ cat /opt/oe-egress/tier-b.conf >> "$SQUID_CONF"
36
+ # Initialise the dynamic-cert DB used by security_file_certgen.
37
+ rm -rf /var/lib/squid/ssl_db
38
+ /usr/lib/squid/security_file_certgen -c -s /var/lib/squid/ssl_db -M 4MB || true
39
+ chown -R squid:squid /var/lib/squid 2>/dev/null || true
40
+ else
41
+ echo "[oe-egress] Tier A (SNI/CONNECT host allow-deny, no decryption)."
42
+ fi
43
+
44
+ # Squid drops to the `squid` user. Give it ownership of its state dir, and chown the
45
+ # container's stdout/stderr pipes so `access_log stdio:/dev/stdout` works as non-root
46
+ # (opening /dev/stdout by path FATALs otherwise — see squid.conf).
47
+ chown -R squid:squid /var/lib/squid 2>/dev/null || true
48
+ chown squid:squid /dev/stdout /dev/stderr 2>/dev/null || true
49
+
50
+ # The denial log (OPE-314). Created unconditionally so `access_log stdio:/var/log/oe-egress/...`
51
+ # can always be opened — Squid FATALs on an access_log it cannot open, and the volume that shares
52
+ # this file with the app is optional. Unmounted, the file lives inside the container and nobody
53
+ # reads it; mounted, the app tails it (read-only) and turns denials into audit rows.
54
+ mkdir -p /var/log/oe-egress
55
+ chown -R squid:squid /var/log/oe-egress 2>/dev/null || true
56
+
57
+ # Squid needs its swap/cache dirs initialised once.
58
+ squid -N -f "$SQUID_CONF" -z 2>/dev/null || true
59
+
60
+ echo "[oe-egress] launching squid."
61
+ # -N = no daemon (stay in foreground so the container's lifecycle tracks squid).
62
+ exec squid -N -f "$SQUID_CONF" -d 1
@@ -0,0 +1,42 @@
1
+ #!/bin/sh
2
+ # Generate the local MITM CA for Tier B (opt-in) of the Open Everywhere egress proxy.
3
+ #
4
+ # Produces, in the shared CA dir (default /opt/oe-egress/ca, a mounted volume in
5
+ # docker-compose.yml so the app container can read the cert too):
6
+ # oe-egress-ca.key — the CA private key (proxy-only; NEVER leaves the proxy container)
7
+ # oe-egress-ca.crt — the CA certificate (installed into the app/sandbox trust store by
8
+ # install-ca.sh so git/npm/curl trust the bumped TLS)
9
+ #
10
+ # Idempotent: if a CA already exists it is left untouched (re-running won't churn the cert
11
+ # and invalidate an already-installed trust anchor). Delete the files to rotate.
12
+ #
13
+ # SECURITY: the CA key can mint a cert for ANY host. It is a real secret — keep it inside
14
+ # the proxy container's volume, never commit it (deploy/egress-proxy/.gitignore blocks it),
15
+ # and treat a leak as "rotate immediately". Tier B is opt-in precisely because introducing a
16
+ # trusted CA into the guest is a real trust decision (see README client-compat caveats).
17
+ set -eu
18
+
19
+ CA_DIR="${OE_EGRESS_CA_DIR:-/opt/oe-egress/ca}"
20
+ CA_KEY="$CA_DIR/oe-egress-ca.key"
21
+ CA_CRT="$CA_DIR/oe-egress-ca.crt"
22
+ CA_CN="${OE_EGRESS_CA_CN:-Open Everywhere Egress Proxy CA}"
23
+ DAYS="${OE_EGRESS_CA_DAYS:-3650}"
24
+
25
+ mkdir -p "$CA_DIR"
26
+
27
+ if [ -f "$CA_KEY" ] && [ -f "$CA_CRT" ]; then
28
+ echo "[gen-ca] CA already present at $CA_DIR — leaving it untouched (delete to rotate)."
29
+ exit 0
30
+ fi
31
+
32
+ echo "[gen-ca] generating a new local CA in $CA_DIR ..."
33
+ openssl genrsa -out "$CA_KEY" 4096
34
+ openssl req -x509 -new -nodes -key "$CA_KEY" -sha256 -days "$DAYS" \
35
+ -subj "/CN=$CA_CN" \
36
+ -addext "basicConstraints=critical,CA:TRUE" \
37
+ -addext "keyUsage=critical,keyCertSign,cRLSign" \
38
+ -out "$CA_CRT"
39
+ chmod 600 "$CA_KEY"
40
+ chmod 644 "$CA_CRT"
41
+ echo "[gen-ca] done. CA cert: $CA_CRT"
42
+ echo "[gen-ca] install it into the app/sandbox with install-ca.sh (see README)."
@@ -0,0 +1,39 @@
1
+ #!/bin/sh
2
+ # Install the Tier B MITM CA into THIS container's trust store + configure the common
3
+ # clients (git/npm/curl) to trust and use the proxy. Run inside the APP container (and,
4
+ # for the docker() sandbox backend, bake it into the sandbox image) — NOT the proxy.
5
+ #
6
+ # Requires the CA cert produced by gen-ca.sh, shared via the mounted /opt/oe-egress/ca
7
+ # volume. No-op-safe to run more than once.
8
+ #
9
+ # ⚠ CLIENT-COMPAT CAVEATS (design flagged these — do not paper over them):
10
+ # - Tools that PIN certificates (some language package managers, apps with bundled CA
11
+ # stores that ignore the OS store) will still reject the bumped cert. Those hosts must
12
+ # be Tier-A-spliced (host allow, no decryption), not bumped.
13
+ # - Tools that IGNORE the proxy env (HTTPS_PROXY) bypass the proxy entirely. On the
14
+ # internal-only Docker network they then simply fail to connect (no WAN route) — the
15
+ # structural backstop turns a proxy-bypass into a hard failure, not a leak.
16
+ # - Node/npm read NODE_EXTRA_CA_CERTS, not always the OS store — set below.
17
+ set -eu
18
+
19
+ CA_CRT="${OE_EGRESS_CA_CRT:-/opt/oe-egress/ca/oe-egress-ca.crt}"
20
+ PROXY_URL="${OE_EGRESS_PROXY_HTTP:-http://oe-egress-proxy:3128}"
21
+
22
+ if [ ! -f "$CA_CRT" ]; then
23
+ echo "[install-ca] CA cert not found at $CA_CRT — run gen-ca.sh in the proxy first (Tier B)." >&2
24
+ exit 1
25
+ fi
26
+
27
+ # 1. OS trust store (alpine: ca-certificates). curl + git (via libcurl) pick this up.
28
+ if command -v update-ca-certificates >/dev/null 2>&1; then
29
+ cp "$CA_CRT" /usr/local/share/ca-certificates/oe-egress-ca.crt 2>/dev/null \
30
+ || cp "$CA_CRT" /etc/ssl/certs/oe-egress-ca.pem
31
+ update-ca-certificates >/dev/null 2>&1 || true
32
+ fi
33
+
34
+ echo "[install-ca] CA installed. Export these in the app environment (compose sets them):"
35
+ echo " HTTP_PROXY=$PROXY_URL"
36
+ echo " HTTPS_PROXY=$PROXY_URL"
37
+ echo " NODE_EXTRA_CA_CERTS=$CA_CRT # Node/npm trust the MITM CA"
38
+ echo " GIT_SSL_CAINFO=$CA_CRT # git trusts the MITM CA"
39
+ echo "[install-ca] (git also honors http.proxy=$PROXY_URL; npm honors https-proxy=$PROXY_URL)"
@@ -0,0 +1,93 @@
1
+ # Open Everywhere egress proxy — Squid base config.
2
+ #
3
+ # TIER A (default, NO MITM): a forward proxy that allows/denies each CONNECT (and plain
4
+ # HTTP) request by destination host, decided live by the control service via an
5
+ # external_acl callout. TLS is never decrypted — the raw bytes are tunnelled once the host
6
+ # passes (peek-and-splice semantics fall out of a plain CONNECT tunnel: Squid sees the
7
+ # CONNECT authority in cleartext and never terminates TLS). This restores the default-deny
8
+ # exfiltration backstop on justbash/docker without KVM.
9
+ #
10
+ # TIER B (opt-in, MITM) lives in conf.d/tier-b.conf, dropped in by entrypoint.sh only when
11
+ # OE_EGRESS_MITM=1. It ssl_bumps with a local CA so the decrypted request's method/path can
12
+ # be narrowed and a credential header injected.
13
+ #
14
+ # NB the exact external_acl format codes below (%SRC %DST %METHOD %PATH) are the widely
15
+ # supported named tokens; if your Squid build differs, adjust in one place here. See
16
+ # deploy/egress-proxy/README.md.
17
+
18
+ # Forward-proxy listener the app points HTTPS_PROXY/HTTP_PROXY at.
19
+ http_port 3128
20
+
21
+ # ── The live policy ACL: one callout per request to the control service (helper.cjs) ──
22
+ # ttl=0 → never cache a decision (the policy changes mid-turn via the git broker).
23
+ # %>{Proxy-Authorization} names the PRINCIPAL (OPE-2406). The app's own model call carries
24
+ # `Bearer <OE_EGRESS_PROXY_TOKEN>` and is decided against the app's host-plane allow list. A
25
+ # request without it is a session request, decided against the session policy for its source
26
+ # IP. Squid consumes the header here and does not forward it to the origin. No auth_param is
27
+ # configured, so Squid never answers 407 and an uncredentialed request is not challenged.
28
+ external_acl_type oe_egress ttl=0 negative_ttl=0 grace=0 concurrency=64 children-max=8 children-startup=1 \
29
+ %SRC %DST %METHOD %PATH %>{Proxy-Authorization} \
30
+ /usr/bin/node /opt/oe-egress/control/helper.cjs
31
+ acl oe_allowed external oe_egress
32
+
33
+ # ── Standard safety ACLs ──
34
+ acl CONNECT method CONNECT
35
+ acl SSL_ports port 443
36
+ acl Safe_ports port 80
37
+ acl Safe_ports port 443
38
+
39
+ http_access deny !Safe_ports
40
+ http_access deny CONNECT !SSL_ports
41
+
42
+ # ── The one allow rule: the control-service policy. Everything else is denied. ──
43
+ http_access allow oe_allowed
44
+ http_access deny all
45
+
46
+ # Policy gateway, not a cache.
47
+ cache deny all
48
+ via off
49
+ forwarded_for delete
50
+
51
+ # Squid drops privileges to the `squid` user. It logs to the container's stdout/stderr
52
+ # (below); the entrypoint chowns those pipes to `squid` so the non-root worker can open them
53
+ # (Squid 7 FATALs on `cache_effective_user root`, so dropping privileges is mandatory).
54
+
55
+ # Logs to the container's stdout/stderr for `docker logs`.
56
+ logfile_rotate 0
57
+ access_log stdio:/dev/stdout squid
58
+
59
+ # ── THE DENIAL LOG (OPE-314) — the ONE file the app is allowed to read ────────────────────────
60
+ # The app tails this to turn policy denials into `egress.denied` audit rows, which is what lets
61
+ # the permission-homeostasis widening loop see this plane at all (before OPE-314 an individual
62
+ # blocked request left no record anywhere — see packages/core/agent/access/egress-denial-log.ts).
63
+ #
64
+ # 🔴 THE MINIMISATION HAPPENS HERE, AT THE SOURCE, not only in the reader. The `squid` format
65
+ # above carries the full request URL, and under Tier B (MITM) Squid has DECRYPTED the request, so
66
+ # that URL includes the path AND the query string — the single most likely place for a token, an
67
+ # object id or a person's email to be sitting. This format carries three fields and no more:
68
+ # %ts.%03tu when (unix seconds + ms)
69
+ # %Ss/%03>Hs what Squid decided (e.g. TCP_DENIED/403 — the reader keys on the `_DENIED` tag)
70
+ # %>rd the DOMAIN (request URL domain — no scheme, no port, no path, no query)
71
+ # No client address, no method, no bytes, no user. The reader re-derives the host defensively
72
+ # anyway (it must also cope with an operator pointing it at a conventional access.log), but this
73
+ # line is why the bytes on the shared volume never contain a path in the first place.
74
+ #
75
+ # The `http_status 403` ACL is what narrows the file to refusals; a Squid ACL cannot match on %Ss.
76
+ # That is imprecise in one direction only — under Tier B an ORIGIN's own 403 also lands here as
77
+ # `TCP_MISS/403` — and the reader discards those, because only a `_DENIED` tag is OUR refusal.
78
+ #
79
+ # The directory is created by entrypoint.sh whether or not a volume is mounted over it, so Squid
80
+ # can never FATAL on an unopenable access_log; unmounted, the file simply stays inside the
81
+ # container and nothing reads it.
82
+ logformat oe_deny %ts.%03tu %Ss/%03>Hs %>rd
83
+ acl oe_deny_status http_status 403
84
+ access_log stdio:/var/log/oe-egress/denied.log oe_deny oe_deny_status
85
+ # cache_log (debug) also to stdout — the non-root `squid` user reliably reopens the chowned
86
+ # /dev/stdout pipe; /dev/stderr is flakier under Docker, and interleaving both on stdout is
87
+ # the conventional container pattern (`docker logs` captures it either way).
88
+ cache_log stdio:/dev/stdout
89
+ pid_filename /tmp/squid.pid
90
+
91
+ # Tier B (MITM) directives, if enabled, are APPENDED to this file by entrypoint.sh when
92
+ # OE_EGRESS_MITM=1 (not via an `include` glob — Squid 6.x treats a conf.d/*.conf glob that
93
+ # matches zero files as a FATAL error, which is exactly the Tier A case).
@@ -0,0 +1,42 @@
1
+ # Open Everywhere egress proxy — TIER B (opt-in MITM) overlay.
2
+ #
3
+ # Dropped into /etc/squid/conf.d/ by entrypoint.sh ONLY when OE_EGRESS_MITM=1. It makes
4
+ # Squid terminate TLS with the locally-generated CA (deploy/egress-proxy/ca, installed into
5
+ # the sandbox/guest trust store by gen-ca.sh) so that:
6
+ # 1. the decrypted request's method + path can be narrowed by the compiled policy, and
7
+ # 2. a Tier B credential header (the git read-broker Authorization) can be injected at the
8
+ # firewall — full parity with Vercel's transform.headers, secret never in-sandbox.
9
+ #
10
+ # ⚠ CLIENT COMPAT (see README): MITM only works for clients that (a) honor the proxy env
11
+ # (HTTPS_PROXY) and (b) trust the local CA. git/npm/curl do once the CA is installed and
12
+ # their proxy env is set; some tools pin certificates or bypass the proxy — those break
13
+ # under Tier B and must be allow-listed for Tier A splice instead.
14
+
15
+ # Bump listener — the CA cert+key generated by gen-ca.sh.
16
+ sslcrtd_program /usr/lib/squid/security_file_certgen -s /var/lib/squid/ssl_db -M 4MB
17
+ tls_outgoing_options options=NO_SSLv3
18
+
19
+ # Peek at the ClientHello (to read SNI for the ACL), then bump (decrypt) allowed hosts.
20
+ acl step1 at_step SslBump1
21
+ # THE HOST PLANE IS SPLICED AT STEP 1, never peeked or bumped (OPE-2406). The app's own model
22
+ # call carries Proxy-Authorization on the CONNECT only. After a peek, Squid re-checks a fake
23
+ # CONNECT built from the SNI, and a bumped inner request is a new request: neither carries the
24
+ # header, so the control service would decide it against the SESSION policy and deny the model
25
+ # call. `oe_proxy_cred` matches only a `Bearer <value>` header, and `oe_allowed` then passes only
26
+ # the valid token to a host-plane host. A missing header, or a literal `-` (which the control
27
+ # service reads as "no credential", the session shape), never matches, so a session request is
28
+ # peeked and bumped as before and cannot dodge Tier B's method/path narrowing by this rule.
29
+ acl oe_proxy_cred req_header Proxy-Authorization ^Bearer[[:space:]]+[^[:space:]]
30
+ ssl_bump splice step1 oe_proxy_cred oe_allowed
31
+ ssl_bump peek step1
32
+ ssl_bump bump oe_allowed
33
+ ssl_bump terminate all
34
+
35
+ # Inject the Tier B credential header the helper returned as the `authz` annotation.
36
+ # Only stamped when the ACL matched AND returned a header (the git read-broker path).
37
+ # The helper's `OK authz="…"` becomes a transaction note read here as %note{authz}
38
+ # (Squid 7 dropped %external_acl_message for request_header_add). The leading `'` is the
39
+ # LOG_QUOTE_NONE modifier — WITHOUT it Squid URL-encodes the note, turning the mandatory
40
+ # space in `Basic <base64>` / `Bearer <jwt>` into %20 and corrupting every real credential.
41
+ acl oe_has_authz note authz
42
+ request_header_add Authorization "%'note{authz}" oe_has_authz
@@ -0,0 +1,135 @@
1
+ # This app on the Docker route: the app, the egress proxy, Postgres and the Postgres WebSocket bridge.
2
+ #
3
+ # THE TOPOLOGY IS THE EGRESS BACKSTOP:
4
+ # - `oe-net-internal` is `internal: true`, so it has NO route to the internet. The app is ONLY on it.
5
+ # - `oe-egress-proxy` is on `oe-net-internal` AND `oe-net-wan`. It is the one container with a route
6
+ # out, and its default is deny. So the app reaches the internet only through the proxy, even
7
+ # when a policy push fails.
8
+ #
9
+ # Before the first start:
10
+ # 1. `npm install` (the image builds from package-lock.json).
11
+ # 2. Put the deployment's values in `.env` (AI_GATEWAY_API_KEY, SLACK_*, OE_CREDENTIAL_KEY, ...).
12
+ # It must hold OE_EGRESS_PROXY_TOKEN (`openssl rand -hex 32`): without it compose refuses to
13
+ # start the stack. Set POSTGRES_PASSWORD there too; the default below is for a local trial only.
14
+ # Start: docker compose up --build
15
+ #
16
+ # The requirements this stack satisfies, and the ones it does not, are in the deployment contract:
17
+ # https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/contract.md
18
+ # The egress proxy is documented in the self-host egress guide:
19
+ # https://github.com/open-everywhere/open-everywhere/blob/main/docs/deploy/platforms/self-host-egress.md
20
+
21
+ services:
22
+ oe-egress-proxy:
23
+ build:
24
+ context: ./deploy/egress-proxy
25
+ networks: [oe-net-internal, oe-net-wan]
26
+ environment:
27
+ OE_EGRESS_MITM: "${OE_EGRESS_MITM:-0}" # 1 = Tier B (MITM); the default is Tier A
28
+ # The shared secret for POST/DELETE /policy. `:?` makes compose refuse to start the stack when
29
+ # it is unset or empty. Without it, any peer on oe-net-internal could install an allow-all policy.
30
+ OE_EGRESS_PROXY_TOKEN: "${OE_EGRESS_PROXY_TOKEN:?set OE_EGRESS_PROXY_TOKEN in .env — the egress control plane refuses to start without it (generate: openssl rand -hex 32)}"
31
+ OE_EGRESS_CONTROL_PORT: "8080"
32
+ OE_EGRESS_CA_DIR: "/opt/oe-egress/ca"
33
+ volumes:
34
+ - oe-ca:/opt/oe-egress/ca # the CA, shared with the app (Tier B trust)
35
+ - oe-egress-log:/var/log/oe-egress # the denial log; the app reads it read-only
36
+ restart: unless-stopped
37
+ # No host port: the proxy is internal to the compose networks.
38
+
39
+ app:
40
+ build:
41
+ context: .
42
+ dockerfile: Dockerfile
43
+ depends_on:
44
+ oe-egress-proxy:
45
+ condition: service_started
46
+ postgres:
47
+ condition: service_healthy
48
+ neon-wsproxy:
49
+ condition: service_started
50
+ networks: [oe-net-internal] # internal ONLY: no direct route out
51
+ env_file:
52
+ - .env
53
+ environment:
54
+ # VERCEL stays UNSET: the self-host sandbox backend and the self-host egress push.
55
+ NODE_USE_ENV_PROXY: "1" # Node 24: built-in fetch uses *_PROXY
56
+ HTTP_PROXY: "http://oe-egress-proxy:3128"
57
+ HTTPS_PROXY: "http://oe-egress-proxy:3128"
58
+ # The policy push and the Postgres WebSocket go direct. `neon-wsproxy` MUST be here: Node 24
59
+ # also sends the driver's WebSocket through HTTP_PROXY, the proxy denies it, and the app exits
60
+ # at boot.
61
+ NO_PROXY: "oe-egress-proxy,neon-wsproxy,localhost,127.0.0.1"
62
+ # The app declares `sandbox.selfHostBackend: "justbash"` (agent/settings/settings.ts). Its
63
+ # session egress leaves through this app process, so the proxy holds every session request to
64
+ # the session's allow list. justbash shares this process's filesystem: development-grade
65
+ # isolation. The app refuses to boot here on `"docker"`: eve starts docker session containers
66
+ # on the daemon's default network, outside this proxy.
67
+ OE_EGRESS_PROXY_URL: "http://oe-egress-proxy:8080" # the control endpoint for the policy push
68
+ OE_EGRESS_PROXY_TOKEN: "${OE_EGRESS_PROXY_TOKEN:?set OE_EGRESS_PROXY_TOKEN in .env — the app's policy push is rejected without it (same value as the proxy)}"
69
+ # The proxy's denial log: a refused host becomes an `egress.denied` audit row. Unset it to turn
70
+ # the reader off.
71
+ OE_EGRESS_DENIAL_LOG: "${OE_EGRESS_DENIAL_LOG:-/var/log/oe-egress/denied.log}"
72
+ # Tier B: trust the MITM CA for git, npm and curl (no effect on Tier A).
73
+ NODE_EXTRA_CA_CERTS: "/opt/oe-egress/ca/oe-egress-ca.crt"
74
+ GIT_SSL_CAINFO: "/opt/oe-egress/ca/oe-egress-ca.crt"
75
+ # The operational store: the `postgres` sidecar below. This line wins over `.env`. For a
76
+ # managed Postgres, put its DATABASE_URL in .env, and delete this line, OE_NEON_WS_PROXY and
77
+ # the `postgres` and `neon-wsproxy` services.
78
+ DATABASE_URL: "postgres://openeverywhere:${POSTGRES_PASSWORD:-change-me-local-only}@postgres:5432/openeverywhere"
79
+ # The Postgres driver speaks WebSocket, not raw TCP, so it reaches the sidecar through
80
+ # `neon-wsproxy`. A hosted Neon database ignores this value.
81
+ OE_NEON_WS_PROXY: "${OE_NEON_WS_PROXY:-neon-wsproxy:80}"
82
+ PORT: "3000"
83
+ volumes:
84
+ - oe-memory:/app/.open-everywhere-memory # the git or sqlite memory backing
85
+ - oe-workflow:/app/.workflow-data # durable sessions
86
+ - oe-ca:/opt/oe-egress/ca:ro # the CA, for Tier B trust
87
+ - oe-egress-log:/var/log/oe-egress:ro # the proxy's denial log (READ-ONLY)
88
+ # Put a TLS-terminating reverse proxy in front of the app, and forward BOTH /eve/ and
89
+ # /.well-known/workflow/. To reach it directly for a local test, publish the port:
90
+ # ports: ["3000:3000"]
91
+ restart: unless-stopped
92
+
93
+ postgres:
94
+ # The same major version as hosted Neon.
95
+ image: postgres:17-alpine
96
+ networks: [oe-net-internal]
97
+ environment:
98
+ POSTGRES_USER: openeverywhere
99
+ POSTGRES_PASSWORD: "${POSTGRES_PASSWORD:-change-me-local-only}"
100
+ POSTGRES_DB: openeverywhere
101
+ volumes:
102
+ - oe-pg:/var/lib/postgresql/data
103
+ healthcheck:
104
+ test: ["CMD-SHELL", "pg_isready -U openeverywhere -d openeverywhere"]
105
+ interval: 5s
106
+ timeout: 3s
107
+ retries: 20
108
+ restart: unless-stopped
109
+
110
+ # Bridges the driver's WebSocket transport to the plain-TCP `postgres` sidecar.
111
+ neon-wsproxy:
112
+ image: ghcr.io/neondatabase/wsproxy:latest
113
+ networks: [oe-net-internal]
114
+ environment:
115
+ # EMPTY: the driver already sends `address=host:port`, and wsproxy appends this value to it.
116
+ APPEND_PORT: ""
117
+ ALLOW_ADDR_REGEX: "postgres:5432" # forward to the sidecar only
118
+ LOG_TRAFFIC: "false"
119
+ depends_on:
120
+ postgres:
121
+ condition: service_healthy
122
+ restart: unless-stopped
123
+
124
+ networks:
125
+ oe-net-internal:
126
+ internal: true # the backstop: NO route to the internet
127
+ oe-net-wan:
128
+ driver: bridge
129
+
130
+ volumes:
131
+ oe-memory:
132
+ oe-workflow:
133
+ oe-ca:
134
+ oe-egress-log:
135
+ oe-pg:
@@ -42,5 +42,9 @@ node_modules/
42
42
  *.pem
43
43
  *.key
44
44
 
45
+ # The egress proxy's MITM CA (Tier B). It lives in a Docker volume; a copy on disk is a real secret.
46
+ deploy/egress-proxy/ca/
47
+ deploy/egress-proxy/*.crt
48
+
45
49
  # A built Teams app package (`surface_app_package`): rebuilt from config, never committed.
46
50
  teams-app/*.zip
@@ -10,14 +10,15 @@
10
10
  "postinstall": "oe-eve-patch",
11
11
  "oe:build": "oe build",
12
12
  "predev": "oe build",
13
- "dev": "eve dev",
13
+ "dev": "EVE_TELEMETRY_DISABLED=1 eve dev",
14
14
  "prebuild": "oe build",
15
- "build": "eve build",
15
+ "build": "EVE_TELEMETRY_DISABLED=1 eve build",
16
16
  "prestart": "oe build",
17
- "start": "eve start",
17
+ "start": "EVE_TELEMETRY_DISABLED=1 eve start",
18
18
  "pretypecheck": "oe build",
19
19
  "typecheck": "tsc --noEmit -p tsconfig.json",
20
20
  "check": "create-open-everywhere --check",
21
+ "check:model-config": "node --import tsx scripts/model-config-check.ts",
21
22
  "ops": "node --import tsx scripts/ops-mcp.ts",
22
23
  "env:pipe": "node scripts/env-pipe.mjs"
23
24
  },
@@ -28,6 +29,7 @@
28
29
  "@open-everywhere/provider-discord": "{{OE_VERSION}}",
29
30
  "@open-everywhere/provider-email": "{{OE_VERSION}}",
30
31
  "@open-everywhere/provider-github": "{{OE_VERSION}}",
32
+ "@open-everywhere/provider-google-chat": "{{OE_VERSION}}",
31
33
  "@open-everywhere/provider-linear": "{{OE_VERSION}}",
32
34
  "@open-everywhere/provider-notion": "{{OE_VERSION}}",
33
35
  "@open-everywhere/provider-slack": "{{OE_VERSION}}",
@@ -29,8 +29,8 @@ export default defineRoster({
29
29
  core: true,
30
30
  adapters: true,
31
31
 
32
- // One provider bundle per surface. Add a line to add a surface (`discord`, `teams`, `linear` and
33
- // `email` are installed too), then `npx oe build --accept`.
32
+ // One provider bundle per surface. Add a line to add a surface (`discord`, `teams`, `linear`,
33
+ // `email` and `"google-chat"` are installed too), then `npx oe build --accept`.
34
34
  providers: {
35
35
  slack: true,
36
36
  notion: true,
@@ -18,6 +18,7 @@
18
18
  "@open-everywhere/core": {
19
19
  "version": "{{OE_VERSION}}",
20
20
  "contributions": {
21
+ "channel/a2a-inbound": "mounted",
21
22
  "channel/aws-sso-callback": "mounted",
22
23
  "channel/control-mcp": "mounted",
23
24
  "channel/control-read": "mounted",
@@ -51,10 +52,14 @@
51
52
  "hook/memory": "mounted",
52
53
  "hook/native-input-audit": "mounted",
53
54
  "hook/own-account-ask": "mounted",
55
+ "hook/personal-watch-enroll": "mounted",
56
+ "hook/question-ledger": "mounted",
54
57
  "hook/requester-identity": "mounted",
55
58
  "hook/requester-messages": "mounted",
56
59
  "hook/routine-outcome": "mounted",
60
+ "hook/routine-report-reply": "mounted",
57
61
  "hook/routine-silence": "mounted",
62
+ "hook/routine-test-run": "mounted",
58
63
  "hook/session-park": "mounted",
59
64
  "hook/spawn-taint": "mounted",
60
65
  "hook/spend": "mounted",
@@ -66,11 +71,13 @@
66
71
  "hook/wake-turn-start": "mounted",
67
72
  "hook/window-routing": "mounted",
68
73
  "schedule/audit-export": "mounted",
74
+ "schedule/cover-recap": "mounted",
69
75
  "schedule/directory-sync": "mounted",
70
76
  "schedule/dispatch": "mounted",
71
77
  "schedule/dream-mode3": "mounted",
72
78
  "schedule/meeting-sweep": "mounted",
73
79
  "schedule/personal-browser-sweep": "mounted",
80
+ "schedule/personal-watch": "mounted",
74
81
  "schedule/tidy": "mounted",
75
82
  "skill/scoped": "mounted",
76
83
  "tool/add_label": "mounted",
@@ -104,9 +111,11 @@
104
111
  "tool/delete_skill": "mounted",
105
112
  "tool/describe_image": "mounted",
106
113
  "tool/dm_person": "mounted",
114
+ "tool/doing_for_me": "mounted",
107
115
  "tool/edit_reply": "mounted",
108
116
  "tool/edit_routine": "mounted",
109
117
  "tool/enable_routine": "mounted",
118
+ "tool/expect_bot_reply": "mounted",
110
119
  "tool/explain_permission": "mounted",
111
120
  "tool/get_pr_feedback": "mounted",
112
121
  "tool/grant_tools": "mounted",
@@ -117,6 +126,7 @@
117
126
  "tool/list_routines": "mounted",
118
127
  "tool/list_skills": "mounted",
119
128
  "tool/list_webhooks": "mounted",
129
+ "tool/look_up": "mounted",
120
130
  "tool/meeting_join": "mounted",
121
131
  "tool/meeting_leave": "mounted",
122
132
  "tool/meeting_recap": "mounted",
@@ -170,12 +180,15 @@
170
180
  "tool/spend_report": "mounted",
171
181
  "tool/stand_down": "mounted",
172
182
  "tool/subscribe_pr": "mounted",
183
+ "tool/test_routine": "mounted",
173
184
  "tool/unlink_identity": "mounted",
185
+ "tool/update_list": "mounted",
174
186
  "tool/update_status": "mounted",
175
187
  "tool/usage_report": "mounted",
176
188
  "tool/waiting_on_me": "mounted",
177
189
  "tool/walk_pr": "mounted",
178
- "tool/work": "mounted"
190
+ "tool/work": "mounted",
191
+ "tool/write_canvas": "mounted"
179
192
  }
180
193
  },
181
194
  "@open-everywhere/provider-github": {
@@ -1,3 +1,4 @@
1
+ agent/channels/a2a-inbound.ts mounted @open-everywhere/core
1
2
  agent/channels/aws-sso-callback.ts mounted @open-everywhere/core
2
3
  agent/channels/control-mcp.ts mounted @open-everywhere/core
3
4
  agent/channels/control-read.ts mounted @open-everywhere/core
@@ -36,10 +37,14 @@ agent/hooks/loop-breaker.ts mounted @open-everywhere/core
36
37
  agent/hooks/memory.ts mounted @open-everywhere/core
37
38
  agent/hooks/native-input-audit.ts mounted @open-everywhere/core
38
39
  agent/hooks/own-account-ask.ts mounted @open-everywhere/core
40
+ agent/hooks/personal-watch-enroll.ts mounted @open-everywhere/core
41
+ agent/hooks/question-ledger.ts mounted @open-everywhere/core
39
42
  agent/hooks/requester-identity.ts mounted @open-everywhere/core
40
43
  agent/hooks/requester-messages.ts mounted @open-everywhere/core
41
44
  agent/hooks/routine-outcome.ts mounted @open-everywhere/core
45
+ agent/hooks/routine-report-reply.ts mounted @open-everywhere/core
42
46
  agent/hooks/routine-silence.ts mounted @open-everywhere/core
47
+ agent/hooks/routine-test-run.ts mounted @open-everywhere/core
43
48
  agent/hooks/sandbox-lifecycle.ts mounted @open-everywhere/adapters
44
49
  agent/hooks/session-park.ts mounted @open-everywhere/core
45
50
  agent/hooks/spawn-taint.ts mounted @open-everywhere/core
@@ -53,12 +58,14 @@ agent/hooks/wake-turn-start.ts mounted @open-everywhere/core
53
58
  agent/hooks/window-routing.ts mounted @open-everywhere/core
54
59
  agent/lib/providers.generated.ts providers slack,notion,github
55
60
  agent/schedules/audit-export.ts mounted @open-everywhere/core
61
+ agent/schedules/cover-recap.ts mounted @open-everywhere/core
56
62
  agent/schedules/directory-sync.ts mounted @open-everywhere/core
57
63
  agent/schedules/dispatch.ts mounted @open-everywhere/core
58
64
  agent/schedules/dream-mode3.ts mounted @open-everywhere/core
59
65
  agent/schedules/meeting-sweep.ts mounted @open-everywhere/core
60
66
  agent/schedules/notion-ingress.ts mounted @open-everywhere/provider-notion
61
67
  agent/schedules/personal-browser-sweep.ts mounted @open-everywhere/core
68
+ agent/schedules/personal-watch.ts mounted @open-everywhere/core
62
69
  agent/schedules/tidy.ts mounted @open-everywhere/core
63
70
  agent/skills/scoped.ts mounted @open-everywhere/core
64
71
  agent/tools/add_label.ts mounted @open-everywhere/core
@@ -93,9 +100,11 @@ agent/tools/delegate_to_agent.ts mounted @open-everywhere/core
93
100
  agent/tools/delete_skill.ts mounted @open-everywhere/core
94
101
  agent/tools/describe_image.ts mounted @open-everywhere/core
95
102
  agent/tools/dm_person.ts mounted @open-everywhere/core
103
+ agent/tools/doing_for_me.ts mounted @open-everywhere/core
96
104
  agent/tools/edit_reply.ts mounted @open-everywhere/core
97
105
  agent/tools/edit_routine.ts mounted @open-everywhere/core
98
106
  agent/tools/enable_routine.ts mounted @open-everywhere/core
107
+ agent/tools/expect_bot_reply.ts mounted @open-everywhere/core
99
108
  agent/tools/explain_permission.ts mounted @open-everywhere/core
100
109
  agent/tools/extend_workspace_time.ts mounted @open-everywhere/adapters
101
110
  agent/tools/get_pr_feedback.ts mounted @open-everywhere/core
@@ -107,6 +116,7 @@ agent/tools/list_repos.ts mounted @open-everywhere/core
107
116
  agent/tools/list_routines.ts mounted @open-everywhere/core
108
117
  agent/tools/list_skills.ts mounted @open-everywhere/core
109
118
  agent/tools/list_webhooks.ts mounted @open-everywhere/core
119
+ agent/tools/look_up.ts mounted @open-everywhere/core
110
120
  agent/tools/meeting_join.ts mounted @open-everywhere/core
111
121
  agent/tools/meeting_leave.ts mounted @open-everywhere/core
112
122
  agent/tools/meeting_recap.ts mounted @open-everywhere/core
@@ -164,11 +174,14 @@ agent/tools/spawn_thread_session.ts mounted @open-everywhere/core
164
174
  agent/tools/spend_report.ts mounted @open-everywhere/core
165
175
  agent/tools/stand_down.ts mounted @open-everywhere/core
166
176
  agent/tools/subscribe_pr.ts mounted @open-everywhere/core
177
+ agent/tools/test_routine.ts mounted @open-everywhere/core
167
178
  agent/tools/unlink_identity.ts mounted @open-everywhere/core
179
+ agent/tools/update_list.ts mounted @open-everywhere/core
168
180
  agent/tools/update_status.ts mounted @open-everywhere/core
169
181
  agent/tools/usage_report.ts mounted @open-everywhere/core
170
182
  agent/tools/waiting_on_me.ts mounted @open-everywhere/core
171
183
  agent/tools/walk_pr.ts mounted @open-everywhere/core
172
184
  agent/tools/web_fetch.ts framework-disabled
173
185
  agent/tools/work.ts mounted @open-everywhere/core
186
+ agent/tools/write_canvas.ts mounted @open-everywhere/core
174
187
  agent/tools/write_file.ts framework-disabled