@jungjaehoon/mama-os 0.8.3 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/agent/agent-loop.d.ts +1 -8
  3. package/dist/agent/agent-loop.d.ts.map +1 -1
  4. package/dist/agent/agent-loop.js +44 -159
  5. package/dist/agent/agent-loop.js.map +1 -1
  6. package/dist/agent/claude-cli-wrapper.d.ts +6 -0
  7. package/dist/agent/claude-cli-wrapper.d.ts.map +1 -1
  8. package/dist/agent/claude-cli-wrapper.js +6 -0
  9. package/dist/agent/claude-cli-wrapper.js.map +1 -1
  10. package/dist/agent/codex-mcp-process.d.ts +85 -0
  11. package/dist/agent/codex-mcp-process.d.ts.map +1 -0
  12. package/dist/agent/codex-mcp-process.js +357 -0
  13. package/dist/agent/codex-mcp-process.js.map +1 -0
  14. package/dist/agent/session-pool.d.ts +17 -2
  15. package/dist/agent/session-pool.d.ts.map +1 -1
  16. package/dist/agent/session-pool.js +51 -26
  17. package/dist/agent/session-pool.js.map +1 -1
  18. package/dist/agent/types.d.ts +9 -24
  19. package/dist/agent/types.d.ts.map +1 -1
  20. package/dist/agent/types.js.map +1 -1
  21. package/dist/api/graph-api.d.ts.map +1 -1
  22. package/dist/api/graph-api.js +133 -45
  23. package/dist/api/graph-api.js.map +1 -1
  24. package/dist/cli/commands/init.d.ts +1 -1
  25. package/dist/cli/commands/init.d.ts.map +1 -1
  26. package/dist/cli/commands/init.js +14 -25
  27. package/dist/cli/commands/init.js.map +1 -1
  28. package/dist/cli/commands/run.d.ts.map +1 -1
  29. package/dist/cli/commands/run.js +3 -10
  30. package/dist/cli/commands/run.js.map +1 -1
  31. package/dist/cli/commands/start.d.ts.map +1 -1
  32. package/dist/cli/commands/start.js +143 -54
  33. package/dist/cli/commands/start.js.map +1 -1
  34. package/dist/cli/commands/status.d.ts.map +1 -1
  35. package/dist/cli/commands/status.js +2 -7
  36. package/dist/cli/commands/status.js.map +1 -1
  37. package/dist/cli/config/config-manager.d.ts.map +1 -1
  38. package/dist/cli/config/config-manager.js +9 -17
  39. package/dist/cli/config/config-manager.js.map +1 -1
  40. package/dist/cli/config/types.d.ts +19 -25
  41. package/dist/cli/config/types.d.ts.map +1 -1
  42. package/dist/cli/config/types.js.map +1 -1
  43. package/dist/cli/index.js +2 -2
  44. package/dist/cli/index.js.map +1 -1
  45. package/dist/gateways/context-injector.d.ts.map +1 -1
  46. package/dist/gateways/context-injector.js +6 -3
  47. package/dist/gateways/context-injector.js.map +1 -1
  48. package/dist/gateways/discord.d.ts +4 -0
  49. package/dist/gateways/discord.d.ts.map +1 -1
  50. package/dist/gateways/discord.js +39 -16
  51. package/dist/gateways/discord.js.map +1 -1
  52. package/dist/gateways/message-router.d.ts +6 -1
  53. package/dist/gateways/message-router.d.ts.map +1 -1
  54. package/dist/gateways/message-router.js +92 -7
  55. package/dist/gateways/message-router.js.map +1 -1
  56. package/dist/multi-agent/agent-process-manager.d.ts.map +1 -1
  57. package/dist/multi-agent/agent-process-manager.js +36 -9
  58. package/dist/multi-agent/agent-process-manager.js.map +1 -1
  59. package/dist/multi-agent/runtime-process.d.ts +4 -4
  60. package/dist/multi-agent/runtime-process.d.ts.map +1 -1
  61. package/dist/multi-agent/runtime-process.js +9 -20
  62. package/dist/multi-agent/runtime-process.js.map +1 -1
  63. package/dist/multi-agent/types.d.ts +13 -8
  64. package/dist/multi-agent/types.d.ts.map +1 -1
  65. package/dist/multi-agent/types.js.map +1 -1
  66. package/dist/setup/setup-prompt.d.ts +1 -1
  67. package/dist/setup/setup-prompt.d.ts.map +1 -1
  68. package/dist/setup/setup-prompt.js +19 -0
  69. package/dist/setup/setup-prompt.js.map +1 -1
  70. package/dist/setup/setup-server.d.ts.map +1 -1
  71. package/dist/setup/setup-server.js +39 -16
  72. package/dist/setup/setup-server.js.map +1 -1
  73. package/dist/skills/skill-registry.d.ts.map +1 -1
  74. package/dist/skills/skill-registry.js +5 -2
  75. package/dist/skills/skill-registry.js.map +1 -1
  76. package/package.json +5 -3
  77. package/public/setup.html +12 -1
  78. package/public/viewer/js/modules/chat.js +1760 -1976
  79. package/public/viewer/js/modules/dashboard.js +613 -695
  80. package/public/viewer/js/modules/graph.js +857 -970
  81. package/public/viewer/js/modules/memory.js +357 -312
  82. package/public/viewer/js/modules/settings.js +1009 -1026
  83. package/public/viewer/js/modules/skills.js +336 -355
  84. package/public/viewer/js/utils/api.js +255 -255
  85. package/public/viewer/js/utils/debug-logger.js +20 -26
  86. package/public/viewer/js/utils/dom.js +73 -60
  87. package/public/viewer/js/utils/format.js +182 -228
  88. package/public/viewer/js/utils/markdown.js +40 -0
  89. package/public/viewer/src/modules/chat.ts +2258 -0
  90. package/public/viewer/src/modules/dashboard.ts +1052 -0
  91. package/public/viewer/src/modules/graph.ts +1080 -0
  92. package/public/viewer/src/modules/memory.ts +453 -0
  93. package/public/viewer/src/modules/settings.ts +1398 -0
  94. package/public/viewer/src/modules/skills.ts +457 -0
  95. package/public/viewer/src/types/global.d.ts +168 -0
  96. package/public/viewer/src/utils/api.ts +650 -0
  97. package/public/viewer/src/utils/debug-logger.ts +36 -0
  98. package/public/viewer/src/utils/dom.ts +138 -0
  99. package/public/viewer/src/utils/format.ts +331 -0
  100. package/public/viewer/src/utils/markdown.ts +46 -0
  101. package/public/viewer/tsconfig.viewer.json +18 -0
  102. package/public/viewer/viewer.html +214 -311
  103. package/dist/agent/codex-cli-wrapper.d.ts +0 -85
  104. package/dist/agent/codex-cli-wrapper.d.ts.map +0 -1
  105. package/dist/agent/codex-cli-wrapper.js +0 -295
  106. package/dist/agent/codex-cli-wrapper.js.map +0 -1
