Sync the self-hosting stack
This commit is contained in:
+109
@@ -9,6 +9,115 @@ one of them changed. An entry says which artefact and which version, so `web 0.1
|
|||||||
Every version published to a channel appears here. Nothing is written from memory afterwards: a build
|
Every version published to a channel appears here. Nothing is written from memory afterwards: a build
|
||||||
cannot be published without its entry.
|
cannot be published without its entry.
|
||||||
|
|
||||||
|
## 2026-08-31 — Jarvis can wake itself up, knows what day it is, and can read the web `api 0.102.0` `web 0.125.0` `browser 0.1.0`
|
||||||
|
|
||||||
|
**The schema step stops once on the way in — see the upgrade note below.** It is the same guard as
|
||||||
|
`api 0.101.0`, tripped by a different change.
|
||||||
|
|
||||||
|
### Taking this build: the schema step stops once again
|
||||||
|
|
||||||
|
Same guard as `api 0.101.0`, tripped by a different change — and for the same reason it is harmless
|
||||||
|
here. Reshaping scheduled tasks around the conversation drops the columns the old organization-wide
|
||||||
|
version needed, and the guard refuses a drop without being told to:
|
||||||
|
|
||||||
|
```
|
||||||
|
⚠️ There might be data loss when applying the changes:
|
||||||
|
• The `Schedule` table will lose columns: kind, autonomy, toolName, assetId, …
|
||||||
|
Error: Use the --accept-data-loss flag …
|
||||||
|
```
|
||||||
|
|
||||||
|
**Nothing of yours is in those columns.** They existed only in `api 0.101.0`, which was published
|
||||||
|
this afternoon, and only a wake-up created through the removed Settings screen could have filled
|
||||||
|
them. Check before you take our word for it:
|
||||||
|
|
||||||
|
```sql
|
||||||
|
SELECT count(*) FROM "Schedule";
|
||||||
|
```
|
||||||
|
|
||||||
|
If that is 0 — which it is on every instance that upgraded straight from 0.100.0 or earlier — the
|
||||||
|
migration cannot lose anything. If it is not 0, those rows are wake-ups you created today and they
|
||||||
|
will be dropped; note down what they were before you continue, and recreate them by asking Jarvis
|
||||||
|
inside the conversation you want them in.
|
||||||
|
|
||||||
|
Then let it through **once**, and put the guard back:
|
||||||
|
|
||||||
|
- **compose** — set `SCHEMA_ACCEPT_DATA_LOSS=true` in `.env`, `docker compose up -d`, wait for the
|
||||||
|
api, then remove the line. Leaving it set would let a genuinely destructive change through
|
||||||
|
unannounced on some future release.
|
||||||
|
- **Kubernetes** — set the same variable on the `jarvis-migrate` Job, run it to completion, then take
|
||||||
|
it off. The api pods keep serving the old build while the Job fails, so there is no outage.
|
||||||
|
|
||||||
|
### Scheduled tasks are now part of a conversation
|
||||||
|
|
||||||
|
`api 0.101.0` shipped these a few hours ago as an organization-wide screen, and that was the wrong
|
||||||
|
shape. **The "Scheduled work" section and the `schedule:view` / `schedule:manage` permissions are
|
||||||
|
gone**; if you had started using them, nothing is silently broken — but read on, because what
|
||||||
|
replaced them is not in the same place.
|
||||||
|
|
||||||
|
The thing people actually want this for needs the conversation. Two cases, and both:
|
||||||
|
|
||||||
|
- **Waiting.** A rebuild is running, a DNS change is propagating, a maintenance window opens at
|
||||||
|
03:00. Jarvis leaves itself a note and picks the work up later with everything it had already
|
||||||
|
established still in front of it.
|
||||||
|
- **A standing check.** "Look at that file every day at noon." Every occurrence lands in the same
|
||||||
|
thread, so the seventh day can say "unchanged since Tuesday" instead of reporting into a vacuum.
|
||||||
|
|
||||||
|
The old version opened a _fresh_ conversation for each occurrence — which meant "carry on where you
|
||||||
|
left off" was impossible, because the woken assistant arrived knowing nothing. Now a wake-up belongs
|
||||||
|
to one conversation, wakes back into it, and appears under a new **Tasks** tab in that
|
||||||
|
conversation's workspace, beside the documents and the approvals. You pause, run and cancel from
|
||||||
|
there; you create one by asking ("check this again tomorrow morning"), because the instruction is
|
||||||
|
written with the conversation around it.
|
||||||
|
|
||||||
|
**It runs as whoever scheduled it, checked when it fires rather than when it was written.** If
|
||||||
|
somebody's access to a machine is revoked, the work they left behind stops reaching it the same day
|
||||||
|
and says so on its row. Three failures in a row switch a wake-up off, so a broken one becomes a
|
||||||
|
thing you find rather than a notification you learn to ignore.
|
||||||
|
|
||||||
|
Wake-ups inherit the conversation's autonomy and its asset scope; they cannot raise either.
|
||||||
|
|
||||||
|
### Jarvis knows what the date is
|
||||||
|
|
||||||
|
It did not before, and it did not know that it did not. Asked what day it is, a language model
|
||||||
|
answers from its training data — confidently, and usually months out. Everything an administrator
|
||||||
|
asks is dated: "the logs from the last week", "has this certificate expired", "wake me tomorrow at
|
||||||
|
noon". Each of those was a subtraction from a number nothing was giving it.
|
||||||
|
|
||||||
|
The current time is now stated at the start of every turn, and there is a `current_time` tool for
|
||||||
|
reading the clock during a long run and for converting into a customer's time zone. Nothing to
|
||||||
|
configure. If you have ever been given a date that was quietly wrong, this is why.
|
||||||
|
|
||||||
|
### Jarvis can read the web — if you turn it on
|
||||||
|
|
||||||
|
Two new abilities: search the web, and open a page and read it. A real browser, so pages that build
|
||||||
|
themselves with JavaScript work — vendor advisories, status pages, release notes, consoles.
|
||||||
|
|
||||||
|
It also fixes the same class of problem as the clock: a model's knowledge has a cut-off. Versions,
|
||||||
|
end-of-life dates, advisories and prices are exactly what it will otherwise answer from memory and
|
||||||
|
get wrong, so it is now told to look things up instead.
|
||||||
|
|
||||||
|
**It is off by default, and turning it on is a deliberate act.** An assistant that can read the
|
||||||
|
internet is a new kind of input into a system that administers your customers. To enable it:
|
||||||
|
|
||||||
|
- **compose** — `COMPOSE_PROFILES=browser docker compose up -d`, and set `BROWSER_URL=http://browser:3000`.
|
||||||
|
- **Kubernetes** — uncomment `09-browser.yaml` in `kustomization.yaml` and `BROWSER_URL` in
|
||||||
|
`02-config.yaml`.
|
||||||
|
|
||||||
|
With it off, the two tools report themselves unavailable and nothing else changes.
|
||||||
|
|
||||||
|
**Chromium runs in its own container, never in the api.** The api holds your vault key and talks to
|
||||||
|
your database; a browser's whole job is to run code written by strangers. Keeping them apart is what
|
||||||
|
stops a web page being used to read your database, your cache or your cloud provider's metadata
|
||||||
|
endpoint. The browser container holds no secret, no volume and no database credentials, and the
|
||||||
|
Kubernetes manifest ships a NetworkPolicy that denies it every private address range. **That policy
|
||||||
|
needs a CNI that enforces NetworkPolicy** — Calico, Cilium and most managed clusters do; plain
|
||||||
|
flannel does not, in which case the in-process address check is the only control and it cannot
|
||||||
|
survive a redirect.
|
||||||
|
|
||||||
|
**What comes back is treated as untrusted.** Page text is delimited and labelled as data rather than
|
||||||
|
instruction, because a page can be written specifically to be read by an assistant with access to
|
||||||
|
your systems. The tools only read: no clicking, no forms, no signing in.
|
||||||
|
|
||||||
## 2026-08-31 — Jarvis works while you are asleep, and can finally administer Exchange `api 0.101.0` `web 0.124.0`
|
## 2026-08-31 — Jarvis works while you are asleep, and can finally administer Exchange `api 0.101.0` `web 0.124.0`
|
||||||
|
|
||||||
Two capabilities, and both are about the half of managed services that does not happen in a
|
Two capabilities, and both are about the half of managed services that does not happen in a
|
||||||
|
|||||||
@@ -215,10 +215,20 @@ services:
|
|||||||
# answer you want from an upgrade that was not expecting to lose anything.
|
# answer you want from an upgrade that was not expecting to lose anything.
|
||||||
SCHEMA_ACCEPT_DATA_LOSS: ${SCHEMA_ACCEPT_DATA_LOSS:-false}
|
SCHEMA_ACCEPT_DATA_LOSS: ${SCHEMA_ACCEPT_DATA_LOSS:-false}
|
||||||
|
|
||||||
|
# The browser the assistant may use, if you run one. Unset is a supported state: the two web
|
||||||
|
# tools report themselves unavailable and everything else works. It is a separate service ON
|
||||||
|
# PURPOSE — a browser runs code written by strangers, and this container holds the vault key.
|
||||||
|
# Empty by default, matching the profile above: with no browser running, pointing the api at
|
||||||
|
# one would only produce timeouts. Set it when you enable the profile.
|
||||||
|
BROWSER_URL: ${BROWSER_URL:-}
|
||||||
|
|
||||||
# Where the agent binaries live, if you have them. Leaving this unset is a supported state:
|
# Where the agent binaries live, if you have them. Leaving this unset is a supported state:
|
||||||
# everything except the agent installer works, and the installer answers 503 saying no build is
|
# everything except the agent installer works, and the installer answers 503 saying no build is
|
||||||
# published. See the README — a self-hosted instance has no way to produce these.
|
# published. See the README — a self-hosted instance has no way to produce these.
|
||||||
AGENT_RELEASE_DIR: ${AGENT_RELEASE_DIR:-}
|
AGENT_RELEASE_DIR: ${AGENT_RELEASE_DIR:-}
|
||||||
|
networks:
|
||||||
|
- default
|
||||||
|
- browsernet
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test:
|
test:
|
||||||
- CMD
|
- CMD
|
||||||
@@ -231,6 +241,49 @@ services:
|
|||||||
# First boot syncs the schema and runs the data backfills before it listens.
|
# First boot syncs the schema and runs the data backfills before it listens.
|
||||||
start_period: 40s
|
start_period: 40s
|
||||||
|
|
||||||
|
# The headless browser, and everything about this service is about keeping it away from the rest.
|
||||||
|
#
|
||||||
|
# WHY IT IS NOT PART OF THE API. A browser's whole job is to fetch code written by strangers and
|
||||||
|
# run it. The api container holds VAULT_MASTER_KEY and a live connection to Postgres and Redis, so
|
||||||
|
# a browser sharing its network would let any page the assistant is pointed at reach them — and
|
||||||
|
# reach a cloud metadata endpoint, which is how instance credentials get read out loud.
|
||||||
|
#
|
||||||
|
# THE ISOLATION IS THE NETWORK, not the good intentions. This service is on `browsernet` and
|
||||||
|
# NOTHING ELSE. postgres and redis are on `default`, which it is not a member of, so it cannot
|
||||||
|
# resolve or reach them at all. The api is on both, because it is the only thing that calls this.
|
||||||
|
# It also carries no secret, no volume and no database URL: a renderer that escapes lands in a
|
||||||
|
# container that holds nothing worth having.
|
||||||
|
#
|
||||||
|
# Delete this service and unset BROWSER_URL if you would rather Jarvis had no web access. Nothing
|
||||||
|
# else stops working; the two web tools report themselves unavailable.
|
||||||
|
browser:
|
||||||
|
image: ${JARVIS_IMAGE_BROWSER:-git.luxit.be/luxit/jarvis-browser:stable}
|
||||||
|
# OPT-IN. A profiled service does not start unless you ask for it:
|
||||||
|
# COMPOSE_PROFILES=browser docker compose up -d
|
||||||
|
# Web access is a decision, not a default — an assistant that can read the internet is a new
|
||||||
|
# class of input into a system that administers your customers, so you turn it on deliberately.
|
||||||
|
profiles:
|
||||||
|
- browser
|
||||||
|
restart: unless-stopped
|
||||||
|
networks:
|
||||||
|
- browsernet
|
||||||
|
# A renderer is a memory hog, and a compromised one is a fork bomb. Both are bounded here rather
|
||||||
|
# than left to take the host down with them.
|
||||||
|
mem_limit: 1g
|
||||||
|
pids_limit: 512
|
||||||
|
security_opt:
|
||||||
|
- no-new-privileges:true
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
- CMD
|
||||||
|
- node
|
||||||
|
- -e
|
||||||
|
- "fetch('http://localhost:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
||||||
|
interval: 30s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
start_period: 20s
|
||||||
|
|
||||||
web:
|
web:
|
||||||
image: ${JARVIS_IMAGE_WEB:-git.luxit.be/luxit/jarvis-web:stable}
|
image: ${JARVIS_IMAGE_WEB:-git.luxit.be/luxit/jarvis-web:stable}
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@@ -247,3 +300,13 @@ volumes:
|
|||||||
# The keys, deliberately apart from postgres_data. Back it up, and not to the same place: a
|
# The keys, deliberately apart from postgres_data. Back it up, and not to the same place: a
|
||||||
# database dump is worthless to a thief without this, and worthless to YOU without it either.
|
# database dump is worthless to a thief without this, and worthless to YOU without it either.
|
||||||
jarvis_secrets:
|
jarvis_secrets:
|
||||||
|
|
||||||
|
# Two networks, and the split is a security control rather than tidiness.
|
||||||
|
#
|
||||||
|
# default is what every service was already on implicitly — postgres, redis, api, web.
|
||||||
|
# browsernet exists so the browser can be reached by the api and reach the internet, WITHOUT
|
||||||
|
# being able to resolve or connect to the database and the cache. A service that names no
|
||||||
|
# network is on default, so nothing changes for the ones that were already there.
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
browsernet:
|
||||||
|
|||||||
@@ -58,6 +58,10 @@ data:
|
|||||||
# first decides whether a restart leaves a closed transcript or a half-written one.
|
# first decides whether a restart leaves a closed transcript or a half-written one.
|
||||||
RUN_SHUTDOWN_GRACE_SEC: "25"
|
RUN_SHUTDOWN_GRACE_SEC: "25"
|
||||||
|
|
||||||
|
# The browser service — a separate pod on purpose, see 09-browser.yaml. COMMENTED OUT to match
|
||||||
|
# that file: with no browser deployed, setting this only turns "unavailable" into a timeout.
|
||||||
|
# BROWSER_URL: "http://jarvis-browser:3000"
|
||||||
|
|
||||||
# No demo organization and no seeded administrator: the install screen is the way in. The seed
|
# No demo organization and no seeded administrator: the install screen is the way in. The seed
|
||||||
# refuses an instance that already has accounts anyway, but a Deployment has no business
|
# refuses an instance that already has accounts anyway, but a Deployment has no business
|
||||||
# furnishing anything.
|
# furnishing anything.
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
# The headless browser Jarvis is allowed to use, and the policy that keeps it away from everything
|
||||||
|
# else.
|
||||||
|
#
|
||||||
|
# WHY IT IS ITS OWN POD. A browser's entire purpose is to fetch code written by strangers and run it.
|
||||||
|
# The api pod holds VAULT_MASTER_KEY, the database URL and a live connection to Postgres and Redis;
|
||||||
|
# putting Chromium in it would let any page the assistant is pointed at reach all three — and reach
|
||||||
|
# the node's metadata endpoint on 169.254.169.254, which is how instance credentials get read out
|
||||||
|
# loud. PowerShell is in the api image and this is not, and the difference is exactly that: the
|
||||||
|
# Exchange module talks to Microsoft, and a browser talks to whoever the model was told to look at.
|
||||||
|
#
|
||||||
|
# THE NETWORK POLICY BELOW IS THE REAL CONTROL. `browser.client.ts` refuses private addresses before
|
||||||
|
# it asks, which is the half that can EXPLAIN itself to the model — but it cannot survive a redirect
|
||||||
|
# and it cannot survive DNS rebinding, because by then the browser is already connecting. The policy
|
||||||
|
# can, because it is enforced on the packet.
|
||||||
|
#
|
||||||
|
# DELETE THIS FILE (and its line in kustomization.yaml, and BROWSER_URL from 02-config.yaml) if you
|
||||||
|
# would rather Jarvis had no web access. Nothing else stops working: the two web tools report
|
||||||
|
# themselves unavailable and every other tool is unaffected.
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: jarvis-browser
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: jarvis
|
||||||
|
app.kubernetes.io/component: browser
|
||||||
|
spec:
|
||||||
|
# One is enough and more is not better: each replica is a Chromium, and a render is short. Scale
|
||||||
|
# this only if the assistant is genuinely browsing concurrently across many conversations.
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: jarvis
|
||||||
|
app.kubernetes.io/component: browser
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app.kubernetes.io/name: jarvis
|
||||||
|
app.kubernetes.io/component: browser
|
||||||
|
spec:
|
||||||
|
# No service account token. This pod has no business talking to the Kubernetes API, and a
|
||||||
|
# mounted token is the first thing anything escaping a renderer would look for.
|
||||||
|
automountServiceAccountToken: false
|
||||||
|
containers:
|
||||||
|
- name: browser
|
||||||
|
image: git.luxit.be/luxit/jarvis-browser:stable
|
||||||
|
ports:
|
||||||
|
- containerPort: 3000
|
||||||
|
name: http
|
||||||
|
# No envFrom, no secretRef, no volume. Deliberately: this container should hold nothing
|
||||||
|
# worth stealing, so that escaping the renderer is not the same as reaching anything.
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
cpu: 100m
|
||||||
|
memory: 256Mi
|
||||||
|
limits:
|
||||||
|
# Chromium will use whatever it is given on a heavy page. The limit is what stops one
|
||||||
|
# bad page evicting the api from the same node.
|
||||||
|
memory: 1Gi
|
||||||
|
securityContext:
|
||||||
|
allowPrivilegeEscalation: false
|
||||||
|
runAsNonRoot: true
|
||||||
|
# `pwuser` in the Playwright image.
|
||||||
|
runAsUser: 1000
|
||||||
|
capabilities:
|
||||||
|
drop: ["ALL"]
|
||||||
|
seccompProfile:
|
||||||
|
type: RuntimeDefault
|
||||||
|
# NOT read-only: Chromium writes its profile, its cache and its shared memory. The
|
||||||
|
# writable surface is an emptyDir that dies with the pod rather than the image.
|
||||||
|
volumeMounts:
|
||||||
|
- name: tmp
|
||||||
|
mountPath: /tmp
|
||||||
|
- name: home
|
||||||
|
mountPath: /home/pwuser
|
||||||
|
livenessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 20
|
||||||
|
periodSeconds: 30
|
||||||
|
readinessProbe:
|
||||||
|
httpGet:
|
||||||
|
path: /health
|
||||||
|
port: http
|
||||||
|
initialDelaySeconds: 5
|
||||||
|
periodSeconds: 10
|
||||||
|
volumes:
|
||||||
|
- name: tmp
|
||||||
|
emptyDir:
|
||||||
|
# /dev/shm is small by default and Chromium is the classic victim of it; the server
|
||||||
|
# passes --disable-dev-shm-usage and writes here instead.
|
||||||
|
sizeLimit: 512Mi
|
||||||
|
- name: home
|
||||||
|
emptyDir:
|
||||||
|
sizeLimit: 512Mi
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: jarvis-browser
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app.kubernetes.io/name: jarvis
|
||||||
|
app.kubernetes.io/component: browser
|
||||||
|
ports:
|
||||||
|
- port: 3000
|
||||||
|
targetPort: http
|
||||||
|
name: http
|
||||||
|
---
|
||||||
|
# WHAT THE BROWSER MAY TALK TO, and it is the file's reason for existing.
|
||||||
|
#
|
||||||
|
# Ingress: the api pods, and nothing else. Nothing in this namespace should be calling a browser.
|
||||||
|
#
|
||||||
|
# Egress: DNS, and then the internet MINUS every private range. The exception list is the whole
|
||||||
|
# point — 10/8, 172.16/12 and 192.168/16 are where the cluster and the customer's estate live, and
|
||||||
|
# 169.254.0.0/16 is the metadata endpoint. Without these `except` clauses a page could ask the
|
||||||
|
# browser to fetch http://169.254.169.254/ and read the node's cloud credentials back as page text.
|
||||||
|
#
|
||||||
|
# NOTE THIS NEEDS A CNI THAT ENFORCES NetworkPolicy. Calico, Cilium and most managed clusters do;
|
||||||
|
# plain kube-proxy with flannel does NOT, and on such a cluster this file is documentation rather
|
||||||
|
# than a control. Check before relying on it — `browser.client.ts` still refuses private addresses
|
||||||
|
# on the near side either way, but that half cannot survive a redirect.
|
||||||
|
apiVersion: networking.k8s.io/v1
|
||||||
|
kind: NetworkPolicy
|
||||||
|
metadata:
|
||||||
|
name: jarvis-browser
|
||||||
|
spec:
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: jarvis
|
||||||
|
app.kubernetes.io/component: browser
|
||||||
|
policyTypes:
|
||||||
|
- Ingress
|
||||||
|
- Egress
|
||||||
|
ingress:
|
||||||
|
- from:
|
||||||
|
- podSelector:
|
||||||
|
matchLabels:
|
||||||
|
app.kubernetes.io/name: jarvis
|
||||||
|
app.kubernetes.io/component: api
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 3000
|
||||||
|
egress:
|
||||||
|
# DNS, to the cluster resolver. Without this nothing resolves and every page fails.
|
||||||
|
- to:
|
||||||
|
- namespaceSelector: {}
|
||||||
|
podSelector:
|
||||||
|
matchLabels:
|
||||||
|
k8s-app: kube-dns
|
||||||
|
ports:
|
||||||
|
- protocol: UDP
|
||||||
|
port: 53
|
||||||
|
- protocol: TCP
|
||||||
|
port: 53
|
||||||
|
# The public internet, and nothing that is not.
|
||||||
|
- to:
|
||||||
|
- ipBlock:
|
||||||
|
cidr: 0.0.0.0/0
|
||||||
|
except:
|
||||||
|
- 10.0.0.0/8
|
||||||
|
- 172.16.0.0/12
|
||||||
|
- 192.168.0.0/16
|
||||||
|
- 169.254.0.0/16
|
||||||
|
- 127.0.0.0/8
|
||||||
|
- 100.64.0.0/10
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 80
|
||||||
|
- protocol: TCP
|
||||||
|
port: 443
|
||||||
@@ -42,6 +42,11 @@ resources:
|
|||||||
# Remove this line if you point REDIS_URL at a managed Redis. It deploys three Redis processes and
|
# Remove this line if you point REDIS_URL at a managed Redis. It deploys three Redis processes and
|
||||||
# three sentinels; see the README on what that costs on three nodes.
|
# three sentinels; see the README on what that costs on three nodes.
|
||||||
- 08-redis-sentinel.yaml
|
- 08-redis-sentinel.yaml
|
||||||
|
# The headless browser and the NetworkPolicy that isolates it. COMMENTED OUT: web access is a
|
||||||
|
# decision rather than a default, and an assistant that can read the internet is a new class of
|
||||||
|
# input into a system that administers customers. Uncomment this AND set BROWSER_URL in
|
||||||
|
# 02-config.yaml to enable it; leaving both alone changes nothing.
|
||||||
|
# - 09-browser.yaml
|
||||||
|
|
||||||
# ─── THE THREE IMAGES ───────────────────────────────────────────────────────────────────────────
|
# ─── THE THREE IMAGES ───────────────────────────────────────────────────────────────────────────
|
||||||
#
|
#
|
||||||
|
|||||||
Reference in New Issue
Block a user