@agentmemory/agentmemory 0.9.2 → 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.
@@ -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; height: calc(100vh - 90px); overflow-y: auto; padding: 24px; }
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 &rarr;</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';
@@ -903,6 +994,7 @@
903
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) ? gs.nodes : (gs.nodeCount || 0);
1038
- var edgeCount = (gs.edges !== undefined) ? gs.edges : (gs.edgeCount || 0);
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 = '<div class="stats-grid">';
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 &rarr; 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&rsquo;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 &rarr;</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;
@@ -1336,8 +1439,8 @@
1336
1439
  var sb = document.getElementById('graph-sidebar');
1337
1440
  if (!sb) return;
1338
1441
  var gs = state.graph.stats || {};
1339
- var nodeCount = gs.nodes !== undefined ? gs.nodes : (gs.nodeCount || state.graph.nodes.length);
1340
- 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));
1341
1444
 
1342
1445
  var html = '<input type="text" class="graph-search" id="graph-search" placeholder="Search nodes...">';
1343
1446
 
@@ -1961,7 +2064,13 @@
1961
2064
  html += '</select></div>';
1962
2065
 
1963
2066
  if (filtered.length === 0) {
1964
- html += '<div class="empty-state"><div class="empty-icon">&#128218;</div><p>No memories found</p><p style="font-size:12px;color:var(--ink-faint);font-style:italic;">Save facts with the <code>memory_remember</code> MCP tool during Claude Code sessions.</p></div>';
2067
+ html += '<div class="empty-state">' +
2068
+ '<div class="empty-icon">&#128218;</div>' +
2069
+ '<div class="empty-title">No memories yet</div>' +
2070
+ '<div class="empty-lead">Memories are the distilled facts agentmemory keeps across sessions &mdash; 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 &rarr;</a></div>' +
2073
+ '</div>';
1965
2074
  } else {
1966
2075
  html += '<table><tr><th>Title</th><th>Type</th><th>Strength</th><th>Version</th><th>Updated</th><th>Actions</th></tr>';
1967
2076
  filtered.forEach(function(m) {
@@ -2583,7 +2692,13 @@
2583
2692
  html += '</div>';
2584
2693
 
2585
2694
  if (items.length === 0) {
2586
- html += '<div class="empty-state"><div class="empty-icon">&#128161;</div><p>No lessons yet</p><p style="font-size:12px;color:var(--ink-faint);font-style:italic;">Import a Claude Code JSONL (Replay tab → Import JSONL) or save manually via <code>memory_lesson_save</code>.</p></div>';
2695
+ html += '<div class="empty-state">' +
2696
+ '<div class="empty-icon">&#128161;</div>' +
2697
+ '<div class="empty-title">No lessons yet</div>' +
2698
+ '<div class="empty-lead">Lessons are confidence-scored pattern observations &mdash; 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 &rarr; 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 &amp; scoring &rarr;</a></div>' +
2701
+ '</div>';
2587
2702
  } else {
2588
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>';
2589
2704
  items.forEach(function(l) {
@@ -2642,7 +2757,14 @@
2642
2757
  html += '</div>';
2643
2758
 
2644
2759
  if (items.length === 0) {
2645
- html += '<div class="empty-state"><div class="empty-icon">&#9745;</div><p>No actions yet</p><p style="font-size:12px;color:var(--ink-faint);font-style:italic;">Create actions via memory_action_create MCP tool or POST /agentmemory/actions</p></div>';
2760
+ html += '<div class="empty-state">' +
2761
+ '<div class="empty-icon">&#9745;</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 &rarr; active &rarr; 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 &rarr;</a></div>' +
2767
+ '</div>';
2646
2768
  } else {
2647
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>';
2648
2770
  items = items.slice().sort(function(a, b) { return (b.priority || 0) - (a.priority || 0); });
@@ -2716,7 +2838,13 @@
2716
2838
  html += '</div>';
2717
2839
 
2718
2840
  if (items.length === 0) {
2719
- html += '<div class="empty-state"><div class="empty-icon">&#128142;</div><p>No crystals yet</p><p style="font-size:12px;color:var(--ink-faint);font-style:italic;">Crystals auto-generate from JSONL imports, or call <code>memory_crystallize</code> on a session.</p></div>';
2841
+ html += '<div class="empty-state">' +
2842
+ '<div class="empty-icon">&#128142;</div>' +
2843
+ '<div class="empty-title">No crystals yet</div>' +
2844
+ '<div class="empty-lead">Crystals are compressed action digests &mdash; 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 &rarr; 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 &rarr;</a></div>' +
2847
+ '</div>';
2720
2848
  } else {
2721
2849
  items.forEach(function(c) {
2722
2850
  html += '<div class="card" style="margin-bottom:12px;border-left:3px solid var(--accent);">';
@@ -3153,6 +3281,151 @@
3153
3281
  switchTab(e.target.dataset.tab);
3154
3282
  }
3155
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: '&#9888;',
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: '&#128274;',
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: '&#9881;',
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 { '<': '&lt;', '>': '&gt;', '&': '&amp;', '"': '&quot;' }[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 &rarr;</a>' : '') +
3362
+ '</div>' +
3363
+ '<button class="flag-close" data-dismiss-flag="' + b.dismissKey + '" aria-label="Dismiss">&times;</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 ? '&#9650;' : '&#9660;') + '</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();
3156
3429
  document.addEventListener('click', function(e) {
3157
3430
  if (!(e.target instanceof Element)) return;
3158
3431
  var target = e.target.closest('[data-action]');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentmemory/agentmemory",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
4
4
  "description": "Persistent memory for AI coding agents, powered by iii-engine's three primitives",
5
5
  "type": "module",
6
6
  "main": "dist/index.mjs",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentmemory",
3
- "version": "0.9.2",
3
+ "version": "0.9.3",
4
4
  "description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 51 MCP tools, 4 skills, real-time viewer.",
5
5
  "author": {
6
6
  "name": "Rohit Ghumare",
@@ -1,3 +0,0 @@
1
- import { n as getImageRefCount, r as incrementImageRef, t as decrementImageRef } from "./src-tmuZyobT.mjs";
2
-
3
- export { decrementImageRef, incrementImageRef };