@@ -8,356 +8,401 @@
8
8
  * - Related decision suggestions for chat messages
9
9
  * - Save decision form modal
10
10
  */
11
-
12
11
  /* eslint-env browser */
13
12
  /* global lucide */
14
-
15
- import { escapeHtml, debounce, showToast } from '../utils/dom.js';
13
+ import { escapeHtml, debounce, showToast, getElementByIdOrNull } from '../utils/dom.js';
16
14
  import { formatRelativeTime, truncateText } from '../utils/format.js';
17
15
  import { API } from '../utils/api.js';
18
-
16
+ import { DebugLogger } from '../utils/debug-logger.js';
17
+ const logger = new DebugLogger('Memory');
19
18
  /**
20
19
  * Memory Module Class
21
20
  */
22
21
  export class MemoryModule {
23
- constructor() {
24
- // State
25
- this.searchData = [];
26
- this.debouncedSearch = debounce(() => this.performSearch(), 300);
27
-
28
- // Initialize event listeners
29
- this.initEventListeners();
30
- }
31
-
32
- /**
33
- * Initialize all event listeners
34
- */
35
- initEventListeners() {
36
- // Modal click outside to close
37
- document.addEventListener('click', (e) => {
38
- const modal = document.getElementById('save-decision-modal');
39
- if (modal && e.target === modal) {
40
- this.hideSaveForm();
41
- }
42
- });
43
-
44
- // Escape key to close modal
45
- document.addEventListener('keydown', (e) => {
46
- if (e.key === 'Escape') {
47
- const modal = document.getElementById('save-decision-modal');
48
- if (modal && modal.classList.contains('visible')) {
49
- this.hideSaveForm();
50
- }
51
- }
52
- });
53
- }
54
-
55
- /**
56
- * Handle memory search input event
57
- * @param {KeyboardEvent} event - Keyboard event
58
- */
59
- handleSearchInput(event) {
60
- if (event.key === 'Enter') {
61
- this.search();
62
- } else {
63
- this.debouncedSearch();
22
+ searchData = [];
23
+ debouncedSearch = debounce(() => this.performSearch(), 300);
24
+ currentQuery = '';
25
+ constructor() {
26
+ // Initialize event listeners
27
+ this.initEventListeners();
64
28
  }
65
- }
66
-
67
- /**
68
- * Perform search (internal, debounced)
69
- */
70
- async performSearch() {
71
- const input = document.getElementById('memory-search-input');
72
- const query = input.value.trim();
73
-
74
- if (!query) {
75
- this.showPlaceholder();
76
- return;
77
- }
78
-
79
- await this.search();
80
- }
81
-
82
- /**
83
- * Search memory decisions via API
84
- */
85
- async search() {
86
- const input = document.getElementById('memory-search-input');
87
- const query = input.value.trim();
88
-
89
- if (!query) {
90
- this.showPlaceholder();
91
- return;
29
+ /**
30
+ * Initialize all event listeners
31
+ */
32
+ initEventListeners() {
33
+ // Memory card click handler (event delegation)
34
+ const resultsContainer = getElementByIdOrNull('memory-results');
35
+ if (resultsContainer) {
36
+ resultsContainer.addEventListener('click', (e) => {
37
+ const card = e.target?.closest('[data-memory-card]');
38
+ if (card) {
39
+ const idx = parseInt(card.dataset.memoryCard || '', 10);
40
+ if (!Number.isNaN(idx)) {
41
+ this.toggleCard(idx);
42
+ }
43
+ }
44
+ });
45
+ }
46
+ // Modal click outside to close
47
+ document.addEventListener('click', (e) => {
48
+ const modal = getElementByIdOrNull('save-decision-modal');
49
+ if (modal && e.target === modal) {
50
+ this.hideSaveForm();
51
+ }
52
+ });
53
+ // Escape key to close modal
54
+ document.addEventListener('keydown', (e) => {
55
+ if (e.key === 'Escape') {
56
+ const modal = getElementByIdOrNull('save-decision-modal');
57
+ if (modal && modal.classList.contains('visible')) {
58
+ this.hideSaveForm();
59
+ }
60
+ }
61
+ });
92
62
  }
93
-
94
- this.setStatus('Searching...', 'loading');
95
-
96
- try {
97
- const data = await API.searchMemory(query, 10);
98
- this.searchData = data.results || [];
99
- this.renderResults(this.searchData, query);
100
- this.setStatus(`Found ${this.searchData.length} decision(s)`, '');
101
- } catch (error) {
102
- console.error('[Memory] Search error:', error);
103
- this.setStatus(`Error: ${error.message}`, 'error');
63
+ /**
64
+ * Handle memory search input event
65
+ * @param {KeyboardEvent} event - Keyboard event
66
+ */
67
+ handleSearchInput(event) {
68
+ if (event.key === 'Enter') {
69
+ this.search();
70
+ }
71
+ else {
72
+ this.debouncedSearch();
73
+ }
104
74
  }
105
- }
106
-
107
- /**
108
- * Search for related decisions (called automatically for chat messages)
109
- * @param {string} message - Chat message text
110
- * @returns {Promise<Array>} Related decisions
111
- */
112
- async searchRelated(message) {
113
- if (!message || message.length < 3) {
114
- return [];
75
+ /**
76
+ * Perform search (internal, debounced)
77
+ */
78
+ async performSearch() {
79
+ const input = getElementByIdOrNull('memory-search-input');
80
+ if (!input) {
81
+ return;
82
+ }
83
+ const query = input.value.trim();
84
+ if (!query) {
85
+ this.showPlaceholder();
86
+ return;
87
+ }
88
+ await this.search();
115
89
  }
116
-
117
- try {
118
- const data = await API.searchMemory(message, 5);
119
- return data.results || [];
120
- } catch (error) {
121
- console.error('[Memory] Related search error:', error);
122
- return [];
90
+ /**
91
+ * Search memory decisions via API
92
+ */
93
+ async search() {
94
+ const input = getElementByIdOrNull('memory-search-input');
95
+ if (!input) {
96
+ return;
97
+ }
98
+ const query = input.value.trim();
99
+ if (!query) {
100
+ this.showPlaceholder();
101
+ return;
102
+ }
103
+ this.setStatus('Searching...', 'loading');
104
+ try {
105
+ const data = await API.searchMemory(query, 10);
106
+ this.searchData = data.results || [];
107
+ this.renderResults(this.searchData, query);
108
+ this.setStatus(`Found ${this.searchData.length} decision(s)`, '');
109
+ }
110
+ catch (error) {
111
+ const message = error instanceof Error ? error.message : String(error);
112
+ logger.error('Search error:', message);
113
+ this.setStatus(`Error: ${message}`, 'error');
114
+ }
123
115
  }
124
- }
125
-
126
- /**
127
- * Show related decisions for a chat message
128
- * @param {string} message - Chat message text
129
- */
130
- async showRelatedForMessage(message) {
131
- const results = await this.searchRelated(message);
132
-
133
- if (results.length > 0) {
134
- this.searchData = results;
135
-
136
- // Update search input with the query (if element exists)
137
- const input = document.getElementById('memory-search-input');
138
- if (input) {
139
- input.value = message.substring(0, 50) + (message.length > 50 ? '...' : '');
140
- }
141
-
142
- // Render results
143
- this.renderResults(results, message);
144
- this.setStatus(`${results.length} related decision(s) found`, '');
145
-
146
- // Show notification
147
- showToast(`🧠 ${results.length} related MAMA decision(s) found`);
116
+ /**
117
+ * Search for related decisions (called automatically for chat messages)
118
+ * @param {string} message - Chat message text
119
+ * @returns {Promise<Array>} Related decisions
120
+ */
121
+ async searchRelated(message) {
122
+ if (!message || message.length < 3) {
123
+ return [];
124
+ }
125
+ try {
126
+ const data = await API.searchMemory(message, 5);
127
+ return data.results || [];
128
+ }
129
+ catch (error) {
130
+ const message = error instanceof Error ? error.message : String(error);
131
+ logger.error('Related search error:', message);
132
+ return [];
133
+ }
148
134
  }
149
- }
150
-
151
- /**
152
- * Render search results
153
- * @param {Array} results - Search results
154
- * @param {string} query - Search query
155
- */
156
- renderResults(results, query) {
157
- const container = document.getElementById('memory-results');
158
-
159
- // Guard: element may not exist if not on Memory tab
160
- if (!container) {
161
- return;
135
+ /**
136
+ * Show related decisions for a chat message
137
+ * @param {string} message - Chat message text
138
+ */
139
+ async showRelatedForMessage(message) {
140
+ const results = await this.searchRelated(message);
141
+ if (results.length > 0) {
142
+ this.searchData = results;
143
+ // Update search input with the query (if element exists)
144
+ const input = getElementByIdOrNull('memory-search-input');
145
+ if (input) {
146
+ input.value = message.substring(0, 50) + (message.length > 50 ? '...' : '');
147
+ }
148
+ // Render results
149
+ this.renderResults(results, message);
150
+ this.setStatus(`${results.length} related decision(s) found`, '');
151
+ // Show notification
152
+ showToast(`🧠 ${results.length} related MAMA decision(s) found`);
153
+ }
162
154
  }
163
-
164
- if (!results || results.length === 0) {
165
- container.innerHTML = `
155
+ /**
156
+ * Render search results
157
+ * @param {Array} results - Search results
158
+ * @param {string} query - Search query
159
+ */
160
+ renderResults(results, query) {
161
+ const container = getElementByIdOrNull('memory-results');
162
+ // Guard: element may not exist if not on Memory tab
163
+ if (!container) {
164
+ return;
165
+ }
166
+ if (!results || results.length === 0) {
167
+ container.innerHTML = `
166
168
  <div class="memory-placeholder">
167
169
  <p>No decisions found for "${escapeHtml(query)}"</p>
168
170
  <p class="memory-hint">Try different keywords or check if you have saved decisions</p>
169
171
  </div>
170
172
  `;
171
- return;
172
- }
173
-
174
- const html = results
175
- .map(
176
- (item, idx) => `
177
- <div class="memory-card" onclick="window.memoryModule.toggleCard(${idx})">
173
+ return;
174
+ }
175
+ const html = results
176
+ .map((item, idx) => {
177
+ const rawOutcome = String(item.outcome || 'PENDING');
178
+ const normalizedOutcome = rawOutcome.toLowerCase();
179
+ const outcomeClass = ['success', 'failed', 'partial', 'pending'].includes(normalizedOutcome)
180
+ ? normalizedOutcome
181
+ : 'pending';
182
+ return `
183
+ <div class="memory-card" data-memory-card="${idx}">
178
184
  <div class="memory-card-header">
179
185
  <span class="memory-card-topic">${escapeHtml(item.topic || 'Unknown')}</span>
180
186
  ${item.similarity ? `<span class="memory-card-score">${Math.round(item.similarity * 100)}%</span>` : ''}
181
187
  </div>
182
188
  <div class="memory-card-decision">${escapeHtml(truncateText(item.decision, 150))}</div>
183
189
  <div class="memory-card-meta">
184
- <span class="memory-card-outcome ${(item.outcome || 'pending').toLowerCase()}">${item.outcome || 'PENDING'}</span>
190
+ <span class="memory-card-outcome ${outcomeClass}">${escapeHtml(rawOutcome)}</span>
185
191
  <span>${formatRelativeTime(item.created_at)}</span>
186
192
  </div>
187
193
  <div class="memory-card-reasoning">${escapeHtml(item.reasoning || 'No reasoning provided')}</div>
188
194
  </div>
189
- `
190
- )
191
- .join('');
192
-
193
- container.innerHTML = html;
194
- }
195
-
196
- /**
197
- * Toggle memory card expand/collapse
198
- * @param {number} idx - Card index
199
- */
200
- toggleCard(idx) {
201
- const cards = document.querySelectorAll('.memory-card');
202
- cards.forEach((card, i) => {
203
- if (i === idx) {
204
- card.classList.toggle('expanded');
205
- } else {
206
- card.classList.remove('expanded');
207
- }
208
- });
209
- }
210
-
211
- /**
212
- * Show memory placeholder
213
- */
214
- showPlaceholder() {
215
- const container = document.getElementById('memory-results');
216
- container.innerHTML = `
195
+ `;
196
+ })
197
+ .join('');
198
+ container.innerHTML = html;
199
+ }
200
+ /**
201
+ * Toggle memory card expand/collapse
202
+ * @param {number} idx - Card index
203
+ */
204
+ toggleCard(idx) {
205
+ const cards = document.querySelectorAll('.memory-card');
206
+ cards.forEach((card, i) => {
207
+ if (i === idx) {
208
+ card.classList.toggle('expanded');
209
+ }
210
+ else {
211
+ card.classList.remove('expanded');
212
+ }
213
+ });
214
+ }
215
+ /**
216
+ * Show memory placeholder
217
+ */
218
+ showPlaceholder() {
219
+ const container = getElementByIdOrNull('memory-results');
220
+ if (!container) {
221
+ return;
222
+ }
223
+ container.innerHTML = `
217
224
  <div class="memory-placeholder">
218
225
  <p><i data-lucide="brain"></i> Search your MAMA decisions</p>
219
226
  <p class="memory-hint">Type a keyword or send a chat message to see related decisions</p>
220
227
  </div>
221
228
  `;
222
- this.setStatus('', '');
223
- // Reinitialize Lucide icons for dynamic content
224
- if (typeof lucide !== 'undefined' && typeof window.lucideConfig !== 'undefined') {
225
- lucide.createIcons(window.lucideConfig);
229
+ this.setStatus('', '');
230
+ // Reinitialize Lucide icons for dynamic content
231
+ if (typeof lucide !== 'undefined' && typeof window.lucideConfig !== 'undefined') {
232
+ lucide.createIcons(window.lucideConfig);
233
+ }
226
234
  }
227
- }
228
-
229
- /**
230
- * Set memory status message
231
- * @param {string} message - Status message
232
- * @param {string} type - Status type (loading, error, success, '')
233
- */
234
- setStatus(message, type) {
235
- const status = document.getElementById('memory-status');
236
- if (!status) {
237
- return;
238
- } // Guard: element may not exist if not on Memory tab
239
- status.textContent = message;
240
- status.className = 'memory-status ' + (type || '');
241
- }
242
-
243
- /**
244
- * Show save decision form modal
245
- */
246
- showSaveForm() {
247
- const modal = document.getElementById('save-decision-modal');
248
- modal.classList.add('visible');
249
-
250
- // Clear form
251
- document.getElementById('save-topic').value = '';
252
- document.getElementById('save-decision').value = '';
253
- document.getElementById('save-reasoning').value = '';
254
- document.getElementById('save-confidence').value = '0.8';
255
- document.getElementById('save-form-status').textContent = '';
256
- document.getElementById('save-form-status').className = 'save-form-status';
257
-
258
- // Focus on topic field
259
- setTimeout(() => {
260
- document.getElementById('save-topic').focus();
261
- }, 100);
262
- }
263
-
264
- /**
265
- * Show save form with pre-filled text (for /save command)
266
- */
267
- showSaveFormWithText(text) {
268
- const modal = document.getElementById('save-decision-modal');
269
- modal.classList.add('visible');
270
-
271
- // Pre-fill decision field
272
- document.getElementById('save-topic').value = '';
273
- document.getElementById('save-decision').value = text;
274
- document.getElementById('save-reasoning').value = '';
275
- document.getElementById('save-confidence').value = '0.8';
276
- document.getElementById('save-form-status').textContent = '';
277
- document.getElementById('save-form-status').className = 'save-form-status';
278
-
279
- // Focus on topic field
280
- setTimeout(() => {
281
- document.getElementById('save-topic').focus();
282
- }, 100);
283
- }
284
-
285
- /**
286
- * Hide save decision form modal
287
- */
288
- hideSaveForm() {
289
- const modal = document.getElementById('save-decision-modal');
290
- modal.classList.remove('visible');
291
- }
292
-
293
- /**
294
- * Execute search with query (for /search command)
295
- */
296
- async searchWithQuery(query) {
297
- const searchInput = document.getElementById('memory-search');
298
- if (searchInput) {
299
- searchInput.value = query;
235
+ /**
236
+ * Set memory status message
237
+ * @param {string} message - Status message
238
+ * @param {string} type - Status type (loading, error, success, '')
239
+ */
240
+ setStatus(message, type = '') {
241
+ const status = getElementByIdOrNull('memory-status');
242
+ if (!status) {
243
+ return;
244
+ } // Guard: element may not exist if not on Memory tab
245
+ status.textContent = message;
246
+ status.className = 'memory-status ' + (type || '');
300
247
  }
301
- this.currentQuery = query;
302
- await this.search();
303
- }
304
-
305
- /**
306
- * Submit save decision form
307
- */
308
- async submitSaveForm() {
309
- const topic = document.getElementById('save-topic').value.trim();
310
- const decision = document.getElementById('save-decision').value.trim();
311
- const reasoning = document.getElementById('save-reasoning').value.trim();
312
- const confidence = parseFloat(document.getElementById('save-confidence').value);
313
-
314
- const statusEl = document.getElementById('save-form-status');
315
- const submitBtn = document.querySelector('.save-form-submit');
316
-
317
- // Validation
318
- if (!topic || !decision || !reasoning) {
319
- statusEl.textContent = 'Please fill in all required fields';
320
- statusEl.className = 'save-form-status error';
321
- return;
248
+ /**
249
+ * Show save decision form modal
250
+ */
251
+ showSaveForm() {
252
+ const modal = getElementByIdOrNull('save-decision-modal');
253
+ if (!modal) {
254
+ return;
255
+ }
256
+ modal.classList.add('visible');
257
+ // Clear form
258
+ const topicInput = getElementByIdOrNull('save-topic');
259
+ const decisionInput = getElementByIdOrNull('save-decision');
260
+ const reasoningInput = getElementByIdOrNull('save-reasoning');
261
+ const confidenceInput = getElementByIdOrNull('save-confidence');
262
+ const statusEl = getElementByIdOrNull('save-form-status');
263
+ if (topicInput) {
264
+ topicInput.value = '';
265
+ }
266
+ if (decisionInput) {
267
+ decisionInput.value = '';
268
+ }
269
+ if (reasoningInput) {
270
+ reasoningInput.value = '';
271
+ }
272
+ if (confidenceInput) {
273
+ confidenceInput.value = '0.8';
274
+ }
275
+ if (statusEl) {
276
+ statusEl.textContent = '';
277
+ statusEl.className = 'save-form-status';
278
+ }
279
+ // Focus on topic field
280
+ setTimeout(() => {
281
+ const focusTarget = getElementByIdOrNull('save-topic');
282
+ focusTarget?.focus();
283
+ }, 100);
284
+ }
285
+ /**
286
+ * Show save form with pre-filled text (for /save command)
287
+ */
288
+ showSaveFormWithText(text) {
289
+ const modal = getElementByIdOrNull('save-decision-modal');
290
+ if (!modal) {
291
+ return;
292
+ }
293
+ modal.classList.add('visible');
294
+ // Pre-fill decision field
295
+ const topicInput = getElementByIdOrNull('save-topic');
296
+ const decisionInput = getElementByIdOrNull('save-decision');
297
+ const reasoningInput = getElementByIdOrNull('save-reasoning');
298
+ const confidenceInput = getElementByIdOrNull('save-confidence');
299
+ const statusEl = getElementByIdOrNull('save-form-status');
300
+ if (topicInput) {
301
+ topicInput.value = '';
302
+ }
303
+ if (decisionInput) {
304
+ decisionInput.value = text;
305
+ }
306
+ if (reasoningInput) {
307
+ reasoningInput.value = '';
308
+ }
309
+ if (confidenceInput) {
310
+ confidenceInput.value = '0.8';
311
+ }
312
+ if (statusEl) {
313
+ statusEl.textContent = '';
314
+ statusEl.className = 'save-form-status';
315
+ }
316
+ // Focus on topic field
317
+ setTimeout(() => {
318
+ const focusTarget = getElementByIdOrNull('save-topic');
319
+ focusTarget?.focus();
320
+ }, 100);
321
+ }
322
+ /**
323
+ * Hide save decision form modal
324
+ */
325
+ hideSaveForm() {
326
+ const modal = getElementByIdOrNull('save-decision-modal');
327
+ if (!modal) {
328
+ return;
329
+ }
330
+ modal.classList.remove('visible');
322
331
  }
323
-
324
- if (isNaN(confidence) || confidence < 0 || confidence > 1) {
325
- statusEl.textContent = 'Confidence must be between 0.0 and 1.0';
326
- statusEl.className = 'save-form-status error';
327
- return;
332
+ /**
333
+ * Execute search with query (for /search command)
334
+ */
335
+ async searchWithQuery(query) {
336
+ const searchInput = getElementByIdOrNull('memory-search-input');
337
+ if (searchInput) {
338
+ searchInput.value = query;
339
+ }
340
+ this.currentQuery = query;
341
+ await this.search();
328
342
  }
329
-
330
- // Disable submit button
331
- submitBtn.disabled = true;
332
- statusEl.textContent = 'Saving...';
333
- statusEl.className = 'save-form-status';
334
-
335
- try {
336
- await API.saveDecision({ topic, decision, reasoning, confidence });
337
-
338
- // Success
339
- statusEl.textContent = '✓ Decision saved successfully!';
340
- statusEl.className = 'save-form-status success';
341
-
342
- // Show toast notification
343
- showToast('✓ Decision saved to MAMA memory');
344
-
345
- // Close modal after 1.5 seconds
346
- setTimeout(() => {
347
- this.hideSaveForm();
348
-
349
- // Refresh memory search if there's a query
350
- const searchInput = document.getElementById('memory-search-input');
351
- if (searchInput.value.trim()) {
352
- this.search();
353
- }
354
- }, 1500);
355
- } catch (error) {
356
- console.error('[Memory] Save error:', error);
357
- statusEl.textContent = `Error: ${error.message}`;
358
- statusEl.className = 'save-form-status error';
359
- } finally {
360
- submitBtn.disabled = false;
343
+ /**
344
+ * Submit save decision form
345
+ */
346
+ async submitSaveForm() {
347
+ const topicInput = getElementByIdOrNull('save-topic');
348
+ const decisionInput = getElementByIdOrNull('save-decision');
349
+ const reasoningInput = getElementByIdOrNull('save-reasoning');
350
+ const confidenceInput = getElementByIdOrNull('save-confidence');
351
+ const statusEl = getElementByIdOrNull('save-form-status');
352
+ // Select the Save button (last button in the modal actions)
353
+ const submitBtn = getElementByIdOrNull('save-form-submit');
354
+ if (!topicInput ||
355
+ !decisionInput ||
356
+ !reasoningInput ||
357
+ !confidenceInput ||
358
+ !statusEl ||
359
+ !submitBtn) {
360
+ return;
361
+ }
362
+ const topic = topicInput.value.trim();
363
+ const decision = decisionInput.value.trim();
364
+ const reasoning = reasoningInput.value.trim();
365
+ const confidence = parseFloat(confidenceInput.value);
366
+ // Validation
367
+ if (!topic || !decision || !reasoning) {
368
+ statusEl.textContent = 'Please fill in all required fields';
369
+ statusEl.className = 'save-form-status error';
370
+ return;
371
+ }
372
+ if (isNaN(confidence) || confidence < 0 || confidence > 1) {
373
+ statusEl.textContent = 'Confidence must be between 0.0 and 1.0';
374
+ statusEl.className = 'save-form-status error';
375
+ return;
376
+ }
377
+ // Disable submit button
378
+ submitBtn.disabled = true;
379
+ statusEl.textContent = 'Saving...';
380
+ statusEl.className = 'save-form-status';
381
+ try {
382
+ await API.saveDecision({ topic, decision, reasoning, confidence });
383
+ // Success
384
+ statusEl.textContent = '✓ Decision saved successfully!';
385
+ statusEl.className = 'save-form-status success';
386
+ // Show toast notification
387
+ showToast('✓ Decision saved to MAMA memory');
388
+ // Close modal after 1.5 seconds
389
+ setTimeout(() => {
390
+ this.hideSaveForm();
391
+ // Refresh memory search if there's a query
392
+ const searchInput = getElementByIdOrNull('memory-search-input');
393
+ if (searchInput?.value.trim()) {
394
+ this.search();
395
+ }
396
+ }, 1500);
397
+ }
398
+ catch (error) {
399
+ const message = error instanceof Error ? error.message : String(error);
400
+ logger.error('Save error:', message);
401
+ statusEl.textContent = `Error: ${message}`;
402
+ statusEl.className = 'save-form-status error';
403
+ }
404
+ finally {
405
+ submitBtn.disabled = false;
406
+ }
361
407
  }
362
- }
363
408
  }