Sync the self-hosting stack
This commit is contained in:
+299
@@ -9,6 +9,305 @@ 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-10-01 — Remote desktop: watch a Windows machine, and take the mouse `api 0.117.0` `web 0.142.0` `agent 0.28.0`
|
||||||
|
|
||||||
|
**Remote desktop opens.** **Agents** → a Windows machine → **Watch the screen**, and you see its
|
||||||
|
screen, live, for as long as you keep the tab open. Three things about it are worth reading before
|
||||||
|
you update rather than after: you can also **type and move the mouse**, not only look; the two
|
||||||
|
switches that allow it are turned on for your owners and administrators **by this update itself**,
|
||||||
|
once, and **no audit entry is written when that happens**; and the way back out is a switch in the
|
||||||
|
product rather than a rollback, because an agent cannot be un-updated.
|
||||||
|
|
||||||
|
**You can watch, and you can take the mouse and the keyboard.** Sight and control are two separate
|
||||||
|
grants and control is the second one — the viewer says "Watching only" and sends nothing at all for
|
||||||
|
as long as that is what you hold. With control, the keyboard and the mouse go to the far machine as
|
||||||
|
they would from a keyboard plugged into it: modifiers, the numeric keypad, AltGr, the wheel, and a
|
||||||
|
right-click. Typing into somebody else's Windows session is a different piece of Windows from reading
|
||||||
|
pixels out of it, so it refuses in places reading does not — see the black screens below.
|
||||||
|
|
||||||
|
**`Ctrl`+`Alt`+`Del` works from a session you hold control of, and only where the machine's own policy
|
||||||
|
allows it.** It is the one keystroke user-mode software cannot forge, which is the whole point of it:
|
||||||
|
the person at the
|
||||||
|
keyboard has to be able to trust that the sign-in screen in front of them is real. A machine permits
|
||||||
|
it only with bit 0 of `SoftwareSASGeneration` set, which is a policy **you** set on **your** machines
|
||||||
|
and that this release deliberately does not set for you. Where it is not set, the control is not
|
||||||
|
offered — and it is not offered on a watch-only session either, so somebody holding "Watching only"
|
||||||
|
finds it missing on a machine whose policy is right, with nothing on screen to say why. Write `3`
|
||||||
|
rather than `1`: both set bit 0, and `1` also widens the sequence to every service on the box while
|
||||||
|
closing the accessibility path Windows leaves open by default, which is how an on-screen keyboard or
|
||||||
|
another vendor's remote-control tool stops working. Do not set the policy on a machine whose agent
|
||||||
|
predates this release: the policy never travels from an older agent, so the edit buys nothing and
|
||||||
|
leaves the machine looser than it was. And what the sequence summons is a screen the viewer
|
||||||
|
deliberately will not show you — nor is it a way in to a machine already locked in front of you, where
|
||||||
|
the keystroke is refused along with every other one. It is for the machine somebody is signed in to: a
|
||||||
|
wedged shell that needs Task Manager, a password to change, a session to sign out.
|
||||||
|
`docs/operations.md` has the value, what it means, and how to read it off a machine; the
|
||||||
|
agent version to require is this one, `agent 0.28.0`.
|
||||||
|
|
||||||
|
**Nothing is captured unless a notice naming you is standing on the screen.** Your organisation's
|
||||||
|
policy decides how, at
|
||||||
|
**Settings → Remote desktop**, and every organisation starts on the banner. On **Show a banner**,
|
||||||
|
nothing at all is captured until a notice naming you exists, is mapped and holds the topmost window
|
||||||
|
style — which is as close to "visible" as Windows lets anything check: if it cannot be drawn the
|
||||||
|
session never starts, and if that window is destroyed or loses its topmost standing the session ends.
|
||||||
|
What is left open is a notice still standing that a later topmost window covers; we do not sample the
|
||||||
|
screen back to find out, and that is the one hole in this story we know of. On **Ask first**, nothing
|
||||||
|
is captured until the person accepts, and a refusal or a minute of silence ends it before it began —
|
||||||
|
recorded as their refusal, or as a question nobody answered, deliberately two different endings and
|
||||||
|
neither of them a failure. There is no third mode, no
|
||||||
|
override, and nothing an operator can do from their side to skip either one. The one deliberate
|
||||||
|
exception is a machine with nobody signed in: there is nobody to ask, so the session starts with the
|
||||||
|
notice shown and no question. **If somebody then signs in, the notice is taken down and raised again on
|
||||||
|
their own screen**, and the picture stops until it is standing there — so they are told, though nobody
|
||||||
|
goes back and asks them, which is the honest description of that one case. A machine nobody can log
|
||||||
|
into is exactly the machine an operator needs this for.
|
||||||
|
|
||||||
|
**Being told is not the same as being able to stop it, and the difference is worth stating.** The
|
||||||
|
notice is click-through on purpose — no click, no menu, no keyboard — so that it can never sit
|
||||||
|
between somebody and their work, and cannot be closed by accident or by a script. The cost is that
|
||||||
|
the person at the keyboard cannot dismiss the viewer from the notice itself. Their ways out are
|
||||||
|
elsewhere: refusing the prompt under the ask policy, before anything is captured; signing out, which
|
||||||
|
ends the session with it; or an administrator turning the feature off for the instance. What the
|
||||||
|
banner guarantees is that watching is never secret. Under the banner policy it is not asking for
|
||||||
|
consent, and it does not pretend to.
|
||||||
|
|
||||||
|
**A sentence of your own is added to the notice, never put in its place.** Whatever you write — a
|
||||||
|
policy reference, the number of your service desk — appears after the standard wording, so nothing
|
||||||
|
anybody can set here makes a session a quiet one. It is a sentence and not a page: 280 characters, and
|
||||||
|
longer is refused rather than trimmed. It is added to the banner only, and deliberately not to the
|
||||||
|
**Ask first** prompt, which stays the short question it has to be — so a number you add so that people
|
||||||
|
can check with you before accepting is not on the window where they accept.
|
||||||
|
|
||||||
|
**The language is the one the person at the machine reads, which is often not the one you read.** They
|
||||||
|
may have no Jarvis account at all: it is a customer's employee looking at their own screen. The server
|
||||||
|
composes the finished sentence in the language you pick and hands the machine that text, so the notice
|
||||||
|
is never assembled on the far end out of pieces, in a language nobody on your team could have checked.
|
||||||
|
English, French and German. The settings page previews it in the language you chose rather than in
|
||||||
|
yours — a preview in your own language would reassure you about a sentence nobody is ever shown. The
|
||||||
|
two buttons on the **Ask first** prompt are the one exception: a label that arrives empty or too long
|
||||||
|
for the button is drawn with the compiled-in English word instead, because a button with its text cut
|
||||||
|
off is worse than a button in the wrong language on the window where somebody decides.
|
||||||
|
|
||||||
|
**Changing any of that takes the right to change the organisation itself** — owners and
|
||||||
|
administrators, the same people who can rename it.
|
||||||
|
|
||||||
|
**Who may watch, and who may type: two new switches on an access grant**, at **Settings → Access**,
|
||||||
|
beside the one that gives a shell: **Watch the screen**, and under it **Move the mouse and type**.
|
||||||
|
They work exactly like the shell switch — per machine, per folder or for the whole organisation, most
|
||||||
|
specific rule wins — and they are independent of it in both directions. A machine room full of
|
||||||
|
headless servers is shells with no screen worth seeing; helping a colleague through their own desktop
|
||||||
|
is a screen with no business holding root. Neither implies the other, and neither implies anything
|
||||||
|
about what the assistant may do. **Control cannot be given without sight:** the second switch only
|
||||||
|
turns on when the first is on, and turning sight off turns control off with it. **Who may hold it:**
|
||||||
|
operators and above, the same as the shell, with a platform super-admin outside that answer as they are
|
||||||
|
everywhere else. A viewer's grant can be written with either switch on and reaches nothing — the
|
||||||
|
refusal is at the session, not at the row. **Who may hand it out** is a narrower question with the same
|
||||||
|
answer it has always had: writing grants belongs to owners and administrators, so an operator handed
|
||||||
|
sight of a client's machines passes nothing on to anybody. Where the ceiling bites is an owner or
|
||||||
|
administrator whose own row has been narrowed — holding sight but not control, they can write a
|
||||||
|
colleague a watch-only row and cannot write one that types; holding both, which is what the default row
|
||||||
|
for both roles carries, they pass on both.
|
||||||
|
|
||||||
|
**Owners and administrators are given both, once, by the schema sync — and the audit trail does not
|
||||||
|
record it.** Without it, turning the feature on at all would mean writing the first row from the
|
||||||
|
platform super-admin account, because nobody hands out access they do not themselves hold and no
|
||||||
|
ordinary grant would carry it. Only a row that **already** carries a shell on the whole organisation,
|
||||||
|
unbounded and not narrowed by a risk ceiling of its own, is widened: somebody an administrator has
|
||||||
|
decided not to trust with a terminal — or whose terminal was deliberately narrowed — is not handed
|
||||||
|
screens by an update, and needs a grant written by hand. **One exception to that, and it is the row to
|
||||||
|
check first:** an organisation where **nobody at all** currently holds a shell has the shell handed back
|
||||||
|
to its owners and administrators by a separate, older pass that runs moments earlier — and the screen
|
||||||
|
pass then widens those same rows. On a one-owner instance "nobody holds it" is simply "I took it off
|
||||||
|
myself", so if that is you, you get the shell back, plus the screen and the mouse, in one start. The
|
||||||
|
widening is part of the schema sync, which on compose is the api
|
||||||
|
container's own migrate step and on Kubernetes is the `jarvis-migrate` Job, so on Kubernetes nobody
|
||||||
|
is widened until that Job has run. It happens once per installation and is then stamped, which is
|
||||||
|
the whole of "once": if the stamp fails, or the process is killed between the first widening and it,
|
||||||
|
the pass stays armed and the next start hands both switches back to every owner and administrator row
|
||||||
|
that has since been revoked. The migrate log says so and names the column to set by hand — except in
|
||||||
|
the killed case, where it says nothing at all; rows that fail individually are skipped and counted in
|
||||||
|
the same log. Nobody else is touched. The missing entry is the part to know about: every _later_
|
||||||
|
change made on the grants screen is audited, but this widening is done by the schema sync rather than
|
||||||
|
by a person and writes no entry naming who now holds what — and neither does the default row a new
|
||||||
|
owner or administrator is seeded with when they are added or accept an invitation, which carries both
|
||||||
|
switches too. If your rule is that screen access is granted deliberately, read the grants of your
|
||||||
|
owners and administrators after updating, and again after adding one, rather than trusting the trail
|
||||||
|
to tell you.
|
||||||
|
|
||||||
|
**The audit trail now records the shell and the screen when a grant changes**, with their previous
|
||||||
|
values, which it did not before. "Who gave this person a shell on that client, and when" could not be
|
||||||
|
answered from the trail — the entry said that a grant had changed and which connectors it named,
|
||||||
|
while the switch that decides whether a human gets administrative control of the machine was
|
||||||
|
invisible. It is a gap that predates screen access; it is closed here rather than left for a third
|
||||||
|
switch to inherit.
|
||||||
|
|
||||||
|
**Windows security screens go black, deliberately.** A sign-in prompt, a `Ctrl`+`Alt`+`Del`, an
|
||||||
|
administrator-approval dialog, a lock screen: Windows puts each of those on a desktop of its own, and
|
||||||
|
the agent captures nothing but the one desktop its notice is standing on — so one that comes up during
|
||||||
|
a session arrives as a blank canvas. The refusal is ours rather than something the operating system
|
||||||
|
does for us, which is worth knowing if you are deciding how much of this to take on trust. **A session
|
||||||
|
you open while one of them is already standing is the exception, and it is deliberate:** the notice is
|
||||||
|
raised on whatever desktop is receiving input at that moment, so on a machine sitting at its sign-in
|
||||||
|
screen — the case this feature most exists for — the notice goes on the credential desktop and the
|
||||||
|
picture is of that. The rule being obeyed is the one rule in all of this, that the notice stands on the
|
||||||
|
desktop being captured and never beside it, rather than a list of screens by name. What the agent
|
||||||
|
refuses is to _move_ a notice onto a security desktop once a session is running, where the alternative
|
||||||
|
is a blank canvas rather than a secret one. The viewer tells you a Windows
|
||||||
|
security screen is up — a sign-in prompt, or a
|
||||||
|
request for administrator approval — without saying which, because what the machine can report is that
|
||||||
|
the desktop changed and not what is on it; a screen saver, or a desktop some other vendor's tool
|
||||||
|
created, reports the same thing under the same words. The picture resumes on its own when the screen
|
||||||
|
goes away, rather than showing you a frozen frame of the desktop underneath, which is the thing a
|
||||||
|
remote viewer must never do. Keystrokes do not reach those screens either. `Ctrl`+`Alt`+`Del` goes by
|
||||||
|
a route of its own for a separate reason: three injected keystrokes arrive on an ordinary desktop as
|
||||||
|
three ordinary keys and the sign-in process ignores them, so the sequence has to be asked for through
|
||||||
|
a dedicated Windows call rather than typed — and that call reports nothing back, so the only
|
||||||
|
confirmation anybody gets is the picture going black.
|
||||||
|
|
||||||
|
**Nothing is recorded, and what is kept is not nothing.** No video, no screenshot, no transcript, and
|
||||||
|
no setting anywhere that turns one on. What is kept is the session record: who connected — or that it
|
||||||
|
was a platform role rather than a grant — which machine, which consent policy applied, **whether
|
||||||
|
control was allowed**, how much data went each way, **how many input events were accepted and how
|
||||||
|
many were dropped**, when it started, when it ended and why; and then, once per screen watched,
|
||||||
|
which Windows session and the account signed in to it, which monitor, whether consent was agreed and
|
||||||
|
when, and whether it was accepted without anybody clicking — which is every session under the banner
|
||||||
|
policy, and an ask-policy session with nobody signed in. A session that moves to another screen keeps
|
||||||
|
one of those rows per stretch, each with its own consent, and that is the part a staff disclosure most
|
||||||
|
needs: whose desktop, for which stretch. The size of the picture is deliberately not stored at all.
|
||||||
|
Unlike a terminal transcript, that record has no retention setting and nothing sweeps it — but it is
|
||||||
|
keyed on the machine: deleting an asset takes its session rows with it, and deleting an account blanks
|
||||||
|
who connected. What outlives both is the platform audit trail, which keeps the open, the consent and
|
||||||
|
the close with no retention at all. Write your staff disclosure from that list rather than from the
|
||||||
|
first sentence.
|
||||||
|
|
||||||
|
**Windows only, and a machine that cannot do it says so instead of failing.** Your Linux and macOS
|
||||||
|
agents announce no screen and are never offered one — they go on doing everything they did, and the
|
||||||
|
server refuses a screen on any machine whose agent did not claim to have one. The row for a Mac or a
|
||||||
|
Linux box carries the action dimmed and **named** "Watch the screen — Windows only", and _named_ is
|
||||||
|
the exact word rather than a hedge: on the **Agents** page that entry is promoted into an icon-only
|
||||||
|
button, so a screen reader reads the sentence out, a touch device and the machine's own row on
|
||||||
|
**Assets** print it as words in the menu, and a mouse-driven browser shows a greyed monitor glyph with
|
||||||
|
the OS column beside it as the visible answer. The tooltip does not open, because a disabled control
|
||||||
|
takes no pointer. **And that control being live is a statement about the machine, not a promise about
|
||||||
|
your grant.** It is drawn for anybody whose organisation role may watch screens at all: a Viewer never
|
||||||
|
sees it, while an operator who holds the role but no **Watch the screen** grant sees it enabled on
|
||||||
|
every connected Windows row and is refused in words when they use it. The grant is checked when the
|
||||||
|
session opens, not when the menu is drawn.
|
||||||
|
|
||||||
|
**Sessions end by themselves.** Four hours for any one session — and the card announcing that ending
|
||||||
|
offers to open another, so the ceiling bounds a forgotten tab rather than how long somebody can be
|
||||||
|
watched. A session also ends if the viewer stops keeping up with the picture for twenty seconds, which
|
||||||
|
on a bad connection is a thing you will meet; it comes back with the same button. Switching to another
|
||||||
|
browser tab does not end a session: the picture pauses, the machine stops sending while the tab is
|
||||||
|
hidden, and both resume when you come back — a window merely covered by another window is not hidden
|
||||||
|
and goes on costing what it cost, which is a trade of liveness for bytes made on purpose. A tab left
|
||||||
|
paused for fifteen minutes
|
||||||
|
is ended, give or take the fifteen seconds it takes the next re-authorisation sweep to notice, on the
|
||||||
|
grounds that a viewer which has said it is not looking and has gone on not looking for that long
|
||||||
|
should not hold a helper process and a banner on somebody's screen until the four-hour ceiling. A
|
||||||
|
session you are actually watching is not idle no matter how long you watch it without touching
|
||||||
|
anything, which is the right answer for a screen and the wrong one for a shell.
|
||||||
|
|
||||||
|
**Two sessions at once on any one machine, and sixteen on any one api pod.** A third attempt on the
|
||||||
|
same machine is refused and says how many people are already watching it — that count is read for the
|
||||||
|
sentence and never for the gate, so when it cannot be read the refusal still stands and says only that
|
||||||
|
the machine is showing its screen to as many people as it allows. Neither wording names the machine.
|
||||||
|
Nothing already running is ended to make room, and two tabs
|
||||||
|
belonging to one operator fill the two seats exactly as two operators would. The per-machine number is
|
||||||
|
compiled into the agent and is the only thing that enforces it, so it cannot be changed from the
|
||||||
|
instance and moves only with an agent release. The per-pod number is the one a single-replica instance
|
||||||
|
meets first, and its refusal says to try again in a moment.
|
||||||
|
|
||||||
|
**Separately, a terminal session's flow-control window is now clamped to what the server actually
|
||||||
|
sent.** A terminal holds output back until the browser confirms it has caught up, and that is what
|
||||||
|
makes a runaway process on the far machine slow down instead of flooding the session. A browser could
|
||||||
|
confirm more than it had been sent, which switched that brake off for the rest of the session on a
|
||||||
|
machine reached through its Jarvis agent — no privilege beyond a terminal the person was already
|
||||||
|
entitled to open, and nothing to see afterwards but load. Sessions reached over SSH were never
|
||||||
|
affected: that transport re-applies its own brake every 256 KiB, and has in every released version. The confirmation is now capped at what was sent and not yet confirmed, so it can never be worth
|
||||||
|
more than the output it answers. A malformed confirmation is ignored rather than jamming the session.
|
||||||
|
Nothing changes for an ordinary session.
|
||||||
|
|
||||||
|
**Self-hosters: there is one switch that turns all of it off, and it is the way back.** Settings →
|
||||||
|
Platform → **Agents** → the **Remote desktop** card, where the switch reads "Screens can be watched".
|
||||||
|
The tab used to be called "Agent updates" and is called **Agents** now, renamed because an operator
|
||||||
|
looking for a kill switch had no reason to open it. Do not confuse it with the page also called
|
||||||
|
**Remote desktop** at Settings → Remote desktop: that one is per organisation, holds the consent
|
||||||
|
policy, and cannot stop anything. Turning the instance switch off refuses every new session and ends
|
||||||
|
the ones already
|
||||||
|
running within fifteen seconds, on every machine at once, without touching terminals, agent updates or
|
||||||
|
anything else the agents do. It exists because the api and the web images roll back by digest and an
|
||||||
|
agent release does not: once `0.28.0` is out on your fleet it is out, so the instance needs a way to
|
||||||
|
say no that does not depend on recalling it. Know where it is before you need it — and know that the
|
||||||
|
page is platform-super-admin only, so an organisation's own administrators cannot reach the switch and
|
||||||
|
will have to ask whoever holds that account.
|
||||||
|
|
||||||
|
**Self-hosters: the agents update themselves on the default policy — and on `MANUAL` or `DISABLED`
|
||||||
|
they do not, so there you push the update yourself before there is anything to watch.** Screen capture
|
||||||
|
and input live in `agent 0.28.0`. An older agent connects and works exactly as before; the action on
|
||||||
|
its machine is dimmed and named "Watch the screen — agent too old", read the way described above,
|
||||||
|
rather than opening a session that cannot start. An agent that is merely offline is named that
|
||||||
|
instead — a third distinct label, in the same place and read the same way. Publish the api first,
|
||||||
|
then the web, then the agent — on the bundled compose overlay those are not three steps, since it
|
||||||
|
couples the agent release to the api's start, and the new release lands in its volume first. That is
|
||||||
|
harmless, and not a sign you have done it in the wrong order.
|
||||||
|
|
||||||
|
**Self-hosters: check your reverse proxy before you update.** The api now listens on a **third**
|
||||||
|
WebSocket path on the same `JARVIS_PORT` — `/api/agents/ws/desktop`, beside `/api/agents/ws` and the
|
||||||
|
browser's socket. A proxy that forwards `/api/agents/ws` by **exact path** rather than by prefix will
|
||||||
|
pass the first two and fail this one: terminals keep working, agents stay green, and every attempt to
|
||||||
|
watch a screen fails at once, with the agent naming the URL it could not reach — "the picture
|
||||||
|
connection to wss://…/api/agents/ws/desktop was refused (HTTP 404)", in the agent's log and on the
|
||||||
|
operator's error card, which is the diagnosis handed to you. Match by prefix. Both
|
||||||
|
configurations shipped here already do — the bundled nginx matches `^~ /api/agents/ws`, and the
|
||||||
|
Kubernetes ingress uses `pathType: Prefix` — so a stock deployment needs no change. It is a
|
||||||
|
hand-written proxy in front of one of those that is worth a look.
|
||||||
|
|
||||||
|
**Self-hosters: the schema.** `pg_dump` first, as for any api minor bump. It gains two tables,
|
||||||
|
`DesktopSession` and `DesktopSessionTarget`, two enum types, three columns on an organisation for the
|
||||||
|
consent policy, two columns on an access grant for the switches, and two on the instance's own
|
||||||
|
settings — the kill switch further down, and a timestamp recording that the widening has run.
|
||||||
|
Everything is defaulted and it only adds, so the schema guard has nothing to refuse. No new
|
||||||
|
environment variable, and nothing to run by hand. **On Kubernetes, the schema is applied by the
|
||||||
|
`jarvis-migrate` Job and not by the api pods** — an arrangement that landed in the _previous_ release
|
||||||
|
rather than in this one, so if you maintain your manifests by hand, check you actually have
|
||||||
|
`03-migrate-job.yaml` and `JARVIS_SKIP_MIGRATIONS=true` on the Deployment before you rely on it. **That
|
||||||
|
Job has to finish before the api Deployment, and there is no safe order the other way round.** An api
|
||||||
|
image that reaches a database the Job has not touched may not get as far as listening: its boot seeds
|
||||||
|
the instance settings row, that write now returns the two new columns with it, and on an installation
|
||||||
|
whose row is still missing a public URL, a proxy-hop count or the vault-key timestamp — the last of
|
||||||
|
which is every Kubernetes install made before api 0.114.1 — the column is not there, the boot hook
|
||||||
|
rejects, and the process exits before anything is served. Nothing answers the health check, so the pod
|
||||||
|
fails to come up rather than coming up wrong. Where the api does start, every read of an access grant
|
||||||
|
fails instead: not only watching a screen, but opening a
|
||||||
|
terminal, the asset-scoped tools an assistant may use, and **Settings → Access** itself, because one
|
||||||
|
query now reads the two new columns for all of them. What that failure says is the database's own
|
||||||
|
complaint about a column that does not exist, not a tidy "try again in a moment". Under Argo CD the Job
|
||||||
|
is a Sync hook in wave 0 and runs on its own; applied by hand, run it and wait for it. On docker
|
||||||
|
compose none of that applies — the api container still applies the schema itself before it serves, and
|
||||||
|
refuses to serve at all rather than serving half of it. **Reload the app in every open browser tab
|
||||||
|
once the web image is out**: a tab still running the previous build does not know about the two new
|
||||||
|
switches, and saving an existing grant from it writes them off — an edit that does not mention a
|
||||||
|
switch turns that switch off, which is how a grant edit has always worked. That is also the one thing
|
||||||
|
that quietly undoes the widening above, and nothing puts it back: the pass runs once per instance, so
|
||||||
|
a grant saved from a tab that was open across the deploy loses both switches for good.
|
||||||
|
|
||||||
|
**One caution, stated because it is the kind of thing a changelog should not leave for a support
|
||||||
|
ticket.** The screen is captured — and the keyboard and mouse delivered — by a short-lived helper
|
||||||
|
process that Jarvis starts inside the machine's interactive Windows session, because a Windows service
|
||||||
|
cannot reach another session's screen or press a key into it at all. It runs as the service's own
|
||||||
|
account moved into that session rather than as whoever is sitting there, and it needs nobody to be
|
||||||
|
sitting there: an active console session with no one signed in is an ordinary target, and on a server
|
||||||
|
it is the usual one. It is started and stopped with the Jarvis session — four hours at the outside —
|
||||||
|
and it asks Windows for no privilege the service did not already hold. What it gains is reach rather
|
||||||
|
than privilege, and that is the part to weigh: a system account standing on an interactive desktop can
|
||||||
|
attach to the desktops Windows keeps for its own prompts, and can deliver a click into a window running
|
||||||
|
elevated. The second is deliberate — control that stopped at an administrator's own dialog would be
|
||||||
|
control that does not work — and of the first we take only the sign-in desktop of a machine nobody is
|
||||||
|
signed in to, which is the case this feature exists for. It is also the reason the notice can be drawn
|
||||||
|
where the person will actually see it. On a machine with unusually strict software-restriction policy,
|
||||||
|
that helper is a new executable path your policy has not seen before.
|
||||||
|
|
||||||
## 2026-09-27 — The install's last step no longer strands a first deployment `api 0.116.1` `web 0.141.2`
|
## 2026-09-27 — The install's last step no longer strands a first deployment `api 0.116.1` `web 0.141.2`
|
||||||
|
|
||||||
**The symptom.** On a first installation the wizard could stop on its last step, the vault key
|
**The symptom.** On a first installation the wizard could stop on its last step, the vault key
|
||||||
|
|||||||
@@ -73,6 +73,13 @@ and different accounts on the far end. The choice is made before the shell opens
|
|||||||
the session and in the audit trail, and every route is held to the same limits: the same idle and
|
the session and in the audit trail, and every route is held to the same limits: the same idle and
|
||||||
duration ceilings, the same recording, the same retention policy.
|
duration ceilings, the same recording, the same retention policy.
|
||||||
|
|
||||||
|
**And, on Windows, a screen.** Where the terminal gives you a shell on an enrolled machine, remote
|
||||||
|
desktop gives you what the person sitting at it is looking at. It reuses the terminal's permission
|
||||||
|
model and adds one thing the terminal has no equivalent of: **the person at the machine is always
|
||||||
|
told.** Each organisation chooses a standing banner or a request they must answer, there is no third
|
||||||
|
choice and no way to switch the notice off, and the session ends if the notice cannot be kept in
|
||||||
|
front of them. Nothing of the screen is recorded — see [The agent](#the-agent).
|
||||||
|
|
||||||
**Documents.** A conversation accumulates what the assistant writes: reports and runbooks in
|
**Documents.** A conversation accumulates what the assistant writes: reports and runbooks in
|
||||||
Markdown, diagrams in Mermaid, tabular data as a workbook. What it writes is always a *source*, and
|
Markdown, diagrams in Mermaid, tabular data as a workbook. What it writes is always a *source*, and
|
||||||
Jarvis renders the file on download — PDF and Word from prose, Excel and CSV from a workbook, HTML
|
Jarvis renders the file on download — PDF and Word from prose, Excel and CSV from a workbook, HTML
|
||||||
@@ -290,14 +297,23 @@ marked installed by a backfill — so you will never see the install screen. Not
|
|||||||
|
|
||||||
All three are the kind that produce confusing symptoms rather than clean errors.
|
All three are the kind that produce confusing symptoms rather than clean errors.
|
||||||
|
|
||||||
- **Forward the WebSocket upgrade.** Two separate sockets ride `JARVIS_PORT`: the chat, on
|
- **Forward the WebSocket upgrade.** Three separate sockets ride `JARVIS_PORT`: the chat, on
|
||||||
`/socket.io/`, and enrolled agents, on `/api/agents/ws`. Neither falls back to plain HTTP. Without
|
`/socket.io/`; enrolled agents, on `/api/agents/ws`; and, for remote desktop only, one media
|
||||||
|
connection per live session on `/api/agents/ws/desktop`. None falls back to plain HTTP. Without
|
||||||
the upgrade the chat does not lose streaming — it never connects at all, and since the prompt
|
the upgrade the chat does not lose streaming — it never connects at all, and since the prompt
|
||||||
itself travels over that socket, nothing sends. Presence and in-chat approvals go with it, and no
|
itself travels over that socket, nothing sends. Presence and in-chat approvals go with it, no
|
||||||
agent can connect.
|
agent can connect, and no screen can be watched. A proxy that forwards `/api/agents/ws` by exact
|
||||||
|
path rather than by prefix will pass the first two and silently fail the third, which reads as
|
||||||
|
"remote terminal works and remote desktop does not".
|
||||||
- **Give it a long read timeout** — 300s or so. A reasoning model can go 90+ seconds without emitting
|
- **Give it a long read timeout** — 300s or so. A reasoning model can go 90+ seconds without emitting
|
||||||
a byte, and a 60s default cuts the response mid-stream. The client sees a connection reset rather
|
a byte, and a 60s default cuts the response mid-stream. The client sees a connection reset rather
|
||||||
than a timeout, which reads like a bug in Jarvis.
|
than a timeout, which reads like a bug in Jarvis. The desktop media socket is legitimately silent
|
||||||
|
for far longer than that — a viewer whose tab is hidden pauses the stream, and nothing is sent
|
||||||
|
until the tab comes back — but it does not need a longer timeout, because the api pings it on a
|
||||||
|
beat of its own and a WebSocket ping is traffic as far as your proxy's read timer is concerned.
|
||||||
|
A proxy that drops idle **upgraded** connections on a separate, shorter clock is the one
|
||||||
|
configuration that breaks this; on nginx that is `proxy_read_timeout` and the ping already
|
||||||
|
covers it.
|
||||||
- **Let uploads through.** Attaching or uploading a file posts it to `/api/uploads/`, up to 10 MB
|
- **Let uploads through.** Attaching or uploading a file posts it to `/api/uploads/`, up to 10 MB
|
||||||
each, and an image uploaded to a workspace travels with a copy of up to 3 MB prepared for the
|
each, and an image uploaded to a workspace travels with a copy of up to 3 MB prepared for the
|
||||||
model. Your proxy must accept request bodies of at least **13,376 KiB** on that path (nginx:
|
model. Your proxy must accept request bodies of at least **13,376 KiB** on that path (nginx:
|
||||||
@@ -416,16 +432,42 @@ Four things worth knowing about it:
|
|||||||
recorded by default, encrypted under `VAULT_MASTER_KEY` and deleted on a retention policy you set
|
recorded by default, encrypted under `VAULT_MASTER_KEY` and deleted on a retention policy you set
|
||||||
under **Settings → Organization → Terminal sessions**. Turning recording off stops the transcript,
|
under **Settings → Organization → Terminal sessions**. Turning recording off stops the transcript,
|
||||||
never the audit entry.
|
never the audit entry.
|
||||||
|
- **It carries a remote desktop, on Windows.** The same permission model, one level up: a person
|
||||||
|
with the right watches the signed-in session's screen from the browser. **Nobody is ever watched
|
||||||
|
invisibly.** Each organization picks one of two policies under **Settings → Organization → Remote
|
||||||
|
desktop** — a banner that stands on the screen for the whole session, or a prompt the person must
|
||||||
|
answer before anything is captured, which auto-accepts only when nobody is signed in. There is no
|
||||||
|
third option and no way to turn the notice off, which is the one decision in this feature that is
|
||||||
|
not configurable. The session ends the moment the banner cannot be kept visible. Nothing of the
|
||||||
|
screen is stored: there is no recording, no thumbnail and no still, so the audit entry is the
|
||||||
|
whole of the trail. Linux and macOS are not supported yet, and a session opened against one is
|
||||||
|
refused rather than silently downgraded.
|
||||||
|
|
||||||
**What an enrolled agent can do.** Inventory the machine, run commands, read, write and fetch files,
|
**What an enrolled agent can do.** Inventory the machine, run commands, read, write and fetch files,
|
||||||
list and control services, list processes, update itself, and carry that shell. It runs as root on
|
list and control services, list processes, update itself, carry that shell, and — on Windows — show
|
||||||
Linux and macOS and as LocalSystem on Windows, deliberately — its purpose is to administer the
|
its screen. It runs as root on Linux and macOS and as LocalSystem on Windows, deliberately: its
|
||||||
machine.
|
purpose is to administer the machine. Watching a screen is the one thing it cannot do from there,
|
||||||
|
because a Windows service has no access to the window station a person is signed in to; capture runs
|
||||||
|
in a short-lived helper the service launches into that session, and that helper is also what draws
|
||||||
|
the notice.
|
||||||
|
|
||||||
What the **assistant** does with that reach is bounded by the autonomy policy and the approval gates
|
What the **assistant** does with that reach is bounded by the autonomy policy and the approval gates
|
||||||
above. **The interactive shell is not.** There is no command to inspect before a shell opens, so the
|
above. **The interactive shell and the remote desktop are not.** There is no command to inspect
|
||||||
risk ceiling has nothing to weigh; it is gated instead by a permission and a per-machine switch on
|
before a shell opens and no command at all behind a screen, so the risk ceiling has nothing to
|
||||||
the grant. Decide who holds those before enrolling anything you care about.
|
weigh; both are gated instead by a permission and a per-machine switch on the grant, and neither is
|
||||||
|
ever opened by the assistant — a person opens them. Decide who holds those before enrolling anything
|
||||||
|
you care about. The desktop has one protection the shell does not, and it is not a permission: the
|
||||||
|
person sitting at the machine is told, every time, for as long as it lasts.
|
||||||
|
|
||||||
|
**Being told is not the same as being able to stop it, and the difference matters.** The notice is
|
||||||
|
click-through on purpose — it takes no click, it has no menu and it has no keyboard — so that it can
|
||||||
|
never sit between somebody and the work they are doing, and so that it cannot be closed by accident
|
||||||
|
or by a script. The cost of that choice is that the person at the keyboard cannot dismiss the viewer
|
||||||
|
from the notice itself. Their ways out are elsewhere: refusing the prompt under the ask policy,
|
||||||
|
before anything is captured; signing out, which ends the session with it; or an administrator
|
||||||
|
turning the feature off for the instance. What the banner guarantees is that watching is never
|
||||||
|
secret. It does not guarantee consent once it has started, and under the banner policy it is not
|
||||||
|
asking for any.
|
||||||
|
|
||||||
## Things worth knowing before you trust it with production
|
## Things worth knowing before you trust it with production
|
||||||
|
|
||||||
@@ -468,6 +510,23 @@ These are deliberate and documented rather than surprises waiting to be found.
|
|||||||
something to watch rather than something to assume. Two API processes have been run together
|
something to watch rather than something to assume. Two API processes have been run together
|
||||||
against one database and one Redis, and the manifests are published, but that is not the same
|
against one database and one Redis, and the manifests are published, but that is not the same
|
||||||
thing as your machines depending on it.
|
thing as your machines depending on it.
|
||||||
|
- **Remote desktop is the newest thing here, and its way out is a switch rather than a rollback.**
|
||||||
|
The api and the web roll back by pinning a digest, but an agent has already replaced its own binary
|
||||||
|
on every machine it runs on by the time you want it back — so the control that matters is
|
||||||
|
**Settings → Platform → Agents → Remote desktop**. Turning it off refuses new sessions and ends
|
||||||
|
every live one within 15 seconds, fleet-wide, without touching the interactive shell or anything
|
||||||
|
else the agent does. Know where it is before you need it. Two consequences worth holding onto: no
|
||||||
|
part of a screen is ever written anywhere, so if an incident needs a record of what was *seen*,
|
||||||
|
this feature cannot produce one after the fact. What the session record does hold is more than a
|
||||||
|
timestamp — who watched, which machine, which Windows session and account, which monitor, which
|
||||||
|
consent policy applied, whether consent was agreed and when, whether it was auto-accepted because
|
||||||
|
nobody was signed in to be asked, whether control was allowed, how many input events were accepted
|
||||||
|
and how many were dropped, and how it ended — so write a staff disclosure from that list rather
|
||||||
|
than from "no recording". Note what is *not* on it: there is deliberately no column saying the
|
||||||
|
banner was drawn. The banner is drawn by the agent on the far side of a websocket and the server
|
||||||
|
never learns whether it went up, so a field answering "was it shown" could only ever hold a guess;
|
||||||
|
what the row holds instead is which policy the session ran under. And it is Windows-only; a session opened against Linux or macOS is
|
||||||
|
refused outright rather than quietly degraded into something weaker.
|
||||||
- **There is no rate limiting on sign-in.** No throttler, no account lockout, and authentication
|
- **There is no rate limiting on sign-in.** No throttler, no account lockout, and authentication
|
||||||
events are not audited. Credential stuffing is bounded only by the reverse proxy you put in front,
|
events are not audited. Credential stuffing is bounded only by the reverse proxy you put in front,
|
||||||
which this repository does not ship. If your proxy can rate-limit one route, make it that one.
|
which this repository does not ship. If your proxy can rate-limit one route, make it that one.
|
||||||
@@ -544,6 +603,8 @@ wrong on the screen that asks for it.
|
|||||||
| An answer dies part-way through, every time | The proxy's read timeout is too short. |
|
| An answer dies part-way through, every time | The proxy's read timeout is too short. |
|
||||||
| Everything works but one kind of thing cannot be created | A limit is full — the community edition's, or your licence's. The message names which. See below. |
|
| Everything works but one kind of thing cannot be created | A limit is full — the community edition's, or your licence's. The message names which. See below. |
|
||||||
| An agent installer answers 503 | No agent build is published — the overlay is off. That is a supported state. |
|
| An agent installer answers 503 | No agent build is published — the overlay is off. That is a supported state. |
|
||||||
|
| The remote terminal works and remote desktop never connects | The proxy forwards `/api/agents/ws` but not the paths under it. The media socket is `/api/agents/ws/desktop` and needs the upgrade too. |
|
||||||
|
| A remote desktop session opens and ends immediately | Almost always the notice. It ends by design if the banner cannot be kept on top, if the person refuses, or if nobody answers the request within the minute. The end reason on the session says which. |
|
||||||
|
|
||||||
The api takes about a minute on first boot, syncing the schema before it listens. `docker compose ps`
|
The api takes about a minute on first boot, syncing the schema before it listens. `docker compose ps`
|
||||||
showing `health: starting` for that long is expected, not a fault.
|
showing `health: starting` for that long is expected, not a fault.
|
||||||
|
|||||||
Reference in New Issue
Block a user