Refactor memory imports; add Alpine move directives

Cleanup and refactor memory plugin imports to use plugins.memory.helpers.memory directly (remove sys.path hacks) and adjust related tools (memory_load, memory_save, memory_delete, memory_forget). Move the memory quick-action entry to a new start extension file and update quick-actions HTML to remove obsolete extension points and rename the dropdown end id. Update JS extension caller to forward multiple arguments (rest params) and add several Alpine directives (move-to-start, move-to-end, move-to, move-before, move-after) plus selector helpers to support dynamic element repositioning. Also removed an unused python/__init__.py and minor whitespace/comment cleanups.

frdel committed Feb 17, 2026 at 11:42 UTC 39a81d0d5d145de4299fb69e7e1adbabbeb96f33
12 files changed +102 -76
plugins/memory/extensions/python/message_loop_prompts_after/_91_recall_wait.py
+1 -1
@@ -1,6 +1,6 @@
1 from python.helpers.extension import Extension
2 from agent import LoopData
3 -from plugins.memory.extensions.backend.message_loop_prompts_after._50_recall_memories import DATA_NAME_TASK as DATA_NAME_TASK_MEMORIES, DATA_NAME_ITER as DATA_NAME_ITER_MEMORIES
3 +from plugins.memory.extensions.python.message_loop_prompts_after._50_recall_memories import DATA_NAME_TASK as DATA_NAME_TASK_MEMORIES, DATA_NAME_ITER as DATA_NAME_ITER_MEMORIES
4 from python.helpers import settings
5
6 class RecallWait(Extension):
plugins/memory/extensions/webui/sidebar-quick-actions-main-2/memory-entry.html deleted
-19
@@ -1,19 +0,0 @@
1 -<!-- <html>
2 -
3 -<head>
4 - <meta name="plugin-target" content=".quick-actions-dropdown">
5 -</head>
6 -
7 -<body>
8 - <button class="dropdown-item" @click="openModal('../plugins/memory/webui/memory-dashboard.html'); dropdownOpen = false">
9 - <span class="material-symbols-outlined">psychology</span>
10 - <span>Memories</span>
11 - </button>
12 -</body>
13 -
14 -</html> -->
15 -
16 - <!-- Memory -->
17 - <button class="config-button" id="memory-dash" @click="openModal('../plugins/memory/webui/memory-dashboard.html')" title="Memory">
18 - <span class="material-symbols-outlined">psychology</span>
19 - </button>
plugins/memory/extensions/webui/sidebar-quick-actions-main-start/memory-entry.html new
+7
@@ -0,0 +1,7 @@
1 +<!-- Memory -->
2 +<div x-data>
3 + <button x-move-after=".config-button#dashboard" class="config-button" id="memory-dash" @click="openModal('../plugins/memory/webui/memory-dashboard.html')"
4 + title="Memory">
5 + <span class="material-symbols-outlined">psychology</span>
6 + </button>
7 +</div>
\ No newline at end of file
plugins/memory/tools/behaviour_adjustment.py
-2
@@ -2,8 +2,6 @@ from python.helpers import files
2 from python.helpers.tool import Tool, Response
3 from agent import Agent
4 from python.helpers.log import LogItem
5 -
6 -# Direct import - this tool lives inside the memory plugin
5 from plugins.memory.helpers import memory
6
7
plugins/memory/tools/memory_delete.py
+1 -7
@@ -1,12 +1,6 @@
1 from python.helpers.tool import Tool, Response
2 +from plugins.memory.helpers.memory import Memory
3
3 -# Import Memory from plugin
4 -import sys
5 -from pathlib import Path
6 -_plugin_root = Path(__file__).parent.parent
7 -if str(_plugin_root) not in sys.path:
8 - sys.path.insert(0, str(_plugin_root))
9 -from helpers.memory import Memory
4
5
6 class MemoryDelete(Tool):
plugins/memory/tools/memory_forget.py
+1 -7
@@ -1,12 +1,6 @@
1 from python.helpers.tool import Tool, Response
2 +from plugins.memory.helpers.memory import Memory
3
3 -# Import Memory and DEFAULT_THRESHOLD from plugin
4 -import sys
5 -from pathlib import Path
6 -_plugin_root = Path(__file__).parent.parent
7 -if str(_plugin_root) not in sys.path:
8 - sys.path.insert(0, str(_plugin_root))
9 -from helpers.memory import Memory
4 from tools.memory_load import DEFAULT_THRESHOLD
5
6
plugins/memory/tools/memory_load.py
+1 -8
@@ -1,12 +1,5 @@
1 from python.helpers.tool import Tool, Response
2 -
3 -# Import Memory from plugin
4 -import sys
5 -from pathlib import Path
6 -_plugin_root = Path(__file__).parent.parent
7 -if str(_plugin_root) not in sys.path:
8 - sys.path.insert(0, str(_plugin_root))
9 -from helpers.memory import Memory
2 +from plugins.memory.helpers.memory import Memory
3
4 DEFAULT_THRESHOLD = 0.7
5 DEFAULT_LIMIT = 10
plugins/memory/tools/memory_save.py
+1 -7
@@ -1,12 +1,6 @@
1 from python.helpers.tool import Tool, Response
2 +from plugins.memory.helpers.memory import Memory
3
3 -# Import Memory from plugin
4 -import sys
5 -from pathlib import Path
6 -_plugin_root = Path(__file__).parent.parent
7 -if str(_plugin_root) not in sys.path:
8 - sys.path.insert(0, str(_plugin_root))
9 -from helpers.memory import Memory
4
5
6 class MemorySave(Tool):
python/__init__.py
webui/components/sidebar/top-section/quick-actions.html
+1 -23
@@ -19,23 +19,11 @@
19 <span class="material-symbols-outlined">home</span>
20 </button>
21
22 - <x-extension id="sidebar-quick-actions-main-2"></x-extension>
23 -
24 -
25 - <!-- Memory -->
26 - <!-- <button class="config-button" id="memory-dash" @click="openModal('../plugins/memory/webui/memory-dashboard.html')" title="Memory">
27 - <span class="material-symbols-outlined">psychology</span>
28 - </button> -->
29 -
30 - <x-extension id="sidebar-quick-actions-main-3"></x-extension>
31 -
22 <!-- Scheduler -->
23 <button class="config-button" id="scheduler" @click="openModal('modals/scheduler/scheduler-modal.html')" title="Scheduler">
24 <span class="material-symbols-outlined">schedule</span>
25 </button>
26
37 - <x-extension id="sidebar-quick-actions-main-4"></x-extension>
38 -
27 <!-- Settings -->
28 <button class="config-button" id="settings" @click="openModal('settings/settings.html')" title="Settings">
29 <span class="material-symbols-outlined">settings</span>
@@ -58,8 +46,6 @@
46
47 <div class="dropdown-header">Navigation</div>
48
61 - <x-extension id="sidebar-quick-actions-dropdown-navigation-start"></x-extension>
62 -
49 <button class="dropdown-item" @click="deselectChat(); dropdownOpen = false">
50 <span class="material-symbols-outlined">home</span>
51 <span>Dashboard</span>
@@ -79,15 +65,10 @@
65 <span class="material-symbols-outlined">settings</span>
66 <span>Settings</span>
67 </button>
82 -
83 - <x-extension id="sidebar-quick-actions-dropdown-navigation-end"></x-extension>
84 -
68
69 <div class="dropdown-separator"></div>
70 <div class="dropdown-header">Chat Actions</div>
71
89 - <x-extension id="sidebar-quick-actions-dropdown-chat-start"></x-extension>
90 -
72 <button class="dropdown-item" @click="$store.chats.newChat(); dropdownOpen = false">
73 <span class="material-symbols-outlined">edit_square</span>
74 <span>New Chat</span>
@@ -108,11 +89,8 @@
89 <span>Clear Chat</span>
90 </button>
91
111 - <x-extension id="sidebar-quick-actions-dropdown-chat-end"></x-extension>
112 -
92 <div class="dropdown-separator"></div>
93
115 - <x-extension id="sidebar-quick-actions-dropdown-rest-start"></x-extension>
94
95 <button class="dropdown-item" @click="$confirmClick($event, () => { $store.chats.logout(); dropdownOpen = false })" :disabled="!$store.chats.loggedIn">
96 <span class="material-symbols-outlined">logout</span>
@@ -124,7 +102,7 @@
102 <span>Restart A0</span>
103 </button>
104
127 - <x-extension id="sidebar-quick-actions-dropdown-rest-end"></x-extension>
105 + <x-extension id="sidebar-quick-actions-dropdown-end"></x-extension>
106 </div>
107 </div>
108 </body>
webui/js/extensions.js
+2 -2
@@ -36,11 +36,11 @@ export function invalidateCache() {
36 * @param {any} data
37 * @returns {Promise<void>}
38 */
39 -export async function callJsExtensions(extensionPoint, data){
39 +export async function callJsExtensions(extensionPoint, ...data){
40 const extensions = jsExtensionsCache.get(extensionPoint) || await loadJsExtensions(extensionPoint);
41 for(const extension of extensions){
42 try{
43 - await extension.module.default(data);
43 + await extension.module.default(...data);
44 }catch(error){
45 console.error(`Error calling extension: ${extension.path}`, error);
46 }
webui/js/initFw.js
+87
@@ -31,6 +31,93 @@ Alpine.directive(
31 }
32 );
33
34 + const resolveSelector = (expression, evaluateLater, cb) => {
35 + if (typeof expression !== "string" || !expression.trim()) return;
36 +
37 + if (/^[\s]*["']/.test(expression)) {
38 + const getSelector = evaluateLater(expression);
39 + getSelector((evaluated) => {
40 + if (typeof evaluated !== "string" || !evaluated.trim()) return;
41 + cb(evaluated.trim());
42 + });
43 + return;
44 + }
45 +
46 + cb(expression.trim());
47 + };
48 +
49 + const moveOnNextTick = (el, expression, evaluateLater, fn) => {
50 + Alpine.nextTick(() => {
51 + resolveSelector(expression, evaluateLater, (selector) => fn(el, selector));
52 + });
53 + };
54 +
55 + Alpine.directive(
56 + "move-to-start",
57 + (el, { expression }, { evaluateLater }) => {
58 + moveOnNextTick(el, expression, evaluateLater, (_el, selector) => {
59 + const parent = document.querySelector(selector);
60 + if (!parent) return;
61 + parent.insertBefore(_el, parent.firstChild);
62 + });
63 + }
64 + );
65 +
66 + Alpine.directive(
67 + "move-to-end",
68 + (el, { expression }, { evaluateLater }) => {
69 + moveOnNextTick(el, expression, evaluateLater, (_el, selector) => {
70 + const parent = document.querySelector(selector);
71 + if (!parent) return;
72 + parent.appendChild(_el);
73 + });
74 + }
75 + );
76 +
77 + Alpine.directive(
78 + "move-to",
79 + (el, { expression, modifiers, value }, { evaluateLater }) => {
80 + const orderModifier = Array.isArray(modifiers)
81 + ? modifiers.find((m) => /^\d+$/.test(m))
82 + : null;
83 +
84 + const orderRaw = orderModifier ?? value;
85 + const order = Number(orderRaw);
86 + if (!Number.isFinite(order)) return;
87 +
88 + moveOnNextTick(el, expression, evaluateLater, (_el, selector) => {
89 + const parent = document.querySelector(selector);
90 + if (!parent) return;
91 +
92 + const index = Math.max(0, Math.floor(order));
93 + const beforeNode = parent.children.item(index) ?? null;
94 + parent.insertBefore(_el, beforeNode);
95 + });
96 + }
97 + );
98 +
99 + Alpine.directive(
100 + "move-before",
101 + (el, { expression }, { evaluateLater }) => {
102 + moveOnNextTick(el, expression, evaluateLater, (_el, selector) => {
103 + const ref = document.querySelector(selector);
104 + if (!ref || !ref.parentElement) return;
105 + ref.parentElement.insertBefore(_el, ref);
106 + });
107 + }
108 + );
109 +
110 + Alpine.directive(
111 + "move-after",
112 + (el, { expression }, { evaluateLater }) => {
113 + moveOnNextTick(el, expression, evaluateLater, (_el, selector) => {
114 + const ref = document.querySelector(selector);
115 + if (!ref || !ref.parentElement) return;
116 + ref.parentElement.insertBefore(_el, ref.nextSibling);
117 + });
118 + }
119 + );
120 +
121 // run every second if the component is active
122 Alpine.directive(
123 "every-second",