@agentmemory/agentmemory 0.8.12 → 0.9.1

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.
@@ -578,6 +578,35 @@
578
578
  .empty-state p { font-size: 14px; font-family: var(--font-body); font-style: italic; }
579
579
 
580
580
  .loading { color: var(--ink-faint); padding: 20px; text-align: center; font-style: italic; font-family: var(--font-body); }
581
+ .empty { color: var(--ink-muted); padding: 24px; text-align: center; font-family: var(--font-body); font-style: italic; border: 1px dashed var(--border); }
582
+
583
+ .replay-controls { display: flex; align-items: center; gap: 6px; padding: 10px 0; flex-wrap: wrap; font-family: var(--font-ui); font-size: 12px; }
584
+ .replay-controls button { padding: 4px 10px; border: 1px solid var(--border); background: var(--bg); color: var(--ink); cursor: pointer; font-family: var(--font-ui); font-size: 12px; }
585
+ .replay-controls button:hover { background: var(--bg-alt); }
586
+ .replay-controls button.active { background: var(--ink); color: var(--bg); }
587
+ .replay-controls .sep { width: 12px; }
588
+ .replay-progress { height: 3px; background: var(--border-light); margin: 4px 0 12px 0; }
589
+ .replay-progress-bar { height: 100%; background: var(--ink); transition: width 100ms linear; }
590
+ .replay-grid { display: grid; grid-template-columns: 340px 1fr; gap: 16px; align-items: start; }
591
+ .replay-list { max-height: 60vh; overflow-y: auto; border: 1px solid var(--border); }
592
+ .replay-event { display: grid; grid-template-columns: 90px 1fr 60px; gap: 8px; padding: 6px 10px; border-bottom: 1px solid var(--border-light); font-family: var(--font-ui); font-size: 11px; cursor: default; }
593
+ .replay-event:hover { background: var(--bg-alt); }
594
+ .replay-event-active { background: var(--bg-alt); border-left: 2px solid var(--ink); }
595
+ .replay-event-kind { font-size: 9px; text-transform: uppercase; letter-spacing: 0.1em; color: var(--ink-muted); align-self: center; }
596
+ .replay-event-label { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
597
+ .replay-event-time { text-align: right; font-family: var(--font-mono); color: var(--ink-muted); }
598
+ .replay-event-prompt .replay-event-kind { color: var(--blue, #0366d6); }
599
+ .replay-event-response .replay-event-kind { color: var(--green, #2ea043); }
600
+ .replay-event-tool_call .replay-event-kind { color: var(--orange, #bf8700); }
601
+ .replay-event-tool_result .replay-event-kind { color: var(--ink-muted); }
602
+ .replay-event-tool_error .replay-event-kind { color: var(--red, #cf222e); }
603
+ .replay-detail { border: 1px solid var(--border); padding: 14px; max-height: 60vh; overflow-y: auto; font-family: var(--font-body); font-size: 13px; }
604
+ .replay-detail-header { margin-bottom: 6px; }
605
+ .replay-body { background: var(--bg-alt); padding: 10px; white-space: pre-wrap; word-break: break-word; font-family: var(--font-mono); font-size: 12px; }
606
+ .replay-tool { margin-top: 10px; font-family: var(--font-ui); font-size: 12px; }
607
+ .replay-tool-block { margin-top: 8px; }
608
+ .replay-tool-block pre { background: var(--bg-alt); padding: 10px; max-height: 240px; overflow: auto; font-family: var(--font-mono); font-size: 11px; white-space: pre-wrap; word-break: break-word; }
609
+ .muted { color: var(--ink-muted); font-size: 11px; }
581
610
 
582
611
  .metric-table { width: 100%; border-collapse: collapse; font-size: 12px; }
583
612
  .metric-table th { padding: 6px 8px; font-size: 9px; text-transform: uppercase; letter-spacing: 0.12em; color: var(--ink-muted); border-bottom: 2px solid var(--border); text-align: left; font-family: var(--font-ui); font-weight: 600; }
@@ -761,7 +790,7 @@
761
790
  <div class="app-header">
762
791
  <div class="brand">
763
792
  <h1>agentmemory</h1>
764
- <span class="version">v0.7.0</span>
793
+ <span class="version">v__AGENTMEMORY_VERSION__</span>
765
794
  </div>
766
795
  <div class="header-right">
767
796
  <span class="dateline" id="dateline"></span>
@@ -782,6 +811,7 @@
782
811
  <button data-tab="audit">Audit</button>
783
812
  <button data-tab="activity">Activity</button>
784
813
  <button data-tab="profile">Profile</button>
814
+ <button data-tab="replay">Replay</button>
785
815
  </div>
786
816
 
787
817
  <div id="view-dashboard" class="view active"></div>
@@ -795,6 +825,7 @@
795
825
  <div id="view-audit" class="view"></div>
796
826
  <div id="view-activity" class="view"></div>
797
827
  <div id="view-profile" class="view"></div>
828
+ <div id="view-replay" class="view"></div>
798
829
 
799
830
  <div id="modal-overlay" class="modal-overlay">
800
831
  <div class="modal" id="modal"></div>
@@ -871,6 +902,7 @@
871
902
  actions: { loaded: false, items: [], frontier: [], statusFilter: '', search: '' },
872
903
  crystals: { loaded: false, items: [], search: '' },
873
904
  profile: { loaded: false, projects: [], selectedProject: '', data: null },
905
+ replay: { loaded: false, sessions: [], selectedId: '', timeline: null, cursor: 0, playing: false, speed: 1, timer: null, startAt: 0, offsetAt: 0 },
874
906
  ws: null
875
907
  };
876
908
 
@@ -934,6 +966,9 @@
934
966
  }
935
967
 
936
968
  function switchTab(tab) {
969
+ if (state.activeTab === 'replay' && tab !== 'replay' && typeof stopReplayTimer === 'function') {
970
+ stopReplayTimer();
971
+ }
937
972
  state.activeTab = tab;
938
973
  document.querySelectorAll('.tab-bar button').forEach(function(b) {
939
974
  b.classList.toggle('active', b.dataset.tab === tab);
@@ -957,6 +992,7 @@
957
992
  case 'audit': if (!state.audit.loaded) await loadAudit(); break;
958
993
  case 'activity': if (!state.activity.loaded) await loadActivity(); break;
959
994
  case 'profile': if (!state.profile.loaded) await loadProfile(); break;
995
+ case 'replay': if (!state.replay.loaded) await loadReplay(); break;
960
996
  }
961
997
  }
962
998
 
@@ -3006,11 +3042,239 @@
3006
3042
  var auditIndex = parseInt(target.getAttribute('data-audit-index') || '', 10);
3007
3043
  if (!Number.isNaN(auditIndex)) toggleAuditDetail(auditIndex);
3008
3044
  }
3045
+ if (action === 'replay-select') {
3046
+ var rSid = target.getAttribute('data-session-id');
3047
+ if (rSid) selectReplaySession(rSid);
3048
+ return;
3049
+ }
3050
+ if (action === 'replay-toggle-play') { toggleReplayPlay(); return; }
3051
+ if (action === 'replay-step') {
3052
+ var d = parseInt(target.getAttribute('data-dir') || '1', 10);
3053
+ stepReplay(d);
3054
+ return;
3055
+ }
3056
+ if (action === 'replay-speed') {
3057
+ var sp = parseFloat(target.getAttribute('data-speed') || '1');
3058
+ setReplaySpeed(sp);
3059
+ return;
3060
+ }
3061
+ if (action === 'replay-reset') { resetReplay(); return; }
3062
+ if (action === 'replay-import') { runReplayImport(); return; }
3063
+ if (action === 'replay-refresh') { refreshReplaySessions(); return; }
3009
3064
  });
3010
3065
  document.getElementById('modal-overlay').addEventListener('click', function(e) {
3011
3066
  if (e.target === this) closeModal();
3012
3067
  });
3013
3068
 
3069
+ async function loadReplay() {
3070
+ var el = document.getElementById('view-replay');
3071
+ el.innerHTML = '<div class="loading">Loading sessions…</div>';
3072
+ var res = await apiGet('replay/sessions');
3073
+ state.replay.sessions = (res && res.sessions) || [];
3074
+ state.replay.loaded = true;
3075
+ renderReplay();
3076
+ }
3077
+
3078
+ async function refreshReplaySessions() {
3079
+ state.replay.loaded = false;
3080
+ await loadReplay();
3081
+ }
3082
+
3083
+ function renderReplay() {
3084
+ var el = document.getElementById('view-replay');
3085
+ var sessions = state.replay.sessions || [];
3086
+ var options = '<option value="">— pick a session —</option>' + sessions.map(function(s) {
3087
+ var label = (s.project || 'unknown') + ' · ' + (s.id || '').slice(0, 8) + ' · ' + (s.observationCount || 0) + ' obs';
3088
+ return '<option value="' + esc(s.id) + '"' + (s.id === state.replay.selectedId ? ' selected' : '') + '>' + esc(label) + '</option>';
3089
+ }).join('');
3090
+
3091
+ var tl = state.replay.timeline;
3092
+ var hasTl = tl && tl.events && tl.events.length > 0;
3093
+ var cursorEvent = hasTl ? tl.events[Math.min(state.replay.cursor, tl.events.length - 1)] : null;
3094
+ var progress = hasTl && tl.totalDurationMs > 0 ? Math.min(100, (state.replay.offsetAt / tl.totalDurationMs) * 100) : 0;
3095
+
3096
+ el.innerHTML =
3097
+ '<div class="toolbar">' +
3098
+ '<select id="replay-session-select">' + options + '</select>' +
3099
+ '<button data-action="replay-refresh">Refresh</button>' +
3100
+ '<span class="sep"></span>' +
3101
+ '<input type="text" id="replay-import-path" placeholder="~/.claude/projects or file.jsonl" style="width:280px">' +
3102
+ '<button data-action="replay-import">Import JSONL</button>' +
3103
+ '</div>' +
3104
+ (hasTl
3105
+ ? '<div class="replay-controls">' +
3106
+ '<button data-action="replay-step" data-dir="-1" title="Previous (←)">◀</button>' +
3107
+ '<button data-action="replay-toggle-play" title="Play/Pause (Space)">' + (state.replay.playing ? '❚❚ Pause' : '▶ Play') + '</button>' +
3108
+ '<button data-action="replay-step" data-dir="1" title="Next (→)">▶</button>' +
3109
+ '<button data-action="replay-reset" title="Reset">⟲</button>' +
3110
+ '<span class="sep"></span>' +
3111
+ '<span>Speed</span>' +
3112
+ ['0.5', '1', '2', '4'].map(function(sp) {
3113
+ var active = Math.abs(state.replay.speed - parseFloat(sp)) < 0.01;
3114
+ return '<button data-action="replay-speed" data-speed="' + sp + '"' + (active ? ' class="active"' : '') + '>' + sp + '×</button>';
3115
+ }).join('') +
3116
+ '<span class="sep"></span>' +
3117
+ '<span>' + (state.replay.cursor + 1) + ' / ' + tl.eventCount + '</span>' +
3118
+ '</div>' +
3119
+ '<div class="replay-progress"><div class="replay-progress-bar" style="width:' + progress.toFixed(1) + '%"></div></div>' +
3120
+ '<div class="replay-grid">' +
3121
+ '<div class="replay-list" id="replay-list">' +
3122
+ tl.events.map(function(ev, i) {
3123
+ var active = i === state.replay.cursor ? ' replay-event-active' : '';
3124
+ return '<div class="replay-event replay-event-' + esc(ev.kind) + active + '" data-replay-idx="' + i + '">' +
3125
+ '<span class="replay-event-kind">' + esc(ev.kind) + '</span>' +
3126
+ '<span class="replay-event-label">' + esc(ev.label) + '</span>' +
3127
+ '<span class="replay-event-time">' + (ev.offsetMs / 1000).toFixed(1) + 's</span>' +
3128
+ '</div>';
3129
+ }).join('') +
3130
+ '</div>' +
3131
+ '<div class="replay-detail">' + renderReplayDetail(cursorEvent) + '</div>' +
3132
+ '</div>'
3133
+ : '<div class="empty">Pick a session to replay, or import Claude Code JSONL transcripts from ~/.claude/projects.</div>');
3134
+
3135
+ var sel = document.getElementById('replay-session-select');
3136
+ if (sel) sel.addEventListener('change', function() { selectReplaySession(sel.value); });
3137
+ }
3138
+
3139
+ function renderReplayDetail(ev) {
3140
+ if (!ev) return '<div class="empty">No event selected.</div>';
3141
+ var blocks = [];
3142
+ blocks.push('<div class="replay-detail-header"><b>' + esc(ev.label) + '</b> <span class="muted">' + esc(ev.kind) + '</span></div>');
3143
+ if (ev.ts) blocks.push('<div class="muted">' + esc(formatTime(ev.ts)) + '</div>');
3144
+ if (ev.body) {
3145
+ blocks.push('<pre class="replay-body">' + esc(ev.body) + '</pre>');
3146
+ }
3147
+ if (ev.toolName) {
3148
+ blocks.push('<div class="replay-tool"><b>Tool:</b> ' + esc(ev.toolName) + '</div>');
3149
+ }
3150
+ if (ev.toolInput !== undefined && ev.toolInput !== null) {
3151
+ var inp = typeof ev.toolInput === 'string' ? ev.toolInput : JSON.stringify(ev.toolInput, null, 2);
3152
+ blocks.push('<div class="replay-tool-block"><b>Input</b><pre>' + esc(truncate(inp, 4000)) + '</pre></div>');
3153
+ }
3154
+ if (ev.toolOutput !== undefined && ev.toolOutput !== null) {
3155
+ var out = typeof ev.toolOutput === 'string' ? ev.toolOutput : JSON.stringify(ev.toolOutput, null, 2);
3156
+ blocks.push('<div class="replay-tool-block"><b>Output</b><pre>' + esc(truncate(out, 4000)) + '</pre></div>');
3157
+ }
3158
+ return blocks.join('');
3159
+ }
3160
+
3161
+ async function selectReplaySession(sessionId) {
3162
+ stopReplayTimer();
3163
+ state.replay.selectedId = sessionId;
3164
+ state.replay.timeline = null;
3165
+ state.replay.cursor = 0;
3166
+ state.replay.offsetAt = 0;
3167
+ state.replay.playing = false;
3168
+ if (!sessionId) { renderReplay(); return; }
3169
+ var el = document.getElementById('view-replay');
3170
+ el.innerHTML = '<div class="loading">Loading replay…</div>';
3171
+ var res = await apiGet('replay/load?sessionId=' + encodeURIComponent(sessionId));
3172
+ if (res && res.success && res.timeline) {
3173
+ state.replay.timeline = res.timeline;
3174
+ } else {
3175
+ state.replay.timeline = { events: [], eventCount: 0, totalDurationMs: 0 };
3176
+ }
3177
+ renderReplay();
3178
+ }
3179
+
3180
+ function toggleReplayPlay() {
3181
+ if (!state.replay.timeline || state.replay.timeline.eventCount === 0) return;
3182
+ if (state.replay.playing) {
3183
+ stopReplayTimer();
3184
+ } else {
3185
+ startReplayTimer();
3186
+ }
3187
+ renderReplay();
3188
+ }
3189
+
3190
+ function startReplayTimer() {
3191
+ state.replay.playing = true;
3192
+ state.replay.startAt = Date.now();
3193
+ var baseOffset = state.replay.offsetAt;
3194
+ if (state.replay.timer) clearInterval(state.replay.timer);
3195
+ state.replay.timer = setInterval(function() {
3196
+ if (!state.replay.timeline) return;
3197
+ var elapsed = (Date.now() - state.replay.startAt) * state.replay.speed;
3198
+ state.replay.offsetAt = baseOffset + elapsed;
3199
+ var events = state.replay.timeline.events;
3200
+ var newCursor = state.replay.cursor;
3201
+ for (var i = newCursor; i < events.length; i++) {
3202
+ if (events[i].offsetMs <= state.replay.offsetAt) newCursor = i;
3203
+ else break;
3204
+ }
3205
+ var changed = newCursor !== state.replay.cursor;
3206
+ state.replay.cursor = newCursor;
3207
+ if (state.replay.offsetAt >= state.replay.timeline.totalDurationMs) {
3208
+ state.replay.offsetAt = state.replay.timeline.totalDurationMs;
3209
+ stopReplayTimer();
3210
+ renderReplay();
3211
+ return;
3212
+ }
3213
+ if (changed) renderReplay();
3214
+ }, 100);
3215
+ }
3216
+
3217
+ function stopReplayTimer() {
3218
+ state.replay.playing = false;
3219
+ if (state.replay.timer) {
3220
+ clearInterval(state.replay.timer);
3221
+ state.replay.timer = null;
3222
+ }
3223
+ }
3224
+
3225
+ function stepReplay(dir) {
3226
+ if (!state.replay.timeline) return;
3227
+ stopReplayTimer();
3228
+ var next = state.replay.cursor + dir;
3229
+ if (next < 0) next = 0;
3230
+ if (next >= state.replay.timeline.eventCount) next = state.replay.timeline.eventCount - 1;
3231
+ state.replay.cursor = next;
3232
+ state.replay.offsetAt = state.replay.timeline.events[next].offsetMs;
3233
+ renderReplay();
3234
+ }
3235
+
3236
+ function setReplaySpeed(sp) {
3237
+ if (!sp || sp <= 0) return;
3238
+ var wasPlaying = state.replay.playing;
3239
+ stopReplayTimer();
3240
+ state.replay.speed = sp;
3241
+ if (wasPlaying) startReplayTimer();
3242
+ renderReplay();
3243
+ }
3244
+
3245
+ function resetReplay() {
3246
+ stopReplayTimer();
3247
+ state.replay.cursor = 0;
3248
+ state.replay.offsetAt = 0;
3249
+ renderReplay();
3250
+ }
3251
+
3252
+ async function runReplayImport() {
3253
+ var input = document.getElementById('replay-import-path');
3254
+ var pathVal = input ? input.value.trim() : '';
3255
+ var body = {};
3256
+ if (pathVal) body.path = pathVal;
3257
+ var el = document.getElementById('view-replay');
3258
+ var prior = el.innerHTML;
3259
+ el.innerHTML = '<div class="loading">Importing JSONL…</div>';
3260
+ var res = await apiPost('replay/import-jsonl', body);
3261
+ if (!res || res.success === false) {
3262
+ el.innerHTML = prior;
3263
+ alert((res && res.error) || 'Import failed');
3264
+ return;
3265
+ }
3266
+ alert('Imported ' + (res.imported || 0) + ' file(s), ' + (res.observations || 0) + ' observation(s)');
3267
+ await refreshReplaySessions();
3268
+ }
3269
+
3270
+ document.addEventListener('keydown', function(e) {
3271
+ if (state.activeTab !== 'replay') return;
3272
+ if (e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT' || e.target.tagName === 'TEXTAREA')) return;
3273
+ if (e.key === ' ') { e.preventDefault(); toggleReplayPlay(); }
3274
+ else if (e.key === 'ArrowLeft') { e.preventDefault(); stepReplay(-1); }
3275
+ else if (e.key === 'ArrowRight') { e.preventDefault(); stepReplay(1); }
3276
+ });
3277
+
3014
3278
  loadTab('dashboard');
3015
3279
  connectWs();
3016
3280
  startDashboardAutoRefresh();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentmemory/agentmemory",
3
- "version": "0.8.12",
3
+ "version": "0.9.1",
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.8.12",
3
+ "version": "0.9.1",
4
4
  "description": "Persistent memory for AI coding agents -- captures tool usage, compresses via LLM, injects context into future sessions. 12 hooks, 44 MCP tools, 4 skills, real-time viewer.",
5
5
  "author": {
6
6
  "name": "Rohit Ghumare",