main
html 614 lines 71.6 KB
Raw
1 <html>
2 <head>
3 <title>Agent Editor</title>
4 <script type="module">
5 import { store } from "/plugins/_agent_editor/webui/agent-editor-store.js";
6 </script>
7 </head>
8 <body>
9 <div x-data class="agent-editor" x-init="await $store.agentEditor.mount($el)"
10 @keydown.ctrl.s.prevent="$store.agentEditor.save(false)"
11 @keydown.meta.s.prevent="$store.agentEditor.save(false)">
12 <template x-if="$store.agentEditor.error">
13 <div class="agent-editor-error" role="alert">
14 <x-icon name="error"></x-icon>
15 <span x-text="$store.agentEditor.error"></span>
16 <button type="button" class="button icon" aria-label="Dismiss error" @click="$store.agentEditor.error = ''"><x-icon name="close"></x-icon></button>
17 </div>
18 </template>
19
20 <template x-if="$store.agentEditor.loading">
21 <div class="agent-editor-loading" role="status">
22 <x-icon class="spinning" name="progress_activity"></x-icon>
23 <span>Loading agent…</span>
24 </div>
25 </template>
26
27 <template x-if="!$store.agentEditor.loading && $store.agentEditor.view === 'manage'">
28 <section class="agent-manager" aria-label="Manage agents">
29 <div class="agent-scope-selector">
30 <label class="agent-scope-field">
31 <span>Project</span>
32 <select x-model="$store.agentEditor.projectName"
33 x-init="$nextTick(() => $el.value = $store.agentEditor.projectName)"
34 @change="$store.agentEditor.onScopeChanged()">
35 <option value="">Global</option>
36 <template x-for="project in $store.agentEditor.projects" :key="project.key">
37 <option :value="project.key" x-text="project.label"></option>
38 </template>
39 </select>
40 </label>
41 </div>
42 <div class="agent-manager-list-header">
43 <template x-if="$store.agentEditor.activeProfile()">
44 <div class="active-agent-display">
45 <span class="active-agent-summary">
46 <span>Active:</span>
47 <span class="active-agent-avatar" aria-hidden="true" :style="`background:${$store.agentEditor.profileVisual($store.agentEditor.activeProfile()).color}`">
48 <img x-show="$store.agentEditor.profileVisual($store.agentEditor.activeProfile()).url" :src="$store.agentEditor.profileVisual($store.agentEditor.activeProfile()).url" alt="">
49 <span x-show="!$store.agentEditor.profileVisual($store.agentEditor.activeProfile()).url" x-text="$store.agentEditor.profileVisual($store.agentEditor.activeProfile()).initials"></span>
50 </span>
51 <strong x-text="$store.agentEditor.activeProfile().title || $store.agentEditor.activeProfile().id"></strong>
52 </span>
53 <button type="button" class="button icon-button" x-show="$store.agentEditor.activeProfile().id !== 'default'" title="Edit" :aria-label="`Edit ${$store.agentEditor.activeProfile().title || $store.agentEditor.activeProfile().id}`" @click="$store.agentEditor.loadEditor($store.agentEditor.activeProfile().id, false)"><x-icon class="icon" name="edit"></x-icon></button>
54 </div>
55 </template>
56 <button type="button" class="button agent-manager-create" @click="$store.agentEditor.loadEditor('new-agent', true)"><x-icon class="icon" name="add"></x-icon>Create agent</button>
57 </div>
58 <div class="agent-manager-list">
59 <template x-for="profile in $store.agentEditor.visibleProfiles" :key="profile.id">
60 <article class="agent-manager-card">
61 <div class="agent-manager-avatar" :style="`background:${$store.agentEditor.profileVisual(profile).color}`">
62 <img x-show="$store.agentEditor.profileVisual(profile).url" :src="$store.agentEditor.profileVisual(profile).url" :alt="`${profile.title || profile.id} avatar`">
63 <span x-show="!$store.agentEditor.profileVisual(profile).url" x-text="$store.agentEditor.profileVisual(profile).initials"></span>
64 </div>
65 <div class="agent-manager-copy">
66 <div class="agent-manager-name">
67 <strong x-text="profile.title || profile.id"></strong>
68 <span class="agent-origin" x-text="profile.origin"></span>
69 </div>
70 <p x-text="profile.description || 'No description'"></p>
71 <button type="button" class="text-button agent-manager-inline-action"
72 :disabled="!!$store.agentEditor.duplicatingProfile"
73 :aria-label="`Duplicate ${profile.title || profile.id}`"
74 @click="$store.agentEditor.duplicateProfile(profile)">
75 <x-icon name="content_copy"></x-icon><span>Duplicate</span>
76 </button>
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="`Reset ${profile.title || profile.id} to default`"
81 @click="$store.agentEditor.restoreProfile(profile)">
82 <x-icon name="restore"></x-icon><span>Reset to default</span>
83 </button>
84 </div>
85 <div class="agent-manager-actions">
86 <label class="toggle agent-profile-availability"
87 :title="`${profile.enabled ? 'Make unavailable' : 'Make available'} in ${$store.agentEditor.scopeLabel}`">
88 <input type="checkbox"
89 :checked="profile.enabled"
90 :disabled="$store.agentEditor.profileAvailabilitySaving"
91 :aria-label="`${profile.enabled ? 'Disable' : 'Enable'} ${profile.title || profile.id} in ${$store.agentEditor.scopeLabel}`"
92 @change="$store.agentEditor.setProfileEnabled(profile, $event.target.checked)"
93 @click.stop>
94 <span class="toggler"></span>
95 </label>
96 <button type="button" class="button icon-button" title="Edit" :aria-label="`Edit ${profile.title || profile.id}`" @click="$store.agentEditor.loadEditor(profile.id, false)"><x-icon class="icon" name="edit"></x-icon></button>
97 <button type="button" class="button cancel icon-button" x-show="profile.deletable" title="Delete" :aria-label="`Delete ${profile.title || profile.id}`" @click="$store.agentEditor.deleteProfile(profile.id)"><x-icon class="icon" name="delete"></x-icon></button>
98 </div>
99 </article>
100 </template>
101 </div>
102 </section>
103 </template>
104
105 <template x-if="!$store.agentEditor.loading && $store.agentEditor.view === 'editor' && $store.agentEditor.draft">
106 <div class="agent-editor-workspace">
107 <header class="agent-editor-topbar">
108 <div class="agent-editor-heading">
109 <button type="button" class="button icon" x-show="$store.agentEditor.intent.view === 'manage'" aria-label="Back to agents" @click="$store.agentEditor.showManager()"><x-icon name="arrow_back"></x-icon></button>
110 <label class="agent-scope-field agent-editor-scope">
111 <span>Project</span>
112 <select x-model="$store.agentEditor.projectName"
113 x-init="$nextTick(() => $el.value = $store.agentEditor.projectName)"
114 @change="$store.agentEditor.onScopeChanged()">
115 <option value="">Global</option>
116 <template x-for="project in $store.agentEditor.projects" :key="project.key">
117 <option :value="project.key" x-text="project.label"></option>
118 </template>
119 </select>
120 </label>
121 <div class="agent-editor-subtitle" x-show="$store.agentEditor.dirty">
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-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
133 <main class="agent-easy" x-show="$store.agentEditor.mode === 'easy'">
134 <section class="agent-easy-identity">
135 <div class="agent-avatar-wrap">
136 <div class="agent-avatar" :style="`background:${$store.agentEditor.avatarColor()}`">
137 <img x-show="$store.agentEditor.draft.avatar?.kind === 'image' && $store.agentEditor.draft.avatarPreview" :src="$store.agentEditor.draft.avatarPreview" alt="Avatar crop preview">
138 <span x-show="$store.agentEditor.draft.avatar?.kind !== 'image' || !$store.agentEditor.draft.avatarPreview" x-text="$store.agentEditor.initials()"></span>
139 <div class="avatar-progress" x-show="$store.agentEditor.avatarUploading" aria-label="Uploading avatar"><x-icon class="spinning" name="progress_activity"></x-icon></div>
140 </div>
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>
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-easy-identity-fields">
148 <div class="agent-field agent-name-field">
149 <label for="agent-editor-name" class="agent-field-label">Agent name</label>
150 <input id="agent-editor-name" type="text" x-model="$store.agentEditor.draft.title" @input="$store.agentEditor.onNameInput(); $store.agentEditor.markMetadataSet('title')" required autocomplete="off" :aria-invalid="$store.agentEditor.fieldIssue('name') ? 'true' : null" :aria-describedby="$store.agentEditor.fieldIssue('name') ? 'agent-editor-name-error' : null">
151 <div class="agent-id-feedback" x-show="$store.agentEditor.fieldIssue('name')">
152 <span id="agent-editor-name-error" class="field-error" role="alert" x-text="$store.agentEditor.fieldIssue('name')?.message"></span>
153 <button type="button" class="text-button" x-show="$store.agentEditor.profileConflict" @click="$store.agentEditor.openConflictingProfile()">Open existing agent</button>
154 </div>
155 </div>
156 <div class="agent-field agent-model-preset">
157 <label for="agent-editor-easy-model-preset" class="agent-field-label">Model preset</label>
158 <div class="agent-model-preset-picker">
159 <select id="agent-editor-easy-model-preset" x-model="$store.agentEditor.draft.modelPreset" x-effect="$nextTick(() => { $el.value = $store.agentEditor.draft.modelPreset })">
160 <option value="" x-text="`Use current preset (${$store.agentEditor.state.model_preset.effective})`"></option>
161 <template x-for="preset in $store.agentEditor.state.model_presets" :key="preset.name">
162 <option :value="preset.name" x-text="preset.name"></option>
163 </template>
164 </select>
165 <button type="button" class="button" @click="$store.agentEditor.openPresetManager()"><x-icon class="icon" name="tune"></x-icon>Edit Presets</button>
166 </div>
167 </div>
168 </div>
169 </section>
170
171 <section class="agent-easy-field">
172 <div class="agent-field-heading">
173 <div><label for="agent-editor-instructions" class="agent-field-label">Instructions</label><p>What should this agent do, and how should it behave?</p></div>
174 </div>
175 <div>
176 <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>
177 <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>
178 <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>
179 </div>
180 </section>
181
182 <section class="agent-easy-field agent-easy-capabilities">
183 <div class="agent-field-heading"><div><div class="agent-field-label">Capabilities</div><p>Choose the default access, or set individual items On or Off.</p></div></div>
184 <div class="capability-policy-group">
185 <div class="future-policy-control"><strong>Allow tools by default</strong><label class="toggle"><input type="checkbox" :checked="$store.agentEditor.policyDefault('tool') === 'allow'" @change="$store.agentEditor.setPolicyDefault('tool', $event.target.checked ? 'allow' : 'block')" aria-label="Allow tools by default"><span class="toggler"></span></label></div>
186 <details class="capability-accordion">
187 <summary><span>Choose individual tools</span><x-icon name="expand_more"></x-icon></summary>
188 <div class="policy-items" role="list" aria-label="Tools this agent can use">
189 <template x-for="tool in $store.agentEditor.standardToolCatalog" :key="tool.id">
190 <div class="policy-item" role="listitem"><div class="policy-item-copy"><strong x-text="tool.label"></strong><p class="policy-item-description" x-show="tool.description" x-text="tool.description"></p></div><div class="policy-state-control" role="group" :aria-label="`${tool.label} access`"><button type="button" :class="{ active: $store.agentEditor.policyItemState('tool', tool.id) === 'allow' }" :aria-pressed="$store.agentEditor.policyItemState('tool', tool.id) === 'allow'" @click="$store.agentEditor.setPolicyItem('tool', tool.id, 'allow')">On</button><button type="button" :class="{ active: $store.agentEditor.policyItemState('tool', tool.id) === 'default' }" :aria-pressed="$store.agentEditor.policyItemState('tool', tool.id) === 'default'" @click="$store.agentEditor.setPolicyItem('tool', tool.id, 'default')" x-text="`Default (${$store.agentEditor.policyDefault('tool') === 'allow' ? 'on' : 'off'})`"></button><button type="button" :class="{ active: $store.agentEditor.policyItemState('tool', tool.id) === 'block' }" :aria-pressed="$store.agentEditor.policyItemState('tool', tool.id) === 'block'" @click="$store.agentEditor.setPolicyItem('tool', tool.id, 'block')">Off</button></div></div>
191 </template>
192 <p class="policy-empty" x-show="!$store.agentEditor.standardToolCatalog.length">No configurable tools are available.</p>
193 </div>
194 </details>
195 </div>
196
197 <div class="capability-policy-group">
198 <div class="future-policy-control"><strong>Allow MCPs by default</strong><label class="toggle"><input type="checkbox" :checked="$store.agentEditor.policyDefault('mcp') === 'allow'" @change="$store.agentEditor.setPolicyDefault('mcp', $event.target.checked ? 'allow' : 'block')" aria-label="Allow MCPs by default"><span class="toggler"></span></label></div>
199 <details class="capability-accordion">
200 <summary><span>Choose individual MCPs</span><x-icon name="expand_more"></x-icon></summary>
201 <div class="policy-items" role="list" aria-label="MCP tools this agent can use">
202 <template x-for="tool in $store.agentEditor.mcpCatalog" :key="tool.id">
203 <div class="policy-item" role="listitem"><div class="policy-item-copy"><strong x-text="tool.label"></strong><p class="policy-item-description" x-show="tool.description" x-text="tool.description"></p></div><div class="policy-state-control" role="group" :aria-label="`${tool.label} access`"><button type="button" :class="{ active: $store.agentEditor.policyItemState('mcp', tool.id) === 'allow' }" :aria-pressed="$store.agentEditor.policyItemState('mcp', tool.id) === 'allow'" @click="$store.agentEditor.setPolicyItem('mcp', tool.id, 'allow')">On</button><button type="button" :class="{ active: $store.agentEditor.policyItemState('mcp', tool.id) === 'default' }" :aria-pressed="$store.agentEditor.policyItemState('mcp', tool.id) === 'default'" @click="$store.agentEditor.setPolicyItem('mcp', tool.id, 'default')" x-text="`Default (${$store.agentEditor.policyDefault('mcp') === 'allow' ? 'on' : 'off'})`"></button><button type="button" :class="{ active: $store.agentEditor.policyItemState('mcp', tool.id) === 'block' }" :aria-pressed="$store.agentEditor.policyItemState('mcp', tool.id) === 'block'" @click="$store.agentEditor.setPolicyItem('mcp', tool.id, 'block')">Off</button></div></div>
204 </template>
205 <p class="policy-empty" x-show="!$store.agentEditor.mcpCatalog.length">No MCP tools are available.</p>
206 </div>
207 </details>
208 </div>
209
210 <div class="capability-policy-group">
211 <div class="future-policy-control"><strong>Allow skills by default</strong><label class="toggle"><input type="checkbox" :checked="$store.agentEditor.policyDefault('skill') === 'allow'" @change="$store.agentEditor.setPolicyDefault('skill', $event.target.checked ? 'allow' : 'block')" aria-label="Allow skills by default"><span class="toggler"></span></label></div>
212 <details class="capability-accordion">
213 <summary><span>Choose individual skills</span><x-icon name="expand_more"></x-icon></summary>
214 <div class="policy-items" role="list" aria-label="Skills this agent can use">
215 <template x-for="skill in $store.agentEditor.skillCatalog" :key="skill.path">
216 <div class="policy-item" role="listitem"><div class="policy-item-copy"><strong x-text="skill.name"></strong><p class="policy-item-description" x-show="skill.description" x-text="skill.description"></p><em x-show="$store.agentEditor.skillWarnings(skill).length" x-text="`Expects blocked tool: ${$store.agentEditor.skillWarnings(skill).join(', ')}`"></em></div><div class="policy-state-control" role="group" :aria-label="`${skill.name} access`"><button type="button" :class="{ active: $store.agentEditor.policyItemState('skill', skill.name) === 'allow' }" :aria-pressed="$store.agentEditor.policyItemState('skill', skill.name) === 'allow'" @click="$store.agentEditor.setPolicyItem('skill', skill.name, 'allow')">On</button><button type="button" :class="{ active: $store.agentEditor.policyItemState('skill', skill.name) === 'default' }" :aria-pressed="$store.agentEditor.policyItemState('skill', skill.name) === 'default'" @click="$store.agentEditor.setPolicyItem('skill', skill.name, 'default')" x-text="`Default (${$store.agentEditor.policyDefault('skill') === 'allow' ? 'on' : 'off'})`"></button><button type="button" :class="{ active: $store.agentEditor.policyItemState('skill', skill.name) === 'block' }" :aria-pressed="$store.agentEditor.policyItemState('skill', skill.name) === 'block'" @click="$store.agentEditor.setPolicyItem('skill', skill.name, 'block')">Off</button></div></div>
217 </template>
218 <p class="policy-empty" x-show="!$store.agentEditor.skillCatalog.length">No configurable skills are available.</p>
219 </div>
220 </details>
221 </div>
222 </section>
223 </main>
224
225 <div class="agent-advanced" x-show="$store.agentEditor.mode === 'advanced'">
226 <nav class="agent-advanced-nav" aria-label="Advanced editor sections">
227 <template x-for="item in [{id:'1',label:'Identity'},{id:'2',label:'Prompt files'},{id:'3',label:'Tools'},{id:'4',label:'MCPs'},{id:'5',label:'Skills'},{id:'6',label:'Review'}]" :key="item.id">
228 <button type="button" :class="{ active: $store.agentEditor.section === item.id }" :aria-current="$store.agentEditor.section === item.id ? 'step' : null" @click="$store.agentEditor.setSection(item.id)">
229 <span class="section-number" x-text="item.id"></span><span x-text="item.label"></span>
230 <span class="agent-status-badge compact" :class="$store.agentEditor.sectionIssues(item.id).length ? 'is-error' : 'is-unsaved'" x-show="$store.agentEditor.sectionIssues(item.id).length || $store.agentEditor.sectionDirty(item.id)"><x-icon :name="$store.agentEditor.sectionIssues(item.id).length ? 'error' : 'edit_note'"></x-icon><span x-text="$store.agentEditor.sectionIssues(item.id).length ? 'Needs attention' : 'Changed'"></span></span>
231 </button>
232 </template>
233 </nav>
234
235 <div class="agent-advanced-content">
236 <section x-show="$store.agentEditor.section === '1'" data-agent-editor-section="1" tabindex="-1" aria-labelledby="agent-section-1-title">
237 <header class="advanced-section-heading"><h3 id="agent-section-1-title">Identity</h3><p>Set the agent’s identity, delegation guidance, and model preset.</p></header>
238 <div class="origin-row">
239 <span class="agent-origin" x-text="$store.agentEditor.state.profile.origin"></span>
240 <span class="agent-status-badge is-customized" x-show="$store.agentEditor.state.profile.scope_has_overrides"><x-icon name="edit_note"></x-icon><span>Customized by you</span></span>
241 </div>
242 <p class="built-in-note" x-show="$store.agentEditor.state.profile.built_in">Your changes override the built-in profile. The original files stay unchanged.</p>
243 <div class="advanced-identity-grid">
244 <div class="agent-avatar-wrap advanced-avatar">
245 <div class="agent-avatar" :style="`background:${$store.agentEditor.avatarColor()}`">
246 <img x-show="$store.agentEditor.draft.avatar?.kind === 'image' && $store.agentEditor.draft.avatarPreview" :src="$store.agentEditor.draft.avatarPreview" alt="Avatar crop preview">
247 <span x-show="$store.agentEditor.draft.avatar?.kind !== 'image' || !$store.agentEditor.draft.avatarPreview" x-text="$store.agentEditor.initials()"></span>
248 </div>
249 <div class="agent-avatar-actions">
250 <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>
251 <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>
252 <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>
253 </div>
254 </div>
255 <div class="identity-fields">
256 <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>
257 <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>
258 <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>
259 <div class="agent-field wide agent-model-preset">
260 <label for="agent-editor-model-preset" class="agent-field-label">Model preset</label>
261 <div class="agent-model-preset-picker">
262 <select id="agent-editor-model-preset" x-model="$store.agentEditor.draft.modelPreset" x-effect="$nextTick(() => { $el.value = $store.agentEditor.draft.modelPreset })">
263 <option value="" x-text="`Use current preset (${$store.agentEditor.state.model_preset.effective})`"></option>
264 <template x-for="preset in $store.agentEditor.state.model_presets" :key="preset.name">
265 <option :value="preset.name" x-text="preset.name"></option>
266 </template>
267 </select>
268 <button type="button" class="button" @click="$store.agentEditor.openPresetManager()"><x-icon class="icon" name="tune"></x-icon>Edit Presets</button>
269 </div>
270 <small>Use the current preset or choose another setup for this agent.</small>
271 </div>
272 <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>
273 </div>
274 </div>
275 </section>
276
277 <section x-show="$store.agentEditor.section === '2'" data-agent-editor-section="2" tabindex="-1" aria-labelledby="agent-section-2-title">
278 <header class="advanced-section-heading"><h3 id="agent-section-2-title">Prompt files</h3><p>Choose a file and edit its prompt.</p></header>
279 <div class="prompt-workspace">
280 <aside class="prompt-browser">
281 <label class="compact-search"><span class="sr-only">Search prompt files</span><x-icon name="search"></x-icon><input type="search" x-model="$store.agentEditor.promptFileSearch" placeholder="Search files"></label>
282 <div class="prompt-file-list" role="region" aria-label="Prompt file list" tabindex="0">
283 <template x-for="group in $store.agentEditor.promptGroups" :key="group.id">
284 <section class="prompt-file-group" x-show="$store.agentEditor.filteredPromptFiles(group.id).length">
285 <h4 x-text="group.label"></h4>
286 <template x-for="prompt in $store.agentEditor.filteredPromptFiles(group.id)" :key="prompt.filename">
287 <button type="button" :class="{ active: $store.agentEditor.selectedPrompt === prompt.filename }" @click="$store.agentEditor.selectPrompt(prompt.filename)">
288 <span class="prompt-file-name" x-text="prompt.filename"></span><span class="prompt-file-state" x-text="$store.agentEditor.promptDisplayState(prompt)"></span>
289 </button>
290 </template>
291 </section>
292 </template>
293 <p class="prompt-empty" x-show="!$store.agentEditor.filteredPromptFiles().length">No prompt files match your search.</p>
294 </div>
295 </aside>
296 <div class="prompt-editor" role="region" aria-label="Selected prompt file" tabindex="0" x-show="$store.agentEditor.selectedPromptDraft">
297 <div class="prompt-editor-header">
298 <div class="prompt-file-heading">
299 <strong x-text="$store.agentEditor.selectedPrompt"></strong>
300 <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>
301 </div>
302 <div class="prompt-actions">
303 <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>
304 <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>
305 <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>
306 </div>
307 </div>
308 <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>
309 <div id="agent-editor-prompt-ace" class="prompt-ace" :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"></div>
310 <div class="agent-editor-note" role="alert" x-show="$store.agentEditor.aceUnavailable">Prompt editor is unavailable. Reload Agent Zero and try again.</div>
311 <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>
312 </div>
313 </div>
314 </section>
315
316 <section x-show="$store.agentEditor.section === '3'" data-agent-editor-section="3" tabindex="-1" aria-labelledby="agent-section-3-title">
317 <header class="advanced-section-heading"><h3 id="agent-section-3-title">Tools</h3><p>Choose which tools this agent can use.</p></header>
318 <div class="future-policy-control"><strong>Allow tools by default</strong><label class="toggle"><input type="checkbox" :checked="$store.agentEditor.policyDefault('tool') === 'allow'" @change="$store.agentEditor.setPolicyDefault('tool', $event.target.checked ? 'allow' : 'block')" aria-label="Allow tools by default"><span class="toggler"></span></label></div>
319 <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>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>
320 <section class="policy-group" aria-labelledby="tools-title"><header><h4 id="tools-title">Tools</h4><span x-text="$store.agentEditor.filteredTools('tool').length"></span></header><div class="policy-items"><p class="policy-empty" x-show="!$store.agentEditor.filteredTools('tool').length">No tools match these filters.</p><template x-for="tool in $store.agentEditor.filteredTools('tool')" :key="tool.id"><div class="policy-item"><div class="policy-item-copy"><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 === false">Unavailable — kept in your settings</em></div><div class="policy-state-control" role="group" :aria-label="`${tool.label} access`"><button type="button" :class="{ active: $store.agentEditor.policyItemState('tool', tool.id) === 'allow' }" :aria-pressed="$store.agentEditor.policyItemState('tool', tool.id) === 'allow'" @click="$store.agentEditor.setPolicyItem('tool', tool.id, 'allow')">On</button><button type="button" :class="{ active: $store.agentEditor.policyItemState('tool', tool.id) === 'default' }" :aria-pressed="$store.agentEditor.policyItemState('tool', tool.id) === 'default'" @click="$store.agentEditor.setPolicyItem('tool', tool.id, 'default')" x-text="`Default (${$store.agentEditor.policyDefault('tool') === 'allow' ? 'on' : 'off'})`"></button><button type="button" :class="{ active: $store.agentEditor.policyItemState('tool', tool.id) === 'block' }" :aria-pressed="$store.agentEditor.policyItemState('tool', tool.id) === 'block'" @click="$store.agentEditor.setPolicyItem('tool', tool.id, 'block')">Off</button></div></div></template></div></section>
321 </section>
322
323 <section x-show="$store.agentEditor.section === '4'" data-agent-editor-section="4" tabindex="-1" aria-labelledby="agent-section-4-title">
324 <header class="advanced-section-heading"><h3 id="agent-section-4-title">MCPs</h3><p>Choose which MCP tools this agent can use.</p></header>
325 <div class="future-policy-control"><strong>Allow MCPs by default</strong><label class="toggle"><input type="checkbox" :checked="$store.agentEditor.policyDefault('mcp') === 'allow'" @change="$store.agentEditor.setPolicyDefault('mcp', $event.target.checked ? 'allow' : 'block')" aria-label="Allow MCPs by default"><span class="toggler"></span></label></div>
326 <div class="policy-filters tools"><label class="policy-search"><x-icon name="search"></x-icon><span class="sr-only">Search MCPs</span><input type="search" x-model="$store.agentEditor.toolSearch" placeholder="Search MCPs"></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>
327 <section class="policy-group" aria-labelledby="mcps-title"><header><h4 id="mcps-title">MCPs</h4><span x-text="$store.agentEditor.filteredTools('mcp').length"></span></header><div class="policy-items"><p class="policy-empty" x-show="!$store.agentEditor.filteredTools('mcp').length">No MCP tools match these filters.</p><template x-for="tool in $store.agentEditor.filteredTools('mcp')" :key="tool.id"><div class="policy-item"><div class="policy-item-copy"><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 === false">Unavailable — kept in your settings</em></div><div class="policy-state-control" role="group" :aria-label="`${tool.label} access`"><button type="button" :class="{ active: $store.agentEditor.policyItemState('mcp', tool.id) === 'allow' }" :aria-pressed="$store.agentEditor.policyItemState('mcp', tool.id) === 'allow'" @click="$store.agentEditor.setPolicyItem('mcp', tool.id, 'allow')">On</button><button type="button" :class="{ active: $store.agentEditor.policyItemState('mcp', tool.id) === 'default' }" :aria-pressed="$store.agentEditor.policyItemState('mcp', tool.id) === 'default'" @click="$store.agentEditor.setPolicyItem('mcp', tool.id, 'default')" x-text="`Default (${$store.agentEditor.policyDefault('mcp') === 'allow' ? 'on' : 'off'})`"></button><button type="button" :class="{ active: $store.agentEditor.policyItemState('mcp', tool.id) === 'block' }" :aria-pressed="$store.agentEditor.policyItemState('mcp', tool.id) === 'block'" @click="$store.agentEditor.setPolicyItem('mcp', tool.id, 'block')">Off</button></div></div></template></div></section>
328 </section>
329
330 <section x-show="$store.agentEditor.section === '5'" data-agent-editor-section="5" tabindex="-1" aria-labelledby="agent-section-5-title">
331 <header class="advanced-section-heading"><h3 id="agent-section-5-title">Skills</h3><p>Choose which skills this agent can find and use.</p></header>
332 <div class="future-policy-control"><strong>Allow skills by default</strong><label class="toggle"><input type="checkbox" :checked="$store.agentEditor.policyDefault('skill') === 'allow'" @change="$store.agentEditor.setPolicyDefault('skill', $event.target.checked ? 'allow' : 'block')" aria-label="Allow skills by default"><span class="toggler"></span></label></div>
333 <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>
334 <section class="policy-group" aria-labelledby="skills-title"><header><h4 id="skills-title">Skills</h4><span x-text="$store.agentEditor.filteredSkills().length"></span></header><div class="policy-items"><p class="policy-empty" x-show="!$store.agentEditor.filteredSkills().length">No skills match these filters.</p><template x-for="skill in $store.agentEditor.filteredSkills()" :key="skill.path"><div class="policy-item"><div class="policy-item-copy"><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 class="policy-state-control" role="group" :aria-label="`${skill.name} access`"><button type="button" :class="{ active: $store.agentEditor.policyItemState('skill', skill.name) === 'allow' }" :aria-pressed="$store.agentEditor.policyItemState('skill', skill.name) === 'allow'" @click="$store.agentEditor.setPolicyItem('skill', skill.name, 'allow')">On</button><button type="button" :class="{ active: $store.agentEditor.policyItemState('skill', skill.name) === 'default' }" :aria-pressed="$store.agentEditor.policyItemState('skill', skill.name) === 'default'" @click="$store.agentEditor.setPolicyItem('skill', skill.name, 'default')" x-text="`Default (${$store.agentEditor.policyDefault('skill') === 'allow' ? 'on' : 'off'})`"></button><button type="button" :class="{ active: $store.agentEditor.policyItemState('skill', skill.name) === 'block' }" :aria-pressed="$store.agentEditor.policyItemState('skill', skill.name) === 'block'" @click="$store.agentEditor.setPolicyItem('skill', skill.name, 'block')">Off</button></div></div></template></div></section>
335 </section>
336
337 <section x-show="$store.agentEditor.section === '6'" data-agent-editor-section="6" tabindex="-1" aria-labelledby="agent-section-6-title">
338 <header class="advanced-section-heading"><h3 id="agent-section-6-title">Review</h3><p>Saving will change exactly these files — nothing else.</p></header>
339 <div class="review-plan-status" role="status" x-show="$store.agentEditor.planStatus === 'loading'"><x-icon class="spinning" name="progress_activity"></x-icon><span>Computing the exact change plan…</span></div>
340 <div class="review-plan-status" x-show="$store.agentEditor.planStatus === 'error'"><span>The change plan could not be computed.</span><button type="button" class="text-button" @click="$store.agentEditor.previewPlan()">Retry</button></div>
341 <div class="review-blocked" role="alert" x-show="$store.agentEditor.planStatus === 'blocked'">
342 <x-icon name="error"></x-icon>
343 <div><strong x-text="`Fix ${$store.agentEditor.validationIssues().length} ${$store.agentEditor.validationIssues().length === 1 ? 'issue' : 'issues'} to see the change plan`"></strong>
344 <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>
345 </div>
346 </div>
347 <section class="review-identity" aria-label="Agent identity" x-show="$store.agentEditor.planStatus === 'ready'">
348 <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>
349 <div><strong x-text="$store.agentEditor.draft.title || $store.agentEditor.draft.profileId"></strong><p x-text="$store.agentEditor.draft.description || 'No description'"></p></div>
350 </section>
351 <div class="change-plan" aria-live="polite" x-show="$store.agentEditor.planStatus === 'ready'">
352 <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>
353 <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>
354 <template x-if="$store.agentEditor.plan.warnings?.length"><section><h4>Notices</h4><ul><template x-for="warning in $store.agentEditor.plan.warnings" :key="warning"><li x-text="warning"></li></template></ul></section></template>
355 </div>
356 <div class="review-actions" x-show="$store.agentEditor.pendingMutation"><button type="button" class="button danger" @click="$store.agentEditor.applyPendingMutation()"><x-icon name="delete"></x-icon>Apply removal plan</button><button type="button" class="text-button" @click="$store.agentEditor.pendingMutation = null; $store.agentEditor.previewPlan()">Cancel removal</button></div>
357 <div class="profile-maintenance" x-show="!$store.agentEditor.pendingMutation && !$store.agentEditor.draft.creating && $store.agentEditor.state.profile.scope_has_overrides && !$store.agentEditor.state.profile.deletable">
358 <h4>Customized by you</h4><p x-text="`Remove only the customizations shown in this editor from ${$store.agentEditor.scopeLabel}. Other files stay in place.`"></p><button type="button" class="button danger" @click="$store.agentEditor.planRemoval(false)"><x-icon name="delete_sweep"></x-icon>Remove my changes</button>
359 <details><summary x-text="`Delete all customizations in ${$store.agentEditor.scopeLabel}`"></summary><p>This removes every customization saved in this scope after showing each affected file. Inherited files remain untouched.</p><button type="button" class="button danger" @click="$store.agentEditor.planRemoval(true)"><x-icon name="delete_forever"></x-icon>Review files to delete</button></details>
360 </div>
361 <div class="profile-maintenance" x-show="!$store.agentEditor.pendingMutation && !$store.agentEditor.draft.creating && $store.agentEditor.state.profile.deletable"><h4>Delete custom agent</h4><p>Projects and open chats that use this agent are shown before confirmation, together with its files and settings.</p><button type="button" class="button danger" @click="$store.agentEditor.deleteProfile($store.agentEditor.draft.profileId)">Delete agent</button></div>
362 </section>
363 </div>
364 </div>
365
366 </div>
367 </template>
368
369 <div class="modal-footer agent-editor-footer" data-modal-footer x-show="!$store.agentEditor.loading && $store.agentEditor.view === 'editor' && !$store.agentEditor.pendingMutation">
370 <div class="footer-left">
371 <button type="button" class="btn btn-cancel agent-reset-profile"
372 x-show="$store.agentEditor.draft && !$store.agentEditor.draft.creating && $store.agentEditor.state?.profile && !$store.agentEditor.state.profile.deletable"
373 :disabled="$store.agentEditor.saving || !$store.agentEditor.state?.profile?.scope_has_overrides"
374 @click="$store.agentEditor.restoreProfile($store.agentEditor.state.profile)">Reset to default</button>
375 </div>
376 <div class="footer-actions">
377 <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>
378 <button type="button" class="btn btn-ok" x-show="$store.agentEditor.view === 'editor'" @click="$store.agentEditor.save(false)" :disabled="$store.agentEditor.saving || $store.agentEditor.avatarUploading || $store.agentEditor.validationIssues().length > 0" :title="$store.agentEditor.validationIssues().length ? 'Fix the highlighted issues before saving' : ''"><span x-text="$store.agentEditor.saving ? 'Saving…' : $store.agentEditor.draft?.creating ? 'Create agent' : 'Save changes'"></span></button>
379 </div>
380 </div>
381 </div>
382
383 <style>
384 .agent-editor {
385 --color-text-secondary: var(--color-text-muted);
386 --color-error: var(--color-error-text);
387 --color-warning: var(--color-warning-text);
388 --agent-editor-action: var(--color-highlight);
389 --agent-editor-change: var(--color-warning-text);
390 --agent-editor-danger: var(--color-error-text);
391 color: var(--color-text);
392 min-height: 12rem;
393 min-width: 0;
394 }
395 .agent-editor h2,.agent-editor h3,.agent-editor h4,.agent-editor p { margin: 0; }
396 .agent-editor button,.agent-editor input,.agent-editor textarea,.agent-editor select { font: inherit; }
397 .agent-editor input[type="search"],.agent-editor select { min-width:0; min-height:2.25rem; box-sizing:border-box; padding:.5rem .6rem; border:1px solid var(--color-border); border-radius:7px; background:var(--color-input); color:var(--color-text); }
398 .agent-editor input[type="radio"] { appearance:none; flex:0 0 auto; width:1.5rem; height:1.5rem; margin:.05rem 0; border:1px solid var(--color-border); background:var(--color-input); }
399 .agent-editor input[type="radio"] { border-radius:50%; }
400 .agent-editor input[type="radio"]:checked { border-color:var(--agent-editor-action); box-shadow:inset 0 0 0 4px var(--color-input); background:var(--agent-editor-action); }
401 .agent-editor-error { display:flex; gap:.55rem; align-items:flex-start; margin:0 0 .8rem; padding:.7rem .8rem; border:1px solid color-mix(in srgb,var(--agent-editor-danger) 55%,var(--color-border)); border-radius:8px; background:color-mix(in srgb,var(--agent-editor-danger) 10%,var(--color-panel)); }
402 .agent-editor-error span { flex:1; white-space:pre-wrap; }
403 .agent-editor-loading { min-height:20rem; display:grid; place-content:center; justify-items:center; gap:.65rem; color:var(--color-text-secondary); }
404 .agent-editor-topbar { display:flex; align-items:center; justify-content:space-between; gap:1rem; margin-bottom:1rem; }
405 .agent-editor-heading { display:flex; flex:1 1 auto; flex-wrap:wrap; align-items:center; gap:.6rem; min-width:0; }
406 .agent-editor-subtitle { margin-top:.2rem; font-size:.78rem; color:var(--color-text-secondary); }
407 .agent-editor-topbar-actions { display:flex; align-items:center; gap:.75rem; margin-left:auto; }
408 .agent-editor .agent-reset-profile { --agent-editor-action:var(--color-primary); gap:.25rem; white-space:nowrap; }
409 .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; }
410 .agent-status-badge x-icon { flex:0 0 auto; font-size:.85rem; }
411 .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); }
412 .agent-status-badge.is-error { color:var(--agent-editor-danger); border-color:color-mix(in srgb,var(--agent-editor-danger) 48%,var(--color-border)); background:color-mix(in srgb,var(--agent-editor-danger) 8%,transparent); }
413 .agent-status-badge.compact { justify-self:end; padding:.15rem .35rem; font-size:.64rem; }
414 .agent-mode-switch { display:flex; margin-left:auto; padding:3px; border:1px solid var(--color-border); border-radius:9px; background:var(--color-input); }
415 .agent-mode-switch button { border:0; border-radius:6px; padding:.42rem .8rem; color:var(--color-text-secondary); background:transparent; }
416 .agent-mode-switch button.active { color:var(--color-text); background:var(--color-panel); box-shadow:0 1px 4px rgba(0,0,0,.2); }
417 .agent-easy { max-width:43rem; margin:0 auto; display:flex; flex-direction:column; gap:1.35rem; padding:.25rem .25rem 1rem; }
418 .agent-easy-identity { position:relative; display:grid; grid-template-columns:7.2rem 1fr; gap:1rem; align-items:start; }
419 .agent-easy-identity-fields { width:100%; min-width:0; display:flex; flex-direction:column; gap:.9rem; }
420 .agent-avatar-wrap { display:flex; flex-direction:column; align-items:center; gap:.45rem; }
421 .agent-avatar { width:5rem; aspect-ratio:1; position:relative; display:grid; place-items:center; border-radius:16px; overflow:hidden; color:white; font-size:1.35rem; font-weight:700; box-shadow:inset 0 0 0 1px rgba(255,255,255,.15); }
422 .agent-avatar img { width:100%; height:100%; object-fit:cover; }
423 .avatar-progress { position:absolute; inset:0; display:grid; place-items:center; background:rgba(0,0,0,.5); }
424 .agent-avatar-actions { --agent-editor-action:var(--color-primary); display:flex; align-items:center; justify-content:center; gap:.35rem; font-size:.76rem; white-space:nowrap; }
425 .avatar-color-action,.avatar-upload-action { position:relative; cursor:pointer; }
426 .avatar-action-icon { display:grid; place-items:center; width:1.75rem; height:1.75rem; border-radius:6px; color:var(--agent-editor-action); }
427 .avatar-action-icon x-icon { font-size:1.05rem; }
428 .avatar-color-action input,.avatar-upload-action input { position:absolute; width:1px; height:1px; opacity:0; pointer-events:none; }
429 .agent-field { display:flex; flex-direction:column; gap:.35rem; min-width:0; }
430 .agent-name-field { width:100%; }
431 .agent-field-label { font-weight:650; font-size:.92rem; }
432 .agent-field input,.agent-field textarea,.agent-easy textarea,.policy-filters input,.policy-filters select,.compact-search input { width:100%; box-sizing:border-box; }
433 .agent-field small,.agent-field-heading p,.field-status { color:var(--color-text-secondary); font-size:.79rem; }
434 .field-count { color:var(--color-text-secondary); font-size:.79rem; font-weight:400; }
435 .field-status { display:block; margin-top:.15rem; }
436 .field-error { display:block; color:var(--color-text); font-size:.79rem; font-weight:600; line-height:1.35; }
437 .agent-id-feedback { display:flex; flex-wrap:wrap; align-items:center; gap:.35rem .65rem; }
438 .agent-easy-field .field-error { margin-top:.35rem; }
439 .agent-field-heading { display:flex; justify-content:space-between; gap:1rem; align-items:flex-start; margin-bottom:.5rem; }
440 .agent-editor .text-button { display:inline-flex; align-items:center; min-height:1.5rem; border:0; padding:0; background:transparent; color:var(--agent-editor-action); cursor:pointer; font-size:.82rem; text-align:left; }
441 .agent-editor .text-button:hover { text-decoration:underline; }
442 .agent-easy textarea { min-height:11rem; resize:vertical; }
443 .restore-action { --agent-editor-action:var(--color-primary); display:inline-flex; align-items:center; gap:.25rem; margin-top:.4rem; }
444 .agent-easy-capabilities { display:flex; flex-direction:column; gap:.65rem; }
445 .future-policy-control { display:flex; align-items:center; justify-content:space-between; gap:1rem; padding:.45rem .2rem; }
446 .capability-policy-group + .capability-policy-group { border-top:1px solid var(--color-border); padding-top:.55rem; }
447 .capability-accordion summary { display:flex; align-items:center; justify-content:space-between; gap:.75rem; padding:.65rem .2rem .75rem; cursor:pointer; color:var(--color-text-secondary); font-weight:500; }
448 .capability-accordion summary x-icon { color:var(--color-text-secondary); transition:transform .15s ease; }
449 .capability-accordion[open] summary x-icon { transform:rotate(180deg); }
450 .capability-accordion .policy-items { max-height:none; overflow:visible; padding:0 0 .45rem; }
451 .agent-advanced { flex:1 1 auto; display:grid; grid-template-columns:14rem minmax(0,1fr); gap:1rem; min-height:0; }
452 .agent-advanced-nav { display:flex; flex-direction:column; gap:.3rem; padding:.45rem; border:1px solid var(--color-border); border-radius:10px; background:var(--color-input); align-self:start; position:sticky; top:0; }
453 .agent-advanced-nav button { display:grid; grid-template-columns:1.7rem 1fr auto; align-items:center; gap:.45rem; border:0; border-radius:7px; padding:.65rem; color:var(--color-text-secondary); background:transparent; text-align:left; }
454 .agent-advanced-nav button.active { color:var(--color-text); background:var(--color-panel); }
455 .section-number { display:grid; place-items:center; width:1.55rem; height:1.55rem; border:1px solid var(--color-border); border-radius:50%; font-size:.75rem; }
456 .agent-advanced-content,.agent-editor-workspace,.agent-manager { min-width:0; }
457 .agent-advanced-content { min-height:0; overflow:auto; }
458 .agent-advanced-content > section { outline:none; display:flex; flex-direction:column; gap:1rem; }
459 .agent-advanced-content > section[data-agent-editor-section="2"] { height:100%; min-height:0; }
460 .advanced-section-heading { display:flex; flex-direction:column; gap:.25rem; padding-bottom:.8rem; border-bottom:1px solid var(--color-border); }
461 .advanced-section-heading h3 { font-size:1.2rem; }
462 .advanced-section-heading p { max-width:48rem; color:var(--color-text-secondary); font-size:.84rem; }
463 .origin-row { display:flex; flex-wrap:wrap; align-items:center; gap:.6rem; }
464 .built-in-note,.field-provenance { color:var(--color-text-secondary); font-size:.76rem; }
465 .agent-origin { padding:.2rem .45rem; border:1px solid var(--color-border); border-radius:999px; font-size:.72rem; color:var(--color-text-secondary); }
466 .agent-editor-note { padding:.55rem .7rem; border-left:3px solid var(--color-warning); background:color-mix(in srgb,var(--color-warning) 8%,transparent); color:var(--color-text-secondary); font-size:.8rem; }
467 .advanced-identity-grid { display:grid; grid-template-columns:9rem minmax(0,1fr); gap:1.2rem; }
468 .advanced-avatar { align-self:start; }
469 .identity-fields { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:.9rem; }
470 .identity-fields .agent-field.wide { grid-column:1/-1; }
471 .agent-model-preset-picker { width:100%; display:grid; grid-template-columns:minmax(13rem,18rem) auto; align-items:center; justify-content:space-between; gap:1rem; }
472 .agent-model-preset-picker select { width:100%; }
473 .agent-model-preset-picker > .button { display:inline-flex; align-items:center; gap:.35rem; }
474 .prompt-workspace { flex:1 1 auto; display:grid; grid-template-columns:minmax(13rem,16rem) minmax(0,1fr); gap:.8rem; min-width:0; min-height:0; }
475 .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; }
476 .compact-search,.policy-search { 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); }
477 .compact-search { margin:.45rem; }
478 .compact-search:focus-within,.policy-search: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); }
479 .agent-editor .compact-search input,.agent-editor .policy-search input { height:100%; min-height:0; padding:0; border:0; outline:0; appearance:none; background:transparent; }
480 .prompt-file-list { flex:1; overflow:auto; padding:.35rem; }
481 .prompt-file-group { min-width:0; margin-bottom:.55rem; }
482 .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; }
483 .prompt-file-list button { width:100%; display:grid; grid-template-columns:minmax(0,1fr) auto; gap:.15rem .4rem; padding:.5rem; border:0; border-radius:6px; background:transparent; color:var(--color-text); text-align:left; }
484 .prompt-file-list button.active { background:var(--color-input); }
485 .prompt-file-name { grid-column:1/-1; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font-family:monospace; font-size:.76rem; }
486 .prompt-file-state { color:var(--color-text-secondary); font-size:.7rem; }
487 .prompt-empty { padding:.65rem; color:var(--color-text-secondary); font-size:.78rem; }
488 .prompt-editor { display:flex; flex-direction:column; gap:.55rem; min-width:0; min-height:0; max-width:100%; overflow:hidden; }
489 .prompt-editor-header { display:flex; justify-content:space-between; gap:.7rem; align-items:flex-start; }
490 .prompt-editor-header > div:first-child { min-width:0; overflow-wrap:anywhere; }
491 .prompt-file-heading { display:flex; flex-direction:column; gap:.25rem; }
492 .prompt-actions { display:flex; flex-wrap:wrap; justify-content:flex-end; gap:.35rem; min-width:0; }
493 .prompt-actions .button { max-width:100%; white-space:normal; overflow-wrap:anywhere; }
494 .prompt-customization-path { display:flex; align-items:center; gap:.4rem; min-width:0; }
495 .prompt-customization-path code { min-width:0; color:var(--color-text-secondary); font-size:.72rem; overflow-wrap:anywhere; }
496 .prompt-customization-path .button { width:2.125rem; height:2.125rem; flex:0 0 auto; padding:0; }
497 .prompt-ace { flex:1; min-height:0; border:1px solid var(--color-border); border-radius:8px; overflow:hidden; font:13px/1.55 ui-monospace,SFMono-Regular,Consolas,monospace; }
498 .profile-maintenance summary { display:flex; align-items:center; min-height:1.5rem; cursor:pointer; }
499 .policy-filters { display:grid; grid-template-columns:minmax(12rem,1fr) minmax(8rem,auto); gap:.6rem; }
500 .policy-filters.skills { grid-template-columns:minmax(12rem,1fr) minmax(8rem,auto); }
501 .policy-filters label { display:flex; align-items:center; gap:.35rem; font-size:.78rem; color:var(--color-text-secondary); }
502 .policy-filters select { flex:1; }
503 .policy-group { display:flex; flex-direction:column; min-width:0; border:1px solid var(--color-border); border-radius:10px; overflow:hidden; }
504 .policy-group header { display:flex; align-items:center; justify-content:space-between; gap:.5rem; padding:.65rem .8rem; border-bottom:1px solid var(--color-border); background:var(--color-input); }
505 .policy-group header span { color:var(--color-text-secondary); font-size:.72rem; }
506 .policy-items { flex:1; max-height:24rem; overflow:auto; padding:.35rem; }
507 .policy-item { display:grid; grid-template-columns:minmax(0,1fr) auto; gap:.75rem; align-items:start; padding:.55rem; border-radius:7px; }
508 .policy-item:hover { background:var(--color-input); }
509 .policy-item-copy { display:flex; flex-direction:column; gap:.15rem; min-width:0; }
510 .policy-items small { color:var(--color-text-secondary); overflow-wrap:anywhere; }
511 .policy-items em { color:var(--color-warning); font-size:.72rem; font-style:normal; }
512 .policy-item-description { margin:0; color:var(--color-text-secondary); font-size:.75rem; white-space:pre-wrap; overflow-wrap:anywhere; }
513 .policy-state-control { display:inline-flex; align-self:center; overflow:hidden; border:1px solid var(--color-border); border-radius:var(--border-radius-sm); background:var(--color-input); }
514 .policy-state-control button { min-height:2rem; padding:.35rem .6rem; border:0; border-left:1px solid var(--color-border); border-radius:0; background:transparent; color:var(--color-text-secondary); cursor:pointer; font-size:.74rem; white-space:nowrap; }
515 .policy-state-control button:first-child { border-left:0; }
516 .policy-state-control button.active { color:var(--color-text); background:#2196f3; box-shadow:0 1px 4px rgba(0,0,0,.2); }
517 .policy-empty { padding:1rem .7rem; color:var(--color-text-secondary); font-size:.8rem; text-align:center; }
518 .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); }
519 .review-identity-avatar { width:3.25rem; aspect-ratio:1; display:grid; place-items:center; overflow:hidden; border-radius:10px; color:#fff; font-weight:700; }
520 .review-identity-avatar img { width:100%; height:100%; object-fit:cover; }
521 .review-identity > div:last-child { display:flex; flex-direction:column; gap:.15rem; min-width:0; }
522 .review-identity strong,.review-identity p { min-width:0; overflow-wrap:anywhere; }
523 .review-identity p { color:var(--color-text-secondary); font-size:.8rem; }
524 .change-plan { display:grid; grid-template-columns:1fr 1fr; gap:.8rem; }
525 .change-plan section { padding:.85rem; border:1px solid var(--color-border); border-radius:9px; background:var(--color-input); min-width:0; }
526 .change-plan ul { margin:.55rem 0 0; padding-left:1.2rem; }
527 .change-plan code { overflow-wrap:anywhere; }
528 .review-blocked { display:flex; align-items:flex-start; gap:.65rem; padding:.8rem; border:1px solid color-mix(in srgb,var(--agent-editor-danger) 55%,var(--color-border)); border-radius:9px; background:color-mix(in srgb,var(--agent-editor-danger) 8%,var(--color-panel)); }
529 .review-blocked > x-icon { flex:0 0 auto; color:var(--agent-editor-danger); }
530 .review-blocked ul { margin:.45rem 0 0; padding-left:1.1rem; }
531 .review-plan-status { display:flex; flex-wrap:wrap; align-items:center; gap:.45rem; color:var(--color-text-secondary); font-size:.82rem; }
532 .review-actions { display:flex; flex-wrap:wrap; align-items:center; gap:.65rem; }
533 .profile-maintenance { display:flex; flex-direction:column; align-items:flex-start; gap:.55rem; margin-top:.5rem; padding:1rem; border:1px solid var(--color-border); border-radius:9px; }
534 .profile-maintenance p { color:var(--color-text-secondary); font-size:.82rem; }
535 .profile-maintenance details { width:100%; padding-top:.6rem; border-top:1px solid var(--color-border); }
536 .profile-maintenance details p { margin:.5rem 0; }
537 .agent-editor-footer { --agent-editor-action:var(--color-highlight); width:100%; display:flex; justify-content:space-between; gap:.7rem; }
538 .footer-actions { display:flex; gap:.5rem; margin-left:auto; }
539 .agent-editor-footer .btn-cancel,.agent-editor-secondary-action { border:1px solid var(--color-border); background:transparent; color:var(--color-text); }
540 .agent-editor-footer .btn-cancel:hover,.agent-editor-secondary-action:hover { border-color:color-mix(in srgb,var(--agent-editor-action) 50%,var(--color-border)); background:color-mix(in srgb,var(--agent-editor-action) 10%,transparent); color:var(--color-text); }
541 .agent-editor-secondary-action { display:inline-flex; align-items:center; }
542 .agent-editor .button.danger { display:inline-flex; align-items:center; gap:.35rem; border-color:color-mix(in srgb,var(--agent-editor-danger) 60%,var(--color-border)); color:var(--agent-editor-danger); background:color-mix(in srgb,var(--agent-editor-danger) 7%,var(--color-panel)); }
543 .agent-editor .button.danger:hover { background:color-mix(in srgb,var(--agent-editor-danger) 15%,var(--color-panel)); }
544 .agent-manager { display:flex; flex-direction:column; gap:1rem; }
545 .agent-scope-selector { padding:.75rem 1rem; border:1px solid var(--color-border); border-radius:4px; }
546 .agent-scope-field { display:flex; flex:1 1 0; align-items:center; gap:.5rem; min-width:12rem; margin:0; }
547 .agent-scope-field span { color:var(--color-text-secondary); font-weight:600; white-space:nowrap; }
548 .agent-scope-field select { flex:1 1 auto; min-width:0; }
549 .agent-editor-scope { flex:0 1 24rem; }
550 .agent-manager-copy p { color:var(--color-text-secondary); font-size:.82rem; }
551 .agent-manager-list-header { display:flex; align-items:center; justify-content:flex-end; gap:1rem; }
552 .active-agent-display { display:flex; align-items:center; gap:.5rem; min-width:0; margin-right:auto; padding:.5rem; border:1px solid var(--color-highlight); border-radius:.5rem; }
553 .active-agent-summary { display:flex; align-items:center; gap:.5rem; min-width:0; }
554 .active-agent-summary strong { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
555 .active-agent-avatar { width:1.5rem; aspect-ratio:1; display:grid; flex:0 0 auto; place-items:center; overflow:hidden; border-radius:6px; color:#fff; font-size:.65rem; font-weight:700; }
556 .active-agent-avatar img { width:100%; height:100%; object-fit:cover; }
557 .agent-manager-create { flex:0 0 auto; display:inline-flex; align-items:center; gap:.35rem; }
558 .agent-manager-list { display:flex; flex-direction:column; }
559 .agent-manager-card { display:grid; grid-template-columns:3rem minmax(0,1fr) auto; gap:.75rem; align-items:center; padding:.65rem .25rem; }
560 .agent-manager-card + .agent-manager-card { border-top:1px solid var(--color-border); }
561 .agent-manager-avatar { width:3rem; aspect-ratio:1; display:grid; place-items:center; border-radius:10px; color:white; font-weight:700; overflow:hidden; }
562 .agent-manager-avatar img { width:100%; height:100%; object-fit:cover; }
563 .agent-manager-copy { min-width:0; }
564 .agent-manager-name { display:flex; flex-wrap:wrap; align-items:center; gap:.4rem; }
565 .agent-manager-name strong,.agent-manager-copy p { overflow-wrap:anywhere; }
566 .agent-editor .agent-manager-inline-action { gap:.25rem; margin-top:.15rem; color:var(--color-message-text); opacity:.7; }
567 .agent-editor .agent-manager-inline-action + .agent-manager-inline-action { margin-left:.5rem; }
568 .agent-editor .agent-manager-inline-action:hover:not(:disabled) { background:transparent; color:var(--color-text); text-decoration:none; opacity:1; }
569 .agent-manager-actions { display:flex; flex-wrap:wrap; align-items:center; gap:.4rem; }
570 .agent-profile-availability input { width:0; height:0; margin:0; border:0; }
571 .sr-only { position:absolute; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden; clip:rect(0,0,0,0); white-space:nowrap; border:0; }
572 .agent-editor :focus-visible { outline:2px solid var(--agent-editor-action); outline-offset:2px; }
573 .modal-inner.agent-editor-easy { width:min(720px,calc(100vw - 2rem)); max-width:720px; }
574 .modal-inner.agent-editor-advanced { width:92vw; max-width:none; height:min(calc(100vh - 4rem),80rem); max-height:calc(100vh - 4rem); }
575 .modal-inner.agent-editor-advanced .modal-scroll,.modal-inner.agent-editor-advanced .modal-bd,.modal-inner.agent-editor-advanced .agent-editor,.modal-inner.agent-editor-advanced .agent-editor-workspace { display:flex; flex:1 1 auto; min-height:0; }
576 .modal-inner.agent-editor-advanced .modal-scroll { max-height:none; }
577 .modal-inner.agent-editor-advanced .agent-editor,.modal-inner.agent-editor-advanced .agent-editor-workspace { flex-direction:column; }
578 @media (max-width: 760px) {
579 .modal-inner.agent-editor-easy,.modal-inner.agent-editor-advanced { width:100vw; max-width:none; height:100vh; max-height:none; border-radius:0; }
580 .agent-editor-topbar { align-items:flex-start; flex-wrap:wrap; }
581 .agent-editor-heading { width:100%; }
582 .agent-editor-topbar-actions { width:100%; flex-wrap:wrap; justify-content:flex-end; }
583 .agent-editor-scope { flex:1 1 14rem; }
584 .agent-easy-identity { grid-template-columns:1fr; justify-items:center; padding-top:1.8rem; }
585 .agent-id-feedback { width:100%; }
586 .agent-advanced { grid-template-columns:1fr; }
587 .agent-advanced-nav { position:static; flex-direction:row; overflow-x:auto; }
588 .agent-advanced-nav button { grid-template-columns:auto auto auto; white-space:nowrap; }
589 .agent-model-preset-picker { grid-template-columns:minmax(0,1fr) auto; gap:.5rem; }
590 .prompt-workspace { grid-template-columns:1fr; }
591 .prompt-browser { height:22rem; max-height:22rem; }
592 .prompt-editor { max-height:none; overflow:visible; }
593 .prompt-ace { min-height:18rem; }
594 .change-plan,.advanced-identity-grid,.identity-fields { grid-template-columns:1fr; }
595 .identity-fields .agent-field.wide { grid-column:1; }
596 .policy-filters { grid-template-columns:1fr; }
597 .future-policy-control { align-items:flex-start; }
598 .policy-item { grid-template-columns:1fr; }
599 .policy-state-control { width:100%; align-self:stretch; }
600 .policy-state-control button { flex:1; }
601 .agent-manager-list-header { flex-wrap:wrap; }
602 .active-agent-display { width:100%; }
603 .agent-manager-create { margin-left:auto; }
604 .agent-manager-card { grid-template-columns:3rem minmax(0,1fr) auto; align-items:start; }
605 .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; }
606 .agent-manager-actions .agent-profile-availability { grid-column:1/-1; }
607 .agent-scope-field { flex-basis:100%; min-width:0; }
608 .agent-editor-footer { flex-wrap:wrap; }
609 .footer-actions { width:auto; max-width:100%; flex-wrap:wrap; justify-content:flex-end; }
610 .footer-actions .btn { flex:0 1 auto; text-align:center; }
611 }
612 </style>
613 </body>
614 </html>