@agentmemory/agentmemory 0.9.1 → 0.9.3
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.
- package/README.md +40 -13
- package/dist/cli.mjs +147 -26
- package/dist/cli.mjs.map +1 -1
- package/dist/hooks/notification.mjs +6 -0
- package/dist/hooks/notification.mjs.map +1 -1
- package/dist/hooks/post-tool-failure.mjs +6 -0
- package/dist/hooks/post-tool-failure.mjs.map +1 -1
- package/dist/hooks/post-tool-use.mjs +35 -1
- package/dist/hooks/post-tool-use.mjs.map +1 -1
- package/dist/hooks/pre-compact.mjs +6 -0
- package/dist/hooks/pre-compact.mjs.map +1 -1
- package/dist/hooks/pre-tool-use.mjs +6 -0
- package/dist/hooks/pre-tool-use.mjs.map +1 -1
- package/dist/hooks/prompt-submit.mjs +6 -0
- package/dist/hooks/prompt-submit.mjs.map +1 -1
- package/dist/hooks/session-end.mjs +6 -0
- package/dist/hooks/session-end.mjs.map +1 -1
- package/dist/hooks/session-start.mjs +6 -0
- package/dist/hooks/session-start.mjs.map +1 -1
- package/dist/hooks/stop.mjs +6 -0
- package/dist/hooks/stop.mjs.map +1 -1
- package/dist/hooks/subagent-start.mjs +6 -0
- package/dist/hooks/subagent-start.mjs.map +1 -1
- package/dist/hooks/subagent-stop.mjs +6 -0
- package/dist/hooks/subagent-stop.mjs.map +1 -1
- package/dist/hooks/task-completed.mjs +6 -0
- package/dist/hooks/task-completed.mjs.map +1 -1
- package/dist/image-refs-CESf9ndJ.mjs +3 -0
- package/dist/image-store-DGvZMMrI.mjs +3 -0
- package/dist/index.mjs +2100 -157
- package/dist/index.mjs.map +1 -1
- package/dist/{src-Dw_gJcCy.mjs → src-3Snd7D3T.mjs} +2021 -267
- package/dist/src-3Snd7D3T.mjs.map +1 -0
- package/dist/{standalone-BEWvWM5P.mjs → standalone-BG9uPsDK.mjs} +2 -2
- package/dist/{standalone-BEWvWM5P.mjs.map → standalone-BG9uPsDK.mjs.map} +1 -1
- package/dist/standalone.mjs +136 -2
- package/dist/standalone.mjs.map +1 -1
- package/dist/{tools-registry-BvWNlj6u.mjs → tools-registry-m8Ofn9vV.mjs} +166 -12
- package/dist/tools-registry-m8Ofn9vV.mjs.map +1 -0
- package/dist/viewer/index.html +528 -68
- package/package.json +5 -3
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/plugin/scripts/notification.mjs +6 -0
- package/plugin/scripts/notification.mjs.map +1 -1
- package/plugin/scripts/post-tool-failure.mjs +6 -0
- package/plugin/scripts/post-tool-failure.mjs.map +1 -1
- package/plugin/scripts/post-tool-use.mjs +35 -1
- package/plugin/scripts/post-tool-use.mjs.map +1 -1
- package/plugin/scripts/pre-compact.mjs +6 -0
- package/plugin/scripts/pre-compact.mjs.map +1 -1
- package/plugin/scripts/pre-tool-use.mjs +6 -0
- package/plugin/scripts/pre-tool-use.mjs.map +1 -1
- package/plugin/scripts/prompt-submit.mjs +6 -0
- package/plugin/scripts/prompt-submit.mjs.map +1 -1
- package/plugin/scripts/session-end.mjs +6 -0
- package/plugin/scripts/session-end.mjs.map +1 -1
- package/plugin/scripts/session-start.mjs +6 -0
- package/plugin/scripts/session-start.mjs.map +1 -1
- package/plugin/scripts/stop.mjs +6 -0
- package/plugin/scripts/stop.mjs.map +1 -1
- package/plugin/scripts/subagent-start.mjs +6 -0
- package/plugin/scripts/subagent-start.mjs.map +1 -1
- package/plugin/scripts/subagent-stop.mjs +6 -0
- package/plugin/scripts/subagent-stop.mjs.map +1 -1
- package/plugin/scripts/task-completed.mjs +6 -0
- package/plugin/scripts/task-completed.mjs.map +1 -1
- package/dist/src-Dw_gJcCy.mjs.map +0 -1
- package/dist/tools-registry-BvWNlj6u.mjs.map +0 -1
- package/dist/transformers-BX_tgxdO.mjs +0 -38684
- package/dist/transformers-BX_tgxdO.mjs.map +0 -1
- package/dist/transformers-KMm1i9no.mjs +0 -38683
- package/dist/transformers-KMm1i9no.mjs.map +0 -1
package/dist/viewer/index.html
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
:root {
|
|
10
10
|
--bg: #F9F9F7;
|
|
11
11
|
--bg-alt: #F0F0EC;
|
|
12
|
+
--bg-subtle: #F4F4F0;
|
|
12
13
|
--bg-inset: #E8E8E3;
|
|
13
14
|
--border: #111111;
|
|
14
15
|
--border-light: #D4D4CF;
|
|
@@ -43,6 +44,7 @@
|
|
|
43
44
|
html[data-theme="dark"] {
|
|
44
45
|
--bg: #1a1a1e;
|
|
45
46
|
--bg-alt: #232328;
|
|
47
|
+
--bg-subtle: #1f1f24;
|
|
46
48
|
--bg-inset: #2a2a30;
|
|
47
49
|
--border: #444;
|
|
48
50
|
--border-light: #3a3a42;
|
|
@@ -77,6 +79,8 @@
|
|
|
77
79
|
line-height: 1.6;
|
|
78
80
|
overflow: hidden;
|
|
79
81
|
height: 100vh;
|
|
82
|
+
display: flex;
|
|
83
|
+
flex-direction: column;
|
|
80
84
|
background-image: radial-gradient(circle, #D4D4CF 0.5px, transparent 0.5px);
|
|
81
85
|
background-size: 16px 16px;
|
|
82
86
|
}
|
|
@@ -168,7 +172,7 @@
|
|
|
168
172
|
border-bottom-color: var(--accent);
|
|
169
173
|
}
|
|
170
174
|
|
|
171
|
-
.view { display: none;
|
|
175
|
+
.view { display: none; flex: 1 1 auto; min-height: 0; overflow-y: auto; padding: 24px; }
|
|
172
176
|
.view.active { display: block; }
|
|
173
177
|
|
|
174
178
|
.stats-grid {
|
|
@@ -576,6 +580,81 @@
|
|
|
576
580
|
}
|
|
577
581
|
.empty-state .empty-icon { font-size: 36px; margin-bottom: 10px; opacity: 0.4; }
|
|
578
582
|
.empty-state p { font-size: 14px; font-family: var(--font-body); font-style: italic; }
|
|
583
|
+
.empty-state .empty-title { font-size: 16px; font-weight: 600; font-style: normal; color: var(--ink-muted); margin-bottom: 8px; }
|
|
584
|
+
.empty-state .empty-lead { font-style: normal; font-size: 14px; color: var(--ink-muted); max-width: 520px; margin: 0 auto 14px; line-height: 1.5; }
|
|
585
|
+
.empty-state pre.empty-cmd {
|
|
586
|
+
display: inline-block; margin: 10px auto 12px; padding: 10px 14px;
|
|
587
|
+
background: var(--bg-alt); border: 1px solid var(--border);
|
|
588
|
+
border-radius: 4px; font-family: var(--font-mono); font-size: 12px;
|
|
589
|
+
color: var(--ink); text-align: left; font-style: normal; white-space: pre;
|
|
590
|
+
}
|
|
591
|
+
.empty-state .empty-link { color: var(--accent); text-decoration: underline; font-size: 13px; font-style: normal; }
|
|
592
|
+
|
|
593
|
+
/* Feature flag banner system — compact collapsed by default */
|
|
594
|
+
.flag-banners { padding: 0 0 10px 0; }
|
|
595
|
+
button.flag-summary {
|
|
596
|
+
display: flex; align-items: center; gap: 12px;
|
|
597
|
+
padding: 8px 14px; border-radius: 4px;
|
|
598
|
+
border: 1px solid var(--border);
|
|
599
|
+
background: var(--bg-subtle);
|
|
600
|
+
font-family: var(--font-ui); font-size: 12px;
|
|
601
|
+
color: var(--ink-muted);
|
|
602
|
+
cursor: pointer; user-select: none;
|
|
603
|
+
width: 100%; text-align: left;
|
|
604
|
+
appearance: none;
|
|
605
|
+
}
|
|
606
|
+
button.flag-summary:hover,
|
|
607
|
+
button.flag-summary:focus-visible { background: var(--bg-alt); outline: 2px solid var(--border); outline-offset: 1px; }
|
|
608
|
+
.flag-summary .flag-count { color: var(--ink); font-weight: 600; }
|
|
609
|
+
.flag-summary .flag-pill {
|
|
610
|
+
display: inline-block; padding: 1px 8px; border-radius: 10px;
|
|
611
|
+
background: #f59e0b20; color: #d97706; font-size: 11px; font-weight: 600;
|
|
612
|
+
margin-right: 6px;
|
|
613
|
+
}
|
|
614
|
+
.flag-summary .flag-pill.info { background: var(--border-light); color: var(--ink-muted); }
|
|
615
|
+
.flag-summary .flag-toggle { margin-left: auto; font-size: 11px; opacity: 0.7; }
|
|
616
|
+
.flag-list {
|
|
617
|
+
display: none; flex-direction: column; gap: 6px;
|
|
618
|
+
margin-top: 6px;
|
|
619
|
+
}
|
|
620
|
+
.flag-list.open { display: flex; }
|
|
621
|
+
.flag-banner {
|
|
622
|
+
display: flex; align-items: flex-start; gap: 10px;
|
|
623
|
+
padding: 10px 14px; border-radius: 3px;
|
|
624
|
+
border: 1px solid var(--border);
|
|
625
|
+
background: var(--bg-subtle);
|
|
626
|
+
font-family: var(--font-ui); font-size: 12px;
|
|
627
|
+
}
|
|
628
|
+
.flag-banner.warn { border-left: 3px solid #f59e0b; }
|
|
629
|
+
.flag-banner.info { border-left: 3px solid var(--ink-muted); }
|
|
630
|
+
.flag-banner .flag-icon { flex-shrink: 0; font-size: 14px; line-height: 1.3; }
|
|
631
|
+
.flag-banner .flag-body { flex: 1; min-width: 0; }
|
|
632
|
+
.flag-banner .flag-title { font-weight: 600; color: var(--ink); margin-bottom: 2px; font-size: 12px; }
|
|
633
|
+
.flag-banner .flag-title code { font-family: var(--font-mono); font-size: 10px; color: var(--ink-muted); font-weight: 400; margin-left: 4px; }
|
|
634
|
+
.flag-banner .flag-desc { color: var(--ink-muted); margin-bottom: 4px; line-height: 1.4; font-size: 12px; }
|
|
635
|
+
.flag-banner .flag-enable {
|
|
636
|
+
display: block; margin-top: 2px; padding: 5px 8px;
|
|
637
|
+
background: var(--bg); border: 1px solid var(--border); border-radius: 3px;
|
|
638
|
+
font-family: var(--font-mono); font-size: 10px; color: var(--ink);
|
|
639
|
+
white-space: pre-wrap; word-break: break-all;
|
|
640
|
+
}
|
|
641
|
+
.flag-banner .flag-close {
|
|
642
|
+
background: none; border: none; color: var(--ink-faint); cursor: pointer;
|
|
643
|
+
font-size: 16px; line-height: 1; padding: 0 4px; font-family: inherit;
|
|
644
|
+
}
|
|
645
|
+
.flag-banner .flag-close:hover { color: var(--ink); }
|
|
646
|
+
|
|
647
|
+
/* Viewer footer */
|
|
648
|
+
.viewer-footer {
|
|
649
|
+
margin-top: 48px; padding: 16px 0 24px;
|
|
650
|
+
border-top: 1px solid var(--border-light);
|
|
651
|
+
display: flex; align-items: center; gap: 10px;
|
|
652
|
+
font-family: var(--font-ui); font-size: 11px;
|
|
653
|
+
color: var(--ink-faint); letter-spacing: 0.05em;
|
|
654
|
+
}
|
|
655
|
+
.viewer-footer a { color: var(--ink-muted); text-decoration: none; }
|
|
656
|
+
.viewer-footer a:hover { color: var(--ink); text-decoration: underline; }
|
|
657
|
+
.viewer-footer .footer-sep { color: var(--ink-faint); opacity: 0.5; }
|
|
579
658
|
|
|
580
659
|
.loading { color: var(--ink-faint); padding: 20px; text-align: center; font-style: italic; font-family: var(--font-body); }
|
|
581
660
|
.empty { color: var(--ink-muted); padding: 24px; text-align: center; font-family: var(--font-body); font-style: italic; border: 1px dashed var(--border); }
|
|
@@ -814,6 +893,8 @@
|
|
|
814
893
|
<button data-tab="replay">Replay</button>
|
|
815
894
|
</div>
|
|
816
895
|
|
|
896
|
+
<div id="flag-banners" class="flag-banners"></div>
|
|
897
|
+
|
|
817
898
|
<div id="view-dashboard" class="view active"></div>
|
|
818
899
|
<div id="view-graph" class="view"></div>
|
|
819
900
|
<div id="view-memories" class="view"></div>
|
|
@@ -831,6 +912,16 @@
|
|
|
831
912
|
<div class="modal" id="modal"></div>
|
|
832
913
|
</div>
|
|
833
914
|
|
|
915
|
+
<footer id="viewer-footer" class="viewer-footer">
|
|
916
|
+
<span>agentmemory viewer · <span id="footer-version">loading...</span></span>
|
|
917
|
+
<span class="footer-sep">·</span>
|
|
918
|
+
<a href="https://github.com/rohitg00/agentmemory" target="_blank" rel="noopener">github</a>
|
|
919
|
+
<span class="footer-sep">·</span>
|
|
920
|
+
<a href="https://github.com/rohitg00/agentmemory#readme" target="_blank" rel="noopener">docs</a>
|
|
921
|
+
<span class="footer-sep">·</span>
|
|
922
|
+
<a id="footer-feedback" href="#" target="_blank" rel="noopener">report issue →</a>
|
|
923
|
+
</footer>
|
|
924
|
+
|
|
834
925
|
<script nonce="__AGENTMEMORY_VIEWER_NONCE__">
|
|
835
926
|
var params = new URLSearchParams(window.location.search);
|
|
836
927
|
var viewerPort = params.get('port') || window.location.port || '3113';
|
|
@@ -900,9 +991,10 @@
|
|
|
900
991
|
activity: { loaded: false, observations: [], sessions: [], typeFilter: '' },
|
|
901
992
|
lessons: { loaded: false, items: [], search: '' },
|
|
902
993
|
actions: { loaded: false, items: [], frontier: [], statusFilter: '', search: '' },
|
|
903
|
-
crystals: { loaded: false, items: [], search: '' },
|
|
994
|
+
crystals: { loaded: false, items: [], search: '', lessonMap: {} },
|
|
904
995
|
profile: { loaded: false, projects: [], selectedProject: '', data: null },
|
|
905
996
|
replay: { loaded: false, sessions: [], selectedId: '', timeline: null, cursor: 0, playing: false, speed: 1, timer: null, startAt: 0, offsetAt: 0 },
|
|
997
|
+
flagsConfig: null,
|
|
906
998
|
ws: null
|
|
907
999
|
};
|
|
908
1000
|
|
|
@@ -1034,13 +1126,24 @@
|
|
|
1034
1126
|
var dotClass = healthStatus === 'healthy' ? 'healthy' : healthStatus === 'degraded' ? 'degraded' : healthStatus === 'critical' ? 'critical' : '';
|
|
1035
1127
|
var activeSessions = d.sessions.filter(function(s) { return s.status === 'active'; }).length;
|
|
1036
1128
|
var gs = d.graphStats || {};
|
|
1037
|
-
var nodeCount = (gs.nodes !== undefined
|
|
1038
|
-
var edgeCount = (gs.edges !== undefined
|
|
1129
|
+
var nodeCount = gs.totalNodes !== undefined ? gs.totalNodes : (gs.nodes !== undefined ? gs.nodes : (gs.nodeCount || 0));
|
|
1130
|
+
var edgeCount = gs.totalEdges !== undefined ? gs.totalEdges : (gs.edges !== undefined ? gs.edges : (gs.edgeCount || 0));
|
|
1039
1131
|
var fMetrics = h.functionMetrics || [];
|
|
1040
1132
|
var cb = h.circuitBreaker || null;
|
|
1041
1133
|
var workers = snap.workers || [];
|
|
1042
1134
|
|
|
1043
|
-
var html = '
|
|
1135
|
+
var html = '';
|
|
1136
|
+
// First-run hero: empty dashboard = guided next step
|
|
1137
|
+
if (d.sessions.length === 0) {
|
|
1138
|
+
html += '<div class="card" style="margin-bottom:14px;padding:24px 28px;background:var(--bg-subtle);border-left:3px solid var(--accent);">' +
|
|
1139
|
+
'<div style="font-family:var(--font-ui);font-size:11px;letter-spacing:0.15em;text-transform:uppercase;color:var(--accent);font-weight:700;margin-bottom:8px;">First run → magical moment in 10 seconds</div>' +
|
|
1140
|
+
'<div style="font-family:var(--font-display,Lora,Georgia,serif);font-size:22px;font-weight:700;color:var(--ink);margin-bottom:8px;">Seed sample data + prove semantic recall works</div>' +
|
|
1141
|
+
'<div style="font-size:13px;color:var(--ink-muted);margin-bottom:12px;line-height:1.5;max-width:640px;">agentmemory is running but hasn’t seen any sessions yet. Run the demo command in a second terminal: it seeds 3 realistic coding sessions and proves the hybrid search finds semantically-related memories that keyword search would miss.</div>' +
|
|
1142
|
+
'<pre style="display:inline-block;margin:0;padding:10px 14px;background:var(--bg);border:1px solid var(--border);border-radius:4px;font-family:var(--font-mono);font-size:12px;color:var(--ink);">npx @agentmemory/agentmemory demo</pre>' +
|
|
1143
|
+
'<div style="margin-top:10px;"><a class="empty-link" href="https://github.com/rohitg00/agentmemory#quick-start" target="_blank" rel="noopener" style="font-size:12px;">Or: wire up your real agent →</a></div>' +
|
|
1144
|
+
'</div>';
|
|
1145
|
+
}
|
|
1146
|
+
html += '<div class="stats-grid">';
|
|
1044
1147
|
html += '<div class="stat-card"><div class="label">Sessions</div><div class="value">' + d.sessions.length + '</div><div class="sub">' + activeSessions + ' active</div></div>';
|
|
1045
1148
|
html += '<div class="stat-card"><div class="label">Memories</div><div class="value">' + d.memories.length + '</div><div class="sub">latest versions</div></div>';
|
|
1046
1149
|
var lessonCount = (d.lessons || []).length;
|
|
@@ -1078,7 +1181,8 @@
|
|
|
1078
1181
|
var heapTotal = Math.round((snap.memory.heapTotal || 0) / 1024 / 1024);
|
|
1079
1182
|
var rss = Math.round((snap.memory.rss || 0) / 1024 / 1024);
|
|
1080
1183
|
var heapPct = heapTotal > 0 ? Math.round((heapUsed / heapTotal) * 100) : 0;
|
|
1081
|
-
var
|
|
1184
|
+
var rssAboveFloor = rss >= 512;
|
|
1185
|
+
var heapColor = (heapPct > 80 && rssAboveFloor) ? 'var(--red)' : (heapPct > 60 && rssAboveFloor) ? 'var(--yellow)' : 'var(--green)';
|
|
1082
1186
|
html += '<div class="gauge"><span class="gauge-label">Heap</span><div class="gauge-bar"><div class="gauge-fill" style="width:' + heapPct + '%;background:' + heapColor + '"></div></div><span class="gauge-value">' + heapUsed + ' / ' + heapTotal + ' MB</span></div>';
|
|
1083
1187
|
html += '<div class="gauge"><span class="gauge-label">RSS</span><div class="gauge-bar"><div class="gauge-fill" style="width:' + Math.min(100, Math.round(rss / 512 * 100)) + '%;background:var(--blue)"></div></div><span class="gauge-value">' + rss + ' MB</span></div>';
|
|
1084
1188
|
if (snap.memory.external) {
|
|
@@ -1113,6 +1217,14 @@
|
|
|
1113
1217
|
html += '</div>';
|
|
1114
1218
|
}
|
|
1115
1219
|
|
|
1220
|
+
if (snap.notes && snap.notes.length > 0) {
|
|
1221
|
+
html += '<div class="card" style="margin-bottom:16px;"><div class="card-title" style="color:var(--ink-muted);">Notes (' + snap.notes.length + ')</div>';
|
|
1222
|
+
snap.notes.forEach(function(n) {
|
|
1223
|
+
html += '<div style="font-size:12px;color:var(--ink-muted);padding:4px 0;border-bottom:1px solid var(--border-light);font-family:var(--font-ui);">' + esc(n) + '</div>';
|
|
1224
|
+
});
|
|
1225
|
+
html += '</div>';
|
|
1226
|
+
}
|
|
1227
|
+
|
|
1116
1228
|
html += '<div class="two-col">';
|
|
1117
1229
|
|
|
1118
1230
|
html += '<div class="card"><div class="card-title">Recent Sessions</div>';
|
|
@@ -1272,6 +1384,7 @@
|
|
|
1272
1384
|
function startDashboardAutoRefresh() {
|
|
1273
1385
|
if (dashboardTimer) clearInterval(dashboardTimer);
|
|
1274
1386
|
dashboardTimer = setInterval(function() {
|
|
1387
|
+
if (pollTimer) return;
|
|
1275
1388
|
if (state.activeTab === 'dashboard') refreshDashboard();
|
|
1276
1389
|
}, 30000);
|
|
1277
1390
|
}
|
|
@@ -1326,8 +1439,8 @@
|
|
|
1326
1439
|
var sb = document.getElementById('graph-sidebar');
|
|
1327
1440
|
if (!sb) return;
|
|
1328
1441
|
var gs = state.graph.stats || {};
|
|
1329
|
-
var nodeCount = gs.nodes !== undefined ? gs.nodes : (gs.nodeCount || state.graph.nodes.length);
|
|
1330
|
-
var edgeCount = gs.edges !== undefined ? gs.edges : (gs.edgeCount || state.graph.edges.length);
|
|
1442
|
+
var nodeCount = gs.totalNodes !== undefined ? gs.totalNodes : (gs.nodes !== undefined ? gs.nodes : (gs.nodeCount || state.graph.nodes.length));
|
|
1443
|
+
var edgeCount = gs.totalEdges !== undefined ? gs.totalEdges : (gs.edges !== undefined ? gs.edges : (gs.edgeCount || state.graph.edges.length));
|
|
1331
1444
|
|
|
1332
1445
|
var html = '<input type="text" class="graph-search" id="graph-search" placeholder="Search nodes...">';
|
|
1333
1446
|
|
|
@@ -1936,7 +2049,13 @@
|
|
|
1936
2049
|
items.forEach(function(m) { types[m.type] = true; });
|
|
1937
2050
|
var typeOptions = Object.keys(types).sort();
|
|
1938
2051
|
|
|
1939
|
-
var html = '<div class="
|
|
2052
|
+
var html = '<div class="card" style="margin-bottom:12px;padding:12px;background:var(--bg-subtle);">';
|
|
2053
|
+
html += '<div style="font-size:13px;color:var(--ink-muted);line-height:1.5;">';
|
|
2054
|
+
html += '<strong>Memories</strong> are durable facts, architecture notes, conventions, and lessons saved via <code>memory_remember</code> MCP tool or the <code>/agentmemory/remember</code> endpoint. They survive across sessions and supersede each other as v1, v2, etc. ';
|
|
2055
|
+
html += '<span style="color:var(--ink-faint);">Shown: ' + items.length + ' total.</span>';
|
|
2056
|
+
html += '</div></div>';
|
|
2057
|
+
|
|
2058
|
+
html += '<div class="toolbar">';
|
|
1940
2059
|
html += '<input type="text" id="mem-search" placeholder="Search memories..." value="' + esc(state.memories.search) + '">';
|
|
1941
2060
|
html += '<select id="mem-type-filter"><option value="">All types</option>';
|
|
1942
2061
|
typeOptions.forEach(function(t) {
|
|
@@ -1945,12 +2064,20 @@
|
|
|
1945
2064
|
html += '</select></div>';
|
|
1946
2065
|
|
|
1947
2066
|
if (filtered.length === 0) {
|
|
1948
|
-
html += '<div class="empty-state"
|
|
2067
|
+
html += '<div class="empty-state">' +
|
|
2068
|
+
'<div class="empty-icon">📚</div>' +
|
|
2069
|
+
'<div class="empty-title">No memories yet</div>' +
|
|
2070
|
+
'<div class="empty-lead">Memories are the distilled facts agentmemory keeps across sessions — things like file paths, architectural decisions, and user preferences. Hooks capture them automatically during coding sessions; you can also save one directly.</div>' +
|
|
2071
|
+
'<pre class="empty-cmd">memory_remember {\n title: "auth uses jose middleware",\n content: "src/middleware/auth.ts handles JWT validation",\n type: "architecture"\n}</pre>' +
|
|
2072
|
+
'<div><a class="empty-link" href="https://github.com/rohitg00/agentmemory#memories" target="_blank" rel="noopener">Memory types →</a></div>' +
|
|
2073
|
+
'</div>';
|
|
1949
2074
|
} else {
|
|
1950
2075
|
html += '<table><tr><th>Title</th><th>Type</th><th>Strength</th><th>Version</th><th>Updated</th><th>Actions</th></tr>';
|
|
1951
2076
|
filtered.forEach(function(m) {
|
|
1952
2077
|
var badgeClass = TYPE_BADGES[m.type] || 'badge-muted';
|
|
1953
|
-
var
|
|
2078
|
+
var rawStrength = m.strength || 0;
|
|
2079
|
+
var strength = Math.round(rawStrength <= 1 ? rawStrength * 100 : rawStrength * 10);
|
|
2080
|
+
if (strength > 100) strength = 100;
|
|
1954
2081
|
var barColor = strength > 70 ? 'var(--green)' : strength > 40 ? 'var(--yellow)' : 'var(--red)';
|
|
1955
2082
|
html += '<tr>';
|
|
1956
2083
|
var preview = (m.content || '').split('\n').slice(0, 2).join(' ').trim();
|
|
@@ -2399,6 +2526,10 @@
|
|
|
2399
2526
|
html += '<div class="session-item' + (selected ? ' selected' : '') + '" data-action="select-session" data-session-id="' + esc(s.id) + '">';
|
|
2400
2527
|
html += '<div class="session-top"><span class="session-project">' + esc(s.project ? s.project.split('/').pop() : 'Unknown') + '</span>';
|
|
2401
2528
|
html += '<span class="badge ' + statusBadge + '">' + esc(s.status) + '</span></div>';
|
|
2529
|
+
var preview = s.firstPrompt || s.summary || '';
|
|
2530
|
+
if (preview) {
|
|
2531
|
+
html += '<div class="session-preview" style="font-size:13px;color:var(--ink);margin:4px 0;line-height:1.4;">' + esc(truncate(preview, 140)) + '</div>';
|
|
2532
|
+
}
|
|
2402
2533
|
html += '<div class="session-meta">' + esc(s.id.slice(0, 12)) + ' · ' + esc(formatTime(s.startedAt));
|
|
2403
2534
|
html += ' · ' + (s.observationCount || 0) + ' obs';
|
|
2404
2535
|
if (s.model) html += ' · ' + esc(s.model);
|
|
@@ -2417,24 +2548,96 @@
|
|
|
2417
2548
|
renderSessions();
|
|
2418
2549
|
}
|
|
2419
2550
|
|
|
2420
|
-
function renderSessionDetail() {
|
|
2551
|
+
async function renderSessionDetail() {
|
|
2421
2552
|
var panel = document.getElementById('session-detail');
|
|
2422
2553
|
if (!panel) return;
|
|
2423
2554
|
var s = state.sessions.items.find(function(x) { return x.id === state.sessions.selectedId; });
|
|
2424
2555
|
if (!s) { panel.innerHTML = ''; return; }
|
|
2425
2556
|
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2557
|
+
panel.innerHTML = '<div class="detail-panel"><h3>Loading session details…</h3></div>';
|
|
2558
|
+
|
|
2559
|
+
var obsRes = await apiGet('observations?sessionId=' + encodeURIComponent(s.id));
|
|
2560
|
+
var obs = (obsRes && obsRes.observations) || [];
|
|
2561
|
+
|
|
2562
|
+
var typeCounts = {};
|
|
2563
|
+
var toolCounts = {};
|
|
2564
|
+
var fileSet = new Set();
|
|
2565
|
+
var firstPromptFromObs = '';
|
|
2566
|
+
obs.forEach(function(o) {
|
|
2567
|
+
var t = o.type || o.hookType || 'other';
|
|
2568
|
+
typeCounts[t] = (typeCounts[t] || 0) + 1;
|
|
2569
|
+
var tool = o.title || o.toolName;
|
|
2570
|
+
if (tool && t !== 'conversation') toolCounts[tool] = (toolCounts[tool] || 0) + 1;
|
|
2571
|
+
(o.files || []).forEach(function(f) { fileSet.add(f); });
|
|
2572
|
+
if (!firstPromptFromObs && (o.userPrompt || (o.type === 'conversation' && o.narrative))) {
|
|
2573
|
+
firstPromptFromObs = o.userPrompt || o.narrative || '';
|
|
2574
|
+
}
|
|
2575
|
+
});
|
|
2576
|
+
|
|
2577
|
+
var durationMs = s.endedAt ? new Date(s.endedAt).getTime() - new Date(s.startedAt).getTime() : 0;
|
|
2578
|
+
var durationLabel = durationMs > 0 ? (durationMs < 60000 ? (durationMs / 1000).toFixed(1) + 's' : (durationMs / 60000).toFixed(1) + 'm') : '-';
|
|
2579
|
+
|
|
2580
|
+
var preview = s.firstPrompt || s.summary || firstPromptFromObs || '';
|
|
2581
|
+
|
|
2582
|
+
var html = '<div class="detail-panel">';
|
|
2583
|
+
html += '<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:12px;">';
|
|
2584
|
+
html += '<h3 style="margin:0;">Session · ' + esc(s.project || 'Unknown') + '</h3>';
|
|
2585
|
+
html += '<span class="badge ' + (s.status === 'active' ? 'badge-green' : 'badge-blue') + '">' + esc(s.status) + '</span>';
|
|
2586
|
+
html += '</div>';
|
|
2587
|
+
|
|
2588
|
+
if (preview) {
|
|
2589
|
+
html += '<div style="padding:10px 12px;margin-bottom:12px;background:var(--bg-alt);border-left:3px solid var(--accent);font-size:13px;line-height:1.5;color:var(--ink);">' + esc(truncate(preview, 600)) + '</div>';
|
|
2590
|
+
}
|
|
2591
|
+
|
|
2592
|
+
html += '<div style="display:grid;grid-template-columns:repeat(4, 1fr);gap:10px;margin-bottom:14px;">';
|
|
2593
|
+
html += '<div class="card" style="padding:10px;"><div style="font-size:10px;letter-spacing:0.08em;color:var(--ink-muted);">OBSERVATIONS</div><div style="font-size:20px;font-weight:600;">' + obs.length + '</div></div>';
|
|
2594
|
+
html += '<div class="card" style="padding:10px;"><div style="font-size:10px;letter-spacing:0.08em;color:var(--ink-muted);">TOOLS USED</div><div style="font-size:20px;font-weight:600;">' + Object.keys(toolCounts).length + '</div></div>';
|
|
2595
|
+
html += '<div class="card" style="padding:10px;"><div style="font-size:10px;letter-spacing:0.08em;color:var(--ink-muted);">FILES TOUCHED</div><div style="font-size:20px;font-weight:600;">' + fileSet.size + '</div></div>';
|
|
2596
|
+
html += '<div class="card" style="padding:10px;"><div style="font-size:10px;letter-spacing:0.08em;color:var(--ink-muted);">DURATION</div><div style="font-size:20px;font-weight:600;">' + esc(durationLabel) + '</div></div>';
|
|
2597
|
+
html += '</div>';
|
|
2598
|
+
|
|
2599
|
+
var topTools = Object.keys(toolCounts).sort(function(a, b) { return toolCounts[b] - toolCounts[a]; }).slice(0, 10);
|
|
2600
|
+
if (topTools.length > 0) {
|
|
2601
|
+
var maxC = toolCounts[topTools[0]] || 1;
|
|
2602
|
+
html += '<div class="card" style="margin-bottom:12px;"><div class="card-title">Tool Invocations</div>';
|
|
2603
|
+
html += '<div class="bar-chart" style="margin-top:8px;">';
|
|
2604
|
+
topTools.forEach(function(t) {
|
|
2605
|
+
var pct = Math.round((toolCounts[t] / maxC) * 100);
|
|
2606
|
+
html += '<div class="bar-row"><span class="bar-label" style="font-family:var(--font-mono);">' + esc(t) + '</span><div class="bar-track"><div class="bar-fill" style="width:' + pct + '%;background:var(--accent);"></div></div><span class="bar-value">' + toolCounts[t] + '</span></div>';
|
|
2607
|
+
});
|
|
2608
|
+
html += '</div></div>';
|
|
2609
|
+
}
|
|
2610
|
+
|
|
2611
|
+
var typeKeys = Object.keys(typeCounts).sort(function(a, b) { return typeCounts[b] - typeCounts[a]; });
|
|
2612
|
+
if (typeKeys.length > 0) {
|
|
2613
|
+
html += '<div class="card" style="margin-bottom:12px;"><div class="card-title">Activity Breakdown</div>';
|
|
2614
|
+
html += '<div style="display:flex;gap:6px;flex-wrap:wrap;margin-top:8px;">';
|
|
2615
|
+
typeKeys.forEach(function(t) {
|
|
2616
|
+
html += '<span class="badge badge-muted" style="font-family:var(--font-mono);">' + esc(t.replace(/_/g, ' ')) + ' · ' + typeCounts[t] + '</span>';
|
|
2617
|
+
});
|
|
2618
|
+
html += '</div></div>';
|
|
2619
|
+
}
|
|
2620
|
+
|
|
2621
|
+
if (fileSet.size > 0) {
|
|
2622
|
+
var filesArr = Array.from(fileSet).slice(0, 30);
|
|
2623
|
+
html += '<div class="card" style="margin-bottom:12px;"><div class="card-title">Files</div>';
|
|
2624
|
+
html += '<div style="font-size:12px;font-family:var(--font-mono);line-height:1.6;margin-top:8px;">';
|
|
2625
|
+
filesArr.forEach(function(f) { html += '<div>• ' + esc(f) + '</div>'; });
|
|
2626
|
+
if (fileSet.size > 30) html += '<div style="color:var(--ink-faint);">+' + (fileSet.size - 30) + ' more</div>';
|
|
2627
|
+
html += '</div></div>';
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
html += '<div class="card" style="margin-bottom:12px;"><div class="card-title">Metadata</div>';
|
|
2631
|
+
html += '<div style="font-size:12px;font-family:var(--font-mono);margin-top:8px;line-height:1.7;">';
|
|
2632
|
+
html += '<div><span style="color:var(--ink-muted);">id:</span> ' + esc(s.id) + '</div>';
|
|
2633
|
+
html += '<div><span style="color:var(--ink-muted);">cwd:</span> ' + esc(s.cwd || '-') + '</div>';
|
|
2634
|
+
html += '<div><span style="color:var(--ink-muted);">started:</span> ' + esc(formatTime(s.startedAt)) + '</div>';
|
|
2635
|
+
if (s.endedAt) html += '<div><span style="color:var(--ink-muted);">ended:</span> ' + esc(formatTime(s.endedAt)) + '</div>';
|
|
2636
|
+
if (s.model) html += '<div><span style="color:var(--ink-muted);">model:</span> ' + esc(s.model) + '</div>';
|
|
2637
|
+
if (s.tags && s.tags.length) html += '<div><span style="color:var(--ink-muted);">tags:</span> ' + s.tags.map(esc).join(', ') + '</div>';
|
|
2638
|
+
html += '</div></div>';
|
|
2639
|
+
|
|
2640
|
+
html += '<div style="display:flex;gap:8px;">';
|
|
2438
2641
|
if (s.status === 'active') {
|
|
2439
2642
|
html += '<button class="btn btn-danger" data-action="end-session" data-session-id="' + esc(s.id) + '">End Session</button>';
|
|
2440
2643
|
}
|
|
@@ -2478,13 +2681,24 @@
|
|
|
2478
2681
|
});
|
|
2479
2682
|
}
|
|
2480
2683
|
|
|
2481
|
-
var html = '<div style="
|
|
2684
|
+
var html = '<div class="card" style="margin-bottom:12px;padding:12px;background:var(--bg-subtle);">';
|
|
2685
|
+
html += '<div style="font-size:13px;color:var(--ink-muted);line-height:1.5;">';
|
|
2686
|
+
html += '<strong>Lessons</strong> are portable heuristics — short imperative rules (always/never/prefer/avoid) extracted from past work. Auto-surface from JSONL imports (low confidence, tag <code>auto-import</code>), get reinforced when the agent applies them, and decay if unused. Higher confidence = more battle-tested.';
|
|
2687
|
+
html += '</div></div>';
|
|
2688
|
+
|
|
2689
|
+
html += '<div style="display:flex;gap:8px;margin-bottom:12px;">';
|
|
2482
2690
|
html += '<input class="search-input" type="text" placeholder="Search lessons..." value="' + esc(state.lessons.search) + '" oninput="state.lessons.search=this.value;renderLessons()" style="flex:1" />';
|
|
2483
2691
|
html += '<span style="font-size:12px;color:var(--ink-faint);align-self:center;">' + items.length + ' lessons</span>';
|
|
2484
2692
|
html += '</div>';
|
|
2485
2693
|
|
|
2486
2694
|
if (items.length === 0) {
|
|
2487
|
-
html += '<div class="empty-state"
|
|
2695
|
+
html += '<div class="empty-state">' +
|
|
2696
|
+
'<div class="empty-icon">💡</div>' +
|
|
2697
|
+
'<div class="empty-title">No lessons yet</div>' +
|
|
2698
|
+
'<div class="empty-lead">Lessons are confidence-scored pattern observations — things you corrected once that the agent should never do again. They persist across projects.</div>' +
|
|
2699
|
+
'<pre class="empty-cmd"># Save a lesson explicitly\nmemory_lesson_save { rule, reason, confidence }\n\n# Or: Replay tab → Import JSONL auto-extracts lessons\n# from your past Claude Code sessions</pre>' +
|
|
2700
|
+
'<div><a class="empty-link" href="https://github.com/rohitg00/agentmemory#lessons" target="_blank" rel="noopener">Lesson decay & scoring →</a></div>' +
|
|
2701
|
+
'</div>';
|
|
2488
2702
|
} else {
|
|
2489
2703
|
html += '<table><thead><tr><th>Lesson</th><th>Confidence</th><th>Reinforcements</th><th>Source</th><th>Project</th><th>Updated</th></tr></thead><tbody>';
|
|
2490
2704
|
items.forEach(function(l) {
|
|
@@ -2510,7 +2724,7 @@
|
|
|
2510
2724
|
el.innerHTML = '<div class="loading">Loading actions...</div>';
|
|
2511
2725
|
var results = await Promise.all([apiGet('actions'), apiGet('frontier')]);
|
|
2512
2726
|
state.actions.items = (results[0] && results[0].actions) || [];
|
|
2513
|
-
state.actions.frontier = (results[1] && results[1].actions) || [];
|
|
2727
|
+
state.actions.frontier = (results[1] && (results[1].frontier || results[1].actions)) || [];
|
|
2514
2728
|
state.actions.loaded = true;
|
|
2515
2729
|
renderActions();
|
|
2516
2730
|
}
|
|
@@ -2543,7 +2757,14 @@
|
|
|
2543
2757
|
html += '</div>';
|
|
2544
2758
|
|
|
2545
2759
|
if (items.length === 0) {
|
|
2546
|
-
html += '<div class="empty-state"
|
|
2760
|
+
html += '<div class="empty-state">' +
|
|
2761
|
+
'<div class="empty-icon">☑</div>' +
|
|
2762
|
+
'<div class="empty-title">No actions tracked yet</div>' +
|
|
2763
|
+
'<div class="empty-lead">Actions are follow-ups the agent surfaced during a session: <em>decisions to revisit</em>, <em>files to inspect</em>, <em>tasks blocked on input</em>. They show up here with status pending → active → done/blocked so nothing slips through between sessions.</div>' +
|
|
2764
|
+
'<div class="empty-lead" style="margin-top:0;">Three ways to create them:</div>' +
|
|
2765
|
+
'<pre class="empty-cmd"># 1. MCP tool (from any agent)\nmemory_action_create { title, description, priority }\n\n# 2. Curl\ncurl -X POST http://localhost:3111/agentmemory/actions \\\n -H \'Content-Type: application/json\' \\\n -d \'{"title":"ship v1","priority":"high"}\'\n\n# 3. Hooks auto-extract from long session bodies</pre>' +
|
|
2766
|
+
'<div><a class="empty-link" href="https://github.com/rohitg00/agentmemory#actions" target="_blank" rel="noopener">Action lifecycle docs →</a></div>' +
|
|
2767
|
+
'</div>';
|
|
2547
2768
|
} else {
|
|
2548
2769
|
html += '<table><thead><tr><th>Title</th><th>Status</th><th>Priority</th><th>Tags</th><th>Frontier</th><th>Updated</th></tr></thead><tbody>';
|
|
2549
2770
|
items = items.slice().sort(function(a, b) { return (b.priority || 0) - (a.priority || 0); });
|
|
@@ -2569,15 +2790,13 @@
|
|
|
2569
2790
|
|
|
2570
2791
|
async function loadCrystals() {
|
|
2571
2792
|
var el = document.getElementById('view-crystals');
|
|
2572
|
-
if (state.dashboard.loaded && state.dashboard.crystals.length) {
|
|
2573
|
-
state.crystals.items = state.dashboard.crystals;
|
|
2574
|
-
state.crystals.loaded = true;
|
|
2575
|
-
renderCrystals();
|
|
2576
|
-
return;
|
|
2577
|
-
}
|
|
2578
2793
|
el.innerHTML = '<div class="loading">Loading crystals...</div>';
|
|
2579
|
-
var
|
|
2580
|
-
state.crystals.items = (
|
|
2794
|
+
var results = await Promise.all([apiGet('crystals'), apiGet('lessons')]);
|
|
2795
|
+
state.crystals.items = (results[0] && results[0].crystals) || [];
|
|
2796
|
+
var lessonMap = {};
|
|
2797
|
+
var lessons = (results[1] && results[1].lessons) || [];
|
|
2798
|
+
lessons.forEach(function(l) { if (l && l.id) lessonMap[l.id] = l; });
|
|
2799
|
+
state.crystals.lessonMap = lessonMap;
|
|
2581
2800
|
state.crystals.loaded = true;
|
|
2582
2801
|
renderCrystals();
|
|
2583
2802
|
}
|
|
@@ -2586,54 +2805,89 @@
|
|
|
2586
2805
|
var el = document.getElementById('view-crystals');
|
|
2587
2806
|
var items = state.crystals.items;
|
|
2588
2807
|
var search = state.crystals.search.toLowerCase();
|
|
2808
|
+
var lessonMap = state.crystals.lessonMap || {};
|
|
2589
2809
|
|
|
2590
2810
|
if (search) {
|
|
2591
2811
|
items = items.filter(function(c) {
|
|
2592
|
-
|
|
2812
|
+
var lessonText = (c.lessons || [])
|
|
2813
|
+
.map(function(lid) {
|
|
2814
|
+
var l = lessonMap[lid];
|
|
2815
|
+
return l && typeof l.content === 'string' ? l.content : lid;
|
|
2816
|
+
})
|
|
2817
|
+
.join(' ');
|
|
2818
|
+
var filesText = (c.filesAffected || []).join(' ');
|
|
2819
|
+
var haystack = [
|
|
2820
|
+
c.narrative || '',
|
|
2821
|
+
(c.keyOutcomes || []).join(' '),
|
|
2822
|
+
lessonText,
|
|
2823
|
+
filesText,
|
|
2824
|
+
c.project || '',
|
|
2825
|
+
].join(' ').toLowerCase();
|
|
2826
|
+
return haystack.indexOf(search) >= 0;
|
|
2593
2827
|
});
|
|
2594
2828
|
}
|
|
2595
2829
|
|
|
2596
|
-
var html = '<div style="
|
|
2830
|
+
var html = '<div class="card" style="margin-bottom:12px;padding:12px;background:var(--bg-subtle);">';
|
|
2831
|
+
html += '<div style="font-size:13px;color:var(--ink-muted);line-height:1.5;">';
|
|
2832
|
+
html += '<strong>Crystals</strong> are frozen snapshots of completed work. Each crystal captures one session\'s narrative, the tools invoked (key outcomes), files touched, and lessons surfaced — a replayable summary you keep after raw observations are pruned. Auto-created on JSONL import or via <code>memory_crystallize</code>.';
|
|
2833
|
+
html += '</div></div>';
|
|
2834
|
+
|
|
2835
|
+
html += '<div style="display:flex;gap:8px;margin-bottom:12px;">';
|
|
2597
2836
|
html += '<input class="search-input" type="text" placeholder="Search crystals..." value="' + esc(state.crystals.search) + '" oninput="state.crystals.search=this.value;renderCrystals()" style="flex:1" />';
|
|
2598
2837
|
html += '<span style="font-size:12px;color:var(--ink-faint);align-self:center;">' + items.length + ' crystals</span>';
|
|
2599
2838
|
html += '</div>';
|
|
2600
2839
|
|
|
2601
2840
|
if (items.length === 0) {
|
|
2602
|
-
html += '<div class="empty-state"
|
|
2841
|
+
html += '<div class="empty-state">' +
|
|
2842
|
+
'<div class="empty-icon">💎</div>' +
|
|
2843
|
+
'<div class="empty-title">No crystals yet</div>' +
|
|
2844
|
+
'<div class="empty-lead">Crystals are compressed action digests — the 3-line summary of what happened in a session. Generated from long conversations to give the next session fast context without re-reading everything.</div>' +
|
|
2845
|
+
'<pre class="empty-cmd"># Auto: import a JSONL transcript\n# Replay tab → Import JSONL\n\n# Manual: crystallize a specific session\nmemory_crystallize { sessionId }</pre>' +
|
|
2846
|
+
'<div><a class="empty-link" href="https://github.com/rohitg00/agentmemory#crystals" target="_blank" rel="noopener">Crystal pipeline →</a></div>' +
|
|
2847
|
+
'</div>';
|
|
2603
2848
|
} else {
|
|
2604
2849
|
items.forEach(function(c) {
|
|
2605
|
-
html += '<div class="card" style="margin-bottom:12px;">';
|
|
2606
|
-
html += '<div
|
|
2607
|
-
html += '<
|
|
2608
|
-
html += '<
|
|
2850
|
+
html += '<div class="card" style="margin-bottom:12px;border-left:3px solid var(--accent);">';
|
|
2851
|
+
html += '<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:8px;">';
|
|
2852
|
+
html += '<div style="flex:1;font-size:14px;font-weight:600;color:var(--ink);line-height:1.4;">' + esc(truncate(c.narrative || 'Untitled crystal', 300)) + '</div>';
|
|
2853
|
+
html += '<div style="font-size:10px;color:var(--ink-faint);font-family:var(--font-mono);white-space:nowrap;">' + esc(formatTime(c.createdAt)) + '</div>';
|
|
2609
2854
|
html += '</div>';
|
|
2610
2855
|
|
|
2856
|
+
var pillRow = [];
|
|
2857
|
+
if (c.project) pillRow.push('<span class="badge badge-muted">' + esc(c.project) + '</span>');
|
|
2858
|
+
if (c.sessionId) pillRow.push('<span class="badge badge-blue" style="font-family:var(--font-mono);">' + esc(c.sessionId.slice(0, 14)) + '</span>');
|
|
2859
|
+
if (c.keyOutcomes && c.keyOutcomes.length) pillRow.push('<span style="font-size:11px;color:var(--ink-muted);">' + c.keyOutcomes.length + ' tools</span>');
|
|
2860
|
+
if (c.filesAffected && c.filesAffected.length) pillRow.push('<span style="font-size:11px;color:var(--ink-muted);">' + c.filesAffected.length + ' files</span>');
|
|
2861
|
+
if (c.lessons && c.lessons.length) pillRow.push('<span style="font-size:11px;color:var(--ink-muted);">' + c.lessons.length + ' lessons</span>');
|
|
2862
|
+
if (pillRow.length) html += '<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px;">' + pillRow.join('') + '</div>';
|
|
2863
|
+
|
|
2611
2864
|
if (c.keyOutcomes && c.keyOutcomes.length > 0) {
|
|
2612
|
-
html += '<div style="margin:
|
|
2865
|
+
html += '<div style="margin:10px 0;"><div style="font-size:10px;letter-spacing:0.08em;color:var(--ink-muted);margin-bottom:4px;">TOOLS USED</div>';
|
|
2866
|
+
html += '<div style="display:flex;gap:4px;flex-wrap:wrap;">';
|
|
2613
2867
|
c.keyOutcomes.forEach(function(o) {
|
|
2614
|
-
html += '<
|
|
2868
|
+
html += '<span class="badge" style="background:var(--bg-alt);color:var(--ink);font-family:var(--font-mono);">' + esc(o) + '</span>';
|
|
2615
2869
|
});
|
|
2616
|
-
html += '</div>';
|
|
2617
|
-
}
|
|
2618
|
-
|
|
2619
|
-
if (c.lessons && c.lessons.length > 0) {
|
|
2620
|
-
html += '<div style="margin:8px 0;"><strong style="font-size:11px;color:var(--ink-muted);">LESSONS</strong>';
|
|
2621
|
-
c.lessons.forEach(function(l) {
|
|
2622
|
-
html += '<div style="font-size:12px;padding:2px 0;color:var(--ink);">💡 ' + esc(l) + '</div>';
|
|
2623
|
-
});
|
|
2624
|
-
html += '</div>';
|
|
2870
|
+
html += '</div></div>';
|
|
2625
2871
|
}
|
|
2626
2872
|
|
|
2627
2873
|
if (c.filesAffected && c.filesAffected.length > 0) {
|
|
2628
|
-
html += '<div style="margin:
|
|
2629
|
-
|
|
2630
|
-
|
|
2631
|
-
|
|
2632
|
-
|
|
2874
|
+
html += '<div style="margin:10px 0;"><div style="font-size:10px;letter-spacing:0.08em;color:var(--ink-muted);margin-bottom:4px;">FILES TOUCHED</div>';
|
|
2875
|
+
html += '<div style="font-size:12px;font-family:var(--font-mono);color:var(--ink);line-height:1.6;">';
|
|
2876
|
+
c.filesAffected.slice(0, 10).forEach(function(f) {
|
|
2877
|
+
html += '<div>• ' + esc(f) + '</div>';
|
|
2878
|
+
});
|
|
2879
|
+
if (c.filesAffected.length > 10) html += '<div style="color:var(--ink-faint);">+' + (c.filesAffected.length - 10) + ' more</div>';
|
|
2880
|
+
html += '</div></div>';
|
|
2633
2881
|
}
|
|
2634
2882
|
|
|
2635
|
-
if (c.
|
|
2636
|
-
html += '<div style="font-size:
|
|
2883
|
+
if (c.lessons && c.lessons.length > 0) {
|
|
2884
|
+
html += '<div style="margin:10px 0;"><div style="font-size:10px;letter-spacing:0.08em;color:var(--ink-muted);margin-bottom:4px;">LESSONS SURFACED</div>';
|
|
2885
|
+
c.lessons.slice(0, 8).forEach(function(lid) {
|
|
2886
|
+
var content = lessonMap[lid] ? lessonMap[lid].content : lid;
|
|
2887
|
+
html += '<div style="font-size:12px;padding:4px 8px;margin:2px 0;background:var(--bg-alt);border-radius:3px;color:var(--ink);line-height:1.4;">💡 ' + esc(content) + '</div>';
|
|
2888
|
+
});
|
|
2889
|
+
if (c.lessons.length > 8) html += '<div style="font-size:11px;color:var(--ink-faint);margin-top:4px;">+' + (c.lessons.length - 8) + ' more lessons</div>';
|
|
2890
|
+
html += '</div>';
|
|
2637
2891
|
}
|
|
2638
2892
|
|
|
2639
2893
|
html += '</div>';
|
|
@@ -2825,13 +3079,66 @@
|
|
|
2825
3079
|
|
|
2826
3080
|
var wsReconnectTimer = null;
|
|
2827
3081
|
var wsRetries = 0;
|
|
2828
|
-
var WS_MAX_RETRIES =
|
|
3082
|
+
var WS_MAX_RETRIES = 4;
|
|
2829
3083
|
var directFailed = false;
|
|
2830
3084
|
var directFailures = 0;
|
|
2831
3085
|
var DIRECT_FAILURE_THRESHOLD = 2;
|
|
3086
|
+
var pollTimer = null;
|
|
3087
|
+
var POLL_INTERVAL_MS = 10000;
|
|
3088
|
+
|
|
3089
|
+
function setWsStatus(text, cls) {
|
|
3090
|
+
var el = document.getElementById('ws-status');
|
|
3091
|
+
if (!el) return;
|
|
3092
|
+
el.textContent = text;
|
|
3093
|
+
el.className = 'ws-status ' + cls;
|
|
3094
|
+
}
|
|
3095
|
+
|
|
3096
|
+
var WS_REPROBE_EVERY_TICKS = 6;
|
|
3097
|
+
|
|
3098
|
+
function startPolling() {
|
|
3099
|
+
if (pollTimer) return;
|
|
3100
|
+
setWsStatus('polling · ' + (POLL_INTERVAL_MS / 1000) + 's', 'disconnected');
|
|
3101
|
+
var tick = 0;
|
|
3102
|
+
pollTimer = setInterval(function() {
|
|
3103
|
+
tick++;
|
|
3104
|
+
if (state.activeTab === 'dashboard') {
|
|
3105
|
+
state.dashboard.loaded = false;
|
|
3106
|
+
loadDashboard();
|
|
3107
|
+
} else if (state.activeTab === 'memories') {
|
|
3108
|
+
state.memories.loaded = false;
|
|
3109
|
+
loadMemories();
|
|
3110
|
+
} else if (state.activeTab === 'sessions') {
|
|
3111
|
+
state.sessions.loaded = false;
|
|
3112
|
+
loadSessions();
|
|
3113
|
+
} else if (state.activeTab === 'activity') {
|
|
3114
|
+
state.activity.loaded = false;
|
|
3115
|
+
loadActivity();
|
|
3116
|
+
}
|
|
3117
|
+
if (tick % WS_REPROBE_EVERY_TICKS === 0) {
|
|
3118
|
+
var ws = state.ws;
|
|
3119
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) {
|
|
3120
|
+
wsRetries = 0;
|
|
3121
|
+
directFailures = 0;
|
|
3122
|
+
directFailed = false;
|
|
3123
|
+
connectWs();
|
|
3124
|
+
}
|
|
3125
|
+
}
|
|
3126
|
+
}, POLL_INTERVAL_MS);
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
function stopPolling() {
|
|
3130
|
+
if (!pollTimer) return;
|
|
3131
|
+
clearInterval(pollTimer);
|
|
3132
|
+
pollTimer = null;
|
|
3133
|
+
}
|
|
3134
|
+
|
|
3135
|
+
var WS_CONNECT_TIMEOUT_MS = 5000;
|
|
2832
3136
|
|
|
2833
3137
|
function connectWs() {
|
|
2834
|
-
if (wsRetries >= WS_MAX_RETRIES)
|
|
3138
|
+
if (wsRetries >= WS_MAX_RETRIES) {
|
|
3139
|
+
startPolling();
|
|
3140
|
+
return;
|
|
3141
|
+
}
|
|
2835
3142
|
var useDirect = !directFailed;
|
|
2836
3143
|
var ws;
|
|
2837
3144
|
try {
|
|
@@ -2841,10 +3148,17 @@
|
|
|
2841
3148
|
ws = new WebSocket(WS_URL);
|
|
2842
3149
|
ws.__direct = false;
|
|
2843
3150
|
}
|
|
3151
|
+
var connectTimer = setTimeout(function() {
|
|
3152
|
+
if (ws.readyState === WebSocket.CONNECTING) {
|
|
3153
|
+
try { ws.close(); } catch {}
|
|
3154
|
+
}
|
|
3155
|
+
}, WS_CONNECT_TIMEOUT_MS);
|
|
2844
3156
|
try {
|
|
2845
3157
|
ws.onopen = function() {
|
|
3158
|
+
clearTimeout(connectTimer);
|
|
2846
3159
|
if (state.ws !== ws) return;
|
|
2847
3160
|
wsRetries = 0;
|
|
3161
|
+
stopPolling();
|
|
2848
3162
|
if (ws.__direct) {
|
|
2849
3163
|
directFailures = 0;
|
|
2850
3164
|
directFailed = false;
|
|
@@ -2859,8 +3173,7 @@
|
|
|
2859
3173
|
}
|
|
2860
3174
|
}));
|
|
2861
3175
|
}
|
|
2862
|
-
|
|
2863
|
-
document.getElementById('ws-status').className = 'ws-status connected';
|
|
3176
|
+
setWsStatus('live', 'connected');
|
|
2864
3177
|
};
|
|
2865
3178
|
ws.onmessage = function(e) {
|
|
2866
3179
|
if (state.ws !== ws) return;
|
|
@@ -2874,6 +3187,7 @@
|
|
|
2874
3187
|
} catch {}
|
|
2875
3188
|
};
|
|
2876
3189
|
ws.onclose = function() {
|
|
3190
|
+
clearTimeout(connectTimer);
|
|
2877
3191
|
if (state.ws !== ws) return;
|
|
2878
3192
|
if (ws.__direct) {
|
|
2879
3193
|
directFailures += 1;
|
|
@@ -2881,13 +3195,12 @@
|
|
|
2881
3195
|
directFailed = true;
|
|
2882
3196
|
}
|
|
2883
3197
|
}
|
|
2884
|
-
document.getElementById('ws-status').textContent = 'reconnecting...';
|
|
2885
|
-
document.getElementById('ws-status').className = 'ws-status disconnected';
|
|
2886
3198
|
wsRetries++;
|
|
2887
3199
|
if (wsRetries < WS_MAX_RETRIES) {
|
|
3200
|
+
setWsStatus('connecting...', 'disconnected');
|
|
2888
3201
|
wsReconnectTimer = setTimeout(connectWs, 2000 + Math.min(wsRetries * 1000, 8000));
|
|
2889
3202
|
} else {
|
|
2890
|
-
|
|
3203
|
+
startPolling();
|
|
2891
3204
|
}
|
|
2892
3205
|
};
|
|
2893
3206
|
ws.onerror = function() {
|
|
@@ -2899,6 +3212,8 @@
|
|
|
2899
3212
|
wsRetries++;
|
|
2900
3213
|
if (wsRetries < WS_MAX_RETRIES) {
|
|
2901
3214
|
wsReconnectTimer = setTimeout(connectWs, 2000 + Math.min(wsRetries * 1000, 8000));
|
|
3215
|
+
} else {
|
|
3216
|
+
startPolling();
|
|
2902
3217
|
}
|
|
2903
3218
|
}
|
|
2904
3219
|
}
|
|
@@ -2966,6 +3281,151 @@
|
|
|
2966
3281
|
switchTab(e.target.dataset.tab);
|
|
2967
3282
|
}
|
|
2968
3283
|
});
|
|
3284
|
+
|
|
3285
|
+
// --- Feature flag banners ---------------------------------------------
|
|
3286
|
+
var FLAG_DISMISS_KEY = 'agentmemory.viewer.flags.dismissed.v1';
|
|
3287
|
+
function loadDismissedFlags() {
|
|
3288
|
+
try {
|
|
3289
|
+
var raw = localStorage.getItem(FLAG_DISMISS_KEY);
|
|
3290
|
+
return raw ? JSON.parse(raw) : {};
|
|
3291
|
+
} catch (_) { return {}; }
|
|
3292
|
+
}
|
|
3293
|
+
function saveDismissedFlags(d) {
|
|
3294
|
+
try { localStorage.setItem(FLAG_DISMISS_KEY, JSON.stringify(d)); } catch (_) {}
|
|
3295
|
+
}
|
|
3296
|
+
function renderFlagBanners(cfg) {
|
|
3297
|
+
var host = document.getElementById('flag-banners');
|
|
3298
|
+
if (!host) return;
|
|
3299
|
+
var dismissed = loadDismissedFlags();
|
|
3300
|
+
var banners = [];
|
|
3301
|
+
// Per-flag banner (only for off flags, affecting current tab or dashboard)
|
|
3302
|
+
(cfg.flags || []).forEach(function(f) {
|
|
3303
|
+
if (f.enabled) return;
|
|
3304
|
+
if (dismissed[f.key]) return;
|
|
3305
|
+
var tabsAffected = (f.affects || []).map(function(t) { return t.toLowerCase(); });
|
|
3306
|
+
if (tabsAffected.length && tabsAffected.indexOf(state.activeTab) === -1 && state.activeTab !== 'dashboard') return;
|
|
3307
|
+
banners.push({
|
|
3308
|
+
kind: 'warn',
|
|
3309
|
+
icon: '⚠',
|
|
3310
|
+
title: f.label,
|
|
3311
|
+
keyLabel: f.key,
|
|
3312
|
+
desc: f.description + (f.needsLlm ? ' Requires an LLM provider key (ANTHROPIC_API_KEY, GEMINI_API_KEY, etc.).' : ''),
|
|
3313
|
+
enable: f.enableHow,
|
|
3314
|
+
docs: f.docsHref,
|
|
3315
|
+
dismissKey: f.key,
|
|
3316
|
+
});
|
|
3317
|
+
});
|
|
3318
|
+
if (cfg.provider === 'noop' && !dismissed['__provider_noop']) {
|
|
3319
|
+
banners.unshift({
|
|
3320
|
+
kind: 'warn',
|
|
3321
|
+
icon: '🔒',
|
|
3322
|
+
title: 'No LLM provider key set',
|
|
3323
|
+
keyLabel: 'ANTHROPIC_API_KEY',
|
|
3324
|
+
desc: 'Compression, summarization, and graph extraction stay disabled until a key is provided.',
|
|
3325
|
+
enable: 'export ANTHROPIC_API_KEY=sk-ant-...\n# then restart: npx @agentmemory/agentmemory',
|
|
3326
|
+
docs: 'https://github.com/rohitg00/agentmemory#quick-start',
|
|
3327
|
+
dismissKey: '__provider_noop',
|
|
3328
|
+
});
|
|
3329
|
+
}
|
|
3330
|
+
if (cfg.embeddingProvider === 'none' && !dismissed['__embedding_none']) {
|
|
3331
|
+
banners.push({
|
|
3332
|
+
kind: 'info',
|
|
3333
|
+
icon: '⚙',
|
|
3334
|
+
title: 'Running in BM25-only mode',
|
|
3335
|
+
keyLabel: 'OPENAI_API_KEY',
|
|
3336
|
+
desc: 'Semantic vector search is off. BM25 keyword search is active and good for exact matches.',
|
|
3337
|
+
enable: 'export OPENAI_API_KEY=sk-...\n# or VOYAGE_API_KEY, COHERE_API_KEY, OLLAMA_HOST',
|
|
3338
|
+
docs: 'https://github.com/rohitg00/agentmemory#embedding-providers',
|
|
3339
|
+
dismissKey: '__embedding_none',
|
|
3340
|
+
});
|
|
3341
|
+
}
|
|
3342
|
+
if (banners.length === 0) { host.innerHTML = ''; return; }
|
|
3343
|
+
var warnCount = banners.filter(function(b) { return b.kind === 'warn'; }).length;
|
|
3344
|
+
var infoCount = banners.filter(function(b) { return b.kind === 'info'; }).length;
|
|
3345
|
+
var expanded = host.getAttribute('data-expanded') === '1';
|
|
3346
|
+
var pills = '';
|
|
3347
|
+
if (warnCount) pills += '<span class="flag-pill">' + warnCount + ' off</span>';
|
|
3348
|
+
if (infoCount) pills += '<span class="flag-pill info">' + infoCount + ' note</span>';
|
|
3349
|
+
var escHtml = function(s) {
|
|
3350
|
+
return String(s).replace(/[<>&"]/g, function(c) {
|
|
3351
|
+
return { '<': '<', '>': '>', '&': '&', '"': '"' }[c];
|
|
3352
|
+
});
|
|
3353
|
+
};
|
|
3354
|
+
var listHtml = banners.map(function(b) {
|
|
3355
|
+
return '<div class="flag-banner ' + b.kind + '" data-flag="' + b.dismissKey + '">' +
|
|
3356
|
+
'<span class="flag-icon">' + b.icon + '</span>' +
|
|
3357
|
+
'<div class="flag-body">' +
|
|
3358
|
+
'<div class="flag-title">' + b.title + ' <code>' + b.keyLabel + '</code></div>' +
|
|
3359
|
+
'<div class="flag-desc">' + escHtml(b.desc) + '</div>' +
|
|
3360
|
+
'<code class="flag-enable">' + escHtml(b.enable) + '</code>' +
|
|
3361
|
+
(b.docs ? ' <a class="empty-link" href="' + b.docs + '" target="_blank" rel="noopener">Learn more →</a>' : '') +
|
|
3362
|
+
'</div>' +
|
|
3363
|
+
'<button class="flag-close" data-dismiss-flag="' + b.dismissKey + '" aria-label="Dismiss">×</button>' +
|
|
3364
|
+
'</div>';
|
|
3365
|
+
}).join('');
|
|
3366
|
+
host.innerHTML = '<button type="button" class="flag-summary" data-action="toggle-flags" aria-expanded="' + (expanded ? 'true' : 'false') + '" aria-controls="flag-list">' +
|
|
3367
|
+
pills +
|
|
3368
|
+
'<span class="flag-count">Feature flags</span>' +
|
|
3369
|
+
'<span style="color:var(--ink-faint);">— click to ' + (expanded ? 'collapse' : 'expand') + '</span>' +
|
|
3370
|
+
'<span class="flag-toggle" aria-hidden="true">' + (expanded ? '▲' : '▼') + '</span>' +
|
|
3371
|
+
'</button>' +
|
|
3372
|
+
'<div class="flag-list' + (expanded ? ' open' : '') + '" id="flag-list">' + listHtml + '</div>';
|
|
3373
|
+
}
|
|
3374
|
+
async function fetchFlags() {
|
|
3375
|
+
var res = await apiGet('config/flags');
|
|
3376
|
+
if (!res) return;
|
|
3377
|
+
state.flagsConfig = res;
|
|
3378
|
+
renderFlagBanners(res);
|
|
3379
|
+
updateFooter(res);
|
|
3380
|
+
}
|
|
3381
|
+
function updateFooter(cfg) {
|
|
3382
|
+
var vEl = document.getElementById('footer-version');
|
|
3383
|
+
if (vEl) vEl.textContent = 'v' + (cfg.version || '?');
|
|
3384
|
+
var fbEl = document.getElementById('footer-feedback');
|
|
3385
|
+
if (fbEl) {
|
|
3386
|
+
var flagSummary = (cfg.flags || []).map(function(f) { return f.key + '=' + (f.enabled ? 'on' : 'off'); }).join(', ');
|
|
3387
|
+
var body = encodeURIComponent(
|
|
3388
|
+
'**Version:** ' + (cfg.version || '?') + '\n' +
|
|
3389
|
+
'**Provider:** ' + (cfg.provider || '?') + '\n' +
|
|
3390
|
+
'**Embedding:** ' + (cfg.embeddingProvider || '?') + '\n' +
|
|
3391
|
+
'**Flags:** ' + flagSummary + '\n' +
|
|
3392
|
+
'**User agent:** ' + navigator.userAgent + '\n\n' +
|
|
3393
|
+
'### What went wrong\n\n' +
|
|
3394
|
+
'(describe the issue)\n\n' +
|
|
3395
|
+
'### Steps to reproduce\n\n' +
|
|
3396
|
+
'1. \n2. \n3. \n'
|
|
3397
|
+
);
|
|
3398
|
+
fbEl.href = 'https://github.com/rohitg00/agentmemory/issues/new?title=' +
|
|
3399
|
+
encodeURIComponent('[viewer] ') + '&body=' + body;
|
|
3400
|
+
}
|
|
3401
|
+
}
|
|
3402
|
+
document.addEventListener('click', function(e) {
|
|
3403
|
+
if (!(e.target instanceof Element)) return;
|
|
3404
|
+
var btn = e.target.closest('[data-dismiss-flag]');
|
|
3405
|
+
if (btn) {
|
|
3406
|
+
e.stopPropagation();
|
|
3407
|
+
var key = btn.getAttribute('data-dismiss-flag');
|
|
3408
|
+
var d = loadDismissedFlags();
|
|
3409
|
+
d[key] = true;
|
|
3410
|
+
saveDismissedFlags(d);
|
|
3411
|
+
if (state.flagsConfig) renderFlagBanners(state.flagsConfig);
|
|
3412
|
+
return;
|
|
3413
|
+
}
|
|
3414
|
+
var toggle = e.target.closest('[data-action="toggle-flags"]');
|
|
3415
|
+
if (toggle) {
|
|
3416
|
+
var host = document.getElementById('flag-banners');
|
|
3417
|
+
var cur = host.getAttribute('data-expanded') === '1';
|
|
3418
|
+
host.setAttribute('data-expanded', cur ? '0' : '1');
|
|
3419
|
+
if (state.flagsConfig) renderFlagBanners(state.flagsConfig);
|
|
3420
|
+
}
|
|
3421
|
+
});
|
|
3422
|
+
// Re-render banners when switching tabs so tab-specific banners appear
|
|
3423
|
+
var _origSwitchTab = switchTab;
|
|
3424
|
+
switchTab = function(tab) {
|
|
3425
|
+
_origSwitchTab(tab);
|
|
3426
|
+
if (state.flagsConfig) renderFlagBanners(state.flagsConfig);
|
|
3427
|
+
};
|
|
3428
|
+
fetchFlags();
|
|
2969
3429
|
document.addEventListener('click', function(e) {
|
|
2970
3430
|
if (!(e.target instanceof Element)) return;
|
|
2971
3431
|
var target = e.target.closest('[data-action]');
|