nlm-memory 0.4.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/.agents/plugins/marketplace.json +20 -0
- package/.github/workflows/ci.yml +30 -0
- package/LICENSE +151 -0
- package/README.md +119 -0
- package/dist/cli/classify-parity.d.ts +48 -0
- package/dist/cli/classify-parity.js +182 -0
- package/dist/cli/classify-parity.js.map +1 -0
- package/dist/cli/launchctl-helpers.d.ts +26 -0
- package/dist/cli/launchctl-helpers.js +42 -0
- package/dist/cli/launchctl-helpers.js.map +1 -0
- package/dist/cli/nlm.d.ts +25 -0
- package/dist/cli/nlm.js +832 -0
- package/dist/cli/nlm.js.map +1 -0
- package/dist/core/actions/actions-log.d.ts +40 -0
- package/dist/core/actions/actions-log.js +72 -0
- package/dist/core/actions/actions-log.js.map +1 -0
- package/dist/core/actions/overlay.d.ts +30 -0
- package/dist/core/actions/overlay.js +101 -0
- package/dist/core/actions/overlay.js.map +1 -0
- package/dist/core/adapters/aider.d.ts +33 -0
- package/dist/core/adapters/aider.js +167 -0
- package/dist/core/adapters/aider.js.map +1 -0
- package/dist/core/adapters/claude-code.d.ts +32 -0
- package/dist/core/adapters/claude-code.js +270 -0
- package/dist/core/adapters/claude-code.js.map +1 -0
- package/dist/core/adapters/common.d.ts +20 -0
- package/dist/core/adapters/common.js +60 -0
- package/dist/core/adapters/common.js.map +1 -0
- package/dist/core/adapters/from-source.d.ts +11 -0
- package/dist/core/adapters/from-source.js +55 -0
- package/dist/core/adapters/from-source.js.map +1 -0
- package/dist/core/adapters/hermes-agent.d.ts +34 -0
- package/dist/core/adapters/hermes-agent.js +192 -0
- package/dist/core/adapters/hermes-agent.js.map +1 -0
- package/dist/core/adapters/hermes.d.ts +31 -0
- package/dist/core/adapters/hermes.js +247 -0
- package/dist/core/adapters/hermes.js.map +1 -0
- package/dist/core/adapters/jsonl-generic.d.ts +56 -0
- package/dist/core/adapters/jsonl-generic.js +185 -0
- package/dist/core/adapters/jsonl-generic.js.map +1 -0
- package/dist/core/adapters/opencode.d.ts +36 -0
- package/dist/core/adapters/opencode.js +213 -0
- package/dist/core/adapters/opencode.js.map +1 -0
- package/dist/core/adapters/pi.d.ts +32 -0
- package/dist/core/adapters/pi.js +233 -0
- package/dist/core/adapters/pi.js.map +1 -0
- package/dist/core/classifier/prompt.d.ts +60 -0
- package/dist/core/classifier/prompt.js +178 -0
- package/dist/core/classifier/prompt.js.map +1 -0
- package/dist/core/dataset/build-dataset.d.ts +87 -0
- package/dist/core/dataset/build-dataset.js +335 -0
- package/dist/core/dataset/build-dataset.js.map +1 -0
- package/dist/core/embedding/chunk-body.d.ts +30 -0
- package/dist/core/embedding/chunk-body.js +60 -0
- package/dist/core/embedding/chunk-body.js.map +1 -0
- package/dist/core/embedding/embed-backfill.d.ts +36 -0
- package/dist/core/embedding/embed-backfill.js +168 -0
- package/dist/core/embedding/embed-backfill.js.map +1 -0
- package/dist/core/embedding/embed-normalize.d.ts +28 -0
- package/dist/core/embedding/embed-normalize.js +98 -0
- package/dist/core/embedding/embed-normalize.js.map +1 -0
- package/dist/core/facts/backfill-facts.d.ts +58 -0
- package/dist/core/facts/backfill-facts.js +169 -0
- package/dist/core/facts/backfill-facts.js.map +1 -0
- package/dist/core/facts/extract-facts.d.ts +20 -0
- package/dist/core/facts/extract-facts.js +37 -0
- package/dist/core/facts/extract-facts.js.map +1 -0
- package/dist/core/hook/citation-detect.d.ts +32 -0
- package/dist/core/hook/citation-detect.js +105 -0
- package/dist/core/hook/citation-detect.js.map +1 -0
- package/dist/core/hook/cite-memo.d.ts +20 -0
- package/dist/core/hook/cite-memo.js +68 -0
- package/dist/core/hook/cite-memo.js.map +1 -0
- package/dist/core/hook/claude-settings.d.ts +34 -0
- package/dist/core/hook/claude-settings.js +117 -0
- package/dist/core/hook/claude-settings.js.map +1 -0
- package/dist/core/hook/gate.d.ts +11 -0
- package/dist/core/hook/gate.js +19 -0
- package/dist/core/hook/gate.js.map +1 -0
- package/dist/core/hook/hook-log.d.ts +25 -0
- package/dist/core/hook/hook-log.js +28 -0
- package/dist/core/hook/hook-log.js.map +1 -0
- package/dist/core/hook/memo-sweep.d.ts +55 -0
- package/dist/core/hook/memo-sweep.js +134 -0
- package/dist/core/hook/memo-sweep.js.map +1 -0
- package/dist/core/hook/memo.d.ts +20 -0
- package/dist/core/hook/memo.js +66 -0
- package/dist/core/hook/memo.js.map +1 -0
- package/dist/core/hook/pointer-block.d.ts +14 -0
- package/dist/core/hook/pointer-block.js +19 -0
- package/dist/core/hook/pointer-block.js.map +1 -0
- package/dist/core/hook/select.d.ts +21 -0
- package/dist/core/hook/select.js +15 -0
- package/dist/core/hook/select.js.map +1 -0
- package/dist/core/hook/transcript.d.ts +31 -0
- package/dist/core/hook/transcript.js +103 -0
- package/dist/core/hook/transcript.js.map +1 -0
- package/dist/core/ingest/ingest-session.d.ts +40 -0
- package/dist/core/ingest/ingest-session.js +71 -0
- package/dist/core/ingest/ingest-session.js.map +1 -0
- package/dist/core/providers/provider-models.d.ts +24 -0
- package/dist/core/providers/provider-models.js +72 -0
- package/dist/core/providers/provider-models.js.map +1 -0
- package/dist/core/providers/provider-registry.d.ts +62 -0
- package/dist/core/providers/provider-registry.js +143 -0
- package/dist/core/providers/provider-registry.js.map +1 -0
- package/dist/core/recall/citation-log.d.ts +28 -0
- package/dist/core/recall/citation-log.js +90 -0
- package/dist/core/recall/citation-log.js.map +1 -0
- package/dist/core/recall/filter.d.ts +11 -0
- package/dist/core/recall/filter.js +20 -0
- package/dist/core/recall/filter.js.map +1 -0
- package/dist/core/recall/index.d.ts +6 -0
- package/dist/core/recall/index.js +5 -0
- package/dist/core/recall/index.js.map +1 -0
- package/dist/core/recall/match-fields.d.ts +10 -0
- package/dist/core/recall/match-fields.js +37 -0
- package/dist/core/recall/match-fields.js.map +1 -0
- package/dist/core/recall/query-log.d.ts +36 -0
- package/dist/core/recall/query-log.js +112 -0
- package/dist/core/recall/query-log.js.map +1 -0
- package/dist/core/recall/query-shape.d.ts +22 -0
- package/dist/core/recall/query-shape.js +64 -0
- package/dist/core/recall/query-shape.js.map +1 -0
- package/dist/core/recall/recall-service.d.ts +19 -0
- package/dist/core/recall/recall-service.js +252 -0
- package/dist/core/recall/recall-service.js.map +1 -0
- package/dist/core/recall/recent-log.d.ts +16 -0
- package/dist/core/recall/recent-log.js +46 -0
- package/dist/core/recall/recent-log.js.map +1 -0
- package/dist/core/recall/tokenize.d.ts +7 -0
- package/dist/core/recall/tokenize.js +18 -0
- package/dist/core/recall/tokenize.js.map +1 -0
- package/dist/core/recall/useful-scan.d.ts +52 -0
- package/dist/core/recall/useful-scan.js +300 -0
- package/dist/core/recall/useful-scan.js.map +1 -0
- package/dist/core/recall-facts/fact-query-log.d.ts +42 -0
- package/dist/core/recall-facts/fact-query-log.js +115 -0
- package/dist/core/recall-facts/fact-query-log.js.map +1 -0
- package/dist/core/recall-facts/fact-recall-service.d.ts +34 -0
- package/dist/core/recall-facts/fact-recall-service.js +246 -0
- package/dist/core/recall-facts/fact-recall-service.js.map +1 -0
- package/dist/core/scheduler/scan-once.d.ts +32 -0
- package/dist/core/scheduler/scan-once.js +100 -0
- package/dist/core/scheduler/scan-once.js.map +1 -0
- package/dist/core/scheduler/scheduler.d.ts +59 -0
- package/dist/core/scheduler/scheduler.js +158 -0
- package/dist/core/scheduler/scheduler.js.map +1 -0
- package/dist/core/sources/source-registry.d.ts +68 -0
- package/dist/core/sources/source-registry.js +208 -0
- package/dist/core/sources/source-registry.js.map +1 -0
- package/dist/core/storage/db-restore.d.ts +53 -0
- package/dist/core/storage/db-restore.js +113 -0
- package/dist/core/storage/db-restore.js.map +1 -0
- package/dist/core/storage/live-status.d.ts +15 -0
- package/dist/core/storage/live-status.js +43 -0
- package/dist/core/storage/live-status.js.map +1 -0
- package/dist/core/storage/migrate.d.ts +14 -0
- package/dist/core/storage/migrate.js +52 -0
- package/dist/core/storage/migrate.js.map +1 -0
- package/dist/core/storage/sqlite-fact-store.d.ts +50 -0
- package/dist/core/storage/sqlite-fact-store.js +256 -0
- package/dist/core/storage/sqlite-fact-store.js.map +1 -0
- package/dist/core/storage/sqlite-session-store.d.ts +152 -0
- package/dist/core/storage/sqlite-session-store.js +587 -0
- package/dist/core/storage/sqlite-session-store.js.map +1 -0
- package/dist/hook/pre-compact-hook.d.ts +26 -0
- package/dist/hook/pre-compact-hook.js +94 -0
- package/dist/hook/pre-compact-hook.js.map +1 -0
- package/dist/hook/prompt-recall-hook.d.ts +23 -0
- package/dist/hook/prompt-recall-hook.js +141 -0
- package/dist/hook/prompt-recall-hook.js.map +1 -0
- package/dist/hook/session-end-hook.d.ts +18 -0
- package/dist/hook/session-end-hook.js +67 -0
- package/dist/hook/session-end-hook.js.map +1 -0
- package/dist/hook/session-start-hook.d.ts +25 -0
- package/dist/hook/session-start-hook.js +129 -0
- package/dist/hook/session-start-hook.js.map +1 -0
- package/dist/hook/stop-hook.d.ts +38 -0
- package/dist/hook/stop-hook.js +171 -0
- package/dist/hook/stop-hook.js.map +1 -0
- package/dist/hook/subagent-start-hook.d.ts +30 -0
- package/dist/hook/subagent-start-hook.js +108 -0
- package/dist/hook/subagent-start-hook.js.map +1 -0
- package/dist/http/app.d.ts +65 -0
- package/dist/http/app.js +1009 -0
- package/dist/http/app.js.map +1 -0
- package/dist/install/claude-code.d.ts +57 -0
- package/dist/install/claude-code.js +76 -0
- package/dist/install/claude-code.js.map +1 -0
- package/dist/install/codex.d.ts +82 -0
- package/dist/install/codex.js +277 -0
- package/dist/install/codex.js.map +1 -0
- package/dist/install/hermes-agent.d.ts +35 -0
- package/dist/install/hermes-agent.js +48 -0
- package/dist/install/hermes-agent.js.map +1 -0
- package/dist/install/hermes.d.ts +29 -0
- package/dist/install/hermes.js +52 -0
- package/dist/install/hermes.js.map +1 -0
- package/dist/install/ollama.d.ts +54 -0
- package/dist/install/ollama.js +178 -0
- package/dist/install/ollama.js.map +1 -0
- package/dist/install/setup.d.ts +37 -0
- package/dist/install/setup.js +339 -0
- package/dist/install/setup.js.map +1 -0
- package/dist/llm/classifier-box.d.ts +29 -0
- package/dist/llm/classifier-box.js +43 -0
- package/dist/llm/classifier-box.js.map +1 -0
- package/dist/llm/deepseek-client.d.ts +40 -0
- package/dist/llm/deepseek-client.js +114 -0
- package/dist/llm/deepseek-client.js.map +1 -0
- package/dist/llm/env-autoload.d.ts +8 -0
- package/dist/llm/env-autoload.js +54 -0
- package/dist/llm/env-autoload.js.map +1 -0
- package/dist/llm/ollama-client.d.ts +47 -0
- package/dist/llm/ollama-client.js +156 -0
- package/dist/llm/ollama-client.js.map +1 -0
- package/dist/mcp/server.d.ts +64 -0
- package/dist/mcp/server.js +430 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/ports/fact-store.d.ts +82 -0
- package/dist/ports/fact-store.js +16 -0
- package/dist/ports/fact-store.js.map +1 -0
- package/dist/ports/llm-client.d.ts +42 -0
- package/dist/ports/llm-client.js +14 -0
- package/dist/ports/llm-client.js.map +1 -0
- package/dist/ports/logger.d.ts +13 -0
- package/dist/ports/logger.js +8 -0
- package/dist/ports/logger.js.map +1 -0
- package/dist/ports/session-store.d.ts +29 -0
- package/dist/ports/session-store.js +9 -0
- package/dist/ports/session-store.js.map +1 -0
- package/dist/ports/transcript-adapter.d.ts +48 -0
- package/dist/ports/transcript-adapter.js +15 -0
- package/dist/ports/transcript-adapter.js.map +1 -0
- package/dist/shared/types.d.ts +129 -0
- package/dist/shared/types.js +6 -0
- package/dist/shared/types.js.map +1 -0
- package/dist/ui/assets/index-BA6IpU8g.css +1 -0
- package/dist/ui/assets/index-B_qIVV0k.js +69 -0
- package/dist/ui/index.html +13 -0
- package/docs/methodology/re-derivation-rate.md +112 -0
- package/docs/methodology/useful-hit-rate.md +79 -0
- package/docs/plans/2026-05-20-fts5-lexical-recall.md +1088 -0
- package/docs/plans/2026-05-20-recall-daemon-wedge-fix.md +662 -0
- package/docs/plans/2026-05-20-recall-hook-design.md +131 -0
- package/docs/plans/2026-05-20-recall-hook-implementation.md +1222 -0
- package/docs/plans/desktop-product.md +69 -0
- package/docs/plans/factstore-design.md +236 -0
- package/logs/CHANGELOG/CHANGELOG-2026.md +1389 -0
- package/logs/CHANGELOG/CHANGELOG.md +320 -0
- package/migrations/000_initial_schema.sql +174 -0
- package/migrations/001_entity_type_rename.sql +17 -0
- package/migrations/002_adapter_state_extend.sql +12 -0
- package/migrations/003_session_embeddings.sql +11 -0
- package/migrations/004_facts.sql +46 -0
- package/migrations/005_sources.sql +31 -0
- package/migrations/006_providers.sql +33 -0
- package/migrations/007_source_tokens.sql +17 -0
- package/migrations/008_fts_rebuild.sql +9 -0
- package/migrations/009_session_embedding_chunks.sql +46 -0
- package/migrations/010_sources_opencode.sql +30 -0
- package/migrations/011_sources_hermes_agent.sql +30 -0
- package/migrations/012_sources_aider.sql +30 -0
- package/migrations/013_adapter_state_failure_count.sql +12 -0
- package/package.json +56 -0
- package/plugin/.codex-plugin/plugin.json +22 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/README.md +51 -0
- package/plugin/hooks/hooks.json +25 -0
- package/plugin/scripts/prompt-recall-hook.mjs +202 -0
- package/plugin/scripts/stop-hook.mjs +306 -0
- package/plugin-hermes-agent/README.md +49 -0
- package/plugin-hermes-agent/__init__.py +75 -0
- package/plugin-hermes-agent/plugin.yaml +15 -0
- package/scripts/backfill-citations.mjs +0 -0
- package/scripts/build-codex-plugin.mjs +61 -0
- package/scripts/deepseek-probe.mjs +67 -0
- package/scripts/extract-triples.mjs +207 -0
- package/scripts/longmemeval/embedding-cache.ts +77 -0
- package/scripts/longmemeval/fetch-dataset.sh +25 -0
- package/scripts/longmemeval/run-harness.ts +315 -0
- package/scripts/longmemeval/scorer.ts +99 -0
- package/scripts/longmemeval/tsconfig.json +9 -0
- package/scripts/longmemeval/types.ts +35 -0
- package/scripts/nlm-daily-digest.py +239 -0
- package/scripts/nlm-daily-digest.sh +28 -0
- package/src/cli/classify-parity.ts +257 -0
- package/src/cli/launchctl-helpers.ts +49 -0
- package/src/cli/nlm.ts +885 -0
- package/src/core/actions/actions-log.ts +118 -0
- package/src/core/actions/overlay.ts +117 -0
- package/src/core/adapters/aider.ts +205 -0
- package/src/core/adapters/claude-code.ts +293 -0
- package/src/core/adapters/common.ts +54 -0
- package/src/core/adapters/from-source.ts +57 -0
- package/src/core/adapters/hermes-agent.ts +240 -0
- package/src/core/adapters/hermes.ts +277 -0
- package/src/core/adapters/jsonl-generic.ts +208 -0
- package/src/core/adapters/opencode.ts +281 -0
- package/src/core/adapters/pi.ts +264 -0
- package/src/core/classifier/prompt.ts +200 -0
- package/src/core/dataset/build-dataset.ts +463 -0
- package/src/core/embedding/chunk-body.ts +76 -0
- package/src/core/embedding/embed-backfill.ts +210 -0
- package/src/core/embedding/embed-normalize.ts +135 -0
- package/src/core/facts/backfill-facts.ts +254 -0
- package/src/core/facts/extract-facts.ts +50 -0
- package/src/core/hook/citation-detect.ts +124 -0
- package/src/core/hook/cite-memo.ts +68 -0
- package/src/core/hook/claude-settings.ts +166 -0
- package/src/core/hook/gate.ts +25 -0
- package/src/core/hook/hook-log.ts +41 -0
- package/src/core/hook/memo-sweep.ts +164 -0
- package/src/core/hook/memo.ts +67 -0
- package/src/core/hook/pointer-block.ts +26 -0
- package/src/core/hook/select.ts +32 -0
- package/src/core/hook/transcript.ts +121 -0
- package/src/core/ingest/ingest-session.ts +111 -0
- package/src/core/providers/provider-models.ts +100 -0
- package/src/core/providers/provider-registry.ts +196 -0
- package/src/core/recall/citation-log.ts +108 -0
- package/src/core/recall/filter.ts +27 -0
- package/src/core/recall/index.ts +6 -0
- package/src/core/recall/match-fields.ts +40 -0
- package/src/core/recall/query-log.ts +149 -0
- package/src/core/recall/query-shape.ts +66 -0
- package/src/core/recall/recall-service.ts +320 -0
- package/src/core/recall/recent-log.ts +59 -0
- package/src/core/recall/tokenize.ts +18 -0
- package/src/core/recall/useful-scan.ts +336 -0
- package/src/core/recall-facts/fact-query-log.ts +150 -0
- package/src/core/recall-facts/fact-recall-service.ts +327 -0
- package/src/core/scheduler/scan-once.ts +142 -0
- package/src/core/scheduler/scheduler.ts +225 -0
- package/src/core/sources/source-registry.ts +260 -0
- package/src/core/storage/db-restore.ts +133 -0
- package/src/core/storage/live-status.ts +45 -0
- package/src/core/storage/migrate.ts +72 -0
- package/src/core/storage/sqlite-fact-store.ts +304 -0
- package/src/core/storage/sqlite-session-store.ts +765 -0
- package/src/hook/prompt-recall-hook.ts +174 -0
- package/src/hook/session-end-hook.ts +81 -0
- package/src/hook/session-start-hook.ts +165 -0
- package/src/hook/stop-hook.ts +236 -0
- package/src/http/app.ts +1114 -0
- package/src/install/claude-code.ts +128 -0
- package/src/install/codex.ts +367 -0
- package/src/install/hermes-agent.ts +76 -0
- package/src/install/hermes.ts +78 -0
- package/src/install/ollama.ts +208 -0
- package/src/install/setup.ts +368 -0
- package/src/llm/classifier-box.ts +64 -0
- package/src/llm/deepseek-client.ts +150 -0
- package/src/llm/env-autoload.ts +55 -0
- package/src/llm/ollama-client.ts +189 -0
- package/src/mcp/server.ts +534 -0
- package/src/ports/fact-store.ts +102 -0
- package/src/ports/llm-client.ts +52 -0
- package/src/ports/logger.ts +16 -0
- package/src/ports/session-store.ts +45 -0
- package/src/ports/transcript-adapter.ts +55 -0
- package/src/shared/types.ts +145 -0
- package/src/ui/App.tsx +58 -0
- package/src/ui/components/PromoteOpenButton.tsx +65 -0
- package/src/ui/components/SessionDrawer.tsx +136 -0
- package/src/ui/components/SideNav.tsx +162 -0
- package/src/ui/components/Skeleton.tsx +107 -0
- package/src/ui/index.html +13 -0
- package/src/ui/lib/actions.ts +30 -0
- package/src/ui/lib/api.ts +92 -0
- package/src/ui/lib/dataset.ts +141 -0
- package/src/ui/lib/registries.ts +155 -0
- package/src/ui/lib/view-settings.ts +41 -0
- package/src/ui/main.tsx +15 -0
- package/src/ui/pages/Live.tsx +229 -0
- package/src/ui/pages/Pulse.tsx +415 -0
- package/src/ui/pages/Recall.tsx +190 -0
- package/src/ui/pages/River.tsx +308 -0
- package/src/ui/pages/Search.tsx +93 -0
- package/src/ui/pages/Stub.tsx +9 -0
- package/src/ui/pages/Thread.tsx +262 -0
- package/src/ui/pages/settings/Classifier.tsx +227 -0
- package/src/ui/pages/settings/Data.tsx +190 -0
- package/src/ui/pages/settings/Index.tsx +65 -0
- package/src/ui/pages/settings/Labels.tsx +224 -0
- package/src/ui/pages/settings/Providers.tsx +305 -0
- package/src/ui/pages/settings/SettingsSubnav.tsx +28 -0
- package/src/ui/pages/settings/Sources.tsx +326 -0
- package/src/ui/pages/settings/Views.tsx +96 -0
- package/src/ui/styles.css +1766 -0
- package/src/ui/tsconfig.json +21 -0
- package/src/ui/vite.config.ts +19 -0
- package/tests/fixtures/claude_code/short_session.jsonl +2 -0
- package/tests/fixtures/claude_code/standard_iso.jsonl +4 -0
- package/tests/fixtures/claude_code/tool_heavy.jsonl +8 -0
- package/tests/fixtures/claude_code/with_subagent.jsonl +7 -0
- package/tests/fixtures/facts.ts +17 -0
- package/tests/fixtures/golden-corpus.ts +85 -0
- package/tests/fixtures/hermes/paired_request_dump.json +24 -0
- package/tests/fixtures/hermes/paired_session.json +23 -0
- package/tests/fixtures/hermes/request_dump.json +28 -0
- package/tests/fixtures/hermes/session_iso.json +38 -0
- package/tests/fixtures/hermes/session_unix.json +38 -0
- package/tests/fixtures/hermes/system_only.json +18 -0
- package/tests/fixtures/pi/error-connection-abort.jsonl +8 -0
- package/tests/fixtures/pi/short-successful.jsonl +5 -0
- package/tests/fixtures/pi/with-custom-message.jsonl +6 -0
- package/tests/fixtures/sessions.ts +22 -0
- package/tests/integration/backfill-facts.test.ts +362 -0
- package/tests/integration/citation-explicit.test.ts +111 -0
- package/tests/integration/cite-event.test.ts +169 -0
- package/tests/integration/cite-memo.test.ts +87 -0
- package/tests/integration/db-restore.test.ts +153 -0
- package/tests/integration/embed-backfill.test.ts +176 -0
- package/tests/integration/fact-supersedence.test.ts +313 -0
- package/tests/integration/fts-index.test.ts +60 -0
- package/tests/integration/getbyids-sqlite.test.ts +60 -0
- package/tests/integration/hermes-agent-hooks.test.ts +248 -0
- package/tests/integration/hook-claude-settings.test.ts +205 -0
- package/tests/integration/hook-log.test.ts +54 -0
- package/tests/integration/hook-memo.test.ts +68 -0
- package/tests/integration/hook-pre-compact.test.ts +105 -0
- package/tests/integration/hook-subagent-start.test.ts +102 -0
- package/tests/integration/http.test.ts +401 -0
- package/tests/integration/keyword-search-fts.test.ts +66 -0
- package/tests/integration/mcp-recall-logging.test.ts +88 -0
- package/tests/integration/mcp.test.ts +248 -0
- package/tests/integration/memo-sweep.test.ts +91 -0
- package/tests/integration/prompt-recall-hook.test.ts +88 -0
- package/tests/integration/provider-registry.test.ts +107 -0
- package/tests/integration/recall-golden.test.ts +59 -0
- package/tests/integration/recall-sqlite.test.ts +169 -0
- package/tests/integration/scheduler.test.ts +391 -0
- package/tests/integration/session-end-hook.test.ts +48 -0
- package/tests/integration/session-start-hook.test.ts +126 -0
- package/tests/integration/source-registry.test.ts +120 -0
- package/tests/integration/sqlite-fact-store.test.ts +346 -0
- package/tests/integration/stop-hook.test.ts +560 -0
- package/tests/integration/wal-checkpoint.test.ts +49 -0
- package/tests/unit/cli/launchctl-helpers.test.ts +60 -0
- package/tests/unit/core/adapters/aider.test.ts +230 -0
- package/tests/unit/core/adapters/claude-code.test.ts +118 -0
- package/tests/unit/core/adapters/hermes-agent.test.ts +329 -0
- package/tests/unit/core/adapters/hermes.test.ts +81 -0
- package/tests/unit/core/adapters/jsonl-generic.test.ts +142 -0
- package/tests/unit/core/adapters/opencode.test.ts +354 -0
- package/tests/unit/core/adapters/pi.test.ts +110 -0
- package/tests/unit/core/classifier/prompt.test.ts +126 -0
- package/tests/unit/core/embedding/chunk-body.test.ts +100 -0
- package/tests/unit/core/facts/extract-facts.test.ts +117 -0
- package/tests/unit/core/filter.test.ts +40 -0
- package/tests/unit/core/hook/citation-detect-cite-session.test.ts +96 -0
- package/tests/unit/core/hook/citation-detect.test.ts +124 -0
- package/tests/unit/core/hook/gate.test.ts +29 -0
- package/tests/unit/core/hook/pointer-block.test.ts +22 -0
- package/tests/unit/core/hook/select.test.ts +66 -0
- package/tests/unit/core/match-fields.test.ts +39 -0
- package/tests/unit/core/mcp-cite-session.test.ts +51 -0
- package/tests/unit/core/providers/provider-models.test.ts +101 -0
- package/tests/unit/core/query-shape.test.ts +92 -0
- package/tests/unit/core/recall-facts/fact-recall-service.test.ts +258 -0
- package/tests/unit/core/recall-service.test.ts +200 -0
- package/tests/unit/core/storage/live-status.test.ts +54 -0
- package/tests/unit/core/tokenize.test.ts +32 -0
- package/tests/unit/core/useful-scan.test.ts +537 -0
- package/tests/unit/llm/embed.test.ts +93 -0
- package/tests/unit/llm/ollama-client.test.ts +124 -0
- package/tests/unit/scripts/longmemeval-scorer.test.ts +114 -0
- package/tsconfig.json +31 -0
- package/tsconfig.test.json +11 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Idle sweep for per-conversation hook memo files.
|
|
3
|
+
*
|
|
4
|
+
* The SessionEnd hook is best-effort — Claude Code doesn't fire it on
|
|
5
|
+
* crashes, kill -9, or IDE force-close. Without a backstop, memo files
|
|
6
|
+
* at ~/.nlm/hook-state/<conv>.json accumulate forever for any session
|
|
7
|
+
* that didn't close cleanly.
|
|
8
|
+
*
|
|
9
|
+
* This sweep is the daemon-side backstop. It runs on a timer, scans the
|
|
10
|
+
* state directory, and deletes any memo whose mtime is older than the
|
|
11
|
+
* dormant threshold. Reuses the same `age > day` threshold the dataset
|
|
12
|
+
* builder uses to mark runtimes as "dormant" so the UI/dataset semantics
|
|
13
|
+
* stay consistent across the system.
|
|
14
|
+
*
|
|
15
|
+
* Hooks are fast-path; this is the always-correct backstop.
|
|
16
|
+
*/
|
|
17
|
+
import { existsSync, readdirSync, rmSync, statSync } from "node:fs";
|
|
18
|
+
import { homedir } from "node:os";
|
|
19
|
+
import { join } from "node:path";
|
|
20
|
+
// Mirrors the dormant threshold in build-dataset.ts:
|
|
21
|
+
// age <= hour → "active"
|
|
22
|
+
// age <= day → "idle"
|
|
23
|
+
// age > day → "dormant"
|
|
24
|
+
// We sweep memos that are dormant.
|
|
25
|
+
const DEFAULT_DORMANT_MS = 24 * 60 * 60 * 1000;
|
|
26
|
+
const DEFAULT_INTERVAL_MS = 5 * 60 * 1000;
|
|
27
|
+
function defaultStateDir() {
|
|
28
|
+
return process.env["NLM_HOOK_STATE_DIR"] ?? join(homedir(), ".nlm", "hook-state");
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* One-shot sweep. Returns the report; safe to call from tests or one-off
|
|
32
|
+
* CLI invocations without standing up the scheduler.
|
|
33
|
+
*/
|
|
34
|
+
export function sweepMemoDir(opts = {}) {
|
|
35
|
+
const stateDir = opts.stateDir ?? defaultStateDir();
|
|
36
|
+
const dormantMs = opts.dormantMs ?? DEFAULT_DORMANT_MS;
|
|
37
|
+
const now = opts.now ?? Date.now;
|
|
38
|
+
const logger = opts.logger ?? ((msg) => console.error(msg));
|
|
39
|
+
if (!existsSync(stateDir)) {
|
|
40
|
+
return { scanned: 0, deleted: 0, kept: 0, errors: 0 };
|
|
41
|
+
}
|
|
42
|
+
let entries;
|
|
43
|
+
try {
|
|
44
|
+
entries = readdirSync(stateDir);
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
logger(`[memo-sweep] readdir failed for ${stateDir}: ${e instanceof Error ? e.message : String(e)}`);
|
|
48
|
+
return { scanned: 0, deleted: 0, kept: 0, errors: 1 };
|
|
49
|
+
}
|
|
50
|
+
const cutoff = now() - dormantMs;
|
|
51
|
+
let deleted = 0;
|
|
52
|
+
let kept = 0;
|
|
53
|
+
let errors = 0;
|
|
54
|
+
for (const name of entries) {
|
|
55
|
+
if (!name.endsWith(".json")) {
|
|
56
|
+
// Don't touch files we don't own (kept silently, don't even count).
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
const path = join(stateDir, name);
|
|
60
|
+
try {
|
|
61
|
+
const stat = statSync(path);
|
|
62
|
+
if (stat.mtimeMs < cutoff) {
|
|
63
|
+
rmSync(path);
|
|
64
|
+
deleted += 1;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
kept += 1;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch (e) {
|
|
71
|
+
errors += 1;
|
|
72
|
+
logger(`[memo-sweep] failed on ${path}: ${e instanceof Error ? e.message : String(e)}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { scanned: deleted + kept + errors, deleted, kept, errors };
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Periodic sweep loop. Mirrors ScanScheduler's start/stop shape so the
|
|
79
|
+
* daemon can manage it the same way. First tick fires immediately on
|
|
80
|
+
* start() — the daemon picking up after a long downtime should sweep
|
|
81
|
+
* accumulated memos right away, not wait an interval.
|
|
82
|
+
*/
|
|
83
|
+
export class MemoSweepScheduler {
|
|
84
|
+
opts;
|
|
85
|
+
stopped = true;
|
|
86
|
+
timer = null;
|
|
87
|
+
constructor(opts = {}) {
|
|
88
|
+
this.opts = {
|
|
89
|
+
stateDir: opts.stateDir,
|
|
90
|
+
dormantMs: opts.dormantMs ?? DEFAULT_DORMANT_MS,
|
|
91
|
+
intervalMs: opts.intervalMs ?? DEFAULT_INTERVAL_MS,
|
|
92
|
+
logger: opts.logger ?? ((msg) => console.error(msg)),
|
|
93
|
+
now: opts.now,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
start() {
|
|
97
|
+
if (!this.stopped)
|
|
98
|
+
return;
|
|
99
|
+
this.stopped = false;
|
|
100
|
+
this.scheduleNext(0);
|
|
101
|
+
}
|
|
102
|
+
stop() {
|
|
103
|
+
this.stopped = true;
|
|
104
|
+
if (this.timer) {
|
|
105
|
+
clearTimeout(this.timer);
|
|
106
|
+
this.timer = null;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
tick() {
|
|
110
|
+
return sweepMemoDir({
|
|
111
|
+
dormantMs: this.opts.dormantMs,
|
|
112
|
+
logger: this.opts.logger,
|
|
113
|
+
...(this.opts.stateDir !== undefined ? { stateDir: this.opts.stateDir } : {}),
|
|
114
|
+
...(this.opts.now !== undefined ? { now: this.opts.now } : {}),
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
scheduleNext(delayMs) {
|
|
118
|
+
if (this.stopped)
|
|
119
|
+
return;
|
|
120
|
+
this.timer = setTimeout(() => {
|
|
121
|
+
try {
|
|
122
|
+
this.tick();
|
|
123
|
+
}
|
|
124
|
+
catch (e) {
|
|
125
|
+
this.opts.logger(`[memo-sweep] tick crashed: ${e instanceof Error ? e.message : String(e)}`);
|
|
126
|
+
}
|
|
127
|
+
this.scheduleNext(this.opts.intervalMs);
|
|
128
|
+
}, delayMs);
|
|
129
|
+
// Don't keep the event loop alive just for the sweep.
|
|
130
|
+
if (this.timer && typeof this.timer.unref === "function")
|
|
131
|
+
this.timer.unref();
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
//# sourceMappingURL=memo-sweep.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memo-sweep.js","sourceRoot":"","sources":["../../../src/core/hook/memo-sweep.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-conversation dedup memo for the recall hook. One JSON file per
|
|
3
|
+
* conversation holds the set of session ids already surfaced, so each is
|
|
4
|
+
* surfaced at most once per conversation.
|
|
5
|
+
*
|
|
6
|
+
* State dir defaults to ~/.nlm/hook-state/, overridable via
|
|
7
|
+
* NLM_HOOK_STATE_DIR (testability — mirrors query-log.ts).
|
|
8
|
+
*
|
|
9
|
+
* Every function is defensive: a missing or corrupt file yields an empty
|
|
10
|
+
* memo, and a write failure is swallowed. The hook must never break on memo
|
|
11
|
+
* I/O.
|
|
12
|
+
*/
|
|
13
|
+
export declare function loadSurfaced(conversationId: string): Set<string>;
|
|
14
|
+
export declare function recordSurfaced(conversationId: string, ids: ReadonlyArray<string>): void;
|
|
15
|
+
/**
|
|
16
|
+
* Delete the memo file for a closed conversation. Called by the SessionEnd
|
|
17
|
+
* hook so memo files don't accumulate forever. Returns true if a file was
|
|
18
|
+
* removed, false otherwise — callers may want to log the outcome.
|
|
19
|
+
*/
|
|
20
|
+
export declare function clearSurfaced(conversationId: string): boolean;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-conversation dedup memo for the recall hook. One JSON file per
|
|
3
|
+
* conversation holds the set of session ids already surfaced, so each is
|
|
4
|
+
* surfaced at most once per conversation.
|
|
5
|
+
*
|
|
6
|
+
* State dir defaults to ~/.nlm/hook-state/, overridable via
|
|
7
|
+
* NLM_HOOK_STATE_DIR (testability — mirrors query-log.ts).
|
|
8
|
+
*
|
|
9
|
+
* Every function is defensive: a missing or corrupt file yields an empty
|
|
10
|
+
* memo, and a write failure is swallowed. The hook must never break on memo
|
|
11
|
+
* I/O.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
14
|
+
import { homedir } from "node:os";
|
|
15
|
+
import { join } from "node:path";
|
|
16
|
+
function stateDir() {
|
|
17
|
+
return process.env["NLM_HOOK_STATE_DIR"] ?? join(homedir(), ".nlm", "hook-state");
|
|
18
|
+
}
|
|
19
|
+
function memoPath(conversationId) {
|
|
20
|
+
const safe = conversationId.replace(/[^A-Za-z0-9_-]/g, "_") || "unknown";
|
|
21
|
+
return join(stateDir(), `${safe}.json`);
|
|
22
|
+
}
|
|
23
|
+
export function loadSurfaced(conversationId) {
|
|
24
|
+
try {
|
|
25
|
+
const path = memoPath(conversationId);
|
|
26
|
+
if (!existsSync(path))
|
|
27
|
+
return new Set();
|
|
28
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
29
|
+
if (!Array.isArray(parsed))
|
|
30
|
+
return new Set();
|
|
31
|
+
return new Set(parsed.filter((x) => typeof x === "string"));
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return new Set();
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
export function recordSurfaced(conversationId, ids) {
|
|
38
|
+
try {
|
|
39
|
+
const merged = loadSurfaced(conversationId);
|
|
40
|
+
for (const id of ids)
|
|
41
|
+
merged.add(id);
|
|
42
|
+
mkdirSync(stateDir(), { recursive: true });
|
|
43
|
+
writeFileSync(memoPath(conversationId), JSON.stringify([...merged]), "utf8");
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// Memo write failure must never break the hook.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Delete the memo file for a closed conversation. Called by the SessionEnd
|
|
51
|
+
* hook so memo files don't accumulate forever. Returns true if a file was
|
|
52
|
+
* removed, false otherwise — callers may want to log the outcome.
|
|
53
|
+
*/
|
|
54
|
+
export function clearSurfaced(conversationId) {
|
|
55
|
+
try {
|
|
56
|
+
const path = memoPath(conversationId);
|
|
57
|
+
if (!existsSync(path))
|
|
58
|
+
return false;
|
|
59
|
+
rmSync(path);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
//# sourceMappingURL=memo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memo.js","sourceRoot":"","sources":["../../../src/core/hook/memo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AACrF,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,SAAS,QAAQ;IACf,OAAO,OAAO,CAAC,GAAG,CAAC,oBAAoB,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC;AACpF,CAAC;AAED,SAAS,QAAQ,CAAC,cAAsB;IACtC,MAAM,IAAI,GAAG,cAAc,CAAC,OAAO,CAAC,iBAAiB,EAAE,GAAG,CAAC,IAAI,SAAS,CAAC;IACzE,OAAO,IAAI,CAAC,QAAQ,EAAE,EAAE,GAAG,IAAI,OAAO,CAAC,CAAC;AAC1C,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,cAAsB;IACjD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;QACtC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,GAAG,EAAE,CAAC;QACxC,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,CAAC;QAC/D,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YAAE,OAAO,IAAI,GAAG,EAAE,CAAC;QAC7C,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAe,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,CAAC,CAAC,CAAC;IAC3E,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,GAAG,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,cAAc,CAC5B,cAAsB,EACtB,GAA0B;IAE1B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,YAAY,CAAC,cAAc,CAAC,CAAC;QAC5C,KAAK,MAAM,EAAE,IAAI,GAAG;YAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrC,SAAS,CAAC,QAAQ,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC3C,aAAa,CAAC,QAAQ,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,GAAG,MAAM,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IAC/E,CAAC;IAAC,MAAM,CAAC;QACP,gDAAgD;IAClD,CAAC;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,cAAsB;IAClD,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;QACtC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,OAAO,KAAK,CAAC;QACpC,MAAM,CAAC,IAAI,CAAC,CAAC;QACb,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders the pointer block injected by the recall hook in live mode. Pure.
|
|
3
|
+
* Pointer-only by design: ids + labels, no session content. The footer
|
|
4
|
+
* names all four NLM MCP tools because the pointer block is the only
|
|
5
|
+
* cross-runtime distribution surface for teaching the tool inventory —
|
|
6
|
+
* fresh-install users never edit a prompt or settings file, so anything
|
|
7
|
+
* we want the agent to know about the tool surface ships here.
|
|
8
|
+
*/
|
|
9
|
+
export interface PointerHit {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
readonly label: string;
|
|
12
|
+
readonly startedAt: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function formatPointerBlock(hits: ReadonlyArray<PointerHit>): string;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renders the pointer block injected by the recall hook in live mode. Pure.
|
|
3
|
+
* Pointer-only by design: ids + labels, no session content. The footer
|
|
4
|
+
* names all four NLM MCP tools because the pointer block is the only
|
|
5
|
+
* cross-runtime distribution surface for teaching the tool inventory —
|
|
6
|
+
* fresh-install users never edit a prompt or settings file, so anything
|
|
7
|
+
* we want the agent to know about the tool surface ships here.
|
|
8
|
+
*/
|
|
9
|
+
export function formatPointerBlock(hits) {
|
|
10
|
+
if (hits.length === 0)
|
|
11
|
+
return "";
|
|
12
|
+
const lines = hits.map((h) => `- ${h.id} · ${h.label} (${h.startedAt.slice(0, 10)})`);
|
|
13
|
+
return [
|
|
14
|
+
"## Possibly-relevant prior sessions (nlm-memory)",
|
|
15
|
+
...lines,
|
|
16
|
+
"NLM tools: recall_sessions (search), get_session (full transcript), recall_facts (prior decisions), get_fact_history (how a decision evolved).",
|
|
17
|
+
].join("\n");
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=pointer-block.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pointer-block.js","sourceRoot":"","sources":["../../../src/core/hook/pointer-block.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,MAAM,UAAU,kBAAkB,CAAC,IAA+B;IAChE,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CACpB,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAC9D,CAAC;IACF,OAAO;QACL,kDAAkD;QAClD,GAAG,KAAK;QACR,gJAAgJ;KACjJ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selects which recall hits the hook surfaces. Pure — no I/O.
|
|
3
|
+
*
|
|
4
|
+
* Order of filtering: score threshold, then dedup against ids already
|
|
5
|
+
* surfaced in this conversation, then the per-fire cap bounded by the
|
|
6
|
+
* remaining per-conversation budget. Hits are assumed pre-ranked best-first.
|
|
7
|
+
*/
|
|
8
|
+
export interface RecallHitInput {
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly label: string;
|
|
11
|
+
readonly startedAt: string;
|
|
12
|
+
readonly matchScore: number;
|
|
13
|
+
}
|
|
14
|
+
export interface SelectParams {
|
|
15
|
+
readonly hits: ReadonlyArray<RecallHitInput>;
|
|
16
|
+
readonly surfaced: ReadonlySet<string>;
|
|
17
|
+
readonly scoreThreshold: number;
|
|
18
|
+
readonly perFireCap: number;
|
|
19
|
+
readonly perConversationCap: number;
|
|
20
|
+
}
|
|
21
|
+
export declare function selectHits(params: SelectParams): ReadonlyArray<RecallHitInput>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Selects which recall hits the hook surfaces. Pure — no I/O.
|
|
3
|
+
*
|
|
4
|
+
* Order of filtering: score threshold, then dedup against ids already
|
|
5
|
+
* surfaced in this conversation, then the per-fire cap bounded by the
|
|
6
|
+
* remaining per-conversation budget. Hits are assumed pre-ranked best-first.
|
|
7
|
+
*/
|
|
8
|
+
export function selectHits(params) {
|
|
9
|
+
const { hits, surfaced, scoreThreshold, perFireCap, perConversationCap } = params;
|
|
10
|
+
const eligible = hits.filter((h) => h.matchScore >= scoreThreshold && !surfaced.has(h.id));
|
|
11
|
+
const budget = Math.max(0, perConversationCap - surfaced.size);
|
|
12
|
+
const limit = Math.min(perFireCap, budget);
|
|
13
|
+
return eligible.slice(0, limit);
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=select.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"select.js","sourceRoot":"","sources":["../../../src/core/hook/select.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAiBH,MAAM,UAAU,UAAU,CAAC,MAAoB;IAC7C,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,cAAc,EAAE,UAAU,EAAE,kBAAkB,EAAE,GAAG,MAAM,CAAC;IAClF,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAC1B,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,IAAI,cAAc,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAC7D,CAAC;IACF,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,kBAAkB,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;IAC3C,OAAO,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read assistant messages from a Claude Code transcript JSONL.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code passes `transcript_path` in the Stop hook payload. Each line is
|
|
5
|
+
* a JSON object; assistant turns have `type:"assistant"` and a `message`
|
|
6
|
+
* object whose `content` is an array of blocks (`{type:"text", text:...}` for
|
|
7
|
+
* prose; `{type:"tool_use", name, input}` for tool invocations).
|
|
8
|
+
*
|
|
9
|
+
* Stop-hook citation detection needs the union of ALL assistant turns in the
|
|
10
|
+
* transcript, not just the last one: the model typically calls a tool, reads
|
|
11
|
+
* the result on the next user turn (tool_result), then writes a prose summary
|
|
12
|
+
* as a separate assistant turn. Scanning only the last turn misses the
|
|
13
|
+
* tool_use entirely. `readAllAssistantTurns` returns every assistant turn in
|
|
14
|
+
* order so the detector can fire across the whole conversation; cross-firing
|
|
15
|
+
* dedup happens upstream via the per-conversation cited memo.
|
|
16
|
+
*
|
|
17
|
+
* Fail-quiet: a malformed file yields nulls/empty rather than throwing —
|
|
18
|
+
* the Stop hook must never break on transcript I/O.
|
|
19
|
+
*/
|
|
20
|
+
export interface ToolUseBlock {
|
|
21
|
+
readonly name: string;
|
|
22
|
+
readonly input: unknown;
|
|
23
|
+
}
|
|
24
|
+
export interface AssistantTurn {
|
|
25
|
+
readonly text: string;
|
|
26
|
+
readonly toolUses: ReadonlyArray<ToolUseBlock>;
|
|
27
|
+
}
|
|
28
|
+
export declare function readAllAssistantTurns(transcriptPath: string): ReadonlyArray<AssistantTurn>;
|
|
29
|
+
export declare function readLastAssistantTurn(transcriptPath: string): AssistantTurn;
|
|
30
|
+
/** Back-compat shim for callers that only need prose. */
|
|
31
|
+
export declare function readLastAssistantText(transcriptPath: string): string | null;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read assistant messages from a Claude Code transcript JSONL.
|
|
3
|
+
*
|
|
4
|
+
* Claude Code passes `transcript_path` in the Stop hook payload. Each line is
|
|
5
|
+
* a JSON object; assistant turns have `type:"assistant"` and a `message`
|
|
6
|
+
* object whose `content` is an array of blocks (`{type:"text", text:...}` for
|
|
7
|
+
* prose; `{type:"tool_use", name, input}` for tool invocations).
|
|
8
|
+
*
|
|
9
|
+
* Stop-hook citation detection needs the union of ALL assistant turns in the
|
|
10
|
+
* transcript, not just the last one: the model typically calls a tool, reads
|
|
11
|
+
* the result on the next user turn (tool_result), then writes a prose summary
|
|
12
|
+
* as a separate assistant turn. Scanning only the last turn misses the
|
|
13
|
+
* tool_use entirely. `readAllAssistantTurns` returns every assistant turn in
|
|
14
|
+
* order so the detector can fire across the whole conversation; cross-firing
|
|
15
|
+
* dedup happens upstream via the per-conversation cited memo.
|
|
16
|
+
*
|
|
17
|
+
* Fail-quiet: a malformed file yields nulls/empty rather than throwing —
|
|
18
|
+
* the Stop hook must never break on transcript I/O.
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
21
|
+
const EMPTY_TURN = { text: "", toolUses: [] };
|
|
22
|
+
function parseTurn(parsed) {
|
|
23
|
+
if (parsed.type !== "assistant" || !parsed.message)
|
|
24
|
+
return null;
|
|
25
|
+
const content = parsed.message.content;
|
|
26
|
+
if (typeof content === "string") {
|
|
27
|
+
return content ? { text: content, toolUses: [] } : null;
|
|
28
|
+
}
|
|
29
|
+
if (!Array.isArray(content))
|
|
30
|
+
return null;
|
|
31
|
+
const textParts = [];
|
|
32
|
+
const toolUses = [];
|
|
33
|
+
for (const block of content) {
|
|
34
|
+
if (block.type === "text" && typeof block.text === "string") {
|
|
35
|
+
textParts.push(block.text);
|
|
36
|
+
}
|
|
37
|
+
else if (block.type === "tool_use" && typeof block.name === "string") {
|
|
38
|
+
toolUses.push({ name: block.name, input: block.input });
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (textParts.length === 0 && toolUses.length === 0)
|
|
42
|
+
return null;
|
|
43
|
+
return { text: textParts.join("\n"), toolUses };
|
|
44
|
+
}
|
|
45
|
+
function readLines(transcriptPath) {
|
|
46
|
+
if (!transcriptPath || !existsSync(transcriptPath))
|
|
47
|
+
return null;
|
|
48
|
+
try {
|
|
49
|
+
return readFileSync(transcriptPath, "utf8").split("\n");
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function readAllAssistantTurns(transcriptPath) {
|
|
56
|
+
const lines = readLines(transcriptPath);
|
|
57
|
+
if (!lines)
|
|
58
|
+
return [];
|
|
59
|
+
const turns = [];
|
|
60
|
+
for (const raw of lines) {
|
|
61
|
+
const line = raw.trim();
|
|
62
|
+
if (!line)
|
|
63
|
+
continue;
|
|
64
|
+
let parsed;
|
|
65
|
+
try {
|
|
66
|
+
parsed = JSON.parse(line);
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const turn = parseTurn(parsed);
|
|
72
|
+
if (turn)
|
|
73
|
+
turns.push(turn);
|
|
74
|
+
}
|
|
75
|
+
return turns;
|
|
76
|
+
}
|
|
77
|
+
export function readLastAssistantTurn(transcriptPath) {
|
|
78
|
+
const lines = readLines(transcriptPath);
|
|
79
|
+
if (!lines)
|
|
80
|
+
return EMPTY_TURN;
|
|
81
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
82
|
+
const line = lines[i]?.trim();
|
|
83
|
+
if (!line)
|
|
84
|
+
continue;
|
|
85
|
+
let parsed;
|
|
86
|
+
try {
|
|
87
|
+
parsed = JSON.parse(line);
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
const turn = parseTurn(parsed);
|
|
93
|
+
if (turn)
|
|
94
|
+
return turn;
|
|
95
|
+
}
|
|
96
|
+
return EMPTY_TURN;
|
|
97
|
+
}
|
|
98
|
+
/** Back-compat shim for callers that only need prose. */
|
|
99
|
+
export function readLastAssistantText(transcriptPath) {
|
|
100
|
+
const turn = readLastAssistantTurn(transcriptPath);
|
|
101
|
+
return turn.text || null;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=transcript.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript.js","sourceRoot":"","sources":["../../../src/core/hook/transcript.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ingestSession — push a single externally-supplied session through the
|
|
3
|
+
* normal classifier → embedder → store pipeline.
|
|
4
|
+
*
|
|
5
|
+
* Shared by the webhook endpoint (POST /api/ingest) and anything else
|
|
6
|
+
* that wants to push without going through a TranscriptAdapter. Mirrors
|
|
7
|
+
* the inner loop of ScanScheduler.runOnce but accepts a pre-built chunk.
|
|
8
|
+
*/
|
|
9
|
+
import type { SqliteFactStore } from "../storage/sqlite-fact-store.js";
|
|
10
|
+
import type { SqliteSessionStore } from "../storage/sqlite-session-store.js";
|
|
11
|
+
import type { LLMClient } from "../../ports/llm-client.js";
|
|
12
|
+
export interface IngestInput {
|
|
13
|
+
/** Optional — if omitted, derived from a hash of (runtime + startedAt + text). */
|
|
14
|
+
readonly id?: string;
|
|
15
|
+
readonly runtime: string;
|
|
16
|
+
readonly runtimeSessionId?: string | null;
|
|
17
|
+
readonly text: string;
|
|
18
|
+
readonly startedAt?: string;
|
|
19
|
+
readonly endedAt?: string | null;
|
|
20
|
+
readonly transcriptPath?: string | null;
|
|
21
|
+
/** Webhook id when the source is webhook-pushed; null for generic. */
|
|
22
|
+
readonly sourceId?: number | null;
|
|
23
|
+
}
|
|
24
|
+
export interface IngestDeps {
|
|
25
|
+
readonly classifier: LLMClient;
|
|
26
|
+
readonly embedder: LLMClient;
|
|
27
|
+
readonly store: SqliteSessionStore;
|
|
28
|
+
readonly factStore?: SqliteFactStore;
|
|
29
|
+
/** Optional logger — defaults to console.error. */
|
|
30
|
+
readonly log?: (msg: string) => void;
|
|
31
|
+
}
|
|
32
|
+
export interface IngestResult {
|
|
33
|
+
readonly id: string;
|
|
34
|
+
readonly status: "ingested" | "low_confidence" | "classifier_failed";
|
|
35
|
+
readonly latencyMs: number;
|
|
36
|
+
readonly confidence?: number;
|
|
37
|
+
readonly error?: string;
|
|
38
|
+
}
|
|
39
|
+
export declare function deriveSessionId(runtime: string, startedAt: string, text: string): string;
|
|
40
|
+
export declare function ingestSession(input: IngestInput, deps: IngestDeps): Promise<IngestResult>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ingestSession — push a single externally-supplied session through the
|
|
3
|
+
* normal classifier → embedder → store pipeline.
|
|
4
|
+
*
|
|
5
|
+
* Shared by the webhook endpoint (POST /api/ingest) and anything else
|
|
6
|
+
* that wants to push without going through a TranscriptAdapter. Mirrors
|
|
7
|
+
* the inner loop of ScanScheduler.runOnce but accepts a pre-built chunk.
|
|
8
|
+
*/
|
|
9
|
+
import { createHash } from "node:crypto";
|
|
10
|
+
import { extractFacts } from "../facts/extract-facts.js";
|
|
11
|
+
const BODY_CAP = 200_000;
|
|
12
|
+
const CONFIDENCE_FLOOR = 0.3;
|
|
13
|
+
export function deriveSessionId(runtime, startedAt, text) {
|
|
14
|
+
const hash = createHash("sha256")
|
|
15
|
+
.update(runtime)
|
|
16
|
+
.update("|")
|
|
17
|
+
.update(startedAt)
|
|
18
|
+
.update("|")
|
|
19
|
+
.update(text.slice(0, 4_000))
|
|
20
|
+
.digest("hex")
|
|
21
|
+
.slice(0, 16);
|
|
22
|
+
return `webhook_${hash}`;
|
|
23
|
+
}
|
|
24
|
+
export async function ingestSession(input, deps) {
|
|
25
|
+
const startedAt = input.startedAt ?? new Date().toISOString();
|
|
26
|
+
const id = input.id ?? deriveSessionId(input.runtime, startedAt, input.text);
|
|
27
|
+
const log = deps.log ?? ((m) => console.error(m));
|
|
28
|
+
const t0 = Date.now();
|
|
29
|
+
let classification;
|
|
30
|
+
try {
|
|
31
|
+
classification = await deps.classifier.classify(input.text);
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
const error = e instanceof Error ? e.message : String(e);
|
|
35
|
+
log(`[ingest] classifier failed for ${id}: ${error}`);
|
|
36
|
+
return { id, status: "classifier_failed", latencyMs: Date.now() - t0, error };
|
|
37
|
+
}
|
|
38
|
+
if (classification.confidence < CONFIDENCE_FLOOR) {
|
|
39
|
+
return {
|
|
40
|
+
id,
|
|
41
|
+
status: "low_confidence",
|
|
42
|
+
latencyMs: Date.now() - t0,
|
|
43
|
+
confidence: classification.confidence,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
const record = {
|
|
47
|
+
id,
|
|
48
|
+
runtime: input.runtime,
|
|
49
|
+
runtimeSessionId: input.runtimeSessionId ?? null,
|
|
50
|
+
startedAt,
|
|
51
|
+
endedAt: input.endedAt ?? null,
|
|
52
|
+
durationMin: null,
|
|
53
|
+
label: classification.label,
|
|
54
|
+
summary: classification.summary,
|
|
55
|
+
body: input.text.slice(0, BODY_CAP),
|
|
56
|
+
status: "closed",
|
|
57
|
+
transcriptKind: "webhook",
|
|
58
|
+
transcriptPath: input.transcriptPath ?? null,
|
|
59
|
+
transcriptOffset: null,
|
|
60
|
+
transcriptLength: null,
|
|
61
|
+
entities: classification.entities,
|
|
62
|
+
decisions: classification.decisions,
|
|
63
|
+
openQuestions: classification.open,
|
|
64
|
+
};
|
|
65
|
+
const factSink = deps.factStore
|
|
66
|
+
? { factStore: deps.factStore, facts: extractFacts(classification, id, startedAt) }
|
|
67
|
+
: null;
|
|
68
|
+
await deps.store.insertSession(record, deps.embedder, null, factSink);
|
|
69
|
+
return { id, status: "ingested", latencyMs: Date.now() - t0, confidence: classification.confidence };
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=ingest-session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ingest-session.js","sourceRoot":"","sources":["../../../src/core/ingest/ingest-session.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider model discovery — runtime lookup of available models.
|
|
3
|
+
*
|
|
4
|
+
* Per-kind strategy:
|
|
5
|
+
* - ollama: GET {baseUrl}/api/tags
|
|
6
|
+
* - openai: GET {baseUrl}/models with Bearer key
|
|
7
|
+
* - openrouter: GET {baseUrl}/models with Bearer key
|
|
8
|
+
* - openai-compatible: GET {baseUrl}/models, key optional
|
|
9
|
+
* - deepseek: hardcoded (no public list endpoint)
|
|
10
|
+
* - anthropic: hardcoded (their /v1/models exists but
|
|
11
|
+
* requires beta header + returns subsets;
|
|
12
|
+
* a hardcoded list is more reliable)
|
|
13
|
+
*
|
|
14
|
+
* Returns a flat `string[]`. Errors throw — callers (the HTTP endpoint
|
|
15
|
+
* and connection-test) catch and surface to the user.
|
|
16
|
+
*/
|
|
17
|
+
import type { ProviderRow } from "./provider-registry.js";
|
|
18
|
+
export type FetchImpl = typeof fetch;
|
|
19
|
+
export interface ListModelsOptions {
|
|
20
|
+
readonly apiKey?: string | null;
|
|
21
|
+
readonly fetchImpl?: FetchImpl;
|
|
22
|
+
readonly timeoutMs?: number;
|
|
23
|
+
}
|
|
24
|
+
export declare function listModels(provider: ProviderRow, opts?: ListModelsOptions): Promise<string[]>;
|