openvibers/openvibe.link Commits

0.32.1: browser on a desk reaches the plugin with desks on a desk host that offers no browser of its own (a shared desk host), as screen and input already did (#81)

OP OpenVibers committed 16 days ago · d53f2e3af9 · 6 files, +13 −10

Changes

ddiff --git a/STATUS.json b/STATUS.json
iindex e20cf6c..0cb19c7 100644
---- a/STATUS.json
++++ b/STATUS.json
@@@ -5,8 +5,8 @@
"wave": "T15",
"plan": "Plan track T15 (Bot complete) and ADR-043 (Bot: devices, pairing, control and safety); the computer plugin and the job worker serve OpenVibe.Actor and the Codes job runner (tracks T14/T17)",
"kind": "client",
-- "version": "0.32.0",
-- "release": "v0.32.0, published 2026-10-28 as a GitHub release (desk hosts for other people, the Desks plan's step D5 completed: install.sh --desk-host --shared makes a host whose browser desks each run in the job worker's sandbox, through the new openvibe-node desk-sandbox: its own namespaces and cgroup as the unprivileged openvibe-job, its own memory, CPU, process and disk limits (desk_memory_mb, desk_cpu_millis, desk_pids, desk_disk_mb), its profile and downloads in its private /tmp, the public internet only, enforced by the kernel; the computer plugin's desk_isolation sandbox offers browser desks only and nothing of the machine (no programs, files, probes, its own screen or browser, whole desks, kept profiles or live view); proven in CI as root with a real Chrome driven over its pipe inside the sandbox; the core gives plugins OPENVIBE_NODE_CONFIG and OPENVIBE_NODE_EXE; v0.31.0: every bundled plugin is tested on Windows and macOS too, and the ai plugin at all; what that found is fixed: on a Mac a browser desk's ctrl or cmd editing shortcuts (select all, copy, paste, cut, undo, redo) work, sent with the command Chrome needs there, and a desk's temp folder moves under /tmp when Chrome's SingletonSocket would not fit macOS's 104-byte socket path, so a Mac desk's browser starts; on Windows the computer plugin keeps its data in %LOCALAPPDATA%\\openvibe-node (an existing ~/.local/share folder is kept) with normal paths; v0.30.0: Windows keeps the original deadline across the pipe too: the core measures every deadline on one precise clock shared with its plugins, CLOCK_MONOTONIC on Linux and macOS and the raw QueryPerformanceCounter on Windows, named in hello as shared_clock, and passes the instant a command expires on it; the Python runtime reads the same raw counter with the same integer conversion; strict timing (Plugin.strict_timing, OPENVIBE_STRICT_TIMING=1, or strict_timing in the Node's plugin config) refuses a motion command whose original expiry cannot be checked with timing_unavailable instead of running it on deadline_ms; the deadline tests run a real Python plugin over real pipes on Linux, macOS and Windows; looi runs as python -m openvibe_looi without a command; v0.29.0: the ai plugin says how fast each model answers here: ai_tokens_per_s for the lastthe ai plugin says how fast each model answers here: ai_tokens_per_s for the last model, averaged with the earlier answers, and ai_busy while one answers, shown beside the machine's models on openvibe.bot; v0.28.0: install.sh --ai sets up your own models: it adds the ai plugin, which a driver chosen later keeps, says which model server answers (Ollama, LM Studio, llama.cpp) or how to get one, and --ai-endpoint names another port; v0.27.0: your own models for your OpenVibe work: the ai plugin relays kind ai (contracts 0.219.0), one chat completion, to a local OpenAI-compatible model server (Ollama, LM Studio, llama.cpp), lists its models, runs one at a time, and reaches nothing else; the core routes ai to it like a computer command that never acts; v0.26.0: programs on a desk: exec takes desk, running in that desk's session so a program's windows open there, and detach, starting a program that keeps running on a whole desk (an office suite, a terminal) and ends with its desk; desk templates follow: research, office and dev desks; v0.25.0: the egress guard: every connection every agent browser makes, a page's own scripts included, goes through a proxy the plugin runs on 127.0.0.1 that resolves, checks and connects to the address it checked, so a page never reaches the machine's own network below level full; browser_egress: open turns it off; v0.24.1: a kept profile carries session cookies too: Chrome drops cookies that end with the browser as it quits, and a sign-in is often one, so a desk keeps them beside the profile and the next desk's browser gets them back as it starts, found by the production proof; v0.24.0: kept desk profiles: desk start takes profile, so a desk's browser starts from the person's kept profile (sign in once, on any desk) and saves it back when it stops, closing the browser gracefully so its cookies are written, the last desk to stop wins, caches never kept, and each desk's own copy removed; the browser follows a page its page opens (a sign-in popup, a new tab) and comes back when it closes; the agent's clicks count as a person's, so sites open their sign-in popups; v0.23.0: browser desks, the Desks plan's step D2: desk start takes kind browser, a headless browser alone with no display at about half the cost of a whole desk, on Linux or macOS; its page is its screen, mouse and keyboard and live view over the browser's pipe, exactly the desk's size; a desk host, which has no screen or input of its own, gets screen, screen_stream and input on its desks; a click on a named desk scales to that desk's size; v0.22.0: desks on a desk host, found by the first production proof: a desk's temp folder is short (Chrome's SingletonSocket must fit a Unix socket path, and the desks folder on a desk host was too deep, so Chrome closed as it started), and a desk no command used for desk_idle_s (15 minutes) stops by itself, so a desk an agent left behind when it crashed or restarted ends; v0.21.0: element refs and receipts, the Desks plan's step D3: the agent's browser lists a page's interactive elements as compact refs (e7 button \"Sign in\"), kept on the element while it stays, and click, press and back answer with what changed (url, lines that appeared, lines gone), so agents act on the page's structure before screenshots; v0.20.0: desk hosts, the Desks plan's step D5: install.sh --desk-host turns an old laptop, a mini PC or a spare workstation into a host of desks for its owner's agents, run as its own openvibe-desk account with Xvfb, openbox, D-Bus, xdotool, ImageMagick and Chromium installed and max_desks sized to its memory; v0.19.0: several desks per machine, the Desks plan's step D1: the computer plugin runs named desks (desk-<name>, private kept), each with its own X display, D-Bus, browser profile, downloads and temp folder, up to max_desks (one per 2 GiB of memory by default), so two agents work at once without seeing or driving each other's screen; proven with two real Chrome desks that never share a cookie; v0.18.0: a motion command's validity also counts the time it waits in the queue to its plugin and in the pipe: the core fixes the instant it expires at receipt, writes what is left at the moment it writes the line with expires_at_mono_ns on the clock it shares with the plugin, and never writes one that has run out, and the Python runtime refuses one that ran out in the pipe before handle(); the clock bound is kept in nanoseconds and counts Bot's whole-millisecond stamps, rounded up from nanoseconds; a Node booting latched sets the e-stop on its plugins before starting them; the Python SDK package reports the Node release; v0.17.0: deadlines are checked against the latest the server's clock can be: the estimate plus its bound, the connection's fastest round trip, and motion waits for that bound on every new connection; every stop and estop names its cause, hello carries the newest halt epoch, and the Python SDK gives drivers on_stop(StopInfo) and on_resume to tell an ordinary stop from an emergency without re-arming anything; v0.16.1: the Python runtime refuses a motion command whose deadline passed while it waited behind a slow handler, so a driver never moves on a stale command; plugins.md answers where deadlines come from, re-stamping, order across a change of driver, stop versus estop, and that nothing is held past its validity; v0.16.0: openvibe-node quit ends a running node cleanly with nothing latched, for launchers; each driver command carries its provenance (link session, seq, Bot send time and deadline) and a halt's stop its turn_epoch, and the Python runtime refuses a replay on a session and a command from an ended turn; the no-redelivery contract written down in plugins.md): binaries for linux amd64/arm64/armv7, darwin amd64/arm64 and windows amd64, the plugins tarball, install.sh and SHA256SUMS",
++ "version": "0.32.1",
++ "release": "v0.32.1, published 2026-10-28 as a GitHub release (a browser command on a desk reaches the plugin with desks on a desk host for other people, which offers no browser of its own, as screen and input already did, found by OpenVibe.Bot's desk lease test; v0.32.0: desk hosts for other people, the Desks plan's step D5 completed: install.sh --desk-host --shared makes a host whose browser desks each run in the job worker's sandbox, through the new openvibe-node desk-sandbox: its own namespaces and cgroup as the unprivileged openvibe-job, its own memory, CPU, process and disk limits (desk_memory_mb, desk_cpu_millis, desk_pids, desk_disk_mb), its profile and downloads in its private /tmp, the public internet only, enforced by the kernel; the computer plugin's desk_isolation sandbox offers browser desks only and nothing of the machine (no programs, files, probes, its own screen or browser, whole desks, kept profiles or live view); proven in CI as root with a real Chrome driven over its pipe inside the sandbox; the core gives plugins OPENVIBE_NODE_CONFIG and OPENVIBE_NODE_EXE; v0.31.0: every bundled plugin is tested on Windows and macOS too, and the ai plugin at all; what that found is fixed: on a Mac a browser desk's ctrl or cmd editing shortcuts (select all, copy, paste, cut, undo, redo) work, sent with the command Chrome needs there, and a desk's temp folder moves under /tmp when Chrome's SingletonSocket would not fit macOS's 104-byte socket path, so a Mac desk's browser starts; on Windows the computer plugin keeps its data in %LOCALAPPDATA%\\openvibe-node (an existing ~/.local/share folder is kept) with normal paths; v0.30.0: Windows keeps the original deadline across the pipe too: the core measures every deadline on one precise clock shared with its plugins, CLOCK_MONOTONIC on Linux and macOS and the raw QueryPerformanceCounter on Windows, named in hello as shared_clock, and passes the instant a command expires on it; the Python runtime reads the same raw counter with the same integer conversion; strict timing (Plugin.strict_timing, OPENVIBE_STRICT_TIMING=1, or strict_timing in the Node's plugin config) refuses a motion command whose original expiry cannot be checked with timing_unavailable instead of running it on deadline_ms; the deadline tests run a real Python plugin over real pipes on Linux, macOS and Windows; looi runs as python -m openvibe_looi without a command; v0.29.0: the ai plugin says how fast each model answers here: ai_tokens_per_s for the lastthe ai plugin says how fast each model answers here: ai_tokens_per_s for the last model, averaged with the earlier answers, and ai_busy while one answers, shown beside the machine's models on openvibe.bot; v0.28.0: install.sh --ai sets up your own models: it adds the ai plugin, which a driver chosen later keeps, says which model server answers (Ollama, LM Studio, llama.cpp) or how to get one, and --ai-endpoint names another port; v0.27.0: your own models for your OpenVibe work: the ai plugin relays kind ai (contracts 0.219.0), one chat completion, to a local OpenAI-compatible model server (Ollama, LM Studio, llama.cpp), lists its models, runs one at a time, and reaches nothing else; the core routes ai to it like a computer command that never acts; v0.26.0: programs on a desk: exec takes desk, running in that desk's session so a program's windows open there, and detach, starting a program that keeps running on a whole desk (an office suite, a terminal) and ends with its desk; desk templates follow: research, office and dev desks; v0.25.0: the egress guard: every connection every agent browser makes, a page's own scripts included, goes through a proxy the plugin runs on 127.0.0.1 that resolves, checks and connects to the address it checked, so a page never reaches the machine's own network below level full; browser_egress: open turns it off; v0.24.1: a kept profile carries session cookies too: Chrome drops cookies that end with the browser as it quits, and a sign-in is often one, so a desk keeps them beside the profile and the next desk's browser gets them back as it starts, found by the production proof; v0.24.0: kept desk profiles: desk start takes profile, so a desk's browser starts from the person's kept profile (sign in once, on any desk) and saves it back when it stops, closing the browser gracefully so its cookies are written, the last desk to stop wins, caches never kept, and each desk's own copy removed; the browser follows a page its page opens (a sign-in popup, a new tab) and comes back when it closes; the agent's clicks count as a person's, so sites open their sign-in popups; v0.23.0: browser desks, the Desks plan's step D2: desk start takes kind browser, a headless browser alone with no display at about half the cost of a whole desk, on Linux or macOS; its page is its screen, mouse and keyboard and live view over the browser's pipe, exactly the desk's size; a desk host, which has no screen or input of its own, gets screen, screen_stream and input on its desks; a click on a named desk scales to that desk's size; v0.22.0: desks on a desk host, found by the first production proof: a desk's temp folder is short (Chrome's SingletonSocket must fit a Unix socket path, and the desks folder on a desk host was too deep, so Chrome closed as it started), and a desk no command used for desk_idle_s (15 minutes) stops by itself, so a desk an agent left behind when it crashed or restarted ends; v0.21.0: element refs and receipts, the Desks plan's step D3: the agent's browser lists a page's interactive elements as compact refs (e7 button \"Sign in\"), kept on the element while it stays, and click, press and back answer with what changed (url, lines that appeared, lines gone), so agents act on the page's structure before screenshots; v0.20.0: desk hosts, the Desks plan's step D5: install.sh --desk-host turns an old laptop, a mini PC or a spare workstation into a host of desks for its owner's agents, run as its own openvibe-desk account with Xvfb, openbox, D-Bus, xdotool, ImageMagick and Chromium installed and max_desks sized to its memory; v0.19.0: several desks per machine, the Desks plan's step D1: the computer plugin runs named desks (desk-<name>, private kept), each with its own X display, D-Bus, browser profile, downloads and temp folder, up to max_desks (one per 2 GiB of memory by default), so two agents work at once without seeing or driving each other's screen; proven with two real Chrome desks that never share a cookie; v0.18.0: a motion command's validity also counts the time it waits in the queue to its plugin and in the pipe: the core fixes the instant it expires at receipt, writes what is left at the moment it writes the line with expires_at_mono_ns on the clock it shares with the plugin, and never writes one that has run out, and the Python runtime refuses one that ran out in the pipe before handle(); the clock bound is kept in nanoseconds and counts Bot's whole-millisecond stamps, rounded up from nanoseconds; a Node booting latched sets the e-stop on its plugins before starting them; the Python SDK package reports the Node release; v0.17.0: deadlines are checked against the latest the server's clock can be: the estimate plus its bound, the connection's fastest round trip, and motion waits for that bound on every new connection; every stop and estop names its cause, hello carries the newest halt epoch, and the Python SDK gives drivers on_stop(StopInfo) and on_resume to tell an ordinary stop from an emergency without re-arming anything; v0.16.1: the Python runtime refuses a motion command whose deadline passed while it waited behind a slow handler, so a driver never moves on a stale command; plugins.md answers where deadlines come from, re-stamping, order across a change of driver, stop versus estop, and that nothing is held past its validity; v0.16.0: openvibe-node quit ends a running node cleanly with nothing latched, for launchers; each driver command carries its provenance (link session, seq, Bot send time and deadline) and a halt's stop its turn_epoch, and the Python runtime refuses a replay on a session and a command from an ended turn; the no-redelivery contract written down in plugins.md): binaries for linux amd64/arm64/armv7, darwin amd64/arm64 and windows amd64, the plugins tarball, install.sh and SHA256SUMS",
"runtime": "go 1.24 (one static binary per platform); plugins in Python 3; an ESP32 Arduino library",
"database": "none",
"deployed": false,
ddiff --git a/internal/plugins/manager.go b/internal/plugins/manager.go
iindex 873b4a7..d08c3b6 100644
---- a/internal/plugins/manager.go
++++ b/internal/plugins/manager.go
@@@ -31,7 +31,9 @@ var capabilityFor = map[string]string{
// deskKinds are the kinds a plugin's desks take too. A desk host (install.sh --desk-host) has no screen, mouse or
// keyboard of its own, only its desks', so its computer plugin offers no "screen" or "input": these kinds then reach
// the plugin that offers "desk", which acts on the desk a command names and refuses the machine's own screen itself.
--var deskKinds = map[string]bool{protocol.KindScreen: true, protocol.KindScreenStream: true, protocol.KindInput: true}
++// A desk host for other people (desk_isolation sandbox, v0.32) offers no browser of its own either: "browser" on one
++// of its desks reaches the plugin the same way.
++var deskKinds = map[string]bool{protocol.KindScreen: true, protocol.KindScreenStream: true, protocol.KindInput: true, protocol.KindBrowser: true}
// Manager supervises every configured plugin.
type Manager struct {
ddiff --git a/internal/plugins/plugins_test.go b/internal/plugins/plugins_test.go
iindex ed30bc6..3e15eac 100644
---- a/internal/plugins/plugins_test.go
++++ b/internal/plugins/plugins_test.go
@@@ -272,17 +272,18 @@ func TestRouting(t *testing.T) {
}
}
--// A desk host offers no screen or input of its own, only its desks': screen, screen_stream and input still reach the
--// plugin with desks (it acts on the desk named, or refuses the machine's own screen); other kinds are not routed there.
++// A desk host offers no screen, input or browser of its own, only its desks': screen, screen_stream, input and browser
++// still reach the plugin with desks (it acts on the desk named, or refuses the machine's own); other kinds are not routed
++// there.
func TestRoutingOnADeskHost(t *testing.T) {
fx := start(t, "deskhost")
fx.waitState(StateReady)
-- for _, kind := range []string{"screen", "screen_stream", "input", "desk", "fs"} {
++ for _, kind := range []string{"screen", "screen_stream", "input", "browser", "desk", "fs"} {
if fx.m.Route(kind, "", "") == nil {
t.Fatalf("%s not routed on a desk host", kind)
}
}
-- for _, kind := range []string{"exec", "browser", "drive"} {
++ for _, kind := range []string{"exec", "drive"} {
if fx.m.Route(kind, "", "") != nil {
t.Fatalf("%s routed to a plugin that does not offer it", kind)
}
ddiff --git a/plugins/sdk/openvibe_plugin/__init__.py b/plugins/sdk/openvibe_plugin/__init__.py
iindex d52125b..9ed9fce 100644
---- a/plugins/sdk/openvibe_plugin/__init__.py
++++ b/plugins/sdk/openvibe_plugin/__init__.py
@@@ -24,7 +24,7 @@ __all__ = ["Plugin", "Fault", "Deferred", "Command", "StopInfo", "Runtime", "run
"RUNTIME_CAUSES", "SHARED_CLOCK", "SHARED_CLOCK_NAME", "TIMING_UNAVAILABLE", "__version__"]
# The Node release this runtime ships with (pyproject.toml carries the same).
--__version__ = "0.32.0"
++__version__ = "0.32.1"
PROTOCOL_VERSION = 1
HEARTBEAT_TIMEOUT_S = 1.0
DEFAULT_DEADLINE_MS = 300
ddiff --git a/plugins/sdk/pyproject.toml b/plugins/sdk/pyproject.toml
iindex 8c05b54..ce45065 100644
---- a/plugins/sdk/pyproject.toml
++++ b/plugins/sdk/pyproject.toml
@@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "openvibe-plugin"
--version = "0.32.0"
++version = "0.32.1"
description = "Runtime for OpenVibe Node plugins (JSON lines over stdin/stdout, deadlines, heartbeat, e-stop)"
requires-python = ">=3.9"
license = { text = "AGPL-3.0-or-later" }
ddiff --git a/plugins/sdk/tests/test_runtime.py b/plugins/sdk/tests/test_runtime.py
iindex 17d83e8..f9b9f63 100644
---- a/plugins/sdk/tests/test_runtime.py
++++ b/plugins/sdk/tests/test_runtime.py
@@@ -439,7 +439,7 @@ class RuntimeTest(unittest.TestCase):
self.assertEqual(rt.stop_infos[-1].halt_epoch, 1792900000500)
def test_the_package_reports_the_node_release(self):
-- self.assertEqual(openvibe_plugin.__version__, "0.32.0")
++ self.assertEqual(openvibe_plugin.__version__, "0.32.1")
with open(os.path.join(os.path.dirname(HERE), "pyproject.toml")) as f:
self.assertIn('version = "%s"' % openvibe_plugin.__version__, f.read())