nlm-memory 0.20.1 → 0.21.12
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 +69 -12
- package/assets/replay-eval-dark.svg +26 -0
- package/assets/replay-eval-light.svg +26 -0
- package/dist/cli/digest.js +69 -4
- package/dist/cli/digest.js.map +1 -1
- package/dist/cli/nlm.d.ts +14 -0
- package/dist/cli/nlm.js +556 -71
- package/dist/cli/nlm.js.map +1 -1
- package/dist/cli/reprocess-job-adapters.d.ts +35 -0
- package/dist/cli/reprocess-job-adapters.js +80 -0
- package/dist/cli/reprocess-job-adapters.js.map +1 -0
- package/dist/cli/scope-backfill.d.ts +34 -0
- package/dist/cli/scope-backfill.js +237 -0
- package/dist/cli/scope-backfill.js.map +1 -0
- package/dist/cli/scope-coverage.d.ts +44 -0
- package/dist/cli/scope-coverage.js +208 -0
- package/dist/cli/scope-coverage.js.map +1 -0
- package/dist/cli/supersede.js +8 -7
- package/dist/cli/supersede.js.map +1 -1
- package/dist/core/actions/actions-log.d.ts +65 -8
- package/dist/core/actions/actions-log.js +134 -17
- package/dist/core/actions/actions-log.js.map +1 -1
- package/dist/core/actions/overlay.d.ts +11 -2
- package/dist/core/actions/overlay.js +18 -6
- package/dist/core/actions/overlay.js.map +1 -1
- package/dist/core/adapters/claude-code.d.ts +14 -0
- package/dist/core/adapters/claude-code.js +30 -0
- package/dist/core/adapters/claude-code.js.map +1 -1
- package/dist/core/adapters/from-source.js +11 -3
- package/dist/core/adapters/from-source.js.map +1 -1
- package/dist/core/adapters/fs-wiki-writer.d.ts +44 -0
- package/dist/core/adapters/fs-wiki-writer.js +132 -0
- package/dist/core/adapters/fs-wiki-writer.js.map +1 -0
- package/dist/core/adapters/hermes-agent.d.ts +15 -2
- package/dist/core/adapters/hermes-agent.js +57 -11
- package/dist/core/adapters/hermes-agent.js.map +1 -1
- package/dist/core/adapters/memory-wiki-writer.d.ts +8 -0
- package/dist/core/adapters/memory-wiki-writer.js +16 -0
- package/dist/core/adapters/memory-wiki-writer.js.map +1 -0
- package/dist/core/adapters/muse.d.ts +51 -0
- package/dist/core/adapters/muse.js +271 -0
- package/dist/core/adapters/muse.js.map +1 -0
- package/dist/core/adapters/opencode.d.ts +11 -4
- package/dist/core/adapters/opencode.js +41 -7
- package/dist/core/adapters/opencode.js.map +1 -1
- package/dist/core/alerts/alert-state.d.ts +17 -0
- package/dist/core/alerts/alert-state.js +45 -0
- package/dist/core/alerts/alert-state.js.map +1 -0
- package/dist/core/alerts/check-and-alert.d.ts +28 -0
- package/dist/core/alerts/check-and-alert.js +53 -0
- package/dist/core/alerts/check-and-alert.js.map +1 -0
- package/dist/core/alerts/drift-transition.d.ts +27 -0
- package/dist/core/alerts/drift-transition.js +56 -0
- package/dist/core/alerts/drift-transition.js.map +1 -0
- package/dist/core/alerts/embedder-transition.d.ts +23 -0
- package/dist/core/alerts/embedder-transition.js +40 -0
- package/dist/core/alerts/embedder-transition.js.map +1 -0
- package/dist/core/alerts/fire-alert.d.ts +24 -0
- package/dist/core/alerts/fire-alert.js +72 -0
- package/dist/core/alerts/fire-alert.js.map +1 -0
- package/dist/core/alerts/job-alert.d.ts +12 -0
- package/dist/core/alerts/job-alert.js +37 -0
- package/dist/core/alerts/job-alert.js.map +1 -0
- package/dist/core/alerts/types.d.ts +104 -0
- package/dist/core/alerts/types.js +11 -0
- package/dist/core/alerts/types.js.map +1 -0
- package/dist/core/alerts/wiki-alert.d.ts +15 -0
- package/dist/core/alerts/wiki-alert.js +31 -0
- package/dist/core/alerts/wiki-alert.js.map +1 -0
- package/dist/core/classifier/prompt.d.ts +13 -0
- package/dist/core/classifier/prompt.js +52 -2
- package/dist/core/classifier/prompt.js.map +1 -1
- package/dist/core/dataset/build-dataset.d.ts +16 -1
- package/dist/core/dataset/build-dataset.js +46 -13
- package/dist/core/dataset/build-dataset.js.map +1 -1
- package/dist/core/digest/compose.d.ts +11 -3
- package/dist/core/digest/compose.js +70 -17
- package/dist/core/digest/compose.js.map +1 -1
- package/dist/core/digest/hook-injection.d.ts +43 -0
- package/dist/core/digest/hook-injection.js +87 -0
- package/dist/core/digest/hook-injection.js.map +1 -0
- package/dist/core/digest/hook-version-parity.d.ts +31 -0
- package/dist/core/digest/hook-version-parity.js +56 -0
- package/dist/core/digest/hook-version-parity.js.map +1 -0
- package/dist/core/embedding/pg-embed-backfill.js +1 -1
- package/dist/core/embedding/pg-embed-backfill.js.map +1 -1
- package/dist/core/exemplars/capture-from-session.d.ts +2 -1
- package/dist/core/exemplars/capture-from-session.js +4 -4
- package/dist/core/exemplars/capture-from-session.js.map +1 -1
- package/dist/core/exemplars/embed-backfill.d.ts +9 -0
- package/dist/core/exemplars/embed-backfill.js +9 -1
- package/dist/core/exemplars/embed-backfill.js.map +1 -1
- package/dist/core/exemplars/extract-exemplar.d.ts +2 -0
- package/dist/core/exemplars/extract-exemplar.js +7 -1
- package/dist/core/exemplars/extract-exemplar.js.map +1 -1
- package/dist/core/exemplars/ingest-exemplar.d.ts +1 -0
- package/dist/core/exemplars/ingest-exemplar.js +1 -0
- package/dist/core/exemplars/ingest-exemplar.js.map +1 -1
- package/dist/core/exemplars/recall-code.d.ts +1 -1
- package/dist/core/exemplars/recall-code.js +2 -2
- package/dist/core/exemplars/recall-code.js.map +1 -1
- package/dist/core/facts/backfill-facts.d.ts +1 -1
- package/dist/core/facts/backfill-facts.js +4 -4
- package/dist/core/facts/backfill-facts.js.map +1 -1
- package/dist/core/facts/embed-backfill.d.ts +54 -0
- package/dist/core/facts/embed-backfill.js +109 -0
- package/dist/core/facts/embed-backfill.js.map +1 -0
- package/dist/core/facts/reconcile-facts.d.ts +42 -0
- package/dist/core/facts/reconcile-facts.js +133 -0
- package/dist/core/facts/reconcile-facts.js.map +1 -0
- package/dist/core/health/embed-failure-state.d.ts +4 -0
- package/dist/core/health/embed-failure-state.js +12 -0
- package/dist/core/health/embed-failure-state.js.map +1 -0
- package/dist/core/health/warmup-retry.d.ts +27 -0
- package/dist/core/health/warmup-retry.js +53 -0
- package/dist/core/health/warmup-retry.js.map +1 -0
- package/dist/core/health/warmup-state.d.ts +15 -0
- package/dist/core/health/warmup-state.js +15 -1
- package/dist/core/health/warmup-state.js.map +1 -1
- package/dist/core/hook/cite-memo.d.ts +11 -7
- package/dist/core/hook/cite-memo.js +25 -17
- package/dist/core/hook/cite-memo.js.map +1 -1
- package/dist/core/hook/claude-settings.d.ts +1 -1
- package/dist/core/hook/claude-settings.js +0 -1
- package/dist/core/hook/claude-settings.js.map +1 -1
- package/dist/core/hook/hook-log.d.ts +13 -2
- package/dist/core/hook/hook-log.js +16 -8
- package/dist/core/hook/hook-log.js.map +1 -1
- package/dist/core/hook/memo.d.ts +10 -6
- package/dist/core/hook/memo.js +25 -17
- package/dist/core/hook/memo.js.map +1 -1
- package/dist/core/hook/pointer-block.d.ts +8 -1
- package/dist/core/hook/pointer-block.js +17 -4
- package/dist/core/hook/pointer-block.js.map +1 -1
- package/dist/core/hook/resolve-conversation-by-query.d.ts +6 -5
- package/dist/core/hook/resolve-conversation-by-query.js +24 -12
- package/dist/core/hook/resolve-conversation-by-query.js.map +1 -1
- package/dist/core/ingest/backfill-derivables.d.ts +92 -0
- package/dist/core/ingest/backfill-derivables.js +175 -0
- package/dist/core/ingest/backfill-derivables.js.map +1 -0
- package/dist/core/ingest/ingest-session.d.ts +1 -1
- package/dist/core/ingest/ingest-session.js +23 -3
- package/dist/core/ingest/ingest-session.js.map +1 -1
- package/dist/core/ingest/reclassify-oversized.d.ts +1 -1
- package/dist/core/ingest/reclassify-oversized.js +8 -2
- package/dist/core/ingest/reclassify-oversized.js.map +1 -1
- package/dist/core/ingest/reprocess.d.ts +1 -1
- package/dist/core/ingest/reprocess.js +7 -2
- package/dist/core/ingest/reprocess.js.map +1 -1
- package/dist/core/ingest/transcript-derivables.d.ts +27 -0
- package/dist/core/ingest/transcript-derivables.js +138 -0
- package/dist/core/ingest/transcript-derivables.js.map +1 -0
- package/dist/core/jobs/job-supervisor.d.ts +168 -0
- package/dist/core/jobs/job-supervisor.js +335 -0
- package/dist/core/jobs/job-supervisor.js.map +1 -0
- package/dist/core/learner/model.d.ts +63 -0
- package/dist/core/learner/model.js +30 -0
- package/dist/core/learner/model.js.map +1 -0
- package/dist/core/learner/service.d.ts +42 -0
- package/dist/core/learner/service.js +74 -0
- package/dist/core/learner/service.js.map +1 -0
- package/dist/core/learner/summary.d.ts +28 -0
- package/dist/core/learner/summary.js +70 -0
- package/dist/core/learner/summary.js.map +1 -0
- package/dist/core/learner/validate.d.ts +18 -0
- package/dist/core/learner/validate.js +95 -0
- package/dist/core/learner/validate.js.map +1 -0
- package/dist/core/metrics/re-derivation.d.ts +31 -2
- package/dist/core/metrics/re-derivation.js +36 -4
- package/dist/core/metrics/re-derivation.js.map +1 -1
- package/dist/core/outcome/coverage.d.ts +33 -0
- package/dist/core/outcome/coverage.js +46 -0
- package/dist/core/outcome/coverage.js.map +1 -0
- package/dist/core/outcome/rollup.d.ts +33 -0
- package/dist/core/outcome/rollup.js +113 -0
- package/dist/core/outcome/rollup.js.map +1 -0
- package/dist/core/providers/provider-registry.d.ts +40 -23
- package/dist/core/providers/provider-registry.js +99 -81
- package/dist/core/providers/provider-registry.js.map +1 -1
- package/dist/core/recall/citation-log.d.ts +10 -5
- package/dist/core/recall/citation-log.js +21 -10
- package/dist/core/recall/citation-log.js.map +1 -1
- package/dist/core/recall/hook-recall-log.d.ts +7 -4
- package/dist/core/recall/hook-recall-log.js +14 -8
- package/dist/core/recall/hook-recall-log.js.map +1 -1
- package/dist/core/recall/miss-log.d.ts +7 -4
- package/dist/core/recall/miss-log.js +18 -11
- package/dist/core/recall/miss-log.js.map +1 -1
- package/dist/core/recall/query-log.d.ts +8 -5
- package/dist/core/recall/query-log.js +16 -9
- package/dist/core/recall/query-log.js.map +1 -1
- package/dist/core/recall/recall-service.d.ts +10 -1
- package/dist/core/recall/recall-service.js +31 -12
- package/dist/core/recall/recall-service.js.map +1 -1
- package/dist/core/recall/recent-log.d.ts +6 -1
- package/dist/core/recall/recent-log.js +13 -5
- package/dist/core/recall/recent-log.js.map +1 -1
- package/dist/core/recall/related-concepts.d.ts +29 -0
- package/dist/core/recall/related-concepts.js +67 -0
- package/dist/core/recall/related-concepts.js.map +1 -0
- package/dist/core/recall/related-exemplars.d.ts +1 -1
- package/dist/core/recall/related-exemplars.js +2 -2
- package/dist/core/recall/related-exemplars.js.map +1 -1
- package/dist/core/recall/related-facts.d.ts +1 -1
- package/dist/core/recall/related-facts.js +3 -3
- package/dist/core/recall/related-facts.js.map +1 -1
- package/dist/core/recall-facts/fact-query-log.d.ts +8 -4
- package/dist/core/recall-facts/fact-query-log.js +16 -8
- package/dist/core/recall-facts/fact-query-log.js.map +1 -1
- package/dist/core/recall-facts/fact-recall-service.d.ts +2 -2
- package/dist/core/recall-facts/fact-recall-service.js +13 -12
- package/dist/core/recall-facts/fact-recall-service.js.map +1 -1
- package/dist/core/scheduler/scan-once.js +15 -12
- package/dist/core/scheduler/scan-once.js.map +1 -1
- package/dist/core/scheduler/scheduler.d.ts +3 -0
- package/dist/core/scheduler/scheduler.js +45 -10
- package/dist/core/scheduler/scheduler.js.map +1 -1
- package/dist/core/scope/alias-map.d.ts +10 -0
- package/dist/core/scope/alias-map.js +41 -0
- package/dist/core/scope/alias-map.js.map +1 -0
- package/dist/core/scope/derive-scope.d.ts +8 -0
- package/dist/core/scope/derive-scope.js +54 -0
- package/dist/core/scope/derive-scope.js.map +1 -0
- package/dist/core/scope/scope-clause.d.ts +16 -0
- package/dist/core/scope/scope-clause.js +21 -0
- package/dist/core/scope/scope-clause.js.map +1 -0
- package/dist/core/scope/stamp-flag.d.ts +1 -0
- package/dist/core/scope/stamp-flag.js +4 -0
- package/dist/core/scope/stamp-flag.js.map +1 -0
- package/dist/core/signals/code-signal.d.ts +1 -0
- package/dist/core/signals/code-signal.js +1 -0
- package/dist/core/signals/code-signal.js.map +1 -1
- package/dist/core/signals/failure-mode-recall.d.ts +1 -1
- package/dist/core/signals/failure-mode-recall.js +2 -2
- package/dist/core/signals/failure-mode-recall.js.map +1 -1
- package/dist/core/signals/ingest-signal.d.ts +1 -1
- package/dist/core/signals/ingest-signal.js +3 -1
- package/dist/core/signals/ingest-signal.js.map +1 -1
- package/dist/core/signals/stamp-scope.d.ts +18 -0
- package/dist/core/signals/stamp-scope.js +28 -0
- package/dist/core/signals/stamp-scope.js.map +1 -0
- package/dist/core/sources/source-registry.d.ts +49 -28
- package/dist/core/sources/source-registry.js +113 -70
- package/dist/core/sources/source-registry.js.map +1 -1
- package/dist/core/storage/pg-code-exemplar-store.d.ts +19 -10
- package/dist/core/storage/pg-code-exemplar-store.js +62 -27
- package/dist/core/storage/pg-code-exemplar-store.js.map +1 -1
- package/dist/core/storage/pg-entity-store.d.ts +6 -1
- package/dist/core/storage/pg-entity-store.js +33 -18
- package/dist/core/storage/pg-entity-store.js.map +1 -1
- package/dist/core/storage/pg-fact-ingest.d.ts +1 -1
- package/dist/core/storage/pg-fact-ingest.js +10 -7
- package/dist/core/storage/pg-fact-ingest.js.map +1 -1
- package/dist/core/storage/pg-fact-store.d.ts +29 -17
- package/dist/core/storage/pg-fact-store.js +99 -46
- package/dist/core/storage/pg-fact-store.js.map +1 -1
- package/dist/core/storage/pg-learner-store.d.ts +27 -0
- package/dist/core/storage/pg-learner-store.js +174 -0
- package/dist/core/storage/pg-learner-store.js.map +1 -0
- package/dist/core/storage/pg-session-store.d.ts +25 -21
- package/dist/core/storage/pg-session-store.js +186 -114
- package/dist/core/storage/pg-session-store.js.map +1 -1
- package/dist/core/storage/pg-signal-store.d.ts +10 -5
- package/dist/core/storage/pg-signal-store.js +26 -13
- package/dist/core/storage/pg-signal-store.js.map +1 -1
- package/dist/core/storage/pg-storage.d.ts +4 -0
- package/dist/core/storage/pg-storage.js +6 -0
- package/dist/core/storage/pg-storage.js.map +1 -1
- package/dist/core/storage/pg-workstream-store.d.ts +17 -11
- package/dist/core/storage/pg-workstream-store.js +47 -29
- package/dist/core/storage/pg-workstream-store.js.map +1 -1
- package/dist/core/storage/sqlite-code-exemplar-store.d.ts +24 -10
- package/dist/core/storage/sqlite-code-exemplar-store.js +86 -40
- package/dist/core/storage/sqlite-code-exemplar-store.js.map +1 -1
- package/dist/core/storage/sqlite-entity-store.d.ts +8 -1
- package/dist/core/storage/sqlite-entity-store.js +46 -27
- package/dist/core/storage/sqlite-entity-store.js.map +1 -1
- package/dist/core/storage/sqlite-fact-store.d.ts +45 -19
- package/dist/core/storage/sqlite-fact-store.js +175 -64
- package/dist/core/storage/sqlite-fact-store.js.map +1 -1
- package/dist/core/storage/sqlite-learner-store.d.ts +33 -0
- package/dist/core/storage/sqlite-learner-store.js +178 -0
- package/dist/core/storage/sqlite-learner-store.js.map +1 -0
- package/dist/core/storage/sqlite-outcome-store.d.ts +104 -0
- package/dist/core/storage/sqlite-outcome-store.js +211 -0
- package/dist/core/storage/sqlite-outcome-store.js.map +1 -0
- package/dist/core/storage/sqlite-session-store.d.ts +50 -21
- package/dist/core/storage/sqlite-session-store.js +233 -149
- package/dist/core/storage/sqlite-session-store.js.map +1 -1
- package/dist/core/storage/sqlite-signal-store.d.ts +12 -5
- package/dist/core/storage/sqlite-signal-store.js +29 -15
- package/dist/core/storage/sqlite-signal-store.js.map +1 -1
- package/dist/core/storage/sqlite-storage.d.ts +4 -0
- package/dist/core/storage/sqlite-storage.js +10 -2
- package/dist/core/storage/sqlite-storage.js.map +1 -1
- package/dist/core/storage/sqlite-workstream-store.d.ts +17 -11
- package/dist/core/storage/sqlite-workstream-store.js +53 -31
- package/dist/core/storage/sqlite-workstream-store.js.map +1 -1
- package/dist/core/storage/supersedence-log.d.ts +10 -7
- package/dist/core/storage/supersedence-log.js +21 -14
- package/dist/core/storage/supersedence-log.js.map +1 -1
- package/dist/core/telemetry/probe-filter.d.ts +35 -0
- package/dist/core/telemetry/probe-filter.js +56 -0
- package/dist/core/telemetry/probe-filter.js.map +1 -0
- package/dist/core/tenancy/default-team.d.ts +6 -0
- package/dist/core/tenancy/default-team.js +7 -0
- package/dist/core/tenancy/default-team.js.map +1 -0
- package/dist/core/tenancy/hosted-mode.d.ts +9 -0
- package/dist/core/tenancy/hosted-mode.js +12 -0
- package/dist/core/tenancy/hosted-mode.js.map +1 -0
- package/dist/core/tenancy/team-auth.d.ts +16 -0
- package/dist/core/tenancy/team-auth.js +22 -0
- package/dist/core/tenancy/team-auth.js.map +1 -0
- package/dist/core/tenancy/team-token-store.d.ts +45 -0
- package/dist/core/tenancy/team-token-store.js +52 -0
- package/dist/core/tenancy/team-token-store.js.map +1 -0
- package/dist/core/tenancy/tenant-clause.d.ts +37 -0
- package/dist/core/tenancy/tenant-clause.js +11 -0
- package/dist/core/tenancy/tenant-clause.js.map +1 -0
- package/dist/core/tenancy/tenant-state-path.d.ts +28 -0
- package/dist/core/tenancy/tenant-state-path.js +49 -0
- package/dist/core/tenancy/tenant-state-path.js.map +1 -0
- package/dist/core/update-check/check.d.ts +6 -0
- package/dist/core/update-check/check.js +35 -0
- package/dist/core/update-check/check.js.map +1 -1
- package/dist/core/wiki/project.d.ts +17 -0
- package/dist/core/wiki/project.js +37 -0
- package/dist/core/wiki/project.js.map +1 -0
- package/dist/core/wiki/reconcile.d.ts +15 -0
- package/dist/core/wiki/reconcile.js +14 -0
- package/dist/core/wiki/reconcile.js.map +1 -0
- package/dist/core/wiki/render.d.ts +5 -0
- package/dist/core/wiki/render.js +84 -0
- package/dist/core/wiki/render.js.map +1 -0
- package/dist/core/wiki/rollup.d.ts +28 -0
- package/dist/core/wiki/rollup.js +91 -0
- package/dist/core/wiki/rollup.js.map +1 -0
- package/dist/core/wiki/select.d.ts +8 -0
- package/dist/core/wiki/select.js +7 -0
- package/dist/core/wiki/select.js.map +1 -0
- package/dist/core/wiki/slug.d.ts +36 -0
- package/dist/core/wiki/slug.js +71 -0
- package/dist/core/wiki/slug.js.map +1 -0
- package/dist/core/wiki/types.d.ts +37 -0
- package/dist/core/wiki/types.js +2 -0
- package/dist/core/wiki/types.js.map +1 -0
- package/dist/core/work-digest/build-work-digest.d.ts +1 -1
- package/dist/core/work-digest/build-work-digest.js +3 -3
- package/dist/core/work-digest/build-work-digest.js.map +1 -1
- package/dist/core/workstream/bind.d.ts +3 -2
- package/dist/core/workstream/bind.js +29 -10
- package/dist/core/workstream/bind.js.map +1 -1
- package/dist/core/workstream/model.d.ts +1 -0
- package/dist/core/workstream/model.js.map +1 -1
- package/dist/core/workstream/rollup.d.ts +1 -1
- package/dist/core/workstream/rollup.js +6 -6
- package/dist/core/workstream/rollup.js.map +1 -1
- package/dist/hook/hook-helpers.d.ts +14 -0
- package/dist/hook/hook-helpers.js +29 -2
- package/dist/hook/hook-helpers.js.map +1 -1
- package/dist/hook/pre-compact-hook.js +8 -5
- package/dist/hook/pre-compact-hook.js.map +1 -1
- package/dist/hook/prompt-recall-hook.d.ts +7 -3
- package/dist/hook/prompt-recall-hook.js +35 -20
- package/dist/hook/prompt-recall-hook.js.map +1 -1
- package/dist/hook/recall-over-http.d.ts +28 -3
- package/dist/hook/recall-over-http.js +58 -12
- package/dist/hook/recall-over-http.js.map +1 -1
- package/dist/hook/session-end-hook.js +7 -5
- package/dist/hook/session-end-hook.js.map +1 -1
- package/dist/hook/session-start-hook.d.ts +14 -1
- package/dist/hook/session-start-hook.js +57 -16
- package/dist/hook/session-start-hook.js.map +1 -1
- package/dist/hook/stop-hook.js +11 -8
- package/dist/hook/stop-hook.js.map +1 -1
- package/dist/http/app.d.ts +47 -3
- package/dist/http/app.js +379 -132
- package/dist/http/app.js.map +1 -1
- package/dist/install/claude-code.d.ts +17 -0
- package/dist/install/claude-code.js +44 -5
- package/dist/install/claude-code.js.map +1 -1
- package/dist/install/codex.d.ts +23 -2
- package/dist/install/codex.js +111 -14
- package/dist/install/codex.js.map +1 -1
- package/dist/install/cursor.d.ts +2 -2
- package/dist/install/cursor.js +7 -7
- package/dist/install/cursor.js.map +1 -1
- package/dist/install/hermes.d.ts +8 -0
- package/dist/install/hermes.js +28 -5
- package/dist/install/hermes.js.map +1 -1
- package/dist/install/local-source.d.ts +20 -0
- package/dist/install/local-source.js +28 -0
- package/dist/install/local-source.js.map +1 -0
- package/dist/install/mcp-block.d.ts +77 -0
- package/dist/install/mcp-block.js +99 -0
- package/dist/install/mcp-block.js.map +1 -0
- package/dist/install/pi.d.ts +26 -0
- package/dist/install/pi.js +43 -0
- package/dist/install/pi.js.map +1 -1
- package/dist/install/setup.js +6 -0
- package/dist/install/setup.js.map +1 -1
- package/dist/install/windsurf.d.ts +2 -2
- package/dist/install/windsurf.js +7 -7
- package/dist/install/windsurf.js.map +1 -1
- package/dist/llm/classifier-box.d.ts +5 -0
- package/dist/llm/classifier-box.js +5 -0
- package/dist/llm/classifier-box.js.map +1 -1
- package/dist/mcp/server.d.ts +94 -15
- package/dist/mcp/server.js +402 -79
- package/dist/mcp/server.js.map +1 -1
- package/dist/ports/clock.d.ts +12 -0
- package/dist/ports/clock.js +10 -0
- package/dist/ports/clock.js.map +1 -0
- package/dist/ports/code-exemplar-store.d.ts +13 -13
- package/dist/ports/entity-store.d.ts +5 -1
- package/dist/ports/fact-store.d.ts +0 -0
- package/dist/ports/fact-store.js +0 -0
- package/dist/ports/fact-store.js.map +1 -1
- package/dist/ports/learner-store.d.ts +80 -0
- package/dist/ports/learner-store.js +19 -0
- package/dist/ports/learner-store.js.map +1 -0
- package/dist/ports/outcome.d.ts +85 -0
- package/dist/ports/outcome.js +11 -0
- package/dist/ports/outcome.js.map +1 -0
- package/dist/ports/session-store.d.ts +20 -20
- package/dist/ports/session-store.js +5 -0
- package/dist/ports/session-store.js.map +1 -1
- package/dist/ports/signal-store.d.ts +12 -8
- package/dist/ports/spawn-child.d.ts +14 -0
- package/dist/ports/spawn-child.js +10 -0
- package/dist/ports/spawn-child.js.map +1 -0
- package/dist/ports/storage.d.ts +6 -0
- package/dist/ports/transcript-adapter.d.ts +11 -0
- package/dist/ports/wiki-writer.d.ts +19 -0
- package/dist/ports/wiki-writer.js +12 -0
- package/dist/ports/wiki-writer.js.map +1 -0
- package/dist/ports/workstream-store.d.ts +20 -12
- package/dist/shared/net.d.ts +3 -0
- package/dist/shared/net.js +4 -0
- package/dist/shared/net.js.map +1 -0
- package/dist/shared/types.d.ts +55 -0
- package/dist/shared/types.js +4 -1
- package/dist/shared/types.js.map +1 -1
- package/dist/shared/version.d.ts +9 -0
- package/dist/shared/version.js +11 -0
- package/dist/shared/version.js.map +1 -0
- package/dist/ui/assets/index-D6F3xAis.js +11 -0
- package/dist/ui/index.html +1 -1
- package/migrations/029_project_scope.sql +21 -0
- package/migrations/032_session_derivables.sql +11 -0
- package/migrations/033_actions_kind_check.sql +58 -0
- package/migrations/034_tenancy_teams_and_stamps.sql +40 -0
- package/migrations/035_tenancy_entity_rekey.sql +75 -0
- package/migrations/036_tenant_name_uniqueness.sql +59 -0
- package/migrations/037_sessions_covering_index.sql +25 -0
- package/migrations/038_sources_muse.sql +33 -0
- package/migrations/039_learner_evidence.sql +51 -0
- package/migrations/pg/030_project_scope.sql +23 -0
- package/migrations/pg/031_widen_session_edges_kind.sql +11 -0
- package/migrations/pg/032_session_derivables.sql +17 -0
- package/migrations/pg/033_actions_kind_check.sql +17 -0
- package/migrations/pg/034_tenancy.sql +77 -0
- package/migrations/pg/035_tenant_name_uniqueness.sql +15 -0
- package/migrations/pg/036_sources_muse.sql +12 -0
- package/migrations/pg/037_learner_evidence.sql +55 -0
- package/nlm/index.js +258 -59
- package/package.json +10 -5
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/scripts/prompt-recall-hook.mjs +256 -58
- package/plugin/scripts/session-start-hook.mjs +283 -55
- package/plugin/scripts/stop-hook.mjs +199 -38
- package/dist/hook/subagent-start-hook.d.ts +0 -28
- package/dist/hook/subagent-start-hook.js +0 -81
- package/dist/hook/subagent-start-hook.js.map +0 -1
- package/dist/ui/assets/index-CMpQkbQy.js +0 -11
|
@@ -10,6 +10,16 @@
|
|
|
10
10
|
* markers + session_embedding_chunks (vec0). Idle-status overlay (computed
|
|
11
11
|
* from transcript mtime) is deferred to a later phase; A.2 returns the
|
|
12
12
|
* persisted status verbatim.
|
|
13
|
+
*
|
|
14
|
+
* Tenancy (program spec §4, M2 plan Wave B): every method takes `tenantId`
|
|
15
|
+
* as its non-optional first parameter. `sessions`, `entities`,
|
|
16
|
+
* `entity_variants`, and `session_entities` are STAMP tables and every
|
|
17
|
+
* SELECT/UPDATE/DELETE against them routes its WHERE fragment through
|
|
18
|
+
* `tenantClause`; INSERTs stamp `tenant_id` explicitly. `markers` and
|
|
19
|
+
* `session_edges` carry no `tenant_id` column (DERIVE-VIA-FK) — they are
|
|
20
|
+
* reached only through an already tenant-filtered session id set, or (for
|
|
21
|
+
* `recentMarkers`/`markSuperseded`'s fact cascade) via an explicit join back
|
|
22
|
+
* to a tenant-filtered `sessions` row.
|
|
13
23
|
*/
|
|
14
24
|
import { existsSync, mkdirSync } from "node:fs";
|
|
15
25
|
import { dirname, resolve } from "node:path";
|
|
@@ -21,6 +31,9 @@ import { runMigrations } from "./migrate.js";
|
|
|
21
31
|
import { tokenize } from "../recall/tokenize.js";
|
|
22
32
|
import { chunkSessionText } from "../embedding/chunk-body.js";
|
|
23
33
|
import { batchWinners } from "./fact-batch.js";
|
|
34
|
+
import { tenantClause } from "../tenancy/tenant-clause.js";
|
|
35
|
+
import { DEFAULT_TEAM_ID } from "../tenancy/default-team.js";
|
|
36
|
+
import { recordEmbedFailure } from "../health/embed-failure-state.js";
|
|
24
37
|
/**
|
|
25
38
|
* Find the most recent prior session whose entity-set is identical to the new
|
|
26
39
|
* session's, so a `continues` edge can link the two. "Identical" means same set
|
|
@@ -28,18 +41,28 @@ import { batchWinners } from "./fact-batch.js";
|
|
|
28
41
|
* session is not superseding/replacing anything — a continuation extends a prior
|
|
29
42
|
* topic rather than overturning it. Returns null when there is no such prior
|
|
30
43
|
* (no entities, or no exact-set match), which leaves the pair unlinked and thus
|
|
31
|
-
* eligible to surface in the re_derivation_rate metric.
|
|
44
|
+
* eligible to surface in the re_derivation_rate metric. Scoped to the same
|
|
45
|
+
* tenant as the new session — a continuation can never cross tenants.
|
|
32
46
|
*/
|
|
33
|
-
function findContinuesPredecessor(db, newId, rawEntities) {
|
|
47
|
+
function findContinuesPredecessor(db, tenantId, newId, rawEntities) {
|
|
34
48
|
const entities = [...new Set(rawEntities.map((e) => e.trim()).filter(Boolean))];
|
|
35
49
|
if (entities.length === 0)
|
|
36
50
|
return null;
|
|
37
51
|
const placeholders = entities.map(() => "?").join(",");
|
|
52
|
+
// session_entities rows are always written with the same tenant_id as their
|
|
53
|
+
// owning session (every write site stamps both together), so scoping the
|
|
54
|
+
// outer `sessions` row by tenant is already sufficient. The `se.tenant_id =
|
|
55
|
+
// s.tenant_id` join condition below is defense in depth (guard test C4
|
|
56
|
+
// explicitly permits column-to-column tenant equality as a join form) — it
|
|
57
|
+
// costs nothing and survives a future write path that stamps the two rows
|
|
58
|
+
// inconsistently.
|
|
59
|
+
const tc = tenantClause(tenantId, "s.tenant_id");
|
|
38
60
|
const row = db
|
|
39
61
|
.prepare(`SELECT s.id AS id
|
|
40
62
|
FROM sessions s
|
|
41
|
-
JOIN session_entities se ON se.session_id = s.id
|
|
63
|
+
JOIN session_entities se ON se.session_id = s.id AND se.tenant_id = s.tenant_id
|
|
42
64
|
WHERE s.id != ?
|
|
65
|
+
AND ${tc.sql}
|
|
43
66
|
AND se.entity_canonical IN (${placeholders})
|
|
44
67
|
GROUP BY s.id
|
|
45
68
|
HAVING COUNT(DISTINCT se.entity_canonical) = ?
|
|
@@ -47,13 +70,16 @@ function findContinuesPredecessor(db, newId, rawEntities) {
|
|
|
47
70
|
= (SELECT COUNT(*) FROM session_entities x WHERE x.session_id = s.id)
|
|
48
71
|
ORDER BY s.started_at DESC, s.id DESC
|
|
49
72
|
LIMIT 1`)
|
|
50
|
-
.get(newId, ...entities, entities.length);
|
|
73
|
+
.get(newId, tc.param, ...entities, entities.length);
|
|
51
74
|
return row?.id ?? null;
|
|
52
75
|
}
|
|
53
76
|
export class SqliteSessionStore {
|
|
54
77
|
db;
|
|
55
|
-
|
|
56
|
-
|
|
78
|
+
// Keyed by tenantId — a single-tenant Map entry today (DEFAULT_TEAM_ID),
|
|
79
|
+
// but the overlay's content is tenant-scoped (program spec §4.6 hardening
|
|
80
|
+
// 1) and must never bleed across tenants once M3 adds real multi-tenant
|
|
81
|
+
// traffic to one daemon instance.
|
|
82
|
+
overlayCache = new Map();
|
|
57
83
|
/**
|
|
58
84
|
* @internal. Construct via SqliteStorage.create(...) instead. Direct
|
|
59
85
|
* construction is preserved for the SqliteStorage adapter only; all
|
|
@@ -90,38 +116,42 @@ export class SqliteSessionStore {
|
|
|
90
116
|
return this.db;
|
|
91
117
|
}
|
|
92
118
|
invalidateOverlayCache() {
|
|
93
|
-
this.overlayCache
|
|
119
|
+
this.overlayCache.clear();
|
|
94
120
|
}
|
|
95
|
-
overlay() {
|
|
121
|
+
overlay(tenantId) {
|
|
96
122
|
// TTL backstop: explicit invalidation covers the daemon's own writers; the
|
|
97
123
|
// 30s expiry bounds staleness if another process ever writes actions to
|
|
98
124
|
// this database file.
|
|
99
|
-
|
|
100
|
-
|
|
125
|
+
const cached = this.overlayCache.get(tenantId);
|
|
126
|
+
if (cached !== undefined && Date.now() - cached.at < 30_000) {
|
|
127
|
+
return cached.overlay;
|
|
101
128
|
}
|
|
102
|
-
|
|
103
|
-
this.
|
|
104
|
-
return
|
|
129
|
+
const overlay = loadActionOverlay(this.db, tenantId);
|
|
130
|
+
this.overlayCache.set(tenantId, { overlay, at: Date.now() });
|
|
131
|
+
return overlay;
|
|
105
132
|
}
|
|
106
133
|
/** Recently-written sessions ordered by created_at desc. Powers /live Writes column. */
|
|
107
|
-
recentWrites(limit) {
|
|
134
|
+
recentWrites(tenantId, limit) {
|
|
135
|
+
const tc = tenantClause(tenantId);
|
|
108
136
|
const rows = this.db
|
|
109
137
|
.prepare(`SELECT id, runtime, label, summary, created_at AS createdAt
|
|
110
138
|
FROM sessions
|
|
139
|
+
WHERE ${tc.sql}
|
|
111
140
|
ORDER BY created_at DESC
|
|
112
141
|
LIMIT ?`)
|
|
113
|
-
.all(limit);
|
|
142
|
+
.all(tc.param, limit);
|
|
114
143
|
if (rows.length === 0)
|
|
115
144
|
return [];
|
|
116
145
|
// Pull associated entities in one shot keyed by session id; cheap because
|
|
117
146
|
// limit is small (<=50). Renders as topic chips on the /live row.
|
|
118
147
|
const placeholders = rows.map(() => "?").join(",");
|
|
148
|
+
const entTc = tenantClause(tenantId);
|
|
119
149
|
const entityRows = this.db
|
|
120
150
|
.prepare(`SELECT session_id, entity_canonical
|
|
121
151
|
FROM session_entities
|
|
122
|
-
WHERE session_id IN (${placeholders})
|
|
152
|
+
WHERE ${entTc.sql} AND session_id IN (${placeholders})
|
|
123
153
|
ORDER BY entity_canonical`)
|
|
124
|
-
.all(...rows.map((r) => r.id));
|
|
154
|
+
.all(entTc.param, ...rows.map((r) => r.id));
|
|
125
155
|
const byId = new Map();
|
|
126
156
|
for (const e of entityRows) {
|
|
127
157
|
const list = byId.get(e.session_id);
|
|
@@ -133,14 +163,16 @@ export class SqliteSessionStore {
|
|
|
133
163
|
return rows.map((r) => ({ ...r, entities: byId.get(r.id) ?? [] }));
|
|
134
164
|
}
|
|
135
165
|
/** Recently-extracted markers ordered by session created_at desc. Powers /live Decisions column. */
|
|
136
|
-
recentMarkers(limit) {
|
|
166
|
+
recentMarkers(tenantId, limit) {
|
|
167
|
+
const tc = tenantClause(tenantId, "s.tenant_id");
|
|
137
168
|
return this.db
|
|
138
169
|
.prepare(`SELECT m.session_id AS sessionId, m.kind, m.text, s.label, s.created_at AS createdAt
|
|
139
170
|
FROM markers m
|
|
140
171
|
JOIN sessions s ON s.id = m.session_id
|
|
172
|
+
WHERE ${tc.sql}
|
|
141
173
|
ORDER BY s.created_at DESC, m.position ASC
|
|
142
174
|
LIMIT ?`)
|
|
143
|
-
.all(limit);
|
|
175
|
+
.all(tc.param, limit);
|
|
144
176
|
}
|
|
145
177
|
/**
|
|
146
178
|
* Atomic ingest: writes the session row, markers, entity rows + links,
|
|
@@ -153,7 +185,7 @@ export class SqliteSessionStore {
|
|
|
153
185
|
* Mirrors Python's SQLiteStore.insert_session. Markdown projection is not
|
|
154
186
|
* yet ported and skipped here.
|
|
155
187
|
*/
|
|
156
|
-
async insertSession(record, embedder = null, supersedes = null, factSink = null) {
|
|
188
|
+
async insertSession(tenantId, record, embedder = null, supersedes = null, factSink = null) {
|
|
157
189
|
const db = this.db;
|
|
158
190
|
const txn = db.transaction(() => {
|
|
159
191
|
db.prepare(`
|
|
@@ -161,11 +193,15 @@ export class SqliteSessionStore {
|
|
|
161
193
|
id, runtime, runtime_session_id, started_at, ended_at, duration_min,
|
|
162
194
|
label, summary, body, status,
|
|
163
195
|
transcript_kind, transcript_path, transcript_offset, transcript_length,
|
|
164
|
-
classifier_provider, classifier_model, classifier_confidence
|
|
196
|
+
classifier_provider, classifier_model, classifier_confidence,
|
|
197
|
+
scope, agent_persona, parent_session_id,
|
|
198
|
+
primary_model, total_tokens, skill, tenant_id
|
|
165
199
|
) VALUES (@id, @runtime, @runtimeSessionId, @startedAt, @endedAt, @durationMin,
|
|
166
200
|
@label, @summary, @body, @status,
|
|
167
201
|
@transcriptKind, @transcriptPath, @transcriptOffset, @transcriptLength,
|
|
168
|
-
@classifierProvider, @classifierModel, @classifierConfidence
|
|
202
|
+
@classifierProvider, @classifierModel, @classifierConfidence,
|
|
203
|
+
@scope, @agentPersona, @parentSessionId,
|
|
204
|
+
@primaryModel, @totalTokens, @skill, @tenantId)
|
|
169
205
|
ON CONFLICT(id) DO UPDATE SET
|
|
170
206
|
ended_at = excluded.ended_at,
|
|
171
207
|
duration_min = excluded.duration_min,
|
|
@@ -176,6 +212,12 @@ export class SqliteSessionStore {
|
|
|
176
212
|
classifier_provider = excluded.classifier_provider,
|
|
177
213
|
classifier_model = excluded.classifier_model,
|
|
178
214
|
classifier_confidence = excluded.classifier_confidence,
|
|
215
|
+
scope = COALESCE(excluded.scope, scope),
|
|
216
|
+
agent_persona = COALESCE(excluded.agent_persona, agent_persona),
|
|
217
|
+
parent_session_id = COALESCE(excluded.parent_session_id, parent_session_id),
|
|
218
|
+
primary_model = COALESCE(excluded.primary_model, primary_model),
|
|
219
|
+
total_tokens = COALESCE(excluded.total_tokens, total_tokens),
|
|
220
|
+
skill = COALESCE(excluded.skill, skill),
|
|
179
221
|
updated_at = datetime('now')
|
|
180
222
|
`).run({
|
|
181
223
|
id: record.id,
|
|
@@ -195,6 +237,13 @@ export class SqliteSessionStore {
|
|
|
195
237
|
classifierProvider: record.classifier?.provider ?? null,
|
|
196
238
|
classifierModel: record.classifier?.model ?? null,
|
|
197
239
|
classifierConfidence: record.classifier?.confidence ?? null,
|
|
240
|
+
scope: record.scope,
|
|
241
|
+
agentPersona: record.agentPersona ?? null,
|
|
242
|
+
parentSessionId: record.parentSessionId ?? null,
|
|
243
|
+
primaryModel: record.primaryModel ?? null,
|
|
244
|
+
totalTokens: record.totalTokens ?? null,
|
|
245
|
+
skill: record.skill ?? null,
|
|
246
|
+
tenantId,
|
|
198
247
|
});
|
|
199
248
|
db.prepare("DELETE FROM markers WHERE session_id = ?").run(record.id);
|
|
200
249
|
const markerStmt = db.prepare("INSERT INTO markers (session_id, kind, text, position) VALUES (?, ?, ?, ?)");
|
|
@@ -207,42 +256,49 @@ export class SqliteSessionStore {
|
|
|
207
256
|
const rawNewEntities = [...new Set(record.entities.map((e) => e.trim()).filter(Boolean))];
|
|
208
257
|
// Resolve each extracted entity through entity_variants so merged surface
|
|
209
258
|
// forms bind to the canonical instead of resurrecting the retired source.
|
|
210
|
-
const
|
|
211
|
-
const
|
|
259
|
+
const variantTc = tenantClause(tenantId);
|
|
260
|
+
const resolveVariant = db.prepare(`SELECT canonical FROM entity_variants WHERE ${variantTc.sql} AND variant = ?`);
|
|
261
|
+
const newEntities = rawNewEntities.map((name) => resolveVariant.get(variantTc.param, name)?.canonical ?? name);
|
|
262
|
+
const oldEntityTc = tenantClause(tenantId);
|
|
212
263
|
const oldEntityRows = db
|
|
213
|
-
.prepare(
|
|
214
|
-
.all(record.id);
|
|
264
|
+
.prepare(`SELECT entity_canonical FROM session_entities WHERE ${oldEntityTc.sql} AND session_id = ?`)
|
|
265
|
+
.all(oldEntityTc.param, record.id);
|
|
215
266
|
const oldEntities = new Set(oldEntityRows.map((r) => r.entity_canonical));
|
|
216
|
-
|
|
267
|
+
const deleteEntTc = tenantClause(tenantId);
|
|
268
|
+
db.prepare(`DELETE FROM session_entities WHERE ${deleteEntTc.sql} AND session_id = ?`).run(deleteEntTc.param, record.id);
|
|
217
269
|
const insertEnt = db.prepare(`
|
|
218
270
|
INSERT OR IGNORE INTO entities
|
|
219
|
-
(canonical, type, status, source, first_seen_session, last_seen_session, session_count)
|
|
220
|
-
VALUES (?, 'candidate', 'candidate', 'auto-detected', ?, ?, 0)
|
|
271
|
+
(tenant_id, canonical, type, status, source, first_seen_session, last_seen_session, session_count)
|
|
272
|
+
VALUES (?, ?, 'candidate', 'candidate', 'auto-detected', ?, ?, 0)
|
|
221
273
|
`);
|
|
222
|
-
const
|
|
223
|
-
const
|
|
274
|
+
const touchLastSeenTc = tenantClause(tenantId);
|
|
275
|
+
const touchLastSeen = db.prepare(`UPDATE entities SET last_seen_session = ?, updated_at = datetime('now') WHERE ${touchLastSeenTc.sql} AND canonical = ?`);
|
|
276
|
+
const linkEnt = db.prepare("INSERT INTO session_entities (tenant_id, session_id, entity_canonical) VALUES (?, ?, ?)");
|
|
224
277
|
for (const name of newEntities) {
|
|
225
|
-
insertEnt.run(name, record.id, record.id);
|
|
278
|
+
insertEnt.run(tenantId, name, record.id, record.id);
|
|
226
279
|
// Update last_seen for entities newly added to this session; matches prior touch semantics.
|
|
227
280
|
if (!oldEntities.has(name)) {
|
|
228
|
-
touchLastSeen.run(record.id, name);
|
|
281
|
+
touchLastSeen.run(record.id, tenantId, name);
|
|
229
282
|
}
|
|
230
|
-
linkEnt.run(record.id, name);
|
|
283
|
+
linkEnt.run(tenantId, record.id, name);
|
|
231
284
|
}
|
|
232
285
|
// Recompute session_count exactly for every entity in (old union new) so
|
|
233
286
|
// counts reflect reality regardless of prior drift.
|
|
234
|
-
const
|
|
287
|
+
const recomputeInnerTc = tenantClause(tenantId);
|
|
288
|
+
const recomputeOuterTc = tenantClause(tenantId);
|
|
289
|
+
const recomputeCount = db.prepare(`UPDATE entities SET session_count = (SELECT COUNT(*) FROM session_entities WHERE ${recomputeInnerTc.sql} AND entity_canonical = ?), updated_at = datetime('now') WHERE ${recomputeOuterTc.sql} AND canonical = ?`);
|
|
235
290
|
for (const name of new Set([...oldEntities, ...newEntities])) {
|
|
236
|
-
recomputeCount.run(name, name);
|
|
291
|
+
recomputeCount.run(tenantId, name, tenantId, name);
|
|
237
292
|
}
|
|
238
293
|
if (supersedes && supersedes.priorSessionId !== record.id) {
|
|
239
294
|
const predecessorStatus = supersedes.kind === "replaces" ? "replaced" : "superseded";
|
|
240
295
|
db.prepare(`INSERT OR IGNORE INTO session_edges (from_session, to_session, kind)
|
|
241
296
|
VALUES (?, ?, ?)`).run(record.id, supersedes.priorSessionId, supersedes.kind);
|
|
242
|
-
|
|
297
|
+
const predStatusTc = tenantClause(tenantId);
|
|
298
|
+
db.prepare(`UPDATE sessions SET status = ?, updated_at = datetime('now') WHERE id = ? AND ${predStatusTc.sql}`).run(predecessorStatus, supersedes.priorSessionId, predStatusTc.param);
|
|
243
299
|
}
|
|
244
300
|
else {
|
|
245
|
-
const priorId = findContinuesPredecessor(db, record.id, record.entities);
|
|
301
|
+
const priorId = findContinuesPredecessor(db, tenantId, record.id, record.entities);
|
|
246
302
|
if (priorId !== null) {
|
|
247
303
|
db.prepare(`INSERT OR IGNORE INTO session_edges (from_session, to_session, kind)
|
|
248
304
|
VALUES (?, ?, 'continues')`).run(record.id, priorId);
|
|
@@ -276,7 +332,7 @@ export class SqliteSessionStore {
|
|
|
276
332
|
// copy drifted from the port method and missed NLM #351 bug 1 (orphan
|
|
277
333
|
// embeddings on replace) and bug 2 (per-fact collapse -> supersedence
|
|
278
334
|
// cycles). Runs inside this session txn.
|
|
279
|
-
factSink.factStore.ingestSessionFactsInTxn(record.id, factSink.facts);
|
|
335
|
+
factSink.factStore.ingestSessionFactsInTxn(tenantId, record.id, factSink.facts, record.scope);
|
|
280
336
|
}
|
|
281
337
|
});
|
|
282
338
|
txn();
|
|
@@ -300,13 +356,15 @@ export class SqliteSessionStore {
|
|
|
300
356
|
const { vector } = await embedder.embed(text, "document");
|
|
301
357
|
this.insertChunkEmbedding(record.id, chunkIdx, vector);
|
|
302
358
|
}
|
|
303
|
-
catch {
|
|
359
|
+
catch (err) {
|
|
304
360
|
// Per-chunk embedder failure must not roll the ingest back or
|
|
305
361
|
// abort subsequent chunks.
|
|
362
|
+
recordEmbedFailure("chunk");
|
|
363
|
+
process.stderr.write(`[nlm] embedding chunk failed session=${record.id} chunk=${chunkIdx}: ${String(err)}\n`);
|
|
306
364
|
}
|
|
307
365
|
}
|
|
308
366
|
if (factSink !== null) {
|
|
309
|
-
await this.embedFacts(factSink.factStore, factSink.facts, embedder);
|
|
367
|
+
await this.embedFacts(tenantId, factSink.factStore, factSink.facts, embedder);
|
|
310
368
|
}
|
|
311
369
|
}
|
|
312
370
|
}
|
|
@@ -339,22 +397,27 @@ export class SqliteSessionStore {
|
|
|
339
397
|
* Non-empty body required (the classifier needs transcript text). When
|
|
340
398
|
* `reprocess` is false, sessions that already have facts are excluded.
|
|
341
399
|
*/
|
|
342
|
-
async listBackfillCandidates(filter) {
|
|
400
|
+
async listBackfillCandidates(tenantId, filter) {
|
|
401
|
+
const tc = tenantClause(tenantId, "s.tenant_id");
|
|
343
402
|
const sql = filter.reprocess
|
|
344
|
-
? `SELECT id, started_at, body FROM sessions
|
|
345
|
-
WHERE started_at < ? AND body IS NOT NULL AND length(body) > 0
|
|
346
|
-
${filter.from ? "AND id > ?" : ""}
|
|
347
|
-
ORDER BY started_at ASC, id ASC`
|
|
403
|
+
? `SELECT s.id, s.started_at, s.body FROM sessions s
|
|
404
|
+
WHERE ${tc.sql} AND s.started_at < ? AND s.body IS NOT NULL AND length(s.body) > 0
|
|
405
|
+
${filter.from ? "AND s.id > ?" : ""}
|
|
406
|
+
ORDER BY s.started_at ASC, s.id ASC`
|
|
407
|
+
// facts.tenant_id is always stamped to match its source_session_id's
|
|
408
|
+
// session tenant, so this is defense in depth on top of the already
|
|
409
|
+
// tenant-filtered outer sessions row (guard test C4 permits
|
|
410
|
+
// column-to-column tenant equality as a join form).
|
|
348
411
|
: `SELECT s.id, s.started_at, s.body FROM sessions s
|
|
349
|
-
WHERE s.started_at < ? AND s.body IS NOT NULL AND length(s.body) > 0
|
|
350
|
-
AND NOT EXISTS (SELECT 1 FROM facts f WHERE f.source_session_id = s.id)
|
|
412
|
+
WHERE ${tc.sql} AND s.started_at < ? AND s.body IS NOT NULL AND length(s.body) > 0
|
|
413
|
+
AND NOT EXISTS (SELECT 1 FROM facts f WHERE f.source_session_id = s.id AND f.tenant_id = s.tenant_id)
|
|
351
414
|
${filter.from ? "AND s.id > ?" : ""}
|
|
352
415
|
ORDER BY s.started_at ASC, s.id ASC`;
|
|
353
416
|
const rows = filter.from
|
|
354
417
|
? this.db.prepare(sql)
|
|
355
|
-
.all(filter.cutoff, filter.from)
|
|
418
|
+
.all(tc.param, filter.cutoff, filter.from)
|
|
356
419
|
: this.db.prepare(sql)
|
|
357
|
-
.all(filter.cutoff);
|
|
420
|
+
.all(tc.param, filter.cutoff);
|
|
358
421
|
return rows.map((r) => ({ id: r.id, startedAt: r.started_at, body: r.body }));
|
|
359
422
|
}
|
|
360
423
|
/**
|
|
@@ -369,16 +432,17 @@ export class SqliteSessionStore {
|
|
|
369
432
|
* write path. The live ingest path (`insertSession`) keeps using the
|
|
370
433
|
* internal helpers directly so session+facts commit together.
|
|
371
434
|
*/
|
|
372
|
-
async insertFactsForSession(sessionId, factStore, facts, embedder = null) {
|
|
435
|
+
async insertFactsForSession(tenantId, sessionId, factStore, facts, embedder = null) {
|
|
373
436
|
// Delegate to the canonical sync ingest (single source of truth for
|
|
374
437
|
// replace + collapse + embedding cleanup). Sync because better-sqlite3 txn
|
|
375
438
|
// callbacks must be sync; ingestSessionFactsInTxn is the sync core.
|
|
439
|
+
const sessionScope = await this.getSessionScopeById(tenantId, sessionId);
|
|
376
440
|
const txn = this.db.transaction(() => {
|
|
377
|
-
factStore.ingestSessionFactsInTxn(sessionId, facts);
|
|
441
|
+
factStore.ingestSessionFactsInTxn(tenantId, sessionId, facts, sessionScope);
|
|
378
442
|
});
|
|
379
443
|
txn();
|
|
380
444
|
if (embedder) {
|
|
381
|
-
await this.embedFacts(factStore, facts, embedder);
|
|
445
|
+
await this.embedFacts(tenantId, factStore, facts, embedder);
|
|
382
446
|
}
|
|
383
447
|
}
|
|
384
448
|
/**
|
|
@@ -386,68 +450,43 @@ export class SqliteSessionStore {
|
|
|
386
450
|
* embeddings to fact_embeddings via FactStore.upsertEmbedding. Per-fact
|
|
387
451
|
* failures don't abort the batch, and never affect committed fact rows.
|
|
388
452
|
*/
|
|
389
|
-
async embedFacts(factStore, facts, embedder) {
|
|
453
|
+
async embedFacts(tenantId, factStore, facts, embedder) {
|
|
390
454
|
for (const fact of batchWinners(facts)) {
|
|
391
455
|
const factText = `${fact.subject} ${fact.predicate} ${fact.value}`.trim();
|
|
392
456
|
if (!factText)
|
|
393
457
|
continue;
|
|
394
458
|
try {
|
|
395
459
|
const { vector } = await embedder.embed(factText, "document");
|
|
396
|
-
await factStore.upsertEmbedding(fact.id, vector);
|
|
460
|
+
await factStore.upsertEmbedding(tenantId, fact.id, vector);
|
|
397
461
|
}
|
|
398
|
-
catch {
|
|
462
|
+
catch (err) {
|
|
399
463
|
// Per-fact embedding failure must not abort embedding of subsequent
|
|
400
464
|
// facts. The fact row stays current; semantic recall just misses it
|
|
401
465
|
// until a future re-ingest.
|
|
466
|
+
recordEmbedFailure("fact");
|
|
467
|
+
process.stderr.write(`[nlm] embedding fact failed fact=${fact.id}: ${String(err)}\n`);
|
|
402
468
|
}
|
|
403
469
|
}
|
|
404
470
|
}
|
|
405
|
-
async
|
|
406
|
-
const
|
|
407
|
-
.prepare(`
|
|
408
|
-
SELECT id, runtime, runtime_session_id, started_at, ended_at, duration_min,
|
|
409
|
-
label, summary, status, transcript_kind, transcript_path, body,
|
|
410
|
-
classifier_provider, classifier_model, classifier_confidence
|
|
411
|
-
FROM sessions
|
|
412
|
-
ORDER BY started_at ASC
|
|
413
|
-
`)
|
|
414
|
-
.all();
|
|
415
|
-
if (rows.length === 0)
|
|
416
|
-
return [];
|
|
417
|
-
const ids = rows.map((r) => r.id);
|
|
418
|
-
const entitiesByIdMap = this.loadEntities(ids);
|
|
419
|
-
const markersByIdMap = this.loadMarkers(ids);
|
|
420
|
-
const overlay = this.overlay();
|
|
421
|
-
const sessions = rows.map((r) => this.rowToSession(r, entitiesByIdMap, markersByIdMap, overlay));
|
|
422
|
-
if (!filter)
|
|
423
|
-
return sessions;
|
|
424
|
-
return sessions.filter((s) => {
|
|
425
|
-
if (filter.entity !== undefined && !s.entities.includes(filter.entity)) {
|
|
426
|
-
return false;
|
|
427
|
-
}
|
|
428
|
-
if (filter.hasDecisions === true && s.decisions.length === 0)
|
|
429
|
-
return false;
|
|
430
|
-
if (filter.hasOpenQuestions === true && s.open.length === 0)
|
|
431
|
-
return false;
|
|
432
|
-
return true;
|
|
433
|
-
});
|
|
434
|
-
}
|
|
435
|
-
async getById(sessionId) {
|
|
471
|
+
async getById(tenantId, sessionId) {
|
|
472
|
+
const tc = tenantClause(tenantId);
|
|
436
473
|
const row = this.db
|
|
437
474
|
.prepare(`
|
|
438
475
|
SELECT id, runtime, runtime_session_id, started_at, ended_at, duration_min,
|
|
439
476
|
label, summary, status, transcript_kind, transcript_path, body,
|
|
440
|
-
classifier_provider, classifier_model, classifier_confidence
|
|
477
|
+
classifier_provider, classifier_model, classifier_confidence,
|
|
478
|
+
agent_persona, parent_session_id,
|
|
479
|
+
primary_model, total_tokens, skill
|
|
441
480
|
FROM sessions
|
|
442
|
-
WHERE id = ?
|
|
481
|
+
WHERE id = ? AND ${tc.sql}
|
|
443
482
|
`)
|
|
444
|
-
.get(sessionId);
|
|
483
|
+
.get(sessionId, tc.param);
|
|
445
484
|
if (!row)
|
|
446
485
|
return null;
|
|
447
|
-
const entities = this.loadEntities([sessionId]);
|
|
486
|
+
const entities = this.loadEntities(tenantId, [sessionId]);
|
|
448
487
|
const markers = this.loadMarkers([sessionId]);
|
|
449
488
|
const edges = this.loadSessionEdges([sessionId]);
|
|
450
|
-
const overlay = this.overlay();
|
|
489
|
+
const overlay = this.overlay(tenantId);
|
|
451
490
|
return this.rowToSession(row, entities, markers, overlay, edges);
|
|
452
491
|
}
|
|
453
492
|
/**
|
|
@@ -456,45 +495,47 @@ export class SqliteSessionStore {
|
|
|
456
495
|
* never reads, and SELECTing it for the corpus is what wedged the
|
|
457
496
|
* daemon. Resolved sessions carry `body: ""`.
|
|
458
497
|
*/
|
|
459
|
-
async getByIds(ids) {
|
|
498
|
+
async getByIds(tenantId, ids) {
|
|
460
499
|
if (ids.length === 0)
|
|
461
500
|
return [];
|
|
462
501
|
const placeholders = ids.map(() => "?").join(",");
|
|
502
|
+
const tc = tenantClause(tenantId);
|
|
463
503
|
const rows = this.db
|
|
464
504
|
.prepare(`
|
|
465
505
|
SELECT id, runtime, runtime_session_id, started_at, ended_at, duration_min,
|
|
466
506
|
label, summary, status, transcript_kind, transcript_path
|
|
467
507
|
FROM sessions
|
|
468
|
-
WHERE id IN (${placeholders})
|
|
508
|
+
WHERE id IN (${placeholders}) AND ${tc.sql}
|
|
469
509
|
`)
|
|
470
|
-
.all(...ids);
|
|
510
|
+
.all(...ids, tc.param);
|
|
471
511
|
if (rows.length === 0)
|
|
472
512
|
return [];
|
|
473
513
|
const foundIds = rows.map((r) => r.id);
|
|
474
|
-
const entitiesByIdMap = this.loadEntities(foundIds);
|
|
514
|
+
const entitiesByIdMap = this.loadEntities(tenantId, foundIds);
|
|
475
515
|
const markersByIdMap = this.loadMarkers(foundIds);
|
|
476
|
-
const overlay = this.overlay();
|
|
516
|
+
const overlay = this.overlay(tenantId);
|
|
477
517
|
return rows.map((r) => this.rowToSession({ ...r, body: null }, entitiesByIdMap, markersByIdMap, overlay));
|
|
478
518
|
}
|
|
479
|
-
async listByDateRange(fromIso, toIso) {
|
|
519
|
+
async listByDateRange(tenantId, fromIso, toIso) {
|
|
520
|
+
const tc = tenantClause(tenantId);
|
|
480
521
|
const rows = this.db
|
|
481
522
|
.prepare(`
|
|
482
523
|
SELECT id, runtime, runtime_session_id, started_at, ended_at, duration_min,
|
|
483
524
|
label, summary, status, transcript_kind, transcript_path, workstream_id
|
|
484
525
|
FROM sessions
|
|
485
|
-
WHERE started_at < ? AND (ended_at IS NULL OR ended_at >= ?)
|
|
526
|
+
WHERE started_at < ? AND (ended_at IS NULL OR ended_at >= ?) AND ${tc.sql}
|
|
486
527
|
ORDER BY started_at ASC
|
|
487
528
|
`)
|
|
488
|
-
.all(toIso, fromIso);
|
|
529
|
+
.all(toIso, fromIso, tc.param);
|
|
489
530
|
if (rows.length === 0)
|
|
490
531
|
return [];
|
|
491
532
|
const ids = rows.map((r) => r.id);
|
|
492
|
-
const entitiesByIdMap = this.loadEntities(ids);
|
|
533
|
+
const entitiesByIdMap = this.loadEntities(tenantId, ids);
|
|
493
534
|
const markersByIdMap = this.loadMarkers(ids);
|
|
494
|
-
const overlay = this.overlay();
|
|
535
|
+
const overlay = this.overlay(tenantId);
|
|
495
536
|
return rows.map((r) => this.rowToSession({ ...r, body: null }, entitiesByIdMap, markersByIdMap, overlay));
|
|
496
537
|
}
|
|
497
|
-
async semanticSearch(queryVector, limit, opts) {
|
|
538
|
+
async semanticSearch(tenantId, queryVector, limit, opts) {
|
|
498
539
|
const k = Math.max(1, Math.trunc(limit));
|
|
499
540
|
const blob = Buffer.from(queryVector.buffer, queryVector.byteOffset, queryVector.byteLength);
|
|
500
541
|
// Overfetch chunks so the max-pool grouping has enough unique sessions
|
|
@@ -505,6 +546,12 @@ export class SqliteSessionStore {
|
|
|
505
546
|
const envOverfetch = Number.parseInt(process.env["NLM_CHUNK_OVERFETCH"] ?? "", 10);
|
|
506
547
|
const CHUNK_OVERFETCH = Number.isFinite(envOverfetch) && envOverfetch > 0 ? envOverfetch : 4;
|
|
507
548
|
const chunkK = k * CHUNK_OVERFETCH;
|
|
549
|
+
// The vec0 KNN scan has no tenant awareness (session_embedding_chunks
|
|
550
|
+
// carries no tenant_id — DERIVE-VIA-FK), so it returns neighbors from the
|
|
551
|
+
// whole corpus. The tenant filter is re-applied below, in the
|
|
552
|
+
// id-resolution join against `sessions` (program spec §4.3, vector-path
|
|
553
|
+
// rule) — any candidate session id that doesn't resolve within the
|
|
554
|
+
// caller's tenant is excluded, not merely "not down-ranked".
|
|
508
555
|
const rows = this.db
|
|
509
556
|
.prepare(`
|
|
510
557
|
SELECT session_id, distance
|
|
@@ -515,19 +562,29 @@ export class SqliteSessionStore {
|
|
|
515
562
|
`)
|
|
516
563
|
.all(blob, chunkK);
|
|
517
564
|
// Max-pool: keep the smallest distance (highest cosine) per session,
|
|
518
|
-
// filtering out superseded, replaced,
|
|
565
|
+
// filtering out superseded, replaced, out-of-workstream, and (via the
|
|
566
|
+
// tenant-filtered id-resolution join) out-of-tenant sessions.
|
|
519
567
|
const best = new Map();
|
|
520
568
|
const excludedSessionIds = new Set();
|
|
521
569
|
const wsIds = opts?.workstreamIds;
|
|
522
570
|
if (wsIds !== undefined && wsIds.length === 0)
|
|
523
571
|
return [];
|
|
524
|
-
// First pass:
|
|
572
|
+
// First pass: resolve each unique candidate session id against the
|
|
573
|
+
// tenant-filtered sessions table to get its status + workstream. A
|
|
574
|
+
// candidate id that fails to resolve (wrong tenant or gone) is excluded
|
|
575
|
+
// outright, not merely left un-excluded.
|
|
525
576
|
const uniqueSessionIds = [...new Set(rows.map((r) => r.session_id))];
|
|
526
577
|
if (uniqueSessionIds.length > 0) {
|
|
527
578
|
const placeholders = uniqueSessionIds.map(() => "?").join(",");
|
|
579
|
+
const tc = tenantClause(tenantId);
|
|
528
580
|
const statusRows = this.db
|
|
529
|
-
.prepare(`SELECT id, status, workstream_id FROM sessions WHERE id IN (${placeholders})`)
|
|
530
|
-
.all(...uniqueSessionIds);
|
|
581
|
+
.prepare(`SELECT id, status, workstream_id FROM sessions WHERE id IN (${placeholders}) AND ${tc.sql}`)
|
|
582
|
+
.all(...uniqueSessionIds, tc.param);
|
|
583
|
+
const resolvedIds = new Set(statusRows.map((r) => r.id));
|
|
584
|
+
for (const id of uniqueSessionIds) {
|
|
585
|
+
if (!resolvedIds.has(id))
|
|
586
|
+
excludedSessionIds.add(id);
|
|
587
|
+
}
|
|
531
588
|
const excludeSuperseded = opts?.includeSuperseded !== true;
|
|
532
589
|
for (const sr of statusRows) {
|
|
533
590
|
const statusExcluded = sr.status === "replaced" || (excludeSuperseded && sr.status === "superseded");
|
|
@@ -537,7 +594,7 @@ export class SqliteSessionStore {
|
|
|
537
594
|
}
|
|
538
595
|
}
|
|
539
596
|
}
|
|
540
|
-
// Second pass: max-pool, excluding superseded/replaced sessions
|
|
597
|
+
// Second pass: max-pool, excluding superseded/replaced/out-of-tenant sessions
|
|
541
598
|
for (const r of rows) {
|
|
542
599
|
if (excludedSessionIds.has(r.session_id))
|
|
543
600
|
continue;
|
|
@@ -558,7 +615,7 @@ export class SqliteSessionStore {
|
|
|
558
615
|
* for better matches). User input is tokenized and rebuilt into a quoted
|
|
559
616
|
* OR query so FTS5 metacharacters cannot reach the MATCH parser.
|
|
560
617
|
*/
|
|
561
|
-
async keywordSearch(query, limit, opts) {
|
|
618
|
+
async keywordSearch(tenantId, query, limit, opts) {
|
|
562
619
|
const wsIds = opts?.workstreamIds;
|
|
563
620
|
if (wsIds !== undefined && wsIds.length === 0)
|
|
564
621
|
return [];
|
|
@@ -569,6 +626,7 @@ export class SqliteSessionStore {
|
|
|
569
626
|
const statusFilter = opts?.includeSuperseded === true
|
|
570
627
|
? "AND s.status != 'replaced'"
|
|
571
628
|
: "AND s.status NOT IN ('superseded', 'replaced')";
|
|
629
|
+
const tc = tenantClause(tenantId, "s.tenant_id");
|
|
572
630
|
let wsFilter = "";
|
|
573
631
|
const params = [matchExpr];
|
|
574
632
|
if (wsIds?.length) {
|
|
@@ -576,6 +634,7 @@ export class SqliteSessionStore {
|
|
|
576
634
|
wsFilter = `AND s.workstream_id IN (${ph})`;
|
|
577
635
|
params.push(...wsIds);
|
|
578
636
|
}
|
|
637
|
+
params.push(tc.param);
|
|
579
638
|
params.push(k);
|
|
580
639
|
const rows = this.db
|
|
581
640
|
.prepare(`
|
|
@@ -586,48 +645,54 @@ export class SqliteSessionStore {
|
|
|
586
645
|
WHERE sessions_fts MATCH ?
|
|
587
646
|
${statusFilter}
|
|
588
647
|
${wsFilter}
|
|
648
|
+
AND ${tc.sql}
|
|
589
649
|
ORDER BY score DESC
|
|
590
650
|
LIMIT ?
|
|
591
651
|
`)
|
|
592
652
|
.all(...params);
|
|
593
653
|
return rows.map((r) => ({ sessionId: r.session_id, score: r.score }));
|
|
594
654
|
}
|
|
595
|
-
async resolveSuccessors(ids) {
|
|
655
|
+
async resolveSuccessors(tenantId, ids) {
|
|
596
656
|
if (ids.length === 0)
|
|
597
657
|
return new Map();
|
|
598
658
|
const placeholders = ids.map(() => "?").join(",");
|
|
659
|
+
const tc = tenantClause(tenantId, "s.tenant_id");
|
|
599
660
|
const rows = this.db
|
|
600
661
|
.prepare(`
|
|
601
|
-
SELECT from_session, to_session
|
|
602
|
-
FROM session_edges
|
|
603
|
-
|
|
604
|
-
|
|
662
|
+
SELECT se.from_session, se.to_session
|
|
663
|
+
FROM session_edges se
|
|
664
|
+
JOIN sessions s ON s.id = se.to_session
|
|
665
|
+
WHERE se.kind = 'supersedes'
|
|
666
|
+
AND se.to_session IN (${placeholders})
|
|
667
|
+
AND ${tc.sql}
|
|
605
668
|
`)
|
|
606
|
-
.all(...ids);
|
|
669
|
+
.all(...ids, tc.param);
|
|
607
670
|
const out = new Map();
|
|
608
671
|
for (const r of rows)
|
|
609
672
|
out.set(r.to_session, r.from_session);
|
|
610
673
|
return out;
|
|
611
674
|
}
|
|
612
|
-
async updateStatus(sessionId, status) {
|
|
675
|
+
async updateStatus(tenantId, sessionId, status) {
|
|
613
676
|
if (status === "idle") {
|
|
614
677
|
throw new Error("Cannot persist derived status 'idle' — only active/closed/superseded");
|
|
615
678
|
}
|
|
679
|
+
const tc = tenantClause(tenantId);
|
|
616
680
|
this.db
|
|
617
|
-
.prepare(
|
|
618
|
-
.run(status, sessionId);
|
|
681
|
+
.prepare(`UPDATE sessions SET status = ?, updated_at = datetime('now') WHERE id = ? AND ${tc.sql}`)
|
|
682
|
+
.run(status, sessionId, tc.param);
|
|
619
683
|
}
|
|
620
|
-
async markSuperseded(predecessorId, successorId) {
|
|
684
|
+
async markSuperseded(tenantId, predecessorId, successorId) {
|
|
621
685
|
if (predecessorId === successorId) {
|
|
622
686
|
throw new Error("A session cannot supersede itself");
|
|
623
687
|
}
|
|
624
|
-
const
|
|
688
|
+
const existsTc = tenantClause(tenantId);
|
|
689
|
+
const existsStmt = this.db.prepare(`SELECT COUNT(*) AS c FROM sessions WHERE id = ? AND ${existsTc.sql}`);
|
|
625
690
|
const txn = this.db.transaction(() => {
|
|
626
|
-
const predExists = (existsStmt.get(predecessorId)?.c ?? 0) > 0;
|
|
691
|
+
const predExists = (existsStmt.get(predecessorId, tenantId)?.c ?? 0) > 0;
|
|
627
692
|
if (!predExists) {
|
|
628
693
|
throw new Error(`predecessor session ${predecessorId} not found`);
|
|
629
694
|
}
|
|
630
|
-
const succExists = (existsStmt.get(successorId)?.c ?? 0) > 0;
|
|
695
|
+
const succExists = (existsStmt.get(successorId, tenantId)?.c ?? 0) > 0;
|
|
631
696
|
if (!succExists) {
|
|
632
697
|
throw new Error(`successor session ${successorId} not found`);
|
|
633
698
|
}
|
|
@@ -672,56 +737,68 @@ export class SqliteSessionStore {
|
|
|
672
737
|
.prepare(`INSERT OR IGNORE INTO session_edges (from_session, to_session, kind)
|
|
673
738
|
VALUES (?, ?, 'supersedes')`)
|
|
674
739
|
.run(successorId, predecessorId);
|
|
740
|
+
const statusTc = tenantClause(tenantId);
|
|
675
741
|
this.db
|
|
676
|
-
.prepare(
|
|
677
|
-
.run(predecessorId);
|
|
742
|
+
.prepare(`UPDATE sessions SET status = 'superseded', updated_at = datetime('now') WHERE id = ? AND ${statusTc.sql}`)
|
|
743
|
+
.run(predecessorId, statusTc.param);
|
|
678
744
|
// Cascade supersedence to facts: link predecessor facts to their successors
|
|
679
|
-
const
|
|
680
|
-
const
|
|
681
|
-
const
|
|
745
|
+
const predFactsTc = tenantClause(tenantId);
|
|
746
|
+
const selectPredFacts = this.db.prepare(`SELECT id, subject, predicate FROM facts WHERE source_session_id = ? AND ${predFactsTc.sql}`);
|
|
747
|
+
const succFactTc = tenantClause(tenantId);
|
|
748
|
+
const selectSuccFact = this.db.prepare(`SELECT id FROM facts WHERE source_session_id = ? AND subject = ? AND predicate = ? AND superseded_by IS NULL AND ${succFactTc.sql} LIMIT 1`);
|
|
749
|
+
const updateFactTc = tenantClause(tenantId);
|
|
750
|
+
const updateFactSuperseded = this.db.prepare(`UPDATE facts SET superseded_by = ? WHERE id = ? AND ${updateFactTc.sql}`);
|
|
682
751
|
const delFactEmbedding = this.db.prepare("DELETE FROM fact_embeddings WHERE fact_id = ?");
|
|
683
|
-
const predecessorFacts = selectPredFacts.all(predecessorId);
|
|
752
|
+
const predecessorFacts = selectPredFacts.all(predecessorId, predFactsTc.param);
|
|
684
753
|
for (const pFact of predecessorFacts) {
|
|
685
|
-
const successor = selectSuccFact.get(successorId, pFact.subject, pFact.predicate);
|
|
754
|
+
const successor = selectSuccFact.get(successorId, pFact.subject, pFact.predicate, succFactTc.param);
|
|
686
755
|
if (successor) {
|
|
687
|
-
updateFactSuperseded.run(successor.id, pFact.id);
|
|
756
|
+
updateFactSuperseded.run(successor.id, pFact.id, updateFactTc.param);
|
|
688
757
|
delFactEmbedding.run(pFact.id);
|
|
689
758
|
}
|
|
690
759
|
}
|
|
691
760
|
});
|
|
692
761
|
txn();
|
|
693
762
|
}
|
|
694
|
-
async
|
|
695
|
-
|
|
763
|
+
async getSessionScopeById(tenantId, id) {
|
|
764
|
+
const tc = tenantClause(tenantId);
|
|
765
|
+
const row = this.db.prepare(`SELECT scope FROM sessions WHERE id = ? AND ${tc.sql}`).get(id, tc.param);
|
|
766
|
+
return row?.scope ?? null;
|
|
696
767
|
}
|
|
697
|
-
async
|
|
768
|
+
async setWorkstreamBinding(tenantId, sessionId, workstreamId, source, confidence) {
|
|
769
|
+
const tc = tenantClause(tenantId);
|
|
770
|
+
this.db.prepare(`UPDATE sessions SET workstream_id = ?, binding_source = ?, binding_confidence = ?, updated_at = datetime('now') WHERE id = ? AND ${tc.sql}`).run(workstreamId, source, confidence, sessionId, tc.param);
|
|
771
|
+
}
|
|
772
|
+
async listSessionIdsByWorkstreams(tenantId, workstreamIds) {
|
|
698
773
|
if (workstreamIds.length === 0)
|
|
699
774
|
return [];
|
|
700
775
|
const ph = workstreamIds.map(() => "?").join(",");
|
|
701
|
-
|
|
776
|
+
const tc = tenantClause(tenantId);
|
|
777
|
+
return this.db.prepare(`SELECT id FROM sessions WHERE workstream_id IN (${ph}) AND ${tc.sql} ORDER BY started_at ASC`).all(...workstreamIds, tc.param).map((r) => r.id);
|
|
702
778
|
}
|
|
703
|
-
async getEntities(sessionId) {
|
|
704
|
-
return this.loadEntities([sessionId]).get(sessionId) ?? [];
|
|
779
|
+
async getEntities(tenantId, sessionId) {
|
|
780
|
+
return this.loadEntities(tenantId, [sessionId]).get(sessionId) ?? [];
|
|
705
781
|
}
|
|
706
|
-
async getWorkstreamIds(sessionIds) {
|
|
782
|
+
async getWorkstreamIds(tenantId, sessionIds) {
|
|
707
783
|
const out = new Map();
|
|
708
784
|
if (sessionIds.length === 0)
|
|
709
785
|
return out;
|
|
710
786
|
const ph = sessionIds.map(() => "?").join(",");
|
|
711
|
-
|
|
787
|
+
const tc = tenantClause(tenantId);
|
|
788
|
+
for (const r of this.db.prepare(`SELECT id, workstream_id FROM sessions WHERE id IN (${ph}) AND ${tc.sql}`).all(...sessionIds, tc.param))
|
|
712
789
|
out.set(r.id, r.workstream_id);
|
|
713
790
|
return out;
|
|
714
791
|
}
|
|
715
792
|
// ── insert helpers used by tests / future ingest path ─────────────────
|
|
716
793
|
/** @internal test-only helper; production callers use insertSession(). */
|
|
717
|
-
insertSessionForTest(session) {
|
|
794
|
+
insertSessionForTest(session, tenantId = DEFAULT_TEAM_ID) {
|
|
718
795
|
const stmt = this.db.prepare(`
|
|
719
796
|
INSERT INTO sessions (
|
|
720
797
|
id, runtime, runtime_session_id, started_at, ended_at, duration_min,
|
|
721
|
-
label, summary, body, status, transcript_kind, transcript_path
|
|
798
|
+
label, summary, body, status, transcript_kind, transcript_path, tenant_id
|
|
722
799
|
) VALUES (
|
|
723
800
|
@id, @runtime, @runtimeSessionId, @startedAt, @endedAt, @durationMin,
|
|
724
|
-
@label, @summary, @body, @status, @transcriptKind, @transcriptPath
|
|
801
|
+
@label, @summary, @body, @status, @transcriptKind, @transcriptPath, @tenantId
|
|
725
802
|
)
|
|
726
803
|
`);
|
|
727
804
|
const status = session.status === "idle" ? "active" : session.status;
|
|
@@ -738,15 +815,16 @@ export class SqliteSessionStore {
|
|
|
738
815
|
status,
|
|
739
816
|
transcriptKind: session.transcriptKind,
|
|
740
817
|
transcriptPath: session.transcriptPath,
|
|
818
|
+
tenantId,
|
|
741
819
|
});
|
|
742
820
|
const entStmt = this.db.prepare(`
|
|
743
|
-
INSERT OR IGNORE INTO entities (canonical, type, status)
|
|
744
|
-
VALUES (?, 'candidate', 'active')
|
|
821
|
+
INSERT OR IGNORE INTO entities (tenant_id, canonical, type, status)
|
|
822
|
+
VALUES (?, ?, 'candidate', 'active')
|
|
745
823
|
`);
|
|
746
|
-
const linkStmt = this.db.prepare("INSERT OR IGNORE INTO session_entities (session_id, entity_canonical) VALUES (?, ?)");
|
|
824
|
+
const linkStmt = this.db.prepare("INSERT OR IGNORE INTO session_entities (tenant_id, session_id, entity_canonical) VALUES (?, ?, ?)");
|
|
747
825
|
for (const e of session.entities) {
|
|
748
|
-
entStmt.run(e);
|
|
749
|
-
linkStmt.run(session.id, e);
|
|
826
|
+
entStmt.run(tenantId, e);
|
|
827
|
+
linkStmt.run(tenantId, session.id, e);
|
|
750
828
|
}
|
|
751
829
|
const markerStmt = this.db.prepare("INSERT INTO markers (session_id, kind, text, position) VALUES (?, ?, ?, ?)");
|
|
752
830
|
session.decisions.forEach((d, i) => markerStmt.run(session.id, "decision", d, i));
|
|
@@ -764,18 +842,19 @@ export class SqliteSessionStore {
|
|
|
764
842
|
this.insertChunkEmbedding(sessionId, chunkIdx, vector);
|
|
765
843
|
}
|
|
766
844
|
// ── internal ──────────────────────────────────────────────────────────
|
|
767
|
-
loadEntities(ids) {
|
|
845
|
+
loadEntities(tenantId, ids) {
|
|
768
846
|
if (ids.length === 0)
|
|
769
847
|
return new Map();
|
|
770
848
|
const placeholders = ids.map(() => "?").join(",");
|
|
849
|
+
const tc = tenantClause(tenantId);
|
|
771
850
|
const rows = this.db
|
|
772
851
|
.prepare(`
|
|
773
852
|
SELECT session_id, entity_canonical
|
|
774
853
|
FROM session_entities
|
|
775
|
-
WHERE session_id IN (${placeholders})
|
|
854
|
+
WHERE session_id IN (${placeholders}) AND ${tc.sql}
|
|
776
855
|
ORDER BY session_id
|
|
777
856
|
`)
|
|
778
|
-
.all(...ids);
|
|
857
|
+
.all(...ids, tc.param);
|
|
779
858
|
const out = new Map();
|
|
780
859
|
for (const r of rows) {
|
|
781
860
|
const list = out.get(r.session_id);
|
|
@@ -879,6 +958,11 @@ export class SqliteSessionStore {
|
|
|
879
958
|
classifierProvider: row.classifier_provider ?? null,
|
|
880
959
|
classifierModel: row.classifier_model ?? null,
|
|
881
960
|
classifierConfidence: row.classifier_confidence ?? null,
|
|
961
|
+
agentPersona: row.agent_persona ?? null,
|
|
962
|
+
parentSessionId: row.parent_session_id ?? null,
|
|
963
|
+
primaryModel: row.primary_model ?? null,
|
|
964
|
+
totalTokens: row.total_tokens ?? null,
|
|
965
|
+
skill: row.skill ?? null,
|
|
882
966
|
};
|
|
883
967
|
}
|
|
884
968
|
}
|