hippo-memory 1.60.0 → 1.62.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 +29 -54
- package/dist/ablation.js +9 -27
- package/dist/agent-memories/apply.d.ts +1 -1
- package/dist/agent-memories/apply.js +4 -1
- package/dist/agent-memories/claude-code.d.ts +3 -1
- package/dist/agent-memories/claude-code.js +54 -10
- 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.d.ts +3 -3
- package/dist/agent-memories/sync.js +22 -9
- package/dist/agent-memories/types.d.ts +0 -2
- 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 +153 -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-select.d.ts +49 -0
- package/dist/api/context-select.js +344 -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 +378 -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 +126 -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 +192 -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 +499 -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 +256 -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 -1262
- package/dist/api.js +25 -2727
- package/dist/audit.d.ts +3 -0
- package/dist/audit.js +6 -3
- package/dist/auth.d.ts +45 -4
- package/dist/auth.js +125 -48
- package/dist/autolearn.js +2 -2
- 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 +453 -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 +376 -0
- package/dist/cli/continuity.d.ts +6 -0
- package/dist/cli/continuity.js +457 -0
- package/dist/cli/curate.d.ts +19 -0
- package/dist/cli/curate.js +556 -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 +156 -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 +190 -0
- package/dist/cli/playbooks.d.ts +4 -0
- package/dist/cli/playbooks.js +573 -0
- package/dist/cli/projects.js +9 -6
- package/dist/cli/recall.d.ts +7 -0
- package/dist/cli/recall.js +582 -0
- package/dist/cli/remember.d.ts +5 -0
- package/dist/cli/remember.js +485 -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 +647 -0
- package/dist/cli/setup.d.ts +4 -0
- package/dist/cli/setup.js +397 -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 +409 -0
- package/dist/cli/transfer.d.ts +10 -0
- package/dist/cli/transfer.js +424 -0
- package/dist/cli/usage.d.ts +85 -0
- package/dist/cli/usage.js +741 -0
- package/dist/cli.d.ts +71 -120
- package/dist/cli.js +170 -8469
- package/dist/client.js +15 -8
- package/dist/compaction-record.d.ts +0 -2
- package/dist/compaction-record.js +7 -5
- package/dist/connectors/github/backfill.js +94 -87
- package/dist/connectors/github/cli-impl.js +4 -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 +83 -72
- package/dist/dag.js +225 -188
- 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 +228 -202
- 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 -3049
- package/dist/decisions.d.ts +4 -1
- package/dist/decisions.js +102 -83
- package/dist/dedupe.js +3 -2
- package/dist/delivery-recorder.js +4 -1
- package/dist/doctor.js +14 -10
- package/dist/embedding-provider.d.ts +1 -1
- package/dist/embedding-provider.js +4 -3
- package/dist/embeddings.d.ts +9 -52
- package/dist/embeddings.js +43 -297
- 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 +1 -1
- package/dist/gated-write.js +3 -1
- package/dist/goals.d.ts +3 -1
- package/dist/goals.js +117 -86
- 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} +6 -384
- 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/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 +52 -43
- package/dist/index.d.ts +25 -6
- package/dist/index.js +23 -6
- package/dist/invalidation.js +2 -1
- package/dist/judgment.js +2 -1
- 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 -75
- package/dist/mcp/server.js +7 -1190
- 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 +3 -0
- 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 +93 -74
- 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 +94 -76
- package/dist/project-briefs.d.ts +3 -0
- package/dist/project-briefs.js +142 -113
- package/dist/project-identity.js +3 -2
- package/dist/project-merge.d.ts +12 -4
- package/dist/project-merge.js +132 -45
- package/dist/quarantine.d.ts +2 -1
- package/dist/quarantine.js +8 -5
- 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 +1 -1
- package/dist/refine-llm.js +2 -1
- package/dist/reject-flow.js +6 -1
- package/dist/rerankers/clef.js +6 -5
- package/dist/rerankers/jev.d.ts +1 -1
- package/dist/rerankers/jev.js +4 -4
- package/dist/rerankers/llm.d.ts +4 -2
- package/dist/rerankers/llm.js +58 -38
- package/dist/rerankers/types.d.ts +1 -1
- package/dist/salience.js +1 -1
- package/dist/scheduler.d.ts +1 -0
- package/dist/scheduler.js +26 -4
- 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 +2 -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 +69 -2362
- package/dist/session-digest.d.ts +1 -1
- package/dist/session-digest.js +9 -2
- package/dist/shared.d.ts +12 -1
- package/dist/shared.js +33 -27
- package/dist/skills.d.ts +3 -0
- package/dist/skills.js +88 -70
- package/dist/stdin.js +2 -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 +260 -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/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 +1 -1
- package/dist/capture.d.ts +0 -155
- package/dist/capture.js +0 -1295
- package/dist/consolidate.d.ts +0 -58
- package/dist/consolidate.js +0 -1124
- package/dist/graph.d.ts +0 -245
- package/dist/hooks.d.ts +0 -208
- package/dist/hooks.js +0 -1076
- package/dist/importers.js +0 -900
- package/dist/predictions.js +0 -620
- package/dist/search.d.ts +0 -320
- package/dist/search.js +0 -970
- package/dist/store.d.ts +0 -776
- package/dist/store.js +0 -3473
- 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
package/dist/audit.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type MemoryEntry } from './memory.js';
|
|
2
2
|
import type { DatabaseSyncLike } from './db.js';
|
|
3
3
|
import type { JsonObject } from './working-memory.js';
|
|
4
|
+
import { type KeysetPosition } from './keyset.js';
|
|
4
5
|
export type AuditSeverity = 'warning' | 'error';
|
|
5
6
|
export interface AuditIssue {
|
|
6
7
|
memoryId: string;
|
|
@@ -42,6 +43,8 @@ export interface QueryAuditOpts {
|
|
|
42
43
|
op?: AuditOp;
|
|
43
44
|
since?: string;
|
|
44
45
|
limit?: number;
|
|
46
|
+
/** Resume after this row: the (ts, id) position the previous page ended on. */
|
|
47
|
+
after?: KeysetPosition;
|
|
45
48
|
}
|
|
46
49
|
export interface AuditEvent {
|
|
47
50
|
id: number;
|
package/dist/audit.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import { canAutoDelete } from './memory.js';
|
|
3
3
|
import { log } from './log.js';
|
|
4
|
+
import { keysetAfter } from './keyset.js';
|
|
4
5
|
export const STOP_WORDS = new Set([
|
|
5
6
|
'the', 'a', 'an', 'is', 'was', 'are', 'were', 'be', 'been', 'being',
|
|
6
7
|
'to', 'of', 'in', 'for', 'on', 'with', 'at', 'by', 'from', 'it',
|
|
@@ -274,11 +275,13 @@ export function queryAuditEvents(db, opts) {
|
|
|
274
275
|
where.push('ts >= ?');
|
|
275
276
|
params.push(opts.since);
|
|
276
277
|
}
|
|
277
|
-
const
|
|
278
|
+
const after = keysetAfter('ts', 'id', opts.after);
|
|
279
|
+
// One past the route's 10000 cap: GET /v1/audit reads a row ahead to tell whether another page exists.
|
|
280
|
+
const limit = Math.max(1, Math.min(opts.limit ?? 100, 10001));
|
|
278
281
|
// SAFETY: AUDIT_COLUMNS names exactly the AuditRow columns, in this order.
|
|
279
282
|
const rows = db
|
|
280
|
-
.prepare(`SELECT ${AUDIT_COLUMNS} FROM audit_log WHERE ${where.join(' AND ')} ORDER BY ts DESC, id DESC LIMIT ?`)
|
|
281
|
-
.all(...params, limit);
|
|
283
|
+
.prepare(`SELECT ${AUDIT_COLUMNS} FROM audit_log WHERE ${where.join(' AND ')}${after.sql} ORDER BY ts DESC, id DESC LIMIT ?`)
|
|
284
|
+
.all(...params, ...after.params, limit);
|
|
282
285
|
return rows.map(rowToAuditEvent);
|
|
283
286
|
}
|
|
284
287
|
/**
|
package/dist/auth.d.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type DatabaseSyncLike } from './db.js';
|
|
2
|
+
import { type KeysetPosition } from './keyset.js';
|
|
2
3
|
/** Every minted API key starts with this, so the server can route a bearer token by shape. */
|
|
3
4
|
export declare const API_KEY_PREFIX = "hk_";
|
|
4
|
-
|
|
5
|
+
/** Counts since process start, so a flood of bad tokens shows as scrypt work, not just as 401s. */
|
|
6
|
+
export interface ApiKeyVerifyStats {
|
|
7
|
+
readonly storeLookups: number;
|
|
8
|
+
readonly scryptRuns: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function apiKeyVerifyStats(): ApiKeyVerifyStats;
|
|
5
11
|
export interface CreateApiKeyOpts {
|
|
6
12
|
tenantId: string;
|
|
7
13
|
label?: string;
|
|
@@ -22,7 +28,29 @@ export interface ValidateResult {
|
|
|
22
28
|
/** EI2: scope grants for this key. Present only when valid=true. */
|
|
23
29
|
scopes?: string[];
|
|
24
30
|
}
|
|
31
|
+
/** The identity a verified API key carries. */
|
|
32
|
+
export interface VerifiedApiKey {
|
|
33
|
+
tenantId: string;
|
|
34
|
+
keyId: string;
|
|
35
|
+
role: 'admin' | 'member';
|
|
36
|
+
scopes: string[];
|
|
37
|
+
}
|
|
25
38
|
export declare function validateApiKey(db: DatabaseSyncLike, plaintext: string): ValidateResult;
|
|
39
|
+
/** How long a verified key is trusted without a store read; also the ceiling on a revoke made by another process. */
|
|
40
|
+
export declare const VERIFIED_KEY_TTL_MS = 60000;
|
|
41
|
+
/** LRU of verified keys with an absolute TTL. Holds a SHA-256 of the token, never the token. */
|
|
42
|
+
export declare class VerifiedKeyCache {
|
|
43
|
+
private readonly capacity;
|
|
44
|
+
private readonly ttlMs;
|
|
45
|
+
private readonly entries;
|
|
46
|
+
constructor(capacity: number, ttlMs: number);
|
|
47
|
+
get size(): number;
|
|
48
|
+
get(hippoRoot: string, keyId: string, plaintext: string, now: number): VerifiedApiKey | undefined;
|
|
49
|
+
set(hippoRoot: string, keyId: string, plaintext: string, key: VerifiedApiKey, now: number): void;
|
|
50
|
+
delete(keyId: string): void;
|
|
51
|
+
}
|
|
52
|
+
/** Verify a bearer API key for `hippoRoot`; a cache hit skips both scrypt and the DB open. Null when invalid. */
|
|
53
|
+
export declare function verifyApiKeyCached(hippoRoot: string, plaintext: string): VerifiedApiKey | null;
|
|
26
54
|
export declare function revokeApiKey(db: DatabaseSyncLike, keyId: string): void;
|
|
27
55
|
/** EI2: grant `keyId` read access to one restricted `scope`. Idempotent. */
|
|
28
56
|
export declare function grantScope(db: DatabaseSyncLike, keyId: string, scope: string): void;
|
|
@@ -45,7 +73,20 @@ export interface ApiKeyListItem {
|
|
|
45
73
|
/** EI2: restricted scopes this key may read. */
|
|
46
74
|
scopes: string[];
|
|
47
75
|
}
|
|
48
|
-
export
|
|
76
|
+
export interface ListApiKeysOpts {
|
|
49
77
|
active: boolean;
|
|
50
|
-
|
|
78
|
+
/** Only this tenant's keys; omit for every tenant (the CLI's single-tenant view). */
|
|
79
|
+
tenantId?: string;
|
|
80
|
+
/** Resume after this row: the position the previous page ended on (key and id are both the row id). */
|
|
81
|
+
after?: KeysetPosition;
|
|
82
|
+
limit?: number;
|
|
83
|
+
}
|
|
84
|
+
/** A listed key plus its row id, the paging key the list item itself does not expose. */
|
|
85
|
+
export interface ApiKeyListRow {
|
|
86
|
+
rowId: number;
|
|
87
|
+
key: ApiKeyListItem;
|
|
88
|
+
}
|
|
89
|
+
/** Keys newest first, with their row ids, filtered and limited in SQL. */
|
|
90
|
+
export declare function listApiKeyRows(db: DatabaseSyncLike, opts: ListApiKeysOpts): ApiKeyListRow[];
|
|
91
|
+
export declare function listApiKeys(db: DatabaseSyncLike, opts: ListApiKeysOpts): ApiKeyListItem[];
|
|
51
92
|
//# sourceMappingURL=auth.d.ts.map
|
package/dist/auth.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
|
|
1
|
+
import { createHash, randomBytes, scryptSync, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { openHippoDb, closeHippoDb } from './db.js';
|
|
3
|
+
import { keysetAfter } from './keyset.js';
|
|
2
4
|
/** Every minted API key starts with this, so the server can route a bearer token by shape. */
|
|
3
5
|
export const API_KEY_PREFIX = 'hk_';
|
|
4
6
|
const ID_LEN = 24; // base32 chars after prefix
|
|
5
7
|
const SECRET_LEN = 32; // base32 chars after dot
|
|
6
8
|
const SCRYPT_KEYLEN = 32;
|
|
7
9
|
const BASE32 = 'abcdefghijklmnopqrstuvwxyz234567';
|
|
10
|
+
const MINTED_KEY_PATTERN = new RegExp(`^${API_KEY_PREFIX}[a-z2-7]{${ID_LEN}}\\.[a-z2-7]{${SECRET_LEN}}$`);
|
|
8
11
|
function randBase32(n) {
|
|
9
12
|
const bytes = randomBytes(n);
|
|
10
13
|
let out = '';
|
|
@@ -18,18 +21,9 @@ function hashKey(plaintext) {
|
|
|
18
21
|
const hash = scryptSync(plaintext, salt, SCRYPT_KEYLEN);
|
|
19
22
|
return `scrypt$${salt.toString('hex')}$${hash.toString('hex')}`;
|
|
20
23
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
// itself can still leak via cache effects — v0.40 follow-up: request-level
|
|
25
|
-
// rate limit on /v1/* to bound key-id enumeration. Stored format identical to
|
|
26
|
-
// real hashes: scrypt$saltHex$hashHex.
|
|
27
|
-
const DUMMY_PLAINTEXT = 'hk_dummy_constant_padding_for_timing.dummy_secret_padding_for_timing_x';
|
|
28
|
-
// Precomputed `hashKey(DUMMY_PLAINTEXT)`: computing it at module load put one scrypt on every CLI start.
|
|
29
|
-
const DUMMY_HASH = 'scrypt$5b2117156f1ad78738fd8b6a5bace454$92e098fa28003b7c38ba8f1451bd8618260a4a3c5a64b87cc2b471567c0960e7';
|
|
30
|
-
// Not a secret (it's padding, not a real key hash): exposed only so a test can pin its shape.
|
|
31
|
-
export function _dummyHashForTests() {
|
|
32
|
-
return DUMMY_HASH;
|
|
24
|
+
const verifyStats = { storeLookups: 0, scryptRuns: 0 };
|
|
25
|
+
export function apiKeyVerifyStats() {
|
|
26
|
+
return { ...verifyStats };
|
|
33
27
|
}
|
|
34
28
|
function verifyKey(plaintext, stored) {
|
|
35
29
|
const parts = stored.split('$');
|
|
@@ -37,6 +31,7 @@ function verifyKey(plaintext, stored) {
|
|
|
37
31
|
return false;
|
|
38
32
|
const salt = Buffer.from(parts[1], 'hex');
|
|
39
33
|
const expected = Buffer.from(parts[2], 'hex');
|
|
34
|
+
verifyStats.scryptRuns++;
|
|
40
35
|
const actual = scryptSync(plaintext, salt, expected.length);
|
|
41
36
|
return expected.length === actual.length && timingSafeEqual(expected, actual);
|
|
42
37
|
}
|
|
@@ -52,49 +47,115 @@ export function createApiKey(db, opts) {
|
|
|
52
47
|
db.prepare(`INSERT INTO api_keys (key_id, key_hash, tenant_id, label, created_at, role) VALUES (?, ?, ?, ?, ?, ?)`).run(keyId, hash, opts.tenantId, opts.label ?? null, new Date().toISOString(), opts.role ?? 'admin');
|
|
53
48
|
return { keyId, plaintext };
|
|
54
49
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
verifyKey(DUMMY_PLAINTEXT, DUMMY_HASH);
|
|
61
|
-
return { valid: false };
|
|
62
|
-
}
|
|
63
|
-
const keyId = plaintext.slice(0, dot);
|
|
50
|
+
/** One full check against the store: shape, row, revocation, then scrypt. Null for any failure. */
|
|
51
|
+
function lookupApiKey(db, plaintext) {
|
|
52
|
+
if (!MINTED_KEY_PATTERN.test(plaintext))
|
|
53
|
+
return null;
|
|
54
|
+
const keyId = plaintext.slice(0, API_KEY_PREFIX.length + ID_LEN);
|
|
64
55
|
// SAFETY: row comes from the SELECT above, which projects exactly
|
|
65
56
|
// key_hash, tenant_id, revoked_at, role; `.get` returns undefined when no
|
|
66
57
|
// row matches key_id.
|
|
67
58
|
const row = db
|
|
68
59
|
.prepare(`SELECT key_hash, tenant_id, revoked_at, role FROM api_keys WHERE key_id = ?`)
|
|
69
60
|
.get(keyId);
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
//
|
|
74
|
-
const target = (row && !row.revoked_at) ? row.key_hash : DUMMY_HASH;
|
|
75
|
-
const matches = verifyKey(plaintext, target);
|
|
76
|
-
if (!row || row.revoked_at || !matches)
|
|
77
|
-
return { valid: false };
|
|
78
|
-
// v1.12.0: fail-safe to least privilege — only 'admin' is admitted as admin,
|
|
79
|
-
// anything else (including future schema drift, manual DB tampering inserting
|
|
80
|
-
// 'superuser', or a NULL slipped past the NOT NULL constraint) downgrades to
|
|
81
|
-
// 'member'. The migration constrains to 'admin' DEFAULT, but defense-in-depth.
|
|
61
|
+
// Ids are 120 random bits and not secret, so padding the miss path with scrypt hid nothing and let junk tokens burn CPU.
|
|
62
|
+
if (!row || row.revoked_at || !verifyKey(plaintext, row.key_hash))
|
|
63
|
+
return null;
|
|
64
|
+
// Fail-safe to least privilege: any role value but 'admin' reads as 'member'.
|
|
82
65
|
const role = row.role === 'admin' ? 'admin' : 'member';
|
|
83
|
-
|
|
84
|
-
|
|
66
|
+
return { tenantId: row.tenant_id, keyId, role, scopes: listScopeGrants(db, keyId) };
|
|
67
|
+
}
|
|
68
|
+
export function validateApiKey(db, plaintext) {
|
|
69
|
+
const key = lookupApiKey(db, plaintext);
|
|
70
|
+
return key ? { valid: true, ...key } : { valid: false };
|
|
71
|
+
}
|
|
72
|
+
/** How long a verified key is trusted without a store read; also the ceiling on a revoke made by another process. */
|
|
73
|
+
export const VERIFIED_KEY_TTL_MS = 60_000;
|
|
74
|
+
const VERIFIED_KEY_CACHE_CAP = 1_000;
|
|
75
|
+
function secretDigest(plaintext) {
|
|
76
|
+
return createHash('sha256').update(plaintext).digest();
|
|
77
|
+
}
|
|
78
|
+
/** LRU of verified keys with an absolute TTL. Holds a SHA-256 of the token, never the token. */
|
|
79
|
+
export class VerifiedKeyCache {
|
|
80
|
+
capacity;
|
|
81
|
+
ttlMs;
|
|
82
|
+
// Map iterates in insertion order, so re-inserting on a hit makes the first key the least recent.
|
|
83
|
+
entries = new Map();
|
|
84
|
+
constructor(capacity, ttlMs) {
|
|
85
|
+
this.capacity = capacity;
|
|
86
|
+
this.ttlMs = ttlMs;
|
|
87
|
+
}
|
|
88
|
+
get size() {
|
|
89
|
+
return this.entries.size;
|
|
90
|
+
}
|
|
91
|
+
get(hippoRoot, keyId, plaintext, now) {
|
|
92
|
+
const entry = this.entries.get(keyId);
|
|
93
|
+
if (!entry || entry.hippoRoot !== hippoRoot)
|
|
94
|
+
return undefined;
|
|
95
|
+
if (now >= entry.expiresAt) {
|
|
96
|
+
this.entries.delete(keyId);
|
|
97
|
+
return undefined;
|
|
98
|
+
}
|
|
99
|
+
// A wrong secret misses but leaves the entry, so a flood of bad guesses cannot evict a good key.
|
|
100
|
+
if (!timingSafeEqual(entry.digest, secretDigest(plaintext)))
|
|
101
|
+
return undefined;
|
|
102
|
+
this.entries.delete(keyId);
|
|
103
|
+
this.entries.set(keyId, entry);
|
|
104
|
+
return { ...entry.key, scopes: [...entry.key.scopes] };
|
|
105
|
+
}
|
|
106
|
+
set(hippoRoot, keyId, plaintext, key, now) {
|
|
107
|
+
this.entries.delete(keyId);
|
|
108
|
+
if (this.entries.size >= this.capacity) {
|
|
109
|
+
const oldest = this.entries.keys().next();
|
|
110
|
+
if (!oldest.done)
|
|
111
|
+
this.entries.delete(oldest.value);
|
|
112
|
+
}
|
|
113
|
+
const frozen = Object.freeze({ ...key, scopes: [...key.scopes] });
|
|
114
|
+
this.entries.set(keyId, { hippoRoot, digest: secretDigest(plaintext), key: frozen, expiresAt: now + this.ttlMs });
|
|
115
|
+
}
|
|
116
|
+
delete(keyId) {
|
|
117
|
+
this.entries.delete(keyId);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
// SHORTCUT: per-process cache, so a revoke or scope change made by another process (the CLI) lands within VERIFIED_KEY_TTL_MS; a shared revocation epoch in the store if that is too slow.
|
|
121
|
+
const verifiedKeys = new VerifiedKeyCache(VERIFIED_KEY_CACHE_CAP, VERIFIED_KEY_TTL_MS);
|
|
122
|
+
/** Verify a bearer API key for `hippoRoot`; a cache hit skips both scrypt and the DB open. Null when invalid. */
|
|
123
|
+
export function verifyApiKeyCached(hippoRoot, plaintext) {
|
|
124
|
+
if (!MINTED_KEY_PATTERN.test(plaintext))
|
|
125
|
+
return null;
|
|
126
|
+
const keyId = plaintext.slice(0, API_KEY_PREFIX.length + ID_LEN);
|
|
127
|
+
const hit = verifiedKeys.get(hippoRoot, keyId, plaintext, Date.now());
|
|
128
|
+
if (hit)
|
|
129
|
+
return hit;
|
|
130
|
+
verifyStats.storeLookups++;
|
|
131
|
+
const db = openHippoDb(hippoRoot);
|
|
132
|
+
let key;
|
|
133
|
+
try {
|
|
134
|
+
key = lookupApiKey(db, plaintext);
|
|
135
|
+
}
|
|
136
|
+
finally {
|
|
137
|
+
closeHippoDb(db);
|
|
138
|
+
}
|
|
139
|
+
// Only successes are cached: caching misses would let junk tokens fill the cache and evict real keys.
|
|
140
|
+
if (key)
|
|
141
|
+
verifiedKeys.set(hippoRoot, keyId, plaintext, key, Date.now());
|
|
142
|
+
return key;
|
|
85
143
|
}
|
|
86
144
|
export function revokeApiKey(db, keyId) {
|
|
87
145
|
db.prepare(`UPDATE api_keys SET revoked_at = ? WHERE key_id = ? AND revoked_at IS NULL`)
|
|
88
146
|
.run(new Date().toISOString(), keyId);
|
|
147
|
+
verifiedKeys.delete(keyId);
|
|
89
148
|
}
|
|
90
149
|
/** EI2: grant `keyId` read access to one restricted `scope`. Idempotent. */
|
|
91
150
|
export function grantScope(db, keyId, scope) {
|
|
92
151
|
db.prepare(`INSERT INTO api_key_scope_grants (key_id, scope, granted_at) VALUES (?, ?, ?)
|
|
93
152
|
ON CONFLICT(key_id, scope) DO NOTHING`).run(keyId, scope, new Date().toISOString());
|
|
153
|
+
verifiedKeys.delete(keyId);
|
|
94
154
|
}
|
|
95
155
|
/** EI2: revoke `keyId`'s grant on `scope`. Not an error when no such grant exists. */
|
|
96
156
|
export function ungrantScope(db, keyId, scope) {
|
|
97
157
|
db.prepare(`DELETE FROM api_key_scope_grants WHERE key_id = ? AND scope = ?`).run(keyId, scope);
|
|
158
|
+
verifiedKeys.delete(keyId);
|
|
98
159
|
}
|
|
99
160
|
/** EI2: every restricted scope `keyId` may read. */
|
|
100
161
|
export function listScopeGrants(db, keyId) {
|
|
@@ -104,19 +165,35 @@ export function listScopeGrants(db, keyId) {
|
|
|
104
165
|
.all(keyId);
|
|
105
166
|
return rows.map((r) => r.scope);
|
|
106
167
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
168
|
+
/** Keys newest first, with their row ids, filtered and limited in SQL. */
|
|
169
|
+
export function listApiKeyRows(db, opts) {
|
|
170
|
+
const where = ['1 = 1'];
|
|
171
|
+
const params = [];
|
|
172
|
+
if (opts.active)
|
|
173
|
+
where.push('revoked_at IS NULL');
|
|
174
|
+
if (opts.tenantId !== undefined) {
|
|
175
|
+
where.push('tenant_id = ?');
|
|
176
|
+
params.push(opts.tenantId);
|
|
177
|
+
}
|
|
178
|
+
const after = keysetAfter('id', 'id', opts.after);
|
|
179
|
+
params.push(...after.params);
|
|
180
|
+
const limitSql = opts.limit === undefined ? '' : ' LIMIT ?';
|
|
181
|
+
if (opts.limit !== undefined)
|
|
182
|
+
params.push(opts.limit);
|
|
183
|
+
const sql = `SELECT id, key_id, tenant_id, label, created_at, revoked_at, role FROM api_keys WHERE ${where.join(' AND ')}${after.sql} ORDER BY id DESC${limitSql}`;
|
|
184
|
+
// SAFETY: the SELECT above projects exactly these 7 columns, in this order.
|
|
185
|
+
const rows = db.prepare(sql).all(...params);
|
|
115
186
|
return rows.map(r => ({
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
187
|
+
rowId: r.id,
|
|
188
|
+
key: {
|
|
189
|
+
keyId: r.key_id, tenantId: r.tenant_id, label: r.label,
|
|
190
|
+
createdAt: r.created_at, revokedAt: r.revoked_at,
|
|
191
|
+
role: r.role === 'admin' ? 'admin' : 'member',
|
|
192
|
+
scopes: listScopeGrants(db, r.key_id),
|
|
193
|
+
},
|
|
120
194
|
}));
|
|
121
195
|
}
|
|
196
|
+
export function listApiKeys(db, opts) {
|
|
197
|
+
return listApiKeyRows(db, opts).map(r => r.key);
|
|
198
|
+
}
|
|
122
199
|
//# sourceMappingURL=auth.js.map
|
package/dist/autolearn.js
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
import { execSync, execFileSync, spawn } from 'child_process';
|
|
6
6
|
import { createMemory, Layer, DEFAULT_HALF_LIFE_DAYS } from './memory.js';
|
|
7
|
-
import { loadAllEntries } from './store.js';
|
|
8
|
-
import { textOverlap } from './
|
|
7
|
+
import { loadAllEntries } from './store/entry-reads.js';
|
|
8
|
+
import { textOverlap } from './tokenize.js';
|
|
9
9
|
import { isContentWorthStoring } from './audit.js';
|
|
10
10
|
import { redactSecretsStrict } from './secret-detect.js';
|
|
11
11
|
import { log } from './log.js';
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { type SessionTurn } from './transcript.js';
|
|
2
|
+
export interface CaptureOptions {
|
|
3
|
+
source: 'stdin' | 'file' | 'last-session';
|
|
4
|
+
filePath?: string;
|
|
5
|
+
/** Explicit transcript path for `--last-session`. Without one, `stdinText`
|
|
6
|
+
* is used, then auto-discovery under `~/.claude/projects/` on a manual run. */
|
|
7
|
+
transcriptPath?: string;
|
|
8
|
+
/** Read from stdin by the caller (cli.ts), which owns the bounded wait.
|
|
9
|
+
* `stdinTimedOut` marks an empty read "unknown", not "no payload". */
|
|
10
|
+
stdinText?: string;
|
|
11
|
+
stdinTimedOut?: boolean;
|
|
12
|
+
sessionTurns?: readonly SessionTurn[];
|
|
13
|
+
/**
|
|
14
|
+
* Tee stdout/stderr to this log file while capture runs. Mirrors the
|
|
15
|
+
* pattern used by `hippo sleep --log-file` so the SessionEnd hook output
|
|
16
|
+
* (invisible during TUI teardown) can be surfaced via `hippo last-sleep`
|
|
17
|
+
* on the next session start. Appends rather than truncates — `hippo sleep`
|
|
18
|
+
* writes the same file first in the SessionEnd sequence.
|
|
19
|
+
*/
|
|
20
|
+
logFile?: string;
|
|
21
|
+
dryRun: boolean;
|
|
22
|
+
global: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* L9: tenant scope for the dedup read in `cmdCaptureCore`. When provided
|
|
25
|
+
* AND `global` is false, the dedup check only considers this tenant's
|
|
26
|
+
* existing memories. Undefined preserves pre-1.12.1 host-wide dedup
|
|
27
|
+
* behaviour. Ignored when `global: true` (global captures are host-wide).
|
|
28
|
+
*/
|
|
29
|
+
tenantId?: string;
|
|
30
|
+
originProject?: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function cmdCapture(hippoRoot: string, options: CaptureOptions): void;
|
|
33
|
+
//# sourceMappingURL=command.d.ts.map
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { createMemory, Layer } from '../memory.js';
|
|
4
|
+
import { duplicateKey, storedTextKeys } from '../same-text.js';
|
|
5
|
+
import { stampOriginProject } from '../store/entry-row.js';
|
|
6
|
+
import { isInitialized, openStore } from '../store/open.js';
|
|
7
|
+
import { writeEntryMirrors } from '../store/entry-writes.js';
|
|
8
|
+
import { loadAllEntries } from '../store/entry-reads.js';
|
|
9
|
+
import { updateStats } from '../store/index-and-stats.js';
|
|
10
|
+
import { gatedWrite } from '../gated-write.js';
|
|
11
|
+
import { getGlobalRoot, initGlobal } from '../shared.js';
|
|
12
|
+
import { embedMemory } from '../embeddings.js';
|
|
13
|
+
import { maskEmails, redactSecretsStrict } from '../secret-detect.js';
|
|
14
|
+
import { RejectedValueError, checkRejectionGuard } from '../rejection.js';
|
|
15
|
+
import { openHippoDb, closeHippoDb } from '../db.js';
|
|
16
|
+
import { loadConfig } from '../config.js';
|
|
17
|
+
import { classifyOriginProject } from '../project-identity.js';
|
|
18
|
+
import { isStringValue } from '../capture-contract.js';
|
|
19
|
+
import { errorMessage, log } from '../log.js';
|
|
20
|
+
import { extractFromText } from './extract.js';
|
|
21
|
+
import { collectSessionTurns, summariseSessionTurns, resolveLastSessionTranscript } from './transcript.js';
|
|
22
|
+
export function cmdCapture(hippoRoot, options) {
|
|
23
|
+
// Tee stdout/stderr to a log file when --log-file is set. Used by the
|
|
24
|
+
// SessionEnd hook so output (otherwise swallowed by TUI teardown) surfaces
|
|
25
|
+
// on the next session start via `hippo last-sleep`. Runs second in the
|
|
26
|
+
// SessionEnd sequence after `hippo sleep`, so we APPEND rather than
|
|
27
|
+
// truncate — sleep already wrote its own header + body to this file.
|
|
28
|
+
const restoreStdio = options.logFile ? beginLogTee(options.logFile) : null;
|
|
29
|
+
try {
|
|
30
|
+
cmdCaptureCore(hippoRoot, options);
|
|
31
|
+
if (options.logFile)
|
|
32
|
+
console.log('[hippo] capture complete');
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
if (options.logFile)
|
|
36
|
+
console.log(`[hippo] capture failed: ${errorMessage(err)}`);
|
|
37
|
+
throw err;
|
|
38
|
+
}
|
|
39
|
+
finally {
|
|
40
|
+
if (restoreStdio)
|
|
41
|
+
restoreStdio();
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Append-mode tee: writes a banner line then mirrors every stdout/stderr
|
|
46
|
+
* chunk to `logFile` until the returned restore function is called.
|
|
47
|
+
* Failures to write the log are non-fatal; the real streams still get
|
|
48
|
+
* the data.
|
|
49
|
+
*/
|
|
50
|
+
function beginLogTee(logFile) {
|
|
51
|
+
try {
|
|
52
|
+
fs.mkdirSync(path.dirname(logFile), { recursive: true });
|
|
53
|
+
fs.appendFileSync(logFile, `[hippo] ${new Date().toISOString()} capturing session...\n`, 'utf8');
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
log.warn(`could not open log file ${logFile}: ${errorMessage(err)}`);
|
|
57
|
+
return () => { };
|
|
58
|
+
}
|
|
59
|
+
const origStdoutWrite = process.stdout.write.bind(process.stdout);
|
|
60
|
+
const origStderrWrite = process.stderr.write.bind(process.stderr);
|
|
61
|
+
const tee = (chunk) => {
|
|
62
|
+
try {
|
|
63
|
+
const buf = isStringValue(chunk) ? chunk : Buffer.from(chunk).toString('utf8');
|
|
64
|
+
fs.appendFileSync(logFile, buf, 'utf8');
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// log failures are non-fatal
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const wrapWrite = (origWrite) => {
|
|
71
|
+
const wrapped = (...args) => {
|
|
72
|
+
tee(args[0]);
|
|
73
|
+
// SAFETY: forwarding the exact arguments Node's real overloaded
|
|
74
|
+
// `write` received is safe regardless of which overload the call
|
|
75
|
+
// site used — Node dispatches on the actual argument shapes at
|
|
76
|
+
// runtime, and `StreamWriteArgs` is the union of both overloads'
|
|
77
|
+
// parameter lists.
|
|
78
|
+
return origWrite(...args);
|
|
79
|
+
};
|
|
80
|
+
// SAFETY: `wrapped` matches both real `write` overload shapes it's
|
|
81
|
+
// assigned to; TS can't verify a single implementation covers an
|
|
82
|
+
// overloaded type, but this one forwards to the real stream method.
|
|
83
|
+
return wrapped;
|
|
84
|
+
};
|
|
85
|
+
process.stdout.write = wrapWrite(origStdoutWrite);
|
|
86
|
+
process.stderr.write = wrapWrite(origStderrWrite);
|
|
87
|
+
return () => {
|
|
88
|
+
process.stdout.write = origStdoutWrite;
|
|
89
|
+
process.stderr.write = origStderrWrite;
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
// Console lines here are the `hippo capture` command's printed result, so they stay off the logger.
|
|
93
|
+
function cmdCaptureCore(hippoRoot, options) {
|
|
94
|
+
const useGlobal = options.global;
|
|
95
|
+
const targetRoot = useGlobal ? getGlobalRoot() : hippoRoot;
|
|
96
|
+
if (useGlobal) {
|
|
97
|
+
initGlobal();
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
if (!isInitialized(hippoRoot)) {
|
|
101
|
+
console.error(`No hippo store at ${hippoRoot} (searched ${process.cwd()} and its parents up to your home directory). Run \`hippo init\` first.`);
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Read input text
|
|
106
|
+
const text = readCaptureText(options);
|
|
107
|
+
if (text === null)
|
|
108
|
+
return;
|
|
109
|
+
if (!text || text.trim().length === 0) {
|
|
110
|
+
console.log('No text to capture from.');
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Scrub once here, as the snapshot fields are: every source can carry a pasted token (AGENTS.md: no secrets in memories).
|
|
114
|
+
const extracted = extractFromText(maskEmails(redactSecretsStrict(text)));
|
|
115
|
+
if (extracted.length === 0) {
|
|
116
|
+
console.log('No actionable items found in the input.');
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
// Dedup only against rows this capture's reader sees: another tenant's rows (L9), or another
|
|
120
|
+
// project's, are hidden from it, so they must not stop its own copy.
|
|
121
|
+
const stored = loadAllEntries(targetRoot, useGlobal ? undefined : options.tenantId);
|
|
122
|
+
const origin = options.originProject;
|
|
123
|
+
const keys = storedTextKeys(origin === undefined
|
|
124
|
+
? stored
|
|
125
|
+
: stored.filter((e) => classifyOriginProject(e.origin_project, origin) !== 'cross-project'));
|
|
126
|
+
const { captured, skipped, rejected } = captureExtractedItems(targetRoot, options, extracted, keys);
|
|
127
|
+
const prefix = options.dryRun ? '[dry-run] ' : '';
|
|
128
|
+
const globalPrefix = useGlobal ? '[global] ' : '';
|
|
129
|
+
console.log(`\n${prefix}${globalPrefix}Captured ${captured} items (${skipped} skipped as duplicates` +
|
|
130
|
+
(rejected > 0 ? `, ${rejected} rejected` : '') +
|
|
131
|
+
')');
|
|
132
|
+
}
|
|
133
|
+
/** The raw text for the chosen source; null after printing why a last-session capture has nothing. */
|
|
134
|
+
function readCaptureText(options) {
|
|
135
|
+
switch (options.source) {
|
|
136
|
+
case 'stdin': {
|
|
137
|
+
try {
|
|
138
|
+
return fs.readFileSync(0, 'utf8');
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
console.error('No input on stdin. Pipe text in or use --file <path>.');
|
|
142
|
+
process.exit(1);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
case 'file': {
|
|
146
|
+
if (!options.filePath) {
|
|
147
|
+
console.error('Missing file path. Usage: hippo capture --file <path>');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
if (!fs.existsSync(options.filePath)) {
|
|
151
|
+
console.error(`File not found: ${options.filePath}`);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
return fs.readFileSync(options.filePath, 'utf8');
|
|
155
|
+
}
|
|
156
|
+
case 'last-session': {
|
|
157
|
+
let turns = options.sessionTurns;
|
|
158
|
+
if (!turns) {
|
|
159
|
+
const resolved = resolveLastSessionTranscript(options.transcriptPath, options.stdinText, { mayScan: !options.stdinTimedOut });
|
|
160
|
+
if (!resolved) {
|
|
161
|
+
console.log('No transcript found. Pass --transcript <path> or run from a SessionEnd hook.');
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
turns = collectSessionTurns(fs.readFileSync(resolved, 'utf8'));
|
|
165
|
+
}
|
|
166
|
+
const text = summariseSessionTurns(turns);
|
|
167
|
+
if (!text) {
|
|
168
|
+
console.log('Transcript had no user/assistant messages to summarise.');
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
return text;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
function captureExtractedItems(targetRoot, options, extracted, keys) {
|
|
176
|
+
const tally = { captured: 0, skipped: 0, rejected: 0 };
|
|
177
|
+
const baseHalfLifeDays = loadConfig(targetRoot).defaultHalfLifeDays;
|
|
178
|
+
// AT1 P2 fix (dry-run parity, docs/plans/2026-08-15-at1-rejected-value-tombstone.md):
|
|
179
|
+
// dry-run used to skip the guarded write branch ENTIRELY, so a tombstoned
|
|
180
|
+
// extraction printed as `[capture]` and counted toward `captured` — the
|
|
181
|
+
// preview lied about what a real run would do. Mirrors importers.ts's
|
|
182
|
+
// importEntries dry-run probe (commit 6146e82): open a read-only handle
|
|
183
|
+
// once, run the same checkRejectionGuard the real write path uses via
|
|
184
|
+
// writeEntry, never write anything.
|
|
185
|
+
const dryRunDb = options.dryRun ? openHippoDb(targetRoot) : null;
|
|
186
|
+
const writeDb = options.dryRun ? null : openStore(targetRoot);
|
|
187
|
+
try {
|
|
188
|
+
for (const item of extracted) {
|
|
189
|
+
if (keys.has(duplicateKey(item.content))) {
|
|
190
|
+
tally.skipped++;
|
|
191
|
+
if (options.dryRun) {
|
|
192
|
+
console.log(` [skip] (${item.category}) ${item.content.slice(0, 80)}`);
|
|
193
|
+
}
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const entry = captureEntry(item, options, baseHalfLifeDays);
|
|
197
|
+
tally[captureOne({ targetRoot, options, dryRunDb, writeDb, keys }, item, entry)]++;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
finally {
|
|
201
|
+
if (dryRunDb)
|
|
202
|
+
closeHippoDb(dryRunDb);
|
|
203
|
+
if (writeDb)
|
|
204
|
+
closeHippoDb(writeDb);
|
|
205
|
+
}
|
|
206
|
+
return tally;
|
|
207
|
+
}
|
|
208
|
+
function captureEntry(item, options, baseHalfLifeDays) {
|
|
209
|
+
const useGlobal = options.global;
|
|
210
|
+
// A3: kind defaults to 'distilled'. capture.ts extracts curated items from
|
|
211
|
+
// session output (not raw transcript chunks), so distilled is correct. If a
|
|
212
|
+
// future variant captures full raw session text, it MUST set kind: 'raw'
|
|
213
|
+
// and route deletions through archiveRawMemory(). See MEMORY_ENVELOPE.md.
|
|
214
|
+
// L9: the dedup read above is scoped by options.tenantId — the WRITE
|
|
215
|
+
// must match, or scoped-dedup-passes-then-default-tenant-write breaks
|
|
216
|
+
// the per-tenant contract. Mirror the dedup-read guard: when
|
|
217
|
+
// global: true, the global store is host-wide and tenant is irrelevant
|
|
218
|
+
// (createMemory's default 'default' applies). When global: false,
|
|
219
|
+
// options.tenantId scopes the write to the same tenant as the dedup.
|
|
220
|
+
const created = createMemory(item.content, {
|
|
221
|
+
layer: Layer.Episodic,
|
|
222
|
+
tags: item.tags,
|
|
223
|
+
source: 'capture',
|
|
224
|
+
confidence: 'observed',
|
|
225
|
+
tenantId: useGlobal ? undefined : options.tenantId,
|
|
226
|
+
baseHalfLifeDays,
|
|
227
|
+
});
|
|
228
|
+
return options.originProject === undefined ? created : { ...created, origin_project: options.originProject };
|
|
229
|
+
}
|
|
230
|
+
/** Previews or writes one non-duplicate item and names the tally it counts toward. */
|
|
231
|
+
function captureOne(ctx, item, entry) {
|
|
232
|
+
const { targetRoot, options, dryRunDb, writeDb, keys } = ctx;
|
|
233
|
+
if (options.dryRun) {
|
|
234
|
+
if (dryRunDb) {
|
|
235
|
+
try {
|
|
236
|
+
checkRejectionGuard(dryRunDb, entry.tenantId ?? 'default', entry.id, entry.content);
|
|
237
|
+
}
|
|
238
|
+
catch (err) {
|
|
239
|
+
if (err instanceof RejectedValueError) {
|
|
240
|
+
console.log(` [reject] (${item.category}) ${item.content.slice(0, 80)} - matches a rejected value`);
|
|
241
|
+
return 'rejected';
|
|
242
|
+
}
|
|
243
|
+
throw err;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
console.log(` [capture] (${item.category}) ${item.content}`);
|
|
247
|
+
}
|
|
248
|
+
else if (writeDb !== null) {
|
|
249
|
+
// AT1 (plan §3 containment): one rejected item must not abort the
|
|
250
|
+
// rest of this capture's items.
|
|
251
|
+
const stamped = stampOriginProject(targetRoot, entry);
|
|
252
|
+
const outcome = gatedWrite(writeDb, targetRoot, stamped);
|
|
253
|
+
if (outcome === 'skipped:rejected')
|
|
254
|
+
return 'rejected';
|
|
255
|
+
if (outcome !== 'written')
|
|
256
|
+
return 'skipped';
|
|
257
|
+
writeEntryMirrors(targetRoot, stamped);
|
|
258
|
+
updateStats(targetRoot, { remembered: 1 });
|
|
259
|
+
keys.add(duplicateKey(item.content)); // within-batch dedup
|
|
260
|
+
void embedMemory(targetRoot, entry);
|
|
261
|
+
}
|
|
262
|
+
return 'captured';
|
|
263
|
+
}
|
|
264
|
+
//# sourceMappingURL=command.js.map
|