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
package/dist/mcp/server.js
CHANGED
|
@@ -26,15 +26,28 @@ import { composeWorkstreamRecall } from "../core/workstream/compose-recall.js";
|
|
|
26
26
|
import { normalizeLabel } from "../core/workstream/model.js";
|
|
27
27
|
import { resolveWorkstreamId } from "../core/workstream/resolve.js";
|
|
28
28
|
import { suggestMerges } from "../core/workstream/merge-suggest.js";
|
|
29
|
+
import { normalizeSignal } from "../core/signals/ingest-signal.js";
|
|
30
|
+
import { stampSignalScope } from "../core/signals/stamp-scope.js";
|
|
31
|
+
import { deriveOutcome } from "../core/outcome/rollup.js";
|
|
32
|
+
import { buildSqliteOutcomeDeps } from "../core/storage/sqlite-outcome-store.js";
|
|
33
|
+
import { completeLearnerCourse, enrollCourse, getCourseSummary, recordLearningEvent, } from "../core/learner/service.js";
|
|
34
|
+
import { truncateSummary } from "../core/hook/pointer-block.js";
|
|
35
|
+
import { SIGNAL_OUTCOMES } from "../shared/types.js";
|
|
29
36
|
const CHARACTER_LIMIT = 25_000;
|
|
30
37
|
const DEFAULT_LIMIT = 10;
|
|
31
38
|
const SERVER_NAME = "nlm-memory-mcp-server";
|
|
32
39
|
const SERVER_VERSION = "0.5.9";
|
|
33
40
|
/** TOON encoding cuts token usage on large recall payloads. Opt in via
|
|
34
|
-
* NLM_FORMAT=toon
|
|
35
|
-
|
|
41
|
+
* NLM_FORMAT=toon. Defaults to JSON.
|
|
42
|
+
*
|
|
43
|
+
* Read at call time, not module scope: on the HTTP transport this module is
|
|
44
|
+
* imported by the daemon long before buildStack() calls autoloadEnv(), so an
|
|
45
|
+
* eager read would capture the environment before ~/.nlm/.env was loaded and
|
|
46
|
+
* silently ignore the setting. Only the stdio path, where the runtime passes
|
|
47
|
+
* env directly to the child, would have worked. */
|
|
48
|
+
const useToon = () => process.env.NLM_FORMAT === "toon";
|
|
36
49
|
function format(data) {
|
|
37
|
-
if (
|
|
50
|
+
if (useToon()) {
|
|
38
51
|
try {
|
|
39
52
|
return toonEncode(data);
|
|
40
53
|
}
|
|
@@ -86,7 +99,7 @@ export function mcpRuntimeFromClient(client) {
|
|
|
86
99
|
const name = client?.name?.trim().toLowerCase();
|
|
87
100
|
return name ? name : null;
|
|
88
101
|
}
|
|
89
|
-
export async function recallSessionsHandler(deps, input, runtime = null) {
|
|
102
|
+
export async function recallSessionsHandler(deps, tenantId, input, runtime = null) {
|
|
90
103
|
try {
|
|
91
104
|
const rewrite = input.rewrite ?? mcpRewriteDefault();
|
|
92
105
|
const query = {
|
|
@@ -102,12 +115,12 @@ export async function recallSessionsHandler(deps, input, runtime = null) {
|
|
|
102
115
|
...(input.kind !== undefined ? { kind: input.kind } : {}),
|
|
103
116
|
...(input.workstream !== undefined ? { workstream: input.workstream } : {}),
|
|
104
117
|
};
|
|
105
|
-
const result = await deps.recall.search(query);
|
|
118
|
+
const result = await deps.recall.search(tenantId, query);
|
|
106
119
|
const conversationId = resolveConversationByQuery(input.query ?? "") ?? undefined;
|
|
107
120
|
// Telemetry — the MCP path is the real agent-usage path; without this it
|
|
108
121
|
// is invisible to query_log.jsonl and the Recall page. Fire-and-forget,
|
|
109
122
|
// mirrors the HTTP /api/recall handler.
|
|
110
|
-
void logQuery({
|
|
123
|
+
void logQuery(tenantId, {
|
|
111
124
|
source: "mcp",
|
|
112
125
|
runtime,
|
|
113
126
|
query: input.query ?? null,
|
|
@@ -125,20 +138,20 @@ export async function recallSessionsHandler(deps, input, runtime = null) {
|
|
|
125
138
|
return err(e);
|
|
126
139
|
}
|
|
127
140
|
}
|
|
128
|
-
export async function workSummaryHandler(deps, input) {
|
|
141
|
+
export async function workSummaryHandler(deps, tenantId, input) {
|
|
129
142
|
if (!deps.workDigest) {
|
|
130
143
|
return okText("work_summary is not available in this deployment.");
|
|
131
144
|
}
|
|
132
145
|
try {
|
|
133
146
|
const date = input.date ?? localToday();
|
|
134
|
-
const digest = await buildWorkDigest(deps.workDigest, date);
|
|
147
|
+
const digest = await buildWorkDigest(deps.workDigest, tenantId, date);
|
|
135
148
|
return okText(composeWorkDigest(digest));
|
|
136
149
|
}
|
|
137
150
|
catch (e) {
|
|
138
151
|
return err(e);
|
|
139
152
|
}
|
|
140
153
|
}
|
|
141
|
-
export async function recallWorkstreamHandler(deps, input, runtime = null) {
|
|
154
|
+
export async function recallWorkstreamHandler(deps, tenantId, input, runtime = null) {
|
|
142
155
|
if (!deps.workstreams)
|
|
143
156
|
return okText("recall_workstream is not available in this deployment.");
|
|
144
157
|
try {
|
|
@@ -147,13 +160,13 @@ export async function recallWorkstreamHandler(deps, input, runtime = null) {
|
|
|
147
160
|
return okText("Provide a workstream id or label.");
|
|
148
161
|
const conversationId = resolveConversationByQuery(idOrLabel) ?? undefined;
|
|
149
162
|
const ws = deps.workstreams.store;
|
|
150
|
-
const found = (await ws.getById(idOrLabel)) ?? (await ws.findByNormalizedLabel(normalizeLabel(idOrLabel)));
|
|
163
|
+
const found = (await ws.getById(tenantId, idOrLabel)) ?? (await ws.findByNormalizedLabel(tenantId, normalizeLabel(idOrLabel)));
|
|
151
164
|
if (!found)
|
|
152
165
|
return okText(`No workstream matches "${idOrLabel}".`);
|
|
153
|
-
const view = await rollupWorkstream({ workstreams: deps.workstreams.store, sessions: deps.workstreams.sessions, facts: deps.workstreams.facts, exemplars: deps.workstreams.exemplars }, found.id);
|
|
166
|
+
const view = await rollupWorkstream({ workstreams: deps.workstreams.store, sessions: deps.workstreams.sessions, facts: deps.workstreams.facts, exemplars: deps.workstreams.exemplars }, tenantId, found.id);
|
|
154
167
|
if (!view)
|
|
155
168
|
return okText(`No workstream matches "${idOrLabel}".`);
|
|
156
|
-
void logQuery({
|
|
169
|
+
void logQuery(tenantId, {
|
|
157
170
|
source: "mcp",
|
|
158
171
|
runtime,
|
|
159
172
|
query: idOrLabel,
|
|
@@ -171,17 +184,17 @@ export async function recallWorkstreamHandler(deps, input, runtime = null) {
|
|
|
171
184
|
return err(e);
|
|
172
185
|
}
|
|
173
186
|
}
|
|
174
|
-
/** idOrLabel -> live survivor workstream (merged_into resolved) | null. One source of truth for lifecycle handlers. */
|
|
175
|
-
async function resolveWorkstream(store, idOrLabel) {
|
|
176
|
-
const found = (await store.getById(idOrLabel)) ?? (await store.findByNormalizedLabel(normalizeLabel(idOrLabel)));
|
|
187
|
+
/** idOrLabel -> live survivor workstream (merged_into resolved) | null, within tenantId. One source of truth for lifecycle handlers. */
|
|
188
|
+
async function resolveWorkstream(store, tenantId, idOrLabel) {
|
|
189
|
+
const found = (await store.getById(tenantId, idOrLabel)) ?? (await store.findByNormalizedLabel(tenantId, normalizeLabel(idOrLabel)));
|
|
177
190
|
if (!found)
|
|
178
191
|
return null;
|
|
179
|
-
const all = await store.listAll();
|
|
192
|
+
const all = await store.listAll(tenantId);
|
|
180
193
|
const byId = new Map(all.map((w) => [w.id, { id: w.id, mergedInto: w.mergedInto }]));
|
|
181
194
|
const survivorId = resolveWorkstreamId(found.id, byId);
|
|
182
|
-
return survivorId === found.id ? found : ((await store.getById(survivorId)) ?? found);
|
|
195
|
+
return survivorId === found.id ? found : ((await store.getById(tenantId, survivorId)) ?? found);
|
|
183
196
|
}
|
|
184
|
-
export async function renameWorkstreamHandler(deps, input) {
|
|
197
|
+
export async function renameWorkstreamHandler(deps, tenantId, input) {
|
|
185
198
|
if (!deps.workstreams)
|
|
186
199
|
return okText("rename_workstream is not available in this deployment.");
|
|
187
200
|
try {
|
|
@@ -190,38 +203,38 @@ export async function renameWorkstreamHandler(deps, input) {
|
|
|
190
203
|
if (!idOrLabel || !label)
|
|
191
204
|
return okText("Provide the workstream (id or label) and the new label.");
|
|
192
205
|
const ws = deps.workstreams.store;
|
|
193
|
-
const target = await resolveWorkstream(ws, idOrLabel);
|
|
206
|
+
const target = await resolveWorkstream(ws, tenantId, idOrLabel);
|
|
194
207
|
if (!target)
|
|
195
208
|
return okText(`No workstream matches "${idOrLabel}".`);
|
|
196
|
-
const collision = await ws.findByNormalizedLabel(normalizeLabel(label));
|
|
209
|
+
const collision = await ws.findByNormalizedLabel(tenantId, normalizeLabel(label));
|
|
197
210
|
if (collision && collision.id !== target.id) {
|
|
198
211
|
return okText(`Label "${label}" is already used by workstream "${collision.label}" (${collision.id}).`);
|
|
199
212
|
}
|
|
200
|
-
await ws.setLabel(target.id, label);
|
|
213
|
+
await ws.setLabel(tenantId, target.id, label);
|
|
201
214
|
return okText(`Renamed workstream ${target.id}: "${target.label}" -> "${label}".`);
|
|
202
215
|
}
|
|
203
216
|
catch (e) {
|
|
204
217
|
return err(e);
|
|
205
218
|
}
|
|
206
219
|
}
|
|
207
|
-
export async function retireWorkstreamHandler(deps, input) {
|
|
220
|
+
export async function retireWorkstreamHandler(deps, tenantId, input) {
|
|
208
221
|
if (!deps.workstreams)
|
|
209
222
|
return okText("retire_workstream is not available in this deployment.");
|
|
210
223
|
try {
|
|
211
224
|
const idOrLabel = (input.idOrLabel ?? "").trim();
|
|
212
225
|
if (!idOrLabel)
|
|
213
226
|
return okText("Provide a workstream id or label.");
|
|
214
|
-
const target = await resolveWorkstream(deps.workstreams.store, idOrLabel);
|
|
227
|
+
const target = await resolveWorkstream(deps.workstreams.store, tenantId, idOrLabel);
|
|
215
228
|
if (!target)
|
|
216
229
|
return okText(`No workstream matches "${idOrLabel}".`);
|
|
217
|
-
await deps.workstreams.store.setStatus(target.id, "retired");
|
|
230
|
+
await deps.workstreams.store.setStatus(tenantId, target.id, "retired");
|
|
218
231
|
return okText(`Retired workstream "${target.label}" (${target.id}).`);
|
|
219
232
|
}
|
|
220
233
|
catch (e) {
|
|
221
234
|
return err(e);
|
|
222
235
|
}
|
|
223
236
|
}
|
|
224
|
-
export async function mergeWorkstreamsHandler(deps, input) {
|
|
237
|
+
export async function mergeWorkstreamsHandler(deps, tenantId, input) {
|
|
225
238
|
if (!deps.workstreams)
|
|
226
239
|
return okText("merge_workstreams is not available in this deployment.");
|
|
227
240
|
try {
|
|
@@ -230,22 +243,22 @@ export async function mergeWorkstreamsHandler(deps, input) {
|
|
|
230
243
|
if (!fromArg || !intoArg)
|
|
231
244
|
return okText("Provide both `from` and `into` (workstream id or label).");
|
|
232
245
|
const ws = deps.workstreams.store;
|
|
233
|
-
const from = await resolveWorkstream(ws, fromArg);
|
|
234
|
-
const into = await resolveWorkstream(ws, intoArg);
|
|
246
|
+
const from = await resolveWorkstream(ws, tenantId, fromArg);
|
|
247
|
+
const into = await resolveWorkstream(ws, tenantId, intoArg);
|
|
235
248
|
if (!from)
|
|
236
249
|
return okText(`No workstream matches "${fromArg}".`);
|
|
237
250
|
if (!into)
|
|
238
251
|
return okText(`No workstream matches "${intoArg}".`);
|
|
239
252
|
if (from.id === into.id)
|
|
240
253
|
return okText(`"${fromArg}" and "${intoArg}" resolve to the same workstream — nothing to merge.`);
|
|
241
|
-
await ws.merge(from.id, into.id);
|
|
254
|
+
await ws.merge(tenantId, from.id, into.id);
|
|
242
255
|
return okText(`Merged "${from.label}" (${from.id}) into "${into.label}" (${into.id}).`);
|
|
243
256
|
}
|
|
244
257
|
catch (e) {
|
|
245
258
|
return err(e);
|
|
246
259
|
}
|
|
247
260
|
}
|
|
248
|
-
export async function rebindSessionHandler(deps, input) {
|
|
261
|
+
export async function rebindSessionHandler(deps, tenantId, input) {
|
|
249
262
|
if (!deps.workstreams)
|
|
250
263
|
return okText("rebind_session is not available in this deployment.");
|
|
251
264
|
try {
|
|
@@ -253,10 +266,10 @@ export async function rebindSessionHandler(deps, input) {
|
|
|
253
266
|
const wsArg = (input.workstream ?? "").trim();
|
|
254
267
|
if (!sessionId || !wsArg)
|
|
255
268
|
return okText("Provide both a sessionId and a workstream (id or label).");
|
|
256
|
-
const ws = await resolveWorkstream(deps.workstreams.store, wsArg);
|
|
269
|
+
const ws = await resolveWorkstream(deps.workstreams.store, tenantId, wsArg);
|
|
257
270
|
if (!ws)
|
|
258
271
|
return okText(`No workstream matches "${wsArg}".`);
|
|
259
|
-
await deps.store.setWorkstreamBinding(sessionId, ws.id, "operator", null);
|
|
272
|
+
await deps.store.setWorkstreamBinding(tenantId, sessionId, ws.id, "operator", null);
|
|
260
273
|
return okText(`Rebound session ${sessionId} -> workstream "${ws.label}" (${ws.id}).`);
|
|
261
274
|
}
|
|
262
275
|
catch (e) {
|
|
@@ -270,9 +283,9 @@ function localToday() {
|
|
|
270
283
|
const da = String(d.getDate()).padStart(2, "0");
|
|
271
284
|
return `${d.getFullYear()}-${mo}-${da}`;
|
|
272
285
|
}
|
|
273
|
-
export async function getSessionHandler(deps, input) {
|
|
286
|
+
export async function getSessionHandler(deps, tenantId, input) {
|
|
274
287
|
try {
|
|
275
|
-
const session = await deps.store.getById(input.id);
|
|
288
|
+
const session = await deps.store.getById(tenantId, input.id);
|
|
276
289
|
if (!session) {
|
|
277
290
|
return err(new Error(`session ${input.id} not found`));
|
|
278
291
|
}
|
|
@@ -281,38 +294,58 @@ export async function getSessionHandler(deps, input) {
|
|
|
281
294
|
...(session.supersedes ?? []),
|
|
282
295
|
...(session.supersededBy ? [session.supersededBy] : []),
|
|
283
296
|
];
|
|
284
|
-
const linked = linkedIds.length > 0 ? await deps.store.getByIds(linkedIds) : [];
|
|
297
|
+
const linked = linkedIds.length > 0 ? await deps.store.getByIds(tenantId, linkedIds) : [];
|
|
285
298
|
const byId = new Map(linked.map((s) => [s.id, s]));
|
|
286
299
|
// Load supersedence log once so we can join reason + recordedBy onto supersededBy
|
|
287
300
|
const supersedenceLog = session.supersededBy
|
|
288
|
-
? await readSupersedenceLog()
|
|
301
|
+
? await readSupersedenceLog(tenantId)
|
|
289
302
|
: [];
|
|
290
303
|
const supersedenceMap = new Map(supersedenceLog.map((e) => [e.predecessorId, e]));
|
|
291
304
|
const supersedes = (session.supersedes ?? []).map((id) => {
|
|
292
305
|
const s = byId.get(id);
|
|
293
306
|
return s ? { id, label: s.label, summary: s.summary } : { id, label: "", summary: "" };
|
|
294
307
|
});
|
|
308
|
+
// Supersedence-enrichment fencing (program spec §4.6 hardening 2): even
|
|
309
|
+
// though readSupersedenceLog now reads the caller's own tenant file, a
|
|
310
|
+
// log entry's `reason`/`recordedBy` may only surface for a successor id
|
|
311
|
+
// that resolved through the tenant-filtered getByIds above (`byId`) —
|
|
312
|
+
// defense in depth against any pre-M6 legacy entry that predates
|
|
313
|
+
// per-tenant log files. An unresolved id contributes nothing — not even
|
|
314
|
+
// its reason — same as the by-id not-found shape (case 4).
|
|
295
315
|
const supersededBy = session.supersededBy
|
|
296
316
|
? (() => {
|
|
297
317
|
const s = byId.get(session.supersededBy);
|
|
318
|
+
if (!s)
|
|
319
|
+
return { id: session.supersededBy, label: "", summary: "" };
|
|
298
320
|
const logEntry = supersedenceMap.get(session.id);
|
|
299
|
-
const base = s
|
|
300
|
-
? { id: session.supersededBy, label: s.label, summary: s.summary }
|
|
301
|
-
: { id: session.supersededBy, label: "", summary: "" };
|
|
302
321
|
return {
|
|
303
|
-
|
|
322
|
+
id: session.supersededBy,
|
|
323
|
+
label: s.label,
|
|
324
|
+
summary: s.summary,
|
|
304
325
|
reason: logEntry?.reason,
|
|
305
326
|
recordedBy: logEntry?.source,
|
|
306
327
|
};
|
|
307
328
|
})()
|
|
308
329
|
: null;
|
|
309
|
-
|
|
330
|
+
// Isolated: the outcome rollup is enrichment, not the payload. A throw
|
|
331
|
+
// here (SQLITE_BUSY, stale handle, unmigrated db) must degrade to
|
|
332
|
+
// no-outcome, never fail the whole get_session call.
|
|
333
|
+
let outcome;
|
|
334
|
+
if (deps.outcomeDb) {
|
|
335
|
+
try {
|
|
336
|
+
outcome = await deriveOutcome(tenantId, session.id, await buildSqliteOutcomeDeps(deps.outcomeDb, tenantId));
|
|
337
|
+
}
|
|
338
|
+
catch {
|
|
339
|
+
outcome = undefined;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return ok({ ...session, supersedes, supersededBy, ...(outcome ? { outcome } : {}) });
|
|
310
343
|
}
|
|
311
344
|
catch (e) {
|
|
312
345
|
return err(e);
|
|
313
346
|
}
|
|
314
347
|
}
|
|
315
|
-
export async function recallFactsHandler(deps, input, runtime = null) {
|
|
348
|
+
export async function recallFactsHandler(deps, tenantId, input, runtime = null) {
|
|
316
349
|
if (!deps.factRecall) {
|
|
317
350
|
return err(new Error("fact recall not wired in this deployment"));
|
|
318
351
|
}
|
|
@@ -331,10 +364,10 @@ export async function recallFactsHandler(deps, input, runtime = null) {
|
|
|
331
364
|
? { minConfidence: input.minConfidence }
|
|
332
365
|
: {}),
|
|
333
366
|
};
|
|
334
|
-
const result = await deps.factRecall.search(query);
|
|
367
|
+
const result = await deps.factRecall.search(tenantId, query);
|
|
335
368
|
const conversationId = resolveConversationByQuery(input.query ?? "") ?? undefined;
|
|
336
369
|
// Telemetry — see recallSessionsHandler. Fire-and-forget.
|
|
337
|
-
void logFactQuery({
|
|
370
|
+
void logFactQuery(tenantId, {
|
|
338
371
|
source: "mcp",
|
|
339
372
|
runtime,
|
|
340
373
|
query: input.query ?? null,
|
|
@@ -353,12 +386,12 @@ export async function recallFactsHandler(deps, input, runtime = null) {
|
|
|
353
386
|
return err(e);
|
|
354
387
|
}
|
|
355
388
|
}
|
|
356
|
-
export async function getFactHistoryHandler(deps, input) {
|
|
389
|
+
export async function getFactHistoryHandler(deps, tenantId, input) {
|
|
357
390
|
if (!deps.factStore) {
|
|
358
391
|
return err(new Error("fact store not wired in this deployment"));
|
|
359
392
|
}
|
|
360
393
|
try {
|
|
361
|
-
const chains = await deps.factStore.getHistory(input.subject, input.predicate);
|
|
394
|
+
const chains = await deps.factStore.getHistory(tenantId, input.subject, input.predicate);
|
|
362
395
|
return ok({ subject: input.subject, predicate: input.predicate ?? null, chains });
|
|
363
396
|
}
|
|
364
397
|
catch (e) {
|
|
@@ -427,6 +460,10 @@ run, or any quote you intend to reference verbatim.
|
|
|
427
460
|
The recall_sessions digest is optimized for ranking and scanning; the full body
|
|
428
461
|
contains the actual conversation transcript that produced the decision.
|
|
429
462
|
|
|
463
|
+
When wired, the response includes an \`outcome\` object: whether the session's
|
|
464
|
+
decision held, was overturned, was built upon, was re-derived later, or
|
|
465
|
+
remains unobserved, plus the evidence tier/confidence behind that verdict.
|
|
466
|
+
|
|
430
467
|
Args:
|
|
431
468
|
- id: Canonical session ID returned by recall_sessions (e.g. "cc_abc123",
|
|
432
469
|
"sess_pgvector"). Pass the id field from the recall_sessions result.`;
|
|
@@ -516,13 +553,13 @@ Args:
|
|
|
516
553
|
|
|
517
554
|
Idempotent. Re-marking the same pair is a no-op. Returns the linked pair on
|
|
518
555
|
success; errors if either id is unknown or the two ids are equal.`;
|
|
519
|
-
export async function markSupersededHandler(deps, input) {
|
|
556
|
+
export async function markSupersededHandler(deps, tenantId, input) {
|
|
520
557
|
if (!input.predecessor_id || !input.successor_id) {
|
|
521
558
|
return err(new Error("predecessor_id and successor_id are required"));
|
|
522
559
|
}
|
|
523
560
|
try {
|
|
524
|
-
await deps.store.markSuperseded(input.predecessor_id, input.successor_id);
|
|
525
|
-
void appendSupersedence({
|
|
561
|
+
await deps.store.markSuperseded(tenantId, input.predecessor_id, input.successor_id);
|
|
562
|
+
void appendSupersedence(tenantId, {
|
|
526
563
|
predecessorId: input.predecessor_id,
|
|
527
564
|
successorId: input.successor_id,
|
|
528
565
|
source: "mcp",
|
|
@@ -560,7 +597,7 @@ Args:
|
|
|
560
597
|
|
|
561
598
|
Idempotent: calling it twice on the same id is a no-op. Errors if fact_id
|
|
562
599
|
is unknown.`;
|
|
563
|
-
export async function supersedeFactHandler(deps, input) {
|
|
600
|
+
export async function supersedeFactHandler(deps, tenantId, input) {
|
|
564
601
|
if (!input.fact_id || input.fact_id.length < 4) {
|
|
565
602
|
return err(new Error("fact_id is required"));
|
|
566
603
|
}
|
|
@@ -568,8 +605,8 @@ export async function supersedeFactHandler(deps, input) {
|
|
|
568
605
|
return err(new Error("fact store not available"));
|
|
569
606
|
}
|
|
570
607
|
try {
|
|
571
|
-
await deps.factStore.retire(input.fact_id);
|
|
572
|
-
void appendFactSupersedence({
|
|
608
|
+
await deps.factStore.retire(tenantId, input.fact_id);
|
|
609
|
+
void appendFactSupersedence(tenantId, {
|
|
573
610
|
factId: input.fact_id,
|
|
574
611
|
source: "mcp",
|
|
575
612
|
...(input.reason !== undefined ? { reason: input.reason } : {}),
|
|
@@ -584,39 +621,96 @@ export async function supersedeFactHandler(deps, input) {
|
|
|
584
621
|
return err(e);
|
|
585
622
|
}
|
|
586
623
|
}
|
|
587
|
-
export async function citeSessionHandler(input) {
|
|
624
|
+
export async function citeSessionHandler(tenantId, input) {
|
|
588
625
|
if (!input.id || input.id.length < MIN_CITE_ID_LEN) {
|
|
589
626
|
return err(new Error(`id must be at least ${MIN_CITE_ID_LEN} characters`));
|
|
590
627
|
}
|
|
591
628
|
try {
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
629
|
+
// Agents rarely pass conversation_id, so resolve it server-side. The
|
|
630
|
+
// surfaced-memo only knows sessions the recall HOOK injected, which is
|
|
631
|
+
// empty whenever ambient recall is off or failing — that gap silently
|
|
632
|
+
// unattributed every pull-driven citation. Fall back to the same
|
|
633
|
+
// transcript scan the recall handlers use: the runtime has already
|
|
634
|
+
// written this cite_session tool_use (carrying `id`) to the transcript
|
|
635
|
+
// before this handler runs, so the id is a reliable join key.
|
|
636
|
+
const conversationId = input.conversation_id ??
|
|
637
|
+
resolveConversationForSession(tenantId, input.id) ??
|
|
638
|
+
resolveConversationByQuery(input.id) ??
|
|
639
|
+
"mcp_tool";
|
|
640
|
+
const logged = await appendCitation(tenantId, {
|
|
641
|
+
conversationId,
|
|
596
642
|
citedId: input.id,
|
|
597
643
|
kind: "tool_use",
|
|
598
644
|
...(input.reason !== undefined ? { responsePreview: input.reason } : {}),
|
|
599
645
|
});
|
|
600
|
-
|
|
646
|
+
// An unattributable citation is dropped by the write path; reporting it as
|
|
647
|
+
// logged would make a dead telemetry path look healthy.
|
|
648
|
+
return ok({ logged, id: input.id, ...(logged ? {} : { reason: "unattributable_conversation" }) });
|
|
601
649
|
}
|
|
602
650
|
catch (e) {
|
|
603
651
|
return err(e);
|
|
604
652
|
}
|
|
605
653
|
}
|
|
606
|
-
|
|
654
|
+
const REPORT_OUTCOME_DESCRIPTION = `Record whether a session's decision held up or was overturned. THIS IS THE CONTRACT SURFACE for outcome signals: it writes the same append-only \`signals\` row that POST /api/signal writes (producer "mcp"), just over MCP instead of HTTP.
|
|
655
|
+
|
|
656
|
+
Expected callers are deterministic producers: hooks, CI pipelines, and automation platforms confirming a build/deploy/test result against a prior session. Agents rarely self-report their own outcome; outcome should come from independent evidence, not the agent's opinion of its own work.
|
|
657
|
+
|
|
658
|
+
Args:
|
|
659
|
+
- session_id or correlation_key: at least one required. session_id is the NLM session this outcome applies to. correlation_key is an external identifier (e.g. a CI run id) to use when no NLM session_id is known; it is recorded on the row for traceability but is not (yet) joined into the Tier-B outcome rollup.
|
|
660
|
+
- outcome: one of pass | fail | fix | exhausted, the same vocabulary POST /api/signal accepts.
|
|
661
|
+
- source_of_record: required. Identifies the deterministic system asserting this outcome (e.g. "ci:github-actions", "hook:post-tool-use", "n8n:deploy-workflow").
|
|
662
|
+
- detail: optional passthrough object for additional context.`;
|
|
663
|
+
/** The kind stamped on rows this tool writes. Loosely, this row represents an
|
|
664
|
+
* outcome *review* of a prior session's decision, and "review" is the closest
|
|
665
|
+
* fit among the existing SignalKind vocabulary (gate|eval|review|test); no
|
|
666
|
+
* literal "outcome" kind exists (see rollup.ts's own note on this same
|
|
667
|
+
* ambiguity). Tier-A correlation in rollup.ts keys on session_id only, so
|
|
668
|
+
* this choice does not affect verdict derivation. */
|
|
669
|
+
const REPORT_OUTCOME_KIND = "review";
|
|
670
|
+
export async function reportOutcomeHandler(deps, tenantId, input) {
|
|
671
|
+
if (!input.session_id && !input.correlation_key) {
|
|
672
|
+
return err(new Error("session_id or correlation_key is required"));
|
|
673
|
+
}
|
|
674
|
+
if (!input.source_of_record || input.source_of_record.length === 0) {
|
|
675
|
+
return err(new Error("source_of_record is required"));
|
|
676
|
+
}
|
|
677
|
+
if (!deps.signalStore || deps.installScope === undefined) {
|
|
678
|
+
return err(new Error("signal store not available"));
|
|
679
|
+
}
|
|
680
|
+
try {
|
|
681
|
+
let signal = normalizeSignal({
|
|
682
|
+
kind: REPORT_OUTCOME_KIND,
|
|
683
|
+
producer: "mcp",
|
|
684
|
+
outcome: input.outcome,
|
|
685
|
+
session: input.session_id ?? null,
|
|
686
|
+
detail: {
|
|
687
|
+
...(input.detail ?? {}),
|
|
688
|
+
source_of_record: input.source_of_record,
|
|
689
|
+
...(input.correlation_key ? { correlation_key: input.correlation_key } : {}),
|
|
690
|
+
},
|
|
691
|
+
}, deps.installScope);
|
|
692
|
+
signal = await stampSignalScope(signal, tenantId, { sessionScopeReader: deps.sessionScopeReader });
|
|
693
|
+
await deps.signalStore.insert(tenantId, signal);
|
|
694
|
+
return ok({ recorded: true, id: signal.id, outcome: signal.outcome });
|
|
695
|
+
}
|
|
696
|
+
catch (e) {
|
|
697
|
+
return err(e);
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
export async function listMergeSuggestionsHandler(deps, tenantId, input) {
|
|
607
701
|
if (!deps.workstreams)
|
|
608
702
|
return okText("list_merge_suggestions is not available in this deployment.");
|
|
609
703
|
try {
|
|
610
704
|
const minScore = typeof input.minScore === "number" ? input.minScore : 0.5;
|
|
611
|
-
const all = (await deps.workstreams.store.listAll()).filter((w) => w.status === "active");
|
|
705
|
+
const all = (await deps.workstreams.store.listAll(tenantId)).filter((w) => w.status === "active");
|
|
612
706
|
if (all.length < 2)
|
|
613
707
|
return okText("Not enough active workstreams to suggest merges.");
|
|
614
708
|
const ids = all.map((w) => w.id);
|
|
615
|
-
const entMap = await deps.workstreams.store.entitiesFor(ids);
|
|
709
|
+
const entMap = await deps.workstreams.store.entitiesFor(tenantId, ids);
|
|
616
710
|
const items = await Promise.all(all.map(async (w) => ({
|
|
617
711
|
id: w.id, label: w.label,
|
|
618
712
|
entities: entMap.get(w.id) ?? [],
|
|
619
|
-
sessionIds: await deps.workstreams.sessions.listSessionIdsByWorkstreams([w.id]),
|
|
713
|
+
sessionIds: await deps.workstreams.sessions.listSessionIdsByWorkstreams(tenantId, [w.id]),
|
|
620
714
|
})));
|
|
621
715
|
const suggestions = suggestMerges(items, minScore);
|
|
622
716
|
if (suggestions.length === 0)
|
|
@@ -632,7 +726,144 @@ export async function listMergeSuggestionsHandler(deps, input) {
|
|
|
632
726
|
return err(e);
|
|
633
727
|
}
|
|
634
728
|
}
|
|
635
|
-
|
|
729
|
+
const LEARNER_ENROLL_DESCRIPTION = `\
|
|
730
|
+
Enroll a learner in a course, binding the enrollment to a workstream so later
|
|
731
|
+
sessions resolve their learning state at recall time. The course plan itself
|
|
732
|
+
stays with the caller — NLM stores only its reference, revision, and
|
|
733
|
+
fingerprint, and refuses writes when a re-enrolled plan differs.`;
|
|
734
|
+
const LEARNER_RECORD_DESCRIPTION = `\
|
|
735
|
+
Record a learning event: concepts covered plus per-concept attempts (task,
|
|
736
|
+
response, result/help/kind), an interpretation, and the next step. Identical
|
|
737
|
+
event ids are safe no-ops; conflicting ids fail rather than rewriting
|
|
738
|
+
history. A submitted exercise is not evidence until recorded here.`;
|
|
739
|
+
const LEARNER_SUMMARY_DESCRIPTION = `\
|
|
740
|
+
Summarize what a learner has demonstrably learned in a course: per-concept
|
|
741
|
+
status (exposed/practicing/demonstrated/retained), the latest next step, and
|
|
742
|
+
sessions bound to the enrollment's workstream since the last recorded event
|
|
743
|
+
(unrecorded candidates — record them with learner_record, don't assume).`;
|
|
744
|
+
const LEARNER_COMPLETE_DESCRIPTION = `\
|
|
745
|
+
Mark a learner's course completed. Preserves completion history and actual
|
|
746
|
+
evidence; untaught or untested topics stay explicitly marked.`;
|
|
747
|
+
const LEARNER_SUMMARY_DEFAULT_LIMIT = 50;
|
|
748
|
+
const LEARNER_UNRECORDED_CAP = 10;
|
|
749
|
+
export async function learnerEnrollHandler(deps, tenantId, input) {
|
|
750
|
+
if (!deps.learners)
|
|
751
|
+
return okText("learner tools are not available in this deployment.");
|
|
752
|
+
try {
|
|
753
|
+
const enrollment = await enrollCourse({ learners: deps.learners.store, workstreams: deps.learners.workstreams }, tenantId, {
|
|
754
|
+
learnerId: input.learner,
|
|
755
|
+
...(input.label !== undefined ? { learnerLabel: input.label } : {}),
|
|
756
|
+
courseId: input.course,
|
|
757
|
+
workstream: input.workstream,
|
|
758
|
+
planRef: input.plan_ref,
|
|
759
|
+
planRevision: input.plan_revision,
|
|
760
|
+
planFingerprint: input.plan_fingerprint,
|
|
761
|
+
});
|
|
762
|
+
return okText(`Enrolled ${input.learner} in ${input.course} (${enrollment.status}, plan rev ${enrollment.planRevision}).`);
|
|
763
|
+
}
|
|
764
|
+
catch (e) {
|
|
765
|
+
return err(e);
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
export async function learnerRecordHandler(deps, tenantId, input) {
|
|
769
|
+
if (!deps.learners)
|
|
770
|
+
return okText("learner tools are not available in this deployment.");
|
|
771
|
+
try {
|
|
772
|
+
const result = await recordLearningEvent({ learners: deps.learners.store, workstreams: deps.learners.workstreams }, tenantId, input.learner, input.course, {
|
|
773
|
+
id: input.event.id,
|
|
774
|
+
date: input.event.date,
|
|
775
|
+
covered: input.event.covered,
|
|
776
|
+
attempts: input.event.attempts,
|
|
777
|
+
interpretation: input.event.interpretation,
|
|
778
|
+
nextStep: input.event.next_step,
|
|
779
|
+
});
|
|
780
|
+
return result === "inserted"
|
|
781
|
+
? okText(`Recorded event ${input.event.id} for ${input.learner}/${input.course}.`)
|
|
782
|
+
: okText(`Event ${input.event.id} already recorded (identical) — no change.`);
|
|
783
|
+
}
|
|
784
|
+
catch (e) {
|
|
785
|
+
return err(e);
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
export async function learnerSummaryHandler(deps, tenantId, input) {
|
|
789
|
+
if (!deps.learners)
|
|
790
|
+
return okText("learner tools are not available in this deployment.");
|
|
791
|
+
try {
|
|
792
|
+
let course = input.course?.trim() ? input.course.trim() : undefined;
|
|
793
|
+
if (!course) {
|
|
794
|
+
const enrollments = await deps.learners.store.listEnrollments(tenantId, input.learner);
|
|
795
|
+
const active = enrollments.filter((e) => e.status === "active");
|
|
796
|
+
if (active.length === 0)
|
|
797
|
+
return okText(`No active enrollments for learner ${input.learner}.`);
|
|
798
|
+
if (active.length > 1) {
|
|
799
|
+
return okText(`Multiple active courses for ${input.learner}; specify course: ${active.map((e) => e.courseId).join(", ")}.`);
|
|
800
|
+
}
|
|
801
|
+
course = active[0].courseId;
|
|
802
|
+
}
|
|
803
|
+
const summary = await getCourseSummary({ learners: deps.learners.store, workstreams: deps.learners.workstreams }, tenantId, input.learner, course);
|
|
804
|
+
if (!summary)
|
|
805
|
+
return okText(`No enrollment for learner ${input.learner} in course ${course}.`);
|
|
806
|
+
const lines = [];
|
|
807
|
+
lines.push(`LEARNER ${summary.learnerId} · ${summary.courseId} (${summary.status}, rev ${summary.planRevision}, ${summary.eventCount} events)`);
|
|
808
|
+
const limit = input.limit ?? LEARNER_SUMMARY_DEFAULT_LIMIT;
|
|
809
|
+
const concepts = summary.concepts.slice(0, Math.max(0, limit));
|
|
810
|
+
if (concepts.length === 0) {
|
|
811
|
+
lines.push("(no concepts recorded yet)");
|
|
812
|
+
}
|
|
813
|
+
else {
|
|
814
|
+
lines.push("CONCEPTS:");
|
|
815
|
+
for (const c of concepts) {
|
|
816
|
+
const n = c.attemptCount === 1 ? "1 attempt" : `${c.attemptCount} attempts`;
|
|
817
|
+
lines.push(` - [${c.status}] ${c.concept} (${n})`);
|
|
818
|
+
}
|
|
819
|
+
if (summary.concepts.length > concepts.length) {
|
|
820
|
+
lines.push(` (+${summary.concepts.length - concepts.length} more — raise limit)`);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
lines.push(`Next: ${summary.nextStep ? truncateSummary(summary.nextStep) : "(none recorded)"}`);
|
|
824
|
+
if (summary.workstreamId) {
|
|
825
|
+
const ids = await deps.learners.sessions.listSessionIdsByWorkstreams(tenantId, [summary.workstreamId]);
|
|
826
|
+
if (ids.length > 0) {
|
|
827
|
+
const sessions = await deps.learners.sessions.getByIds(tenantId, ids);
|
|
828
|
+
const cutoff = summary.lastEventDate ?? "";
|
|
829
|
+
const unrecorded = sessions
|
|
830
|
+
.filter((s) => s.startedAt.slice(0, 10) > cutoff)
|
|
831
|
+
.sort((a, b) => (a.startedAt < b.startedAt ? -1 : 1))
|
|
832
|
+
.slice(0, LEARNER_UNRECORDED_CAP);
|
|
833
|
+
if (unrecorded.length > 0) {
|
|
834
|
+
lines.push(`Unrecorded sessions (${unrecorded.length}):`);
|
|
835
|
+
for (const s of unrecorded) {
|
|
836
|
+
lines.push(` - ${s.id} · ${truncateSummary(s.label, 80)} (${s.startedAt.slice(0, 10)})`);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
return okText(lines.join("\n"));
|
|
842
|
+
}
|
|
843
|
+
catch (e) {
|
|
844
|
+
return err(e);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
export async function learnerCompleteHandler(deps, tenantId, input) {
|
|
848
|
+
if (!deps.learners)
|
|
849
|
+
return okText("learner tools are not available in this deployment.");
|
|
850
|
+
try {
|
|
851
|
+
await completeLearnerCourse({ learners: deps.learners.store, workstreams: deps.learners.workstreams }, tenantId, input.learner, input.course);
|
|
852
|
+
return okText(`Marked ${input.course} completed for ${input.learner}.`);
|
|
853
|
+
}
|
|
854
|
+
catch (e) {
|
|
855
|
+
return err(e);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* The composition root for the MCP surface (program spec §4.6 "M2 threading
|
|
860
|
+
* semantics until M3"): `tenantId` is required here, not defaulted, and
|
|
861
|
+
* every tool handler below receives it explicitly rather than reading a
|
|
862
|
+
* module-level constant. The stdio caller (src/cli/nlm.ts) is local-mode
|
|
863
|
+
* only and always passes DEFAULT_TEAM_ID; the HTTP /mcp mount
|
|
864
|
+
* (src/http/app.ts) passes the token-resolved team (M3).
|
|
865
|
+
*/
|
|
866
|
+
export function createMcpServer(deps, tenantId) {
|
|
636
867
|
const server = new McpServer({
|
|
637
868
|
name: SERVER_NAME,
|
|
638
869
|
version: SERVER_VERSION,
|
|
@@ -679,7 +910,7 @@ export function createMcpServer(deps) {
|
|
|
679
910
|
idempotentHint: true,
|
|
680
911
|
openWorldHint: true,
|
|
681
912
|
},
|
|
682
|
-
}, async (args) => recallSessionsHandler(deps, args, mcpRuntimeFromClient(server.server.getClientVersion())));
|
|
913
|
+
}, async (args) => recallSessionsHandler(deps, tenantId, args, mcpRuntimeFromClient(server.server.getClientVersion())));
|
|
683
914
|
server.registerTool("get_session", {
|
|
684
915
|
title: "Get Full NLM Session",
|
|
685
916
|
description: GET_SESSION_DESCRIPTION,
|
|
@@ -692,7 +923,7 @@ export function createMcpServer(deps) {
|
|
|
692
923
|
idempotentHint: true,
|
|
693
924
|
openWorldHint: true,
|
|
694
925
|
},
|
|
695
|
-
}, async (args) => getSessionHandler(deps, args));
|
|
926
|
+
}, async (args) => getSessionHandler(deps, tenantId, args));
|
|
696
927
|
if (deps.factRecall && deps.factStore) {
|
|
697
928
|
server.registerTool("recall_facts", {
|
|
698
929
|
title: "Recall Facts from NLM",
|
|
@@ -742,7 +973,7 @@ export function createMcpServer(deps) {
|
|
|
742
973
|
idempotentHint: true,
|
|
743
974
|
openWorldHint: true,
|
|
744
975
|
},
|
|
745
|
-
}, async (args) => recallFactsHandler(deps, args, mcpRuntimeFromClient(server.server.getClientVersion())));
|
|
976
|
+
}, async (args) => recallFactsHandler(deps, tenantId, args, mcpRuntimeFromClient(server.server.getClientVersion())));
|
|
746
977
|
server.registerTool("get_fact_history", {
|
|
747
978
|
title: "Get Fact Supersedence History",
|
|
748
979
|
description: GET_FACT_HISTORY_DESCRIPTION,
|
|
@@ -759,7 +990,7 @@ export function createMcpServer(deps) {
|
|
|
759
990
|
idempotentHint: true,
|
|
760
991
|
openWorldHint: true,
|
|
761
992
|
},
|
|
762
|
-
}, async (args) => getFactHistoryHandler(deps, args));
|
|
993
|
+
}, async (args) => getFactHistoryHandler(deps, tenantId, args));
|
|
763
994
|
server.registerTool("supersede_fact", {
|
|
764
995
|
title: "Supersede a Stale NLM Fact",
|
|
765
996
|
description: SUPERSEDE_FACT_DESCRIPTION,
|
|
@@ -779,7 +1010,7 @@ export function createMcpServer(deps) {
|
|
|
779
1010
|
idempotentHint: true,
|
|
780
1011
|
openWorldHint: false,
|
|
781
1012
|
},
|
|
782
|
-
}, async (args) => supersedeFactHandler(deps, args));
|
|
1013
|
+
}, async (args) => supersedeFactHandler(deps, tenantId, args));
|
|
783
1014
|
}
|
|
784
1015
|
server.registerTool("cite_session", {
|
|
785
1016
|
title: "Cite NLM Session",
|
|
@@ -801,7 +1032,40 @@ export function createMcpServer(deps) {
|
|
|
801
1032
|
idempotentHint: false,
|
|
802
1033
|
openWorldHint: false,
|
|
803
1034
|
},
|
|
804
|
-
}, async (args) => citeSessionHandler(args));
|
|
1035
|
+
}, async (args) => citeSessionHandler(tenantId, args));
|
|
1036
|
+
if (deps.signalStore && deps.installScope !== undefined) {
|
|
1037
|
+
server.registerTool("report_outcome", {
|
|
1038
|
+
title: "Report Session Outcome",
|
|
1039
|
+
description: REPORT_OUTCOME_DESCRIPTION,
|
|
1040
|
+
inputSchema: {
|
|
1041
|
+
session_id: z
|
|
1042
|
+
.string()
|
|
1043
|
+
.optional()
|
|
1044
|
+
.describe("NLM session ID this outcome applies to. Required if correlation_key is omitted."),
|
|
1045
|
+
correlation_key: z
|
|
1046
|
+
.string()
|
|
1047
|
+
.optional()
|
|
1048
|
+
.describe("External correlation identifier (e.g. a CI run id) when no NLM session_id is known. Required if session_id is omitted."),
|
|
1049
|
+
outcome: z
|
|
1050
|
+
.enum(SIGNAL_OUTCOMES)
|
|
1051
|
+
.describe("pass = held. fail | fix | exhausted = overturned."),
|
|
1052
|
+
source_of_record: z
|
|
1053
|
+
.string()
|
|
1054
|
+
.min(1)
|
|
1055
|
+
.describe("The deterministic system asserting this outcome (e.g. \"ci:github-actions\", \"hook:post-tool-use\")."),
|
|
1056
|
+
detail: z
|
|
1057
|
+
.record(z.string(), z.unknown())
|
|
1058
|
+
.optional()
|
|
1059
|
+
.describe("Optional passthrough context, stored on the signal row."),
|
|
1060
|
+
},
|
|
1061
|
+
annotations: {
|
|
1062
|
+
readOnlyHint: false,
|
|
1063
|
+
destructiveHint: false,
|
|
1064
|
+
idempotentHint: true,
|
|
1065
|
+
openWorldHint: false,
|
|
1066
|
+
},
|
|
1067
|
+
}, async (args) => reportOutcomeHandler(deps, tenantId, args));
|
|
1068
|
+
}
|
|
805
1069
|
server.registerTool("mark_superseded", {
|
|
806
1070
|
title: "Mark NLM Session Superseded",
|
|
807
1071
|
description: MARK_SUPERSEDED_DESCRIPTION,
|
|
@@ -825,7 +1089,7 @@ export function createMcpServer(deps) {
|
|
|
825
1089
|
idempotentHint: true,
|
|
826
1090
|
openWorldHint: false,
|
|
827
1091
|
},
|
|
828
|
-
}, async (args) => markSupersededHandler(deps, args));
|
|
1092
|
+
}, async (args) => markSupersededHandler(deps, tenantId, args));
|
|
829
1093
|
server.registerTool("work_summary", {
|
|
830
1094
|
title: "Daily work summary",
|
|
831
1095
|
description: "The operator's agent-assisted work recap for a day: where attention went (active time by topic), focus quality (context switches, longest block), and progress (decisions, open loops). Optional `date` (YYYY-MM-DD); defaults to today.",
|
|
@@ -837,7 +1101,7 @@ export function createMcpServer(deps) {
|
|
|
837
1101
|
.describe("Day to summarize, YYYY-MM-DD. Defaults to today."),
|
|
838
1102
|
},
|
|
839
1103
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
840
|
-
}, async (args) => workSummaryHandler(deps, args));
|
|
1104
|
+
}, async (args) => workSummaryHandler(deps, tenantId, args));
|
|
841
1105
|
server.registerTool("recall_workstream", {
|
|
842
1106
|
title: "Recall a workstream's accumulated context",
|
|
843
1107
|
description: "Return the coherent project view for a workstream: its member sessions, current decisions and open loops, accumulated facts, and code exemplars. Accepts a workstream id or label; merge chains resolve to the live workstream.",
|
|
@@ -845,7 +1109,7 @@ export function createMcpServer(deps) {
|
|
|
845
1109
|
idOrLabel: z.string().describe("Workstream id (ws_...) or label (e.g. 'NLM')."),
|
|
846
1110
|
},
|
|
847
1111
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
848
|
-
}, async (args) => recallWorkstreamHandler(deps, args, mcpRuntimeFromClient(server.server.getClientVersion())));
|
|
1112
|
+
}, async (args) => recallWorkstreamHandler(deps, tenantId, args, mcpRuntimeFromClient(server.server.getClientVersion())));
|
|
849
1113
|
server.registerTool("rebind_session", {
|
|
850
1114
|
title: "Rebind a session to a different workstream",
|
|
851
1115
|
description: "Move a session's primary workstream binding (operator correction). Sets binding_source=operator. The session's facts and exemplars roll up under the new workstream automatically (rollup is by session binding).",
|
|
@@ -854,7 +1118,7 @@ export function createMcpServer(deps) {
|
|
|
854
1118
|
workstream: z.string().describe("Target workstream id (ws_...) or label."),
|
|
855
1119
|
},
|
|
856
1120
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
857
|
-
}, async (args) => rebindSessionHandler(deps, args));
|
|
1121
|
+
}, async (args) => rebindSessionHandler(deps, tenantId, args));
|
|
858
1122
|
server.registerTool("merge_workstreams", {
|
|
859
1123
|
title: "Merge one workstream into another",
|
|
860
1124
|
description: "Supersede a duplicate workstream into the one to keep: sets merged_into, marks it merged, and unions its entity index. Sessions, facts, and exemplars resolve to the survivor automatically (no session rewrite). Accepts ids or labels; merge chains resolve.",
|
|
@@ -863,7 +1127,7 @@ export function createMcpServer(deps) {
|
|
|
863
1127
|
into: z.string().describe("Workstream to keep (the survivor); id or label."),
|
|
864
1128
|
},
|
|
865
1129
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
866
|
-
}, async (args) => mergeWorkstreamsHandler(deps, args));
|
|
1130
|
+
}, async (args) => mergeWorkstreamsHandler(deps, tenantId, args));
|
|
867
1131
|
server.registerTool("rename_workstream", {
|
|
868
1132
|
title: "Rename a workstream",
|
|
869
1133
|
description: "Relabel a workstream. Refuses a label that collides with a different existing workstream's normalized label (prevents accidental duplicates). Accepts id or label.",
|
|
@@ -872,19 +1136,19 @@ export function createMcpServer(deps) {
|
|
|
872
1136
|
label: z.string().describe("New label."),
|
|
873
1137
|
},
|
|
874
1138
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
875
|
-
}, async (args) => renameWorkstreamHandler(deps, args));
|
|
1139
|
+
}, async (args) => renameWorkstreamHandler(deps, tenantId, args));
|
|
876
1140
|
server.registerTool("retire_workstream", {
|
|
877
1141
|
title: "Retire a workstream",
|
|
878
1142
|
description: "Mark a workstream retired (status=retired). Operator cleanup for dead one-off workstreams. Reversible by re-setting status. Accepts id or label.",
|
|
879
1143
|
inputSchema: { idOrLabel: z.string().describe("Workstream id or label.") },
|
|
880
1144
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
881
|
-
}, async (args) => retireWorkstreamHandler(deps, args));
|
|
1145
|
+
}, async (args) => retireWorkstreamHandler(deps, tenantId, args));
|
|
882
1146
|
server.registerTool("list_merge_suggestions", {
|
|
883
1147
|
title: "Suggest duplicate workstreams to merge",
|
|
884
1148
|
description: "Score active workstream pairs by shared entities, co-occurring sessions, and label similarity; list likely duplicates for one-click merge_workstreams. Computed on demand; read-only.",
|
|
885
1149
|
inputSchema: { minScore: z.number().optional().describe("Minimum similarity score 0..1 (default 0.5).") },
|
|
886
1150
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
887
|
-
}, async (args) => listMergeSuggestionsHandler(deps, args));
|
|
1151
|
+
}, async (args) => listMergeSuggestionsHandler(deps, tenantId, args));
|
|
888
1152
|
if (deps.exemplarStore &&
|
|
889
1153
|
deps.installScope &&
|
|
890
1154
|
process.env["NLM_CODE_EXEMPLARS_ENABLED"] === "1") {
|
|
@@ -926,7 +1190,7 @@ export function createMcpServer(deps) {
|
|
|
926
1190
|
openWorldHint: false,
|
|
927
1191
|
},
|
|
928
1192
|
}, async (args) => {
|
|
929
|
-
const result = await recallCode({
|
|
1193
|
+
const result = await recallCode(tenantId, {
|
|
930
1194
|
query: args.query,
|
|
931
1195
|
installScope,
|
|
932
1196
|
...(args.repo ? { repo: args.repo } : {}),
|
|
@@ -939,7 +1203,7 @@ export function createMcpServer(deps) {
|
|
|
939
1203
|
...result.positives.map((e) => e.id),
|
|
940
1204
|
...result.negatives.map((e) => e.id),
|
|
941
1205
|
];
|
|
942
|
-
void logQuery({
|
|
1206
|
+
void logQuery(tenantId, {
|
|
943
1207
|
source: "mcp",
|
|
944
1208
|
runtime: mcpRuntimeFromClient(server.server.getClientVersion()),
|
|
945
1209
|
query: args.query,
|
|
@@ -972,10 +1236,69 @@ export function createMcpServer(deps) {
|
|
|
972
1236
|
if (patch.retired === undefined && patch.outcome === undefined) {
|
|
973
1237
|
return { content: [{ type: "text", text: JSON.stringify({ error: "provide retire and/or outcome" }) }] };
|
|
974
1238
|
}
|
|
975
|
-
const res = await exemplarStore.setVerdict(a.exemplar_id, patch, "human");
|
|
1239
|
+
const res = await exemplarStore.setVerdict(tenantId, a.exemplar_id, patch, "human");
|
|
976
1240
|
return { content: [{ type: "text", text: JSON.stringify({ exemplar_id: a.exemplar_id, status: res.status }) }] };
|
|
977
1241
|
});
|
|
978
1242
|
}
|
|
1243
|
+
if (deps.learners) {
|
|
1244
|
+
server.registerTool("learner_enroll", {
|
|
1245
|
+
title: "Enroll Learner in Course",
|
|
1246
|
+
description: LEARNER_ENROLL_DESCRIPTION,
|
|
1247
|
+
inputSchema: {
|
|
1248
|
+
learner: z.string().min(1).describe("Learner id (lowercase slug). Auto-created on first enroll."),
|
|
1249
|
+
label: z.string().optional().describe("Display label for a new learner; defaults to the id."),
|
|
1250
|
+
course: z.string().min(1).describe("Course id (lowercase slug)."),
|
|
1251
|
+
workstream: z.string().min(1).describe("Workstream id or label to bind the enrollment to."),
|
|
1252
|
+
plan_ref: z.string().min(1).describe("Caller-side course plan reference (NLM stores the ref, not the plan)."),
|
|
1253
|
+
plan_revision: z.number().int().min(1).describe("Plan revision; a changed plan must bump, never silently upsert."),
|
|
1254
|
+
plan_fingerprint: z.string().min(1).describe("Plan content fingerprint; mismatch blocks writes until reconciled."),
|
|
1255
|
+
},
|
|
1256
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1257
|
+
}, async (args) => learnerEnrollHandler(deps, tenantId, args));
|
|
1258
|
+
server.registerTool("learner_record", {
|
|
1259
|
+
title: "Record Learning Event",
|
|
1260
|
+
description: LEARNER_RECORD_DESCRIPTION,
|
|
1261
|
+
inputSchema: {
|
|
1262
|
+
learner: z.string().min(1).describe("Learner id."),
|
|
1263
|
+
course: z.string().min(1).describe("Course id."),
|
|
1264
|
+
event: z.object({
|
|
1265
|
+
id: z.string().min(1).describe("Event id (lowercase slug, unique per enrollment)."),
|
|
1266
|
+
date: z.string().describe("ISO date YYYY-MM-DD, never in the future."),
|
|
1267
|
+
covered: z.array(z.string()).describe("Concept ids covered (subject.concept)."),
|
|
1268
|
+
attempts: z.array(z.object({
|
|
1269
|
+
concept: z.string().describe("Concept id attempted."),
|
|
1270
|
+
task: z.string().min(1).describe("The task given."),
|
|
1271
|
+
response: z.string().min(1).describe("Short excerpt of the learner's response."),
|
|
1272
|
+
result: z.enum(["correct", "partial", "incorrect"]),
|
|
1273
|
+
help: z.enum(["none", "hint", "worked-example"]),
|
|
1274
|
+
kind: z.enum(["application", "retrieval", "transfer"]),
|
|
1275
|
+
})).describe("Per-concept attempts (may be empty for an untested lesson)."),
|
|
1276
|
+
interpretation: z.string().describe("Tentative explanation of the evidence (may be empty)."),
|
|
1277
|
+
next_step: z.string().min(1).describe("What the next teacher should try or ask."),
|
|
1278
|
+
}).describe("The learning event to record."),
|
|
1279
|
+
},
|
|
1280
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1281
|
+
}, async (args) => learnerRecordHandler(deps, tenantId, args));
|
|
1282
|
+
server.registerTool("learner_summary", {
|
|
1283
|
+
title: "Summarize Learner Progress",
|
|
1284
|
+
description: LEARNER_SUMMARY_DESCRIPTION,
|
|
1285
|
+
inputSchema: {
|
|
1286
|
+
learner: z.string().min(1).describe("Learner id."),
|
|
1287
|
+
course: z.string().optional().describe("Course id. Omit only when exactly one active enrollment exists."),
|
|
1288
|
+
limit: z.number().int().min(1).max(200).optional().describe("Max concepts to list (default 50)."),
|
|
1289
|
+
},
|
|
1290
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1291
|
+
}, async (args) => learnerSummaryHandler(deps, tenantId, args));
|
|
1292
|
+
server.registerTool("learner_complete_course", {
|
|
1293
|
+
title: "Complete Learner Course",
|
|
1294
|
+
description: LEARNER_COMPLETE_DESCRIPTION,
|
|
1295
|
+
inputSchema: {
|
|
1296
|
+
learner: z.string().min(1).describe("Learner id."),
|
|
1297
|
+
course: z.string().min(1).describe("Course id."),
|
|
1298
|
+
},
|
|
1299
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
1300
|
+
}, async (args) => learnerCompleteHandler(deps, tenantId, args));
|
|
1301
|
+
}
|
|
979
1302
|
return server;
|
|
980
1303
|
}
|
|
981
1304
|
const RECALL_CODE_DESCRIPTION = `\
|