@agentmemory/agentmemory 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +42 -15
  2. package/dist/cli.mjs +60 -25
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/hooks/notification.mjs +6 -0
  5. package/dist/hooks/notification.mjs.map +1 -1
  6. package/dist/hooks/post-tool-failure.mjs +6 -0
  7. package/dist/hooks/post-tool-failure.mjs.map +1 -1
  8. package/dist/hooks/post-tool-use.mjs +35 -1
  9. package/dist/hooks/post-tool-use.mjs.map +1 -1
  10. package/dist/hooks/pre-compact.mjs +6 -0
  11. package/dist/hooks/pre-compact.mjs.map +1 -1
  12. package/dist/hooks/pre-tool-use.mjs +6 -0
  13. package/dist/hooks/pre-tool-use.mjs.map +1 -1
  14. package/dist/hooks/prompt-submit.mjs +6 -0
  15. package/dist/hooks/prompt-submit.mjs.map +1 -1
  16. package/dist/hooks/session-end.mjs +6 -0
  17. package/dist/hooks/session-end.mjs.map +1 -1
  18. package/dist/hooks/session-start.mjs +6 -0
  19. package/dist/hooks/session-start.mjs.map +1 -1
  20. package/dist/hooks/stop.mjs +6 -0
  21. package/dist/hooks/stop.mjs.map +1 -1
  22. package/dist/hooks/subagent-start.mjs +6 -0
  23. package/dist/hooks/subagent-start.mjs.map +1 -1
  24. package/dist/hooks/subagent-stop.mjs +6 -0
  25. package/dist/hooks/subagent-stop.mjs.map +1 -1
  26. package/dist/hooks/task-completed.mjs +6 -0
  27. package/dist/hooks/task-completed.mjs.map +1 -1
  28. package/dist/image-refs-Dq5wcV-a.mjs +3 -0
  29. package/dist/image-store-BLOkD0xV.mjs +3 -0
  30. package/dist/index.mjs +2054 -144
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/{src-B3pEsBSb.mjs → src-tmuZyobT.mjs} +1974 -253
  33. package/dist/src-tmuZyobT.mjs.map +1 -0
  34. package/dist/{standalone-DXc-BEqr.mjs → standalone-BiwX0rdC.mjs} +2 -2
  35. package/dist/{standalone-DXc-BEqr.mjs.map → standalone-BiwX0rdC.mjs.map} +1 -1
  36. package/dist/standalone.mjs +136 -2
  37. package/dist/standalone.mjs.map +1 -1
  38. package/dist/{tools-registry-DXIK5CxQ.mjs → tools-registry-CHH84gIQ.mjs} +166 -12
  39. package/dist/tools-registry-CHH84gIQ.mjs.map +1 -0
  40. package/dist/viewer/index.html +249 -62
  41. package/package.json +5 -3
  42. package/plugin/.claude-plugin/plugin.json +2 -2
  43. package/plugin/scripts/notification.mjs +6 -0
  44. package/plugin/scripts/notification.mjs.map +1 -1
  45. package/plugin/scripts/post-tool-failure.mjs +6 -0
  46. package/plugin/scripts/post-tool-failure.mjs.map +1 -1
  47. package/plugin/scripts/post-tool-use.mjs +35 -1
  48. package/plugin/scripts/post-tool-use.mjs.map +1 -1
  49. package/plugin/scripts/pre-compact.mjs +6 -0
  50. package/plugin/scripts/pre-compact.mjs.map +1 -1
  51. package/plugin/scripts/pre-tool-use.mjs +6 -0
  52. package/plugin/scripts/pre-tool-use.mjs.map +1 -1
  53. package/plugin/scripts/prompt-submit.mjs +6 -0
  54. package/plugin/scripts/prompt-submit.mjs.map +1 -1
  55. package/plugin/scripts/session-end.mjs +6 -0
  56. package/plugin/scripts/session-end.mjs.map +1 -1
  57. package/plugin/scripts/session-start.mjs +6 -0
  58. package/plugin/scripts/session-start.mjs.map +1 -1
  59. package/plugin/scripts/stop.mjs +6 -0
  60. package/plugin/scripts/stop.mjs.map +1 -1
  61. package/plugin/scripts/subagent-start.mjs +6 -0
  62. package/plugin/scripts/subagent-start.mjs.map +1 -1
  63. package/plugin/scripts/subagent-stop.mjs +6 -0
  64. package/plugin/scripts/subagent-stop.mjs.map +1 -1
  65. package/plugin/scripts/task-completed.mjs +6 -0
  66. package/plugin/scripts/task-completed.mjs.map +1 -1
  67. package/dist/src-B3pEsBSb.mjs.map +0 -1
  68. package/dist/tools-registry-DXIK5CxQ.mjs.map +0 -1
  69. package/dist/transformers-BX_tgxdO.mjs +0 -38684
  70. package/dist/transformers-BX_tgxdO.mjs.map +0 -1
  71. package/dist/transformers-KMm1i9no.mjs +0 -38683
  72. package/dist/transformers-KMm1i9no.mjs.map +0 -1
@@ -790,7 +790,7 @@
790
790
  <div class="app-header">
791
791
  <div class="brand">
792
792
  <h1>agentmemory</h1>
793
- <span class="version">v0.7.0</span>
793
+ <span class="version">v__AGENTMEMORY_VERSION__</span>
794
794
  </div>
795
795
  <div class="header-right">
796
796
  <span class="dateline" id="dateline"></span>
@@ -900,7 +900,7 @@
900
900
  activity: { loaded: false, observations: [], sessions: [], typeFilter: '' },
901
901
  lessons: { loaded: false, items: [], search: '' },
902
902
  actions: { loaded: false, items: [], frontier: [], statusFilter: '', search: '' },
903
- crystals: { loaded: false, items: [], search: '' },
903
+ crystals: { loaded: false, items: [], search: '', lessonMap: {} },
904
904
  profile: { loaded: false, projects: [], selectedProject: '', data: null },
905
905
  replay: { loaded: false, sessions: [], selectedId: '', timeline: null, cursor: 0, playing: false, speed: 1, timer: null, startAt: 0, offsetAt: 0 },
906
906
  ws: null
@@ -1078,7 +1078,8 @@
1078
1078
  var heapTotal = Math.round((snap.memory.heapTotal || 0) / 1024 / 1024);
