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