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";