1079
1079
  var rss = Math.round((snap.memory.rss || 0) / 1024 / 1024);
1080
1080
  var heapPct = heapTotal > 0 ? Math.round((heapUsed / heapTotal) * 100) : 0;
1081
- var heapColor = heapPct > 80 ? 'var(--red)' : heapPct > 60 ? 'var(--yellow)' : 'var(--green)';
1081
+ var rssAboveFloor = rss >= 512;
1082
+ var heapColor = (heapPct > 80 && rssAboveFloor) ? 'var(--red)' : (heapPct > 60 && rssAboveFloor) ? 'var(--yellow)' : 'var(--green)';
1082
1083
  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
1084
  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
1085
  if (snap.memory.external) {
@@ -1113,6 +1114,14 @@
1113
1114
  html += '</div>';
1114
1115
  }
1115
1116
 
1117
+ if (snap.notes && snap.notes.length > 0) {
1118
+ html += '<div class="card" style="margin-bottom:16px;"><div class="card-title" style="color:var(--ink-muted);">Notes (' + snap.notes.length + ')</div>';
1119
+ snap.notes.forEach(function(n) {
1120
+ 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>';
1121
+ });
1122
+ html += '</div>';
1123
+ }
1124
+
1116
1125
  html += '<div class="two-col">';
1117
1126
 
1118
1127
  html += '<div class="card"><div class="card-title">Recent Sessions</div>';
@@ -1272,6 +1281,7 @@
1272
1281
  function startDashboardAutoRefresh() {
1273
1282
  if (dashboardTimer) clearInterval(dashboardTimer);
1274
1283
  dashboardTimer = setInterval(function() {
1284
+ if (pollTimer) return;
1275
1285
  if (state.activeTab === 'dashboard') refreshDashboard();
1276
1286
  }, 30000);
1277
1287
  }
@@ -1936,7 +1946,13 @@
1936
1946
  items.forEach(function(m) { types[m.type] = true; });
1937
1947
  var typeOptions = Object.keys(types).sort();
1938
1948
 
1939
- var html = '<div class="toolbar">';
1949
+ var html = '<div class="card" style="margin-bottom:12px;padding:12px;background:var(--bg-subtle);">';
1950
+ html += '<div style="font-size:13px;color:var(--ink-muted);line-height:1.5;">';
1951
+ 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. ';
1952
+ html += '<span style="color:var(--ink-faint);">Shown: ' + items.length + ' total.</span>';
1953
+ html += '</div></div>';
1954
+
1955
+ html += '<div class="toolbar">';
1940
1956
  html += '<input type="text" id="mem-search" placeholder="Search memories..." value="' + esc(state.memories.search) + '">';
1941
1957
  html += '<select id="mem-type-filter"><option value="">All types</option>';
