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):