Yash030 commited on
Commit
0d0d847
·
1 Parent(s): 1e3eb08

feat: rename sessions using agent summary title, display narrative summary, and add folder-wise filter to sessions tab

Browse files
Files changed (3) hide show
  1. src/app.py +2 -3
  2. src/functions.py +21 -1
  3. src/viewer/index.html +50 -8
src/app.py CHANGED
@@ -419,8 +419,7 @@ def api_replay_sessions():
419
  if auth_err:
420
  return auth_err
421
 
422
- sessions = kv.list(KV.sessions)
423
- sessions.sort(key=lambda s: s.get("startedAt", ""), reverse=True)
424
  return jsonify({"success": True, "sessions": sessions}), 200
425
 
426
  @app.route("/agentmemory/session/start", methods=["POST"])
@@ -1152,7 +1151,7 @@ def api_replay_load():
1152
  session_id = request.args.get("sessionId")
1153
  if not session_id:
1154
  return jsonify({"error": "sessionId required"}), 400
1155
- session = kv.get(KV.sessions, session_id)
1156
  if not session:
1157
  return jsonify({"error": "session not found"}), 404
1158
  obs = kv.list(KV.observations(session_id))
 
419
  if auth_err:
420
  return auth_err
421
 
422
+ sessions = functions.list_sessions(kv)
 
423
  return jsonify({"success": True, "sessions": sessions}), 200
424
 
425
  @app.route("/agentmemory/session/start", methods=["POST"])
 
1151
  session_id = request.args.get("sessionId")
1152
  if not session_id:
1153
  return jsonify({"error": "sessionId required"}), 400
1154
+ session = functions.get_session(kv, session_id)
1155
  if not session:
1156
  return jsonify({"error": "session not found"}), 404
1157
  obs = kv.list(KV.observations(session_id))
src/functions.py CHANGED
@@ -1709,11 +1709,24 @@ def rebuild_index(kv: StateKV) -> int:
1709
 
1710
  def list_sessions(kv: StateKV) -> List[Dict[str, Any]]:
1711
  sessions = kv.list(KV.sessions)
 
 
 
 
 
 
 
1712
  sessions.sort(key=lambda s: s.get("startedAt", ""), reverse=True)
1713
  return sessions
1714
 
1715
  def get_session(kv: StateKV, session_id: str) -> Optional[Dict[str, Any]]:
1716
- return kv.get(KV.sessions, session_id)
 
 
 
 
 
 
1717
 
1718
  def create_session(kv: StateKV, session: Dict[str, Any]) -> Dict[str, Any]:
1719
  kv.set(KV.sessions, session["id"], session)
@@ -2150,6 +2163,13 @@ def summarize(kv: StateKV, data: Dict[str, Any]) -> Dict[str, Any]:
2150
  return {"success": False, "error": f"Reduction failed: {e}"}
2151
 
2152
  kv.set(KV.summaries, session_id, final_summary)
 
 
 
 
 
 
 
