Defer Office desktop startup

Make the Office canvas mount passive so Xpra starts only when the Desktop surface is opened or an official Office document is created/opened. Track Desktop host visibility to unload hidden frames, stop monitors, dedupe viewport resize work, and set Xpra offscreen mode according to HTTPS support. Add a near-future note for the tunnel memory footprint. Show Office desktop startup progress Display a loading message while the Agent Zero Desktop environment is starting or restarting, so the right-canvas Desktop button gives immediate feedback before Xpra finishes waking up.

Alessandro committed May 3, 2026 at 02:57 UTC 2d389af7279ae0b51c878eac6869899e7ded1bce
8 files changed +162 -48
docs/agents/office-tunnel-memory-note.md new
+20
@@ -0,0 +1,20 @@
1 +# Office Tunnel Memory Footprint Note
2 +
3 +During the ARM Desktop audit on 2026-05-03, `run_tunnel.py` was the clearest near-future memory optimization candidate. The process held roughly 573 MiB PSS while the main UI process held roughly 706 MiB PSS, even though the tunnel should mostly be a lightweight network edge.
4 +
5 +## Observed Shape
6 +
7 +- `run_tunnel.py` imports enough of the framework stack to pull in heavy provider and API dependencies.
8 +- The tunnel stays resident for the life of the container, so every eagerly imported module becomes steady-state memory.
9 +- The Desktop/Xpra service itself was not the largest outlier; the always-on tunnel process was.
10 +
11 +## Future Work
12 +
13 +- Split tunnel startup into a small import surface that only loads routing, auth, and socket plumbing required for health and proxy operation.
14 +- Lazy-import provider/framework modules only when a tunnel request truly needs them.
15 +- Review any `ApiHandler` or helper imports used by the tunnel path and replace broad framework imports with narrower functions.
16 +- Measure with `smem -P run_tunnel.py`, `/proc/<pid>/smaps_rollup`, and before/after cold-start RSS/PSS on ARM64.
17 +
18 +## Success Signal
19 +
20 +The tunnel process should remain useful as an always-on edge while dropping its idle PSS substantially below the main UI process. A good first target is under 250 MiB PSS on ARM64 without changing tunnel behavior.
helpers/virtual_desktop.py
+1 -1
@@ -131,7 +131,7 @@ def session_url(token: str, *, title: str = "Desktop") -> str:
131 "printing": "true",
132 "file_transfer": "true",
133 "sound": "false",
134 - "offscreen": "false",
134 + "offscreen": "true",
135 "floating_menu": "false",
136 "xpramenu": "false",
137 },
plugins/_office/extensions/python/startup_migration/_20_office_routes.py
+1 -4
@@ -3,7 +3,7 @@ from __future__ import annotations
3 from helpers.extension import Extension
4 from helpers.print_style import PrintStyle
5 from plugins._office import hooks
6 -from plugins._office.helpers import libreoffice_desktop, libreoffice_desktop_routes
6 +from plugins._office.helpers import libreoffice_desktop_routes
7
8
9 class OfficeStartupCleanup(Extension):
@@ -14,6 +14,3 @@ class OfficeStartupCleanup(Extension):
14 PrintStyle.warning("Office runtime preparation reported errors:", result["errors"])
15 elif result.get("installed") or result.get("removed"):
16 PrintStyle.info("Office runtime prepared:", result)
17 - desktop = libreoffice_desktop.get_manager().ensure_system_desktop()
18 - if not desktop.get("available"):
19 - PrintStyle.warning("Office desktop startup was deferred:", desktop.get("error") or desktop)
plugins/_office/extensions/webui/right-canvas-panels/office-panel.html
+1
@@ -2,6 +2,7 @@
2 class="right-canvas-surface-panel office-canvas-surface"
3 data-surface-id="office"
4 x-show="$store.rightCanvas && $store.rightCanvas.isOpen && $store.rightCanvas.activeSurfaceId === 'office'"
5 + x-effect="$store.office?.setDesktopHostVisible?.($store.rightCanvas && $store.rightCanvas.isOpen && $store.rightCanvas.activeSurfaceId === 'office')"
6 style="display: none;"
7 >
8 <x-component path="/plugins/_office/webui/office-panel.html"></x-component>
plugins/_office/helpers/libreoffice_desktop.py
+5 -3
@@ -271,21 +271,23 @@ class LibreOfficeDesktopManager:
271 session = self.get(session_id)
272 if not session:
273 return {"ok": False, "error": "LibreOffice desktop session not found."}
274 + is_system_desktop = session.session_id == SYSTEM_SESSION_ID and session.extension == "desktop"
275 result = virtual_desktop.resize_display(
276 display=session.display,
277 width=width,
278 height=height,
279 max_width=MAX_SCREEN_WIDTH,
280 max_height=MAX_SCREEN_HEIGHT,
280 - window_class="libreoffice",
281 - keys=("Escape",),
281 + window_class="" if is_system_desktop else "libreoffice",
282 + keys=() if is_system_desktop else ("Escape",),
283 xauthority=self._xauthority(session),
284 home=str(session.profile_dir),
285 )
286 if result.get("ok"):
287 session.width = int(result["width"])
288 session.height = int(result["height"])
288 - self._dismiss_blocking_dialogs(session)
289 + if not is_system_desktop:
290 + self._dismiss_blocking_dialogs(session)
291 return result
292
293 def proxy_for_token(self, token: str) -> tuple[str, int] | None:
plugins/_office/webui/office-store.js
+117 -27
@@ -12,6 +12,7 @@ const SAVE_MESSAGE_MS = 1800;
12 const INPUT_PUSH_DELAY_MS = 650;
13 const DESKTOP_HEARTBEAT_MS = 3500;
14 const DESKTOP_RESIZE_DELAY_MS = 80;
15 +const DESKTOP_START_MESSAGE = "Starting Agent Zero Desktop environment";
16 const XPRA_DESKTOP_PRIME_INTERVAL_MS = 220;
17 const XPRA_DESKTOP_PRIME_ATTEMPTS = 120;
18 const SYSTEM_DESKTOP_FILE_ID = "system-desktop";
@@ -219,8 +220,11 @@ const model = {
220 _desktopResizeTarget: null,
221 _desktopResizeTimer: null,
222 _desktopResizeKey: "",
223 + _desktopResizePendingKey: "",
224 _desktopResizeSuspended: false,
225 _desktopResizePending: false,
226 + _desktopViewportSyncTimers: [],
227 + _desktopHostVisible: false,
228 _desktopPrimeTimer: null,
229 _desktopPrimeAttempts: 0,
230 _desktopKeyboardActive: false,
@@ -237,10 +241,12 @@ const model = {
241 async onMount(element = null, options = {}) {
242 if (element) this._root = element;
243 this._mode = options?.mode === "modal" ? "modal" : "canvas";
240 - if (this._mode === "modal") this.setupFloatingModal(element);
241 - await this.refresh();
242 - await this.ensureDesktopSession({ select: !this.session });
243 - this.ensureActiveTab();
244 + if (this._mode === "modal") {
245 + this._desktopHostVisible = true;
246 + this.setupFloatingModal(element);
247 + await this.onOpen({ source: "modal" });
248 + return;
249 + }
250 this.queueRender();
251 },
252
@@ -259,7 +265,9 @@ const model = {
265 },
266
267 beforeHostHidden(options = {}) {
268 + this._desktopHostVisible = false;
269 this.flushInput();
270 + this.clearDesktopViewportSyncTimers();
271 this.unloadDesktopFrames();
272 },
273
@@ -267,6 +275,7 @@ const model = {
275 this.flushInput();
276 this.stopDesktopMonitor();
277 this.stopDesktopResizeObserver();
278 + this.clearDesktopViewportSyncTimers();
279 this.stopXpraDesktopPrime();
280 this.stopDesktopKeyboardBridge();
281 this.stopDesktopClipboardBridge();
@@ -292,10 +301,23 @@ const model = {
301 this.updateDesktopMonitor();
302 return existing;
303 }
295 - if (this._desktopStarting) return await this._desktopStarting;
304 + const showProgress = options.progress !== false;
305 + const progressMessage = String(options.message || DESKTOP_START_MESSAGE);
306 + if (this._desktopStarting) {
307 + if (showProgress) {
308 + this.loading = true;
309 + this.message = progressMessage;
310 + }
311 + return await this._desktopStarting;
312 + }
313
314 this._desktopStarting = (async () => {
315 try {
316 + if (showProgress) {
317 + this.loading = true;
318 + this.message = progressMessage;
319 + this.error = "";
320 + }
321 const response = await callOffice("desktop");
322 if (response?.ok === false) throw new Error(response.error || "Desktop session could not be opened.");
323 const session = normalizeSession(response);
@@ -327,6 +349,10 @@ const model = {
349 this.error = error instanceof Error ? error.message : String(error);
350 return null;
351 } finally {
352 + if (showProgress) {
353 + this.loading = false;
354 + if (this.message === progressMessage) this.message = "";
355 + }
356 this._desktopStarting = null;
357 }
358 })();
@@ -493,7 +519,7 @@ const model = {
519 this.ensureActiveTab();
520 }
521 this.updateDesktopMonitor();
496 - await this.ensureDesktopSession({ select: !this.session });
522 + this.ensureActiveTab();
523 await this.refresh();
524 },
525
@@ -836,7 +862,36 @@ const model = {
862 },
863
864 officialOfficeUrl(tab = this.session) {
839 - return tab?.desktop?.url || "";
865 + const url = tab?.desktop?.url || "";
866 + if (!url) return "";
867 + try {
868 + const parsed = new URL(url, window.location.href);
869 + const secureContext = globalThis.isSecureContext === true;
870 + parsed.searchParams.set("offscreen", secureContext ? "true" : "false");
871 + parsed.searchParams.set("clipboard_poll", secureContext ? "true" : "false");
872 + if (parsed.origin === window.location.origin) return `${parsed.pathname}${parsed.search}${parsed.hash}`;
873 + return parsed.href;
874 + } catch {
875 + return url;
876 + }
877 + },
878 +
879 + isDesktopHostVisible() {
880 + if (this._mode === "modal") return true;
881 + const canvas = globalThis.Alpine?.store?.("rightCanvas") || rightCanvasStore;
882 + return Boolean(canvas?.isOpen && canvas.activeSurfaceId === "office");
883 + },
884 +
885 + setDesktopHostVisible(visible) {
886 + const next = Boolean(visible);
887 + if (!next && this._mode === "modal") return;
888 + if (this._desktopHostVisible === next) return;
889 + this._desktopHostVisible = next;
890 + if (next) {
891 + this.afterDesktopHostShown({ source: "canvas-visibility" });
892 + } else {
893 + this.beforeHostHidden({ reason: "hidden" });
894 + }
895 },
896
897 desktopFrames() {
@@ -879,6 +934,7 @@ const model = {
934 },
935
936 restoreDesktopFrames() {
937 + if (!this.isDesktopHostVisible()) return;
938 const url = this.officialOfficeUrl();
939 if (!url) return;
940 for (const frame of this.desktopFrames()) {
@@ -892,22 +948,21 @@ const model = {
948
949 afterDesktopHostShown() {
950 if (!this.hasOfficialOffice()) return;
951 + this._desktopHostVisible = true;
952 this._desktopResizeKey = "";
953 + this._desktopResizePendingKey = "";
954 this._desktopResizeSuspended = false;
955 this._desktopResizePending = false;
956 this.restoreDesktopFrames();
957 this.requestDesktopViewportSync({ force: true, frame: this.desktopFrame() });
900 - for (const delay of [720, 1280]) {
901 - globalThis.setTimeout(() => {
902 - this.requestDesktopViewportSync({ force: true, frame: this.desktopFrame() });
903 - }, delay);
904 - }
958 },
959
960 beforeDesktopHostHandoff() {
961 this.stopDesktopResizeObserver();
962 + this.clearDesktopViewportSyncTimers();
963 this.stopXpraDesktopPrime();
964 this._desktopResizeKey = "";
965 + this._desktopResizePendingKey = "";
966 this._desktopResizeSuspended = true;
967 this._desktopResizePending = true;
968 },
@@ -920,6 +975,7 @@ const model = {
975
976 onDesktopFrameLoaded(event = null) {
977 if (event?.target?.getAttribute?.("src") === "about:blank") return;
978 + if (!this.isDesktopHostVisible()) return;
979 this.error = "";
980 this.queueDesktopFrameFocus(event?.target || null);
981 this.requestDesktopViewportSync({ force: true, frame: event?.target || null });
@@ -955,7 +1011,7 @@ const model = {
1011 },
1012
1013 updateDesktopMonitor() {
958 - if (!this.hasOfficialOffice()) {
1014 + if (!this.hasOfficialOffice() || !this.isDesktopHostVisible()) {
1015 this.stopDesktopMonitor();
1016 this.stopDesktopResizeObserver();
1017 this._desktopKeyboardActive = false;
@@ -975,7 +1031,7 @@ const model = {
1031 },
1032
1033 startDesktopResizeObserver() {
978 - if (!this.hasOfficialOffice()) {
1034 + if (!this.hasOfficialOffice() || !this.isDesktopHostVisible()) {
1035 this.stopDesktopResizeObserver();
1036 return;
1037 }
@@ -1017,6 +1073,7 @@ const model = {
1073 this._desktopResizeCleanup = null;
1074 this._desktopResizeTarget = null;
1075 this._desktopResizeKey = "";
1076 + this._desktopResizePendingKey = "";
1077 this._desktopResizeSuspended = false;
1078 this._desktopResizePending = false;
1079 },
@@ -1027,6 +1084,7 @@ const model = {
1084 globalThis.clearTimeout(this._desktopResizeTimer);
1085 this._desktopResizeTimer = null;
1086 }
1087 + this._desktopResizePendingKey = "";
1088 },
1089
1090 resumeDesktopResize() {
@@ -1046,7 +1104,15 @@ const model = {
1104 );
1105 },
1106
1107 + clearDesktopViewportSyncTimers() {
1108 + for (const timer of this._desktopViewportSyncTimers.splice(0)) {
1109 + globalThis.clearTimeout(timer);
1110 + }
1111 + },
1112 +
1113 requestDesktopViewportSync(options = {}) {
1114 + if (!this.hasOfficialOffice() || !this.isDesktopHostVisible()) return;
1115 + if (options.force) this.clearDesktopViewportSyncTimers();
1116 const run = (force = false) => {
1117 this.syncDesktopViewport({ ...options, force });
1118 };
@@ -1055,13 +1121,16 @@ const model = {
1121 } else {
1122 globalThis.setTimeout(() => run(Boolean(options.force)), 0);
1123 }
1058 - for (const delay of [140, 420]) {
1059 - globalThis.setTimeout(() => run(false), delay);
1060 - }
1124 + if (options.followup === false) return;
1125 + const timer = globalThis.setTimeout(() => {
1126 + this._desktopViewportSyncTimers = this._desktopViewportSyncTimers.filter((item) => item !== timer);
1127 + run(false);
1128 + }, options.force ? 260 : 180);
1129 + this._desktopViewportSyncTimers.push(timer);
1130 },
1131
1132 syncDesktopViewport(options = {}) {
1064 - if (!this.hasOfficialOffice()) return false;
1133 + if (!this.hasOfficialOffice() || !this.isDesktopHostVisible()) return false;
1134 const frame = this.desktopFrame(options.frame || null);
1135 if (!frame) return false;
1136 this.startDesktopResizeObserver();
@@ -1554,7 +1623,7 @@ const model = {
1623 },
1624
1625 queueDesktopResize(options = {}) {
1557 - if (!this.hasOfficialOffice()) return;
1626 + if (!this.hasOfficialOffice() || !this.isDesktopHostVisible()) return;
1627 const token = this.session?.desktop?.token || "";
1628 const frame = this.desktopFrame(options.frame || null);
1629 const target = frame?.parentElement || frame;
@@ -1565,18 +1634,33 @@ const model = {
1634 const width = Math.round(rect.width);
1635 const height = Math.round(rect.height);
1636 if (width < 320 || height < 220) return;
1568 - this.applyXpraDesktopFrameMode(frame, { requestServerResize: false, requestRefresh: false });
1637 + const key = `${token}:${width}x${height}`;
1638 + const refreshFrameOnly = () => {
1639 + this.applyXpraDesktopFrameMode(frame, { requestServerResize: false, requestRefresh: false });
1640 + };
1641 + if (!serverResize) {
1642 + refreshFrameOnly();
1643 + return;
1644 + }
1645 + if (key === this._desktopResizeKey || key === this._desktopResizePendingKey) {
1646 + refreshFrameOnly();
1647 + return;
1648 + }
1649 + refreshFrameOnly();
1650 if (!force && this.shouldDeferDesktopResize()) {
1651 this._desktopResizePending = true;
1652 return;
1653 }
1573 - const key = `${token}:${width}x${height}`;
1574 - if (!serverResize) return;
1575 - if (!force && key === this._desktopResizeKey) return;
1654 if (this._desktopResizeTimer) globalThis.clearTimeout(this._desktopResizeTimer);
1655 + this._desktopResizePendingKey = key;
1656 this._desktopResizeTimer = globalThis.setTimeout(async () => {
1657 this._desktopResizeTimer = null;
1658 + if (!this.hasOfficialOffice() || !this.isDesktopHostVisible()) {
1659 + if (this._desktopResizePendingKey === key) this._desktopResizePendingKey = "";
1660 + return;
1661 + }
1662 if (!force && this.shouldDeferDesktopResize()) {
1663 + if (this._desktopResizePendingKey === key) this._desktopResizePendingKey = "";
1664 this._desktopResizePending = true;
1665 return;
1666 }
@@ -1603,6 +1687,8 @@ const model = {
1687 }
1688 } catch (error) {
1689 console.warn("Desktop resize skipped", error);
1690 + } finally {
1691 + if (this._desktopResizePendingKey === key) this._desktopResizePendingKey = "";
1692 }
1693 }, DESKTOP_RESIZE_DELAY_MS);
1694 },
@@ -1711,7 +1797,7 @@ const model = {
1797
1798 startDesktopMonitor() {
1799 this.stopDesktopMonitor();
1714 - if (!this.hasOfficialOffice()) return;
1800 + if (!this.hasOfficialOffice() || !this.isDesktopHostVisible()) return;
1801 const tabId = this.session?.tab_id || "";
1802 const sessionId = this.session?.desktop_session_id || this.session?.session_id || "";
1803 if (!tabId || !sessionId) return;
@@ -1720,7 +1806,7 @@ const model = {
1806 this._desktopHeartbeatMisses = 0;
1807
1808 const tick = async () => {
1723 - if (!this.session || this.session.tab_id !== tabId || !this.hasOfficialOffice()) return;
1809 + if (!this.session || this.session.tab_id !== tabId || !this.hasOfficialOffice() || !this.isDesktopHostVisible()) return;
1810 try {
1811 const response = await callOffice("desktop_sync", {
1812 desktop_session_id: sessionId,
@@ -1773,8 +1859,12 @@ const model = {
1859 this.stopDesktopMonitor();
1860 this.stopDesktopResizeObserver();
1861 this.stopXpraDesktopPrime();
1776 - this.setMessage("Desktop is restarting");
1777 - await this.ensureDesktopSession({ force: true, select: this.activeTabId === tabId || Boolean(hiddenDesktopDocument) });
1862 + this.message = "Desktop is restarting";
1863 + await this.ensureDesktopSession({
1864 + force: true,
1865 + select: this.activeTabId === tabId || Boolean(hiddenDesktopDocument),
1866 + message: "Desktop is restarting",
1867 + });
1868 target._desktopClosed = false;
1869 await this.refresh();
1870 },
tests/test_office_canvas_setup.py
+14 -1
@@ -13,6 +13,9 @@ def test_document_canvas_uses_markdown_editor_and_official_libreoffice_desktop_f
13 store = (PROJECT_ROOT / "plugins" / "_office" / "webui" / "office-store.js").read_text(
14 encoding="utf-8",
15 )
16 + canvas_panel = (
17 + PROJECT_ROOT / "plugins" / "_office" / "extensions" / "webui" / "right-canvas-panels" / "office-panel.html"
18 + ).read_text(encoding="utf-8")
19
20 assert "office-source-editor" in panel
21 assert "data-office-source" in panel
@@ -47,10 +50,18 @@ def test_document_canvas_uses_markdown_editor_and_official_libreoffice_desktop_f
50 assert "zoom: 1" not in store
51 assert 'callOffice("desktop")' in store
52 assert "ensureDesktopSession" in store
53 + assert 'await this.onOpen({ source: "modal" });' in store
54 + assert "setDesktopHostVisible" in store
55 + assert "isDesktopHostVisible" in store
56 + assert "clearDesktopViewportSyncTimers" in store
57 + assert "setDesktopHostVisible" in canvas_panel
58 + assert "Starting Agent Zero Desktop environment" in store
59 assert "handleOfficialOfficeClosed" in store
60 assert "ResizeObserver" in store
61 assert "_desktopResizeSuspended" in store
62 assert "_desktopResizePending" in store
63 + assert "_desktopResizePendingKey" in store
64 + assert "_desktopViewportSyncTimers" in store
65 assert "shouldDeferDesktopResize" in store
66 assert "right-canvas-resize-start" in store
67 assert "right-canvas-resize-end" in store
@@ -91,6 +102,8 @@ def test_document_canvas_uses_markdown_editor_and_official_libreoffice_desktop_f
102 assert "_desktopHeartbeatTimer" in store
103 assert "office-modal-focus-button" in store
104 assert "officialOfficeUrl" in store
105 + assert 'parsed.searchParams.set("offscreen", secureContext ? "true" : "false")' in store
106 + assert 'parsed.searchParams.set("clipboard_poll", secureContext ? "true" : "false")' in store
107 assert "hasOfficialOffice" in store
108 assert "isOfficeSocketData" in store
109 assert "office_command" not in store
@@ -233,7 +246,7 @@ def test_official_libreoffice_desktop_route_and_packages_are_declared():
246 assert '"quality": "85"' in primitive
247 assert '"speed": "80"' in primitive
248 assert '"printing": "true"' in primitive
236 - assert "offscreen" in primitive
249 + assert '"offscreen": "true"' in primitive
250 assert "xpra" in desktop
251 assert "xpra-html5" in desktop
252 assert "Xvfb" in desktop
tests/test_office_document_store.py
+3 -12
@@ -684,7 +684,7 @@ def test_cleanup_hook_removes_stale_runtime_state_idempotently(tmp_path, monkeyp
684 assert marker.exists()
685
686
687 -def test_office_startup_bootstraps_persistent_desktop_runtime(monkeypatch):
687 +def test_office_startup_defers_persistent_desktop_runtime(monkeypatch):
688 calls = []
689 routes_module = types.ModuleType("plugins._office.helpers.libreoffice_desktop_routes")
690 routes_module.install_route_hooks = lambda: calls.append("routes")
@@ -697,25 +697,16 @@ def test_office_startup_bootstraps_persistent_desktop_runtime(monkeypatch):
697
698 from plugins._office.extensions.python.startup_migration import _20_office_routes as office_startup
699
700 - class Manager:
701 - def ensure_system_desktop(self):
702 - calls.append("desktop")
703 - return {"available": True, "session_id": "agent-zero-desktop"}
704 -
700 monkeypatch.setattr(
701 office_startup.hooks,
702 "cleanup_stale_runtime_state",
703 lambda: {"ok": True, "errors": [], "installed": [], "removed": []},
704 )
710 - monkeypatch.setattr(
711 - office_startup.libreoffice_desktop,
712 - "get_manager",
713 - lambda: Manager(),
714 - )
705
706 office_startup.OfficeStartupCleanup(agent=None).execute()
707
718 - assert calls == ["routes", "desktop"]
708 + assert calls == ["routes"]
709 + assert not hasattr(office_startup, "libreoffice_desktop")
710
711
712 def test_cleanup_hook_reruns_when_stale_packages_exist_after_old_marker(tmp_path, monkeypatch):