Polish Agent Editor layouts and interactions

Refine the Advanced modal, prompt path display, review identity summary, reset-to-default workflow, and compact policy controls. Improve mobile footer and profile-card wrapping, make Advanced tool and skill rows directly selectable, and extend WebUI regression coverage for the updated behavior.

Alessandro committed Aug 10, 2026 at 15:23 UTC 36672493ff3aa8f1be9d7ec07172e2b70fcf4dea
3 files changed +138 -49
plugins/_agent_editor/webui/agent-editor-store.js
+19 -6
@@ -299,10 +299,16 @@ const model = {
299
300 async restoreProfile(profile) {
301 if (!profile?.id || profile.deletable || !profile.scope_has_overrides || this.saving) return;
302 + const editorState = this.view === "editor"
303 + ? { mode: this.mode, section: this.section }
304 + : null;
305 + const unsaved = editorState && this.dirty
306 + ? "<p>Unsaved edits will also be discarded.</p>"
307 + : "";
308 const confirmed = await showConfirmDialog({
303 - title: `Restore ${escapeHtml(profile.title || profile.id)}?`,
304 - message: `<p>This removes your Agent Editor customizations from ${escapeHtml(this.scopeLabel)}. Other files stay in place.</p>`,
305 - confirmText: "Restore original",
309 + title: `Reset ${escapeHtml(profile.title || profile.id)} to default?`,
310 + message: `<p>This removes your Agent Editor customizations from ${escapeHtml(this.scopeLabel)}. Other files stay in place.</p>${unsaved}`,
311 + confirmText: "Reset to default",
312 type: "danger",
313 });
314 if (!confirmed) return;
@@ -317,6 +323,10 @@ const model = {
323 });
324 await this.loadProfiles();
325 await modelConfigStore.loadAgentProfiles(true);
326 + if (editorState) {
327 + await this.loadEditor(profile.id, false);
328 + this.setMode(editorState.mode, editorState.section, false);
329 + }
330 } catch (error) {
331 this.error = error.message || String(error);
332 } finally {
@@ -892,12 +902,15 @@ const model = {
902 textarea.setSelectionRange(index, index + query.length);
903 },
904
895 - copyPromptPath() {
905 + promptCustomizationPath() {
906 const root = this.projectName
907 ? `usr/projects/${this.projectName}/.a0proj/agents`
908 : "usr/agents";
899 - const path = `${root}/${this.draft.profileId}/prompts/${this.selectedPrompt}`;
900 - navigator.clipboard?.writeText(path);
909 + return `${root}/${this.draft.profileId}/prompts/${this.selectedPrompt}`;
910 + },
911 +
912 + copyPromptPath() {
913 + navigator.clipboard?.writeText(this.promptCustomizationPath());
914 globalThis.justToast?.("Path copied", "success", 1200, "agent-editor-copy");
915 },
916
plugins/_agent_editor/webui/main.html
+57 -36
@@ -77,9 +77,9 @@
77 <button type="button" class="text-button agent-manager-inline-action"
78 x-show="profile.scope_has_overrides && !profile.deletable"
79 :disabled="$store.agentEditor.saving"
80 - :aria-label="`Restore original ${profile.title || profile.id}`"
80 + :aria-label="`Reset ${profile.title || profile.id} to default`"
81 @click="$store.agentEditor.restoreProfile(profile)">
82 - <x-icon name="restore"></x-icon><span>Restore original</span>
82 + <x-icon name="restore"></x-icon><span>Reset to default</span>
83 </button>
84 </div>
85 <div class="agent-manager-actions">
@@ -122,9 +122,11 @@
122 <span class="agent-status-badge is-unsaved"><x-icon name="edit_note"></x-icon><span>Unsaved changes</span></span>
123 </div>
124 </div>
125 - <div class="agent-mode-switch" role="group" aria-label="Editor mode">
126 - <button type="button" :class="{ active: $store.agentEditor.mode === 'easy' }" :aria-pressed="$store.agentEditor.mode === 'easy'" @click="$store.agentEditor.setMode('easy')">Easy</button>
127 - <button type="button" :class="{ active: $store.agentEditor.mode === 'advanced' }" :aria-pressed="$store.agentEditor.mode === 'advanced'" @click="$store.agentEditor.setMode('advanced')">Advanced</button>
125 + <div class="agent-editor-topbar-actions">
126 + <div class="agent-mode-switch" role="group" aria-label="Editor mode">
127 + <button type="button" :class="{ active: $store.agentEditor.mode === 'easy' }" :aria-pressed="$store.agentEditor.mode === 'easy'" @click="$store.agentEditor.setMode('easy')">Easy</button>
128 + <button type="button" :class="{ active: $store.agentEditor.mode === 'advanced' }" :aria-pressed="$store.agentEditor.mode === 'advanced'" @click="$store.agentEditor.setMode('advanced')">Advanced</button>
129 + </div>
130 </div>
131 </header>
132
@@ -139,7 +141,7 @@
141 <div class="agent-avatar-actions">
142 <label class="avatar-action-icon avatar-color-action" title="Choose color"><x-icon name="palette"></x-icon><span class="sr-only">Choose color</span><input type="color" :value="$store.agentEditor.avatarColor()" @input="$store.agentEditor.chooseAvatarColor($event.target.value)" aria-label="Choose avatar color"></label>
143 <label class="avatar-action-icon avatar-upload-action" title="Upload image"><x-icon name="add_photo_alternate"></x-icon><span class="sr-only">Upload image</span><input type="file" accept="image/png,image/jpeg,image/webp" @change="$store.agentEditor.uploadAvatar($event)" aria-label="Upload avatar image"></label>
142 - <button type="button" class="text-button" x-show="$store.agentEditor.state.profile.metadata.avatar.has_override || $store.agentEditor.draft.avatar" @click="$store.agentEditor.resetAvatar()">Remove</button>
144 + <button type="button" class="text-button" x-show="$store.agentEditor.state.profile.metadata.avatar.has_override || $store.agentEditor.draft.avatar" @click="$store.agentEditor.resetAvatar()">Reset</button>
145 </div>
146 </div>
147 <div class="agent-field agent-name-field">
@@ -159,7 +161,7 @@
161 <div>
162 <textarea id="agent-editor-instructions" rows="9" x-model="$store.agentEditor.instructions.value" @input="$store.agentEditor.markPromptSet('agent.system.main.specifics.md')" placeholder="Research technical topics, verify important claims with reliable sources, and return concise reports with links." :aria-invalid="$store.agentEditor.fieldIssue('instructions') ? 'true' : null" :aria-describedby="$store.agentEditor.fieldIssue('instructions') ? 'agent-editor-instructions-error' : null"></textarea>
163 <span id="agent-editor-instructions-error" class="field-error" role="alert" x-show="$store.agentEditor.fieldIssue('instructions')" x-text="$store.agentEditor.fieldIssue('instructions')?.message"></span>
162 - <button type="button" class="text-button restore-action" x-show="$store.agentEditor.instructions.has_override && !$store.agentEditor.instructions.reset" @click="$store.agentEditor.restoreInstructions()"><x-icon name="restart_alt"></x-icon><span x-text="$store.agentEditor.projectName ? 'Use inherited instructions' : 'Use default instructions'"></span></button>
164 + <button type="button" class="text-button restore-action" x-show="$store.agentEditor.instructions.has_override && !$store.agentEditor.instructions.reset" @click="$store.agentEditor.restoreInstructions()"><x-icon name="restart_alt"></x-icon><span>Reset to default</span></button>
165 </div>
166 </section>
167
@@ -204,13 +206,13 @@
206 <div class="agent-avatar-actions">
207 <label class="avatar-action-icon avatar-color-action" title="Choose color"><x-icon name="palette"></x-icon><span class="sr-only">Choose color</span><input type="color" :value="$store.agentEditor.avatarColor()" @input="$store.agentEditor.chooseAvatarColor($event.target.value)" aria-label="Choose avatar color"></label>
208 <label class="avatar-action-icon avatar-upload-action" title="Upload image"><x-icon name="add_photo_alternate"></x-icon><span class="sr-only">Upload image</span><input type="file" accept="image/png,image/jpeg,image/webp" @change="$store.agentEditor.uploadAvatar($event)" aria-label="Upload avatar image"></label>
207 - <button type="button" class="text-button" @click="$store.agentEditor.resetAvatar()">Reset</button>
209 + <button type="button" class="text-button" x-show="$store.agentEditor.state.profile.metadata.avatar.has_override || $store.agentEditor.draft.avatar" @click="$store.agentEditor.resetAvatar()">Reset</button>
210 </div>
211 </div>
212 <div class="identity-fields">
211 - <div class="agent-field"><label for="agent-editor-advanced-name" class="agent-field-label">Agent name</label><input id="agent-editor-advanced-name" type="text" x-model="$store.agentEditor.draft.title" @input="$store.agentEditor.onNameInput(); $store.agentEditor.markMetadataSet('title')" :aria-invalid="$store.agentEditor.fieldIssue('name') ? 'true' : null" :aria-describedby="$store.agentEditor.fieldIssue('name') ? 'agent-editor-advanced-name-error' : null"><span id="agent-editor-advanced-name-error" class="field-error" role="alert" x-show="$store.agentEditor.fieldIssue('name')" x-text="$store.agentEditor.fieldIssue('name')?.message"></span><small class="field-provenance" x-show="$store.agentEditor.metadataProvenance('title')" x-text="$store.agentEditor.metadataProvenance('title')"></small><button type="button" class="text-button" x-show="$store.agentEditor.canResetMetadata('title')" @click="$store.agentEditor.resetMetadata('title')">Reset to inherited</button><small class="field-status" x-show="$store.agentEditor.metadataResetPending('title')">Will reset to inherited on save.</small></div>
213 + <div class="agent-field"><label for="agent-editor-advanced-name" class="agent-field-label">Agent name</label><input id="agent-editor-advanced-name" type="text" x-model="$store.agentEditor.draft.title" @input="$store.agentEditor.onNameInput(); $store.agentEditor.markMetadataSet('title')" :aria-invalid="$store.agentEditor.fieldIssue('name') ? 'true' : null" :aria-describedby="$store.agentEditor.fieldIssue('name') ? 'agent-editor-advanced-name-error' : null"><span id="agent-editor-advanced-name-error" class="field-error" role="alert" x-show="$store.agentEditor.fieldIssue('name')" x-text="$store.agentEditor.fieldIssue('name')?.message"></span><small class="field-provenance" x-show="$store.agentEditor.metadataProvenance('title')" x-text="$store.agentEditor.metadataProvenance('title')"></small><button type="button" class="text-button" x-show="$store.agentEditor.canResetMetadata('title')" @click="$store.agentEditor.resetMetadata('title')">Reset to default</button><small class="field-status" x-show="$store.agentEditor.metadataResetPending('title')">Will reset to default on save.</small></div>
214 <label class="agent-field"><span class="agent-field-label">Profile ID</span><input type="text" :value="$store.agentEditor.draft.profileId" readonly aria-describedby="profile-id-help"><small id="profile-id-help">Used as the profile folder name. Existing IDs do not change when the display name changes.</small></label>
213 - <div class="agent-field wide"><label for="agent-editor-description" class="agent-field-label">Description</label><textarea id="agent-editor-description" rows="2" x-model="$store.agentEditor.draft.description" @input="$store.agentEditor.markMetadataSet('description')"></textarea><small>Short summary shown in profile lists.</small><small class="field-provenance" x-show="$store.agentEditor.metadataProvenance('description')" x-text="$store.agentEditor.metadataProvenance('description')"></small><button type="button" class="text-button" x-show="$store.agentEditor.canResetMetadata('description')" @click="$store.agentEditor.resetMetadata('description')">Reset to inherited</button><small class="field-status" x-show="$store.agentEditor.metadataResetPending('description')">Will reset to inherited on save.</small></div>
215 + <div class="agent-field wide"><label for="agent-editor-description" class="agent-field-label">Description</label><textarea id="agent-editor-description" rows="2" x-model="$store.agentEditor.draft.description" @input="$store.agentEditor.markMetadataSet('description')"></textarea><small>Short summary shown in profile lists.</small><button type="button" class="text-button" x-show="$store.agentEditor.canResetMetadata('description')" @click="$store.agentEditor.resetMetadata('description')">Reset to default</button><small class="field-status" x-show="$store.agentEditor.metadataResetPending('description')">Will reset to default on save.</small></div>
216 <div class="agent-field wide agent-model-preset">
217 <label for="agent-editor-model-preset" class="agent-field-label">Model preset</label>
218 <div class="agent-model-preset-picker">
@@ -224,7 +226,7 @@
226 </div>
227 <small>Use the current preset or choose another setup for this agent.</small>
228 </div>
227 - <div class="agent-field wide"><label for="agent-editor-context" class="agent-field-label">When should other agents use this agent?</label><textarea id="agent-editor-context" rows="3" x-model="$store.agentEditor.draft.context" @input="$store.agentEditor.markMetadataSet('context')"></textarea><small>Helps Agent Zero decide when to delegate work; leaving this empty may reduce delegation quality.</small><button type="button" class="text-button" x-show="$store.agentEditor.canResetMetadata('context')" @click="$store.agentEditor.resetMetadata('context')">Reset to inherited</button><small class="field-status" x-show="$store.agentEditor.metadataResetPending('context')">Will reset to inherited on save.</small></div>
229 + <div class="agent-field wide"><label for="agent-editor-context" class="agent-field-label">When should other agents use this agent?</label><textarea id="agent-editor-context" rows="3" x-model="$store.agentEditor.draft.context" @input="$store.agentEditor.markMetadataSet('context')"></textarea><small>Helps Agent Zero decide when to delegate work; leaving this empty may reduce delegation quality.</small><button type="button" class="text-button" x-show="$store.agentEditor.canResetMetadata('context')" @click="$store.agentEditor.resetMetadata('context')">Reset to default</button><small class="field-status" x-show="$store.agentEditor.metadataResetPending('context')">Will reset to default on save.</small></div>
230 </div>
231 </div>
232 </section>
@@ -254,22 +256,21 @@
256 <div class="prompt-actions">
257 <button type="button" class="btn btn-action-header cancel" x-show="$store.agentEditor.promptEditPending($store.agentEditor.selectedPromptDraft)" title="Discard current edit" aria-label="Discard current edit" @click="$store.agentEditor.discardPromptEdit($store.agentEditor.selectedPrompt)"><x-icon name="close"></x-icon></button>
258 <button type="button" class="btn btn-action-header confirm" x-show="$store.agentEditor.promptEditPending($store.agentEditor.selectedPromptDraft)" title="Accept current edit" aria-label="Accept current edit" @click="$store.agentEditor.acceptPromptEdit($store.agentEditor.selectedPrompt)"><x-icon name="check"></x-icon></button>
257 - <button type="button" class="button" x-show="$store.agentEditor.selectedPromptDraft.has_override || $store.agentEditor.promptDirty($store.agentEditor.selectedPromptDraft)" @click="$store.agentEditor.resetPrompt($store.agentEditor.selectedPrompt)" x-text="`Use ${$store.agentEditor.inheritedLabel.toLowerCase()}`"></button>
258 - <button type="button" class="button icon" title="Copy customization path" aria-label="Copy customization path" @click="$store.agentEditor.copyPromptPath()"><x-icon name="content_copy"></x-icon></button>
259 + <button type="button" class="button" x-show="$store.agentEditor.selectedPromptDraft.has_override || $store.agentEditor.promptDirty($store.agentEditor.selectedPromptDraft)" @click="$store.agentEditor.resetPrompt($store.agentEditor.selectedPrompt)">Reset to default</button>
260 </div>
261 </div>
262 + <div class="prompt-customization-path"><code x-text="$store.agentEditor.promptCustomizationPath()"></code><button type="button" class="button" title="Copy customization path" aria-label="Copy customization path" @click="$store.agentEditor.copyPromptPath()"><x-icon name="content_copy"></x-icon></button></div>
263 <div class="agent-editor-note" x-show="$store.agentEditor.selectedPromptDraft.dynamic_processor">This file is generated dynamically at runtime. Python processors are read-only and are not executed by the editor.</div>
264 <div class="prompt-view-tabs" role="tablist" aria-label="Prompt view">
265 <button type="button" role="tab" :aria-selected="$store.agentEditor.comparePrompt === ''" :class="{ active: $store.agentEditor.comparePrompt === '' }" @click="$store.agentEditor.comparePrompt = ''">Your version</button>
266 <button type="button" role="tab" :aria-selected="$store.agentEditor.comparePrompt === 'inherited'" :class="{ active: $store.agentEditor.comparePrompt === 'inherited' }" @click="$store.agentEditor.comparePrompt = 'inherited'" x-text="$store.agentEditor.inheritedLabel"></button>
267 <button type="button" role="tab" :aria-selected="$store.agentEditor.comparePrompt === 'compare'" :class="{ active: $store.agentEditor.comparePrompt === 'compare' }" @click="$store.agentEditor.comparePrompt = 'compare'">Compare</button>
268 </div>
267 - <div class="prompt-find"><label><span class="sr-only">Search within prompt</span><input type="search" x-model="$store.agentEditor.promptTextSearch" placeholder="Find in file" @keydown.enter.prevent="$store.agentEditor.findInPrompt($event.shiftKey ? -1 : 1)"></label><span x-text="`${$store.agentEditor.promptMatchCount()} matches`"></span><button type="button" class="button icon" aria-label="Previous match" @click="$store.agentEditor.findInPrompt(-1)"><x-icon name="keyboard_arrow_up"></x-icon></button><button type="button" class="button icon" aria-label="Next match" @click="$store.agentEditor.findInPrompt(1)"><x-icon name="keyboard_arrow_down"></x-icon></button></div>
269 + <div class="prompt-find"><label><x-icon name="search"></x-icon><span class="sr-only">Search within prompt</span><input type="search" x-model="$store.agentEditor.promptTextSearch" placeholder="Find in file" @keydown.enter.prevent="$store.agentEditor.findInPrompt($event.shiftKey ? -1 : 1)"></label><span x-text="`${$store.agentEditor.promptMatchCount()} matches`"></span><button type="button" class="button icon prompt-match-action" aria-label="Previous match" @click="$store.agentEditor.findInPrompt(-1)"><x-icon name="keyboard_arrow_up"></x-icon></button><button type="button" class="button icon prompt-match-action" aria-label="Next match" @click="$store.agentEditor.findInPrompt(1)"><x-icon name="keyboard_arrow_down"></x-icon></button></div>
270 <div class="prompt-panes" :class="{ compare: $store.agentEditor.comparePrompt === 'compare' }">
271 <div class="prompt-pane" x-show="$store.agentEditor.comparePrompt !== 'inherited'"><label for="agent-editor-prompt-text">Your version</label><textarea id="agent-editor-prompt-text" spellcheck="false" x-model="$store.agentEditor.selectedPromptDraft.value" @input="$store.agentEditor.onPromptInput($store.agentEditor.selectedPrompt)" aria-label="Prompt Markdown" :aria-invalid="$store.agentEditor.selectedPrompt === 'agent.system.main.specifics.md' && $store.agentEditor.fieldIssue('instructions') ? 'true' : null" :aria-describedby="$store.agentEditor.selectedPrompt === 'agent.system.main.specifics.md' && $store.agentEditor.fieldIssue('instructions') ? 'agent-editor-prompt-error' : null"></textarea><span id="agent-editor-prompt-error" class="field-error" role="alert" x-show="$store.agentEditor.selectedPrompt === 'agent.system.main.specifics.md' && $store.agentEditor.fieldIssue('instructions')" x-text="$store.agentEditor.fieldIssue('instructions')?.message"></span></div>
272 <div class="prompt-pane inherited" x-show="$store.agentEditor.comparePrompt"><div class="prompt-pane-title" x-text="$store.agentEditor.inheritedLabel"></div><pre x-text="$store.agentEditor.selectedPromptDraft.inherited || '(empty)'" tabindex="0"></pre></div>
273 </div>
272 - <details class="effective-preview"><summary>Preview combined prompt</summary><p>Static preview only. Runtime variables, projects, skills, tools, secrets, time, and dynamic extensions can differ.</p><pre x-text="$store.agentEditor.selectedPromptDraft.preview || '(empty)'" tabindex="0"></pre></details>
274 </div>
275 </div>
276 </section>
@@ -281,9 +282,9 @@
282 <legend class="sr-only">Tool access selection</legend>
283 <div class="policy-filters tools"><label class="policy-search"><x-icon name="search"></x-icon><span class="sr-only">Search tools</span><input type="search" x-model="$store.agentEditor.toolSearch" placeholder="Search tools"></label><label>Category <select x-model="$store.agentEditor.toolCategory"><option value="all">All</option><option value="local">Local</option><option value="plugin">Plugin</option><option value="mcp">MCP</option></select></label><label>Origin <select x-model="$store.agentEditor.toolOrigin"><option value="all">All</option><template x-for="origin in $store.agentEditor.toolOrigins" :key="origin"><option :value="origin" x-text="origin"></option></template></select></label></div>
284 <div class="policy-lists">
284 - <section class="policy-list" aria-labelledby="allowed-tools-title"><header><div><h4 id="allowed-tools-title">Allowed</h4><span x-text="`${$store.agentEditor.filteredTools(true).length} ${$store.agentEditor.filteredTools(true).length === 1 ? 'tool' : 'tools'}`"></span></div><button type="button" class="button policy-bulk" x-show="$store.agentEditor.draft.toolPolicy.mode === 'custom' && $store.agentEditor.filteredTools(true).length" @click="$store.agentEditor.moveAllVisibleTools(false)" x-text="`Block ${$store.agentEditor.filteredTools(true).length} shown`"></button></header><div class="policy-items"><p class="policy-empty" x-show="!$store.agentEditor.filteredTools(true).length">No allowed tools — select items on the right, or allow new tools automatically.</p><template x-for="tool in $store.agentEditor.filteredTools(true)" :key="tool.id"><div class="policy-item"><input type="checkbox" :value="tool.id" x-model="$store.agentEditor.selectedAllowedTools" :aria-label="`Select ${tool.label}`"><div><strong x-text="tool.label"></strong><small x-text="tool.id"></small><p class="policy-item-description" x-show="tool.description" x-text="tool.description"></p><em x-show="!tool.available">Unavailable — kept in your settings</em></div></div></template></div></section>
285 + <section class="policy-list" aria-labelledby="allowed-tools-title"><header><div><h4 id="allowed-tools-title">Allowed</h4><span x-text="`${$store.agentEditor.filteredTools(true).length} ${$store.agentEditor.filteredTools(true).length === 1 ? 'tool' : 'tools'}`"></span></div><button type="button" class="button policy-bulk" x-show="$store.agentEditor.draft.toolPolicy.mode === 'custom' && $store.agentEditor.filteredTools(true).length" @click="$store.agentEditor.moveAllVisibleTools(false)" x-text="`Block ${$store.agentEditor.filteredTools(true).length} shown`"></button></header><div class="policy-items"><p class="policy-empty" x-show="!$store.agentEditor.filteredTools(true).length">No allowed tools — select items on the right, or allow new tools automatically.</p><template x-for="tool in $store.agentEditor.filteredTools(true)" :key="tool.id"><label class="policy-item"><input type="checkbox" :value="tool.id" x-model="$store.agentEditor.selectedAllowedTools" :aria-label="`Select ${tool.label}`"><div><strong x-text="tool.label"></strong><small x-text="tool.id"></small><p class="policy-item-description" x-show="tool.description" x-text="tool.description"></p><em x-show="!tool.available">Unavailable — kept in your settings</em></div></label></template></div></section>
286 <div class="policy-transfer-actions" role="group" aria-label="Move selected tools"><button type="button" class="button icon" :disabled="!$store.agentEditor.selectedAllowedTools.length" aria-label="Block selected tools" @click="$store.agentEditor.moveTools($store.agentEditor.selectedAllowedTools, false)"><x-icon name="arrow_forward"></x-icon></button><button type="button" class="button icon" :disabled="!$store.agentEditor.selectedBlockedTools.length" aria-label="Allow selected tools" @click="$store.agentEditor.moveTools($store.agentEditor.selectedBlockedTools, true)"><x-icon name="arrow_back"></x-icon></button></div>
286 - <section class="policy-list" aria-labelledby="blocked-tools-title"><header><div><h4 id="blocked-tools-title">Blocked</h4><span x-text="`${$store.agentEditor.filteredTools(false).length} ${$store.agentEditor.filteredTools(false).length === 1 ? 'tool' : 'tools'}`"></span></div><button type="button" class="button policy-bulk" x-show="$store.agentEditor.draft.toolPolicy.mode === 'custom' && $store.agentEditor.filteredTools(false).length" @click="$store.agentEditor.moveAllVisibleTools(true)" x-text="`Allow ${$store.agentEditor.filteredTools(false).length} shown`"></button></header><div class="policy-items"><p class="policy-empty" x-show="!$store.agentEditor.filteredTools(false).length" x-text="$store.agentEditor.draft.toolPolicy.mode === 'custom' ? 'No blocked tools — select items on the left, or block new tools until reviewed.' : 'Choose tools to customize standard access.'"></p><template x-for="tool in $store.agentEditor.filteredTools(false)" :key="tool.id"><div class="policy-item"><input type="checkbox" :value="tool.id" x-model="$store.agentEditor.selectedBlockedTools" :aria-label="`Select ${tool.label}`"><div><strong x-text="tool.label"></strong><small x-text="tool.id"></small><p class="policy-item-description" x-show="tool.description" x-text="tool.description"></p><em x-show="!tool.available">Unavailable — kept in your settings</em></div></div></template></div></section>
287 + <section class="policy-list" aria-labelledby="blocked-tools-title"><header><div><h4 id="blocked-tools-title">Blocked</h4><span x-text="`${$store.agentEditor.filteredTools(false).length} ${$store.agentEditor.filteredTools(false).length === 1 ? 'tool' : 'tools'}`"></span></div><button type="button" class="button policy-bulk" x-show="$store.agentEditor.draft.toolPolicy.mode === 'custom' && $store.agentEditor.filteredTools(false).length" @click="$store.agentEditor.moveAllVisibleTools(true)" x-text="`Allow ${$store.agentEditor.filteredTools(false).length} shown`"></button></header><div class="policy-items"><p class="policy-empty" x-show="!$store.agentEditor.filteredTools(false).length" x-text="$store.agentEditor.draft.toolPolicy.mode === 'custom' ? 'No blocked tools — select items on the left, or block new tools until reviewed.' : 'Choose tools to customize standard access.'"></p><template x-for="tool in $store.agentEditor.filteredTools(false)" :key="tool.id"><label class="policy-item"><input type="checkbox" :value="tool.id" x-model="$store.agentEditor.selectedBlockedTools" :aria-label="`Select ${tool.label}`"><div><strong x-text="tool.label"></strong><small x-text="tool.id"></small><p class="policy-item-description" x-show="tool.description" x-text="tool.description"></p><em x-show="!tool.available">Unavailable — kept in your settings</em></div></label></template></div></section>
288 </div>
289 <fieldset class="future-default"><legend>When new tools are installed later</legend><label><input type="radio" name="tool-future-default" value="allow" :checked="$store.agentEditor.draft.toolPolicy.default === 'allow'" @change="$store.agentEditor.setPolicyDefault('tool','allow')">Allow automatically</label><label><input type="radio" name="tool-future-default" value="block" :checked="$store.agentEditor.draft.toolPolicy.default === 'block'" @change="$store.agentEditor.setPolicyDefault('tool','block')">Block until reviewed</label></fieldset>
290 </fieldset>
@@ -296,9 +297,9 @@
297 <legend class="sr-only">Skill access selection</legend>
298 <div class="policy-filters skills"><label class="policy-search"><x-icon name="search"></x-icon><span class="sr-only">Search skills</span><input type="search" x-model="$store.agentEditor.skillSearch" placeholder="Search skills"></label><label>Origin <select x-model="$store.agentEditor.skillOrigin"><option value="all">All</option><template x-for="origin in $store.agentEditor.skillOrigins" :key="origin"><option :value="origin" x-text="origin"></option></template></select></label></div>
299 <div class="policy-lists">
299 - <section class="policy-list" aria-labelledby="allowed-skills-title"><header><div><h4 id="allowed-skills-title">Allowed</h4><span x-text="`${$store.agentEditor.filteredSkills(true).length} ${$store.agentEditor.filteredSkills(true).length === 1 ? 'skill' : 'skills'}`"></span></div><button type="button" class="button policy-bulk" x-show="$store.agentEditor.draft.skillPolicy.mode === 'custom' && $store.agentEditor.filteredSkills(true).length" @click="$store.agentEditor.moveAllVisibleSkills(false)" x-text="`Block ${$store.agentEditor.filteredSkills(true).length} shown`"></button></header><div class="policy-items"><p class="policy-empty" x-show="!$store.agentEditor.filteredSkills(true).length">No allowed skills — select items on the right, or allow new skills automatically.</p><template x-for="skill in $store.agentEditor.filteredSkills(true)" :key="skill.path"><div class="policy-item"><input type="checkbox" :value="skill.name" x-model="$store.agentEditor.selectedAllowedSkills" :aria-label="`Select ${skill.name}`"><div><strong x-text="skill.name"></strong><small x-text="skill.origin"></small><p class="policy-item-description" x-show="skill.description" x-text="skill.description"></p><em x-show="skill.available === false">Unavailable — kept in your settings</em><em x-show="$store.agentEditor.skillWarnings(skill).length" x-text="`Expects blocked tool: ${$store.agentEditor.skillWarnings(skill).join(', ')}`"></em></div></div></template></div></section>
300 + <section class="policy-list" aria-labelledby="allowed-skills-title"><header><div><h4 id="allowed-skills-title">Allowed</h4><span x-text="`${$store.agentEditor.filteredSkills(true).length} ${$store.agentEditor.filteredSkills(true).length === 1 ? 'skill' : 'skills'}`"></span></div><button type="button" class="button policy-bulk" x-show="$store.agentEditor.draft.skillPolicy.mode === 'custom' && $store.agentEditor.filteredSkills(true).length" @click="$store.agentEditor.moveAllVisibleSkills(false)" x-text="`Block ${$store.agentEditor.filteredSkills(true).length} shown`"></button></header><div class="policy-items"><p class="policy-empty" x-show="!$store.agentEditor.filteredSkills(true).length">No allowed skills — select items on the right, or allow new skills automatically.</p><template x-for="skill in $store.agentEditor.filteredSkills(true)" :key="skill.path"><label class="policy-item"><input type="checkbox" :value="skill.name" x-model="$store.agentEditor.selectedAllowedSkills" :aria-label="`Select ${skill.name}`"><div><strong x-text="skill.name"></strong><small x-text="skill.origin"></small><p class="policy-item-description" x-show="skill.description" x-text="skill.description"></p><em x-show="skill.available === false">Unavailable — kept in your settings</em><em x-show="$store.agentEditor.skillWarnings(skill).length" x-text="`Expects blocked tool: ${$store.agentEditor.skillWarnings(skill).join(', ')}`"></em></div></label></template></div></section>
301 <div class="policy-transfer-actions" role="group" aria-label="Move selected skills"><button type="button" class="button icon" :disabled="!$store.agentEditor.selectedAllowedSkills.length" aria-label="Block selected skills" @click="$store.agentEditor.moveSkills($store.agentEditor.selectedAllowedSkills, false)"><x-icon name="arrow_forward"></x-icon></button><button type="button" class="button icon" :disabled="!$store.agentEditor.selectedBlockedSkills.length" aria-label="Allow selected skills" @click="$store.agentEditor.moveSkills($store.agentEditor.selectedBlockedSkills, true)"><x-icon name="arrow_back"></x-icon></button></div>
301 - <section class="policy-list" aria-labelledby="blocked-skills-title"><header><div><h4 id="blocked-skills-title">Blocked</h4><span x-text="`${$store.agentEditor.filteredSkills(false).length} ${$store.agentEditor.filteredSkills(false).length === 1 ? 'skill' : 'skills'}`"></span></div><button type="button" class="button policy-bulk" x-show="$store.agentEditor.draft.skillPolicy.mode === 'custom' && $store.agentEditor.filteredSkills(false).length" @click="$store.agentEditor.moveAllVisibleSkills(true)" x-text="`Allow ${$store.agentEditor.filteredSkills(false).length} shown`"></button></header><div class="policy-items"><p class="policy-empty" x-show="!$store.agentEditor.filteredSkills(false).length" x-text="$store.agentEditor.draft.skillPolicy.mode === 'custom' ? 'No blocked skills — select items on the left, or block new skills until reviewed.' : 'Choose skills to customize standard access.'"></p><template x-for="skill in $store.agentEditor.filteredSkills(false)" :key="skill.path"><div class="policy-item"><input type="checkbox" :value="skill.name" x-model="$store.agentEditor.selectedBlockedSkills" :aria-label="`Select ${skill.name}`"><div><strong x-text="skill.name"></strong><small x-text="skill.origin"></small><p class="policy-item-description" x-show="skill.description" x-text="skill.description"></p><em x-show="skill.available === false">Unavailable — kept in your settings</em></div></div></template></div></section>
302 + <section class="policy-list" aria-labelledby="blocked-skills-title"><header><div><h4 id="blocked-skills-title">Blocked</h4><span x-text="`${$store.agentEditor.filteredSkills(false).length} ${$store.agentEditor.filteredSkills(false).length === 1 ? 'skill' : 'skills'}`"></span></div><button type="button" class="button policy-bulk" x-show="$store.agentEditor.draft.skillPolicy.mode === 'custom' && $store.agentEditor.filteredSkills(false).length" @click="$store.agentEditor.moveAllVisibleSkills(true)" x-text="`Allow ${$store.agentEditor.filteredSkills(false).length} shown`"></button></header><div class="policy-items"><p class="policy-empty" x-show="!$store.agentEditor.filteredSkills(false).length" x-text="$store.agentEditor.draft.skillPolicy.mode === 'custom' ? 'No blocked skills — select items on the left, or block new skills until reviewed.' : 'Choose skills to customize standard access.'"></p><template x-for="skill in $store.agentEditor.filteredSkills(false)" :key="skill.path"><label class="policy-item"><input type="checkbox" :value="skill.name" x-model="$store.agentEditor.selectedBlockedSkills" :aria-label="`Select ${skill.name}`"><div><strong x-text="skill.name"></strong><small x-text="skill.origin"></small><p class="policy-item-description" x-show="skill.description" x-text="skill.description"></p><em x-show="skill.available === false">Unavailable — kept in your settings</em></div></label></template></div></section>
303 </div>
304 <fieldset class="future-default"><legend>When new skills are installed later</legend><label><input type="radio" name="skill-future-default" :checked="$store.agentEditor.draft.skillPolicy.default === 'allow'" @change="$store.agentEditor.setPolicyDefault('skill','allow')">Allow automatically</label><label><input type="radio" name="skill-future-default" :checked="$store.agentEditor.draft.skillPolicy.default === 'block'" @change="$store.agentEditor.setPolicyDefault('skill','block')">Block until reviewed</label></fieldset>
305 </fieldset>
@@ -314,6 +315,10 @@
315 <ul><template x-for="issue in $store.agentEditor.validationIssues()" :key="`${issue.section}-${issue.field}-${issue.message}`"><li><button type="button" class="text-button" @click="$store.agentEditor.showValidationIssue(issue)" x-text="`${issue.label}: ${issue.message} — fix`"></button></li></template></ul>
316 </div>
317 </div>
318 + <section class="review-identity" aria-label="Agent identity" x-show="$store.agentEditor.planStatus === 'ready'">
319 + <div class="review-identity-avatar" aria-hidden="true" :style="`background:${$store.agentEditor.avatarColor()}`"><img x-show="$store.agentEditor.draft.avatar?.kind === 'image' && $store.agentEditor.draft.avatarPreview" :src="$store.agentEditor.draft.avatarPreview" alt=""><span x-show="$store.agentEditor.draft.avatar?.kind !== 'image' || !$store.agentEditor.draft.avatarPreview" x-text="$store.agentEditor.initials()"></span></div>
320 + <div><strong x-text="$store.agentEditor.draft.title || $store.agentEditor.draft.profileId"></strong><p x-text="$store.agentEditor.draft.description || 'No description'"></p></div>
321 + </section>
322 <div class="change-plan" aria-live="polite" x-show="$store.agentEditor.planStatus === 'ready'">
323 <section><h4>Will create or update</h4><template x-if="!$store.agentEditor.plan.written?.length"><p>None</p></template><ul><template x-for="path in $store.agentEditor.plan.written || []" :key="path"><li><code x-text="path"></code></li></template></ul></section>
324 <section><h4>Will delete</h4><template x-if="!$store.agentEditor.plan.deleted?.length"><p>None</p></template><ul><template x-for="path in $store.agentEditor.plan.deleted || []" :key="path"><li><code x-text="path"></code></li></template></ul></section>
@@ -334,7 +339,10 @@
339
340 <div class="modal-footer agent-editor-footer" data-modal-footer x-show="!$store.agentEditor.loading && $store.agentEditor.view === 'editor' && !$store.agentEditor.pendingMutation">
341 <div class="footer-left">
337 - <button type="button" class="btn btn-cancel" @click="window.closeModal?.()">Cancel</button>
342 + <button type="button" class="btn btn-cancel agent-reset-profile"
343 + x-show="$store.agentEditor.draft && !$store.agentEditor.draft.creating && $store.agentEditor.state?.profile && !$store.agentEditor.state.profile.deletable"
344 + :disabled="$store.agentEditor.saving || !$store.agentEditor.state?.profile?.scope_has_overrides"
345 + @click="$store.agentEditor.restoreProfile($store.agentEditor.state.profile)">Reset to default</button>
346 </div>
347 <div class="footer-actions">
348 <button type="button" class="btn agent-editor-secondary-action" x-show="$store.agentEditor.view === 'editor' && !$store.agentEditor.draft?.creating" @click="$store.agentEditor.save(true)" :disabled="$store.agentEditor.saving || $store.agentEditor.validationIssues().length > 0">Save & test</button>
@@ -371,6 +379,8 @@
379 .agent-editor-topbar { display:flex; align-items:center; justify-content:space-between; gap:1rem; margin-bottom:1rem; }
380 .agent-editor-heading { display:flex; flex:1 1 auto; flex-wrap:wrap; align-items:center; gap:.6rem; min-width:0; }
381 .agent-editor-subtitle { margin-top:.2rem; font-size:.78rem; color:var(--color-text-secondary); }
382 + .agent-editor-topbar-actions { display:flex; align-items:center; gap:.75rem; margin-left:auto; }
383 + .agent-editor .agent-reset-profile { --agent-editor-action:var(--color-primary); gap:.25rem; white-space:nowrap; }
384 .agent-status-badge { display:inline-flex; align-items:center; gap:.25rem; max-width:100%; padding:.2rem .45rem; border:1px solid var(--color-border); border-radius:999px; font-size:.7rem; font-weight:500; line-height:1.2; white-space:normal; }
385 .agent-status-badge x-icon { flex:0 0 auto; font-size:.85rem; }
386 .agent-status-badge.is-customized,.agent-status-badge.is-unsaved { color:var(--agent-editor-change); border-color:color-mix(in srgb,var(--agent-editor-change) 44%,var(--color-border)); background:color-mix(in srgb,var(--agent-editor-change) 8%,transparent); }
@@ -429,10 +439,12 @@
439 .agent-model-preset-picker { width:100%; display:grid; grid-template-columns:minmax(13rem,18rem) auto; align-items:center; justify-content:space-between; gap:1rem; }
440 .agent-model-preset-picker select { width:100%; }
441 .agent-model-preset-picker > .button { display:inline-flex; align-items:center; gap:.35rem; }
432 - .prompt-workspace { display:grid; grid-template-columns:minmax(13rem,16rem) minmax(0,1fr); gap:.8rem; min-width:0; height:34rem; }
442 + .prompt-workspace { display:grid; grid-template-columns:minmax(13rem,16rem) minmax(0,1fr); gap:.8rem; min-width:0; height:clamp(34rem,65vh,58rem); }
443 .prompt-browser { display:flex; flex-direction:column; min-width:0; min-height:0; height:100%; border:1px solid var(--color-border); border-radius:10px; overflow:hidden; }
434 - .compact-search { display:flex; align-items:center; gap:.3rem; padding:.45rem; border-bottom:1px solid var(--color-border); }
435 - .agent-editor .compact-search input,.agent-editor .policy-search input { min-height:1.8rem; padding:.2rem; border:0; background:transparent; }
444 + .compact-search,.policy-search,.prompt-find label { display:grid; grid-template-columns:1.25rem minmax(0,1fr); align-items:center; gap:.3rem; min-width:0; height:2.125rem; padding:0 .45rem; border:1px solid color-mix(in srgb,var(--color-border) 42%,transparent); border-radius:7px; background:color-mix(in srgb,var(--color-text) 10%,transparent); color:var(--color-text-secondary); }
445 + .compact-search { margin:.45rem; }
446 + .compact-search:focus-within,.policy-search:focus-within,.prompt-find label:focus-within { border-color:color-mix(in srgb,var(--color-primary) 46%,var(--color-border)); background:color-mix(in srgb,var(--color-text) 13%,transparent); }
447 + .agent-editor .compact-search input,.agent-editor .policy-search input,.agent-editor .prompt-find label input { height:100%; min-height:0; padding:0; border:0; outline:0; appearance:none; background:transparent; }
448 .prompt-file-list { flex:1; overflow:auto; padding:.35rem; }
449 .prompt-file-group { min-width:0; margin-bottom:.55rem; }
450 .prompt-file-group h4 { padding:.35rem .5rem .25rem; color:var(--color-text-secondary); font-size:.7rem; font-weight:650; letter-spacing:.03em; text-transform:uppercase; }
@@ -447,23 +459,24 @@
459 .source-chain { margin-top:.2rem; color:var(--color-text-secondary); font-size:.75rem; }
460 .prompt-actions { display:flex; flex-wrap:wrap; justify-content:flex-end; gap:.35rem; min-width:0; }
461 .prompt-actions .button { max-width:100%; white-space:normal; overflow-wrap:anywhere; }
462 + .prompt-customization-path { display:flex; align-items:center; justify-content:flex-end; gap:.4rem; min-width:0; }
463 + .prompt-customization-path code { min-width:0; color:var(--color-text-secondary); font-size:.72rem; overflow-wrap:anywhere; text-align:right; }
464 + .prompt-customization-path .button { width:2.125rem; height:2.125rem; flex:0 0 auto; padding:0; }
465 .prompt-view-tabs { display:flex; flex-wrap:wrap; gap:.25rem; padding-bottom:.35rem; border-bottom:1px solid var(--color-border); }
466 .prompt-view-tabs button { min-height:2rem; padding:.35rem .65rem; border:0; border-radius:6px; background:transparent; color:var(--color-text-secondary); }
467 .prompt-view-tabs button.active { background:var(--color-input); color:var(--color-text); }
468 .prompt-find { display:flex; flex-wrap:wrap; align-items:center; gap:.35rem; min-width:0; }
469 .prompt-find label { flex:1 1 10rem; min-width:0; }
470 .prompt-find span { color:var(--color-text-secondary); font-size:.75rem; white-space:nowrap; }
471 + .prompt-find .prompt-match-action { flex:0 0 2.125rem; width:2.125rem; height:2.125rem; min-height:2.125rem; padding:0; border-radius:7px; justify-content:center; }
472 .prompt-panes { display:grid; grid-template-columns:1fr; gap:.65rem; min-height:18rem; }
473 .prompt-panes.compare { grid-template-columns:1fr 1fr; }
474 .prompt-pane { min-width:0; min-height:0; display:flex; flex-direction:column; gap:.35rem; }
475 .prompt-pane label,.prompt-pane-title { color:var(--color-text-secondary); font-size:.75rem; }
460 - .prompt-pane textarea,.prompt-pane pre,.effective-preview pre { flex:1; box-sizing:border-box; width:100%; min-height:0; margin:0; padding:.75rem; overflow:auto; border:1px solid var(--color-border); border-radius:8px; background:var(--color-input); color:var(--color-text); font:13px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace; white-space:pre-wrap; tab-size:2; resize:vertical; }
476 + .prompt-pane textarea,.prompt-pane pre { flex:1; box-sizing:border-box; width:100%; min-height:0; margin:0; padding:.75rem; overflow:auto; border:1px solid var(--color-border); border-radius:8px; background:var(--color-input); color:var(--color-text); font:13px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace; white-space:pre-wrap; tab-size:2; resize:vertical; }
477 .prompt-pane textarea:focus-visible { outline-offset:-2px; }
478 .prompt-pane.inherited pre { opacity:.86; }
463 - .effective-preview { border:1px solid var(--color-border); border-radius:8px; padding:.55rem .7rem; }
464 - .effective-preview > summary,.profile-maintenance summary { display:flex; align-items:center; min-height:1.5rem; cursor:pointer; }
465 - .effective-preview p { margin:.5rem 0; color:var(--color-text-secondary); font-size:.78rem; }
466 - .effective-preview pre { min-height:10rem; max-height:25rem; resize:none; }
479 + .profile-maintenance summary { display:flex; align-items:center; min-height:1.5rem; cursor:pointer; }
480 .policy-mode-row,.future-default { display:flex; flex-wrap:wrap; align-items:center; gap:.7rem 1rem; padding:.7rem; border:1px solid var(--color-border); border-radius:9px; }
481 .policy-mode-row label,.future-default label { display:flex; gap:.35rem; align-items:center; }
482 .policy-mode-row small { color:var(--color-text-secondary); }
@@ -475,12 +488,11 @@
488 .policy-filters.skills { grid-template-columns:minmax(12rem,1fr) minmax(8rem,auto); }
489 .policy-filters label { display:flex; align-items:center; gap:.35rem; font-size:.78rem; color:var(--color-text-secondary); }
490 .policy-filters select { flex:1; }
478 - .policy-search { min-height:2.25rem; padding:.25rem .5rem; border:1px solid var(--color-border); border-radius:7px; background:var(--color-input); color:var(--color-text-secondary); }
491 .policy-lists { display:grid; grid-template-columns:minmax(0,1fr) 2.5rem minmax(0,1fr); gap:.55rem; }
492 .policy-transfer-actions { display:flex; flex-direction:column; align-items:center; justify-content:center; gap:.5rem; }
493 .policy-transfer-actions .button { width:2.5rem; height:2.5rem; padding:0; justify-content:center; }
494 .policy-list { display:flex; flex-direction:column; min-width:0; min-height:28rem; border:1px solid var(--color-border); border-radius:10px; overflow:hidden; }
483 - .policy-list header { display:flex; flex-wrap:wrap; justify-content:space-between; gap:.5rem; padding:.7rem; border-bottom:1px solid var(--color-border); background:var(--color-input); }
495 + .policy-list header { display:flex; flex-wrap:wrap; align-items:center; justify-content:space-between; gap:.5rem; min-height:3rem; box-sizing:border-box; padding:.7rem; border-bottom:1px solid var(--color-border); background:var(--color-input); }
496 .policy-list h4 { display:inline; margin-right:.35rem; }
497 .policy-list header span { color:var(--color-text-secondary); font-size:.72rem; }
498 .policy-items { flex:1; max-height:32rem; overflow:auto; padding:.35rem; }
@@ -491,7 +503,13 @@
503 .policy-items em { color:var(--color-warning); font-size:.72rem; font-style:normal; }
504 .policy-item-description { margin:0; color:var(--color-text-secondary); font-size:.75rem; white-space:pre-wrap; overflow-wrap:anywhere; }
505 .policy-empty { padding:1rem .7rem; color:var(--color-text-secondary); font-size:.8rem; text-align:center; }
494 - .policy-bulk { min-height:2rem; padding:.25rem .55rem; white-space:normal; }
506 + .agent-editor .policy-bulk { min-height:1.5rem; padding:.1rem .45rem; font-size:.72rem; line-height:1.15; white-space:normal; }
507 + .review-identity { display:grid; grid-template-columns:3.25rem minmax(0,1fr); align-items:center; gap:.8rem; padding:.8rem; border-bottom:1px solid var(--color-border); }
508 + .review-identity-avatar { width:3.25rem; aspect-ratio:1; display:grid; place-items:center; overflow:hidden; border-radius:10px; color:#fff; font-weight:700; }
509 + .review-identity-avatar img { width:100%; height:100%; object-fit:cover; }
510 + .review-identity > div:last-child { display:flex; flex-direction:column; gap:.15rem; min-width:0; }
511 + .review-identity strong,.review-identity p { min-width:0; overflow-wrap:anywhere; }
512 + .review-identity p { color:var(--color-text-secondary); font-size:.8rem; }
513 .change-plan { display:grid; grid-template-columns:1fr 1fr; gap:.8rem; }
514 .change-plan section { padding:.85rem; border:1px solid var(--color-border); border-radius:9px; background:var(--color-input); min-width:0; }
515 .change-plan ul { margin:.55rem 0 0; padding-left:1.2rem; }
@@ -543,13 +561,15 @@
561 .toast-link { border:0; background:transparent; color:var(--agent-editor-action); text-decoration:underline; cursor:pointer; }
562 .agent-editor :focus-visible { outline:2px solid var(--agent-editor-action); outline-offset:2px; }
563 .modal-inner.agent-editor-easy { width:min(720px,calc(100vw - 2rem)); max-width:720px; }
546 - .modal-inner.agent-editor-advanced { width:calc(100vw - 2rem); max-width:none; height:calc(100vh - 2rem); }
564 + .modal-inner.agent-editor-advanced { width:92vw; max-width:none; height:min(calc(100vh - 4rem),80rem); max-height:calc(100vh - 4rem); }
565 + .modal-inner.agent-editor-advanced .modal-scroll { max-height:none; }
566 .modal-inner.agent-editor-advanced .modal-bd { min-height:0; }
567 .modal-inner.agent-editor-advanced .agent-editor,.modal-inner.agent-editor-advanced .agent-editor-workspace { min-height:100%; }
568 @media (max-width: 760px) {
569 .modal-inner.agent-editor-easy,.modal-inner.agent-editor-advanced { width:100vw; max-width:none; height:100vh; max-height:none; border-radius:0; }
570 .agent-editor-topbar { align-items:flex-start; flex-wrap:wrap; }
571 .agent-editor-heading { width:100%; }
572 + .agent-editor-topbar-actions { width:100%; flex-wrap:wrap; justify-content:flex-end; }
573 .agent-editor-scope { flex:1 1 14rem; }
574 .agent-easy-identity { grid-template-columns:1fr; justify-items:center; padding-top:1.8rem; }
575 .agent-name-field { width:100%; }
@@ -570,12 +590,13 @@
590 .agent-manager-list-header { flex-wrap:wrap; }
591 .active-agent-display { width:100%; }
592 .agent-manager-create { margin-left:auto; }
573 - .agent-manager-card { grid-template-columns:3rem minmax(0,1fr); }
574 - .agent-manager-actions { grid-column:1/-1; justify-content:flex-end; }
593 + .agent-manager-card { grid-template-columns:3rem minmax(0,1fr) auto; align-items:start; }
594 + .agent-manager-actions { grid-column:auto; align-self:stretch; display:grid; grid-template-columns:auto auto; align-content:space-between; justify-content:end; justify-items:end; }
595 + .agent-manager-actions .agent-profile-availability { grid-column:1/-1; }
596 .agent-scope-field { flex-basis:100%; min-width:0; }
597 .agent-editor-footer { flex-wrap:wrap; }
577 - .footer-actions { width:100%; }
578 - .footer-actions .btn { flex:1; }
598 + .footer-actions { width:auto; max-width:100%; flex-wrap:wrap; justify-content:flex-end; }
599 + .footer-actions .btn { flex:0 1 auto; text-align:center; }
600 }
601 </style>
602 </body>
tests/test_agent_editor_webui.py
+62 -7
@@ -117,8 +117,24 @@ def test_agent_editor_surface_has_normative_entry_points_and_accessible_controls
117 assert ':aria-label="`Duplicate ${profile.title || profile.id}`"' in modal
118 assert "$store.agentEditor.duplicateProfile(profile)" in modal
119 assert 'x-show="profile.scope_has_overrides && !profile.deletable"' in modal
120 - assert ':aria-label="`Restore original ${profile.title || profile.id}`"' in modal
120 + assert ':aria-label="`Reset ${profile.title || profile.id} to default`"' in modal
121 + assert "Restore original" not in modal
122 + assert modal.count("Reset to default") >= 6
123 assert "$store.agentEditor.restoreProfile(profile)" in modal
124 + assert "$store.agentEditor.restoreProfile($store.agentEditor.state.profile)" in modal
125 + assert 'class="agent-editor-topbar-actions"' in modal
126 + topbar = modal.split('class="agent-editor-topbar-actions"', 1)[1].split("</header>", 1)[0]
127 + footer = modal.split('class="modal-footer agent-editor-footer"', 1)[1]
128 + assert "Reset to default" not in topbar
129 + assert "$store.agentEditor.restoreProfile($store.agentEditor.state.profile)" in footer
130 + assert 'class="btn btn-cancel agent-reset-profile"' in footer
131 + assert '<x-icon name="restore"></x-icon><span>Reset to default</span>' not in footer
132 + assert "x-show=\"$store.agentEditor.draft && !$store.agentEditor.draft.creating" in footer
133 + assert ':disabled="$store.agentEditor.saving || !$store.agentEditor.state?.profile?.scope_has_overrides"' in footer
134 + avatar_reset = 'x-show="$store.agentEditor.state.profile.metadata.avatar.has_override || $store.agentEditor.draft.avatar" @click="$store.agentEditor.resetAvatar()">Reset</button>'
135 + assert modal.count(avatar_reset) == 2
136 + assert '@click="$store.agentEditor.resetAvatar()">Remove</button>' not in modal
137 + assert 'class="btn btn-cancel" @click="window.closeModal?.()">Cancel</button>' not in modal
138 assert 'class="toggle agent-profile-availability"' in modal
139 assert ':disabled="$store.agentEditor.profileAvailabilitySaving"' in modal
140 assert "Default is always available" not in modal
@@ -152,7 +168,19 @@ def test_agent_editor_surface_has_normative_entry_points_and_accessible_controls
168 assert "policy-description" not in modal and "-webkit-line-clamp:2" not in modal
169 assert 'class="prompt-file-list" role="region" aria-label="Prompt file list" tabindex="0"' in modal
170 assert 'class="prompt-editor" role="region" aria-label="Selected prompt file" tabindex="0"' in modal
171 + assert "Preview combined prompt" not in modal
172 + assert 'class="prompt-customization-path"' in modal
173 + assert "$store.agentEditor.promptCustomizationPath()" in modal
174 + assert 'class="review-identity"' in modal
175 + assert 'aria-label="Agent identity"' in modal
176 + assert 'id="review-identity-title"' not in modal
177 + review_identity = modal[modal.index('class="review-identity"'):modal.index('class="change-plan"')]
178 + assert "draft.profileId\"></code>" not in review_identity
179 + assert "width:92vw" in modal
180 + assert ".modal-inner.agent-editor-advanced .modal-scroll { max-height:none; }" in modal
181 assert 'width:1.5rem; height:1.5rem' in modal
182 + assert modal.count('class="button icon prompt-match-action"') == 2
183 + assert "min-height:3rem" in modal and "font-size:.72rem" in modal
184 assert "moveAllVisibleTools(false)" in modal and "moveAllVisibleSkills(false)" in modal
185 assert 'class="agent-editor-heading"' in modal
186 assert ':aria-invalid=' in modal
@@ -166,9 +194,9 @@ def test_agent_editor_surface_has_normative_entry_points_and_accessible_controls
194 assert 'input[type="checkbox"]' in modal and "appearance:none" in modal
195 assert 'promptDisplayState(prompt)' in modal
196 assert 'promptSourceChain($store.agentEditor.selectedPromptDraft)' in modal
169 - assert "Will reset to inherited on save." in modal
170 - for key in ("title", "description"):
171 - assert f'x-show="$store.agentEditor.metadataProvenance(\'{key}\')"' in modal
197 + assert "Will reset to default on save." in modal
198 + assert "metadataProvenance('description')" not in modal
199 + assert 'x-show="$store.agentEditor.metadataProvenance(\'title\')"' in modal
200 assert 'metadataProvenance(\'context\')' not in modal
201 assert ':title="prompt.filename"' not in modal
202 assert "promptEditPending($store.agentEditor.selectedPromptDraft)" in modal
@@ -211,6 +239,13 @@ def test_agent_editor_surface_has_normative_entry_points_and_accessible_controls
239 assert 'x-show="!$store.modelConfig.agentProfilesLoading"' in switcher
240 assert "@keydown.ctrl.s.prevent" in modal
241 assert "@media (max-width: 760px)" in modal
242 + assert modal.count('<label class="policy-item"><input type="checkbox"') == 4
243 + assert '<div class="policy-item"><input type="checkbox"' not in modal
244 + assert ".agent-manager-card { grid-template-columns:3rem minmax(0,1fr) auto; align-items:start; }" in modal
245 + assert ".agent-manager-actions { grid-column:auto; align-self:stretch; display:grid;" in modal
246 + assert ".agent-manager-actions .agent-profile-availability { grid-column:1/-1; }" in modal
247 + assert ".footer-actions { width:auto; max-width:100%; flex-wrap:wrap; justify-content:flex-end; }" in modal
248 + assert ".footer-actions .btn { flex:0 1 auto; text-align:center; }" in modal
249 assert modal.count("data-modal-footer") == 1
250 assert ">Close</button>" not in modal
251
@@ -336,6 +371,13 @@ if (store.metadataProvenance("title") !== "Inherited from Researcher") throw new
371 store.state.profile.metadata.title.has_override = true;
372 if (store.metadataProvenance("title") !== "Customized by you") throw new Error("custom provenance mismatch");
373 store.draft.creating = true;
374 +store.draft.profileId = "researcher";
375 +store.selectedPrompt = "agent.system.main.specifics.md";
376 +store.projectName = "";
377 +if (store.promptCustomizationPath() !== "usr/agents/researcher/prompts/agent.system.main.specifics.md") throw new Error("Global prompt customization path mismatch");
378 +store.projectName = "demo";
379 +if (store.promptCustomizationPath() !== "usr/projects/demo/.a0proj/agents/researcher/prompts/agent.system.main.specifics.md") throw new Error("project prompt customization path mismatch");
380 +store.projectName = "";
381 store.state.tools.catalog = [
382 { id: "local:shell", name: "shell", label: "Shell", origin: "Agent Zero", available: true },
383 { id: "local:gone", name: "gone", label: "Gone", origin: "Unavailable", available: false },
@@ -403,11 +445,24 @@ if (!calls.some(item => item.payload?.action === "duplicate" && item.payload.pro
445 calls.length = 0;
446 confirmResult = false;
447 await store.restoreProfile({ id: "researcher", title: "Researcher", scope_has_overrides: true, deletable: false });
406 -if (calls.length || confirmations.at(-1)?.title !== "Restore Researcher?") throw new Error("restore cancellation failed");
448 +if (calls.length || confirmations.at(-1)?.title !== "Reset Researcher to default?") throw new Error("restore cancellation failed");
449 confirmResult = true;
450 await store.restoreProfile({ id: "researcher", title: "Researcher", scope_has_overrides: true, deletable: false });
409 -if (!calls.some(item => item.payload?.action === "remove_changes" && item.payload.profile_id === "researcher" && item.payload.destructive === false)) throw new Error("restore original did not use sparse removal");
410 -if (!calls.some(item => item.endpoint === "loadAgentProfiles" && item.payload === true) || store.saving) throw new Error("restore original did not refresh profile state");
451 +if (!calls.some(item => item.payload?.action === "remove_changes" && item.payload.profile_id === "researcher" && item.payload.destructive === false)) throw new Error("reset to default did not use sparse removal");
452 +if (!calls.some(item => item.endpoint === "loadAgentProfiles" && item.payload === true) || store.saving) throw new Error("reset to default did not refresh profile state");
453 +let editorReload = null;
454 +const loadEditor = store.loadEditor;
455 +const setMode = store.setMode;
456 +store.view = "editor";
457 +store.mode = "advanced";
458 +store.section = "3";
459 +store.loadEditor = async (...args) => { editorReload = { args }; };
460 +store.setMode = (...args) => { editorReload.mode = args; };
461 +await store.restoreProfile({ id: "researcher", title: "Researcher", scope_has_overrides: true, deletable: false });
462 +if (editorReload?.args.join(":") !== "researcher:false" || editorReload.mode.join(":") !== "advanced:3:false") throw new Error("editor reset did not restore its surface");
463 +store.loadEditor = loadEditor;
464 +store.setMode = setMode;
465 +store.view = "manage";
466 confirmations.length = 0;
467 confirmResult = false;
468 store.projectName = "other";