main
html 691 lines 25.7 KB
Raw
1 <html>
2 <head>
3 <title>Browser Settings</title>
4 <script type="module">
5 import { store } from "/plugins/_browser/webui/browser-config-store.js";
6 </script>
7 </head>
8
9 <body>
10 <div x-data>
11 <template x-if="$store.browserConfig && config">
12 <div
13 class="browser-config-sections"
14 x-init="$store.browserConfig.init(config)"
15 x-effect="$store.browserConfig.bindConfig(config)"
16 x-destroy="$store.browserConfig.cleanup()"
17 >
18 <div class="browser-config-card">
19 <div class="section-title">Browser Runtime</div>
20 <div class="section-description">
21 Choose the internal Docker browser or a browser on the computer running A0 CLI.
22 </div>
23
24 <label class="browser-config-field">
25 <span class="browser-config-field-label">Browser location</span>
26 <select x-model="$store.browserConfig.config.runtime_backend">
27 <option value="container">Internal Docker browser</option>
28 <option value="host_required">Host browser through A0 CLI</option>
29 </select>
30 <span
31 class="browser-config-field-help"
32 x-show="$store.browserConfig.config.runtime_backend !== 'host_required'"
33 >
34 Runs inside Agent Zero's Docker environment. No A0 CLI connection or host browser setup is needed.
35 </span>
36 <span
37 class="browser-config-field-help"
38 x-show="$store.browserConfig.config.runtime_backend === 'host_required'"
39 >
40 Uses Chrome, Edge, Brave, Opera, Vivaldi, or Chromium on the A0 CLI host. Keep A0 CLI connected; the browser opens when the agent first needs it. For an already-open browser, use its inspect page, such as chrome://inspect/#remote-debugging or opera://inspect/#remote-debugging, and enable "Allow remote debugging for this browser instance".
41 </span>
42 </label>
43
44 <div
45 class="browser-config-note browser-config-success"
46 x-show="$store.browserConfig.config.runtime_backend !== 'host_required'"
47 >
48 <x-icon name="deployed_code"></x-icon>
49 <span>Internal Docker browser selected. Host-browser CLI settings are ignored for Browser tool runs.</span>
50 </div>
51
52 <details
53 class="browser-config-proxy"
54 x-show="$store.browserConfig.config.runtime_backend === 'container'"
55 >
56 <summary>Proxy server</summary>
57 <div class="browser-config-proxy-body">
58 <label class="browser-config-field">
59 <input
60 type="text"
61 x-model="$store.browserConfig.config.proxy_server"
62 placeholder="http://proxy.example:3128 or socks5://proxy.example:1080"
63 aria-label="Proxy server"
64 autocomplete="off"
65 />
66 <span class="browser-config-field-help">Optional. Routes the internal browser through this HTTP or SOCKS proxy.</span>
67 </label>
68
69 <label class="browser-config-field">
70 <span class="browser-config-field-label">Bypass proxy for</span>
71 <input
72 type="text"
73 x-model="$store.browserConfig.config.proxy_bypass"
74 placeholder="localhost, .example.com"
75 autocomplete="off"
76 />
77 <span class="browser-config-field-help">Optional comma-separated domains that should connect directly.</span>
78 </label>
79
80 <label class="browser-config-field">
81 <span class="browser-config-field-label">Proxy username</span>
82 <input
83 type="text"
84 x-model="$store.browserConfig.config.proxy_username"
85 autocomplete="off"
86 />
87 </label>
88
89 <label class="browser-config-field">
90 <span class="browser-config-field-label">Proxy password</span>
91 <input
92 type="password"
93 x-model="$store.browserConfig.config.proxy_password"
94 autocomplete="off"
95 />
96 <span class="browser-config-field-help">Saving proxy changes restarts active internal Browser sessions.</span>
97 </label>
98 </div>
99 </details>
100
101 <label
102 class="browser-config-field"
103 x-show="$store.browserConfig.config.runtime_backend === 'host_required'"
104 >
105 <span class="browser-config-field-label">Host browser profile</span>
106 <select x-model="$store.browserConfig.config.host_browser_profile_mode">
107 <option value="existing">Existing browser profile</option>
108 <option value="agent">Clean Agent profile</option>
109 </select>
110 <span
111 class="browser-config-field-help"
112 x-show="$store.browserConfig.config.host_browser_profile_mode === 'agent'"
113 >
114 Uses a separate A0-controlled local profile on this computer.
115 </span>
116 </label>
117
118 <label
119 class="browser-config-field"
120 x-show="$store.browserConfig.config.runtime_backend === 'host_required' && $store.browserConfig.config.host_browser_profile_mode !== 'agent'"
121 >
122 <span class="browser-config-field-label">Host browser</span>
123 <select
124 :value="$store.browserConfig.hostBrowserSelectValue()"
125 @focus="$store.browserConfig.loadHostBrowserStatus()"
126 @click="$store.browserConfig.loadHostBrowserStatus()"
127 @change="$store.browserConfig.setHostBrowserSelection($event.target.value)"
128 >
129 <template x-for="option in $store.browserConfig.hostBrowserOptions()" :key="option.value">
130 <option :value="option.value" x-text="option.label"></option>
131 </template>
132 </select>
133 <span class="browser-config-field-help">Choose an allowed debug endpoint, or leave Automatic so A0 CLI picks.</span>
134 </label>
135
136 <label
137 class="browser-config-field"
138 x-show="$store.browserConfig.config.runtime_backend === 'host_required' && $store.browserConfig.config.host_browser_profile_mode !== 'agent' && $store.browserConfig.showCustomHostBrowserEndpoint()"
139 >
140 <span class="browser-config-field-label">Custom endpoint</span>
141 <input
142 type="text"
143 :value="$store.browserConfig.hostBrowserCustomEndpoint"
144 @focus="$store.browserConfig.loadHostBrowserStatus()"
145 @input="$store.browserConfig.setCustomHostBrowserEndpoint($event.target.value)"
146 placeholder="ws://127.0.0.1:9222/devtools/browser/..."
147 autocomplete="off"
148 />
149 <span class="browser-config-field-help" x-text="$store.browserConfig.customHostBrowserEndpointDiagnostic()"></span>
150 </label>
151
152 <div
153 class="browser-config-note"
154 x-show="$store.browserConfig.config.runtime_backend === 'host_required' && $store.browserConfig.config.host_browser_profile_mode !== 'agent'"
155 >
156 <x-icon name="info"></x-icon>
157 <span>
158 For an already-open browser, Chrome, Edge, Brave, Vivaldi, and Chromium use chrome://inspect/#remote-debugging; Opera uses opera://inspect/#remote-debugging. Enable "Allow remote debugging for this browser instance", then restart or reconnect A0 CLI if the browser does not appear. If needed, launch the browser with --remote-debugging-port=9222 and --user-data-dir=&lt;profile-dir&gt;, then use localhost:9222 or the full ws://.../devtools/browser/... endpoint here or in A0_HOST_BROWSER_REMOTE_DEBUGGING_ENDPOINTS.
159 </span>
160 </div>
161
162 <div
163 class="browser-config-warning"
164 x-show="$store.browserConfig.config.runtime_backend === 'host_required' && $store.browserConfig.config.host_browser_profile_mode !== 'agent'"
165 >
166 <x-icon name="warning"></x-icon>
167 <span>
168 Existing profile lets the agent interact with the browser instance you authorize,
169 including signed-in sites, cookies, tabs, downloads, and page content.
170 </span>
171 </div>
172
173 <label
174 class="browser-config-field"
175 x-show="$store.browserConfig.config.runtime_backend === 'host_required'"
176 >
177 <span class="browser-config-field-label">Page content access</span>
178 <select x-model="$store.browserConfig.config.host_browser_privacy_policy">
179 <option value="enforce_local">Local models only</option>
180 <option value="warn">Warn when using cloud</option>
181 <option value="allow">Allow</option>
182 </select>
183 <span class="browser-config-field-help">Controls page text and screenshots from the host browser.</span>
184 </label>
185
186 <div class="browser-config-note">
187 <x-icon :class="{ spinning: $store.browserConfig.hostBrowserStatusLoading }" :name="$store.browserConfig.hostBrowserStatusLoading ? 'progress_activity' : 'captive_portal'"></x-icon>
188 <span x-text="$store.browserConfig.browserRuntimeStatusLabel()"></span>
189 </div>
190 </div>
191
192 <div class="browser-config-card">
193 <div class="section-title">Browsing</div>
194 <div class="section-description">
195 Set how new Browser sessions start and how an already-open Browser surface follows agent activity.
196 </div>
197
198 <label class="browser-config-field">
199 <span class="browser-config-field-label">Starting page</span>
200 <input
201 type="text"
202 x-model="$store.browserConfig.config.default_homepage"
203 placeholder="about:blank or https://example.com"
204 autocomplete="off"
205 />
206 </label>
207
208 <label class="browser-config-field">
209 <span class="browser-config-field-label">Browser tabs</span>
210 <select
211 x-model="$store.browserConfig.config.browser_tab_scope"
212 @change="$store.browserConfig.setBrowserTabScope($event.target.value)"
213 >
214 <option value="per_context">Separate per chat</option>
215 <option value="shared">Shared across chats</option>
216 </select>
217 <span
218 class="browser-config-field-help"
219 x-show="$store.browserConfig.config.browser_tab_scope !== 'shared'"
220 >
221 Each chat shows only its own tabs. Browser sign-ins are shared across chats.
222 </span>
223 <span
224 class="browser-config-field-help"
225 x-show="$store.browserConfig.config.browser_tab_scope === 'shared'"
226 >
227 The tab strip shows tabs from every active chat. Browser sign-ins are shared across chats.
228 </span>
229 </label>
230
231 <label class="browser-config-field">
232 <span class="browser-config-field-label">Maximum tabs per chat</span>
233 <input
234 type="number"
235 min="1"
236 max="50"
237 step="1"
238 x-model.number="$store.browserConfig.config.max_open_tabs"
239 @change="$store.browserConfig.normalizeMaxOpenTabs()"
240 />
241 <span class="browser-config-field-help">
242 New Browser tabs stop opening in a chat after this limit.
243 </span>
244 </label>
245
246 <label class="browser-config-field">
247 <span class="browser-config-field-label">Keyboard layout</span>
248 <input
249 type="text"
250 x-model="$store.browserConfig.config.keyboard_layout"
251 placeholder="de, fr, us, ..."
252 autocomplete="off"
253 />
254 <span class="browser-config-field-help">
255 XKB layout of the interactive Browser window, e.g. de for a German keyboard. Match your physical keyboard so characters like @, umlauts, and brackets land on the printed keys. Empty keeps US.
256 <a href="https://man.archlinux.org/man/xkeyboard-config.7#LAYOUTS" target="_blank" rel="noreferrer">Full layout list</a>. Changing it restarts the internal browser on save.
257 </span>
258 </label>
259
260 <label
261 class="browser-config-field"
262 x-show="$store.browserConfig.config.keyboard_layout"
263 >
264 <span class="browser-config-field-label">Keyboard variant</span>
265 <input
266 type="text"
267 x-model="$store.browserConfig.config.keyboard_variant"
268 placeholder="mac, nodeadkeys, colemak, ..."
269 autocomplete="off"
270 />
271 <span class="browser-config-field-help">
272 XKB variant for the layout above. German Mac keyboards use mac, so Option+L types @.
273 <a href="https://man.archlinux.org/man/xkeyboard-config.7#LAYOUTS" target="_blank" rel="noreferrer">Variants per layout</a>. Changing it restarts the internal browser on save.
274 </span>
275 </label>
276
277 <label class="browser-config-switch-row">
278 <span class="browser-config-switch-copy">
279 <span class="browser-config-field-label">Autofocus active page</span>
280 <span class="browser-config-field-help">Update the visible Browser surface for pages opened or changed by Browser tool results.</span>
281 </span>
282 <span class="browser-config-toggle-with-label">
283 <span class="browser-config-toggle-label" x-text="$store.browserConfig.autofocusLabel()"></span>
284 <span class="browser-config-toggle">
285 <input
286 type="checkbox"
287 :checked="$store.browserConfig.config.autofocus_active_page !== false"
288 @change="$store.browserConfig.setAutofocusActivePage($event.target.checked)"
289 />
290 <span class="browser-config-switch"></span>
291 </span>
292 </span>
293 </label>
294 </div>
295
296 <div class="browser-config-card">
297 <div class="section-title">Extensions</div>
298 <div class="section-description">
299 Choose which installed Chrome extensions Browser loads.
300 </div>
301
302 <div class="browser-config-warning">
303 <x-icon name="warning"></x-icon>
304 <span>
305 Extensions run inside the Docker browser sandbox, but malicious or buggy extensions can
306 still damage that environment. Keep only the extensions you trust enabled.
307 </span>
308 </div>
309
310 <div class="browser-config-extension-list">
311 <div class="browser-config-subtitle">
312 <span>Installed extensions</span>
313 <x-icon class="spinning" x-show="$store.browserConfig.extensionsLoading" name="progress_activity"></x-icon>
314 </div>
315 <template x-if="!$store.browserConfig.extensionsLoading && !$store.browserConfig.extensionsList.length">
316 <div class="browser-config-empty">No installed extensions found.</div>
317 </template>
318 <template x-for="extension in $store.browserConfig.extensionsList" :key="extension.path">
319 <div class="browser-config-extension-row" :title="extension.path">
320 <span class="browser-config-extension-text">
321 <span class="browser-config-extension-name" x-text="extension.name || 'Unnamed extension'"></span>
322 <span class="browser-config-extension-meta" x-text="$store.browserConfig.extensionVersionLabel(extension)"></span>
323 </span>
324 <span class="browser-config-extension-actions">
325 <label class="browser-config-toggle">
326 <input
327 type="checkbox"
328 :checked="$store.browserConfig.extensionEnabled(extension)"
329 :disabled="$store.browserConfig.extensionDeleteLoadingPath === extension.path"
330 @change="$store.browserConfig.setExtensionEnabled(extension, $event.target.checked)"
331 />
332 <span class="browser-config-switch"></span>
333 </label>
334 <button
335 type="button"
336 class="browser-config-extension-delete"
337 :disabled="!$store.browserConfig.extensionCanDelete(extension) || $store.browserConfig.extensionDeleteLoadingPath === extension.path"
338 :title="$store.browserConfig.extensionDeleteTitle(extension)"
339 :aria-label="'Delete ' + (extension.name || 'extension')"
340 @click.stop="$store.browserConfig.deleteExtension(extension)"
341 >
342 <x-icon
343 :class="{ spinning: $store.browserConfig.extensionDeleteLoadingPath === extension.path }"
344 :name="$store.browserConfig.extensionDeleteLoadingPath === extension.path ? 'progress_activity' : 'delete'"
345 ></x-icon>
346 </button>
347 </span>
348 </div>
349 </template>
350 <div class="browser-config-note browser-config-success" x-show="$store.browserConfig.extensionsMessage">
351 <x-icon name="check_circle"></x-icon>
352 <span x-text="$store.browserConfig.extensionsMessage"></span>
353 </div>
354 <div class="browser-config-note" x-show="$store.browserConfig.extensionsError">
355 <x-icon name="error"></x-icon>
356 <span x-text="$store.browserConfig.extensionsError"></span>
357 </div>
358 </div>
359
360 <div class="browser-config-pill-row">
361 <span class="browser-config-pill" x-text="$store.browserConfig.pathCountLabel()"></span>
362 <span class="browser-config-pill tone-active" x-show="$store.browserConfig.extensionModeReady()">
363 Ready
364 </span>
365 </div>
366 </div>
367 </div>
368 </template>
369 </div>
370
371 <style>
372 .browser-config-sections {
373 display: flex;
374 flex-direction: column;
375 gap: 16px;
376 }
377
378 .browser-config-card {
379 display: flex;
380 flex-direction: column;
381 gap: 14px;
382 padding: 16px;
383 border: 1px solid var(--color-border);
384 border-radius: 8px;
385 }
386
387 .browser-config-field {
388 display: flex;
389 flex-direction: column;
390 gap: 6px;
391 }
392
393 .browser-config-field-label {
394 color: var(--color-text);
395 font-size: 0.84rem;
396 font-weight: 650;
397 }
398
399 .browser-config-field-help {
400 color: var(--color-text-secondary);
401 font-size: 0.78rem;
402 line-height: 1.35;
403 }
404
405 .browser-config-proxy {
406 overflow: hidden;
407 border-radius: 8px;
408 }
409
410 .browser-config-proxy summary {
411 padding: 10px 12px;
412 color: var(--color-text);
413 font-size: 0.84rem;
414 font-weight: 650;
415 cursor: pointer;
416 }
417
418 .browser-config-proxy-body {
419 display: flex;
420 flex-direction: column;
421 gap: 14px;
422 padding: 14px;
423 border-top: 1px solid color-mix(in srgb, var(--color-border) 74%, transparent);
424 }
425
426 .browser-config-field input[type="text"],
427 .browser-config-field input[type="password"],
428 .browser-config-field input[type="number"],
429 .browser-config-field select {
430 width: 100%;
431 min-height: 36px;
432 padding: 7px 10px;
433 border: 1px solid color-mix(in srgb, var(--color-border) 74%, transparent);
434 border-radius: 8px;
435 background: var(--color-input);
436 color: var(--color-text);
437 font: inherit;
438 }
439
440 .browser-config-switch-row {
441 display: grid;
442 grid-template-columns: minmax(0, 1fr) auto;
443 align-items: center;
444 gap: 16px;
445 min-height: 46px;
446 padding-top: 2px;
447 }
448
449 .browser-config-switch-copy {
450 display: flex;
451 min-width: 0;
452 flex-direction: column;
453 gap: 3px;
454 }
455
456 .browser-config-toggle-with-label {
457 display: inline-flex;
458 align-items: center;
459 gap: 8px;
460 }
461
462 .browser-config-toggle-label {
463 min-width: 24px;
464 color: var(--color-text-secondary);
465 font-size: 0.78rem;
466 font-weight: 650;
467 text-align: right;
468 }
469
470 .browser-config-extension-list {
471 display: flex;
472 flex-direction: column;
473 gap: 8px;
474 padding: 12px;
475 border: 1px solid color-mix(in srgb, var(--color-border) 72%, transparent);
476 border-radius: 8px;
477 background: color-mix(in srgb, var(--color-panel) 78%, transparent);
478 }
479
480 .browser-config-subtitle {
481 display: flex;
482 align-items: center;
483 justify-content: space-between;
484 gap: 8px;
485 color: var(--color-text-secondary);
486 font-size: 0.82rem;
487 font-weight: 650;
488 }
489
490 .browser-config-subtitle .material-symbols-outlined {
491 font-size: 18px;
492 }
493
494 .browser-config-empty,
495 .browser-config-extension-meta {
496 color: var(--color-text-secondary);
497 font-size: 0.78rem;
498 }
499
500 .browser-config-extension-row {
501 display: grid;
502 grid-template-columns: minmax(0, 1fr) auto;
503 align-items: center;
504 gap: 14px;
505 min-height: 42px;
506 padding: 8px 0;
507 }
508
509 .browser-config-extension-row + .browser-config-extension-row {
510 border-top: 1px solid color-mix(in srgb, var(--color-border) 52%, transparent);
511 }
512
513 .browser-config-extension-text {
514 display: flex;
515 min-width: 0;
516 flex-direction: column;
517 gap: 2px;
518 }
519
520 .browser-config-extension-name {
521 overflow: hidden;
522 text-overflow: ellipsis;
523 white-space: nowrap;
524 color: var(--color-text);
525 font-weight: 650;
526 }
527
528 .browser-config-extension-actions {
529 display: inline-flex;
530 align-items: center;
531 gap: 8px;
532 }
533
534 .browser-config-toggle {
535 position: relative;
536 display: inline-flex;
537 flex: 0 0 auto;
538 align-items: center;
539 width: 42px;
540 height: 24px;
541 }
542
543 .browser-config-toggle input {
544 position: absolute;
545 inset: 0;
546 margin: 0;
547 opacity: 0;
548 cursor: pointer;
549 }
550
551 .browser-config-toggle input:disabled {
552 cursor: wait;
553 }
554
555 .browser-config-switch {
556 width: 100%;
557 height: 100%;
558 border-radius: 999px;
559 background: color-mix(in srgb, var(--color-border) 78%, transparent);
560 pointer-events: none;
561 transition: background-color 0.18s cubic-bezier(0.4, 0, 0.2, 1);
562 }
563
564 .browser-config-switch::after {
565 content: "";
566 position: absolute;
567 top: 3px;
568 left: 3px;
569 width: 18px;
570 height: 18px;
571 border-radius: 50%;
572 background: var(--color-background);
573 box-shadow: 0 1px 2px rgba(0, 0, 0, 0.24);
574 transition: transform 0.18s cubic-bezier(0.4, 0, 0.2, 1);
575 }
576
577 .browser-config-toggle input:checked + .browser-config-switch {
578 background: color-mix(in srgb, var(--color-primary) 76%, #16a34a);
579 }
580
581 .browser-config-toggle input:checked + .browser-config-switch::after {
582 transform: translateX(18px);
583 }
584
585 .browser-config-toggle input:disabled + .browser-config-switch {
586 opacity: 0.58;
587 }
588
589 .browser-config-extension-delete {
590 display: inline-flex;
591 align-items: center;
592 justify-content: center;
593 width: 28px;
594 min-width: 28px;
595 height: 28px;
596 min-height: 28px;
597 padding: 0;
598 border: 1px solid color-mix(in srgb, #be123c 30%, var(--color-border));
599 border-radius: 7px;
600 background: transparent;
601 color: color-mix(in srgb, #be123c 78%, var(--color-text));
602 cursor: pointer;
603 }
604
605 .browser-config-extension-delete:hover:not(:disabled) {
606 background: color-mix(in srgb, #be123c 12%, var(--color-background));
607 color: #be123c;
608 }
609
610 .browser-config-extension-delete:disabled {
611 cursor: not-allowed;
612 opacity: 0.48;
613 }
614
615 .browser-config-extension-delete .material-symbols-outlined {
616 font-size: 17px;
617 }
618
619 .browser-config-note {
620 display: flex;
621 align-items: flex-start;
622 gap: 8px;
623 padding: 10px 12px;
624 border-radius: 8px;
625 background: color-mix(in srgb, var(--color-panel) 82%, transparent);
626 color: var(--color-text-secondary);
627 font-size: var(--font-size-small);
628 }
629
630 .browser-config-note.browser-config-success {
631 background: color-mix(in srgb, #15803d 12%, var(--color-background));
632 color: color-mix(in srgb, var(--color-text) 88%, #166534);
633 }
634
635 .browser-config-warning {
636 display: flex;
637 align-items: flex-start;
638 gap: 9px;
639 padding: 11px 12px;
640 border: 1px solid color-mix(in srgb, #d97706 44%, var(--color-border));
641 border-radius: 8px;
642 background: color-mix(in srgb, #d97706 14%, var(--color-background));
643 color: color-mix(in srgb, var(--color-text) 86%, #92400e);
644 font-size: var(--font-size-small);
645 line-height: 1.4;
646 }
647
648 .browser-config-warning .material-symbols-outlined {
649 color: #b45309;
650 font-size: 20px;
651 }
652
653 .browser-config-sections .spinning {
654 display: inline-block;
655 transform-origin: center;
656 animation: browser-config-spin 0.8s linear infinite;
657 }
658
659 @keyframes browser-config-spin {
660 to {
661 transform: rotate(360deg);
662 }
663 }
664
665 .browser-config-pill-row {
666 display: flex;
667 flex-wrap: wrap;
668 gap: 8px;
669 }
670
671 .browser-config-pill {
672 display: inline-flex;
673 align-items: center;
674 gap: 6px;
675 min-height: 28px;
676 padding: 0 10px;
677 border-radius: 999px;
678 border: 1px solid color-mix(in srgb, var(--color-border) 70%, transparent);
679 background: color-mix(in srgb, var(--color-panel) 88%, transparent);
680 font-size: 0.78rem;
681 color: var(--color-text-secondary);
682 }
683
684 .browser-config-pill.tone-active {
685 color: #1b5e20;
686 border-color: rgba(27, 94, 32, 0.18);
687 background: rgba(46, 125, 50, 0.12);
688 }
689 </style>
690 </body>
691 </html>