hippo-memory 1.59.0 → 1.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/ablation.js +9 -27
- package/dist/agent-memories/apply.js +4 -1
- package/dist/agent-memories/claude-code.js +1 -1
- package/dist/agent-memories/codex.js +1 -1
- package/dist/agent-memories/gemini.js +1 -1
- package/dist/agent-memories/legacy.js +1 -1
- package/dist/agent-memories/source.js +1 -1
- package/dist/agent-memories/sync.js +8 -3
- package/dist/ambient-store.d.ts +14 -0
- package/dist/ambient-store.js +90 -0
- package/dist/ambient.d.ts +23 -0
- package/dist/ambient.js +72 -50
- package/dist/api/assemble.d.ts +93 -0
- package/dist/api/assemble.js +152 -0
- package/dist/api/audit.d.ts +17 -0
- package/dist/api/audit.js +23 -0
- package/dist/api/auth.d.ts +79 -0
- package/dist/api/auth.js +178 -0
- package/dist/api/context-types.d.ts +105 -0
- package/dist/api/context-types.js +3 -0
- package/dist/api/context.d.ts +31 -0
- package/dist/api/context.js +705 -0
- package/dist/api/dormant.d.ts +30 -0
- package/dist/api/dormant.js +140 -0
- package/dist/api/drill-down.d.ts +84 -0
- package/dist/api/drill-down.js +123 -0
- package/dist/api/forget.d.ts +57 -0
- package/dist/api/forget.js +87 -0
- package/dist/api/goals.d.ts +18 -0
- package/dist/api/goals.js +33 -0
- package/dist/api/learn.d.ts +31 -0
- package/dist/api/learn.js +88 -0
- package/dist/api/outcome.d.ts +61 -0
- package/dist/api/outcome.js +66 -0
- package/dist/api/promote.d.ts +54 -0
- package/dist/api/promote.js +203 -0
- package/dist/api/quarantine.d.ts +24 -0
- package/dist/api/quarantine.js +121 -0
- package/dist/api/recall-types.d.ts +390 -0
- package/dist/api/recall-types.js +3 -0
- package/dist/api/recall.d.ts +36 -0
- package/dist/api/recall.js +634 -0
- package/dist/api/remember.d.ts +35 -0
- package/dist/api/remember.js +43 -0
- package/dist/api/sleep.d.ts +136 -0
- package/dist/api/sleep.js +271 -0
- package/dist/api/tokens.d.ts +26 -0
- package/dist/api/tokens.js +60 -0
- package/dist/api/types.d.ts +56 -0
- package/dist/api/types.js +38 -0
- package/dist/api.d.ts +20 -1260
- package/dist/api.js +25 -2711
- package/dist/audit.d.ts +3 -0
- package/dist/audit.js +7 -3
- package/dist/auth.d.ts +45 -4
- package/dist/auth.js +125 -48
- package/dist/autolearn.js +7 -4
- package/dist/capture/command.d.ts +33 -0
- package/dist/capture/command.js +264 -0
- package/dist/capture/compact.d.ts +44 -0
- package/dist/capture/compact.js +354 -0
- package/dist/capture/extract.d.ts +21 -0
- package/dist/capture/extract.js +464 -0
- package/dist/capture/transcript.d.ts +40 -0
- package/dist/capture/transcript.js +193 -0
- package/dist/capture-error.js +2 -1
- package/dist/churn-git.js +4 -2
- package/dist/cli/audit.d.ts +3 -0
- package/dist/cli/audit.js +159 -0
- package/dist/cli/auth.d.ts +2 -0
- package/dist/cli/auth.js +171 -0
- package/dist/cli/briefs.d.ts +4 -0
- package/dist/cli/briefs.js +435 -0
- package/dist/cli/card.d.ts +3 -0
- package/dist/cli/card.js +333 -0
- package/dist/cli/context.d.ts +15 -0
- package/dist/cli/context.js +366 -0
- package/dist/cli/continuity.d.ts +6 -0
- package/dist/cli/continuity.js +445 -0
- package/dist/cli/curate.d.ts +19 -0
- package/dist/cli/curate.js +555 -0
- package/dist/cli/dag.d.ts +5 -0
- package/dist/cli/dag.js +177 -0
- package/dist/cli/decisions.d.ts +4 -0
- package/dist/cli/decisions.js +528 -0
- package/dist/cli/eval.d.ts +5 -0
- package/dist/cli/eval.js +213 -0
- package/dist/cli/explain.d.ts +4 -0
- package/dist/cli/explain.js +150 -0
- package/dist/cli/goals.d.ts +2 -0
- package/dist/cli/goals.js +196 -0
- package/dist/cli/hook-blocks.d.ts +18 -0
- package/dist/cli/hook-blocks.js +233 -0
- package/dist/cli/init.d.ts +2 -0
- package/dist/cli/init.js +305 -0
- package/dist/cli/maintenance.d.ts +4 -0
- package/dist/cli/maintenance.js +179 -0
- package/dist/cli/playbooks.d.ts +4 -0
- package/dist/cli/playbooks.js +556 -0
- package/dist/cli/projects.js +1 -1
- package/dist/cli/recall.d.ts +7 -0
- package/dist/cli/recall.js +597 -0
- package/dist/cli/remember.d.ts +5 -0
- package/dist/cli/remember.js +442 -0
- package/dist/cli/serve.d.ts +5 -0
- package/dist/cli/serve.js +40 -0
- package/dist/cli/session-hooks.d.ts +29 -0
- package/dist/cli/session-hooks.js +637 -0
- package/dist/cli/setup.d.ts +4 -0
- package/dist/cli/setup.js +376 -0
- package/dist/cli/shared.d.ts +15 -19
- package/dist/cli/shared.js +50 -341
- package/dist/cli/slack.d.ts +2 -0
- package/dist/cli/slack.js +171 -0
- package/dist/cli/status.d.ts +18 -0
- package/dist/cli/status.js +400 -0
- package/dist/cli/transfer.d.ts +10 -0
- package/dist/cli/transfer.js +438 -0
- package/dist/cli/usage.d.ts +85 -0
- package/dist/cli/usage.js +741 -0
- package/dist/cli.d.ts +319 -13
- package/dist/cli.js +436 -8580
- package/dist/client.js +15 -8
- package/dist/compaction-record.js +12 -9
- package/dist/config.js +12 -11
- package/dist/connectors/github/backfill.js +94 -87
- package/dist/connectors/github/cli-impl.js +5 -3
- package/dist/connectors/github/dlq.js +67 -54
- package/dist/connectors/github/ingest.js +34 -35
- package/dist/connectors/github/tenant-routing.js +3 -2
- package/dist/connectors/github/webhook.js +135 -216
- package/dist/connectors/slack/dlq.js +49 -61
- package/dist/connectors/slack/ingest.js +55 -48
- package/dist/connectors/slack/tenant-routing.js +4 -3
- package/dist/connectors/slack/webhook.js +72 -74
- package/dist/consolidate/conflicts.d.ts +10 -0
- package/dist/consolidate/conflicts.js +178 -0
- package/dist/consolidate/decay.d.ts +12 -0
- package/dist/consolidate/decay.js +145 -0
- package/dist/consolidate/llm-passes.d.ts +3 -0
- package/dist/consolidate/llm-passes.js +141 -0
- package/dist/consolidate/merge.d.ts +7 -0
- package/dist/consolidate/merge.js +251 -0
- package/dist/consolidate/physics-pass.d.ts +3 -0
- package/dist/consolidate/physics-pass.js +60 -0
- package/dist/consolidate/run.d.ts +69 -0
- package/dist/consolidate/run.js +76 -0
- package/dist/consolidate/sleep.d.ts +18 -0
- package/dist/consolidate/sleep.js +209 -0
- package/dist/consolidate/traces.d.ts +4 -0
- package/dist/consolidate/traces.js +178 -0
- package/dist/context-auto.js +7 -11
- package/dist/context-render.d.ts +1 -1
- package/dist/context-render.js +1 -1
- package/dist/customer-notes.d.ts +3 -0
- package/dist/customer-notes.js +6 -4
- package/dist/dag.js +13 -12
- package/dist/dashboard-actions.d.ts +20 -0
- package/dist/dashboard-actions.js +88 -0
- package/dist/dashboard-params.d.ts +45 -0
- package/dist/dashboard-params.js +127 -0
- package/dist/dashboard-queries.d.ts +15 -0
- package/dist/dashboard-queries.js +355 -0
- package/dist/dashboard-snapshot.d.ts +138 -0
- package/dist/dashboard-snapshot.js +308 -0
- package/dist/dashboard-types.d.ts +163 -0
- package/dist/dashboard-types.js +3 -0
- package/dist/dashboard.d.ts +6 -7
- package/dist/dashboard.js +231 -203
- package/dist/db/busy.d.ts +5 -0
- package/dist/db/busy.js +24 -0
- package/dist/db/continuity.d.ts +5 -0
- package/dist/db/continuity.js +145 -0
- package/dist/db/meta.d.ts +8 -0
- package/dist/db/meta.js +35 -0
- package/dist/db/migrate.d.ts +9 -0
- package/dist/db/migrate.js +138 -0
- package/dist/db/migrations/index.d.ts +5 -0
- package/dist/db/migrations/index.js +109 -0
- package/dist/db/migrations/types.d.ts +14 -0
- package/dist/db/migrations/types.js +2 -0
- package/dist/db/migrations/v01.d.ts +3 -0
- package/dist/db/migrations/v01.js +38 -0
- package/dist/db/migrations/v02.d.ts +3 -0
- package/dist/db/migrations/v02.js +21 -0
- package/dist/db/migrations/v03.d.ts +3 -0
- package/dist/db/migrations/v03.js +22 -0
- package/dist/db/migrations/v04.d.ts +3 -0
- package/dist/db/migrations/v04.js +28 -0
- package/dist/db/migrations/v05.d.ts +3 -0
- package/dist/db/migrations/v05.js +21 -0
- package/dist/db/migrations/v06.d.ts +3 -0
- package/dist/db/migrations/v06.js +25 -0
- package/dist/db/migrations/v07.d.ts +3 -0
- package/dist/db/migrations/v07.js +13 -0
- package/dist/db/migrations/v08.d.ts +3 -0
- package/dist/db/migrations/v08.js +8 -0
- package/dist/db/migrations/v09.d.ts +3 -0
- package/dist/db/migrations/v09.js +13 -0
- package/dist/db/migrations/v10.d.ts +3 -0
- package/dist/db/migrations/v10.js +17 -0
- package/dist/db/migrations/v11.d.ts +3 -0
- package/dist/db/migrations/v11.js +15 -0
- package/dist/db/migrations/v12.d.ts +3 -0
- package/dist/db/migrations/v12.js +11 -0
- package/dist/db/migrations/v13.d.ts +3 -0
- package/dist/db/migrations/v13.js +15 -0
- package/dist/db/migrations/v14.d.ts +3 -0
- package/dist/db/migrations/v14.js +66 -0
- package/dist/db/migrations/v15.d.ts +3 -0
- package/dist/db/migrations/v15.js +42 -0
- package/dist/db/migrations/v16.d.ts +3 -0
- package/dist/db/migrations/v16.js +61 -0
- package/dist/db/migrations/v17.d.ts +3 -0
- package/dist/db/migrations/v17.js +46 -0
- package/dist/db/migrations/v18.d.ts +3 -0
- package/dist/db/migrations/v18.js +60 -0
- package/dist/db/migrations/v19.d.ts +3 -0
- package/dist/db/migrations/v19.js +28 -0
- package/dist/db/migrations/v20.d.ts +3 -0
- package/dist/db/migrations/v20.js +42 -0
- package/dist/db/migrations/v21.d.ts +3 -0
- package/dist/db/migrations/v21.js +16 -0
- package/dist/db/migrations/v22.d.ts +3 -0
- package/dist/db/migrations/v22.js +82 -0
- package/dist/db/migrations/v23.d.ts +3 -0
- package/dist/db/migrations/v23.js +48 -0
- package/dist/db/migrations/v24.d.ts +3 -0
- package/dist/db/migrations/v24.js +72 -0
- package/dist/db/migrations/v25.d.ts +3 -0
- package/dist/db/migrations/v25.js +46 -0
- package/dist/db/migrations/v26.d.ts +3 -0
- package/dist/db/migrations/v26.js +23 -0
- package/dist/db/migrations/v27.d.ts +3 -0
- package/dist/db/migrations/v27.js +57 -0
- package/dist/db/migrations/v28.d.ts +3 -0
- package/dist/db/migrations/v28.js +38 -0
- package/dist/db/migrations/v29.d.ts +3 -0
- package/dist/db/migrations/v29.js +78 -0
- package/dist/db/migrations/v30.d.ts +3 -0
- package/dist/db/migrations/v30.js +92 -0
- package/dist/db/migrations/v31.d.ts +3 -0
- package/dist/db/migrations/v31.js +74 -0
- package/dist/db/migrations/v32.d.ts +3 -0
- package/dist/db/migrations/v32.js +102 -0
- package/dist/db/migrations/v33.d.ts +3 -0
- package/dist/db/migrations/v33.js +104 -0
- package/dist/db/migrations/v34.d.ts +3 -0
- package/dist/db/migrations/v34.js +93 -0
- package/dist/db/migrations/v35.d.ts +3 -0
- package/dist/db/migrations/v35.js +98 -0
- package/dist/db/migrations/v36.d.ts +3 -0
- package/dist/db/migrations/v36.js +98 -0
- package/dist/db/migrations/v37.d.ts +3 -0
- package/dist/db/migrations/v37.js +219 -0
- package/dist/db/migrations/v38.d.ts +3 -0
- package/dist/db/migrations/v38.js +277 -0
- package/dist/db/migrations/v39.d.ts +3 -0
- package/dist/db/migrations/v39.js +59 -0
- package/dist/db/migrations/v40.d.ts +3 -0
- package/dist/db/migrations/v40.js +74 -0
- package/dist/db/migrations/v41.d.ts +3 -0
- package/dist/db/migrations/v41.js +43 -0
- package/dist/db/migrations/v42.d.ts +3 -0
- package/dist/db/migrations/v42.js +41 -0
- package/dist/db/migrations/v43.d.ts +3 -0
- package/dist/db/migrations/v43.js +67 -0
- package/dist/db/migrations/v44.d.ts +3 -0
- package/dist/db/migrations/v44.js +28 -0
- package/dist/db/migrations/v45.d.ts +3 -0
- package/dist/db/migrations/v45.js +30 -0
- package/dist/db/migrations/v46.d.ts +3 -0
- package/dist/db/migrations/v46.js +25 -0
- package/dist/db/migrations/v47.d.ts +3 -0
- package/dist/db/migrations/v47.js +17 -0
- package/dist/db/migrations/v48.d.ts +3 -0
- package/dist/db/migrations/v48.js +10 -0
- package/dist/db/migrations/v49.d.ts +3 -0
- package/dist/db/migrations/v49.js +31 -0
- package/dist/db/migrations/v50.d.ts +3 -0
- package/dist/db/migrations/v50.js +67 -0
- package/dist/db/migrations/v51.d.ts +3 -0
- package/dist/db/migrations/v51.js +14 -0
- package/dist/db/migrations/v52.d.ts +3 -0
- package/dist/db/migrations/v52.js +7 -0
- package/dist/db/open.d.ts +23 -0
- package/dist/db/open.js +146 -0
- package/dist/db/sqlite.d.ts +21 -0
- package/dist/db/sqlite.js +8 -0
- package/dist/db/tables.d.ts +7 -0
- package/dist/db/tables.js +38 -0
- package/dist/db.d.ts +6 -46
- package/dist/db.js +5 -3036
- package/dist/decisions.d.ts +4 -1
- package/dist/decisions.js +9 -7
- package/dist/dedupe.js +3 -2
- package/dist/delivery-recorder.js +5 -1
- package/dist/doctor.js +4 -3
- package/dist/dormant.js +1 -0
- package/dist/embedding-provider.d.ts +1 -1
- package/dist/embedding-provider.js +7 -5
- package/dist/embeddings.d.ts +9 -52
- package/dist/embeddings.js +47 -298
- package/dist/env.d.ts +75 -0
- package/dist/env.js +119 -0
- package/dist/eval-suite.js +1 -1
- package/dist/eval.js +2 -2
- package/dist/extract.js +5 -4
- package/dist/gated-write.js +3 -1
- package/dist/goals.d.ts +3 -1
- package/dist/goals.js +18 -0
- package/dist/graph/read.d.ts +73 -0
- package/dist/graph/read.js +325 -0
- package/dist/graph/rows.d.ts +45 -0
- package/dist/graph/rows.js +51 -0
- package/dist/graph/types.d.ts +83 -0
- package/dist/graph/types.js +11 -0
- package/dist/graph/write.d.ts +93 -0
- package/dist/{graph.js → graph/write.js} +8 -391
- package/dist/graph-extract.js +2 -1
- package/dist/graph-recall.d.ts +1 -1
- package/dist/graph-recall.js +2 -2
- package/dist/graph-stream.js +1 -1
- package/dist/graph-view.d.ts +1 -1
- package/dist/graph-view.js +1 -1
- package/dist/half-life-migration.d.ts +1 -1
- package/dist/half-life-migration.js +2 -1
- package/dist/handoff.js +3 -0
- package/dist/hooks/codex-session.d.ts +8 -0
- package/dist/hooks/codex-session.js +76 -0
- package/dist/hooks/codex-wrapper.d.ts +55 -0
- package/dist/hooks/codex-wrapper.js +288 -0
- package/dist/hooks/json-hooks.d.ts +63 -0
- package/dist/hooks/json-hooks.js +356 -0
- package/dist/hooks/opencode.d.ts +50 -0
- package/dist/hooks/opencode.js +202 -0
- package/dist/hooks/shared.d.ts +54 -0
- package/dist/hooks/shared.js +77 -0
- package/dist/http-retry.d.ts +2 -0
- package/dist/http-retry.js +4 -3
- package/dist/http-util.d.ts +3 -0
- package/dist/http-util.js +10 -0
- package/dist/{importers.d.ts → importers/core.d.ts} +13 -17
- package/dist/importers/core.js +141 -0
- package/dist/importers/markdown-parse.d.ts +41 -0
- package/dist/importers/markdown-parse.js +132 -0
- package/dist/importers/markdown.d.ts +3 -0
- package/dist/importers/markdown.js +92 -0
- package/dist/importers/sources.d.ts +7 -0
- package/dist/importers/sources.js +229 -0
- package/dist/importers/vault.d.ts +11 -0
- package/dist/importers/vault.js +352 -0
- package/dist/incidents.d.ts +3 -0
- package/dist/incidents.js +8 -5
- package/dist/index.d.ts +25 -6
- package/dist/index.js +23 -6
- package/dist/invalidation.js +2 -1
- package/dist/judgment.js +7 -3
- package/dist/keyset.d.ts +13 -0
- package/dist/keyset.js +8 -0
- package/dist/local-embedding.d.ts +13 -0
- package/dist/local-embedding.js +165 -0
- package/dist/log.d.ts +7 -0
- package/dist/log.js +19 -1
- package/dist/mcp/admin-tools.d.ts +8 -0
- package/dist/mcp/admin-tools.js +116 -0
- package/dist/mcp/format.d.ts +27 -0
- package/dist/mcp/format.js +135 -0
- package/dist/mcp/memory-tools.d.ts +5 -0
- package/dist/mcp/memory-tools.js +83 -0
- package/dist/mcp/protocol.d.ts +83 -0
- package/dist/mcp/protocol.js +55 -0
- package/dist/mcp/recall-tools.d.ts +6 -0
- package/dist/mcp/recall-tools.js +320 -0
- package/dist/mcp/request.d.ts +10 -0
- package/dist/mcp/request.js +163 -0
- package/dist/mcp/server.d.ts +4 -70
- package/dist/mcp/server.js +8 -1172
- package/dist/mcp/session-state.d.ts +11 -0
- package/dist/mcp/session-state.js +28 -0
- package/dist/mcp/stdio.d.ts +8 -0
- package/dist/mcp/stdio.js +79 -0
- package/dist/mcp/tools.d.ts +11 -0
- package/dist/mcp/tools.js +247 -0
- package/dist/memory.d.ts +8 -3
- package/dist/memory.js +27 -1
- package/dist/multihop.d.ts +1 -1
- package/dist/multihop.js +2 -1
- package/dist/owner-validation.js +2 -1
- package/dist/physics-state.js +10 -7
- package/dist/policies.d.ts +3 -0
- package/dist/policies.js +8 -6
- package/dist/postinstall.js +3 -2
- package/dist/predictions/planning-fallacy.d.ts +100 -0
- package/dist/predictions/planning-fallacy.js +190 -0
- package/dist/{predictions.d.ts → predictions/store.d.ts} +7 -102
- package/dist/predictions/store.js +434 -0
- package/dist/processes.d.ts +3 -0
- package/dist/processes.js +8 -5
- package/dist/project-briefs.d.ts +3 -0
- package/dist/project-briefs.js +7 -5
- package/dist/project-identity.d.ts +1 -1
- package/dist/project-identity.js +12 -6
- package/dist/project-merge.js +3 -1
- package/dist/quarantine.d.ts +2 -1
- package/dist/quarantine.js +8 -5
- package/dist/raw-archive-mirror-cleanup.js +2 -1
- package/dist/raw-archive.js +1 -1
- package/dist/recall-history.js +3 -2
- package/dist/recall-pipeline.d.ts +2 -2
- package/dist/recall-pipeline.js +16 -8
- package/dist/recall-scope.js +1 -1
- package/dist/recall-trace.d.ts +3 -3
- package/dist/recall-trace.js +10 -14
- package/dist/refine-llm.js +20 -11
- package/dist/reject-flow.js +6 -1
- package/dist/rerankers/clef.d.ts +2 -2
- package/dist/rerankers/clef.js +72 -30
- package/dist/rerankers/cross-encoder.js +6 -4
- package/dist/rerankers/jev.d.ts +4 -2
- package/dist/rerankers/jev.js +24 -19
- package/dist/rerankers/llm.d.ts +4 -2
- package/dist/rerankers/llm.js +61 -41
- package/dist/rerankers/types.d.ts +1 -1
- package/dist/salience.js +1 -1
- package/dist/same-text.d.ts +2 -0
- package/dist/same-text.js +4 -0
- package/dist/scheduler.d.ts +1 -0
- package/dist/scheduler.js +26 -3
- package/dist/scope.js +4 -3
- package/dist/search/as-of.d.ts +10 -0
- package/dist/search/as-of.js +22 -0
- package/dist/search/bm25-search.d.ts +14 -0
- package/dist/search/bm25-search.js +43 -0
- package/dist/search/bm25.d.ts +15 -0
- package/dist/search/bm25.js +54 -0
- package/dist/search/boosts.d.ts +54 -0
- package/dist/search/boosts.js +94 -0
- package/dist/search/breakdown.d.ts +7 -0
- package/dist/search/breakdown.js +20 -0
- package/dist/search/explain.d.ts +25 -0
- package/dist/search/explain.js +31 -0
- package/dist/search/finalize.d.ts +8 -0
- package/dist/search/finalize.js +52 -0
- package/dist/search/fusion.d.ts +27 -0
- package/dist/search/fusion.js +42 -0
- package/dist/search/hybrid-score.d.ts +20 -0
- package/dist/search/hybrid-score.js +73 -0
- package/dist/search/hybrid.d.ts +46 -0
- package/dist/search/hybrid.js +64 -0
- package/dist/search/physics-search.d.ts +29 -0
- package/dist/search/physics-search.js +162 -0
- package/dist/search/rerank.d.ts +10 -0
- package/dist/search/rerank.js +72 -0
- package/dist/search/temporal.d.ts +15 -0
- package/dist/search/temporal.js +45 -0
- package/dist/search/types.d.ts +91 -0
- package/dist/search/types.js +2 -0
- package/dist/search/vector.d.ts +30 -0
- package/dist/search/vector.js +71 -0
- package/dist/secret-detect.d.ts +2 -0
- package/dist/secret-detect.js +3 -1
- package/dist/server/auth.d.ts +52 -0
- package/dist/server/auth.js +221 -0
- package/dist/server/client-ip.d.ts +25 -0
- package/dist/server/client-ip.js +92 -0
- package/dist/server/cursor.d.ts +23 -0
- package/dist/server/cursor.js +58 -0
- package/dist/server/lifecycle.d.ts +7 -0
- package/dist/server/lifecycle.js +29 -0
- package/dist/server/mcp-http.d.ts +5 -0
- package/dist/server/mcp-http.js +199 -0
- package/dist/server/request.d.ts +33 -0
- package/dist/server/request.js +103 -0
- package/dist/server/routes/admin.d.ts +9 -0
- package/dist/server/routes/admin.js +158 -0
- package/dist/server/routes/customer-notes.d.ts +7 -0
- package/dist/server/routes/customer-notes.js +112 -0
- package/dist/server/routes/decisions.d.ts +7 -0
- package/dist/server/routes/decisions.js +133 -0
- package/dist/server/routes/incidents.d.ts +7 -0
- package/dist/server/routes/incidents.js +126 -0
- package/dist/server/routes/memories.d.ts +10 -0
- package/dist/server/routes/memories.js +179 -0
- package/dist/server/routes/policies.d.ts +8 -0
- package/dist/server/routes/policies.js +151 -0
- package/dist/server/routes/predictions.d.ts +7 -0
- package/dist/server/routes/predictions.js +164 -0
- package/dist/server/routes/processes.d.ts +7 -0
- package/dist/server/routes/processes.js +161 -0
- package/dist/server/routes/project-briefs.d.ts +8 -0
- package/dist/server/routes/project-briefs.js +136 -0
- package/dist/server/routes/recall.d.ts +8 -0
- package/dist/server/routes/recall.js +340 -0
- package/dist/server/routes/skills.d.ts +8 -0
- package/dist/server/routes/skills.js +150 -0
- package/dist/server/types.d.ts +56 -0
- package/dist/server/types.js +2 -0
- package/dist/server/validation.d.ts +14 -0
- package/dist/server/validation.js +91 -0
- package/dist/server.d.ts +7 -61
- package/dist/server.js +92 -2368
- package/dist/session-digest.d.ts +1 -1
- package/dist/session-digest.js +9 -2
- package/dist/shared.d.ts +3 -1
- package/dist/shared.js +46 -40
- package/dist/skills.d.ts +3 -0
- package/dist/skills.js +7 -5
- package/dist/stdin.js +3 -2
- package/dist/store/audit-event.d.ts +19 -0
- package/dist/store/audit-event.js +33 -0
- package/dist/store/candidates.d.ts +42 -0
- package/dist/store/candidates.js +152 -0
- package/dist/store/conflicts.d.ts +44 -0
- package/dist/store/conflicts.js +444 -0
- package/dist/store/delete-and-batch.d.ts +63 -0
- package/dist/store/delete-and-batch.js +310 -0
- package/dist/store/entry-reads.d.ts +92 -0
- package/dist/store/entry-reads.js +255 -0
- package/dist/store/entry-row.d.ts +67 -0
- package/dist/store/entry-row.js +208 -0
- package/dist/store/entry-writes.d.ts +46 -0
- package/dist/store/entry-writes.js +148 -0
- package/dist/store/handoffs.d.ts +26 -0
- package/dist/store/handoffs.js +184 -0
- package/dist/store/index-and-stats.d.ts +52 -0
- package/dist/store/index-and-stats.js +214 -0
- package/dist/store/markdown.d.ts +10 -0
- package/dist/store/markdown.js +108 -0
- package/dist/store/mirrors.d.ts +49 -0
- package/dist/store/mirrors.js +312 -0
- package/dist/store/open.d.ts +15 -0
- package/dist/store/open.js +223 -0
- package/dist/store/rows.d.ts +178 -0
- package/dist/store/rows.js +158 -0
- package/dist/store/search-rows.d.ts +86 -0
- package/dist/store/search-rows.js +254 -0
- package/dist/store/sessions.d.ts +83 -0
- package/dist/store/sessions.js +272 -0
- package/dist/store/summaries.d.ts +94 -0
- package/dist/store/summaries.js +377 -0
- package/dist/store/tenant-lookup.d.ts +12 -0
- package/dist/store/tenant-lookup.js +16 -0
- package/dist/store-cards.js +2 -1
- package/dist/summary-dirty.d.ts +4 -0
- package/dist/summary-dirty.js +32 -0
- package/dist/support-bundle.js +4 -3
- package/dist/tenant.js +2 -2
- package/dist/token-ledger.js +1 -0
- package/dist/tokenize.d.ts +2 -0
- package/dist/tokenize.js +16 -0
- package/dist/transcript-tail.d.ts +7 -0
- package/dist/transcript-tail.js +48 -0
- package/dist/vector-store.d.ts +27 -0
- package/dist/vector-store.js +210 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/dist/working-memory.js +1 -1
- package/dist/yaml.js +36 -11
- package/dist-ui/assets/ibm-plex-mono-latin-400-normal-CvHOgSBP.woff +0 -0
- package/dist-ui/assets/ibm-plex-mono-latin-400-normal-DMJ8VG8y.woff2 +0 -0
- package/dist-ui/assets/ibm-plex-mono-latin-500-normal-CB9ihrfo.woff +0 -0
- package/dist-ui/assets/ibm-plex-mono-latin-500-normal-DSY6xOcd.woff2 +0 -0
- package/dist-ui/assets/ibm-plex-mono-latin-600-normal-BgSNZQsw.woff2 +0 -0
- package/dist-ui/assets/ibm-plex-mono-latin-600-normal-DWFSQ4vo.woff +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CDDApCn2.woff2 +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-400-normal-CYLoc0-x.woff +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-500-normal-6ng42L7E.woff2 +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-500-normal-BgVn5rGT.woff +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-600-normal-Cu4Hd6ag.woff +0 -0
- package/dist-ui/assets/ibm-plex-sans-latin-600-normal-CuJfVYMP.woff2 +0 -0
- package/dist-ui/assets/index-DPN7cP19.js +33 -0
- package/dist-ui/assets/index-dFloKRVr.css +1 -0
- package/dist-ui/index.html +3 -25
- package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
- package/extensions/openclaw-plugin/package.json +1 -1
- package/openclaw.plugin.json +1 -1
- package/package.json +2 -1
- package/dist/capture.d.ts +0 -155
- package/dist/capture.js +0 -1293
- package/dist/consolidate.d.ts +0 -58
- package/dist/consolidate.js +0 -1123
- package/dist/graph.d.ts +0 -245
- package/dist/hooks.d.ts +0 -208
- package/dist/hooks.js +0 -1073
- package/dist/importers.js +0 -897
- package/dist/predictions.js +0 -620
- package/dist/search.d.ts +0 -320
- package/dist/search.js +0 -968
- package/dist/store.d.ts +0 -744
- package/dist/store.js +0 -3372
- package/dist-ui/assets/d3-BiWEKnn4.js +0 -1
- package/dist-ui/assets/index-BhT8RvO6.js +0 -61
- package/dist-ui/assets/index-RoXXJ5dq.css +0 -1
- package/dist-ui/assets/three-BDgTxR1l.js +0 -4112
|
@@ -0,0 +1,705 @@
|
|
|
1
|
+
// Ambient context injection: the admission policy and getContext.
|
|
2
|
+
import { openHippoDb, closeHippoDb } from '../db.js';
|
|
3
|
+
import { isInitialized } from '../store/open.js';
|
|
4
|
+
import { strengthenRetrieved } from '../store/entry-writes.js';
|
|
5
|
+
import { loadRecallSearchEntries, recallScopeFilter } from '../store/search-rows.js';
|
|
6
|
+
import { loadAmbientCandidates, loadContextCandidates, } from '../store/candidates.js';
|
|
7
|
+
import { loadIndex, saveIndex, updateStats } from '../store/index-and-stats.js';
|
|
8
|
+
import { loadFreshActiveTaskSnapshot, listSessionEvents, SNAPSHOT_AMBIENT_MAX_AGE_MS } from '../store/sessions.js';
|
|
9
|
+
import { loadLatestHandoff } from '../store/handoffs.js';
|
|
10
|
+
import { estimateTokens } from '../token-ledger.js';
|
|
11
|
+
import { calculateStrength, markRetrieved, COMPACTION_MEMORY_TAG } from '../memory.js';
|
|
12
|
+
import { appendAuditEvent, auditQueryFields, isContentWorthStoring } from '../audit.js';
|
|
13
|
+
import { getGlobalRoot, searchBothHybrid } from '../shared.js';
|
|
14
|
+
import { writeRecallTraceAtRoot } from '../recall-trace.js';
|
|
15
|
+
import { evalNow, isRecallBoostAblated } from '../ablation.js';
|
|
16
|
+
import { hybridSearch } from '../search/hybrid.js';
|
|
17
|
+
import { physicsSearch } from '../search/physics-search.js';
|
|
18
|
+
import { compareScoredResults } from '../compare.js';
|
|
19
|
+
import { dropHeldCopies } from '../same-text.js';
|
|
20
|
+
import { scopeMatch } from '../scope.js';
|
|
21
|
+
import { loadConfig } from '../config.js';
|
|
22
|
+
import { resolveProjectIdentity, classifyOriginProject, isGlobalStoreRoot } from '../project-identity.js';
|
|
23
|
+
import { promptTokens, contentTokens, gatePromptRecall, } from '../prompt-recall.js';
|
|
24
|
+
import { detectSecret } from '../secret-detect.js';
|
|
25
|
+
import { isSessionDigestRow } from '../session-digest.js';
|
|
26
|
+
import { addAmbientTallies, ambientStateFromTallies } from '../ambient.js';
|
|
27
|
+
import { loadAmbientTallies } from '../ambient-store.js';
|
|
28
|
+
import { passesScopeFilterForRecall, assertScopeRequestAllowed } from '../recall-scope.js';
|
|
29
|
+
/**
|
|
30
|
+
* v39: the single ambient-injection admission policy, shared by getContext
|
|
31
|
+
* and the CLI-side ambient-state summary so the two cannot drift.
|
|
32
|
+
*
|
|
33
|
+
* - S4 secret veto is UNCONDITIONAL: neither crossProject nor
|
|
34
|
+
* contextProjectIsolation:false re-includes secrets. A flagged row only
|
|
35
|
+
* injects inside its owning project; flagged rows with no project origin
|
|
36
|
+
* (''/null) never ambient-inject at all. Explicit recall is unaffected -
|
|
37
|
+
* recalling a secret is a deliberate act.
|
|
38
|
+
* - S2 envelope parity: private/quarantine scopes never inject unless `exactScope` names one.
|
|
39
|
+
* - S3 origin partition: other-project rows are excluded unless
|
|
40
|
+
* `includeCrossProject`.
|
|
41
|
+
*/
|
|
42
|
+
function ambientAdmitEntry(e, currentProjectName, includeCrossProject, exactScope) {
|
|
43
|
+
if (!ambientSecretAdmit(e, currentProjectName))
|
|
44
|
+
return false;
|
|
45
|
+
if (!passesScopeFilterForRecall(e.scope ?? null, exactScope))
|
|
46
|
+
return false;
|
|
47
|
+
if (includeCrossProject)
|
|
48
|
+
return true;
|
|
49
|
+
return classifyOriginProject(e.origin_project, currentProjectName) !== 'cross-project';
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* v39 S4: the secret half of the ambient policy on its own, for callers
|
|
53
|
+
* that apply their own scope rule. A flagged row is only admitted inside its owning project;
|
|
54
|
+
* flagged rows with no project origin never ambient-inject.
|
|
55
|
+
*/
|
|
56
|
+
export function ambientSecretAdmit(e, currentProjectName) {
|
|
57
|
+
if (!detectSecret(e).flagged)
|
|
58
|
+
return true;
|
|
59
|
+
const origin = e.origin_project;
|
|
60
|
+
if (origin === undefined || origin === null || origin === '')
|
|
61
|
+
return false;
|
|
62
|
+
return origin === currentProjectName;
|
|
63
|
+
}
|
|
64
|
+
/** Most rows per store a no-query context reads; past it, ranking and ambientState see the strongest by decay. */
|
|
65
|
+
export const CONTEXT_CANDIDATE_CAP = 2000;
|
|
66
|
+
// The pinned-only branch needs pins and recent-N candidates, not the corpus; `recall` applies there only.
|
|
67
|
+
function loadAmbientEntries(hippoRoot, tenantId, pinnedOnly, includeRecent, admit, window, recall, onQualityDrop) {
|
|
68
|
+
if (!pinnedOnly) {
|
|
69
|
+
const rows = 'query' in window
|
|
70
|
+
? loadRecallSearchEntries(hippoRoot, window.query, CONTEXT_CANDIDATE_CAP, tenantId, window.exactScope, 'exact', false)
|
|
71
|
+
: loadContextCandidates(hippoRoot, tenantId, window);
|
|
72
|
+
return { entries: rows.filter(admit) };
|
|
73
|
+
}
|
|
74
|
+
// DF3's quality floor runs on the recent-N slice AFTER this load, so the load
|
|
75
|
+
// counts by it too, or it stops short of a store whose newest rows are junk.
|
|
76
|
+
const admitAmbient = (e) => {
|
|
77
|
+
if (!admit(e))
|
|
78
|
+
return false;
|
|
79
|
+
if (e.pinned || isContentWorthStoring(e.content))
|
|
80
|
+
return true;
|
|
81
|
+
onQualityDrop?.(e);
|
|
82
|
+
return false;
|
|
83
|
+
};
|
|
84
|
+
return loadAmbientCandidates(hippoRoot, tenantId, includeRecent, admitAmbient, recall);
|
|
85
|
+
}
|
|
86
|
+
// Share and promote copy a memory to the global store under a new id, so equal content is the only link.
|
|
87
|
+
// A pinned copy wins, then the stronger one after the ranking's own global discount; a tie keeps the local copy.
|
|
88
|
+
export function oneCopyPerMemory(local, global, now) {
|
|
89
|
+
const score = (e, isGlobal) => calculateStrength(e, now) * (isGlobal ? 1 / 1.2 : 1);
|
|
90
|
+
const best = new Map();
|
|
91
|
+
const offer = (entry, isGlobal) => {
|
|
92
|
+
const held = best.get(entry.content);
|
|
93
|
+
const wins = !held || (held.entry.pinned !== entry.pinned
|
|
94
|
+
? entry.pinned
|
|
95
|
+
: score(entry, isGlobal) > score(held.entry, held.isGlobal));
|
|
96
|
+
if (wins)
|
|
97
|
+
best.set(entry.content, { entry, isGlobal });
|
|
98
|
+
};
|
|
99
|
+
for (const e of local)
|
|
100
|
+
offer(e, false);
|
|
101
|
+
for (const e of global)
|
|
102
|
+
offer(e, true);
|
|
103
|
+
const kept = new Set([...best.values()].map((b) => b.entry));
|
|
104
|
+
return [local.filter((e) => kept.has(e)), global.filter((e) => kept.has(e))];
|
|
105
|
+
}
|
|
106
|
+
const finiteOr = (v, dflt, min) => Number.isFinite(v) && v >= min ? v : dflt;
|
|
107
|
+
/**
|
|
108
|
+
* Assemble a context bundle: recalled memories (pinned-only / strength-sorted
|
|
109
|
+
* fallback / hybrid search) + active task snapshot + session handoff + recent
|
|
110
|
+
* session events. Budget-bounded, tenant-scoped. Mutates `last_retrieval_ids`
|
|
111
|
+
* + emits a 'recall' audit row for non-pinned, non-'*' queries.
|
|
112
|
+
*
|
|
113
|
+
* Behaves like the pre-extraction `cmdContext` data-loading + selection
|
|
114
|
+
* pipeline. CLI presentation (markdown / json / additional-context rendering)
|
|
115
|
+
* stays in `cli.ts`.
|
|
116
|
+
*
|
|
117
|
+
* Tenant scope: all `loadAllEntries` / snapshot / handoff / events reads use
|
|
118
|
+
* `ctx.tenantId`. Cross-tenant rows are filtered out.
|
|
119
|
+
*
|
|
120
|
+
* Returns an empty result (`entries: []`, snapshot/handoff/events undefined)
|
|
121
|
+
* when there's nothing to surface (no memories AND no snapshot AND no handoff
|
|
122
|
+
* AND no recent events).
|
|
123
|
+
*/
|
|
124
|
+
export async function getContext(ctx, opts = {}) {
|
|
125
|
+
const pinnedOnly = opts.pinnedOnly === true;
|
|
126
|
+
const budget = opts.budget ?? 1500;
|
|
127
|
+
const limit = opts.limit ?? Number.POSITIVE_INFINITY;
|
|
128
|
+
const includeRecent = opts.includeRecent ?? 0;
|
|
129
|
+
const activeScope = opts.scope ?? '';
|
|
130
|
+
assertScopeRequestAllowed(ctx.actor, opts.exactScope);
|
|
131
|
+
const exactScope = opts.exactScope || undefined;
|
|
132
|
+
if (budget <= 0) {
|
|
133
|
+
return { entries: [], tokens: 0 };
|
|
134
|
+
}
|
|
135
|
+
// Global memories do not establish a project boundary for task state.
|
|
136
|
+
const hasLocal = isInitialized(ctx.hippoRoot);
|
|
137
|
+
const query = (opts.q ?? '').trim() || '*';
|
|
138
|
+
const globalRoot = getGlobalRoot();
|
|
139
|
+
const hasGlobal = isInitialized(globalRoot);
|
|
140
|
+
const primaryIsGlobal = isGlobalStoreRoot(ctx.hippoRoot);
|
|
141
|
+
const hasLocalTaskState = hasLocal && !primaryIsGlobal;
|
|
142
|
+
// v39 memory scope isolation (docs/plans/2026-07-01-memory-scope-isolation.md).
|
|
143
|
+
// S2: envelope-filter parity with api.recall; opts.scope is only the tag boost, opts.exactScope the envelope request.
|
|
144
|
+
// S3: origin partition - other-project memories are excluded unless the
|
|
145
|
+
// caller explicitly asks for them (crossProject) or isolation is disabled.
|
|
146
|
+
const config = loadConfig(ctx.hippoRoot);
|
|
147
|
+
const isolationEnabled = config.contextProjectIsolation !== false;
|
|
148
|
+
const currentProjectName = opts.currentProject ?? resolveProjectIdentity(process.cwd()).name;
|
|
149
|
+
const includeCrossProject = opts.crossProject === true || !isolationEnabled;
|
|
150
|
+
// Z1: decided before the ambient loads so the FTS candidate query below (pinned-only
|
|
151
|
+
// branch) can piggyback on that connection instead of opening its own.
|
|
152
|
+
const promptRecallPending = pinnedOnly && Boolean(opts.prompt?.trim()) && config.pinnedInject.promptRecall === true;
|
|
153
|
+
const promptRecallTerms = promptRecallPending && config.pinnedInject.enabled
|
|
154
|
+
? Array.from(promptTokens(opts.prompt ?? ''))
|
|
155
|
+
: [];
|
|
156
|
+
const recallRequest = promptRecallTerms.length > 0
|
|
157
|
+
? { terms: promptRecallTerms, limit: Math.floor(finiteOr(config.pinnedInject.promptRecallCandidates, 100, 1)) }
|
|
158
|
+
: undefined;
|
|
159
|
+
const cost = opts.cost;
|
|
160
|
+
const price = (entry, isGlobal, promptRecall) => cost
|
|
161
|
+
? cost.entry({ entry, isGlobal, promptRecall, origin: entry.origin_project ?? null, category: classifyOriginProject(entry.origin_project, currentProjectName) })
|
|
162
|
+
: estimateTokens(entry.content);
|
|
163
|
+
const blockBudget = pinnedOnly && opts.budget === undefined ? config.pinnedInject.budget : budget;
|
|
164
|
+
const obs = opts.deliveryObserver;
|
|
165
|
+
obs?.facts({ projectName: currentProjectName, budgetTokens: blockBudget, promptRecall: promptRecallPending });
|
|
166
|
+
if (pinnedOnly && !config.pinnedInject.enabled)
|
|
167
|
+
obs?.disabled();
|
|
168
|
+
let left = cost
|
|
169
|
+
? Math.max(0, blockBudget - cost.fixed(blockBudget, { cross: includeCrossProject, promptRecall: promptRecallPending, ambient: !pinnedOnly && config.ambient.enabled }))
|
|
170
|
+
: blockBudget;
|
|
171
|
+
// Sections print ahead of the memories, so they are paid first; one that does not fit is dropped, as an oversize entry is.
|
|
172
|
+
const pays = (tokens) => {
|
|
173
|
+
if (tokens > left)
|
|
174
|
+
return false;
|
|
175
|
+
left -= tokens;
|
|
176
|
+
return true;
|
|
177
|
+
};
|
|
178
|
+
// DF1 T2: bounded read — an orphaned snapshot (no later pre-compact
|
|
179
|
+
// superseded it, no session-end closed it) must age out of this ambient
|
|
180
|
+
// surface instead of injecting into every future prompt forever. Owner
|
|
181
|
+
// reads (opts.currentSessionId matches the snapshot's session_id) stay
|
|
182
|
+
// unbounded; see loadFreshActiveTaskSnapshot's own doc comment for the
|
|
183
|
+
// exact null/empty-id matching rules.
|
|
184
|
+
const rowScope = (r) => r?.scope ?? null;
|
|
185
|
+
const rawActiveSnapshot = hasLocalTaskState
|
|
186
|
+
? loadFreshActiveTaskSnapshot(ctx.hippoRoot, ctx.tenantId, {
|
|
187
|
+
sessionId: opts.currentSessionId,
|
|
188
|
+
})
|
|
189
|
+
: null;
|
|
190
|
+
// W1: the same envelope rule ambientAdmitEntry applies to memory rows.
|
|
191
|
+
const activeSnapshot = rawActiveSnapshot && passesScopeFilterForRecall(rowScope(rawActiveSnapshot), exactScope)
|
|
192
|
+
? rawActiveSnapshot
|
|
193
|
+
: null;
|
|
194
|
+
// Key on the RAW snapshot: a scope-hidden active session must not fall through to another session's ambient handoff.
|
|
195
|
+
const rawSessionHandoff = !hasLocalTaskState
|
|
196
|
+
? null
|
|
197
|
+
: rawActiveSnapshot?.session_id
|
|
198
|
+
? loadLatestHandoff(ctx.hippoRoot, ctx.tenantId, rawActiveSnapshot.session_id)
|
|
199
|
+
: loadLatestHandoff(ctx.hippoRoot, ctx.tenantId, undefined, {
|
|
200
|
+
unfinishedOnly: true,
|
|
201
|
+
maxAgeMs: SNAPSHOT_AMBIENT_MAX_AGE_MS,
|
|
202
|
+
// codex P2: admit scope in SQL so a newer denied row can't hide an older eligible one before LIMIT 1.
|
|
203
|
+
scopeFilter: 'default-deny',
|
|
204
|
+
});
|
|
205
|
+
const sessionHandoff = rawSessionHandoff && passesScopeFilterForRecall(rowScope(rawSessionHandoff), exactScope)
|
|
206
|
+
? rawSessionHandoff
|
|
207
|
+
: null;
|
|
208
|
+
// Raw session id here too: each event is admitted on its own scope, same as recall and the CLI.
|
|
209
|
+
const recentSessionEvents = hasLocalTaskState && rawActiveSnapshot?.session_id
|
|
210
|
+
? listSessionEvents(ctx.hippoRoot, ctx.tenantId, {
|
|
211
|
+
session_id: rawActiveSnapshot.session_id,
|
|
212
|
+
limit: 5,
|
|
213
|
+
}).filter((e) => passesScopeFilterForRecall(rowScope(e), exactScope))
|
|
214
|
+
: [];
|
|
215
|
+
const shownSnapshot = activeSnapshot && (!cost || pays(cost.snapshot(activeSnapshot))) ? activeSnapshot : null;
|
|
216
|
+
const shownHandoff = sessionHandoff && (!cost || pays(cost.handoff(sessionHandoff))) ? sessionHandoff : null;
|
|
217
|
+
const shownEvents = recentSessionEvents.length > 0 && (!cost || pays(cost.trail(recentSessionEvents))) ? recentSessionEvents : [];
|
|
218
|
+
obs?.sections(Number(shownSnapshot !== null) + Number(shownHandoff !== null) + Number(shownEvents.length > 0), Number(activeSnapshot !== shownSnapshot) + Number(sessionHandoff !== shownHandoff) + Number(recentSessionEvents.length !== shownEvents.length));
|
|
219
|
+
const transcriptHandoffSession = shownHandoff?.evidence?.derivedFrom === 'transcript' ? shownHandoff.sessionId : null;
|
|
220
|
+
let digestHiddenForHandoff = false;
|
|
221
|
+
const ambientAdmit = (e) => {
|
|
222
|
+
// A printed handoff already carries the session's closing message, which its digest would print a second time.
|
|
223
|
+
if (transcriptHandoffSession !== null && e.source_session_id === transcriptHandoffSession && isSessionDigestRow(e)) {
|
|
224
|
+
digestHiddenForHandoff = true;
|
|
225
|
+
return false;
|
|
226
|
+
}
|
|
227
|
+
return ambientAdmitEntry(e, currentProjectName, includeCrossProject, exactScope);
|
|
228
|
+
};
|
|
229
|
+
const ownSessionId = opts.currentSessionId || '';
|
|
230
|
+
// Inside admit, not after the load, so the loader's window widens past a session's own items.
|
|
231
|
+
const isOwnCompactionItem = (e) => ownSessionId !== '' &&
|
|
232
|
+
e.source_session_id === ownSessionId &&
|
|
233
|
+
e.tags.includes(COMPACTION_MEMORY_TAG);
|
|
234
|
+
// Superseded rows never inject; which rows reach ambientAdmitEntry matters because it regex-scans content for secrets.
|
|
235
|
+
const admit = (e) => !e.superseded_by && !isOwnCompactionItem(e) && ambientAdmit(e);
|
|
236
|
+
const loadAdmit = obs ? obs.watchAdmit(admit) : admit;
|
|
237
|
+
const qualityDrop = (isGlobal) => obs && !promptRecallPending ? (e) => obs.qualityDropped(e, isGlobal) : undefined;
|
|
238
|
+
// The window's predicates are ones admit applies anyway, so below the cap the admitted rows are unchanged.
|
|
239
|
+
const searchesLocalRows = query !== '*' && !(hasGlobal && !primaryIsGlobal);
|
|
240
|
+
const originProject = includeCrossProject || currentProjectName === '' ? undefined : currentProjectName;
|
|
241
|
+
const window = searchesLocalRows && !pinnedOnly
|
|
242
|
+
? { query, exactScope }
|
|
243
|
+
: {
|
|
244
|
+
exactScope,
|
|
245
|
+
project: originProject,
|
|
246
|
+
cap: CONTEXT_CANDIDATE_CAP,
|
|
247
|
+
now: evalNow(),
|
|
248
|
+
};
|
|
249
|
+
// Tenant-scoped loads (v1.11.1 lesson: NEVER resolveTenantId({}) here).
|
|
250
|
+
const localLoad = hasLocal
|
|
251
|
+
? loadAmbientEntries(ctx.hippoRoot, ctx.tenantId, pinnedOnly, includeRecent, loadAdmit, window, recallRequest, qualityDrop(primaryIsGlobal))
|
|
252
|
+
: { entries: [] };
|
|
253
|
+
const globalLoad = hasGlobal && !primaryIsGlobal
|
|
254
|
+
? loadAmbientEntries(globalRoot, ctx.tenantId, pinnedOnly, includeRecent, loadAdmit, window, recallRequest, qualityDrop(true))
|
|
255
|
+
: { entries: [] };
|
|
256
|
+
let localEntries = localLoad.entries;
|
|
257
|
+
let globalEntries = globalLoad.entries;
|
|
258
|
+
// Computed after markRetrieved runs, so avgStrength reflects post-retrieval strengths.
|
|
259
|
+
let ambientState;
|
|
260
|
+
if (!promptRecallPending &&
|
|
261
|
+
localEntries.length === 0 &&
|
|
262
|
+
globalEntries.length === 0 &&
|
|
263
|
+
!shownSnapshot &&
|
|
264
|
+
!shownHandoff &&
|
|
265
|
+
shownEvents.length === 0) {
|
|
266
|
+
return { entries: [], tokens: 0 };
|
|
267
|
+
}
|
|
268
|
+
let selectedItems = [];
|
|
269
|
+
let totalTokens = 0;
|
|
270
|
+
if (pinnedOnly) {
|
|
271
|
+
// loadConfig is safe even when local isn't initialised — returns defaults.
|
|
272
|
+
const pinnedCfg = loadConfig(ctx.hippoRoot);
|
|
273
|
+
if (!pinnedCfg.pinnedInject.enabled) {
|
|
274
|
+
return { entries: [], tokens: 0 };
|
|
275
|
+
}
|
|
276
|
+
// Effective budget: explicit opts.budget wins over config, less what the sections took.
|
|
277
|
+
const effBudget = left;
|
|
278
|
+
const nowP = evalNow(); // honors HIPPO_FAKE_NOW (eval-only; see ablation.ts)
|
|
279
|
+
obs?.offer(localEntries, primaryIsGlobal);
|
|
280
|
+
obs?.offer(globalEntries, true);
|
|
281
|
+
const [localPool, globalPool] = oneCopyPerMemory(localEntries, globalEntries, nowP);
|
|
282
|
+
obs?.dropMissing([...localEntries, ...globalEntries], [...localPool, ...globalPool], 'load', 'duplicate');
|
|
283
|
+
const selectedIds = new Set();
|
|
284
|
+
let usedP = 0;
|
|
285
|
+
// Pinned entries are explicit user intent, the recent-N list an automatic
|
|
286
|
+
// backfill. Both loops share ONE budget and the recent loop runs first, so
|
|
287
|
+
// pins are ranked here and reserve their share before it can spend.
|
|
288
|
+
const pinnedLocal = localPool.filter((e) => e.pinned);
|
|
289
|
+
const pinnedGlobal = globalPool.filter((e) => e.pinned);
|
|
290
|
+
const rankedPinned = [
|
|
291
|
+
...pinnedLocal.map((e) => ({ entry: e, isGlobal: primaryIsGlobal })),
|
|
292
|
+
...pinnedGlobal.map((e) => ({ entry: e, isGlobal: true })),
|
|
293
|
+
]
|
|
294
|
+
.map(({ entry, isGlobal }) => {
|
|
295
|
+
const scopeSig = scopeMatch(entry.tags, activeScope);
|
|
296
|
+
const sBst = scopeSig === 1 ? 1.5 : scopeSig === -1 ? 0.5 : 1.0;
|
|
297
|
+
return {
|
|
298
|
+
entry,
|
|
299
|
+
score: calculateStrength(entry, nowP) * (isGlobal ? 1 / 1.2 : 1) * sBst,
|
|
300
|
+
tokens: price(entry, isGlobal),
|
|
301
|
+
isGlobal,
|
|
302
|
+
};
|
|
303
|
+
})
|
|
304
|
+
.sort(compareScoredResults);
|
|
305
|
+
// Mirror the pinned admission loop's own `continue`-not-`break`
|
|
306
|
+
// semantics (further down) so the reserve equals what that loop will
|
|
307
|
+
// actually admit -- a big pin near the front should not block smaller
|
|
308
|
+
// pins behind it from reserving their share too.
|
|
309
|
+
// Dedupe by id: `syncGlobalToLocal` copies global rows into the local
|
|
310
|
+
// store preserving `entry.id`, so a synced pin appears in BOTH
|
|
311
|
+
// `pinnedLocal` and `pinnedGlobal` and would otherwise reserve its cost
|
|
312
|
+
// twice. The admission loop already dedupes via `selectedIds`; the
|
|
313
|
+
// reserve has to mirror that or it silently starves recents of budget a
|
|
314
|
+
// single returned pin never needed.
|
|
315
|
+
let pinnedReserve = 0;
|
|
316
|
+
const reservedIds = new Set();
|
|
317
|
+
for (const r of rankedPinned) {
|
|
318
|
+
if (reservedIds.has(r.entry.id))
|
|
319
|
+
continue;
|
|
320
|
+
if (pinnedReserve + r.tokens <= effBudget) {
|
|
321
|
+
pinnedReserve += r.tokens;
|
|
322
|
+
reservedIds.add(r.entry.id);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
// Known, accepted tradeoff: a pin that also lands in the recent-N slice
|
|
326
|
+
// is counted once in `pinnedReserve` (here) AND admitted again by the
|
|
327
|
+
// recent loop below, so a little budget goes unused (`recentBudget` is
|
|
328
|
+
// more conservative than it needs to be in that case). That only
|
|
329
|
+
// under-fills recents slightly -- it never displaces a pin -- so it is
|
|
330
|
+
// the safe direction and is not worth extra bookkeeping to recover.
|
|
331
|
+
const recentBudget = Math.max(0, effBudget - pinnedReserve);
|
|
332
|
+
// Z1: gate the backfill on the prompt instead of recency (docs/plans/2026-09-26-z1-prompt-recall.md).
|
|
333
|
+
const promptRecallOn = promptRecallPending;
|
|
334
|
+
if (promptRecallOn) {
|
|
335
|
+
const rawMetric = pinnedCfg.pinnedInject.promptRecallMetric;
|
|
336
|
+
const metric = rawMetric === 'cosine' ? 'cosine' : 'jaccard';
|
|
337
|
+
const gate = {
|
|
338
|
+
metric,
|
|
339
|
+
threshold: finiteOr(pinnedCfg.pinnedInject.promptRecallThreshold, 0.04, 0),
|
|
340
|
+
minShared: finiteOr(pinnedCfg.pinnedInject.promptRecallMinShared, 2, 0),
|
|
341
|
+
maxItems: finiteOr(pinnedCfg.pinnedInject.promptRecallMaxItems, 5, 1),
|
|
342
|
+
};
|
|
343
|
+
const p = promptTokens(opts.prompt ?? '');
|
|
344
|
+
if (p.size > 0) {
|
|
345
|
+
// Candidates came off the ambient load's own connection (recallRequest above), not a fresh open.
|
|
346
|
+
// A candidate carrying a pin's text would inject that memory a second time.
|
|
347
|
+
const pinnedText = new Set(rankedPinned.map((r) => r.entry.content));
|
|
348
|
+
const ineligibleReason = (e) => !admit(e) ? 'scope'
|
|
349
|
+
: e.pinned ? 'pinned'
|
|
350
|
+
: !isContentWorthStoring(e.content) ? 'quality'
|
|
351
|
+
: pinnedText.has(e.content) ? 'duplicate'
|
|
352
|
+
: null;
|
|
353
|
+
const eligible = (e) => {
|
|
354
|
+
const why = ineligibleReason(e);
|
|
355
|
+
if (why !== null && why !== 'pinned')
|
|
356
|
+
obs?.reject(e, 'eligible', why);
|
|
357
|
+
return why === null;
|
|
358
|
+
};
|
|
359
|
+
obs?.offer(localLoad.recall ?? [], primaryIsGlobal, 'prompt-recall');
|
|
360
|
+
obs?.offer(globalLoad.recall ?? [], true, 'prompt-recall');
|
|
361
|
+
const localEligible = (localLoad.recall ?? []).filter(eligible);
|
|
362
|
+
const globalEligible = (globalLoad.recall ?? []).filter(eligible);
|
|
363
|
+
const [localCandidates, globalCandidates] = oneCopyPerMemory(localEligible, globalEligible, nowP);
|
|
364
|
+
obs?.dropMissing([...localEligible, ...globalEligible], [...localCandidates, ...globalCandidates], 'eligible', 'duplicate');
|
|
365
|
+
const seenCandidateIds = new Set();
|
|
366
|
+
const candidateItems = [];
|
|
367
|
+
// Local wins the id collision (a global row synced into the local store).
|
|
368
|
+
for (const e of localCandidates) {
|
|
369
|
+
if (seenCandidateIds.has(e.id))
|
|
370
|
+
continue;
|
|
371
|
+
seenCandidateIds.add(e.id);
|
|
372
|
+
candidateItems.push({ id: e.id, tokens: contentTokens(e.content), entry: e, isGlobal: primaryIsGlobal });
|
|
373
|
+
}
|
|
374
|
+
for (const e of globalCandidates) {
|
|
375
|
+
if (seenCandidateIds.has(e.id))
|
|
376
|
+
continue;
|
|
377
|
+
seenCandidateIds.add(e.id);
|
|
378
|
+
candidateItems.push({ id: e.id, tokens: contentTokens(e.content), entry: e, isGlobal: true });
|
|
379
|
+
}
|
|
380
|
+
const gated = gatePromptRecall(p, candidateItems, gate);
|
|
381
|
+
obs?.gated(p, candidateItems, gate, gated);
|
|
382
|
+
for (const g of gated) {
|
|
383
|
+
if (selectedIds.has(g.item.id))
|
|
384
|
+
continue;
|
|
385
|
+
const tokens = price(g.item.entry, g.item.isGlobal, true);
|
|
386
|
+
if (usedP + tokens > recentBudget) {
|
|
387
|
+
obs?.reject(g.item.entry, 'budget', 'budget', g.score, tokens);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
selectedItems.push({ entry: g.item.entry, score: g.score, tokens, isGlobal: g.item.isGlobal, promptRecall: true });
|
|
391
|
+
selectedIds.add(g.item.id);
|
|
392
|
+
usedP += tokens;
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
else if (includeRecent > 0) {
|
|
397
|
+
const recent = [
|
|
398
|
+
...localPool.map((entry) => ({ entry, isGlobal: primaryIsGlobal })),
|
|
399
|
+
...globalPool.map((entry) => ({ entry, isGlobal: true })),
|
|
400
|
+
]
|
|
401
|
+
// T2 (src/compare.ts) note: this already carries an explicit
|
|
402
|
+
// per-instance tiebreak (created desc -> id localeCompare) and is
|
|
403
|
+
// deliberately left as-is rather than routed through
|
|
404
|
+
// compareEntryIdentity. `created` reflects ingest order, so it is
|
|
405
|
+
// cross-ingest stable at ms granularity; the residual is honest,
|
|
406
|
+
// not silently ignored — rows created in the same millisecond fall
|
|
407
|
+
// to `id.localeCompare`, which is per-instance random (id is
|
|
408
|
+
// crypto.randomUUID()), so this listing is per-instance-
|
|
409
|
+
// deterministic but NOT cross-ingest-stable under same-ms
|
|
410
|
+
// collisions.
|
|
411
|
+
.sort((a, b) => {
|
|
412
|
+
const byCreated = Date.parse(b.entry.created) - Date.parse(a.entry.created);
|
|
413
|
+
return byCreated !== 0 ? byCreated : b.entry.id.localeCompare(a.entry.id);
|
|
414
|
+
})
|
|
415
|
+
// DF3 (docs/plans/2026-08-23-df3-include-recent-quality-floor.md):
|
|
416
|
+
// filter before slice, not after — the caller asked for N recent
|
|
417
|
+
// *useful* entries, so a junk row must be skipped and backfilled
|
|
418
|
+
// past, not counted against the N. Skip-only: no mutation, no audit
|
|
419
|
+
// row, nothing becomes unrecoverable.
|
|
420
|
+
//
|
|
421
|
+
// `entry.pinned ||` bypass IS needed here (codex review finding,
|
|
422
|
+
// corrects the earlier claim in this comment that it wasn't): under
|
|
423
|
+
// budget pressure, a pinned entry that fails the heuristic gets
|
|
424
|
+
// dropped from this recent slice, and an unpinned entry backfills
|
|
425
|
+
// into its slot and consumes `usedP` in the loop below. By the time
|
|
426
|
+
// the pinned block runs (further down), the budget it needed is
|
|
427
|
+
// already spent, so it hits `continue` and the pinned entry is
|
|
428
|
+
// omitted entirely — the pinned block is NOT a safety net once the
|
|
429
|
+
// recent loop has already spent the shared budget.
|
|
430
|
+
.filter(({ entry }) => entry.pinned || isContentWorthStoring(entry.content))
|
|
431
|
+
.slice(0, includeRecent)
|
|
432
|
+
.map(({ entry, isGlobal }) => ({
|
|
433
|
+
entry,
|
|
434
|
+
score: calculateStrength(entry, nowP) * (isGlobal ? 1 / 1.2 : 1),
|
|
435
|
+
tokens: price(entry, isGlobal),
|
|
436
|
+
isGlobal,
|
|
437
|
+
}));
|
|
438
|
+
for (const r of recent) {
|
|
439
|
+
if (selectedIds.has(r.entry.id))
|
|
440
|
+
continue;
|
|
441
|
+
if (usedP + r.tokens > recentBudget) {
|
|
442
|
+
obs?.reject(r.entry, 'budget', 'budget', r.score, r.tokens);
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
selectedItems.push(r);
|
|
446
|
+
selectedIds.add(r.entry.id);
|
|
447
|
+
usedP += r.tokens;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
if (pinnedLocal.length === 0 &&
|
|
451
|
+
pinnedGlobal.length === 0 &&
|
|
452
|
+
selectedItems.length === 0 &&
|
|
453
|
+
!digestHiddenForHandoff) {
|
|
454
|
+
return { entries: [], tokens: 0 };
|
|
455
|
+
}
|
|
456
|
+
for (const r of rankedPinned) {
|
|
457
|
+
if (selectedIds.has(r.entry.id))
|
|
458
|
+
continue;
|
|
459
|
+
if (usedP + r.tokens > effBudget) {
|
|
460
|
+
obs?.reject(r.entry, 'budget', 'budget', r.score, r.tokens);
|
|
461
|
+
continue;
|
|
462
|
+
}
|
|
463
|
+
selectedItems.push(r);
|
|
464
|
+
selectedIds.add(r.entry.id);
|
|
465
|
+
usedP += r.tokens;
|
|
466
|
+
}
|
|
467
|
+
totalTokens = usedP;
|
|
468
|
+
}
|
|
469
|
+
else if (query === '*') {
|
|
470
|
+
// No query: return strongest memories by strength, up to budget.
|
|
471
|
+
const now = evalNow(); // honors HIPPO_FAKE_NOW (eval-only; see ablation.ts)
|
|
472
|
+
const [localPool, globalPool] = oneCopyPerMemory(localEntries, globalEntries, now);
|
|
473
|
+
const localRanked = localPool
|
|
474
|
+
.map((e) => ({
|
|
475
|
+
entry: e,
|
|
476
|
+
score: calculateStrength(e, now),
|
|
477
|
+
tokens: price(e, primaryIsGlobal),
|
|
478
|
+
isGlobal: primaryIsGlobal,
|
|
479
|
+
}))
|
|
480
|
+
.sort(compareScoredResults);
|
|
481
|
+
const globalRanked = globalPool
|
|
482
|
+
.map((e) => ({
|
|
483
|
+
entry: e,
|
|
484
|
+
score: calculateStrength(e, now) * (1 / 1.2),
|
|
485
|
+
tokens: price(e, true),
|
|
486
|
+
isGlobal: true,
|
|
487
|
+
}))
|
|
488
|
+
.sort(compareScoredResults);
|
|
489
|
+
const combined = [...localRanked, ...globalRanked].sort(compareScoredResults);
|
|
490
|
+
let used = 0;
|
|
491
|
+
for (const r of combined) {
|
|
492
|
+
if (used + r.tokens > left)
|
|
493
|
+
continue;
|
|
494
|
+
selectedItems.push(r);
|
|
495
|
+
used += r.tokens;
|
|
496
|
+
}
|
|
497
|
+
totalTokens = used;
|
|
498
|
+
}
|
|
499
|
+
else {
|
|
500
|
+
// Real query: hybrid search (global + local) or physics+hybrid (local only).
|
|
501
|
+
let results;
|
|
502
|
+
const minResults = cost ? 0 : undefined; // a priced block skips an oversize top hit too, so the budget bounds it
|
|
503
|
+
if (hasGlobal && !primaryIsGlobal) {
|
|
504
|
+
// searchBothHybrid loads from the store roots itself, so the ambient
|
|
505
|
+
// filter above never saw its candidates. Admission runs INSIDE the
|
|
506
|
+
// search via the opt-in entryFilter, BEFORE ranking, cross-store
|
|
507
|
+
// content-dedupe, and budgeting - a post-filter instead would let an
|
|
508
|
+
// excluded row saturate the budget (codex rounds 1+3) or shadow its
|
|
509
|
+
// admitted duplicate in the dedupe pass (codex round 4). Recall paths
|
|
510
|
+
// never set entryFilter, so their behavior is unchanged.
|
|
511
|
+
const localIndex = loadIndex(ctx.hippoRoot);
|
|
512
|
+
const isGlobalHit = (e) => !localIndex.entries[e.id];
|
|
513
|
+
const merged = await searchBothHybrid(query, ctx.hippoRoot, globalRoot, {
|
|
514
|
+
budget: left,
|
|
515
|
+
minResults,
|
|
516
|
+
cost: cost && ((r) => price(r.entry, isGlobalHit(r.entry))),
|
|
517
|
+
scope: activeScope,
|
|
518
|
+
tenantId: ctx.tenantId,
|
|
519
|
+
entryFilter: ambientAdmit,
|
|
520
|
+
});
|
|
521
|
+
results = merged.map((r) => ({
|
|
522
|
+
entry: r.entry,
|
|
523
|
+
score: r.score,
|
|
524
|
+
tokens: price(r.entry, isGlobalHit(r.entry)),
|
|
525
|
+
isGlobal: isGlobalHit(r.entry),
|
|
526
|
+
}));
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
const ctxConfig = loadConfig(ctx.hippoRoot);
|
|
530
|
+
const usePhysicsCtx = ctxConfig.physics?.enabled !== false;
|
|
531
|
+
const localCost = cost && ((r) => price(r.entry, primaryIsGlobal));
|
|
532
|
+
const vectorCandidates = {
|
|
533
|
+
tenantId: ctx.tenantId, scope: recallScopeFilter(exactScope, 'exact'), includeSuperseded: false, admit,
|
|
534
|
+
};
|
|
535
|
+
const ctxResults = usePhysicsCtx
|
|
536
|
+
? await physicsSearch(query, localEntries, {
|
|
537
|
+
budget: left,
|
|
538
|
+
minResults,
|
|
539
|
+
cost: localCost,
|
|
540
|
+
hippoRoot: ctx.hippoRoot,
|
|
541
|
+
physicsConfig: ctxConfig.physics,
|
|
542
|
+
scope: activeScope,
|
|
543
|
+
vectorCandidates,
|
|
544
|
+
})
|
|
545
|
+
: await hybridSearch(query, localEntries, {
|
|
546
|
+
budget: left,
|
|
547
|
+
minResults,
|
|
548
|
+
cost: localCost,
|
|
549
|
+
hippoRoot: ctx.hippoRoot,
|
|
550
|
+
scope: activeScope,
|
|
551
|
+
vectorCandidates,
|
|
552
|
+
});
|
|
553
|
+
results = ctxResults.map((r) => ({
|
|
554
|
+
entry: r.entry,
|
|
555
|
+
score: r.score,
|
|
556
|
+
tokens: price(r.entry, primaryIsGlobal),
|
|
557
|
+
isGlobal: primaryIsGlobal,
|
|
558
|
+
}));
|
|
559
|
+
}
|
|
560
|
+
selectedItems = results;
|
|
561
|
+
totalTokens = results.reduce((sum, r) => sum + r.tokens, 0);
|
|
562
|
+
// A5 H4: emit recall audit row for context-mode searches (matches the
|
|
563
|
+
// 'recall' op emitted by api.recall for parity). pinnedOnly + '*' fallback
|
|
564
|
+
// never hit the search engines, so they don't emit (matches cmdContext).
|
|
565
|
+
const ctxRecallMetadata = {
|
|
566
|
+
...auditQueryFields(query),
|
|
567
|
+
results: selectedItems.length,
|
|
568
|
+
mode: 'context',
|
|
569
|
+
};
|
|
570
|
+
if (hasLocal) {
|
|
571
|
+
const localDb = openHippoDb(ctx.hippoRoot);
|
|
572
|
+
try {
|
|
573
|
+
appendAuditEvent(localDb, {
|
|
574
|
+
tenantId: ctx.tenantId,
|
|
575
|
+
actor: ctx.actor.subject,
|
|
576
|
+
op: 'recall',
|
|
577
|
+
metadata: ctxRecallMetadata,
|
|
578
|
+
});
|
|
579
|
+
}
|
|
580
|
+
finally {
|
|
581
|
+
closeHippoDb(localDb);
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
if (hasGlobal && !primaryIsGlobal) {
|
|
585
|
+
const globalDb = openHippoDb(globalRoot);
|
|
586
|
+
try {
|
|
587
|
+
appendAuditEvent(globalDb, {
|
|
588
|
+
tenantId: ctx.tenantId,
|
|
589
|
+
actor: ctx.actor.subject,
|
|
590
|
+
op: 'recall',
|
|
591
|
+
metadata: ctxRecallMetadata,
|
|
592
|
+
});
|
|
593
|
+
}
|
|
594
|
+
finally {
|
|
595
|
+
closeHippoDb(globalDb);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
if (limit < selectedItems.length) {
|
|
600
|
+
const cut = selectedItems.slice(0, limit);
|
|
601
|
+
obs?.dropMissing(selectedItems.map((r) => r.entry), cut.map((r) => r.entry), 'limit', 'limit');
|
|
602
|
+
selectedItems = cut;
|
|
603
|
+
}
|
|
604
|
+
const heldDropped = dropHeldCopies(selectedItems, (r) => r.entry); // after the last cut, so a merged row that was cut hides nothing
|
|
605
|
+
obs?.dropMissing(selectedItems.map((r) => r.entry), heldDropped.map((r) => r.entry), 'limit', 'duplicate');
|
|
606
|
+
selectedItems = heldDropped;
|
|
607
|
+
totalTokens = selectedItems.reduce((sum, r) => sum + r.tokens, 0);
|
|
608
|
+
// v39: annotate every returned entry with its origin and how it relates to
|
|
609
|
+
// the active project, so renderers can demarcate cross-project inclusions.
|
|
610
|
+
selectedItems = selectedItems.map((r) => ({
|
|
611
|
+
...r,
|
|
612
|
+
origin: r.entry.origin_project ?? null,
|
|
613
|
+
category: classifyOriginProject(r.entry.origin_project, currentProjectName),
|
|
614
|
+
}));
|
|
615
|
+
obs?.selected(selectedItems);
|
|
616
|
+
if (selectedItems.length === 0 &&
|
|
617
|
+
!shownSnapshot &&
|
|
618
|
+
!shownHandoff &&
|
|
619
|
+
shownEvents.length === 0) {
|
|
620
|
+
// LC1 F5 fix: this bare early-return used to skip tracing entirely — a
|
|
621
|
+
// query that found nothing is exactly the coverage-gap signal Track LC
|
|
622
|
+
// needs. Write an empty trace (result_count 0, no result rows) so it
|
|
623
|
+
// lands in the training corpus. Never touches localIndex/
|
|
624
|
+
// last_retrieval_ids/last_trace_id — by construction it can't desync
|
|
625
|
+
// (mirrors the CLI zero-result path). Skipped under pinnedOnly (hot
|
|
626
|
+
// path stays read-only, same reason it skips markRetrieved). Fail-soft
|
|
627
|
+
// internally; never throws.
|
|
628
|
+
if (!pinnedOnly) {
|
|
629
|
+
// No snapshot in this branch, so the caller's own id is the only session to stamp.
|
|
630
|
+
writeRecallTraceAtRoot(ctx.hippoRoot, {
|
|
631
|
+
tenantId: ctx.tenantId,
|
|
632
|
+
sessionId: opts.currentSessionId || null,
|
|
633
|
+
pipeline: 'context',
|
|
634
|
+
query,
|
|
635
|
+
explainMode: false,
|
|
636
|
+
results: [],
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
return { entries: [], tokens: 0 };
|
|
640
|
+
}
|
|
641
|
+
// pinnedOnly is the UserPromptSubmit hot path — read-only so pinned
|
|
642
|
+
// memories don't inflate retrieval_count or extend half_life by 2 days per
|
|
643
|
+
// turn over a long session.
|
|
644
|
+
if (!pinnedOnly) {
|
|
645
|
+
const toUpdate = selectedItems.map((s) => s.entry);
|
|
646
|
+
const updatedEntries = markRetrieved(toUpdate);
|
|
647
|
+
const localIndex = loadIndex(ctx.hippoRoot);
|
|
648
|
+
const retrievedIds = updatedEntries.map((u) => u.id);
|
|
649
|
+
const strengthenedHere = strengthenRetrieved(ctx.hippoRoot, retrievedIds, { recallBoostAblated: isRecallBoostAblated() });
|
|
650
|
+
if (hasGlobal)
|
|
651
|
+
strengthenRetrieved(globalRoot, retrievedIds.filter((id) => !strengthenedHere.has(id)), { recallBoostAblated: isRecallBoostAblated() });
|
|
652
|
+
localIndex.last_retrieval_ids = retrievedIds;
|
|
653
|
+
// LC1 F1 structural fix (docs/plans/2026-08-02-lc1-recall-trace-persistence.md):
|
|
654
|
+
// write the trace FIRST — post-limit, post-annotation `selectedItems`
|
|
655
|
+
// actually returned, on a fresh short-lived connection (the audit
|
|
656
|
+
// handles above ~2410 are already closed by this point, matching this
|
|
657
|
+
// block's own per-call-handle convention: writeEntry, saveIndex) — then
|
|
658
|
+
// fold the resulting id into `localIndex` so the SAME `saveIndex` call
|
|
659
|
+
// below persists last_retrieval_ids + last_trace_id atomically.
|
|
660
|
+
// LOCKSTEP INVARIANT: last_trace_id must only ever advance together
|
|
661
|
+
// with last_retrieval_ids; a two-connection stamp-then-clear design
|
|
662
|
+
// could desync them on a crash between writes. A failed trace write
|
|
663
|
+
// (traceId null) sets last_trace_id to null rather than leaving the
|
|
664
|
+
// OLD id pointing at ids that are about to be overwritten. Fail-soft
|
|
665
|
+
// internally; never throws.
|
|
666
|
+
const traceId = writeRecallTraceAtRoot(ctx.hippoRoot, {
|
|
667
|
+
tenantId: ctx.tenantId,
|
|
668
|
+
sessionId: opts.currentSessionId || activeSnapshot?.session_id || null,
|
|
669
|
+
pipeline: 'context',
|
|
670
|
+
query,
|
|
671
|
+
explainMode: false,
|
|
672
|
+
results: selectedItems.map((s) => ({
|
|
673
|
+
memoryId: s.entry.id,
|
|
674
|
+
score: s.score,
|
|
675
|
+
})),
|
|
676
|
+
});
|
|
677
|
+
localIndex.last_trace_id = traceId !== null ? String(traceId) : null;
|
|
678
|
+
saveIndex(ctx.hippoRoot, localIndex);
|
|
679
|
+
updateStats(ctx.hippoRoot, { recalled: selectedItems.length });
|
|
680
|
+
// Replace selectedItems entries with markRetrieved-updated copies so
|
|
681
|
+
// the returned ContextResult reflects post-recall state.
|
|
682
|
+
selectedItems = selectedItems.map((s) => ({
|
|
683
|
+
...s,
|
|
684
|
+
entry: updatedEntries.find((u) => u.id === s.entry.id) ?? s.entry,
|
|
685
|
+
}));
|
|
686
|
+
// Read after strengthenRetrieved commits, so the rows just retrieved count at their new strength.
|
|
687
|
+
if (config.ambient.enabled) {
|
|
688
|
+
const filter = { exactScope, project: originProject, currentProject: currentProjectName, now: evalNow() };
|
|
689
|
+
const roots = [...(hasLocal ? [ctx.hippoRoot] : []), ...(hasGlobal && !primaryIsGlobal ? [globalRoot] : [])];
|
|
690
|
+
const tallies = roots.map((root) => loadAmbientTallies(root, ctx.tenantId, filter));
|
|
691
|
+
const total = tallies.length > 0 ? tallies.reduce(addAmbientTallies) : undefined;
|
|
692
|
+
if (total && total.total > 0)
|
|
693
|
+
ambientState = ambientStateFromTallies(total);
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
return {
|
|
697
|
+
entries: selectedItems,
|
|
698
|
+
tokens: totalTokens,
|
|
699
|
+
activeSnapshot: shownSnapshot ?? undefined,
|
|
700
|
+
sessionHandoff: shownHandoff ?? undefined,
|
|
701
|
+
recentEvents: shownEvents.length > 0 ? shownEvents : undefined,
|
|
702
|
+
ambientState,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
//# sourceMappingURL=context.js.map
|