nlm-memory 0.19.0 → 0.20.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -10
- package/dist/cli/digest.js +21 -0
- package/dist/cli/digest.js.map +1 -1
- package/dist/cli/init.d.ts +12 -0
- package/dist/cli/init.js +49 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/nlm.d.ts +13 -1
- package/dist/cli/nlm.js +766 -55
- package/dist/cli/nlm.js.map +1 -1
- package/dist/core/actions/overlay.d.ts +20 -6
- package/dist/core/actions/overlay.js +32 -18
- package/dist/core/actions/overlay.js.map +1 -1
- package/dist/core/adapters/claude-code.js +6 -0
- package/dist/core/adapters/claude-code.js.map +1 -1
- package/dist/core/classifier/hierarchical-classify.d.ts +6 -2
- package/dist/core/classifier/hierarchical-classify.js +10 -6
- package/dist/core/classifier/hierarchical-classify.js.map +1 -1
- package/dist/core/classifier/prompt.js +3 -3
- package/dist/core/digest/compose.d.ts +10 -0
- package/dist/core/digest/compose.js +7 -1
- package/dist/core/digest/compose.js.map +1 -1
- package/dist/core/embedding/embed-backfill.d.ts +13 -2
- package/dist/core/embedding/embed-backfill.js +181 -43
- package/dist/core/embedding/embed-backfill.js.map +1 -1
- package/dist/core/embedding/embedding-config.d.ts +17 -0
- package/dist/core/embedding/embedding-config.js +12 -0
- package/dist/core/embedding/embedding-config.js.map +1 -0
- package/dist/core/embedding/pg-embed-backfill.d.ts +31 -0
- package/dist/core/embedding/pg-embed-backfill.js +264 -0
- package/dist/core/embedding/pg-embed-backfill.js.map +1 -0
- package/dist/core/entities/dedup-suggest.d.ts +25 -0
- package/dist/core/entities/dedup-suggest.js +107 -0
- package/dist/core/entities/dedup-suggest.js.map +1 -0
- package/dist/core/eval/classifier-fixture-eval.d.ts +29 -0
- package/dist/core/eval/classifier-fixture-eval.js +217 -0
- package/dist/core/eval/classifier-fixture-eval.js.map +1 -0
- package/dist/core/eval/extraction-scoring.d.ts +63 -0
- package/dist/core/eval/extraction-scoring.js +79 -0
- package/dist/core/eval/extraction-scoring.js.map +1 -0
- package/dist/core/eval/run-eval.d.ts +40 -0
- package/dist/core/eval/run-eval.js +34 -0
- package/dist/core/eval/run-eval.js.map +1 -0
- package/dist/core/exemplars/capture-from-session.d.ts +1 -0
- package/dist/core/exemplars/capture-from-session.js +6 -2
- package/dist/core/exemplars/capture-from-session.js.map +1 -1
- package/dist/core/exemplars/diff-parse.d.ts +23 -0
- package/dist/core/exemplars/diff-parse.js +73 -0
- package/dist/core/exemplars/diff-parse.js.map +1 -0
- package/dist/core/exemplars/embed-backfill.d.ts +36 -0
- package/dist/core/exemplars/embed-backfill.js +85 -0
- package/dist/core/exemplars/embed-backfill.js.map +1 -0
- package/dist/core/exemplars/embed-text.d.ts +15 -0
- package/dist/core/exemplars/embed-text.js +18 -0
- package/dist/core/exemplars/embed-text.js.map +1 -0
- package/dist/core/exemplars/extract-exemplar.js +2 -65
- package/dist/core/exemplars/extract-exemplar.js.map +1 -1
- package/dist/core/exemplars/recall-code.js +4 -0
- package/dist/core/exemplars/recall-code.js.map +1 -1
- package/dist/core/exemplars/warm-embedder.d.ts +14 -0
- package/dist/core/exemplars/warm-embedder.js +18 -0
- package/dist/core/exemplars/warm-embedder.js.map +1 -0
- package/dist/core/health/corpus-state.d.ts +8 -0
- package/dist/core/health/corpus-state.js +11 -0
- package/dist/core/health/corpus-state.js.map +1 -0
- package/dist/core/health/embed-inflight.d.ts +9 -0
- package/dist/core/health/embed-inflight.js +30 -0
- package/dist/core/health/embed-inflight.js.map +1 -0
- package/dist/core/health/embedding-lane-state.d.ts +6 -0
- package/dist/core/health/embedding-lane-state.js +18 -0
- package/dist/core/health/embedding-lane-state.js.map +1 -0
- package/dist/core/health/warmup-state.d.ts +12 -0
- package/dist/core/health/warmup-state.js +12 -0
- package/dist/core/health/warmup-state.js.map +1 -0
- package/dist/core/hook/gate.d.ts +13 -6
- package/dist/core/hook/gate.js +47 -6
- package/dist/core/hook/gate.js.map +1 -1
- package/dist/core/hook/hook-log.d.ts +5 -0
- package/dist/core/hook/hook-log.js.map +1 -1
- package/dist/core/hook/memo.d.ts +12 -0
- package/dist/core/hook/memo.js +41 -1
- package/dist/core/hook/memo.js.map +1 -1
- package/dist/core/hook/pointer-block.d.ts +1 -0
- package/dist/core/hook/pointer-block.js +23 -2
- package/dist/core/hook/pointer-block.js.map +1 -1
- package/dist/core/hook/query-extract.d.ts +2 -1
- package/dist/core/hook/query-extract.js +10 -1
- package/dist/core/hook/query-extract.js.map +1 -1
- package/dist/core/hook/resolve-conversation-by-query.d.ts +28 -0
- package/dist/core/hook/resolve-conversation-by-query.js +98 -0
- package/dist/core/hook/resolve-conversation-by-query.js.map +1 -0
- package/dist/core/hook/select.d.ts +8 -0
- package/dist/core/hook/select.js +9 -2
- package/dist/core/hook/select.js.map +1 -1
- package/dist/core/hook/transcript.js +16 -2
- package/dist/core/hook/transcript.js.map +1 -1
- package/dist/core/ingest/ingest-session.d.ts +5 -0
- package/dist/core/ingest/ingest-session.js +3 -0
- package/dist/core/ingest/ingest-session.js.map +1 -1
- package/dist/core/ingest/reclassify-oversized.d.ts +5 -0
- package/dist/core/ingest/reclassify-oversized.js +3 -0
- package/dist/core/ingest/reclassify-oversized.js.map +1 -1
- package/dist/core/ingest/reprocess.d.ts +96 -0
- package/dist/core/ingest/reprocess.js +275 -0
- package/dist/core/ingest/reprocess.js.map +1 -0
- package/dist/core/integrity/check-invariants.d.ts +6 -1
- package/dist/core/integrity/check-invariants.js +209 -3
- package/dist/core/integrity/check-invariants.js.map +1 -1
- package/dist/core/metrics/corpus-stats.d.ts +49 -0
- package/dist/core/metrics/corpus-stats.js +111 -0
- package/dist/core/metrics/corpus-stats.js.map +1 -0
- package/dist/core/metrics/re-derivation.d.ts +48 -0
- package/dist/core/metrics/re-derivation.js +87 -0
- package/dist/core/metrics/re-derivation.js.map +1 -0
- package/dist/core/recall/citation-log.d.ts +8 -0
- package/dist/core/recall/citation-log.js +17 -0
- package/dist/core/recall/citation-log.js.map +1 -1
- package/dist/core/recall/query-intent.d.ts +7 -0
- package/dist/core/recall/query-intent.js +25 -0
- package/dist/core/recall/query-intent.js.map +1 -0
- package/dist/core/recall/query-log.d.ts +4 -0
- package/dist/core/recall/query-log.js +4 -0
- package/dist/core/recall/query-log.js.map +1 -1
- package/dist/core/recall/recall-service.d.ts +37 -1
- package/dist/core/recall/recall-service.js +106 -63
- package/dist/core/recall/recall-service.js.map +1 -1
- package/dist/core/recall/related-exemplars.d.ts +1 -0
- package/dist/core/recall/related-exemplars.js +4 -1
- package/dist/core/recall/related-exemplars.js.map +1 -1
- package/dist/core/recall/related-facts.js +2 -2
- package/dist/core/recall/related-facts.js.map +1 -1
- package/dist/core/recall/reranker.d.ts +2 -1
- package/dist/core/recall/reranker.js +3 -3
- package/dist/core/recall/reranker.js.map +1 -1
- package/dist/core/recall-facts/fact-query-log.d.ts +7 -0
- package/dist/core/recall-facts/fact-query-log.js +2 -0
- package/dist/core/recall-facts/fact-query-log.js.map +1 -1
- package/dist/core/recall-facts/fact-recall-service.d.ts +9 -1
- package/dist/core/recall-facts/fact-recall-service.js +21 -8
- package/dist/core/recall-facts/fact-recall-service.js.map +1 -1
- package/dist/core/scheduler/scan-once.d.ts +6 -6
- package/dist/core/scheduler/scan-once.js +13 -49
- package/dist/core/scheduler/scan-once.js.map +1 -1
- package/dist/core/scheduler/scheduler.d.ts +10 -0
- package/dist/core/scheduler/scheduler.js +80 -28
- package/dist/core/scheduler/scheduler.js.map +1 -1
- package/dist/core/signals/code-signal.d.ts +49 -0
- package/dist/core/signals/code-signal.js +53 -0
- package/dist/core/signals/code-signal.js.map +1 -0
- package/dist/core/storage/db-restore.js +3 -1
- package/dist/core/storage/db-restore.js.map +1 -1
- package/dist/core/storage/fact-batch.d.ts +9 -0
- package/dist/core/storage/fact-batch.js +14 -0
- package/dist/core/storage/fact-batch.js.map +1 -0
- package/dist/core/storage/pg-code-exemplar-store.d.ts +1 -0
- package/dist/core/storage/pg-code-exemplar-store.js +7 -0
- package/dist/core/storage/pg-code-exemplar-store.js.map +1 -1
- package/dist/core/storage/pg-embedding-config.d.ts +29 -0
- package/dist/core/storage/pg-embedding-config.js +53 -0
- package/dist/core/storage/pg-embedding-config.js.map +1 -0
- package/dist/core/storage/pg-entity-store.d.ts +12 -0
- package/dist/core/storage/pg-entity-store.js +79 -0
- package/dist/core/storage/pg-entity-store.js.map +1 -0
- package/dist/core/storage/pg-fact-ingest.d.ts +15 -0
- package/dist/core/storage/pg-fact-ingest.js +34 -0
- package/dist/core/storage/pg-fact-ingest.js.map +1 -0
- package/dist/core/storage/pg-fact-store.d.ts +3 -0
- package/dist/core/storage/pg-fact-store.js +26 -30
- package/dist/core/storage/pg-fact-store.js.map +1 -1
- package/dist/core/storage/pg-migrate.d.ts +18 -0
- package/dist/core/storage/pg-migrate.js +73 -0
- package/dist/core/storage/pg-migrate.js.map +1 -0
- package/dist/core/storage/pg-session-store.d.ts +9 -0
- package/dist/core/storage/pg-session-store.js +204 -76
- package/dist/core/storage/pg-session-store.js.map +1 -1
- package/dist/core/storage/pg-storage.d.ts +9 -7
- package/dist/core/storage/pg-storage.js +20 -50
- package/dist/core/storage/pg-storage.js.map +1 -1
- package/dist/core/storage/pg-workstream-store.d.ts +24 -0
- package/dist/core/storage/pg-workstream-store.js +96 -0
- package/dist/core/storage/pg-workstream-store.js.map +1 -0
- package/dist/core/storage/sqlite-code-exemplar-store.d.ts +1 -0
- package/dist/core/storage/sqlite-code-exemplar-store.js +11 -0
- package/dist/core/storage/sqlite-code-exemplar-store.js.map +1 -1
- package/dist/core/storage/sqlite-embedding-config.d.ts +10 -0
- package/dist/core/storage/sqlite-embedding-config.js +27 -0
- package/dist/core/storage/sqlite-embedding-config.js.map +1 -0
- package/dist/core/storage/sqlite-entity-store.d.ts +13 -0
- package/dist/core/storage/sqlite-entity-store.js +96 -0
- package/dist/core/storage/sqlite-entity-store.js.map +1 -0
- package/dist/core/storage/sqlite-fact-store.d.ts +15 -7
- package/dist/core/storage/sqlite-fact-store.js +76 -25
- package/dist/core/storage/sqlite-fact-store.js.map +1 -1
- package/dist/core/storage/sqlite-session-store.d.ts +20 -5
- package/dist/core/storage/sqlite-session-store.js +183 -68
- package/dist/core/storage/sqlite-session-store.js.map +1 -1
- package/dist/core/storage/sqlite-storage.d.ts +8 -8
- package/dist/core/storage/sqlite-storage.js +21 -32
- package/dist/core/storage/sqlite-storage.js.map +1 -1
- package/dist/core/storage/sqlite-workstream-store.d.ts +24 -0
- package/dist/core/storage/sqlite-workstream-store.js +98 -0
- package/dist/core/storage/sqlite-workstream-store.js.map +1 -0
- package/dist/core/util/with-timeout.d.ts +5 -0
- package/dist/core/util/with-timeout.js +20 -0
- package/dist/core/util/with-timeout.js.map +1 -0
- package/dist/core/work-digest/active-spans.d.ts +8 -0
- package/dist/core/work-digest/active-spans.js +29 -0
- package/dist/core/work-digest/active-spans.js.map +1 -0
- package/dist/core/work-digest/attribute.d.ts +15 -0
- package/dist/core/work-digest/attribute.js +76 -0
- package/dist/core/work-digest/attribute.js.map +1 -0
- package/dist/core/work-digest/build-work-digest.d.ts +12 -0
- package/dist/core/work-digest/build-work-digest.js +69 -0
- package/dist/core/work-digest/build-work-digest.js.map +1 -0
- package/dist/core/work-digest/compose-work-digest.d.ts +3 -0
- package/dist/core/work-digest/compose-work-digest.js +52 -0
- package/dist/core/work-digest/compose-work-digest.js.map +1 -0
- package/dist/core/work-digest/merge-active.d.ts +10 -0
- package/dist/core/work-digest/merge-active.js +22 -0
- package/dist/core/work-digest/merge-active.js.map +1 -0
- package/dist/core/work-digest/read-transcript-timestamps.d.ts +7 -0
- package/dist/core/work-digest/read-transcript-timestamps.js +40 -0
- package/dist/core/work-digest/read-transcript-timestamps.js.map +1 -0
- package/dist/core/work-digest/topics.d.ts +18 -0
- package/dist/core/work-digest/topics.js +26 -0
- package/dist/core/work-digest/topics.js.map +1 -0
- package/dist/core/work-digest/types.d.ts +45 -0
- package/dist/core/work-digest/types.js +2 -0
- package/dist/core/work-digest/types.js.map +1 -0
- package/dist/core/workstream/bind.d.ts +25 -0
- package/dist/core/workstream/bind.js +23 -0
- package/dist/core/workstream/bind.js.map +1 -0
- package/dist/core/workstream/compose-recall.d.ts +2 -0
- package/dist/core/workstream/compose-recall.js +39 -0
- package/dist/core/workstream/compose-recall.js.map +1 -0
- package/dist/core/workstream/merge-suggest.d.ts +19 -0
- package/dist/core/workstream/merge-suggest.js +61 -0
- package/dist/core/workstream/merge-suggest.js.map +1 -0
- package/dist/core/workstream/model.d.ts +20 -0
- package/dist/core/workstream/model.js +9 -0
- package/dist/core/workstream/model.js.map +1 -0
- package/dist/core/workstream/name-match.d.ts +8 -0
- package/dist/core/workstream/name-match.js +18 -0
- package/dist/core/workstream/name-match.js.map +1 -0
- package/dist/core/workstream/resolve.d.ts +10 -0
- package/dist/core/workstream/resolve.js +21 -0
- package/dist/core/workstream/resolve.js.map +1 -0
- package/dist/core/workstream/rollup.d.ts +12 -0
- package/dist/core/workstream/rollup.js +17 -0
- package/dist/core/workstream/rollup.js.map +1 -0
- package/dist/core/workstream/work-topics.d.ts +10 -0
- package/dist/core/workstream/work-topics.js +55 -0
- package/dist/core/workstream/work-topics.js.map +1 -0
- package/dist/hook/hook-helpers.d.ts +4 -0
- package/dist/hook/hook-helpers.js +36 -0
- package/dist/hook/hook-helpers.js.map +1 -0
- package/dist/hook/pre-compact-hook.d.ts +23 -0
- package/dist/hook/pre-compact-hook.js +66 -0
- package/dist/hook/pre-compact-hook.js.map +1 -0
- package/dist/hook/prompt-recall-hook.d.ts +37 -0
- package/dist/hook/prompt-recall-hook.js +120 -20
- package/dist/hook/prompt-recall-hook.js.map +1 -1
- package/dist/hook/recall-gate.d.ts +29 -0
- package/dist/hook/recall-gate.js +50 -0
- package/dist/hook/recall-gate.js.map +1 -0
- package/dist/hook/recall-over-http.d.ts +1 -1
- package/dist/hook/recall-over-http.js +11 -11
- package/dist/hook/recall-over-http.js.map +1 -1
- package/dist/hook/recent-context.d.ts +25 -0
- package/dist/hook/recent-context.js +104 -0
- package/dist/hook/recent-context.js.map +1 -0
- package/dist/hook/score-floor.d.ts +27 -0
- package/dist/hook/score-floor.js +42 -0
- package/dist/hook/score-floor.js.map +1 -0
- package/dist/hook/session-end-hook.js +8 -29
- package/dist/hook/session-end-hook.js.map +1 -1
- package/dist/hook/session-start-hook.js +24 -59
- package/dist/hook/session-start-hook.js.map +1 -1
- package/dist/hook/stop-hook.js +22 -59
- package/dist/hook/stop-hook.js.map +1 -1
- package/dist/hook/subagent-start-hook.d.ts +28 -0
- package/dist/hook/subagent-start-hook.js +81 -0
- package/dist/hook/subagent-start-hook.js.map +1 -0
- package/dist/http/app.js +28 -8
- package/dist/http/app.js.map +1 -1
- package/dist/install/setup.js +2 -0
- package/dist/install/setup.js.map +1 -1
- package/dist/llm/build-classifier.d.ts +22 -0
- package/dist/llm/build-classifier.js +42 -0
- package/dist/llm/build-classifier.js.map +1 -0
- package/dist/llm/build-embedder.d.ts +11 -0
- package/dist/llm/build-embedder.js +42 -0
- package/dist/llm/build-embedder.js.map +1 -0
- package/dist/llm/bundled-embedder-client.d.ts +37 -0
- package/dist/llm/bundled-embedder-client.js +90 -0
- package/dist/llm/bundled-embedder-client.js.map +1 -0
- package/dist/llm/classifier-box.d.ts +13 -5
- package/dist/llm/classifier-box.js +28 -6
- package/dist/llm/classifier-box.js.map +1 -1
- package/dist/llm/classifier-egress.d.ts +18 -0
- package/dist/llm/classifier-egress.js +59 -0
- package/dist/llm/classifier-egress.js.map +1 -0
- package/dist/llm/client-shared.d.ts +4 -0
- package/dist/llm/client-shared.js +39 -0
- package/dist/llm/client-shared.js.map +1 -0
- package/dist/llm/deepseek-client.d.ts +20 -0
- package/dist/llm/deepseek-client.js +59 -45
- package/dist/llm/deepseek-client.js.map +1 -1
- package/dist/llm/embedder-info.d.ts +15 -0
- package/dist/llm/embedder-info.js +27 -0
- package/dist/llm/embedder-info.js.map +1 -0
- package/dist/llm/model-quirks.d.ts +4 -0
- package/dist/llm/model-quirks.js +7 -0
- package/dist/llm/model-quirks.js.map +1 -0
- package/dist/llm/naming.d.ts +5 -0
- package/dist/llm/naming.js +20 -0
- package/dist/llm/naming.js.map +1 -0
- package/dist/llm/ollama-client.d.ts +7 -8
- package/dist/llm/ollama-client.js +59 -51
- package/dist/llm/ollama-client.js.map +1 -1
- package/dist/llm/ollama-code-embedder.d.ts +8 -3
- package/dist/llm/ollama-code-embedder.js +14 -24
- package/dist/llm/ollama-code-embedder.js.map +1 -1
- package/dist/llm/openai-code-embedder-client.d.ts +25 -0
- package/dist/llm/openai-code-embedder-client.js +52 -0
- package/dist/llm/openai-code-embedder-client.js.map +1 -0
- package/dist/llm/openai-embedder-client.d.ts +41 -0
- package/dist/llm/openai-embedder-client.js +83 -0
- package/dist/llm/openai-embedder-client.js.map +1 -0
- package/dist/mcp/server.d.ts +47 -2
- package/dist/mcp/server.js +303 -18
- package/dist/mcp/server.js.map +1 -1
- package/dist/ports/code-embedder.d.ts +6 -1
- package/dist/ports/code-exemplar-store.d.ts +5 -0
- package/dist/ports/entity-store.d.ts +35 -0
- package/dist/ports/entity-store.js +7 -0
- package/dist/ports/entity-store.js.map +1 -0
- package/dist/ports/fact-store.d.ts +0 -0
- package/dist/ports/fact-store.js.map +1 -1
- package/dist/ports/llm-client.d.ts +9 -1
- package/dist/ports/session-store.d.ts +12 -0
- package/dist/ports/storage.d.ts +7 -30
- package/dist/ports/storage.js +3 -7
- package/dist/ports/storage.js.map +1 -1
- package/dist/ports/transcript-adapter.d.ts +2 -0
- package/dist/ports/workstream-store.d.ts +21 -0
- package/dist/ports/workstream-store.js +2 -0
- package/dist/ports/workstream-store.js.map +1 -0
- package/dist/shared/types.d.ts +23 -2
- package/fixtures/classifier-gold/README.md +94 -0
- package/fixtures/classifier-gold/reference.json +318 -0
- package/fixtures/classifier-gold/transcripts/01-bug-fix-null-pointer-api.txt +75 -0
- package/fixtures/classifier-gold/transcripts/02-bug-fix-race-condition-queue.txt +77 -0
- package/fixtures/classifier-gold/transcripts/03-bug-fix-jwt-clock-skew.txt +86 -0
- package/fixtures/classifier-gold/transcripts/04-feature-webhook-retry.txt +95 -0
- package/fixtures/classifier-gold/transcripts/05-feature-csv-export.txt +86 -0
- package/fixtures/classifier-gold/transcripts/06-feature-rate-limiting.txt +61 -0
- package/fixtures/classifier-gold/transcripts/07-refactor-db-abstraction.txt +72 -0
- package/fixtures/classifier-gold/transcripts/08-refactor-config-loading.txt +89 -0
- package/fixtures/classifier-gold/transcripts/09-ops-docker-staging.txt +94 -0
- package/fixtures/classifier-gold/transcripts/10-ops-nginx-ssl.txt +121 -0
- package/fixtures/classifier-gold/transcripts/11-ops-postgres-migration.txt +74 -0
- package/fixtures/classifier-gold/transcripts/12-ops-cron-debugging.txt +73 -0
- package/fixtures/classifier-gold/transcripts/13-research-vector-db-comparison.txt +59 -0
- package/fixtures/classifier-gold/transcripts/14-research-oauth-provider.txt +74 -0
- package/fixtures/classifier-gold/transcripts/15-writing-api-docs.txt +154 -0
- package/fixtures/classifier-gold/transcripts/16-meeting-sprint-planning.txt +69 -0
- package/fixtures/classifier-gold/transcripts/17-trivial-list-files.txt +14 -0
- package/fixtures/classifier-gold/transcripts/18-trivial-version-check.txt +21 -0
- package/fixtures/classifier-gold/transcripts/19-trivial-syntax-help.txt +29 -0
- package/fixtures/classifier-gold/transcripts/20-trivial-read-package-json.txt +41 -0
- package/migrations/000_initial_schema.sql +1 -1
- package/migrations/024_backfill_exemplar_repo_basename.sql +32 -0
- package/migrations/025_workstreams.sql +30 -0
- package/migrations/026_embedding_config.sql +12 -0
- package/migrations/027_drop_legacy_session_embeddings.sql +7 -0
- package/migrations/028_classifier_provenance.sql +5 -0
- package/migrations/pg/001_initial.sql +34 -0
- package/migrations/pg/019_split_replaces.sql +16 -7
- package/migrations/pg/025_workstreams.sql +34 -0
- package/migrations/pg/026_embedding_config.sql +12 -0
- package/migrations/pg/028_classifier_provenance.sql +5 -0
- package/migrations/pg/029_entity_variants.sql +9 -0
- package/nlm/index.js +395 -37
- package/package.json +12 -2
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/scripts/prompt-recall-hook.mjs +402 -41
- package/plugin/scripts/session-start-hook.mjs +286 -60
- package/plugin/scripts/stop-hook.mjs +85 -65
- package/plugin-hermes-agent/__init__.py +2 -1
- package/templates/agent-contract/README.md +24 -0
- package/templates/agent-contract/claude-code.md +40 -0
- package/templates/agent-contract/generic.md +38 -0
- package/dist/core/embedding/embed-normalize.d.ts +0 -28
- package/dist/core/embedding/embed-normalize.js +0 -98
- package/dist/core/embedding/embed-normalize.js.map +0 -1
- package/dist/core/storage/pg-tx-context.d.ts +0 -52
- package/dist/core/storage/pg-tx-context.js +0 -149
- package/dist/core/storage/pg-tx-context.js.map +0 -1
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version-gated PostgreSQL migration runner. Reads versioned *.sql files from
|
|
3
|
+
* a directory, applies any whose integer prefix is not yet in schema_migrations,
|
|
4
|
+
* and returns the list of newly applied versions. Idempotent: re-running on an
|
|
5
|
+
* up-to-date database is a no-op.
|
|
6
|
+
*
|
|
7
|
+
* Migration files that contain their own BEGIN/COMMIT markers have those lines
|
|
8
|
+
* stripped; the runner wraps each file in a single transaction instead. A file
|
|
9
|
+
* whose first line is `-- nlm:no-wrap` is executed as-is (manages its own
|
|
10
|
+
* transaction). No pg migration files use that convention today; the check is
|
|
11
|
+
* kept for parity with the sqlite runner.
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
14
|
+
import { join } from "node:path";
|
|
15
|
+
const FILE_PATTERN = /^(\d+)_([a-z0-9_-]+)\.sql$/i;
|
|
16
|
+
function stripTransactionMarkers(sql) {
|
|
17
|
+
return sql
|
|
18
|
+
.split("\n")
|
|
19
|
+
.filter((line) => !/^\s*(?:BEGIN|COMMIT)\s*;\s*$/.test(line))
|
|
20
|
+
.join("\n");
|
|
21
|
+
}
|
|
22
|
+
export async function runMigrationsPg(pool, migrationsDir) {
|
|
23
|
+
await pool.query(`
|
|
24
|
+
CREATE TABLE IF NOT EXISTS schema_migrations (
|
|
25
|
+
version INTEGER PRIMARY KEY,
|
|
26
|
+
name TEXT NOT NULL,
|
|
27
|
+
applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
|
28
|
+
)
|
|
29
|
+
`);
|
|
30
|
+
const { rows: appliedRows } = await pool.query("SELECT version FROM schema_migrations");
|
|
31
|
+
const applied = new Set(appliedRows.map((r) => r.version));
|
|
32
|
+
const files = readdirSync(migrationsDir)
|
|
33
|
+
.filter((f) => FILE_PATTERN.test(f))
|
|
34
|
+
.sort();
|
|
35
|
+
const newlyApplied = [];
|
|
36
|
+
for (const file of files) {
|
|
37
|
+
const match = FILE_PATTERN.exec(file);
|
|
38
|
+
if (!match)
|
|
39
|
+
continue;
|
|
40
|
+
const version = Number(match[1]);
|
|
41
|
+
const name = match[2] ?? file;
|
|
42
|
+
if (applied.has(version))
|
|
43
|
+
continue;
|
|
44
|
+
const rawSql = readFileSync(join(migrationsDir, file), "utf8");
|
|
45
|
+
const noWrap = rawSql.startsWith("-- nlm:no-wrap");
|
|
46
|
+
const client = await pool.connect();
|
|
47
|
+
try {
|
|
48
|
+
if (noWrap) {
|
|
49
|
+
await client.query(rawSql);
|
|
50
|
+
await client.query("INSERT INTO schema_migrations (version, name) VALUES ($1, $2) ON CONFLICT DO NOTHING", [version, name]);
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
const sql = stripTransactionMarkers(rawSql);
|
|
54
|
+
await client.query("BEGIN");
|
|
55
|
+
try {
|
|
56
|
+
await client.query(sql);
|
|
57
|
+
await client.query("INSERT INTO schema_migrations (version, name) VALUES ($1, $2) ON CONFLICT DO NOTHING", [version, name]);
|
|
58
|
+
await client.query("COMMIT");
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
await client.query("ROLLBACK");
|
|
62
|
+
throw new Error(`Migration ${file} failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
client.release();
|
|
68
|
+
}
|
|
69
|
+
newlyApplied.push({ version, name });
|
|
70
|
+
}
|
|
71
|
+
return newlyApplied;
|
|
72
|
+
}
|
|
73
|
+
//# sourceMappingURL=pg-migrate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pg-migrate.js","sourceRoot":"","sources":["../../../src/core/storage/pg-migrate.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,14 +13,23 @@ import type { PgFactStore } from "./pg-fact-store.js";
|
|
|
13
13
|
export declare class PgSessionStore implements SessionStore {
|
|
14
14
|
readonly pool: Pool;
|
|
15
15
|
constructor(pool: Pool);
|
|
16
|
+
private overlayCache;
|
|
17
|
+
private overlayCacheAt;
|
|
18
|
+
invalidateOverlayCache(): void;
|
|
19
|
+
private overlay;
|
|
16
20
|
list(filter?: SessionFilter): Promise<ReadonlyArray<Session>>;
|
|
17
21
|
getById(sessionId: string): Promise<Session | null>;
|
|
18
22
|
getByIds(ids: ReadonlyArray<string>): Promise<ReadonlyArray<Session>>;
|
|
23
|
+
listByDateRange(fromIso: string, toIso: string): Promise<ReadonlyArray<Session>>;
|
|
19
24
|
semanticSearch(queryVector: Float32Array, limit: number, opts?: SearchOptions): Promise<ReadonlyArray<SemanticNeighbor>>;
|
|
20
25
|
keywordSearch(query: string, limit: number, opts?: SearchOptions): Promise<ReadonlyArray<KeywordNeighbor>>;
|
|
21
26
|
resolveSuccessors(ids: ReadonlyArray<string>): Promise<Map<string, string>>;
|
|
22
27
|
updateStatus(sessionId: string, status: SessionStatus): Promise<void>;
|
|
23
28
|
markSuperseded(predecessorId: string, successorId: string): Promise<void>;
|
|
29
|
+
setWorkstreamBinding(sessionId: string, workstreamId: string | null, source: import("../workstream/model.js").BindingSource | null, confidence: number | null): Promise<void>;
|
|
30
|
+
listSessionIdsByWorkstreams(workstreamIds: ReadonlyArray<string>): Promise<ReadonlyArray<string>>;
|
|
31
|
+
getEntities(sessionId: string): Promise<ReadonlyArray<string>>;
|
|
32
|
+
getWorkstreamIds(sessionIds: ReadonlyArray<string>): Promise<Map<string, string | null>>;
|
|
24
33
|
recentWrites(limit: number): Promise<RecentWrite[]>;
|
|
25
34
|
recentMarkers(limit: number): Promise<RecentMarker[]>;
|
|
26
35
|
insertSession(record: IngestRecord, embedder?: import("../../ports/llm-client.js").LLMClient | null, supersedes?: Supersedes | null, factSink?: {
|
|
@@ -5,7 +5,36 @@
|
|
|
5
5
|
* and recentMarkers() for the /live HTTP endpoints, and insertSession() +
|
|
6
6
|
* insertSessionForTest() for ingest and test seeding.
|
|
7
7
|
*/
|
|
8
|
+
import { tokenize } from "../recall/tokenize.js";
|
|
9
|
+
import { ingestSessionFactsOnClient } from "./pg-fact-ingest.js";
|
|
8
10
|
import { chunkSessionText } from "../embedding/chunk-body.js";
|
|
11
|
+
import { batchWinners } from "./fact-batch.js";
|
|
12
|
+
import { loadActionOverlayPg, openQuestionId } from "../actions/overlay.js";
|
|
13
|
+
import { liveSessionStatus } from "./live-status.js";
|
|
14
|
+
/**
|
|
15
|
+
* PG mirror of SQLite findContinuesPredecessor: the most recent prior session
|
|
16
|
+
* whose distinct entity-set is identical to the new session's. Runs on the
|
|
17
|
+
* ingest transaction's own client. Returns null when there is no entity-set or
|
|
18
|
+
* no exact-set match, leaving the pair unlinked for the re_derivation_rate metric.
|
|
19
|
+
*/
|
|
20
|
+
async function findContinuesPredecessorPg(client, newId, rawEntities) {
|
|
21
|
+
const entities = [...new Set(rawEntities.map((e) => e.trim()).filter(Boolean))];
|
|
22
|
+
if (entities.length === 0)
|
|
23
|
+
return null;
|
|
24
|
+
const placeholders = entities.map((_, i) => `$${i + 2}`).join(",");
|
|
25
|
+
const res = await client.query(`SELECT s.id AS id
|
|
26
|
+
FROM sessions s
|
|
27
|
+
JOIN session_entities se ON se.session_id = s.id
|
|
28
|
+
WHERE s.id != $1
|
|
29
|
+
AND se.entity_canonical IN (${placeholders})
|
|
30
|
+
GROUP BY s.id
|
|
31
|
+
HAVING COUNT(DISTINCT se.entity_canonical) = $${entities.length + 2}
|
|
32
|
+
AND COUNT(DISTINCT se.entity_canonical)
|
|
33
|
+
= (SELECT COUNT(*) FROM session_entities x WHERE x.session_id = s.id)
|
|
34
|
+
ORDER BY s.started_at DESC, s.id DESC
|
|
35
|
+
LIMIT 1`, [newId, ...entities, entities.length]);
|
|
36
|
+
return res.rows[0]?.id ?? null;
|
|
37
|
+
}
|
|
9
38
|
export class PgSessionStore {
|
|
10
39
|
pool;
|
|
11
40
|
// `pool` is public-readonly so PG-native sibling helpers (actions-log Pg
|
|
@@ -13,18 +42,36 @@ export class PgSessionStore {
|
|
|
13
42
|
constructor(pool) {
|
|
14
43
|
this.pool = pool;
|
|
15
44
|
}
|
|
45
|
+
overlayCache = null;
|
|
46
|
+
overlayCacheAt = 0;
|
|
47
|
+
invalidateOverlayCache() {
|
|
48
|
+
this.overlayCache = null;
|
|
49
|
+
}
|
|
50
|
+
async overlay() {
|
|
51
|
+
// TTL backstop: explicit invalidation covers the daemon's own writers; the
|
|
52
|
+
// 30s expiry bounds staleness if another process ever writes actions to
|
|
53
|
+
// this database.
|
|
54
|
+
if (this.overlayCache !== null && Date.now() - this.overlayCacheAt < 30_000) {
|
|
55
|
+
return this.overlayCache;
|
|
56
|
+
}
|
|
57
|
+
this.overlayCache = await loadActionOverlayPg(this.pool);
|
|
58
|
+
this.overlayCacheAt = Date.now();
|
|
59
|
+
return this.overlayCache;
|
|
60
|
+
}
|
|
16
61
|
async list(filter) {
|
|
17
62
|
const result = await this.pool.query(`SELECT id, runtime, runtime_session_id, started_at, ended_at, duration_min,
|
|
18
|
-
label, summary, status, transcript_kind, transcript_path, body
|
|
63
|
+
label, summary, status, transcript_kind, transcript_path, body,
|
|
64
|
+
classifier_provider, classifier_model, classifier_confidence
|
|
19
65
|
FROM sessions ORDER BY started_at ASC`);
|
|
20
66
|
if (result.rows.length === 0)
|
|
21
67
|
return [];
|
|
22
68
|
const ids = result.rows.map((r) => r.id);
|
|
23
|
-
const [entitiesMap, markersMap] = await Promise.all([
|
|
69
|
+
const [entitiesMap, markersMap, overlay] = await Promise.all([
|
|
24
70
|
this.loadEntities(ids),
|
|
25
71
|
this.loadMarkers(ids),
|
|
72
|
+
this.overlay(),
|
|
26
73
|
]);
|
|
27
|
-
const sessions = result.rows.map((r) => rowToSession(r, entitiesMap, markersMap));
|
|
74
|
+
const sessions = result.rows.map((r) => rowToSession(r, entitiesMap, markersMap, overlay));
|
|
28
75
|
if (!filter)
|
|
29
76
|
return sessions;
|
|
30
77
|
return sessions.filter((s) => {
|
|
@@ -39,17 +86,19 @@ export class PgSessionStore {
|
|
|
39
86
|
}
|
|
40
87
|
async getById(sessionId) {
|
|
41
88
|
const result = await this.pool.query(`SELECT id, runtime, runtime_session_id, started_at, ended_at, duration_min,
|
|
42
|
-
label, summary, status, transcript_kind, transcript_path, body
|
|
89
|
+
label, summary, status, transcript_kind, transcript_path, body,
|
|
90
|
+
classifier_provider, classifier_model, classifier_confidence
|
|
43
91
|
FROM sessions WHERE id = $1`, [sessionId]);
|
|
44
92
|
if (!result.rows[0])
|
|
45
93
|
return null;
|
|
46
|
-
const [entitiesMap, markersMap, edgesMap] = await Promise.all([
|
|
94
|
+
const [entitiesMap, markersMap, edgesMap, overlay] = await Promise.all([
|
|
47
95
|
this.loadEntities([sessionId]),
|
|
48
96
|
this.loadMarkers([sessionId]),
|
|
49
97
|
this.loadEdges([sessionId]),
|
|
98
|
+
this.overlay(),
|
|
50
99
|
]);
|
|
51
100
|
const edges = edgesMap.get(sessionId);
|
|
52
|
-
return rowToSession(result.rows[0], entitiesMap, markersMap, edges);
|
|
101
|
+
return rowToSession(result.rows[0], entitiesMap, markersMap, overlay, edges);
|
|
53
102
|
}
|
|
54
103
|
async getByIds(ids) {
|
|
55
104
|
if (ids.length === 0)
|
|
@@ -61,41 +110,84 @@ export class PgSessionStore {
|
|
|
61
110
|
if (result.rows.length === 0)
|
|
62
111
|
return [];
|
|
63
112
|
const foundIds = result.rows.map((r) => r.id);
|
|
64
|
-
const [entitiesMap, markersMap] = await Promise.all([
|
|
113
|
+
const [entitiesMap, markersMap, overlay] = await Promise.all([
|
|
65
114
|
this.loadEntities(foundIds),
|
|
66
115
|
this.loadMarkers(foundIds),
|
|
116
|
+
this.overlay(),
|
|
117
|
+
]);
|
|
118
|
+
return result.rows.map((r) => rowToSession({ ...r, body: null }, entitiesMap, markersMap, overlay));
|
|
119
|
+
}
|
|
120
|
+
async listByDateRange(fromIso, toIso) {
|
|
121
|
+
const result = await this.pool.query(`SELECT id, runtime, runtime_session_id, started_at, ended_at, duration_min,
|
|
122
|
+
label, summary, status, transcript_kind, transcript_path, workstream_id
|
|
123
|
+
FROM sessions
|
|
124
|
+
WHERE started_at < $1 AND (ended_at IS NULL OR ended_at >= $2)
|
|
125
|
+
ORDER BY started_at ASC`, [toIso, fromIso]);
|
|
126
|
+
if (result.rows.length === 0)
|
|
127
|
+
return [];
|
|
128
|
+
const ids = result.rows.map((r) => r.id);
|
|
129
|
+
const [entitiesMap, markersMap, overlay] = await Promise.all([
|
|
130
|
+
this.loadEntities(ids),
|
|
131
|
+
this.loadMarkers(ids),
|
|
132
|
+
this.overlay(),
|
|
67
133
|
]);
|
|
68
|
-
return result.rows.map((r) => rowToSession({ ...r, body: null }, entitiesMap, markersMap));
|
|
134
|
+
return result.rows.map((r) => rowToSession({ ...r, body: null }, entitiesMap, markersMap, overlay));
|
|
69
135
|
}
|
|
70
136
|
async semanticSearch(queryVector, limit, opts) {
|
|
137
|
+
const wsIds = opts?.workstreamIds;
|
|
138
|
+
if (wsIds !== undefined && wsIds.length === 0)
|
|
139
|
+
return [];
|
|
71
140
|
const k = Math.max(1, Math.trunc(limit));
|
|
72
141
|
const vecStr = `[${Array.from(queryVector).join(",")}]`;
|
|
73
142
|
const statusFilter = opts?.includeSuperseded === true
|
|
74
143
|
? "s.status != 'replaced'"
|
|
75
144
|
: "s.status NOT IN ('superseded', 'replaced')";
|
|
145
|
+
const params = [vecStr];
|
|
146
|
+
let wsClause = "";
|
|
147
|
+
if (wsIds?.length) {
|
|
148
|
+
params.push(wsIds);
|
|
149
|
+
wsClause = `AND s.workstream_id = ANY($${params.length}::text[])`;
|
|
150
|
+
}
|
|
151
|
+
params.push(k);
|
|
152
|
+
const limitParam = `$${params.length}`;
|
|
76
153
|
const result = await this.pool.query(`SELECT sec.session_id, MIN(sec.embedding <-> $1::vector) AS distance, s.status
|
|
77
154
|
FROM session_embedding_chunks sec
|
|
78
155
|
JOIN sessions s ON s.id = sec.session_id
|
|
156
|
+
WHERE TRUE ${wsClause}
|
|
79
157
|
GROUP BY sec.session_id, s.status
|
|
80
158
|
HAVING ${statusFilter}
|
|
81
159
|
ORDER BY distance
|
|
82
|
-
LIMIT $
|
|
160
|
+
LIMIT ${limitParam}`, params);
|
|
83
161
|
return result.rows.map((r) => ({ sessionId: r.session_id, distance: r.distance }));
|
|
84
162
|
}
|
|
85
163
|
async keywordSearch(query, limit, opts) {
|
|
86
|
-
|
|
164
|
+
const wsIds = opts?.workstreamIds;
|
|
165
|
+
if (wsIds !== undefined && wsIds.length === 0)
|
|
166
|
+
return [];
|
|
167
|
+
const terms = tokenize(query).map(sanitizeTsToken).filter(Boolean);
|
|
168
|
+
if (terms.length === 0)
|
|
87
169
|
return [];
|
|
170
|
+
const tsQuery = terms.join(" OR ");
|
|
88
171
|
const k = Math.max(1, Math.trunc(limit));
|
|
89
172
|
const statusFilter = opts?.includeSuperseded === true
|
|
90
173
|
? "status != 'replaced'"
|
|
91
174
|
: "status NOT IN ('superseded', 'replaced')";
|
|
175
|
+
const params = [tsQuery];
|
|
176
|
+
let wsClause = "";
|
|
177
|
+
if (wsIds?.length) {
|
|
178
|
+
params.push(wsIds);
|
|
179
|
+
wsClause = `AND workstream_id = ANY($${params.length}::text[])`;
|
|
180
|
+
}
|
|
181
|
+
params.push(k);
|
|
182
|
+
const limitParam = `$${params.length}`;
|
|
92
183
|
const result = await this.pool.query(`SELECT id AS session_id,
|
|
93
184
|
ts_rank_cd(fts_vector, websearch_to_tsquery('english', $1)) AS score
|
|
94
185
|
FROM sessions
|
|
95
186
|
WHERE fts_vector @@ websearch_to_tsquery('english', $1)
|
|
96
187
|
AND ${statusFilter}
|
|
188
|
+
${wsClause}
|
|
97
189
|
ORDER BY score DESC
|
|
98
|
-
LIMIT $
|
|
190
|
+
LIMIT ${limitParam}`, params);
|
|
99
191
|
return result.rows.map((r) => ({ sessionId: r.session_id, score: r.score }));
|
|
100
192
|
}
|
|
101
193
|
async resolveSuccessors(ids) {
|
|
@@ -176,7 +268,11 @@ export class PgSessionStore {
|
|
|
176
268
|
AND s.superseded_by IS NULL
|
|
177
269
|
)
|
|
178
270
|
`;
|
|
179
|
-
await client.query(cascadeSQL, [predecessorId, successorId]);
|
|
271
|
+
const cascaded = await client.query(cascadeSQL + " RETURNING p.id", [predecessorId, successorId]);
|
|
272
|
+
const cascadedIds = cascaded.rows.map((r) => r.id);
|
|
273
|
+
if (cascadedIds.length > 0) {
|
|
274
|
+
await client.query("DELETE FROM fact_embeddings WHERE fact_id = ANY($1)", [cascadedIds]);
|
|
275
|
+
}
|
|
180
276
|
await client.query("COMMIT");
|
|
181
277
|
}
|
|
182
278
|
catch (err) {
|
|
@@ -187,6 +283,29 @@ export class PgSessionStore {
|
|
|
187
283
|
client.release();
|
|
188
284
|
}
|
|
189
285
|
}
|
|
286
|
+
async setWorkstreamBinding(sessionId, workstreamId, source, confidence) {
|
|
287
|
+
await this.pool.query("UPDATE sessions SET workstream_id = $1, binding_source = $2, binding_confidence = $3, updated_at = NOW() WHERE id = $4", [workstreamId, source, confidence, sessionId]);
|
|
288
|
+
}
|
|
289
|
+
async listSessionIdsByWorkstreams(workstreamIds) {
|
|
290
|
+
if (workstreamIds.length === 0)
|
|
291
|
+
return [];
|
|
292
|
+
const ph = workstreamIds.map((_, i) => `$${i + 1}`).join(",");
|
|
293
|
+
const r = await this.pool.query(`SELECT id FROM sessions WHERE workstream_id IN (${ph}) ORDER BY started_at ASC`, [...workstreamIds]);
|
|
294
|
+
return r.rows.map((row) => row.id);
|
|
295
|
+
}
|
|
296
|
+
async getEntities(sessionId) {
|
|
297
|
+
return (await this.loadEntities([sessionId])).get(sessionId) ?? [];
|
|
298
|
+
}
|
|
299
|
+
async getWorkstreamIds(sessionIds) {
|
|
300
|
+
const out = new Map();
|
|
301
|
+
if (sessionIds.length === 0)
|
|
302
|
+
return out;
|
|
303
|
+
const ph = sessionIds.map((_, i) => `$${i + 1}`).join(",");
|
|
304
|
+
const result = await this.pool.query(`SELECT id, workstream_id FROM sessions WHERE id IN (${ph})`, [...sessionIds]);
|
|
305
|
+
for (const r of result.rows)
|
|
306
|
+
out.set(r.id, r.workstream_id);
|
|
307
|
+
return out;
|
|
308
|
+
}
|
|
190
309
|
async recentWrites(limit) {
|
|
191
310
|
const result = await this.pool.query(`SELECT id, runtime, label, summary, created_at AS "createdAt"
|
|
192
311
|
FROM sessions ORDER BY created_at DESC LIMIT $1`, [limit]);
|
|
@@ -222,8 +341,9 @@ export class PgSessionStore {
|
|
|
222
341
|
await client.query(`INSERT INTO sessions (
|
|
223
342
|
id, runtime, runtime_session_id, started_at, ended_at, duration_min,
|
|
224
343
|
label, summary, body, status, transcript_kind, transcript_path,
|
|
225
|
-
transcript_offset, transcript_length
|
|
226
|
-
|
|
344
|
+
transcript_offset, transcript_length,
|
|
345
|
+
classifier_provider, classifier_model, classifier_confidence
|
|
346
|
+
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17)
|
|
227
347
|
ON CONFLICT (id) DO UPDATE SET
|
|
228
348
|
ended_at = EXCLUDED.ended_at,
|
|
229
349
|
duration_min = EXCLUDED.duration_min,
|
|
@@ -231,6 +351,9 @@ export class PgSessionStore {
|
|
|
231
351
|
summary = EXCLUDED.summary,
|
|
232
352
|
body = EXCLUDED.body,
|
|
233
353
|
status = EXCLUDED.status,
|
|
354
|
+
classifier_provider = EXCLUDED.classifier_provider,
|
|
355
|
+
classifier_model = EXCLUDED.classifier_model,
|
|
356
|
+
classifier_confidence = EXCLUDED.classifier_confidence,
|
|
234
357
|
updated_at = NOW()`, [
|
|
235
358
|
record.id, record.runtime, record.runtimeSessionId,
|
|
236
359
|
record.startedAt, record.endedAt, record.durationMin,
|
|
@@ -238,6 +361,9 @@ export class PgSessionStore {
|
|
|
238
361
|
record.status === "idle" ? "active" : record.status,
|
|
239
362
|
record.transcriptKind, record.transcriptPath,
|
|
240
363
|
record.transcriptOffset, record.transcriptLength,
|
|
364
|
+
record.classifier?.provider ?? null,
|
|
365
|
+
record.classifier?.model ?? null,
|
|
366
|
+
record.classifier?.confidence ?? null,
|
|
241
367
|
]);
|
|
242
368
|
await client.query("DELETE FROM markers WHERE session_id = $1", [record.id]);
|
|
243
369
|
for (let i = 0; i < record.decisions.length; i++) {
|
|
@@ -246,17 +372,34 @@ export class PgSessionStore {
|
|
|
246
372
|
for (let i = 0; i < record.openQuestions.length; i++) {
|
|
247
373
|
await client.query("INSERT INTO markers (session_id, kind, text, position) VALUES ($1, 'open', $2, $3)", [record.id, record.openQuestions[i].trim(), i]);
|
|
248
374
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
375
|
+
// Replace entity-link semantics: delete the session's existing links, then
|
|
376
|
+
// re-insert for the new entity list. Without this, nlm reprocess amplifies
|
|
377
|
+
// stale links (ON CONFLICT DO NOTHING keeps dropped entities forever) and
|
|
378
|
+
// double-counts session_count on every re-ingest pass.
|
|
379
|
+
const rawNewEntities = [...new Set(record.entities.map((e) => e.trim()).filter(Boolean))];
|
|
380
|
+
// Resolve each extracted entity through entity_variants so merged surface
|
|
381
|
+
// forms bind to the canonical instead of resurrecting the retired source.
|
|
382
|
+
const variantRes = await client.query(`SELECT variant, canonical FROM entity_variants WHERE variant = ANY($1)`, [rawNewEntities]);
|
|
383
|
+
const variantMap = new Map(variantRes.rows.map((r) => [r.variant, r.canonical]));
|
|
384
|
+
const newEntities = rawNewEntities.map((name) => variantMap.get(name) ?? name);
|
|
385
|
+
const oldRes = await client.query("SELECT entity_canonical FROM session_entities WHERE session_id = $1", [record.id]);
|
|
386
|
+
const oldEntities = new Set(oldRes.rows.map((r) => r.entity_canonical));
|
|
387
|
+
await client.query("DELETE FROM session_entities WHERE session_id = $1", [record.id]);
|
|
388
|
+
for (const name of newEntities) {
|
|
253
389
|
await client.query(`INSERT INTO entities (canonical, type, status, source, first_seen_session, last_seen_session, session_count)
|
|
254
390
|
VALUES ($1, 'candidate', 'candidate', 'auto-detected', $2, $2, 0)
|
|
255
|
-
ON CONFLICT (canonical) DO
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
391
|
+
ON CONFLICT (canonical) DO NOTHING`, [name, record.id]);
|
|
392
|
+
// Update last_seen for entities newly added to this session; matches prior touch semantics.
|
|
393
|
+
if (!oldEntities.has(name)) {
|
|
394
|
+
await client.query("UPDATE entities SET last_seen_session = $1, updated_at = NOW() WHERE canonical = $2", [record.id, name]);
|
|
395
|
+
}
|
|
396
|
+
await client.query("INSERT INTO session_entities (session_id, entity_canonical) VALUES ($1, $2)", [record.id, name]);
|
|
397
|
+
}
|
|
398
|
+
// Recompute session_count exactly for every entity in (old union new) so
|
|
399
|
+
// counts reflect reality regardless of prior drift.
|
|
400
|
+
const allTouched = [...new Set([...oldEntities, ...newEntities])];
|
|
401
|
+
for (const name of allTouched) {
|
|
402
|
+
await client.query("UPDATE entities SET session_count = (SELECT COUNT(*) FROM session_entities WHERE entity_canonical = $1), updated_at = NOW() WHERE canonical = $1", [name]);
|
|
260
403
|
}
|
|
261
404
|
if (supersedes && supersedes.priorSessionId !== record.id) {
|
|
262
405
|
const predecessorStatus = supersedes.kind === "replaces" ? "replaced" : "superseded";
|
|
@@ -264,35 +407,17 @@ export class PgSessionStore {
|
|
|
264
407
|
VALUES ($1, $2, $3) ON CONFLICT DO NOTHING`, [record.id, supersedes.priorSessionId, supersedes.kind]);
|
|
265
408
|
await client.query("UPDATE sessions SET status = $1, updated_at = NOW() WHERE id = $2", [predecessorStatus, supersedes.priorSessionId]);
|
|
266
409
|
}
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
if (factSink !== null) {
|
|
273
|
-
await client.query("DELETE FROM facts WHERE source_session_id = $1", [record.id]);
|
|
274
|
-
if (factSink.facts.length > 0) {
|
|
275
|
-
for (const f of factSink.facts) {
|
|
276
|
-
await client.query(`INSERT INTO facts (id, kind, subject, predicate, value, source_session_id,
|
|
277
|
-
source_quote, created_at, superseded_by, confidence)
|
|
278
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, [f.id, f.kind, f.subject, f.predicate, f.value, f.sourceSessionId,
|
|
279
|
-
f.sourceQuote, f.createdAt, f.supersededBy, f.confidence]);
|
|
280
|
-
}
|
|
281
|
-
const values = factSink.facts
|
|
282
|
-
.map((_, i) => `($${i * 3 + 1}, $${i * 3 + 2}, $${i * 3 + 3})`)
|
|
283
|
-
.join(", ");
|
|
284
|
-
const params = [];
|
|
285
|
-
for (const f of factSink.facts)
|
|
286
|
-
params.push(f.subject, f.predicate, f.id);
|
|
287
|
-
await client.query(`UPDATE facts AS old
|
|
288
|
-
SET superseded_by = new_f.new_id
|
|
289
|
-
FROM (VALUES ${values}) AS new_f(subject, predicate, new_id)
|
|
290
|
-
WHERE old.subject = new_f.subject
|
|
291
|
-
AND old.predicate = new_f.predicate
|
|
292
|
-
AND old.superseded_by IS NULL
|
|
293
|
-
AND old.id != new_f.new_id`, params);
|
|
410
|
+
else {
|
|
411
|
+
const priorId = await findContinuesPredecessorPg(client, record.id, record.entities);
|
|
412
|
+
if (priorId !== null) {
|
|
413
|
+
await client.query(`INSERT INTO session_edges (from_session, to_session, kind)
|
|
414
|
+
VALUES ($1, $2, 'continues') ON CONFLICT DO NOTHING`, [record.id, priorId]);
|
|
294
415
|
}
|
|
295
416
|
}
|
|
417
|
+
// Atomic session+facts ingest on the session's own client. Single source of truth: pg-fact-ingest.ts.
|
|
418
|
+
if (factSink !== null) {
|
|
419
|
+
await ingestSessionFactsOnClient(client, record.id, factSink.facts);
|
|
420
|
+
}
|
|
296
421
|
await client.query("COMMIT");
|
|
297
422
|
}
|
|
298
423
|
catch (err) {
|
|
@@ -325,7 +450,7 @@ export class PgSessionStore {
|
|
|
325
450
|
// SQLite path. A per-fact embed failure leaves the fact current but
|
|
326
451
|
// semantically unreachable until a future re-ingest.
|
|
327
452
|
if (factSink !== null) {
|
|
328
|
-
for (const fact of factSink.facts) {
|
|
453
|
+
for (const fact of batchWinners(factSink.facts)) {
|
|
329
454
|
const factText = `${fact.subject} ${fact.predicate} ${fact.value}`.trim();
|
|
330
455
|
if (!factText)
|
|
331
456
|
continue;
|
|
@@ -385,26 +510,7 @@ export class PgSessionStore {
|
|
|
385
510
|
const client = await this.pool.connect();
|
|
386
511
|
try {
|
|
387
512
|
await client.query("BEGIN");
|
|
388
|
-
await client
|
|
389
|
-
if (facts.length > 0) {
|
|
390
|
-
for (const f of facts) {
|
|
391
|
-
await client.query(`INSERT INTO facts (id, kind, subject, predicate, value, source_session_id,
|
|
392
|
-
source_quote, created_at, superseded_by, confidence)
|
|
393
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)`, [f.id, f.kind, f.subject, f.predicate, f.value, f.sourceSessionId,
|
|
394
|
-
f.sourceQuote, f.createdAt, f.supersededBy, f.confidence]);
|
|
395
|
-
}
|
|
396
|
-
const values = facts.map((_, i) => `($${i * 3 + 1}, $${i * 3 + 2}, $${i * 3 + 3})`).join(", ");
|
|
397
|
-
const supersedeParams = [];
|
|
398
|
-
for (const f of facts)
|
|
399
|
-
supersedeParams.push(f.subject, f.predicate, f.id);
|
|
400
|
-
await client.query(`UPDATE facts AS old
|
|
401
|
-
SET superseded_by = new_f.new_id
|
|
402
|
-
FROM (VALUES ${values}) AS new_f(subject, predicate, new_id)
|
|
403
|
-
WHERE old.subject = new_f.subject
|
|
404
|
-
AND old.predicate = new_f.predicate
|
|
405
|
-
AND old.superseded_by IS NULL
|
|
406
|
-
AND old.id != new_f.new_id`, supersedeParams);
|
|
407
|
-
}
|
|
513
|
+
await ingestSessionFactsOnClient(client, sessionId, facts);
|
|
408
514
|
await client.query("COMMIT");
|
|
409
515
|
}
|
|
410
516
|
catch (err) {
|
|
@@ -415,7 +521,7 @@ export class PgSessionStore {
|
|
|
415
521
|
client.release();
|
|
416
522
|
}
|
|
417
523
|
if (embedder) {
|
|
418
|
-
for (const fact of facts) {
|
|
524
|
+
for (const fact of batchWinners(facts)) {
|
|
419
525
|
const factText = `${fact.subject} ${fact.predicate} ${fact.value}`.trim();
|
|
420
526
|
if (!factText)
|
|
421
527
|
continue;
|
|
@@ -488,8 +594,26 @@ export class PgSessionStore {
|
|
|
488
594
|
return out;
|
|
489
595
|
}
|
|
490
596
|
}
|
|
491
|
-
function
|
|
597
|
+
function sanitizeTsToken(token) {
|
|
598
|
+
return token.replace(/[&|!():*'<]/g, "");
|
|
599
|
+
}
|
|
600
|
+
function rowToSession(row, entitiesById, markersById, overlay, edges) {
|
|
492
601
|
const m = markersById.get(row.id);
|
|
602
|
+
const rawDecisions = m?.decisions ?? [];
|
|
603
|
+
const rawOpen = m?.open ?? [];
|
|
604
|
+
const activeOpen = [];
|
|
605
|
+
const promotedDecisions = [];
|
|
606
|
+
for (const text of rawOpen) {
|
|
607
|
+
const id = openQuestionId(row.id, text);
|
|
608
|
+
if (overlay.resolvedOpens.has(id))
|
|
609
|
+
continue;
|
|
610
|
+
const resolution = overlay.promotedOpens.get(id);
|
|
611
|
+
if (resolution !== undefined) {
|
|
612
|
+
promotedDecisions.push(resolution);
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
activeOpen.push(text);
|
|
616
|
+
}
|
|
493
617
|
return {
|
|
494
618
|
id: row.id,
|
|
495
619
|
runtime: row.runtime,
|
|
@@ -499,16 +623,20 @@ function rowToSession(row, entitiesById, markersById, edges) {
|
|
|
499
623
|
durationMin: row.duration_min,
|
|
500
624
|
label: row.label,
|
|
501
625
|
summary: row.summary,
|
|
502
|
-
status: row.status,
|
|
626
|
+
status: liveSessionStatus(row.transcript_path, row.status),
|
|
503
627
|
transcriptKind: row.transcript_kind ?? "",
|
|
504
628
|
transcriptPath: row.transcript_path,
|
|
505
629
|
body: row.body ?? "",
|
|
506
630
|
entities: entitiesById.get(row.id) ?? [],
|
|
507
|
-
decisions:
|
|
508
|
-
open:
|
|
631
|
+
decisions: [...rawDecisions, ...promotedDecisions],
|
|
632
|
+
open: activeOpen,
|
|
509
633
|
...(edges !== undefined
|
|
510
634
|
? { supersededBy: edges.supersededBy, supersedes: edges.supersedes }
|
|
511
635
|
: {}),
|
|
636
|
+
workstreamId: row.workstream_id ?? null,
|
|
637
|
+
classifierProvider: row.classifier_provider ?? null,
|
|
638
|
+
classifierModel: row.classifier_model ?? null,
|
|
639
|
+
classifierConfidence: row.classifier_confidence ?? null,
|
|
512
640
|
};
|
|
513
641
|
}
|
|
514
642
|
//# sourceMappingURL=pg-session-store.js.map
|