nlm-memory 0.5.22 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +106 -14
- package/dist/cli/classify-parity.js +1 -1
- package/dist/cli/classify-parity.js.map +1 -1
- package/dist/cli/nlm.js +152 -12
- package/dist/cli/nlm.js.map +1 -1
- package/dist/core/adapters/pi.js +13 -3
- package/dist/core/adapters/pi.js.map +1 -1
- package/dist/core/classifier/prompt.d.ts +1 -1
- package/dist/core/classifier/prompt.js +1 -1
- package/dist/core/hook/pointer-block.d.ts +12 -1
- package/dist/core/hook/pointer-block.js +25 -8
- package/dist/core/hook/pointer-block.js.map +1 -1
- package/dist/core/providers/provider-registry.js +1 -1
- package/dist/core/providers/provider-registry.js.map +1 -1
- package/dist/core/recall/recall-service.d.ts +9 -0
- package/dist/core/recall/recall-service.js +81 -18
- package/dist/core/recall/recall-service.js.map +1 -1
- package/dist/core/recall-facts/fact-recall-service.d.ts +11 -0
- package/dist/core/recall-facts/fact-recall-service.js +69 -4
- package/dist/core/recall-facts/fact-recall-service.js.map +1 -1
- package/dist/core/scheduler/scheduler.d.ts +7 -0
- package/dist/core/scheduler/scheduler.js +26 -0
- package/dist/core/scheduler/scheduler.js.map +1 -1
- package/dist/core/signals/aggregate.d.ts +21 -0
- package/dist/core/signals/aggregate.js +34 -0
- package/dist/core/signals/aggregate.js.map +1 -0
- package/dist/core/signals/failure-mode-recall.d.ts +18 -0
- package/dist/core/signals/failure-mode-recall.js +28 -0
- package/dist/core/signals/failure-mode-recall.js.map +1 -0
- package/dist/core/signals/ingest-signal.d.ts +19 -0
- package/dist/core/signals/ingest-signal.js +63 -0
- package/dist/core/signals/ingest-signal.js.map +1 -0
- package/dist/core/signals/install-scope.d.ts +8 -0
- package/dist/core/signals/install-scope.js +41 -0
- package/dist/core/signals/install-scope.js.map +1 -0
- package/dist/core/signals/recommend.d.ts +13 -0
- package/dist/core/signals/recommend.js +24 -0
- package/dist/core/signals/recommend.js.map +1 -0
- package/dist/core/storage/pg-fact-store.d.ts +5 -0
- package/dist/core/storage/pg-fact-store.js +29 -0
- package/dist/core/storage/pg-fact-store.js.map +1 -1
- package/dist/core/storage/pg-signal-store.d.ts +16 -0
- package/dist/core/storage/pg-signal-store.js +100 -0
- package/dist/core/storage/pg-signal-store.js.map +1 -0
- package/dist/core/storage/pg-storage.d.ts +2 -0
- package/dist/core/storage/pg-storage.js +3 -0
- package/dist/core/storage/pg-storage.js.map +1 -1
- package/dist/core/storage/pg-tx-context.d.ts +5 -0
- package/dist/core/storage/pg-tx-context.js +3 -0
- package/dist/core/storage/pg-tx-context.js.map +1 -1
- package/dist/core/storage/sqlite-fact-store.d.ts +5 -0
- package/dist/core/storage/sqlite-fact-store.js +30 -0
- package/dist/core/storage/sqlite-fact-store.js.map +1 -1
- package/dist/core/storage/sqlite-signal-store.d.ts +20 -0
- package/dist/core/storage/sqlite-signal-store.js +112 -0
- package/dist/core/storage/sqlite-signal-store.js.map +1 -0
- package/dist/core/storage/sqlite-storage.d.ts +2 -0
- package/dist/core/storage/sqlite-storage.js +6 -2
- package/dist/core/storage/sqlite-storage.js.map +1 -1
- package/dist/hook/prompt-recall-hook.d.ts +10 -1
- package/dist/hook/prompt-recall-hook.js +10 -4
- package/dist/hook/prompt-recall-hook.js.map +1 -1
- package/dist/hook/recall-over-http.d.ts +11 -1
- package/dist/hook/recall-over-http.js +16 -4
- package/dist/hook/recall-over-http.js.map +1 -1
- package/dist/hook/session-start-hook.d.ts +2 -0
- package/dist/hook/session-start-hook.js +32 -2
- package/dist/hook/session-start-hook.js.map +1 -1
- package/dist/hook/stop-hook.js +16 -0
- package/dist/hook/stop-hook.js.map +1 -1
- package/dist/http/app.d.ts +9 -0
- package/dist/http/app.js +124 -11
- package/dist/http/app.js.map +1 -1
- package/dist/install/codex.d.ts +1 -1
- package/dist/install/codex.js +4 -4
- package/dist/install/codex.js.map +1 -1
- package/dist/install/ollama.d.ts +10 -0
- package/dist/install/ollama.js +19 -3
- package/dist/install/ollama.js.map +1 -1
- package/dist/install/pi.d.ts +1 -1
- package/dist/install/pi.js +1 -1
- package/dist/install/setup.js +63 -10
- package/dist/install/setup.js.map +1 -1
- package/dist/llm/classifier-box.d.ts +2 -1
- package/dist/llm/classifier-box.js +3 -0
- package/dist/llm/classifier-box.js.map +1 -1
- package/dist/llm/deepseek-client.d.ts +5 -2
- package/dist/llm/deepseek-client.js +51 -1
- package/dist/llm/deepseek-client.js.map +1 -1
- package/dist/llm/ollama-client.d.ts +3 -2
- package/dist/llm/ollama-client.js +45 -2
- package/dist/llm/ollama-client.js.map +1 -1
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +22 -3
- package/dist/mcp/server.js.map +1 -1
- package/dist/ports/fact-store.d.ts +0 -0
- package/dist/ports/fact-store.js +0 -0
- package/dist/ports/fact-store.js.map +1 -1
- package/dist/ports/llm-client.d.ts +24 -0
- package/dist/ports/signal-store.d.ts +30 -0
- package/dist/ports/signal-store.js +9 -0
- package/dist/ports/signal-store.js.map +1 -0
- package/dist/ports/storage.d.ts +2 -0
- package/dist/ports/transcript-adapter.d.ts +6 -0
- package/dist/shared/types.d.ts +76 -0
- package/dist/ui/assets/index-BTj60bAH.js +11 -0
- package/dist/ui/assets/index-Baw2qIKc.css +1 -0
- package/dist/ui/index.html +2 -2
- package/nlm/README.md +1 -1
- package/nlm/index.js +40 -17
- package/package.json +19 -18
- package/plugin/.codex-plugin/plugin.json +2 -2
- package/plugin/README.md +3 -3
- package/plugin/scripts/prompt-recall-hook.mjs +40 -17
- package/plugin/scripts/stop-hook.mjs +80 -6
- package/dist/ui/assets/index-BR1BHwew.js +0 -69
- package/dist/ui/assets/index-CgHq6xeZ.css +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
:root{--surface-0:#161616;--surface-1:#1d1d1d;--surface-2:#242424;--surface-float:#2c2c2c;--bg-1:var(--surface-float);--bg-2:var(--surface-2);--border-1:#262626;--border-2:#303030;--border-3:#3d3d3d;--border-4:#525252;--text-1:#e8e8e8;--text-2:#b0b0b0;--text-3:#686868;--text-4:#383838;--accent:#e8ff6e;--accent-dim:#e8ff6e1f;--accent-glow:#e8ff6e0f;--warn:#f93;--warn-dim:#ff99331f;--danger:#ff6b35;--danger-dim:#ff6b3512;--danger-glow:#ff6b351f;--font-mono:"IBM Plex Mono", "Fira Code", ui-monospace, monospace;--text-xs:10px;--text-sm:11px;--text-base:13px;--text-md:15px;--text-lg:18px;--text-xl:22px;--text-stat:36px;--lh-tight:1.3;--lh-base:1.5;--lh-loose:1.7;--page-x:28px;--section-gap:24px;--r-sm:3px;--r-md:5px;--r-lg:7px;--ease-fast:.1s ease;--ease:.15s ease;--ease-slow:.3s ease;--z-base:0;--z-raised:1;--z-sticky:10;--z-sidenav:20;--z-dropdown:30;--z-overlay:40;--z-drawer-bg:50;--z-drawer:60;--z-palette-bg:70;--z-palette:80;--z-tooltip:90;--z-toast:100}*,:before,:after{box-sizing:border-box;margin:0;padding:0}html{background-color:var(--surface-0)}body{color:var(--text-1);font-family:var(--font-mono);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;background-image:radial-gradient(100% 40% at 50% -8%,#e8ff6e08 0%,#0000 100%);background-attachment:fixed;min-height:100vh}::-webkit-scrollbar{width:4px;height:4px}::-webkit-scrollbar-track{background:var(--surface-1)}::-webkit-scrollbar-thumb{background:var(--border-3);border-radius:2px}@keyframes fadeSlideIn{0%{opacity:0;transform:translateY(6px)}to{opacity:1;transform:translateY(0)}}@media (prefers-reduced-motion:reduce){*,:before,:after{transition-duration:.01ms!important;animation-duration:.01ms!important;animation-delay:0s!important}}.skeleton{background:linear-gradient(90deg, var(--surface-1) 0%, var(--surface-2) 50%, var(--surface-1) 100%);vertical-align:middle;background-size:200% 100%;animation:1.2s ease-in-out infinite skeleton-shimmer;display:inline-block}@keyframes skeleton-shimmer{0%{background-position:200% 0}to{background-position:-200% 0}}@media (prefers-reduced-motion:reduce){.skeleton{background:var(--surface-2);animation:none}}.card{background:var(--surface-1);border:1px solid var(--border-2);border-radius:var(--r-md)}.card-lift{transition:border-color var(--ease), background var(--ease)}.card-lift:hover{background:var(--surface-2);border-color:var(--border-3)}.btn{font-family:var(--font-mono);font-size:var(--text-sm);letter-spacing:.06em;text-transform:uppercase;border-radius:var(--r-sm);border:1px solid var(--border-3);color:var(--text-2);cursor:pointer;white-space:nowrap;-webkit-user-select:none;user-select:none;transition:border-color var(--ease), color var(--ease), background var(--ease);background:0 0;align-items:center;gap:5px;padding:5px 12px;text-decoration:none;display:inline-flex}.btn:hover{border-color:var(--border-4);color:var(--text-1)}.btn:active{background:var(--surface-2)}.btn.active{background:var(--surface-2);border-color:var(--border-3);color:var(--text-1)}.btn-primary{background:var(--accent);border-color:var(--accent);color:#080808;font-weight:600}.btn-primary:hover{color:#080808;background:#d4eb5e;border-color:#d4eb5e}.btn-accent{color:var(--accent);border-color:#e8ff6e40}.btn-accent:hover{background:var(--accent-glow);border-color:#e8ff6e66}.btn-danger:hover{border-color:var(--danger);color:var(--danger)}.btn.small{font-size:var(--text-xs);padding:3px 9px}.form-row{flex-wrap:wrap;align-items:center;gap:12px;margin:12px 0;display:flex}.form-row.between{justify-content:space-between}.form-row.tight{gap:8px}.form-row .form-label{color:var(--text-3);font-size:var(--text-xs)}input[type=file].file-input{font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-3);max-width:24rem}input[type=file].file-input::file-selector-button{font-family:var(--font-mono);font-size:var(--text-sm);letter-spacing:.06em;text-transform:uppercase;border-radius:var(--r-sm);border:1px solid var(--border-3);color:var(--text-2);cursor:pointer;transition:border-color var(--ease), color var(--ease), background var(--ease);background:0 0;align-items:center;margin-right:10px;padding:5px 12px;display:inline-flex}input[type=file].file-input::file-selector-button:hover{border-color:var(--border-4);color:var(--text-1)}input[type=file].file-input:disabled{cursor:not-allowed}input[type=file].file-input:disabled::file-selector-button{opacity:.5;cursor:not-allowed}.chip{font-family:var(--font-mono);font-size:var(--text-sm);letter-spacing:.06em;text-transform:lowercase;border-radius:var(--r-sm);border:1px solid var(--border-1);color:var(--text-3);cursor:pointer;-webkit-user-select:none;user-select:none;white-space:nowrap;transition:border-color var(--ease), color var(--ease), background var(--ease);background:0 0;align-items:center;gap:4px;padding:3px 9px;display:inline-flex}.chip:hover{border-color:var(--border-3);color:var(--text-2)}.chip.active{border-color:var(--border-3);color:var(--text-1);background:var(--surface-2);box-shadow:inset 0 0 0 1px var(--border-3);font-weight:600}.chip[data-severity=critical].active{color:var(--danger);background:var(--danger-dim);border-color:#ff6b3566}.chip[data-severity=high].active{color:#c42;background:#cc44220f;border-color:#cc442259}.chip[data-severity=medium].active{color:var(--warn);background:var(--warn-dim);border-color:#ff993359}.chip[data-severity=low].active{border-color:var(--border-3);color:var(--text-2);background:var(--surface-2)}.chip[data-status=active].active{color:var(--accent);background:var(--accent-glow);border-color:#e8ff6e4d}.chip[data-status=idle].active{color:#ffcc80}.chip[data-length].active{color:var(--text-1)}.chip[data-marker].active{color:var(--accent);background:var(--accent-glow);border-color:#e8ff6e4d}.panel-float{-webkit-backdrop-filter:blur(18px);border:1px solid var(--border-3);border-radius:var(--r-md);background:#101010f5;box-shadow:0 12px 40px #000000a6,0 0 0 1px #ffffff08}.nav-link{color:var(--text-3);border-radius:var(--r-sm);font-size:var(--text-sm);font-family:var(--font-mono);letter-spacing:.06em;text-transform:uppercase;transition:color var(--ease), border-color var(--ease), background var(--ease);background:0 0;border:1px solid #0000;align-items:center;padding:4px 10px;text-decoration:none;display:inline-flex}.nav-link:hover{color:var(--text-2);border-color:var(--border-3)}.nav-link.active{background:var(--surface-2);border-color:var(--border-3);color:var(--text-1)}.search-input{background:var(--surface-1);border:1px solid var(--border-2);color:var(--text-1);font-family:var(--font-mono);font-size:var(--text-xs);border-radius:var(--r-sm);letter-spacing:.04em;transition:border-color var(--ease);outline:none;padding:5px 10px}.search-input:focus{border-color:var(--border-3)}.search-input::placeholder{color:var(--text-4);letter-spacing:.06em}.ctrl-btn{background:var(--surface-1);border:1px solid var(--border-2);color:var(--text-2);font-family:var(--font-mono);font-size:var(--text-sm);letter-spacing:.06em;text-transform:uppercase;border-radius:var(--r-sm);cursor:pointer;white-space:nowrap;transition:border-color var(--ease), color var(--ease), background var(--ease);padding:4px 10px}.ctrl-btn:hover{border-color:var(--border-3);color:var(--text-1)}.ctrl-btn.active{background:var(--surface-2);border-color:var(--border-3);color:var(--accent)}.ctrl-btn[data-active="1"]{color:var(--accent);border-color:#e8ff6e40}.ctrl-label{font-size:var(--text-sm);color:var(--text-3);letter-spacing:.06em;text-transform:uppercase;flex-shrink:0}.subnav{border-bottom:1px solid var(--border-1);gap:0;padding:0 28px;display:flex}.subnav-link{font-size:var(--text-sm);font-family:var(--font-mono);letter-spacing:.06em;text-transform:uppercase;color:var(--text-3);transition:color var(--ease), border-color var(--ease);border-bottom:2px solid #0000;margin-bottom:-1px;padding:10px 14px;text-decoration:none}.subnav-link:hover{color:var(--text-2)}.subnav-link.active{color:var(--accent);border-bottom-color:var(--accent)}.page-shell{background:var(--surface-0);height:100vh;display:flex;overflow:hidden}.page-main{flex-direction:column;flex:1;min-width:0;min-height:0;display:flex}.app-header{z-index:var(--z-sticky);height:52px;padding:0 var(--page-x);-webkit-backdrop-filter:blur(12px);border-bottom:1px solid var(--border-1);background:#161616e6;align-items:center;gap:20px;display:flex;position:sticky;top:0}.app-header .wordmark-page{font-size:var(--text-sm);color:var(--text-3);letter-spacing:.04em;text-transform:lowercase}.app-header .header-spacer{flex:1}.app-header .status-dot{background:var(--accent);width:7px;height:7px;box-shadow:0 0 6px var(--accent-dim);border-radius:50%;animation:2s ease-in-out infinite pulse-dot}@keyframes pulse-dot{0%,to{opacity:1}50%{opacity:.4}}.app-header .header-info{font-size:var(--text-xs);color:var(--text-3);letter-spacing:.04em}.sidenav{background:var(--surface-1);border-right:1px solid var(--border-1);width:180px;height:100vh;transition:width var(--ease);z-index:var(--z-sidenav);flex-direction:column;flex-shrink:0;display:flex;position:sticky;top:0;overflow:hidden}.sidenav.collapsed{width:52px}.sidenav-header{border-bottom:1px solid var(--border-1);flex-shrink:0;justify-content:space-between;align-items:center;height:52px;padding:0 8px 0 16px;display:flex}.sidenav-wordmark{font-size:var(--text-sm);font-family:var(--font-mono);letter-spacing:.2em;text-transform:uppercase;color:var(--accent);-webkit-user-select:none;user-select:none;white-space:nowrap;transition:opacity var(--ease-fast), width var(--ease-fast);overflow:hidden}.sidenav.collapsed .sidenav-wordmark{opacity:0;width:0;padding:0}.sidenav-toggle{color:var(--text-3);cursor:pointer;border-radius:var(--r-sm);transition:color var(--ease), background var(--ease);background:0 0;border:none;flex-shrink:0;justify-content:center;align-items:center;padding:5px;display:flex}.sidenav-toggle:hover{color:var(--text-1);background:var(--surface-2)}.sidenav.collapsed .sidenav-header{justify-content:center;padding:0 8px}.sidenav-items{flex-direction:column;flex:1;gap:2px;padding:10px 8px;display:flex;overflow:hidden}.sidenav-item{border-radius:var(--r-sm);font-size:var(--text-sm);font-family:var(--font-mono);letter-spacing:.06em;text-transform:uppercase;color:var(--text-3);transition:color var(--ease), background var(--ease), border-color var(--ease);-webkit-user-select:none;user-select:none;white-space:nowrap;border-left:3px solid #0000;align-items:center;gap:9px;padding:8px 10px;text-decoration:none;display:flex;position:relative;overflow:hidden}.sidenav-item:hover{color:var(--text-1);background:var(--surface-2)}.sidenav-item.active{color:var(--text-1);border-left-color:var(--accent);background:var(--accent-glow)}.item-icon{opacity:.7;flex-shrink:0;align-items:center;display:flex}.sidenav-item.active .item-icon{opacity:1}.item-label{transition:opacity var(--ease-fast)}.sidenav.collapsed .item-label{display:none}.sidenav.collapsed .sidenav-item{border-left:3px solid #0000;justify-content:center;gap:0;padding:8px}.sidenav.collapsed .sidenav-item.active{border-left-color:var(--accent)}.sidenav.collapsed .sidenav-item:after{content:attr(data-label);background:var(--surface-float);color:var(--text-1);font-size:var(--text-xs);font-family:var(--font-mono);letter-spacing:.06em;text-transform:uppercase;border-radius:var(--r-sm);border:1px solid var(--border-2);white-space:nowrap;pointer-events:none;opacity:0;transition:opacity var(--ease-fast);z-index:var(--z-tooltip);padding:4px 8px;position:absolute;top:50%;left:calc(100% + 8px);transform:translateY(-50%)}.sidenav.collapsed .sidenav-item:hover:after{opacity:1}.sidenav-footer{border-top:1px solid var(--border-1);flex-shrink:0;padding:8px}.sidenav-data{font-size:var(--text-xs)}.update-banner{background:var(--bg-2);border:1px solid var(--border-1);font-family:var(--font-sans);border-radius:6px;margin-bottom:8px;padding:8px 10px}.update-banner-head{justify-content:space-between;align-items:center;gap:8px;display:flex}.update-banner-version{color:var(--text-1)}.update-banner-dismiss{color:var(--text-3);cursor:pointer;background:0 0;border:none;padding:0 4px;font-size:16px;line-height:1}.update-banner-dismiss:hover{color:var(--text-1)}.update-banner-body{color:var(--text-2,var(--text-1));margin:4px 0 6px}.update-banner-cmd{background:var(--bg-1);border:1px solid var(--border-1);color:var(--text-1);white-space:nowrap;border-radius:4px;margin-bottom:6px;padding:4px 6px;overflow-x:auto}.update-banner-actions{justify-content:space-between;align-items:center;gap:8px;display:flex}.update-banner-copy{background:var(--accent);color:var(--bg-1);cursor:pointer;font-family:var(--font-mono);font-size:var(--text-xs);border:none;border-radius:4px;padding:4px 8px}.update-banner-link{color:var(--text-3);text-underline-offset:2px;text-decoration:underline}.update-banner-link:hover{color:var(--text-1)}.update-banner-nudge{background:var(--bg-1);border-left:2px solid var(--accent);color:var(--text-2,var(--text-1));border-radius:3px;margin:8px 0 0;padding:6px 8px}.update-banner-nudge code{background:var(--bg-0,var(--bg-1));border-radius:3px;padding:1px 4px}.update-banner--collapsed{cursor:pointer;background:0 0;border:none;justify-content:center;align-items:center;width:100%;margin-bottom:8px;padding:6px 0;display:flex}.update-dot{color:var(--accent);font-size:14px;line-height:1;animation:2.6s ease-in-out infinite nlm-update-pulse}@keyframes nlm-update-pulse{0%,to{opacity:.45}50%{opacity:1}}.live-page{flex-direction:column;flex:1;min-height:0;display:flex}.live-status{padding:12px var(--page-x) 4px;align-items:center;gap:8px;display:flex}.live-status-dot{border-radius:50%;flex-shrink:0;width:7px;height:7px}.live-status-live{background:var(--accent);box-shadow:0 0 6px var(--accent)}.live-status-connecting{background:var(--text-3)}.live-status-reconnecting{background:var(--warn);animation:1.2s ease-in-out infinite pulse-dot}.live-status-label{font-family:var(--font-mono);font-size:var(--text-sm);letter-spacing:.06em;text-transform:uppercase;color:var(--text-2)}.live-board{padding:4px var(--page-x) 20px;flex:1;grid-template-columns:repeat(3,1fr);gap:16px;min-height:0;display:grid}.live-col{background:var(--surface-1);border:1px solid var(--border-2);border-radius:var(--r-md);flex-direction:column;min-height:0;display:flex;overflow:hidden}.live-col-head{border-bottom:1px solid var(--border-1);justify-content:space-between;align-items:baseline;padding:10px 14px;display:flex}.live-col-title{font-size:var(--text-sm);font-family:var(--font-mono);letter-spacing:.06em;text-transform:uppercase;color:var(--text-2)}.live-col-count{font-size:var(--text-xs);color:var(--text-3);font-family:var(--font-mono)}.live-col-body{flex:1;overflow:hidden auto}.live-row{border-bottom:1px solid var(--border-1);overflow-wrap:anywhere;min-width:0;padding:8px 14px;animation:.2s ease-out fadeSlideIn}.live-row .label,.live-row .body,.live-row .meta{overflow-wrap:anywhere;word-break:break-word;min-width:0}.live-row:last-child{border-bottom:none}.clickable:focus-visible,.bar-item-clickable:focus-visible,.kpi-clickable:focus-visible{outline:1px solid var(--accent);outline-offset:-1px}.clickable[aria-busy=true],.clickable.is-disabled,.bar-item-clickable[aria-busy=true],.bar-item-clickable.is-disabled{opacity:.5;pointer-events:none}.live-row.clickable{cursor:pointer;transition:background var(--ease)}.live-row.clickable:hover{background:var(--surface-2)}.live-row.is-new{animation:.2s ease-out fadeSlideIn,1.2s ease-out liveFlash}.live-row.is-related{background:var(--accent-glow);border-left:2px solid var(--accent);padding-left:12px}@keyframes liveFlash{0%{background:var(--accent-glow)}to{background:0 0}}.live-row .label{color:var(--text-1);font-size:var(--text-base);line-height:var(--lh-tight)}.live-row .body{color:var(--text-3);font-size:var(--text-sm);line-height:var(--lh-tight);margin-top:3px}.live-row .meta{color:var(--text-3);font-size:var(--text-xs);letter-spacing:.04em;margin-top:4px}.live-tag{font-size:var(--text-xs);font-family:var(--font-mono);letter-spacing:.06em;text-transform:uppercase;border-radius:var(--r-sm);background:var(--surface-2);color:var(--text-2);border:1px solid var(--border-2);margin-right:6px;padding:1px 6px;display:inline-block}.live-tag[data-kind=decision]{background:var(--accent-glow);color:var(--accent);border-color:#e8ff6e40}.live-tag[data-kind=open]{background:var(--warn-dim);color:var(--warn);border-color:#ff993340}.live-empty{text-align:center;color:var(--text-3);font-size:var(--text-xs);letter-spacing:.06em;text-transform:uppercase;padding:32px 16px}.stub-shell{padding:80px var(--page-x);text-align:center;color:var(--text-3);font-size:var(--text-base);line-height:var(--lh-loose)}.stub-shell .stub-name{color:var(--accent);font-size:var(--text-md);text-transform:lowercase;letter-spacing:.04em;margin-bottom:12px;display:block}.stub-shell .stub-hint{color:var(--text-4);font-size:var(--text-sm)}.page-pad{padding:20px var(--page-x) 60px;flex:1;min-width:0;min-height:0;overflow-y:auto}.page-title{font-size:var(--text-lg);font-family:var(--font-mono);color:var(--text-1);margin-bottom:16px;font-weight:500}.page-header{align-items:center;gap:24px;margin-bottom:24px;padding-top:8px;display:flex}.subnav+.page-header,.subnav+.page-title{gap:28px;margin-top:28px;margin-bottom:28px;padding-top:0}.page-header .page-title,.form-row.between .page-title{margin-bottom:0}.page-header .header-spacer,.thread-header .header-spacer,.river-toolbar .header-spacer{flex:1}.section-title{font-size:var(--text-sm);text-transform:uppercase;letter-spacing:.06em;color:var(--text-3);margin:24px 0 8px;font-weight:500}.muted{color:var(--text-3)}.muted.small{font-size:var(--text-xs);letter-spacing:.04em}.muted.error{color:var(--danger)}.right{text-align:right}.mono{font-family:var(--font-mono)}.small{font-size:var(--text-xs)}code,.code-block{font-family:var(--font-mono);font-size:var(--text-sm);background:var(--surface-2);border:1px solid var(--border-2);border-radius:var(--r-sm);color:var(--text-1);padding:2px 6px}.code-block{margin:8px 0;padding:10px 14px;display:block}.settings-grid{grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:12px;margin-top:16px;display:grid}.settings-card{color:var(--text-1);flex-direction:column;gap:6px;padding:14px;text-decoration:none;display:flex}.settings-card-title{font-size:var(--text-sm);letter-spacing:.06em;text-transform:uppercase;color:var(--accent);font-weight:500}.settings-card-body{color:var(--text-2);font-size:var(--text-sm)}.kv-list{grid-template-columns:max-content 1fr;gap:6px 16px;margin-top:12px;display:grid}.kv-label{color:var(--text-3);font-size:var(--text-sm);letter-spacing:.04em}.kv-value{color:var(--text-1);font-size:var(--text-sm)}.kv-value.mono{font-family:var(--font-mono);word-break:break-all}.form-grid{gap:12px;max-width:420px;margin-top:12px;display:grid}.form-field{flex-direction:column;gap:4px;display:flex}.form-label{font-size:var(--text-xs);color:var(--text-3);letter-spacing:.06em;text-transform:uppercase}.form-input{background:var(--surface-1);border:1px solid var(--border-2);color:var(--text-1);font-family:var(--font-mono);font-size:var(--text-sm);border-radius:var(--r-sm);transition:border-color var(--ease);padding:6px 10px}.form-input:hover:not(:disabled){border-color:var(--border-3)}.form-input:focus{border-color:var(--border-4);outline:none}.form-input:focus-visible{outline:1px solid var(--accent);outline-offset:-1px}.form-input:disabled,.form-input.is-disabled{opacity:.5;cursor:not-allowed}.form-input[aria-invalid=true],.form-input.is-invalid{border-color:var(--danger)}.form-input[aria-invalid=true]:focus-visible,.form-input.is-invalid:focus-visible{outline-color:var(--danger);border-color:var(--danger)}.form-error{color:var(--danger);font-size:var(--text-xs);margin:4px 0 0}.data-table{border-collapse:collapse;width:100%;font-size:var(--text-sm);margin-top:12px}.data-table th{text-align:left;font-weight:500;font-size:var(--text-xs);letter-spacing:.06em;text-transform:uppercase;color:var(--text-3);border-bottom:1px solid var(--border-2);padding:8px 10px}.data-table th.right{text-align:right}.data-table td{border-bottom:1px solid var(--border-1);color:var(--text-1);vertical-align:middle;padding:7px 10px}.data-table tr:hover td{background:var(--surface-2)}.canonical .dot{vertical-align:middle;margin-right:8px}.topic-name{align-items:baseline;gap:8px;display:inline-flex}.topic-name .muted.small.mono{font-size:var(--text-xs);opacity:.7}.dot{border-radius:50%;flex-shrink:0;width:8px;height:8px;display:inline-block}.dot.lg{width:14px;height:14px}.chip-inline{border-radius:var(--r-sm);border:1px solid var(--border-2);background:var(--surface-2);color:var(--text-2);font-family:var(--font-mono);font-size:var(--text-xs);letter-spacing:.04em;text-transform:lowercase;align-items:center;gap:4px;padding:2px 8px;display:inline-flex}.chip-inline.status-active{color:var(--accent);background:var(--accent-glow);border-color:#e8ff6e40}.chip-inline.status-idle{color:#ffcc80;border-color:#ffcc8040}.chip-inline.status-closed{color:var(--text-2)}.chip-inline.status-superseded{color:var(--text-3);text-decoration:line-through}.chip-inline.status-retired{color:var(--danger);background:var(--danger-dim);border-color:#ff6b3540}.chip-inline.status-stale{color:var(--warn);background:var(--warn-dim);border-color:#ff993340}.chip-inline.severity-high{color:var(--danger);background:var(--danger-dim);border-color:#ff6b3559}.chip-inline.severity-medium{color:var(--warn);background:var(--warn-dim);border-color:#ff993359}.chip-inline.severity-low{color:var(--text-2);border-color:var(--border-3);background:var(--surface-2)}.chip-x{color:inherit;cursor:pointer;background:0 0;border:none;padding:0 0 0 4px;font-size:12px;line-height:1}.kpi-row{grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin-bottom:18px;display:grid}.kpi{background:var(--surface-1);border:1px solid var(--border-2);border-radius:var(--r-md);flex-direction:column;gap:4px;padding:14px 16px;display:flex}.kpi-label{font-size:var(--text-xs);letter-spacing:.06em;text-transform:uppercase;color:var(--text-3)}.kpi-value{font-size:var(--text-stat);font-family:var(--font-mono);color:var(--text-1);line-height:1}.kpi-hint{font-size:var(--text-xs);color:var(--text-3)}.kpi-sparkline{gap:8px}.sparkline{align-items:flex-end;gap:4px;height:40px;display:flex}.spark-bar{background:var(--accent);opacity:.7;border-radius:1px;flex:1;min-height:2px}.pulse-grid{grid-template:"coherence recent stale"1fr"runtimes recent stale"1fr/1fr 1fr 1fr;gap:12px;height:calc(100vh - 280px);min-height:360px;margin-top:6px;display:grid}.pulse-area-coherence{grid-area:coherence}.pulse-area-runtimes{grid-area:runtimes}.pulse-area-recent{grid-area:recent}.pulse-area-stale{grid-area:stale}.pulse-scroll-card{flex-direction:column;min-height:0;display:flex;overflow:hidden}.pulse-scroll-card .card-head{flex-shrink:0}.kpi,.pulse-grid>.card{transition:outline-color var(--ease), border-color var(--ease);outline-offset:-1px;outline:1px solid #0000}.kpi:hover{outline-color:#ffffff8c}.kpi-clickable{cursor:pointer;color:inherit;text-decoration:none}.kpi-clickable:hover .kpi-value{color:var(--accent)}.kpi-clickable:hover{border-color:var(--border-3)}.pulse-grid>.pulse-scroll-card:hover{outline-color:#ffffff8c}.pulse-scroll-body{flex:1;min-height:0;overflow:hidden auto}.card-head{border-bottom:1px solid var(--border-1);justify-content:space-between;align-items:baseline;padding:10px 14px;display:flex}.card-head h3{font-size:var(--text-sm);letter-spacing:.06em;text-transform:uppercase;color:var(--text-2);font-weight:500}.bar-stack{flex-direction:column;padding:4px 0;display:flex}.bar-item{border:0;border-bottom:1px solid var(--border-1);text-align:left;width:100%;color:inherit;font:inherit;background:0 0;grid-template-columns:80px 1fr auto;align-items:center;gap:10px;padding:8px 14px;display:grid}.bar-stack .bar-item:last-child{border-bottom:none}.bar-item-clickable{cursor:pointer;transition:background var(--ease)}.bar-item-clickable:hover{background:var(--surface-2)}.bar-item-clickable:hover .bar-label{color:var(--accent)}.bar-item[disabled]{cursor:default;opacity:.5}.bar-label{font-size:var(--text-sm);color:var(--text-2)}.bar-track{background:var(--surface-2);border-radius:4px;height:8px;overflow:hidden}.bar-fill{height:100%;transition:width var(--ease-slow)}.bar-fill.tone-active{background:var(--accent)}.bar-fill.tone-warn{background:var(--warn)}.bar-fill.tone-danger{background:var(--danger)}.bar-value{font-size:var(--text-sm);color:var(--text-1);text-align:right;white-space:nowrap;min-width:96px}.bar-pct{margin-left:2px}.recall-head{justify-content:space-between;align-items:flex-start;gap:24px;margin-bottom:20px;display:flex}.recall-note{max-width:60ch;margin:0;line-height:1.5}.recall-block{margin-bottom:28px}.recall-block-head{margin-bottom:14px}.recall-block-head .page-title{margin-bottom:2px}.recall-block-head p{max-width:70ch;margin:0}.recall-empty{font-size:var(--text-sm);padding:18px}.recall-cards{grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px;margin-top:14px;display:grid}.recall-bars .bar-item{grid-template-columns:140px 1fr auto}.recall-bar-label{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.recall-bars .bar-value{min-width:0}@media (width<=720px){.recall-head{flex-direction:column;gap:12px}}.runtime-list{margin:0;padding:4px 0;list-style:none}.runtime-row{border-bottom:1px solid var(--border-1);grid-template-columns:12px 1fr auto auto;align-items:center;gap:10px;padding:8px 14px;display:grid}.runtime-row:last-child{border-bottom:none}.runtime-dot{background:var(--text-3);border-radius:50%;width:8px;height:8px;box-shadow:0 0 0 2px #ffffff0a}.runtime-dot.runtime-active{background:var(--accent);box-shadow:0 0 6px #e8ff6e99}.runtime-dot.runtime-idle{background:var(--warn)}.runtime-dot.runtime-dormant{background:var(--text-3);opacity:.5}.runtime-name{color:var(--text-1);font-size:var(--text-sm)}.runtime-counts{white-space:nowrap}.runtime-counts-sep{opacity:.5;margin:0 6px}.runtime-counts-prev{opacity:.7}.runtime-age{text-align:right;min-width:60px}.runtime-row.clickable{cursor:pointer;transition:background var(--ease)}.runtime-row.clickable:hover{background:var(--surface-2)}.runtime-row.clickable:hover .runtime-name{color:var(--accent)}.runtime-topics{flex-wrap:wrap;align-items:center;gap:6px;margin-bottom:4px;display:flex}.alert-list{padding:4px 0;list-style:none}.alert-row{border-bottom:1px solid var(--border-1);grid-template-columns:auto auto 1fr;align-items:center;gap:10px;padding:7px 14px;display:grid}.alert-entity{color:var(--text-1);font-size:var(--text-sm);text-overflow:ellipsis;white-space:nowrap;min-width:0;overflow:hidden}.alert-summary{-webkit-line-clamp:1;text-overflow:ellipsis;color:var(--text-3);font-size:var(--text-xs);letter-spacing:.04em;min-width:0;line-height:var(--lh-base);-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.alert-row.clickable{cursor:pointer;transition:background var(--ease)}.alert-row.clickable:hover{background:var(--surface-2)}.alert-row.clickable:hover .alert-entity{color:var(--accent)}.drawer-actions{flex-wrap:wrap;gap:6px;margin:14px 0 6px;display:flex}.session-list{list-style:none}.session-list.compact .session-row{border-bottom:1px solid var(--border-1);padding:6px 0}.session-row{border-bottom:1px solid var(--border-1);grid-template-columns:auto 1fr auto;align-items:center;gap:10px;padding:8px 14px;display:grid}.session-row-detail{grid-template-columns:auto 1fr auto}.session-row-main{flex-direction:column;gap:2px;min-width:0;display:flex}.session-label{color:var(--text-1);font-size:var(--text-sm);white-space:nowrap;text-overflow:ellipsis;text-decoration:none;overflow:hidden}a.session-label:hover{color:var(--accent)}.session-meta{color:var(--text-3);font-size:var(--text-xs);letter-spacing:.04em}@media (width<=1100px){.pulse-grid{grid-template:"coherence runtimes""recent recent""stale stale"/1fr 1fr;height:auto;min-height:0}.pulse-scroll-body{max-height:360px}}@media (width<=700px){.pulse-grid{grid-template-columns:1fr;grid-template-areas:"coherence""runtimes""recent""stale"}}.search-bar{align-items:center;gap:10px;margin-bottom:12px;display:flex}.search-input.search-big{font-size:var(--text-md);flex:1;padding:10px 14px}.search-meta{margin:8px 0}.thread-header{align-items:center;gap:14px;margin-bottom:16px;display:flex}.thread-header .page-title{margin-bottom:0}.thread-grid{grid-template-columns:1fr 1fr;gap:12px;margin-bottom:18px;display:grid}.marker-list{max-height:360px;list-style:none;overflow-y:auto}.marker-row{border-bottom:1px solid var(--border-1);grid-template-columns:auto auto 1fr auto;align-items:baseline;gap:10px;padding:7px 14px;display:grid}.marker-text{color:var(--text-1);font-size:var(--text-sm);min-width:0}.marker-timestamp{font-variant-numeric:tabular-nums;white-space:nowrap}.marker-action-select{min-width:130px;font-size:var(--text-xs)}.marker-editor{align-items:center;gap:6px;display:inline-flex}.marker-editor-input{min-width:260px}.entity-grid{grid-template-columns:repeat(auto-fill,minmax(180px,1fr));gap:10px;margin-top:16px;list-style:none;display:grid}.entity-card{color:var(--text-1);align-items:center;gap:8px;padding:10px 12px;text-decoration:none;display:flex}.entity-name{font-size:var(--text-sm);text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}@media (width<=800px){.thread-grid{grid-template-columns:1fr}}.river-toolbar{align-items:center;gap:12px;margin-bottom:16px;display:flex}.river-toolbar .page-title{font-size:var(--text-lg);margin-bottom:0}.river-grid{--river-label-w:200px;--river-row-gap:3px;--river-cell-gap:2px;--river-cell-h:20px;gap:var(--river-row-gap);flex-direction:column;width:100%;padding:12px;display:flex}.river-grid.river-density-compact{--river-row-gap:1px;--river-cell-gap:1px;--river-cell-h:10px}.river-grid.river-density-spacious{--river-row-gap:6px;--river-cell-gap:4px;--river-cell-h:24px}.river-row{grid-template-columns:var(--river-label-w) 1fr;align-items:center;gap:6px;width:100%;min-width:0;display:grid}.river-row-dates{margin-bottom:2px}.river-lane-label{border-radius:var(--r-sm);color:var(--text-2);font-size:var(--text-sm);white-space:nowrap;align-items:center;gap:6px;width:100%;min-width:0;padding:2px 6px;text-decoration:none;display:flex;overflow:hidden}.river-lane-label:hover{background:var(--surface-2);color:var(--text-1)}.river-lane-label--header{background:0 0;padding:0}.river-lane-name{text-overflow:ellipsis;flex:1;min-width:0;overflow:hidden}.river-cells{grid-template-columns:repeat(var(--cells,1), minmax(0, 1fr));gap:var(--river-cell-gap);width:100%;min-width:0;display:grid}.river-date-cell{font-size:var(--text-xs);color:var(--text-2);letter-spacing:.04em;text-align:center;text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.river-cell{height:var(--river-cell-h);background:var(--surface-2);border-radius:2px;min-width:0}.river-cell.tier-1{background:#e8ff6e33}.river-cell.tier-2{background:#e8ff6e66}.river-cell.tier-3{background:#e8ff6ea6}.river-cell.tier-4{background:var(--accent)}.river-cell.has-corrections{box-shadow:inset 0 -2px #ffb400b3}.alert-row{grid-template-columns:auto minmax(0,max-content) minmax(0,1fr) auto}.alert-actions{align-items:center;gap:6px;display:flex}.alert-actions .chip{font-size:var(--text-xs);padding:2px 8px}.row-actions{gap:6px;display:flex}.row-actions .chip{font-size:var(--text-xs);padding:2px 8px}.row-busy{opacity:.5;pointer-events:none;transition:opacity var(--ease)}.form-input-inline{font-size:var(--text-xs);letter-spacing:.04em;padding:3px 8px}.river-grid{-webkit-user-select:none;user-select:none;position:relative}.river-drag-rect{background:var(--accent-glow);border-left:1px dashed var(--accent);border-right:1px dashed var(--accent);pointer-events:none;z-index:var(--z-raised);position:absolute;top:0;bottom:0}.river-lane-label{text-align:left;cursor:pointer;font-family:var(--font-mono);background:0 0;border:none}.river-cell{transition:outline-color var(--ease);outline-offset:1px;z-index:var(--z-base);outline:1px solid #0000;position:relative}.river-cell:hover{z-index:var(--z-raised);outline-color:#ffffffb3}.river-hover{background:var(--surface-float);border:1px solid var(--border-3);border-radius:var(--r-sm);font-size:var(--text-xs);pointer-events:none;z-index:var(--z-tooltip);align-items:center;gap:8px;padding:6px 10px;display:flex;position:fixed;box-shadow:0 6px 20px #00000080}.river-hover-name{color:var(--text-1);font-size:var(--text-sm)}.session-row.clickable{cursor:pointer;transition:background var(--ease)}.session-row.clickable:hover{background:var(--surface-2)}.session-row.clickable:hover .session-label{color:var(--accent)}.link-button{color:var(--text-3);font-family:var(--font-mono);font-size:var(--text-xs);cursor:pointer;letter-spacing:.04em;background:0 0;border:none;padding:0}.link-button:hover{color:var(--accent)}.drawer-backdrop{z-index:var(--z-drawer-bg);background:#0000008c;animation:.15s fadeIn;position:fixed;inset:0}@keyframes fadeIn{0%{opacity:0}to{opacity:1}}.session-drawer{background:var(--surface-1);border-left:1px solid var(--border-3);width:min(560px,100vw);z-index:var(--z-drawer);flex-direction:column;animation:.18s slideInRight;display:flex;position:fixed;top:0;bottom:0;right:0;box-shadow:-16px 0 40px #0000008c}.coherence-drawer{width:min(480px,100vw)}@keyframes slideInRight{0%{opacity:0;transform:translate(20px)}to{opacity:1;transform:translate(0)}}.drawer-head{border-bottom:1px solid var(--border-1);align-items:center;gap:10px;padding:14px 18px;display:flex}.drawer-title{font-size:var(--text-md);font-family:var(--font-mono);color:var(--text-1);text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;font-weight:500;overflow:hidden}.drawer-close{color:var(--text-3);cursor:pointer;background:0 0;border:none;padding:0 6px;font-size:24px;line-height:1}.drawer-close:hover{color:var(--text-1)}.drawer-body{flex:1;min-height:0;padding:16px 18px 80px;overflow-y:auto}.drawer-paragraph.drawer-hint{color:var(--text-2);margin:0 0 16px}.coherence-session-list .session-row{transition:background var(--ease);grid-template-columns:auto 1fr auto auto}.coherence-session-list .coherence-meta{white-space:nowrap}.coherence-session-list .session-row{cursor:pointer}.coherence-session-list .session-row:hover{background:var(--surface-2)}.coherence-session-list .session-row:hover .session-label{color:var(--accent)}.coherence-session-list .session-row[aria-busy=true]{opacity:.5;pointer-events:none}.coherence-session-list .chip[data-bucket=active].active{color:var(--accent);background:var(--accent-glow);border-color:#e8ff6e4d}.coherence-session-list .chip[data-bucket=sparse].active{color:var(--warn);background:var(--warn-dim);border-color:#ff993359}.coherence-session-list .chip[data-bucket=stale].active{color:var(--danger);background:var(--danger-dim);border-color:#ff6b3566}.coherence-drawer .drawer-body{flex-direction:column;padding-bottom:0;display:flex;overflow:hidden}.coherence-session-list{flex:1;min-height:0;overflow-y:auto}.coherence-drawer .pagination{border-top:1px solid var(--border-1);flex-shrink:0;margin-top:0}.coherence-drawer .pagination.pagination-compact{padding:10px 18px}.merge-picker{background:var(--surface-2);border:1px solid var(--border-2);border-radius:6px;margin-bottom:12px;padding:12px}.merge-result-list{max-height:240px;margin:8px 0 0;padding:0;list-style:none;overflow-y:auto}.merge-result-row{cursor:pointer;border-radius:4px;align-items:center;gap:8px;padding:6px 8px;display:flex}.merge-result-row:hover{background:var(--surface-float)}.merge-result-row.is-selected{outline:2px solid var(--accent);outline-offset:-2px;background:var(--surface-float)}.coherence-rows-dimmed{opacity:.4;pointer-events:none}.coherence-filters{flex-direction:column;gap:8px;margin-bottom:12px;display:flex}.coherence-search{width:100%}.coherence-filter-row{flex-wrap:wrap;align-items:center;gap:8px;display:flex}.drawer-section{font-size:var(--text-xs);letter-spacing:.06em;text-transform:uppercase;color:var(--text-3);margin:18px 0 8px;font-weight:500}.drawer-list{font-size:var(--text-sm);color:var(--text-1);flex-direction:column;gap:8px;list-style:none;display:flex}.drawer-list li{align-items:baseline;gap:8px;display:flex}.drawer-paragraph{font-size:var(--text-sm);color:var(--text-1);line-height:var(--lh-loose)}.drawer-body-text{background:var(--surface-0);border:1px solid var(--border-1);border-radius:var(--r-sm);font-size:var(--text-xs);color:var(--text-2);white-space:pre-wrap;word-break:break-word;max-height:360px;line-height:var(--lh-base);padding:10px 12px;overflow-y:auto}.entity-chips{flex-wrap:wrap;gap:6px;display:flex}.entity-chips-row{align-items:center;margin-top:4px}.supersedence-banner{border-radius:var(--r-sm);font-size:var(--text-sm);flex-wrap:wrap;align-items:baseline;gap:6px;margin-bottom:10px;padding:8px 12px;display:flex}.supersedence-banner--superseded{color:var(--danger);background:#ff6b3514;border:1px solid #ff6b3540}.supersedence-banner--supersedes{border:1px solid var(--border-2);color:var(--text-2);background:#a8a8a80f}.supersedence-label{white-space:nowrap;font-weight:500}.supersedence-ids{flex-wrap:wrap;gap:4px;display:flex}.supersedence-id{color:inherit;opacity:.8}.supersedence-link{cursor:pointer;font-family:var(--font-mono);font-size:var(--text-xs);color:inherit;text-underline-offset:2px;background:0 0;border:none;padding:0;text-decoration:underline}.supersedence-link:hover{opacity:.75}.drawer-menu{position:relative}.drawer-menu-trigger{color:var(--text-3);font-size:var(--text-lg);cursor:pointer;background:0 0;border:none;border-radius:4px;padding:4px 8px;line-height:1}.drawer-menu-trigger:hover{color:var(--text-1);background:var(--bg-2)}.drawer-menu-trigger:disabled{opacity:.4;cursor:not-allowed}.drawer-menu-list{z-index:var(--z-dropdown);background:var(--bg-1);border:1px solid var(--border-1);border-radius:6px;min-width:200px;margin:0;padding:4px;list-style:none;position:absolute;top:calc(100% + 4px);right:0;box-shadow:0 6px 20px #00000059}.drawer-menu-item{text-align:left;width:100%;color:var(--text-1);font-family:var(--font-sans);font-size:var(--text-sm);cursor:pointer;background:0 0;border:none;border-radius:4px;padding:8px 10px;display:block}.drawer-menu-item:hover:not(:disabled){background:var(--bg-2)}.drawer-menu-item:disabled{opacity:.4;cursor:not-allowed}.palette-backdrop{z-index:var(--z-palette-bg);background:#0000008c;position:fixed;inset:0}.supersede-palette{background:var(--bg-1);border:1px solid var(--border-1);width:min(640px,92vw);max-height:76vh;z-index:var(--z-palette);border-radius:8px;flex-direction:column;display:flex;position:fixed;top:12vh;left:50%;transform:translate(-50%);box-shadow:0 24px 60px #00000073}.palette-head{border-bottom:1px solid var(--border-1);align-items:center;gap:10px;padding:12px 16px;display:flex}.palette-title{font-family:var(--font-mono);font-size:var(--text-md);color:var(--text-1);flex:1}.palette-body{flex-direction:column;flex:1;gap:12px;padding:14px 16px;display:flex;overflow-y:auto}.palette-context{background:var(--bg-2);border-left:2px solid var(--border-2);border-radius:4px;grid-template-columns:auto 1fr;gap:4px 10px;padding:10px 12px;display:grid}.palette-context-label{font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-3);text-transform:uppercase;letter-spacing:.04em}.palette-context-value{color:var(--text-1);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.palette-context-id{color:var(--text-3);grid-column:2}.palette-field-label{font-family:var(--font-mono);font-size:var(--text-xs);color:var(--text-3);text-transform:uppercase;letter-spacing:.04em}.palette-input{box-sizing:border-box;background:var(--bg-2);border:1px solid var(--border-1);width:100%;color:var(--text-1);font-size:var(--text-sm);border-radius:4px;padding:8px 10px}.palette-input:focus{border-color:var(--accent);outline:none}.palette-list{border:1px solid var(--border-1);border-radius:4px;max-height:280px;margin:0;padding:0;list-style:none;overflow-y:auto}.palette-row{cursor:pointer;border-bottom:1px solid var(--border-1);grid-template-columns:80px 110px minmax(0,1fr) minmax(80px,160px);align-items:center;gap:10px;padding:6px 10px;display:grid}.palette-row-id{text-overflow:ellipsis;white-space:nowrap;text-align:right;overflow:hidden}.palette-row:last-child{border-bottom:none}.palette-row--hot{background:var(--bg-2)}.palette-row:focus-visible{outline:2px solid var(--accent);outline-offset:-2px}.palette-row[aria-selected=true] .palette-row-date,.palette-row[aria-selected=true] .palette-row-runtime,.palette-row[aria-selected=true] .palette-row-id{color:var(--text-2,var(--text-1))}.palette-row-label{color:var(--text-1);text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.palette-empty{text-align:center;padding:10px}.palette-pick{background:var(--bg-2);border-left:2px solid var(--accent);border-radius:4px;grid-template-columns:auto 1fr auto;gap:4px 10px;padding:10px 12px;display:grid}.palette-pick .palette-context-id{grid-column:2/3}.palette-undo{border:1px solid var(--border-1);color:var(--text-3);cursor:pointer;font-size:var(--text-xs);background:0 0;border-radius:4px;grid-area:1/3/span 2;align-self:center;padding:4px 8px}.palette-undo:hover:not(:disabled){color:var(--text-1);border-color:var(--border-2)}.palette-error{color:#f0a0a0;font-size:var(--text-sm);background:#dc50501f;border:1px solid #dc505059;border-radius:4px;padding:8px 10px}.palette-foot{border-top:1px solid var(--border-1);justify-content:space-between;align-items:center;gap:12px;padding:10px 16px;display:flex}.palette-hint{font-family:var(--font-mono)}.palette-confirm{background:var(--accent);color:var(--bg-1);cursor:pointer;font-family:var(--font-mono);font-size:var(--text-sm);border:none;border-radius:4px;padding:8px 14px}.palette-confirm:disabled{opacity:.4;cursor:not-allowed}.card-head.card-head-stack{flex-direction:column;align-items:stretch;gap:8px;padding:10px 14px 12px}.card-head-row{justify-content:space-between;align-items:baseline;display:flex}.card-filters{flex-wrap:wrap;gap:12px;display:flex}.filter-group{gap:4px;display:flex}.filter-group .chip{font-size:var(--text-xs);padding:2px 8px}.thread-sessions-head{align-items:center;gap:12px;margin:24px 0 8px;display:flex}.thread-sessions-title{flex-shrink:0;margin:0}.thread-sessions-head .search-input{flex:1}.thread-filters{flex-wrap:wrap;align-items:center;gap:12px;margin-bottom:8px;display:flex}.thread-filters .filter-group{flex-wrap:wrap}.pagination{border-top:1px solid var(--border-1);flex-wrap:wrap;align-items:center;gap:16px;margin-top:4px;padding:14px 0 4px;display:flex}.page-size{align-items:center;gap:8px;display:flex}.page-nav{align-items:center;gap:4px;display:flex}.page-nav .chip:disabled{opacity:.4;cursor:not-allowed}.page-indicator{font-size:var(--text-sm);color:var(--text-2);text-align:center;min-width:60px;padding:0 8px}.marker-row-promotable{grid-template-columns:auto 1fr auto auto;align-items:baseline;gap:10px;display:grid}.marker-row-promotable .marker-actions{align-items:baseline;gap:6px;display:flex}.promote-chip{font-size:var(--text-xs);color:var(--accent);background:var(--accent-glow);border-color:#e8ff6e4d;padding:2px 8px}.promote-chip:hover{color:var(--accent);background:var(--accent-dim);border-color:#e8ff6e8c}.promote-editor{align-items:center;gap:6px;min-width:280px;display:flex}.promote-input{flex:1;min-width:200px}.pagination.pagination-compact{font-size:var(--text-xs);flex-wrap:wrap;gap:10px;padding:10px 0 4px}.pagination.pagination-compact .page-indicator{min-width:40px;font-size:var(--text-xs);padding:0 4px}.pagination.pagination-compact .chip{font-size:var(--text-xs);padding:1px 6px}.pagination.pagination-compact .form-label{font-size:9px}.river-date-cell--month-start{border-left:1px solid var(--border-2);padding-left:3px}.river-legend{align-items:center;gap:3px;display:flex}.river-legend-cell{background:var(--surface-2);border-radius:2px;width:12px;height:12px}.river-legend-cell.tier-1{background:#e8ff6e33}.river-legend-cell.tier-2{background:#e8ff6e66}.river-legend-cell.tier-3{background:#e8ff6ea6}.river-legend-cell.tier-4{background:var(--accent)}.empty-row{color:var(--text-2);font-size:var(--text-sm);padding:16px 14px}.list-action-row{padding:8px 14px}.toast-host{z-index:var(--z-toast);pointer-events:none;flex-direction:column;gap:8px;max-width:min(420px,100vw - 48px);display:flex;position:fixed;bottom:24px;right:24px}.toast{background:var(--surface-float);border:1px solid var(--border-3);border-left:3px solid var(--text-2);border-radius:var(--r-md);font-size:var(--text-sm);color:var(--text-1);pointer-events:auto;align-items:flex-start;gap:8px;padding:10px 12px;animation:.2s ease-out fadeSlideIn;display:flex;box-shadow:0 12px 32px #00000073}.toast-success{border-left-color:var(--accent)}.toast-error{border-left-color:var(--danger)}.toast-info{border-left-color:var(--text-2)}.toast-message{line-height:var(--lh-base);flex:1}.toast-close{color:var(--text-3);cursor:pointer;font-size:var(--text-lg);background:0 0;border:0;margin-top:-2px;padding:0 2px;line-height:1}.toast-close:hover{color:var(--text-1)}.tooltip-anchor{display:inline-flex;position:relative}.tooltip{background:var(--surface-float);color:var(--text-1);border:1px solid var(--border-3);border-radius:var(--r-sm);font-size:var(--text-xs);font-family:var(--font-mono);letter-spacing:.04em;text-align:center;pointer-events:none;opacity:0;width:max-content;max-width:240px;transition:opacity var(--ease-fast), transform var(--ease-fast);z-index:var(--z-tooltip);padding:4px 8px;position:absolute;left:50%;transform:translate(-50%)translateY(-4px)}.tooltip-top .tooltip{margin-bottom:4px;bottom:100%}.tooltip-bottom .tooltip{margin-top:4px;top:100%}.tooltip-anchor:hover .tooltip,.tooltip-anchor:focus-within .tooltip{opacity:1;transform:translate(-50%)translateY(0)}.confirm-dialog{background:var(--surface-float);border:1px solid var(--border-3);border-radius:var(--r-md);width:min(440px,92vw);z-index:var(--z-palette);padding:20px 22px 16px;animation:.2s ease-out fadeSlideIn;position:fixed;top:22vh;left:50%;transform:translate(-50%);box-shadow:0 24px 60px #0000008c}.confirm-title{font-family:var(--font-mono);font-size:var(--text-base);color:var(--text-1);letter-spacing:.04em;margin:0 0 8px}.confirm-message{color:var(--text-2);font-size:var(--text-sm);line-height:var(--lh-base);margin:0 0 16px}.confirm-actions{justify-content:flex-end;gap:8px;display:flex}.session-meta{-webkit-line-clamp:2;line-clamp:2;white-space:normal;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.search-wrap{align-items:center;display:flex;position:relative}.search-wrap .search-input{width:100%;padding-right:28px}.search-clear{color:var(--text-3);cursor:pointer;background:0 0;border:none;align-items:center;padding:0;display:flex;position:absolute;right:6px}.search-clear:hover{color:var(--text-1)}.drawer-nav{flex-shrink:0;align-items:center;gap:4px;display:flex}.drawer-nav-btn{border:1px solid var(--border-2);color:var(--text-3);cursor:pointer;border-radius:var(--r-sm);transition:color var(--ease), border-color var(--ease), background var(--ease);background:0 0;justify-content:center;align-items:center;padding:3px;display:flex}.drawer-nav-btn:hover:not(:disabled){color:var(--text-1);border-color:var(--border-3);background:var(--surface-2)}.drawer-nav-btn:disabled{opacity:.35;cursor:not-allowed}.dot-pulse{animation:2s ease-in-out infinite dot-pulse}@keyframes dot-pulse{0%,to{opacity:1;transform:scale(1)}50%{opacity:.6;transform:scale(1.35)}}.search-hint{margin:8px 0 12px}.search-header{z-index:var(--z-sticky);background:var(--surface-0);border-bottom:1px solid var(--border-1);margin-bottom:16px;padding-bottom:12px;position:sticky;top:0}.session-row.is-selected{background:var(--surface-2);border-left:2px solid var(--accent);padding-left:12px}.live-tag[data-kind=summary]{background:var(--surface-2);color:var(--text-2)}.live-tag[data-kind=label]{background:var(--surface-2);color:var(--text-1)}.live-tag[data-kind=entity]{background:var(--accent-glow);color:var(--accent);border-color:#e8ff6e40}.match-snippet{color:var(--text-2);font-size:var(--text-xs);line-height:var(--lh-base);-webkit-line-clamp:2;-webkit-box-orient:vertical;margin-top:4px;display:-webkit-box;overflow:hidden}.match-snippet mark{color:var(--accent);background:#e8ff6e26;border-radius:2px;padding:0 2px}
|
package/dist/ui/index.html
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
6
|
<title>nlm memory</title>
|
|
7
|
-
<script type="module" crossorigin src="/ui/assets/index-
|
|
8
|
-
<link rel="stylesheet" crossorigin href="/ui/assets/index-
|
|
7
|
+
<script type="module" crossorigin src="/ui/assets/index-BTj60bAH.js"></script>
|
|
8
|
+
<link rel="stylesheet" crossorigin href="/ui/assets/index-Baw2qIKc.css">
|
|
9
9
|
</head>
|
|
10
10
|
<body>
|
|
11
11
|
<div id="root"></div>
|
package/nlm/README.md
CHANGED
|
@@ -7,7 +7,7 @@ the prompt-recall hook.
|
|
|
7
7
|
|
|
8
8
|
Pi's interactive UI strips `index.{ts,js}` from the load path, so this
|
|
9
9
|
extension surfaces in pi's `[Extensions]` list as `nlm` — matching the
|
|
10
|
-
naming convention used by `pi-mcp-adapter`, `
|
|
10
|
+
naming convention used by `pi-mcp-adapter`, `my-tasks`, etc.
|
|
11
11
|
|
|
12
12
|
## Install
|
|
13
13
|
|
package/nlm/index.js
CHANGED
|
@@ -53,7 +53,7 @@ var RECALL_LIMIT = 5;
|
|
|
53
53
|
var RECALL_TIMEOUT_MS = 2e3;
|
|
54
54
|
async function recallOverHttp(prompt, runtime) {
|
|
55
55
|
const portValue = process.env["NLM_PORT"] ?? "3940";
|
|
56
|
-
const url = `http://localhost:${portValue}/api/recall?q=${encodeURIComponent(prompt)}&mode=keyword&limit=${RECALL_LIMIT}`;
|
|
56
|
+
const url = `http://localhost:${portValue}/api/recall?q=${encodeURIComponent(prompt)}&mode=keyword&limit=${RECALL_LIMIT}&withFacts=true`;
|
|
57
57
|
const controller = new AbortController();
|
|
58
58
|
const timer = setTimeout(() => controller.abort(), RECALL_TIMEOUT_MS);
|
|
59
59
|
try {
|
|
@@ -63,19 +63,26 @@ async function recallOverHttp(prompt, runtime) {
|
|
|
63
63
|
headers: hookAuthHeaders(extra),
|
|
64
64
|
signal: controller.signal
|
|
65
65
|
});
|
|
66
|
-
if (!res.ok) return [];
|
|
66
|
+
if (!res.ok) return { hits: [], facts: [] };
|
|
67
67
|
let body;
|
|
68
68
|
try {
|
|
69
69
|
body = await res.json();
|
|
70
70
|
} catch {
|
|
71
|
-
return [];
|
|
71
|
+
return { hits: [], facts: [] };
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
const hits = (body.results ?? []).map((r) => ({
|
|
74
74
|
id: r.id,
|
|
75
75
|
label: r.label,
|
|
76
76
|
startedAt: r.startedAt,
|
|
77
77
|
matchScore: r.matchScore
|
|
78
78
|
}));
|
|
79
|
+
const facts = (body.relatedFacts ?? []).map((f) => ({
|
|
80
|
+
subject: f.subject,
|
|
81
|
+
predicate: f.predicate,
|
|
82
|
+
value: f.value,
|
|
83
|
+
corroborationCount: f.corroborationCount
|
|
84
|
+
}));
|
|
85
|
+
return { hits, facts };
|
|
79
86
|
} finally {
|
|
80
87
|
clearTimeout(timer);
|
|
81
88
|
}
|
|
@@ -145,16 +152,27 @@ function recordSurfaced(conversationId, ids) {
|
|
|
145
152
|
}
|
|
146
153
|
|
|
147
154
|
// src/core/hook/pointer-block.ts
|
|
148
|
-
function formatPointerBlock(hits) {
|
|
149
|
-
if (hits.length === 0) return "";
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
155
|
+
function formatPointerBlock(hits, facts = []) {
|
|
156
|
+
if (hits.length === 0 && facts.length === 0) return "";
|
|
157
|
+
const out = [];
|
|
158
|
+
if (hits.length > 0) {
|
|
159
|
+
out.push("## Possibly-relevant prior sessions (nlm-memory)");
|
|
160
|
+
for (const h of hits) {
|
|
161
|
+
out.push(`- ${h.id} \xB7 ${h.label} (${h.startedAt.slice(0, 10)})`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
if (facts.length > 0) {
|
|
165
|
+
if (out.length > 0) out.push("");
|
|
166
|
+
out.push("## Known facts about top entities");
|
|
167
|
+
for (const f of facts) {
|
|
168
|
+
const tag = f.corroborationCount > 1 ? ` [${f.corroborationCount} sessions]` : "";
|
|
169
|
+
out.push(`- ${f.subject} ${f.predicate}: ${f.value}${tag}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
out.push(
|
|
156
173
|
"NLM tools: recall_sessions (search), get_session (full transcript), recall_facts (prior decisions), get_fact_history (how a decision evolved)."
|
|
157
|
-
|
|
174
|
+
);
|
|
175
|
+
return out.join("\n");
|
|
158
176
|
}
|
|
159
177
|
|
|
160
178
|
// src/core/hook/select.ts
|
|
@@ -173,6 +191,10 @@ var SCORE_THRESHOLD = 0;
|
|
|
173
191
|
var PER_FIRE_CAP = 3;
|
|
174
192
|
var PER_CONVERSATION_CAP = 10;
|
|
175
193
|
var PROMPT_PREVIEW_CHARS = 200;
|
|
194
|
+
function normalizeRecall(raw) {
|
|
195
|
+
if (Array.isArray(raw)) return { hits: raw, facts: [] };
|
|
196
|
+
return raw;
|
|
197
|
+
}
|
|
176
198
|
async function runHook(input, deps) {
|
|
177
199
|
const gate = classifyPrompt(input.prompt);
|
|
178
200
|
const preview = input.prompt.slice(0, PROMPT_PREVIEW_CHARS);
|
|
@@ -189,12 +211,13 @@ async function runHook(input, deps) {
|
|
|
189
211
|
});
|
|
190
212
|
return "";
|
|
191
213
|
}
|
|
192
|
-
let
|
|
214
|
+
let fetched = { hits: [], facts: [] };
|
|
193
215
|
try {
|
|
194
|
-
|
|
216
|
+
fetched = normalizeRecall(await deps.recall(input.prompt));
|
|
195
217
|
} catch {
|
|
196
|
-
|
|
218
|
+
fetched = { hits: [], facts: [] };
|
|
197
219
|
}
|
|
220
|
+
const hits = fetched.hits;
|
|
198
221
|
const surfaced = loadSurfaced(input.conversationId);
|
|
199
222
|
const selected = selectHits({
|
|
200
223
|
hits,
|
|
@@ -203,7 +226,7 @@ async function runHook(input, deps) {
|
|
|
203
226
|
perFireCap: PER_FIRE_CAP,
|
|
204
227
|
perConversationCap: PER_CONVERSATION_CAP
|
|
205
228
|
});
|
|
206
|
-
const block = formatPointerBlock(selected);
|
|
229
|
+
const block = formatPointerBlock(selected, fetched.facts);
|
|
207
230
|
const estTokens = Math.ceil(block.length / 4);
|
|
208
231
|
appendHookLog({
|
|
209
232
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
package/package.json
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nlm-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Local-first non-linear memory operating system for AI operators.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"engines": {
|
|
8
|
-
"node": ">=
|
|
8
|
+
"node": ">=22.0.0"
|
|
9
9
|
},
|
|
10
10
|
"repository": {
|
|
11
11
|
"type": "git",
|
|
12
|
-
"url": "git+https://github.com/pbmagnet4/nlm-memory
|
|
12
|
+
"url": "git+https://github.com/pbmagnet4/nlm-memory.git"
|
|
13
13
|
},
|
|
14
|
-
"homepage": "https://github.com/pbmagnet4/nlm-memory
|
|
14
|
+
"homepage": "https://github.com/pbmagnet4/nlm-memory#readme",
|
|
15
15
|
"bugs": {
|
|
16
|
-
"url": "https://github.com/pbmagnet4/nlm-memory
|
|
16
|
+
"url": "https://github.com/pbmagnet4/nlm-memory/issues"
|
|
17
17
|
},
|
|
18
18
|
"keywords": [
|
|
19
19
|
"ai",
|
|
@@ -54,37 +54,38 @@
|
|
|
54
54
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json",
|
|
55
55
|
"bench:longmemeval": "tsx scripts/longmemeval/run-harness.ts",
|
|
56
56
|
"bench:classifier": "tsx scripts/longmemeval/run-harness.ts --classifier",
|
|
57
|
-
"bench:compare": "tsx scripts/longmemeval/compare-classifiers.ts"
|
|
57
|
+
"bench:compare": "tsx scripts/longmemeval/compare-classifiers.ts",
|
|
58
|
+
"eval:signals": "tsx scripts/eval/signals-eval.ts"
|
|
58
59
|
},
|
|
59
60
|
"dependencies": {
|
|
60
61
|
"@clack/prompts": "^1.4.0",
|
|
61
62
|
"@hono/node-server": "^2.0.3",
|
|
62
63
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
63
64
|
"@toon-format/toon": "^2.3.0",
|
|
64
|
-
"better-sqlite3": "^
|
|
65
|
-
"commander": "^
|
|
65
|
+
"better-sqlite3": "^12.10.0",
|
|
66
|
+
"commander": "^15.0.0",
|
|
66
67
|
"hono": "^4.6.0",
|
|
67
68
|
"pg": "^8.21.0",
|
|
68
|
-
"pgvector": "^0.
|
|
69
|
+
"pgvector": "^0.3.0",
|
|
69
70
|
"sqlite-vec": "^0.1.6",
|
|
70
71
|
"yaml": "^2.9.0",
|
|
71
|
-
"zod": "^
|
|
72
|
+
"zod": "^4.4.3"
|
|
72
73
|
},
|
|
73
74
|
"devDependencies": {
|
|
74
75
|
"@types/better-sqlite3": "^7.6.13",
|
|
75
76
|
"@types/node": "^22.10.0",
|
|
76
77
|
"@types/pg": "^8.20.0",
|
|
77
|
-
"@types/react": "^
|
|
78
|
-
"@types/react-dom": "^
|
|
79
|
-
"@vitejs/plugin-react": "^
|
|
78
|
+
"@types/react": "^19.2.17",
|
|
79
|
+
"@types/react-dom": "^19.2.3",
|
|
80
|
+
"@vitejs/plugin-react": "^6.0.2",
|
|
80
81
|
"esbuild": "^0.28.0",
|
|
81
|
-
"react": "^
|
|
82
|
-
"react-dom": "^
|
|
82
|
+
"react": "^19.2.7",
|
|
83
|
+
"react-dom": "^19.2.7",
|
|
83
84
|
"react-router-dom": "^6.30.3",
|
|
84
85
|
"tsc-alias": "^1.8.17",
|
|
85
86
|
"tsx": "^4.19.0",
|
|
86
|
-
"typescript": "^
|
|
87
|
-
"vite": "^
|
|
88
|
-
"vitest": "^
|
|
87
|
+
"typescript": "^6.0.3",
|
|
88
|
+
"vite": "^8.0.16",
|
|
89
|
+
"vitest": "^4.1.8"
|
|
89
90
|
}
|
|
90
91
|
}
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
"url": "https://github.com/pbmagnet4"
|
|
8
8
|
},
|
|
9
9
|
"license": "MIT",
|
|
10
|
-
"homepage": "https://github.com/pbmagnet4/nlm-memory
|
|
11
|
-
"repository": "https://github.com/pbmagnet4/nlm-memory
|
|
10
|
+
"homepage": "https://github.com/pbmagnet4/nlm-memory",
|
|
11
|
+
"repository": "https://github.com/pbmagnet4/nlm-memory",
|
|
12
12
|
"keywords": [
|
|
13
13
|
"memory",
|
|
14
14
|
"context",
|
package/plugin/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
This directory is the Codex plugin distribution surface for nlm-memory. The
|
|
4
4
|
repo root acts as a Codex marketplace (`codex plugin marketplace add
|
|
5
|
-
pbmagnet4/nlm-memory
|
|
5
|
+
pbmagnet4/nlm-memory`), and this directory is the plugin Codex installs.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -10,8 +10,8 @@ Prerequisite: `npm install -g nlm-memory` (puts `nlm` on PATH; the MCP server
|
|
|
10
10
|
spawns `nlm mcp`).
|
|
11
11
|
|
|
12
12
|
```bash
|
|
13
|
-
codex plugin marketplace add pbmagnet4/nlm-memory
|
|
14
|
-
codex plugin add nlm-memory@nlm-memory
|
|
13
|
+
codex plugin marketplace add pbmagnet4/nlm-memory
|
|
14
|
+
codex plugin add nlm-memory@nlm-memory
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
Or use the wrapper: `nlm connect codex`.
|
|
@@ -64,16 +64,27 @@ function recordSurfaced(conversationId, ids) {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
// src/core/hook/pointer-block.ts
|
|
67
|
-
function formatPointerBlock(hits) {
|
|
68
|
-
if (hits.length === 0) return "";
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
67
|
+
function formatPointerBlock(hits, facts = []) {
|
|
68
|
+
if (hits.length === 0 && facts.length === 0) return "";
|
|
69
|
+
const out = [];
|
|
70
|
+
if (hits.length > 0) {
|
|
71
|
+
out.push("## Possibly-relevant prior sessions (nlm-memory)");
|
|
72
|
+
for (const h of hits) {
|
|
73
|
+
out.push(`- ${h.id} \xB7 ${h.label} (${h.startedAt.slice(0, 10)})`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (facts.length > 0) {
|
|
77
|
+
if (out.length > 0) out.push("");
|
|
78
|
+
out.push("## Known facts about top entities");
|
|
79
|
+
for (const f of facts) {
|
|
80
|
+
const tag = f.corroborationCount > 1 ? ` [${f.corroborationCount} sessions]` : "";
|
|
81
|
+
out.push(`- ${f.subject} ${f.predicate}: ${f.value}${tag}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
out.push(
|
|
75
85
|
"NLM tools: recall_sessions (search), get_session (full transcript), recall_facts (prior decisions), get_fact_history (how a decision evolved)."
|
|
76
|
-
|
|
86
|
+
);
|
|
87
|
+
return out.join("\n");
|
|
77
88
|
}
|
|
78
89
|
|
|
79
90
|
// src/core/hook/select.ts
|
|
@@ -142,7 +153,7 @@ var RECALL_LIMIT = 5;
|
|
|
142
153
|
var RECALL_TIMEOUT_MS = 2e3;
|
|
143
154
|
async function recallOverHttp(prompt, runtime) {
|
|
144
155
|
const portValue = process.env["NLM_PORT"] ?? "3940";
|
|
145
|
-
const url = `http://localhost:${portValue}/api/recall?q=${encodeURIComponent(prompt)}&mode=keyword&limit=${RECALL_LIMIT}`;
|
|
156
|
+
const url = `http://localhost:${portValue}/api/recall?q=${encodeURIComponent(prompt)}&mode=keyword&limit=${RECALL_LIMIT}&withFacts=true`;
|
|
146
157
|
const controller = new AbortController();
|
|
147
158
|
const timer = setTimeout(() => controller.abort(), RECALL_TIMEOUT_MS);
|
|
148
159
|
try {
|
|
@@ -152,19 +163,26 @@ async function recallOverHttp(prompt, runtime) {
|
|
|
152
163
|
headers: hookAuthHeaders(extra),
|
|
153
164
|
signal: controller.signal
|
|
154
165
|
});
|
|
155
|
-
if (!res.ok) return [];
|
|
166
|
+
if (!res.ok) return { hits: [], facts: [] };
|
|
156
167
|
let body;
|
|
157
168
|
try {
|
|
158
169
|
body = await res.json();
|
|
159
170
|
} catch {
|
|
160
|
-
return [];
|
|
171
|
+
return { hits: [], facts: [] };
|
|
161
172
|
}
|
|
162
|
-
|
|
173
|
+
const hits = (body.results ?? []).map((r) => ({
|
|
163
174
|
id: r.id,
|
|
164
175
|
label: r.label,
|
|
165
176
|
startedAt: r.startedAt,
|
|
166
177
|
matchScore: r.matchScore
|
|
167
178
|
}));
|
|
179
|
+
const facts = (body.relatedFacts ?? []).map((f) => ({
|
|
180
|
+
subject: f.subject,
|
|
181
|
+
predicate: f.predicate,
|
|
182
|
+
value: f.value,
|
|
183
|
+
corroborationCount: f.corroborationCount
|
|
184
|
+
}));
|
|
185
|
+
return { hits, facts };
|
|
168
186
|
} finally {
|
|
169
187
|
clearTimeout(timer);
|
|
170
188
|
}
|
|
@@ -175,6 +193,10 @@ var SCORE_THRESHOLD = 0;
|
|
|
175
193
|
var PER_FIRE_CAP = 3;
|
|
176
194
|
var PER_CONVERSATION_CAP = 10;
|
|
177
195
|
var PROMPT_PREVIEW_CHARS = 200;
|
|
196
|
+
function normalizeRecall(raw) {
|
|
197
|
+
if (Array.isArray(raw)) return { hits: raw, facts: [] };
|
|
198
|
+
return raw;
|
|
199
|
+
}
|
|
178
200
|
async function runHook(input, deps) {
|
|
179
201
|
const gate = classifyPrompt(input.prompt);
|
|
180
202
|
const preview = input.prompt.slice(0, PROMPT_PREVIEW_CHARS);
|
|
@@ -191,12 +213,13 @@ async function runHook(input, deps) {
|
|
|
191
213
|
});
|
|
192
214
|
return "";
|
|
193
215
|
}
|
|
194
|
-
let
|
|
216
|
+
let fetched = { hits: [], facts: [] };
|
|
195
217
|
try {
|
|
196
|
-
|
|
218
|
+
fetched = normalizeRecall(await deps.recall(input.prompt));
|
|
197
219
|
} catch {
|
|
198
|
-
|
|
220
|
+
fetched = { hits: [], facts: [] };
|
|
199
221
|
}
|
|
222
|
+
const hits = fetched.hits;
|
|
200
223
|
const surfaced = loadSurfaced(input.conversationId);
|
|
201
224
|
const selected = selectHits({
|
|
202
225
|
hits,
|
|
@@ -205,7 +228,7 @@ async function runHook(input, deps) {
|
|
|
205
228
|
perFireCap: PER_FIRE_CAP,
|
|
206
229
|
perConversationCap: PER_CONVERSATION_CAP
|
|
207
230
|
});
|
|
208
|
-
const block = formatPointerBlock(selected);
|
|
231
|
+
const block = formatPointerBlock(selected, fetched.facts);
|
|
209
232
|
const estTokens = Math.ceil(block.length / 4);
|
|
210
233
|
appendHookLog({
|
|
211
234
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
// src/hook/stop-hook.ts
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
5
|
import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
|
|
6
|
-
import { homedir as
|
|
7
|
-
import { dirname, join as
|
|
6
|
+
import { homedir as homedir5 } from "node:os";
|
|
7
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
8
8
|
|
|
9
9
|
// src/core/hook/citation-detect.ts
|
|
10
10
|
var MIN_ID_LEN = 6;
|
|
@@ -58,6 +58,43 @@ function safeStringify(value) {
|
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// src/core/hook/miss-detect.ts
|
|
62
|
+
var MIN_ID_LEN2 = 6;
|
|
63
|
+
function detectMisses(input) {
|
|
64
|
+
const surfaced = /* @__PURE__ */ new Set();
|
|
65
|
+
for (const id of input.surfacedIds) {
|
|
66
|
+
if (id.length >= MIN_ID_LEN2) surfaced.add(id);
|
|
67
|
+
}
|
|
68
|
+
const seenMissed = /* @__PURE__ */ new Set();
|
|
69
|
+
const misses = [];
|
|
70
|
+
for (const tu of input.toolUses) {
|
|
71
|
+
if (!isNlmTool2(tu.name)) continue;
|
|
72
|
+
const kind = explicitIdKind(tu.name);
|
|
73
|
+
if (!kind) continue;
|
|
74
|
+
const id = extractId(tu.input);
|
|
75
|
+
if (!id || id.length < MIN_ID_LEN2) continue;
|
|
76
|
+
if (surfaced.has(id)) continue;
|
|
77
|
+
if (seenMissed.has(id)) continue;
|
|
78
|
+
seenMissed.add(id);
|
|
79
|
+
misses.push({ id, kind });
|
|
80
|
+
}
|
|
81
|
+
return misses;
|
|
82
|
+
}
|
|
83
|
+
function isNlmTool2(name) {
|
|
84
|
+
return /^mcp__[^_]*nlm[^_]*__/.test(name);
|
|
85
|
+
}
|
|
86
|
+
function explicitIdKind(name) {
|
|
87
|
+
if (name.endsWith("__get_session")) return "get_session";
|
|
88
|
+
if (name.endsWith("__cite_session")) return "cite_session";
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
function extractId(input) {
|
|
92
|
+
if (input === null || typeof input !== "object") return null;
|
|
93
|
+
const obj = input;
|
|
94
|
+
const candidate = obj["id"];
|
|
95
|
+
return typeof candidate === "string" ? candidate : null;
|
|
96
|
+
}
|
|
97
|
+
|
|
61
98
|
// src/core/hook/memo.ts
|
|
62
99
|
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
63
100
|
import { homedir } from "node:os";
|
|
@@ -114,6 +151,32 @@ function recordCited(conversationId, ids) {
|
|
|
114
151
|
}
|
|
115
152
|
}
|
|
116
153
|
|
|
154
|
+
// src/core/recall/miss-log.ts
|
|
155
|
+
import { appendFile, mkdir, readFile, stat } from "node:fs/promises";
|
|
156
|
+
import { dirname, join as join3 } from "node:path";
|
|
157
|
+
import { homedir as homedir3 } from "node:os";
|
|
158
|
+
function defaultLogPath() {
|
|
159
|
+
return process.env["NLM_MISS_LOG"] ?? join3(homedir3(), ".nlm", "miss-log.jsonl");
|
|
160
|
+
}
|
|
161
|
+
function isEnabled() {
|
|
162
|
+
const raw = process.env["NLM_MISS_LOG_ENABLED"];
|
|
163
|
+
if (raw === void 0) return true;
|
|
164
|
+
return raw !== "0" && raw.toLowerCase() !== "false";
|
|
165
|
+
}
|
|
166
|
+
async function appendMisses(entries, logPath2) {
|
|
167
|
+
if (!isEnabled()) return;
|
|
168
|
+
if (entries.length === 0) return;
|
|
169
|
+
const path = logPath2 ?? defaultLogPath();
|
|
170
|
+
try {
|
|
171
|
+
await mkdir(dirname(path), { recursive: true });
|
|
172
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
173
|
+
const block = entries.map((e) => `${JSON.stringify({ ts, ...e })}
|
|
174
|
+
`).join("");
|
|
175
|
+
await appendFile(path, block, "utf8");
|
|
176
|
+
} catch {
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
117
180
|
// src/core/hook/transcript.ts
|
|
118
181
|
import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
|
|
119
182
|
function parseTurn(parsed) {
|
|
@@ -164,7 +227,7 @@ function readAllAssistantTurns(transcriptPath) {
|
|
|
164
227
|
|
|
165
228
|
// src/llm/env-autoload.ts
|
|
166
229
|
import { readFileSync as readFileSync4, existsSync as existsSync4 } from "node:fs";
|
|
167
|
-
import { homedir as
|
|
230
|
+
import { homedir as homedir4 } from "node:os";
|
|
168
231
|
import { resolve } from "node:path";
|
|
169
232
|
var DEFAULT_SEARCH_PATHS = [
|
|
170
233
|
"~/.nlm/.env",
|
|
@@ -173,7 +236,7 @@ var DEFAULT_SEARCH_PATHS = [
|
|
|
173
236
|
"../../.env"
|
|
174
237
|
];
|
|
175
238
|
function expandHome(p) {
|
|
176
|
-
if (p.startsWith("~/")) return resolve(
|
|
239
|
+
if (p.startsWith("~/")) return resolve(homedir4(), p.slice(2));
|
|
177
240
|
return p;
|
|
178
241
|
}
|
|
179
242
|
function autoloadEnv(extraPaths = []) {
|
|
@@ -259,6 +322,17 @@ async function runStopHook(input, deps) {
|
|
|
259
322
|
});
|
|
260
323
|
const alreadyCited = loadCited(input.conversationId);
|
|
261
324
|
const fresh = detected.filter((c) => !alreadyCited.has(c.id));
|
|
325
|
+
const misses = detectMisses({ toolUses: allToolUses, surfacedIds: surfaced });
|
|
326
|
+
if (misses.length > 0) {
|
|
327
|
+
void appendMisses(
|
|
328
|
+
misses.map((m) => ({
|
|
329
|
+
conversationId: input.conversationId,
|
|
330
|
+
missedId: m.id,
|
|
331
|
+
kind: m.kind,
|
|
332
|
+
surfacedCount: surfaced.size
|
|
333
|
+
}))
|
|
334
|
+
);
|
|
335
|
+
}
|
|
262
336
|
const lastText = turns[turns.length - 1]?.text ?? "";
|
|
263
337
|
const preview = lastText.slice(0, RESPONSE_PREVIEW_CHARS);
|
|
264
338
|
for (const c of fresh) {
|
|
@@ -279,12 +353,12 @@ async function runStopHook(input, deps) {
|
|
|
279
353
|
};
|
|
280
354
|
}
|
|
281
355
|
function logPath() {
|
|
282
|
-
return process.env["NLM_HOOK_LOG"] ??
|
|
356
|
+
return process.env["NLM_HOOK_LOG"] ?? join4(homedir5(), ".nlm", "hook-log.jsonl");
|
|
283
357
|
}
|
|
284
358
|
function logStopResult(result) {
|
|
285
359
|
try {
|
|
286
360
|
const path = logPath();
|
|
287
|
-
mkdirSync3(
|
|
361
|
+
mkdirSync3(dirname2(path), { recursive: true });
|
|
288
362
|
appendFileSync(
|
|
289
363
|
path,
|
|
290
364
|
`${JSON.stringify({
|