Open Launcher Host access from Core
Show a direct Open Host access action when Launcher settings disable the gateway while preserving Reconnect for Core-originated disconnects. Use the bounded Launcher bridge so Core opens the trusted settings editor instead of re-enabling access itself.
Alessandro committed
Jul 16, 2026 at 13:37 UTC
cfd0df4a62fb186699d62d9d2c56e2e6b777370f
4 files changed
+52
-12
plugins/_a0_connector/AGENTS.md
+6
-4
@@ -51,10 +51,12 @@
51
failures through the shared notification system. Use the standard WebUI
52
border-radius tokens for its controls and popover. After Disconnect is
53
acknowledged, keep the popover open and turn that same action into Reconnect;
54
- use only the narrow Launcher WebContents bridge so this also works in a
55
- detached Instance window. `/computer-use on` may use that same bounded bridge
56
- to request approval for the current lease, while `/computer-use off` disables
57
- only the gateway scope. Pausing remains the reversible master switch.
54
+ when Host access was turned off in Launcher settings, open that Launcher-owned
55
+ editor instead of silently re-granting access. Use only the narrow Launcher
56
+ WebContents bridge so both actions also work in a detached Instance window.
57
+ `/computer-use on` may use that same bounded bridge to request approval for
58
+ the current lease, while `/computer-use off` disables only the gateway scope.
59
+ Pausing remains the reversible master switch.
60
- File operation results may arrive as chunked JSON/base64
61
`connector_file_op_result` frames; resolve the pending file operation only
62
after all chunks for the `op_id` are assembled.
plugins/_a0_connector/extensions/webui/sync-status-end/launcher-gateway.html
+8
-6
@@ -103,18 +103,20 @@
103
</template>
104
</div>
105
106
- <p class="launcher-gateway-empty" x-show="!$store.launcherGateway.gateway">
107
- Host access is not connected for this Launcher tab.
108
- </p>
106
+ <p
107
+ class="launcher-gateway-empty"
108
+ x-show="!$store.launcherGateway.gateway"
109
+ x-text="$store.launcherGateway.unavailableMessage"
110
+ ></p>
111
112
<button
113
type="button"
114
class="launcher-gateway-connection-action"
115
:class="{ 'is-reconnect': !$store.launcherGateway.gateway }"
114
- x-show="$store.launcherGateway.gateway || $store.launcherGateway.reconnectAvailable"
116
+ x-show="$store.launcherGateway.gateway || $store.launcherGateway.reconnectAvailable || $store.launcherGateway.canOpenSettings"
117
:disabled="$store.launcherGateway.saving"
116
- @click="$store.launcherGateway.gateway ? $store.launcherGateway.emergencyDisconnect() : $store.launcherGateway.reconnect()"
117
- x-text="$store.launcherGateway.saving ? ($store.launcherGateway.gateway ? 'Disconnecting…' : 'Reconnecting…') : ($store.launcherGateway.gateway ? 'Disconnect' : 'Reconnect')"
118
+ @click="$store.launcherGateway.gateway ? $store.launcherGateway.emergencyDisconnect() : ($store.launcherGateway.reconnectAvailable ? $store.launcherGateway.reconnect() : $store.launcherGateway.openSettings())"
119
+ x-text="$store.launcherGateway.saving ? ($store.launcherGateway.gateway ? 'Disconnecting…' : ($store.launcherGateway.reconnectAvailable ? 'Reconnecting…' : 'Opening…')) : ($store.launcherGateway.gateway ? 'Disconnect' : ($store.launcherGateway.reconnectAvailable ? 'Reconnect' : 'Open Host access'))"
120
></button>
121
</section>
122
</div>
plugins/_a0_connector/webui/launcher-gateway-store.js
+32
@@ -21,6 +21,10 @@ const model = {
21
return typeof window.a0LauncherHost?.reconnect === "function";
22
},
23
24
+ get canOpenSettings() {
25
+ return typeof window.a0LauncherHost?.openSettings === "function";
26
+ },
27
+
28
get gateway() {
29
return this.state === "disconnected" ? null : this.status?.gateway || null;
30
},
@@ -59,6 +63,16 @@ const model = {
63
return [...new Set(messages)];
64
},
65
66
+ get unavailableMessage() {
67
+ if (this.reconnectAvailable) return "Host access was disconnected for this Launcher tab.";
68
+ if (this.state === "disconnected") {
69
+ return this.canOpenSettings
70
+ ? "Host access is off in A0 Launcher."
71
+ : "Host access is off. Use the computer icon beside Reload in the Launcher header to turn it on.";
72
+ }
73
+ return "Open Host access in A0 Launcher for connection details.";
74
+ },
75
+
76
onMount() {
77
if (!this.visible || this.intervalId) return;
78
void this.refresh();
@@ -167,6 +181,24 @@ const model = {
181
}
182
},
183
184
+ async openSettings() {
185
+ if (this.saving) return;
186
+ this.saving = true;
187
+ try {
188
+ const response = await window.a0LauncherHost?.openSettings?.();
189
+ if (!response?.ok) throw new Error(response?.message || "Host access settings could not open.");
190
+ this.open = false;
191
+ } catch (error) {
192
+ console.error("Failed to open Launcher host settings:", error);
193
+ void toastFrontendError(
194
+ error?.message || "Host access settings could not open.",
195
+ "Host access",
196
+ );
197
+ } finally {
198
+ this.saving = false;
199
+ }
200
+ },
201
+
202
async control(payload) {
203
if (this.saving) return;
204
this.saving = true;
tests/test_a0_connector_launcher_gateway.py
+6
-2
@@ -78,8 +78,9 @@ def test_launcher_gateway_indicator_joins_sync_status_without_visual_noise() ->
78
assert "launcher-gateway-trigger-label" not in source
79
assert "launcher-gateway-dot" not in source
80
assert ":title=" not in source
81
- assert "'Disconnect' : 'Reconnect'" in source
82
- assert "$store.launcherGateway.gateway ? $store.launcherGateway.emergencyDisconnect() : $store.launcherGateway.reconnect()" in source
81
+ assert "'Disconnect' : ($store.launcherGateway.reconnectAvailable ? 'Reconnect' : 'Open Host access')" in source
82
+ assert "$store.launcherGateway.reconnectAvailable ? $store.launcherGateway.reconnect() : $store.launcherGateway.openSettings()" in source
83
+ assert "'Reconnect' : 'Open Host access'" in source
84
assert "Emergency disconnect" not in source
85
popover_css = source.split(".launcher-gateway-popover {", 1)[1].split("}", 1)[0]
86
assert "border-radius: var(--border-radius-sm);" in popover_css
@@ -95,7 +96,10 @@ def test_launcher_gateway_disconnect_becomes_reconnect_through_the_launcher_brid
96
).read_text(encoding="utf-8")
97
assert "window.a0LauncherHost?.getState?.()" in source
98
assert "window.a0LauncherHost?.reconnect?.()" in source
99
+ assert "window.a0LauncherHost?.openSettings?.()" in source
100
assert 'typeof window.a0LauncherHost?.reconnect === "function"' in source
101
+ assert 'typeof window.a0LauncherHost?.openSettings === "function"' in source
102
+ assert "Host access is off in A0 Launcher." in source
103
assert 'state: "disconnected", connected: false, gateway: null' in source
104
assert 'state: "connecting", connected: false, gateway: null' in source
105