1942
1958
  typeOptions.forEach(function(t) {
@@ -1945,12 +1961,14 @@
1945
1961
  html += '</select></div>';
1946
1962
 
1947
1963
  if (filtered.length === 0) {
1948
- html += '<div class="empty-state"><div class="empty-icon">&#128218;</div><p>No memories found</p></div>';
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>';
1949
1965
  } else {
1950
1966
  html += '<table><tr><th>Title</th><th>Type</th><th>Strength</th><th>Version</th><th>Updated</th><th>Actions</th></tr>';
1951
1967
  filtered.forEach(function(m) {
1952
1968
  var badgeClass = TYPE_BADGES[m.type] || 'badge-muted';
1953
- var strength = Math.round((m.strength || 0) * 100);
1969
+ var rawStrength = m.strength || 0;
1970
+ var strength = Math.round(rawStrength <= 1 ? rawStrength * 100 : rawStrength * 10);
1971
+ if (strength > 100) strength = 100;
1954
1972
  var barColor = strength > 70 ? 'var(--green)' : strength > 40 ? 'var(--yellow)' : 'var(--red)';
1955
1973
  html += '<tr>';
1956
1974
  var preview = (m.content || '').split('\n').slice(0, 2).join(' ').trim();
@@ -2399,6 +2417,10 @@
2399
2417
  html += '<div class="session-item' + (selected ? ' selected' : '') + '" data-action="select-session" data-session-id="' + esc(s.id) + '">';
2400
2418
  html += '<div class="session-top"><span class="session-project">' + esc(s.project ? s.project.split('/').pop() : 'Unknown') + '</span>';
2401
2419
  html += '<span class="badge ' + statusBadge + '">' + esc(s.status) + '</span></div>';
2420
+ var preview = s.firstPrompt || s.summary || '';
2421
+ if (preview) {
2422
+ html += '<div class="session-preview" style="font-size:13px;color:var(--ink);margin:4px 0;line-height:1.4;">' + esc(truncate(preview, 140)) + '</div>';
2423
+ }
2402
2424
  html += '<div class="session-meta">' + esc(s.id.slice(0, 12)) + ' &middot; ' + esc(formatTime(s.startedAt));
2403
2425
  html += ' &middot; ' + (s.observationCount || 0) + ' obs';
2404
2426
  if (s.model) html += ' &middot; ' + esc(s.model);
@@ -2417,24 +2439,96 @@
2417
2439
  renderSessions();
2418
2440
  }
2419
2441
 
2420
- function renderSessionDetail() {
2442
+ async function renderSessionDetail() {
2421
2443
  var panel = document.getElementById('session-detail');
2422
2444
  if (!panel) return;
2423
2445
  var s = state.sessions.items.find(function(x) { return x.id === state.sessions.selectedId; });
2424
2446
  if (!s) { panel.innerHTML = ''; return; }
2425
2447
 
2426
- var html = '<div class="detail-panel"><h3>Session Details</h3>';
2427
- html += '<div class="detail-row"><div class="dl">Session ID</div><div class="dv" style="font-family:var(--font-mono);font-size:12px;">' + esc(s.id) + '</div></div>';
2428
- html += '<div class="detail-row"><div class="dl">Project</div><div class="dv">' + esc(s.project) + '</div></div>';
2429
- html += '<div class="detail-row"><div class="dl">Working Dir</div><div class="dv" style="font-family:var(--font-mono);font-size:12px;">' + esc(s.cwd) + '</div></div>';
2430
- html += '<div class="detail-row"><div class="dl">Status</div><div class="dv">' + esc(s.status) + '</div></div>';
2431
- html += '<div class="detail-row"><div class="dl">Started</div><div class="dv" style="font-family:var(--font-mono);font-size:12px;">' + esc(formatTime(s.startedAt)) + '</div></div>';
2432
- if (s.endedAt) html += '<div class="detail-row"><div class="dl">Ended</div><div class="dv" style="font-family:var(--font-mono);font-size:12px;">' + esc(formatTime(s.endedAt)) + '</div></div>';
2433
- html += '<div class="detail-row"><div class="dl">Observations</div><div class="dv" style="font-family:var(--font-mono);">' + (s.observationCount || 0) + '</div></div>';
2434
- if (s.model) html += '<div class="detail-row"><div class="dl">Model</div><div class="dv">' + esc(s.model) + '</div></div>';
2435
- if (s.tags && s.tags.length) html += '<div class="detail-row"><div class="dl">Tags</div><div class="dv">' + s.tags.map(function(t) { return '<span class="badge badge-muted" style="margin-right:4px;">' + esc(t) + '</span>'; }).join('') + '</div></div>';
2436
-
2437
- html += '<div style="margin-top:16px;display:flex;gap:8px;">';
2448
+ panel.innerHTML = '<div class="detail-panel"><h3>Loading session details…</h3></div>';
2449
+
2450
+ var obsRes = await apiGet('observations?sessionId=' + encodeURIComponent(s.id));
2451
+ var obs = (obsRes && obsRes.observations) || [];
2452
+
2453
+ var typeCounts = {};
2454
+ var toolCounts = {};
2455
+ var fileSet = new Set();
2456
+ var firstPromptFromObs = '';
2457
+ obs.forEach(function(o) {
2458
+ var t = o.type || o.hookType || 'other';
2459
+ typeCounts[t] = (typeCounts[t] || 0) + 1;
2460
+ var tool = o.title || o.toolName;
2461
+ if (tool && t !== 'conversation') toolCounts[tool] = (toolCounts[tool] || 0) + 1;
2462
+ (o.files || []).forEach(function(f) { fileSet.add(f); });
2463
+ if (!firstPromptFromObs && (o.userPrompt || (o.type === 'conversation' && o.narrative))) {
2464
+ firstPromptFromObs = o.userPrompt || o.narrative || '';
2465
+ }
2466
+ });
2467
+
2468
+ var durationMs = s.endedAt ? new Date(s.endedAt).getTime() - new Date(s.startedAt).getTime() : 0;
2469
+ var durationLabel = durationMs > 0 ? (durationMs < 60000 ? (durationMs / 1000).toFixed(1) + 's' : (durationMs / 60000).toFixed(1) + 'm') : '-';
2470
+
2471
+ var preview = s.firstPrompt || s.summary || firstPromptFromObs || '';
2472
+
2473
+ var html = '<div class="detail-panel">';
2474
+ html += '<div style="display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:12px;">';
2475
+ html += '<h3 style="margin:0;">Session · ' + esc(s.project || 'Unknown') + '</h3>';
2476
+ html += '<span class="badge ' + (s.status === 'active' ? 'badge-green' : 'badge-blue') + '">' + esc(s.status) + '</span>';
2477
+ html += '</div>';
2478
+
2479
+ if (preview) {
2480
+ 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>';
2481
+ }
2482
+
2483
+ html += '<div style="display:grid;grid-template-columns:repeat(4, 1fr);gap:10px;margin-bottom:14px;">';
2484
+ 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>';
2485
+ 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>';
2486
+ 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>';
2487
+ 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>';
2488
+ html += '</div>';
2489
+
2490
+ var topTools = Object.keys(toolCounts).sort(function(a, b) { return toolCounts[b] - toolCounts[a]; }).slice(0, 10);
2491
+ if (topTools.length > 0) {
2492
+ var maxC = toolCounts[topTools[0]] || 1;
2493
+ html += '<div class="card" style="margin-bottom:12px;"><div class="card-title">Tool Invocations</div>';
2494
+ html += '<div class="bar-chart" style="margin-top:8px;">';
2495
+ topTools.forEach(function(t) {
2496
+ var pct = Math.round((toolCounts[t] / maxC) * 100);
2497
+ 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>';
2498
+ });
2499
+ html += '</div></div>';
2500
+ }
2501
+
2502
+ var typeKeys = Object.keys(typeCounts).sort(function(a, b) { return typeCounts[b] - typeCounts[a]; });
2503
+ if (typeKeys.length > 0) {
2504
+ html += '<div class="card" style="margin-bottom:12px;"><div class="card-title">Activity Breakdown</div>';
2505
+ html += '<div style="display:flex;gap:6px;flex-wrap:wrap;margin-top:8px;">';
2506
+ typeKeys.forEach(function(t) {
2507
+ html += '<span class="badge badge-muted" style="font-family:var(--font-mono);">' + esc(t.replace(/_/g, ' ')) + ' · ' + typeCounts[t] + '</span>';
2508
+ });
2509
+ html += '</div></div>';
2510
+ }
2511
+
2512
+ if (fileSet.size > 0) {
2513
+ var filesArr = Array.from(fileSet).slice(0, 30);
2514
+ html += '<div class="card" style="margin-bottom:12px;"><div class="card-title">Files</div>';
2515
+ html += '<div style="font-size:12px;font-family:var(--font-mono);line-height:1.6;margin-top:8px;">';
2516
+ filesArr.forEach(function(f) { html += '<div>&#8226; ' + esc(f) + '</div>'; });
2517
+ if (fileSet.size > 30) html += '<div style="color:var(--ink-faint);">+' + (fileSet.size - 30) + ' more</div>';
2518
+ html += '</div></div>';
2519
+ }
2520
+
2521
+ html += '<div class="card" style="margin-bottom:12px;"><div class="card-title">Metadata</div>';
2522
+ html += '<div style="font-size:12px;font-family:var(--font-mono);margin-top:8px;line-height:1.7;">';
2523
+ html += '<div><span style="color:var(--ink-muted);">id:</span> ' + esc(s.id) + '</div>';
2524
+ html += '<div><span style="color:var(--ink-muted);">cwd:</span> ' + esc(s.cwd || '-') + '</div>';
2525
+ html += '<div><span style="color:var(--ink-muted);">started:</span> ' + esc(formatTime(s.startedAt)) + '</div>';
2526
+ if (s.endedAt) html += '<div><span style="color:var(--ink-muted);">ended:</span> ' + esc(formatTime(s.endedAt)) + '</div>';
2527
+ if (s.model) html += '<div><span style="color:var(--ink-muted);">model:</span> ' + esc(s.model) + '</div>';
2528
+ if (s.tags && s.tags.length) html += '<div><span style="color:var(--ink-muted);">tags:</span> ' + s.tags.map(esc).join(', ') + '</div>';
2529
+ html += '</div></div>';
2530
+
2531
+ html += '<div style="display:flex;gap:8px;">';
2438
2532
  if (s.status === 'active') {
2439
2533
  html += '<button class="btn btn-danger" data-action="end-session" data-session-id="' + esc(s.id) + '">End Session</button>';
2440
2534
  }
@@ -2478,13 +2572,18 @@
2478
2572
  });
2479
2573
  }
2480
2574
 
2481
- var html = '<div style="display:flex;gap:8px;margin-bottom:12px;">';
2575
+ var html = '<div class="card" style="margin-bottom:12px;padding:12px;background:var(--bg-subtle);">';
2576
+ html += '<div style="font-size:13px;color:var(--ink-muted);line-height:1.5;">';
2577
+ 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.';
2578
+ html += '</div></div>';
2579
+
2580
+ html += '<div style="display:flex;gap:8px;margin-bottom:12px;">';
2482
2581
  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
2582
  html += '<span style="font-size:12px;color:var(--ink-faint);align-self:center;">' + items.length + ' lessons</span>';
2484
2583
  html += '</div>';
2485
2584
 
2486
2585
  if (items.length === 0) {
2487
- 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;">Lessons are extracted from crystals or saved manually via memory_lesson_save.</p></div>';
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>';
2488
2587
  } else {
2489
2588
  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
2589
  items.forEach(function(l) {
@@ -2510,7 +2609,7 @@
2510
2609
  el.innerHTML = '<div class="loading">Loading actions...</div>';
2511
2610
  var results = await Promise.all([apiGet('actions'), apiGet('frontier')]);
2512
2611
  state.actions.items = (results[0] && results[0].actions) || [];
2513
- state.actions.frontier = (results[1] && results[1].actions) || [];
2612
+ state.actions.frontier = (results[1] && (results[1].frontier || results[1].actions)) || [];
2514
2613
  state.actions.loaded = true;
2515
2614
  renderActions();
2516
2615
  }
@@ -2569,15 +2668,13 @@
2569
2668
 
2570
2669
  async function loadCrystals() {
2571
2670
  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
2671
  el.innerHTML = '<div class="loading">Loading crystals...</div>';
2579
- var result = await apiGet('crystals');
2580
- state.crystals.items = (result && result.crystals) || [];
2672
+ var results = await Promise.all([apiGet('crystals'), apiGet('lessons')]);
2673
+ state.crystals.items = (results[0] && results[0].crystals) || [];
2674
+ var lessonMap = {};
2675
+ var lessons = (results[1] && results[1].lessons) || [];
2676
+ lessons.forEach(function(l) { if (l && l.id) lessonMap[l.id] = l; });
2677
+ state.crystals.lessonMap = lessonMap;
2581
2678
  state.crystals.loaded = true;
2582
2679
  renderCrystals();
2583
2680
  }
@@ -2586,54 +2683,83 @@
2586
2683
  var el = document.getElementById('view-crystals');
2587
2684
  var items = state.crystals.items;
2588
2685
  var search = state.crystals.search.toLowerCase();
2686
+ var lessonMap = state.crystals.lessonMap || {};
2589
2687
 
2590
2688
  if (search) {
2591
2689
  items = items.filter(function(c) {
2592
- return ((c.narrative || '') + ' ' + (c.keyOutcomes || []).join(' ') + ' ' + (c.lessons || []).join(' ')).toLowerCase().indexOf(search) >= 0;
2690
+ var lessonText = (c.lessons || [])
2691
+ .map(function(lid) {
2692
+ var l = lessonMap[lid];
2693
+ return l && typeof l.content === 'string' ? l.content : lid;
2694
+ })
2695
+ .join(' ');
2696
+ var filesText = (c.filesAffected || []).join(' ');
2697
+ var haystack = [
2698
+ c.narrative || '',
2699
+ (c.keyOutcomes || []).join(' '),
2700
+ lessonText,
2701
+ filesText,
2702
+ c.project || '',
2703
+ ].join(' ').toLowerCase();
2704
+ return haystack.indexOf(search) >= 0;
2593
2705
  });
2594
2706
  }
2595
2707
 
2596
- var html = '<div style="display:flex;gap:8px;margin-bottom:12px;">';
2708
+ var html = '<div class="card" style="margin-bottom:12px;padding:12px;background:var(--bg-subtle);">';
2709
+ html += '<div style="font-size:13px;color:var(--ink-muted);line-height:1.5;">';
2710
+ 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>.';
2711
+ html += '</div></div>';
2712
+
2713
+ html += '<div style="display:flex;gap:8px;margin-bottom:12px;">';
2597
2714
  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
2715
  html += '<span style="font-size:12px;color:var(--ink-faint);align-self:center;">' + items.length + ' crystals</span>';
2599
2716
  html += '</div>';
2600
2717
 
2601
2718
  if (items.length === 0) {
2602
- 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 are created by compressing completed action chains via memory_crystallize.</p></div>';
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>';
2603
2720
  } else {
2604
2721
  items.forEach(function(c) {
2605
- html += '<div class="card" style="margin-bottom:12px;">';
2606
- html += '<div class="card-title" style="display:flex;justify-content:space-between;">';
2607
- html += '<span>' + esc(truncate(c.narrative, 100)) + '</span>';
2608
- html += '<span style="font-size:11px;color:var(--ink-faint);">' + formatTime(c.createdAt) + '</span>';
2722
+ html += '<div class="card" style="margin-bottom:12px;border-left:3px solid var(--accent);">';
2723
+ html += '<div style="display:flex;justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:8px;">';
2724
+ 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>';
2725
+ html += '<div style="font-size:10px;color:var(--ink-faint);font-family:var(--font-mono);white-space:nowrap;">' + esc(formatTime(c.createdAt)) + '</div>';
2609
2726
  html += '</div>';
2610
2727
 
2728
+ var pillRow = [];
2729
+ if (c.project) pillRow.push('<span class="badge badge-muted">' + esc(c.project) + '</span>');
2730
+ if (c.sessionId) pillRow.push('<span class="badge badge-blue" style="font-family:var(--font-mono);">' + esc(c.sessionId.slice(0, 14)) + '</span>');
2731
+ if (c.keyOutcomes && c.keyOutcomes.length) pillRow.push('<span style="font-size:11px;color:var(--ink-muted);">' + c.keyOutcomes.length + ' tools</span>');
2732
+ if (c.filesAffected && c.filesAffected.length) pillRow.push('<span style="font-size:11px;color:var(--ink-muted);">' + c.filesAffected.length + ' files</span>');
2733
+ if (c.lessons && c.lessons.length) pillRow.push('<span style="font-size:11px;color:var(--ink-muted);">' + c.lessons.length + ' lessons</span>');
2734
+ if (pillRow.length) html += '<div style="display:flex;gap:6px;flex-wrap:wrap;margin-bottom:10px;">' + pillRow.join('') + '</div>';
2735
+
2611
2736
  if (c.keyOutcomes && c.keyOutcomes.length > 0) {
2612
- html += '<div style="margin:8px 0;"><strong style="font-size:11px;color:var(--ink-muted);">KEY OUTCOMES</strong>';
2737
+ 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>';
2738
+ html += '<div style="display:flex;gap:4px;flex-wrap:wrap;">';
2613
2739
  c.keyOutcomes.forEach(function(o) {
2614
- html += '<div style="font-size:12px;padding:2px 0;color:var(--ink);">&#8226; ' + esc(o) + '</div>';
2740
+ html += '<span class="badge" style="background:var(--bg-alt);color:var(--ink);font-family:var(--font-mono);">' + esc(o) + '</span>';
2615
2741
  });
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);">&#128161; ' + esc(l) + '</div>';
2623
- });
2624
- html += '</div>';
2742
+ html += '</div></div>';
2625
2743
  }
2626
2744
 
2627
2745
  if (c.filesAffected && c.filesAffected.length > 0) {
2628
- html += '<div style="margin:8px 0;font-size:11px;color:var(--ink-muted);">Files: <span style="font-family:var(--font-mono);">' + c.filesAffected.map(esc).join(', ') + '</span></div>';
2629
- }
2630
-
2631
- if (c.sourceActionIds && c.sourceActionIds.length > 0) {
2632
- html += '<div style="font-size:11px;color:var(--ink-faint);">Source actions: ' + c.sourceActionIds.map(esc).join(', ') + '</div>';
2746
+ 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>';
2747
+ html += '<div style="font-size:12px;font-family:var(--font-mono);color:var(--ink);line-height:1.6;">';
2748
+ c.filesAffected.slice(0, 10).forEach(function(f) {
2749
+ html += '<div>&#8226; ' + esc(f) + '</div>';
2750
+ });
2751
+ if (c.filesAffected.length > 10) html += '<div style="color:var(--ink-faint);">+' + (c.filesAffected.length - 10) + ' more</div>';
2752
+ html += '</div></div>';
2633
2753
  }
2634
2754
 
2635
- if (c.project) {
2636
- html += '<div style="font-size:11px;color:var(--ink-faint);margin-top:4px;">Project: ' + esc(c.project) + '</div>';
2755
+ if (c.lessons && c.lessons.length > 0) {
2756
+ 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>';
2757
+ c.lessons.slice(0, 8).forEach(function(lid) {
2758
+ var content = lessonMap[lid] ? lessonMap[lid].content : lid;
2759
+ 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;">&#128161; ' + esc(content) + '</div>';
2760
+ });
2761
+ 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>';
2762
+ html += '</div>';
2637
2763
  }
2638
2764
 
2639
2765
  html += '</div>';
@@ -2825,13 +2951,66 @@
2825
2951
 
2826
2952
  var wsReconnectTimer = null;
2827
2953
  var wsRetries = 0;
2828
- var WS_MAX_RETRIES = 10;
2954
+ var WS_MAX_RETRIES = 4;
2829
2955
  var directFailed = false;
2830
2956
  var directFailures = 0;
2831
2957
  var DIRECT_FAILURE_THRESHOLD = 2;
2958
+ var pollTimer = null;
2959
+ var POLL_INTERVAL_MS = 10000;
2960
+
2961
+ function setWsStatus(text, cls) {
2962
+ var el = document.getElementById('ws-status');
2963
+ if (!el) return;
2964
+ el.textContent = text;
2965
+ el.className = 'ws-status ' + cls;
2966
+ }
2967
+
2968
+ var WS_REPROBE_EVERY_TICKS = 6;
2969
+
2970
+ function startPolling() {
2971
+ if (pollTimer) return;
2972
+ setWsStatus('polling · ' + (POLL_INTERVAL_MS / 1000) + 's', 'disconnected');
2973
+ var tick = 0;
2974
+ pollTimer = setInterval(function() {
2975
+ tick++;
2976
+ if (state.activeTab === 'dashboard') {
2977
+ state.dashboard.loaded = false;
2978
+ loadDashboard();
2979
+ } else if (state.activeTab === 'memories') {
2980
+ state.memories.loaded = false;
2981
+ loadMemories();
2982
+ } else if (state.activeTab === 'sessions') {
2983
+ state.sessions.loaded = false;
2984
+ loadSessions();
2985
+ } else if (state.activeTab === 'activity') {
2986
+ state.activity.loaded = false;
2987
+ loadActivity();
2988
+ }
2989
+ if (tick % WS_REPROBE_EVERY_TICKS === 0) {
2990
+ var ws = state.ws;
2991
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
2992
+ wsRetries = 0;
2993
+ directFailures = 0;
2994
+ directFailed = false;
2995
+ connectWs();
2996
+ }
2997
+ }
2998
+ }, POLL_INTERVAL_MS);
2999
+ }
3000
+
3001
+ function stopPolling() {
3002
+ if (!pollTimer) return;
3003
+ clearInterval(pollTimer);
3004
+ pollTimer = null;
3005
+ }
3006
+
3007
+ var WS_CONNECT_TIMEOUT_MS = 5000;
2832
3008
 
2833
3009
  function connectWs() {
2834
- if (wsRetries >= WS_MAX_RETRIES) return;
3010
+ if (wsRetries >= WS_MAX_RETRIES) {
3011
+ startPolling();
3012
+ return;
3013
+ }
2835
3014
  var useDirect = !directFailed;
2836
3015
  var ws;
2837
3016
  try {
@@ -2841,10 +3020,17 @@
2841
3020
  ws = new WebSocket(WS_URL);
2842
3021
  ws.__direct = false;
2843
3022
  }
3023
+ var connectTimer = setTimeout(function() {
3024
+ if (ws.readyState === WebSocket.CONNECTING) {
3025
+ try { ws.close(); } catch {}
3026
+ }
3027
+ }, WS_CONNECT_TIMEOUT_MS);
2844
3028
  try {
2845
3029
  ws.onopen = function() {
3030
+ clearTimeout(connectTimer);
2846
3031
  if (state.ws !== ws) return;
2847
3032
  wsRetries = 0;
3033
+ stopPolling();
2848
3034
  if (ws.__direct) {
2849
3035
  directFailures = 0;
2850
3036
  directFailed = false;
@@ -2859,8 +3045,7 @@
2859
3045
  }
2860
3046
  }));
2861
3047
  }
2862
- document.getElementById('ws-status').textContent = 'live';
2863
- document.getElementById('ws-status').className = 'ws-status connected';
3048
+ setWsStatus('live', 'connected');
2864
3049
  };
2865
3050
  ws.onmessage = function(e) {
2866
3051
  if (state.ws !== ws) return;
@@ -2874,6 +3059,7 @@
2874
3059
  } catch {}
2875
3060
  };
2876
3061
  ws.onclose = function() {
3062
+ clearTimeout(connectTimer);
2877
3063
  if (state.ws !== ws) return;
2878
3064
  if (ws.__direct) {
2879
3065
  directFailures += 1;
@@ -2881,13 +3067,12 @@
2881
3067
  directFailed = true;
2882
3068
  }
2883
3069
  }
2884
- document.getElementById('ws-status').textContent = 'reconnecting...';
2885
- document.getElementById('ws-status').className = 'ws-status disconnected';
2886
3070
  wsRetries++;
2887
3071
  if (wsRetries < WS_MAX_RETRIES) {
3072
+ setWsStatus('connecting...', 'disconnected');
2888
3073
  wsReconnectTimer = setTimeout(connectWs, 2000 + Math.min(wsRetries * 1000, 8000));
2889
3074
  } else {
2890
- document.getElementById('ws-status').textContent = 'disconnected';
3075
+ startPolling();
2891
3076
  }
2892
3077
  };
2893
3078
  ws.onerror = function() {
@@ -2899,6 +3084,8 @@
2899
3084
  wsRetries++;
2900
3085
  if (wsRetries < WS_MAX_RETRIES) {
2901
3086
  wsReconnectTimer = setTimeout(connectWs, 2000 + Math.min(wsRetries * 1000, 8000));
3087
+ } else {
3088
+ startPolling();
2902
3089
  }
2903
3090
  }
2904
3091
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentmemory/agentmemory",
3
- "version": "0.9.0",
3
+ "version": "0.9.2",
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",
@@ -58,11 +58,13 @@
58
58
  "@anthropic-ai/sdk": "^0.39.0",
59
59
  "@clack/prompts": "^1.2.0",
60
60
  "dotenv": "^16.4.7",
61
- "iii-sdk": "^0.11.0",
61
+ "iii-sdk": "^0.11.2",
62
62
  "zod": "^4.0.0"
63
63
  },