2153
  safe_audit(kv, "compress", "mem::summarize", [session_id], {
2154
  "title": final_summary["title"],
2155
  "observationCount": len(compressed)
 
1709
 
1710
  def list_sessions(kv: StateKV) -> List[Dict[str, Any]]:
1711
  sessions = kv.list(KV.sessions)
1712
+ for s in sessions:
1713
+ sid = s.get("id")
1714
+ if sid:
1715
+ summary = kv.get(KV.summaries, sid)
1716
+ if summary:
1717
+ s["title"] = summary.get("title")
1718
+ s["summary"] = summary.get("narrative")
1719
  sessions.sort(key=lambda s: s.get("startedAt", ""), reverse=True)
1720
  return sessions
1721
 
1722
  def get_session(kv: StateKV, session_id: str) -> Optional[Dict[str, Any]]:
1723
+ s = kv.get(KV.sessions, session_id)
1724
+ if s:
1725
+ summary = kv.get(KV.summaries, session_id)
1726
+ if summary:
1727
+ s["title"] = summary.get("title")
1728
+ s["summary"] = summary.get("narrative")
1729
+ return s
1730
 
1731
  def create_session(kv: StateKV, session: Dict[str, Any]) -> Dict[str, Any]:
1732
  kv.set(KV.sessions, session["id"], session)
 
2163
  return {"success": False, "error": f"Reduction failed: {e}"}
2164
 
2165
  kv.set(KV.summaries, session_id, final_summary)
2166
+
2167
+ session = kv.get(KV.sessions, session_id)
2168
+ if session:
2169
+ session["title"] = final_summary["title"]
2170
+ session["summary"] = final_summary["narrative"]
2171
+ kv.set(KV.sessions, session_id, session)
2172
+
2173
  safe_audit(kv, "compress", "mem::summarize", [session_id], {
2174
  "title": final_summary["title"],
2175
  "observationCount": len(compressed)
src/viewer/index.html CHANGED
@@ -1259,7 +1259,7 @@
1259
  graph: { loaded: false, nodes: [], edges: [], stats: null, filters: {}, selectedNode: null, queryError: null, truncated: false, totalNodes: 0, totalEdges: 0 },
1260
  memories: { loaded: false, items: [], search: '', typeFilter: '' },
1261
  timeline: { loaded: false, observations: [], sessionId: '', minImportance: 0, page: 0, pageSize: 50 },
1262
- sessions: { loaded: false, items: [], selectedId: null },
1263
  audit: { loaded: false, entries: [], opFilter: '' },
1264
  activity: { loaded: false, observations: [], sessions: [], typeFilter: '' },
1265
  lessons: { loaded: false, items: [], search: '' },
@@ -1299,8 +1299,15 @@
1299
  return id ? id.slice(0, n || 8) : '';
1300
  }
1301
  function sessionDisplayName(s) {
1302
- var project = s && s.project ? String(s.project).split('/').pop() : '';
1303
- if (project) return project;
 
 
 
 
 
 
 
1304
  return shortSessionId(s, 8) || 'Unknown session';
1305
  }
1306
  function sessionLabel(s) {
@@ -2997,18 +3004,45 @@
2997
  return (b.startedAt || '').localeCompare(a.startedAt || '');
2998
  });
2999
 
3000
- var html = '<div class="session-list">';
3001
- if (items.length === 0) {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3002
  html += '<div class="empty-state"><div class="empty-icon">&#128466;</div><p>No sessions</p></div>';
3003
  } else {
3004
- items.forEach(function(s) {
3005
  var statusBadge = s.status === 'active' ? 'badge-green' : s.status === 'completed' ? 'badge-blue' : 'badge-muted';
3006
  var id = sessionId(s);
3007
  var selected = id && state.sessions.selectedId === id;
3008
  html += '<div class="session-item' + (selected ? ' selected' : '') + '"' + (id ? ' data-action="select-session" data-session-id="' + esc(id) + '"' : '') + '>';
3009
  html += '<div class="session-top"><span class="session-project">' + esc(sessionDisplayName(s)) + '</span>';
3010
  html += '<span class="badge ' + statusBadge + '">' + esc(s.status) + '</span></div>';
3011
- var preview = s.firstPrompt || s.summary || '';
3012
  if (preview) {
3013
  html += '<div class="session-preview" style="font-size:13px;color:var(--ink);margin:4px 0;line-height:1.4;">' + esc(truncate(preview, 140)) + '</div>';
3014
  }
@@ -3022,6 +3056,14 @@
3022
  html += '<div id="session-detail"></div>';
3023
  el.innerHTML = html;
3024
 
 
 
 
 
 
 
 
 
3025
  if (state.sessions.selectedId) renderSessionDetail();
3026
  }
3027
 
@@ -3060,7 +3102,7 @@
3060
  var durationMs = s.endedAt ? new Date(s.endedAt).getTime() - new Date(s.startedAt).getTime() : 0;
3061
  var durationLabel = durationMs > 0 ? (durationMs < 60000 ? (durationMs / 1000).toFixed(1) + 's' : (durationMs / 60000).toFixed(1) + 'm') : '-';
3062
 
3063
- var preview = s.firstPrompt || s.summary || firstPromptFromObs || '';
3064
 
3065
  var html = '<div class="detail-panel">';
3066
  html += '<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:12px;">';
 
1259
  graph: { loaded: false, nodes: [], edges: [], stats: null, filters: {}, selectedNode: null, queryError: null, truncated: false, totalNodes: 0, totalEdges: 0 },
1260
  memories: { loaded: false, items: [], search: '', typeFilter: '' },
1261
  timeline: { loaded: false, observations: [], sessionId: '', minImportance: 0, page: 0, pageSize: 50 },
1262
+ sessions: { loaded: false, items: [], selectedId: null, folderFilter: '' },
1263
  audit: { loaded: false, entries: [], opFilter: '' },
1264
  activity: { loaded: false, observations: [], sessions: [], typeFilter: '' },
1265
  lessons: { loaded: false, items: [], search: '' },
 
1299
  return id ? id.slice(0, n || 8) : '';
1300
  }
1301
  function sessionDisplayName(s) {
1302
+ var folder = s && s.project ? String(s.project).split(/[\\/]/).pop() : '';
1303
+ var title = s && s.title ? String(s.title).trim() : '';
1304
+ if (title && folder) {
1305
+ return title + ' (' + folder + ')';
1306
+ } else if (title) {
1307
+ return title;
1308
+ } else if (folder) {
1309
+ return folder;
1310
+ }
1311
  return shortSessionId(s, 8) || 'Unknown session';
1312
  }
1313
  function sessionLabel(s) {
 
3004
  return (b.startedAt || '').localeCompare(a.startedAt || '');
3005
  });
3006
 
3007
+ var folderFilter = state.sessions.folderFilter || '';
3008
+ var projects = [];
3009
+ state.sessions.items.forEach(function(s) {
3010
+ var folder = s.project ? String(s.project).split(/[\\/]/).pop() : '';
3011
+ if (folder && !projects.includes(folder)) {
3012
+ projects.push(folder);
3013
+ }
3014
+ });
3015
+ projects.sort();
3016
+
3017
+ var toolbarHtml = '<div class="toolbar" style="margin-bottom: 12px; display: flex; gap: 10px; align-items: center;">';
3018
+ toolbarHtml += '<span style="font-size: 12px; font-weight: 600; color: var(--ink-muted);">FILTER BY FOLDER:</span>';
3019
+ toolbarHtml += '<select id="sessions-folder-filter" style="width: 200px; padding: 6px 10px; border-radius: 4px; border: 1px solid var(--border); background: var(--bg); color: var(--ink);">';
3020
+ toolbarHtml += '<option value="">All Folders</option>';
3021
+ projects.forEach(function(p) {
3022
+ var selected = p === folderFilter ? ' selected' : '';
3023
+ toolbarHtml += '<option value="' + esc(p) + '"' + selected + '>' + esc(p) + '</option>';
3024
+ });
3025
+ toolbarHtml += '</select>';
3026
+ toolbarHtml += '</div>';
3027
+
3028
+ var filteredItems = items.filter(function(s) {
3029
+ if (!folderFilter) return true;
3030
+ var folder = s.project ? String(s.project).split(/[\\/]/).pop() : '';
3031
+ return folder === folderFilter;
3032
+ });
3033
+
3034
+ var html = toolbarHtml + '<div class="session-list">';
3035
+ if (filteredItems.length === 0) {
3036
  html += '<div class="empty-state"><div class="empty-icon">&#128466;</div><p>No sessions</p></div>';
3037
  } else {
3038
+ filteredItems.forEach(function(s) {
3039
  var statusBadge = s.status === 'active' ? 'badge-green' : s.status === 'completed' ? 'badge-blue' : 'badge-muted';
3040
  var id = sessionId(s);
3041
  var selected = id && state.sessions.selectedId === id;
3042
  html += '<div class="session-item' + (selected ? ' selected' : '') + '"' + (id ? ' data-action="select-session" data-session-id="' + esc(id) + '"' : '') + '>';
3043
  html += '<div class="session-top"><span class="session-project">' + esc(sessionDisplayName(s)) + '</span>';
3044
  html += '<span class="badge ' + statusBadge + '">' + esc(s.status) + '</span></div>';
3045
+ var preview = s.summary || s.firstPrompt || '';
3046
  if (preview) {
3047
  html += '<div class="session-preview" style="font-size:13px;color:var(--ink);margin:4px 0;line-height:1.4;">' + esc(truncate(preview, 140)) + '</div>';
3048
  }
 
3056
  html += '<div id="session-detail"></div>';
3057
  el.innerHTML = html;
3058
 
3059
+ var selectFilter = document.getElementById('sessions-folder-filter');
3060
+ if (selectFilter) {
3061
+ selectFilter.addEventListener('change', function() {
3062
+ state.sessions.folderFilter = this.value;
3063
+ renderSessions();
3064
+ });
3065
+ }
3066
+
3067
  if (state.sessions.selectedId) renderSessionDetail();
3068
  }
3069
 
 
3102
  var durationMs = s.endedAt ? new Date(s.endedAt).getTime() - new Date(s.startedAt).getTime() : 0;
3103
  var durationLabel = durationMs > 0 ? (durationMs < 60000 ? (durationMs / 1000).toFixed(1) + 's' : (durationMs / 60000).toFixed(1) + 'm') : '-';
3104
 
3105
+ var preview = s.summary || s.firstPrompt || firstPromptFromObs || '';
3106
 
3107
  var html = '<div class="detail-panel">';
3108
  html += '<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:12px;">';