From c327701c8330d9695b4e2f74c9401530aa304086 Mon Sep 17 00:00:00 2001 From: Alessandro <155005371+3clyp50@users.noreply.github.com> Date: Tue, 11 Aug 2026 16:25:42 +0200 Subject: [PATCH] Fix Agent Editor profile lifecycle state Base reset visibility on the sparse removal plan, preserve unrelated profile files, and surface completion feedback without waiting for model-profile refreshes. Keep saved preset controls and fresh-chat selection synchronized across global and project scopes, including the shared sidebar context race, with focused regressions and updated DOX. --- plugins/_agent_editor/AGENTS.md | 3 ++- plugins/_agent_editor/helpers/editor.py | 18 ++++++------- .../_agent_editor/webui/agent-editor-store.js | 10 +++++++- plugins/_agent_editor/webui/main.html | 4 +-- tests/test_agent_editor.py | 9 +++++++ tests/test_agent_editor_webui.py | 25 ++++++++++++++++++- tests/test_webui_chat_deletion.py | 8 ++++++ webui/components/sidebar/AGENTS.md | 1 + webui/components/sidebar/chats/chats-store.js | 5 +++- 9 files changed, 67 insertions(+), 16 deletions(-) diff --git a/plugins/_agent_editor/AGENTS.md b/plugins/_agent_editor/AGENTS.md index b32322fe5..d19032cc1 100644 --- a/plugins/_agent_editor/AGENTS.md +++ b/plugins/_agent_editor/AGENTS.md @@ -68,7 +68,8 @@ row exposes scoped availability, duplication, restore for inherited profiles, icon-only Edit, and Delete for profiles owned by that scope. Duplicate materializes the effective source profile into the selected writable layer - with a collision-free ID and title. + with a collision-free ID and title. Restore visibility follows the sparse + removal plan, ignores unrelated profile files, and reports successful reset. Availability changes quietly refresh the adjacent profile switcher catalog without a success toast. - The same project selector is available inside Create and Edit. Create keeps diff --git a/plugins/_agent_editor/helpers/editor.py b/plugins/_agent_editor/helpers/editor.py index 59105f57d..3920f7b52 100644 --- a/plugins/_agent_editor/helpers/editor.py +++ b/plugins/_agent_editor/helpers/editor.py @@ -166,12 +166,6 @@ def _profile_root(profile_id: str, project_name: str = "") -> Path: ) -def _scope_has_files(root: Path) -> bool: - return root.is_dir() and any( - path.is_file() or path.is_symlink() for path in root.rglob("*") - ) - - def _scope_owns_custom_profile(profile_id: str, project_name: str) -> bool: if not _profile_root(profile_id, project_name).is_dir(): return False @@ -222,7 +216,10 @@ def list_profiles(context: Any | None = None) -> list[dict[str, Any]]: "origin": state["origin"], "origin_chain": state["origin_chain"], "built_in": state["built_in"], - "scope_has_overrides": state["scope_has_overrides"], + "scope_has_overrides": bool( + profile_exists(profile_id, context) + and plan_remove_changes(profile_id, context).changes + ), "deletable": state["deletable"], "avatar": state["avatar"]["effective"], "avatar_url": effective_avatar_url(profile_id, context), @@ -348,7 +345,10 @@ def build_profile_state( "origin": metadata.pop("origin"), "origin_chain": metadata.pop("origin_chain"), "built_in": metadata.pop("built_in"), - "scope_has_overrides": metadata.pop("scope_has_overrides"), + "scope_has_overrides": bool( + profile_exists(profile_id, context) + and plan_remove_changes(profile_id, context).changes + ), "deletable": metadata.pop("deletable"), "metadata": metadata, "avatar_url": effective_avatar_url(profile_id, context), @@ -378,7 +378,6 @@ def metadata_state(profile_id: str, context: Any | None = None) -> dict[str, Any "inherited_source": _relative_source(inherited_source), } - scope_root = _profile_root(profile_id, project_name) built_in = (Path(files.get_abs_path("agents")) / profile_id).is_dir() plugin_origin = any(layer.kind == "plugin" for layer in layers) state.update( @@ -386,7 +385,6 @@ def metadata_state(profile_id: str, context: Any | None = None) -> dict[str, Any "origin": "Built-in" if built_in else "Plugin" if plugin_origin else "Custom", "origin_chain": list(dict.fromkeys(layer.kind for layer in layers)), "built_in": built_in, - "scope_has_overrides": _scope_has_files(scope_root), "deletable": _scope_owns_custom_profile(profile_id, project_name), } ) diff --git a/plugins/_agent_editor/webui/agent-editor-store.js b/plugins/_agent_editor/webui/agent-editor-store.js index b0e4712b6..1bfabd8b4 100644 --- a/plugins/_agent_editor/webui/agent-editor-store.js +++ b/plugins/_agent_editor/webui/agent-editor-store.js @@ -346,6 +346,10 @@ const model = { ...this.scopeInput(), }); await this.loadProfiles(); + globalThis.justToast?.( + `${profile.title || profile.id} reset to default.`, + "success", 1800, "agent-profile-reset", + ); await modelConfigStore.loadAgentProfiles(true); if (editorState) { await this.loadEditor(profile.id, false); @@ -1275,7 +1279,7 @@ const model = { context_id: created.ctxid, ...(scopeProject ? { name: scopeProject } : {}), }); - await callJsonApi("/agent_profile_set", { + const selectedProfile = await callJsonApi("/agent_profile_set", { context_id: created.ctxid, agent_profile: profileId, }); @@ -1288,6 +1292,10 @@ const model = { try { sessionStorage.setItem(READY_NOTE_KEY, created.ctxid); } catch {} } await chatsStore.selectChat(created.ctxid); + if (chatsStore.selectedContext) { + chatsStore.selectedContext.agent_profile = selectedProfile.agent_profile || profileId; + chatsStore.selectedContext.agent_profile_label = selectedProfile.agent_profile_label || profileId; + } document.dispatchEvent(new CustomEvent("chat-created", { detail: { ctxid: created.ctxid } })); return created.ctxid; } catch (error) { diff --git a/plugins/_agent_editor/webui/main.html b/plugins/_agent_editor/webui/main.html index 1a3e91d7c..45c05e9e1 100644 --- a/plugins/_agent_editor/webui/main.html +++ b/plugins/_agent_editor/webui/main.html @@ -156,7 +156,7 @@
-