64
64
  "optionalDependencies": {
65
- "@xenova/transformers": "^2.17.2"
65
+ "@xenova/transformers": "^2.17.2",
66
+ "onnxruntime-node": "^1.14.0",
67
+ "onnxruntime-web": "^1.14.0"
66
68
  },
67
69
  "devDependencies": {
68
70
  "@types/node": "^22.0.0",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "agentmemory",
3
- "version": "0.9.0",
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.",
3
+ "version": "0.9.2",
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",
7
7
  "url": "https://github.com/rohitg00"
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  //#region src/hooks/notification.ts
3
+ function isSdkChildContext(payload) {
4
+ if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true;
5
+ if (!payload || typeof payload !== "object") return false;
6
+ return payload.entrypoint === "sdk-ts";
7
+ }
3
8
  const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111";
4
9
  const SECRET = process.env["AGENTMEMORY_SECRET"] || "";
5
10
  function authHeaders() {
@@ -16,6 +21,7 @@ async function main() {
16
21
  } catch {
17
22
  return;
18
23
  }
24
+ if (isSdkChildContext(data)) return;
19
25
  if (data.notification_type !== "permission_prompt") return;
20
26
  const sessionId = data.session_id || "unknown";
21
27
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"notification.mjs","names":[],"sources":["../../src/hooks/notification.ts"],"sourcesContent":["#!/usr/bin/env node\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = process.env[\"AGENTMEMORY_SECRET\"] || \"\";\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function main() {\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n if (data.notification_type !== \"permission_prompt\") return;\n\n const sessionId = (data.session_id as string) || \"unknown\";\n\n try {\n await fetch(`${REST_URL}/agentmemory/observe`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({\n hookType: \"notification\",\n sessionId,\n project: data.cwd || process.cwd(),\n cwd: data.cwd || process.cwd(),\n timestamp: new Date().toISOString(),\n data: {\n notification_type: data.notification_type,\n title: data.title,\n message: data.message,\n },\n }),\n signal: AbortSignal.timeout(2000),\n });\n } catch {\n // fire and forget\n }\n}\n\nmain();\n"],"mappings":";;AAEA,MAAM,WAAW,QAAQ,IAAI,sBAAsB;AACnD,MAAM,SAAS,QAAQ,IAAI,yBAAyB;AAEpD,SAAS,cAAsC;CAC7C,MAAM,IAA4B,EAAE,gBAAgB,oBAAoB;AACxE,KAAI,OAAQ,GAAE,mBAAmB,UAAU;AAC3C,QAAO;;AAGT,eAAe,OAAO;CACpB,IAAI,QAAQ;AACZ,YAAW,MAAM,SAAS,QAAQ,MAChC,UAAS;CAGX,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,MAAM;SAClB;AACN;;AAGF,KAAI,KAAK,sBAAsB,oBAAqB;CAEpD,MAAM,YAAa,KAAK,cAAyB;AAEjD,KAAI;AACF,QAAM,MAAM,GAAG,SAAS,uBAAuB;GAC7C,QAAQ;GACR,SAAS,aAAa;GACtB,MAAM,KAAK,UAAU;IACnB,UAAU;IACV;IACA,SAAS,KAAK,OAAO,QAAQ,KAAK;IAClC,KAAK,KAAK,OAAO,QAAQ,KAAK;IAC9B,4BAAW,IAAI,MAAM,EAAC,aAAa;IACnC,MAAM;KACJ,mBAAmB,KAAK;KACxB,OAAO,KAAK;KACZ,SAAS,KAAK;KACf;IACF,CAAC;GACF,QAAQ,YAAY,QAAQ,IAAK;GAClC,CAAC;SACI;;AAKV,MAAM"}
1
+ {"version":3,"file":"notification.mjs","names":[],"sources":["../../src/hooks/notification.ts"],"sourcesContent":["#!/usr/bin/env node\n\nfunction isSdkChildContext(payload: unknown): boolean {\n if (process.env[\"AGENTMEMORY_SDK_CHILD\"] === \"1\") return true;\n if (!payload || typeof payload !== \"object\") return false;\n return (payload as { entrypoint?: unknown }).entrypoint === \"sdk-ts\";\n}\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = process.env[\"AGENTMEMORY_SECRET\"] || \"\";\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function main() {\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n if (isSdkChildContext(data)) return;\n if (data.notification_type !== \"permission_prompt\") return;\n\n const sessionId = (data.session_id as string) || \"unknown\";\n\n try {\n await fetch(`${REST_URL}/agentmemory/observe`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({\n hookType: \"notification\",\n sessionId,\n project: data.cwd || process.cwd(),\n cwd: data.cwd || process.cwd(),\n timestamp: new Date().toISOString(),\n data: {\n notification_type: data.notification_type,\n title: data.title,\n message: data.message,\n },\n }),\n signal: AbortSignal.timeout(2000),\n });\n } catch {\n // fire and forget\n }\n}\n\nmain();\n"],"mappings":";;AAEA,SAAS,kBAAkB,SAA2B;AACpD,KAAI,QAAQ,IAAI,6BAA6B,IAAK,QAAO;AACzD,KAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAQ,QAAqC,eAAe;;AAG9D,MAAM,WAAW,QAAQ,IAAI,sBAAsB;AACnD,MAAM,SAAS,QAAQ,IAAI,yBAAyB;AAEpD,SAAS,cAAsC;CAC7C,MAAM,IAA4B,EAAE,gBAAgB,oBAAoB;AACxE,KAAI,OAAQ,GAAE,mBAAmB,UAAU;AAC3C,QAAO;;AAGT,eAAe,OAAO;CACpB,IAAI,QAAQ;AACZ,YAAW,MAAM,SAAS,QAAQ,MAChC,UAAS;CAGX,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,MAAM;SAClB;AACN;;AAGF,KAAI,kBAAkB,KAAK,CAAE;AAC7B,KAAI,KAAK,sBAAsB,oBAAqB;CAEpD,MAAM,YAAa,KAAK,cAAyB;AAEjD,KAAI;AACF,QAAM,MAAM,GAAG,SAAS,uBAAuB;GAC7C,QAAQ;GACR,SAAS,aAAa;GACtB,MAAM,KAAK,UAAU;IACnB,UAAU;IACV;IACA,SAAS,KAAK,OAAO,QAAQ,KAAK;IAClC,KAAK,KAAK,OAAO,QAAQ,KAAK;IAC9B,4BAAW,IAAI,MAAM,EAAC,aAAa;IACnC,MAAM;KACJ,mBAAmB,KAAK;KACxB,OAAO,KAAK;KACZ,SAAS,KAAK;KACf;IACF,CAAC;GACF,QAAQ,YAAY,QAAQ,IAAK;GAClC,CAAC;SACI;;AAKV,MAAM"}
@@ -1,5 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  //#region src/hooks/post-tool-failure.ts
3
+ function isSdkChildContext(payload) {
4
+ if (process.env["AGENTMEMORY_SDK_CHILD"] === "1") return true;
5
+ if (!payload || typeof payload !== "object") return false;
6
+ return payload.entrypoint === "sdk-ts";
7
+ }
3
8
  const REST_URL = process.env["AGENTMEMORY_URL"] || "http://localhost:3111";
4
9
  const SECRET = process.env["AGENTMEMORY_SECRET"] || "";
5
10
  function authHeaders() {
@@ -16,6 +21,7 @@ async function main() {
16
21
  } catch {
17
22
  return;
18
23
  }
24
+ if (isSdkChildContext(data)) return;
19
25
  if (data.is_interrupt) return;
20
26
  const sessionId = data.session_id || "unknown";
21
27
  try {
@@ -1 +1 @@
1
- {"version":3,"file":"post-tool-failure.mjs","names":[],"sources":["../../src/hooks/post-tool-failure.ts"],"sourcesContent":["#!/usr/bin/env node\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = process.env[\"AGENTMEMORY_SECRET\"] || \"\";\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function main() {\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n if (data.is_interrupt) return;\n\n const sessionId = (data.session_id as string) || \"unknown\";\n\n try {\n await fetch(`${REST_URL}/agentmemory/observe`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({\n hookType: \"post_tool_failure\",\n sessionId,\n project: data.cwd || process.cwd(),\n cwd: data.cwd || process.cwd(),\n timestamp: new Date().toISOString(),\n data: {\n tool_name: data.tool_name,\n tool_input:\n typeof data.tool_input === \"string\"\n ? data.tool_input.slice(0, 4000)\n : JSON.stringify(data.tool_input ?? \"\").slice(0, 4000),\n error:\n typeof data.error === \"string\"\n ? data.error.slice(0, 4000)\n : JSON.stringify(data.error ?? \"\").slice(0, 4000),\n },\n }),\n signal: AbortSignal.timeout(3000),\n });\n } catch {\n // fire and forget\n }\n}\n\nmain();\n"],"mappings":";;AAEA,MAAM,WAAW,QAAQ,IAAI,sBAAsB;AACnD,MAAM,SAAS,QAAQ,IAAI,yBAAyB;AAEpD,SAAS,cAAsC;CAC7C,MAAM,IAA4B,EAAE,gBAAgB,oBAAoB;AACxE,KAAI,OAAQ,GAAE,mBAAmB,UAAU;AAC3C,QAAO;;AAGT,eAAe,OAAO;CACpB,IAAI,QAAQ;AACZ,YAAW,MAAM,SAAS,QAAQ,MAChC,UAAS;CAGX,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,MAAM;SAClB;AACN;;AAGF,KAAI,KAAK,aAAc;CAEvB,MAAM,YAAa,KAAK,cAAyB;AAEjD,KAAI;AACF,QAAM,MAAM,GAAG,SAAS,uBAAuB;GAC7C,QAAQ;GACR,SAAS,aAAa;GACtB,MAAM,KAAK,UAAU;IACnB,UAAU;IACV;IACA,SAAS,KAAK,OAAO,QAAQ,KAAK;IAClC,KAAK,KAAK,OAAO,QAAQ,KAAK;IAC9B,4BAAW,IAAI,MAAM,EAAC,aAAa;IACnC,MAAM;KACJ,WAAW,KAAK;KAChB,YACE,OAAO,KAAK,eAAe,WACvB,KAAK,WAAW,MAAM,GAAG,IAAK,GAC9B,KAAK,UAAU,KAAK,cAAc,GAAG,CAAC,MAAM,GAAG,IAAK;KAC1D,OACE,OAAO,KAAK,UAAU,WAClB,KAAK,MAAM,MAAM,GAAG,IAAK,GACzB,KAAK,UAAU,KAAK,SAAS,GAAG,CAAC,MAAM,GAAG,IAAK;KACtD;IACF,CAAC;GACF,QAAQ,YAAY,QAAQ,IAAK;GAClC,CAAC;SACI;;AAKV,MAAM"}
1
+ {"version":3,"file":"post-tool-failure.mjs","names":[],"sources":["../../src/hooks/post-tool-failure.ts"],"sourcesContent":["#!/usr/bin/env node\n\nfunction isSdkChildContext(payload: unknown): boolean {\n if (process.env[\"AGENTMEMORY_SDK_CHILD\"] === \"1\") return true;\n if (!payload || typeof payload !== \"object\") return false;\n return (payload as { entrypoint?: unknown }).entrypoint === \"sdk-ts\";\n}\n\nconst REST_URL = process.env[\"AGENTMEMORY_URL\"] || \"http://localhost:3111\";\nconst SECRET = process.env[\"AGENTMEMORY_SECRET\"] || \"\";\n\nfunction authHeaders(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (SECRET) h[\"Authorization\"] = `Bearer ${SECRET}`;\n return h;\n}\n\nasync function main() {\n let input = \"\";\n for await (const chunk of process.stdin) {\n input += chunk;\n }\n\n let data: Record<string, unknown>;\n try {\n data = JSON.parse(input);\n } catch {\n return;\n }\n\n if (isSdkChildContext(data)) return;\n if (data.is_interrupt) return;\n\n const sessionId = (data.session_id as string) || \"unknown\";\n\n try {\n await fetch(`${REST_URL}/agentmemory/observe`, {\n method: \"POST\",\n headers: authHeaders(),\n body: JSON.stringify({\n hookType: \"post_tool_failure\",\n sessionId,\n project: data.cwd || process.cwd(),\n cwd: data.cwd || process.cwd(),\n timestamp: new Date().toISOString(),\n data: {\n tool_name: data.tool_name,\n tool_input:\n typeof data.tool_input === \"string\"\n ? data.tool_input.slice(0, 4000)\n : JSON.stringify(data.tool_input ?? \"\").slice(0, 4000),\n error:\n typeof data.error === \"string\"\n ? data.error.slice(0, 4000)\n : JSON.stringify(data.error ?? \"\").slice(0, 4000),\n },\n }),\n signal: AbortSignal.timeout(3000),\n });\n } catch {\n // fire and forget\n }\n}\n\nmain();\n"],"mappings":";;AAEA,SAAS,kBAAkB,SAA2B;AACpD,KAAI,QAAQ,IAAI,6BAA6B,IAAK,QAAO;AACzD,KAAI,CAAC,WAAW,OAAO,YAAY,SAAU,QAAO;AACpD,QAAQ,QAAqC,eAAe;;AAG9D,MAAM,WAAW,QAAQ,IAAI,sBAAsB;AACnD,MAAM,SAAS,QAAQ,IAAI,yBAAyB;AAEpD,SAAS,cAAsC;CAC7C,MAAM,IAA4B,EAAE,gBAAgB,oBAAoB;AACxE,KAAI,OAAQ,GAAE,mBAAmB,UAAU;AAC3C,QAAO;;AAGT,eAAe,OAAO;CACpB,IAAI,QAAQ;AACZ,YAAW,MAAM,SAAS,QAAQ,MAChC,UAAS;CAGX,IAAI;AACJ,KAAI;AACF,SAAO,KAAK,MAAM,MAAM;SAClB;AACN;;AAGF,KAAI,kBAAkB,KAAK,CAAE;AAC7B,KAAI,KAAK,aAAc;CAEvB,MAAM,YAAa,KAAK,cAAyB;AAEjD,KAAI;AACF,QAAM,MAAM,GAAG,SAAS,uBAAuB;GAC7C,QAAQ;GACR,SAAS,aAAa;GACtB,MAAM,KAAK,UAAU;IACnB,UAAU;IACV;IACA,SAAS,KAAK,OAAO,QAAQ,KAAK;IAClC,KAAK,KAAK,OAAO,QAAQ,KAAK;IAC9B,4BAAW,IAAI,MAAM,EAAC,aAAa;IACnC,MAAM;KACJ,WAAW,KAAK;KAChB,YACE,OAAO,KAAK,eAAe,WACvB,KAAK,WAAW,MAAM,GAAG,IAAK,GAC9B,KAAK,UAAU,KAAK,cAAc,GAAG,CAAC,MAAM,GAAG,IAAK;KAC1D,OACE,OAAO,KAAK,UAAU,WAClB,KAAK,MAAM,MAAM,GAAG,IAAK,GACzB,KAAK,UAAU,KAAK,SAAS,GAAG,CAAC,MAAM,GAAG,IAAK;KACtD;IACF,CAAC;GACF,QAAQ,YAAY,QAAQ,IAAK;GAClC,CAAC;SACI;;AAKV,MAAM"}