hippo-memory 1.61.0 → 1.63.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 +37 -53
- package/dist/agent-memories/apply.d.ts +3 -1
- package/dist/agent-memories/apply.js +23 -1
- package/dist/agent-memories/claude-code.d.ts +4 -1
- package/dist/agent-memories/claude-code.js +53 -9
- package/dist/agent-memories/report.d.ts +1 -0
- package/dist/agent-memories/report.js +2 -2
- package/dist/agent-memories/sync.d.ts +3 -3
- package/dist/agent-memories/sync.js +46 -24
- package/dist/agent-memories/types.d.ts +0 -2
- package/dist/ambient-store.d.ts +4 -4
- package/dist/ambient-store.js +4 -3
- package/dist/api/assemble.d.ts +7 -10
- package/dist/api/assemble.js +62 -66
- package/dist/api/audit.d.ts +2 -2
- package/dist/api/audit.js +2 -2
- package/dist/api/auth.d.ts +6 -6
- package/dist/api/auth.js +5 -7
- package/dist/api/context-select.d.ts +50 -0
- package/dist/api/context-select.js +342 -0
- package/dist/api/context-types.d.ts +12 -17
- package/dist/api/context.d.ts +5 -4
- package/dist/api/context.js +208 -535
- package/dist/api/dormant.js +1 -1
- package/dist/api/drill-down.d.ts +4 -4
- package/dist/api/drill-down.js +56 -41
- package/dist/api/outcome.d.ts +8 -13
- package/dist/api/outcome.js +13 -17
- package/dist/api/promote.d.ts +5 -8
- package/dist/api/promote.js +55 -66
- package/dist/api/quarantine.js +3 -2
- package/dist/api/recall-types.d.ts +43 -55
- package/dist/api/recall.d.ts +3 -3
- package/dist/api/recall.js +310 -456
- package/dist/api/remember.d.ts +2 -2
- package/dist/api/sleep.d.ts +7 -35
- package/dist/api/sleep.js +206 -220
- package/dist/api/tokens.d.ts +2 -2
- package/dist/api/tokens.js +2 -2
- package/dist/api/types.d.ts +6 -10
- package/dist/api/types.js +2 -4
- package/dist/audit-prune.d.ts +4 -6
- package/dist/audit-prune.js +3 -5
- package/dist/audit.js +11 -33
- package/dist/auth.d.ts +8 -9
- package/dist/auth.js +5 -7
- package/dist/autolearn.d.ts +1 -1
- package/dist/autolearn.js +1 -1
- package/dist/availability.js +3 -5
- package/dist/capture/command.d.ts +5 -4
- package/dist/capture/command.js +8 -22
- package/dist/capture/compact.d.ts +2 -2
- package/dist/capture/compact.js +11 -15
- package/dist/capture/extract.js +37 -115
- package/dist/capture-error.d.ts +1 -1
- package/dist/capture-error.js +1 -1
- package/dist/churn-git.d.ts +1 -1
- package/dist/churn-git.js +1 -1
- package/dist/cli/audit.js +3 -4
- package/dist/cli/auth.js +3 -6
- package/dist/cli/briefs.js +324 -306
- package/dist/cli/context.js +44 -34
- package/dist/cli/continuity.js +283 -271
- package/dist/cli/curate.d.ts +1 -1
- package/dist/cli/curate.js +43 -58
- package/dist/cli/dag.js +5 -9
- package/dist/cli/decisions.js +334 -345
- package/dist/cli/explain.js +68 -61
- package/dist/cli/goals.js +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/maintenance.js +62 -51
- package/dist/cli/playbooks.js +391 -379
- package/dist/cli/projects.js +11 -6
- package/dist/cli/recall.js +30 -44
- package/dist/cli/remember.js +118 -87
- package/dist/cli/session-hooks.js +106 -115
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.js +267 -250
- package/dist/cli/shared.js +3 -3
- package/dist/cli/slack.js +1 -1
- package/dist/cli/sleep.js +27 -1
- package/dist/cli/status.d.ts +4 -4
- package/dist/cli/status.js +80 -76
- package/dist/cli/transfer.js +88 -107
- package/dist/cli/usage.js +9 -6
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +4 -9
- package/dist/compaction-record.d.ts +2 -2
- package/dist/compaction-record.js +89 -68
- package/dist/compare.d.ts +11 -16
- package/dist/compare.js +11 -16
- package/dist/config.d.ts +19 -18
- package/dist/config.js +90 -67
- package/dist/connectors/github/backfill.d.ts +2 -2
- package/dist/connectors/github/backfill.js +8 -15
- package/dist/connectors/github/cli-impl.js +3 -8
- package/dist/connectors/github/deletion.d.ts +5 -12
- package/dist/connectors/github/deletion.js +5 -12
- package/dist/connectors/github/dlq.d.ts +6 -9
- package/dist/connectors/github/dlq.js +2 -3
- package/dist/connectors/github/ingest.d.ts +5 -7
- package/dist/connectors/github/ingest.js +8 -12
- package/dist/connectors/github/octokit-client.d.ts +3 -5
- package/dist/connectors/github/octokit-client.js +5 -6
- package/dist/connectors/github/signature.d.ts +9 -39
- package/dist/connectors/github/signature.js +9 -39
- package/dist/connectors/github/tenant-routing.d.ts +1 -1
- package/dist/connectors/github/tenant-routing.js +1 -1
- package/dist/connectors/github/transform.js +2 -2
- package/dist/connectors/github/types.d.ts +2 -10
- package/dist/connectors/github/types.js +1 -3
- package/dist/connectors/slack/deletion.d.ts +3 -8
- package/dist/connectors/slack/deletion.js +3 -8
- package/dist/connectors/slack/dlq.d.ts +1 -1
- package/dist/connectors/slack/ingest.d.ts +1 -1
- package/dist/connectors/slack/ingest.js +7 -16
- package/dist/connectors/slack/signature.d.ts +1 -1
- package/dist/connectors/slack/tenant-routing.d.ts +3 -5
- package/dist/connectors/slack/tenant-routing.js +3 -5
- package/dist/connectors/slack/transform.d.ts +5 -6
- package/dist/connectors/slack/transform.js +5 -6
- package/dist/connectors/slack/types.d.ts +2 -6
- package/dist/connectors/slack/types.js +1 -3
- package/dist/connectors/slack/web-client.js +10 -3
- package/dist/connectors/slack/workspaces.d.ts +3 -5
- package/dist/connectors/slack/workspaces.js +3 -5
- package/dist/consolidate/conflicts.js +3 -14
- package/dist/consolidate/decay.js +9 -29
- package/dist/consolidate/llm-passes.js +4 -5
- package/dist/consolidate/merge.js +8 -23
- package/dist/consolidate/run.d.ts +1 -8
- package/dist/consolidate/run.js +3 -25
- package/dist/consolidate/sleep.js +5 -17
- package/dist/consolidate/traces.js +9 -21
- package/dist/customer-notes.d.ts +5 -7
- package/dist/customer-notes.js +82 -76
- package/dist/dag.d.ts +10 -21
- package/dist/dag.js +189 -203
- package/dist/db/continuity.js +2 -2
- package/dist/db/migrations/v14.js +1 -1
- package/dist/db/migrations/v15.js +1 -2
- package/dist/db/migrations/v16.js +3 -4
- package/dist/db/migrations/v17.js +2 -3
- package/dist/db/migrations/v19.js +1 -1
- package/dist/db/migrations/v20.js +1 -1
- package/dist/db/migrations/v21.js +2 -6
- package/dist/db/migrations/v22.js +2 -4
- package/dist/db/migrations/v23.js +1 -1
- package/dist/db/migrations/v24.js +4 -6
- package/dist/db/migrations/v25.js +2 -3
- package/dist/db/migrations/v26.js +3 -3
- package/dist/db/migrations/v27.js +2 -10
- package/dist/db/migrations/v28.js +5 -8
- package/dist/db/migrations/v29.js +3 -4
- package/dist/db/migrations/v30.js +2 -2
- package/dist/db/migrations/v31.js +1 -1
- package/dist/db/migrations/v32.js +1 -1
- package/dist/db/migrations/v33.js +3 -3
- package/dist/db/migrations/v34.js +1 -1
- package/dist/db/migrations/v35.js +3 -4
- package/dist/db/migrations/v36.js +3 -4
- package/dist/db/migrations/v37.js +5 -5
- package/dist/db/migrations/v38.js +7 -8
- package/dist/db/migrations/v39.js +1 -1
- package/dist/db/migrations/v40.js +4 -16
- package/dist/db/migrations/v41.js +3 -4
- package/dist/db/migrations/v42.js +3 -4
- package/dist/db/migrations/v45.js +1 -1
- package/dist/db/migrations/v46.js +1 -1
- package/dist/db/migrations/v47.js +1 -1
- package/dist/db/migrations/v48.js +1 -1
- package/dist/decisions.d.ts +2 -2
- package/dist/decisions.js +97 -80
- package/dist/dedupe.js +86 -61
- package/dist/delivery-recorder.js +154 -135
- package/dist/doctor.js +129 -110
- package/dist/dormant.js +1 -4
- package/dist/embedding-provider.d.ts +4 -8
- package/dist/embedding-provider.js +4 -8
- package/dist/embeddings.js +55 -47
- package/dist/env.d.ts +1 -1
- package/dist/env.js +12 -12
- package/dist/escape.d.ts +5 -0
- package/dist/escape.js +10 -0
- package/dist/eval-stats.d.ts +1 -2
- package/dist/eval-stats.js +1 -2
- package/dist/eval-suite.js +27 -21
- package/dist/extract.js +4 -9
- package/dist/failure-log.d.ts +3 -3
- package/dist/failure-log.js +1 -1
- package/dist/forward-claim-detector.d.ts +2 -4
- package/dist/forward-claim-detector.js +6 -11
- package/dist/goals.d.ts +3 -3
- package/dist/goals.js +103 -91
- package/dist/graph/read.d.ts +2 -2
- package/dist/graph/read.js +5 -6
- package/dist/graph/types.d.ts +8 -8
- package/dist/graph/write.d.ts +7 -14
- package/dist/graph/write.js +16 -23
- package/dist/graph-extract.d.ts +7 -8
- package/dist/graph-extract.js +62 -72
- package/dist/graph-recall.d.ts +2 -2
- package/dist/graph-recall.js +55 -49
- package/dist/graph-stream.d.ts +5 -6
- package/dist/graph-stream.js +66 -57
- package/dist/graph-view.d.ts +2 -2
- package/dist/graph-view.js +7 -7
- package/dist/half-life-migration.d.ts +1 -2
- package/dist/half-life-migration.js +2 -3
- package/dist/hooks/codex-session.js +1 -1
- package/dist/hooks/codex-wrapper.d.ts +1 -1
- package/dist/hooks/codex-wrapper.js +3 -2
- package/dist/hooks/json-hooks.d.ts +2 -2
- package/dist/hooks/json-hooks.js +5 -4
- package/dist/hooks/opencode.d.ts +1 -1
- package/dist/hooks/opencode.js +5 -4
- package/dist/hooks/shared.d.ts +3 -7
- package/dist/hooks/shared.js +1 -8
- package/dist/http-util.d.ts +2 -3
- package/dist/http-util.js +3 -0
- package/dist/importers/core.d.ts +2 -9
- package/dist/importers/core.js +15 -30
- package/dist/importers/sources.js +2 -1
- package/dist/importers/vault.js +2 -20
- package/dist/incidents.d.ts +1 -1
- package/dist/incidents.js +46 -39
- package/dist/instruction-detect.d.ts +1 -1
- package/dist/instruction-detect.js +1 -1
- package/dist/invalidation.d.ts +3 -0
- package/dist/invalidation.js +160 -114
- package/dist/json.d.ts +5 -0
- package/dist/json.js +4 -0
- package/dist/judgment.js +1 -2
- package/dist/local-embedding.js +1 -1
- package/dist/mcp/admin-tools.js +7 -17
- package/dist/mcp/format.js +1 -1
- package/dist/mcp/framing.js +3 -6
- package/dist/mcp/protocol.d.ts +2 -5
- package/dist/mcp/protocol.js +1 -3
- package/dist/mcp/recall-tools.js +12 -15
- package/dist/mcp/request.js +4 -3
- package/dist/mcp/session-state.js +2 -3
- package/dist/mcp/stdio.js +2 -1
- package/dist/mcp/tools.js +9 -6
- package/dist/memory-value-weights.d.ts +5 -8
- package/dist/memory-value-weights.js +5 -8
- package/dist/memory-value.d.ts +13 -13
- package/dist/memory-value.js +26 -37
- package/dist/memory.d.ts +20 -22
- package/dist/memory.js +24 -48
- package/dist/multihop.d.ts +1 -1
- package/dist/multihop.js +3 -2
- package/dist/owner-validation.d.ts +4 -5
- package/dist/owner-validation.js +4 -5
- package/dist/physics.d.ts +4 -4
- package/dist/physics.js +7 -9
- package/dist/policies.d.ts +9 -10
- package/dist/policies.js +96 -81
- package/dist/postinstall.js +3 -6
- package/dist/predictions/planning-fallacy.d.ts +9 -14
- package/dist/predictions/planning-fallacy.js +10 -16
- package/dist/predictions/store.d.ts +15 -23
- package/dist/predictions/store.js +36 -33
- package/dist/processes.d.ts +2 -7
- package/dist/processes.js +88 -72
- package/dist/project-briefs.d.ts +2 -3
- package/dist/project-briefs.js +141 -118
- package/dist/project-identity.d.ts +22 -9
- package/dist/project-identity.js +47 -12
- package/dist/project-merge.d.ts +28 -5
- package/dist/project-merge.js +213 -46
- package/dist/project-remote.d.ts +12 -0
- package/dist/project-remote.js +138 -0
- package/dist/prompt-recall.js +1 -2
- package/dist/rate-limit.d.ts +1 -1
- package/dist/rate-limit.js +1 -1
- package/dist/raw-archive.d.ts +9 -0
- package/dist/raw-archive.js +70 -53
- package/dist/recall-history.d.ts +19 -20
- package/dist/recall-history.js +24 -42
- package/dist/recall-pipeline.js +4 -28
- package/dist/recall-scope.d.ts +7 -8
- package/dist/recall-scope.js +7 -8
- package/dist/recall-trace.d.ts +5 -9
- package/dist/recall-trace.js +6 -10
- package/dist/refine-llm.d.ts +1 -1
- package/dist/refine-llm.js +2 -2
- package/dist/reject-flow.d.ts +3 -4
- package/dist/reject-flow.js +122 -117
- package/dist/rejection.d.ts +5 -6
- package/dist/rejection.js +7 -15
- package/dist/rerankers/clef.d.ts +1 -1
- package/dist/rerankers/jev.d.ts +1 -2
- package/dist/rerankers/jev.js +4 -5
- package/dist/rerankers/llm.d.ts +1 -2
- package/dist/rerankers/llm.js +1 -2
- package/dist/rerankers/types.d.ts +1 -2
- package/dist/rrf.d.ts +2 -2
- package/dist/rrf.js +2 -2
- package/dist/search/bm25-search.d.ts +1 -1
- package/dist/search/bm25-search.js +2 -1
- package/dist/search/boosts.js +2 -1
- package/dist/search/hybrid.d.ts +1 -1
- package/dist/search/hybrid.js +2 -1
- package/dist/search/physics-search.d.ts +1 -1
- package/dist/search/physics-search.js +2 -1
- package/dist/search/types.d.ts +2 -0
- package/dist/search/types.js +3 -1
- package/dist/secret-detect.d.ts +4 -5
- package/dist/secret-detect.js +6 -10
- package/dist/server/auth.js +5 -5
- package/dist/server/client-ip.js +1 -1
- package/dist/server/cursor.js +2 -1
- package/dist/server/mcp-http.js +4 -4
- package/dist/server/request.d.ts +3 -6
- package/dist/server/request.js +6 -7
- package/dist/server/routes/admin.js +5 -4
- package/dist/server/routes/customer-notes.js +6 -5
- package/dist/server/routes/decisions.js +4 -3
- package/dist/server/routes/incidents.js +7 -5
- package/dist/server/routes/memories.js +7 -7
- package/dist/server/routes/policies.js +3 -2
- package/dist/server/routes/predictions.js +12 -15
- package/dist/server/routes/processes.js +3 -2
- package/dist/server/routes/project-briefs.js +8 -7
- package/dist/server/routes/recall.js +95 -93
- package/dist/server/routes/skills.js +6 -5
- package/dist/server/types.d.ts +1 -1
- package/dist/server/validation.d.ts +1 -2
- package/dist/server/validation.js +7 -14
- package/dist/server-detect.js +72 -58
- package/dist/server.d.ts +2 -2
- package/dist/server.js +131 -117
- package/dist/shared.d.ts +26 -17
- package/dist/shared.js +102 -104
- package/dist/skills.d.ts +3 -3
- package/dist/skills.js +88 -72
- package/dist/store/audit-event.d.ts +2 -2
- package/dist/store/audit-event.js +1 -1
- package/dist/store/candidates.d.ts +2 -2
- package/dist/store/candidates.js +4 -3
- package/dist/store/conflicts.js +30 -22
- package/dist/store/delete-and-batch.d.ts +11 -14
- package/dist/store/delete-and-batch.js +40 -91
- package/dist/store/entry-reads.d.ts +17 -25
- package/dist/store/entry-reads.js +59 -40
- package/dist/store/entry-row.d.ts +6 -24
- package/dist/store/entry-row.js +6 -24
- package/dist/store/entry-writes.d.ts +6 -7
- package/dist/store/entry-writes.js +13 -11
- package/dist/store/handoffs.d.ts +1 -1
- package/dist/store/handoffs.js +7 -10
- package/dist/store/index-and-stats.d.ts +2 -6
- package/dist/store/index-and-stats.js +4 -10
- package/dist/store/mirrors.d.ts +6 -19
- package/dist/store/mirrors.js +14 -39
- package/dist/store/open.js +9 -31
- package/dist/store/rows.d.ts +5 -11
- package/dist/store/rows.js +6 -11
- package/dist/store/search-rows.d.ts +17 -34
- package/dist/store/search-rows.js +34 -56
- package/dist/store/sessions.d.ts +4 -5
- package/dist/store/sessions.js +5 -6
- package/dist/store/summaries.d.ts +13 -17
- package/dist/store/summaries.js +26 -70
- package/dist/support-bundle.js +4 -8
- package/dist/tenant.d.ts +1 -5
- package/dist/token-ledger.d.ts +1 -1
- package/dist/token-ledger.js +3 -5
- package/dist/trace.js +1 -3
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/working-memory.d.ts +1 -1
- 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
|
@@ -24,12 +24,7 @@ function byDepthThenScore(a, b) {
|
|
|
24
24
|
return score;
|
|
25
25
|
return a.id < b.id ? -1 : a.id > b.id ? 1 : 0;
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
export function createDeliveryRecorder(init) {
|
|
29
|
-
const startedMs = Date.now();
|
|
30
|
-
const ts = evalNow().toISOString();
|
|
31
|
-
// Test-only fault injection, as HIPPO_FAKE_NOW is for time.
|
|
32
|
-
const fault = envTestDeliveryFault();
|
|
27
|
+
function readPayload(init) {
|
|
33
28
|
const payloadSession = hookPayloadSessionId(init.stdinText);
|
|
34
29
|
const subagent = isSubagentPayload(init.stdinText);
|
|
35
30
|
const envSession = init.envSessionId !== undefined && init.envSessionId !== '' ? init.envSessionId : null;
|
|
@@ -40,148 +35,127 @@ export function createDeliveryRecorder(init) {
|
|
|
40
35
|
const sessionState = subagent
|
|
41
36
|
? 'subagent'
|
|
42
37
|
: payloadSession !== null ? 'payload' : envSession !== null ? 'env' : 'missing';
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
emittedCount: rows.filter((r) => r.outcome === 'emitted').length,
|
|
132
|
-
rejectedCount: rejected.length + undecided,
|
|
133
|
-
rejectedUnlisted: overflow + undecided,
|
|
134
|
-
sectionsShown: shown,
|
|
135
|
-
sectionsDropped: dropped,
|
|
136
|
-
budgetTokens: facts?.budgetTokens ?? 0,
|
|
137
|
-
selectedTokens: [...picked.values()].reduce((sum, p) => sum + p.tokens, 0),
|
|
138
|
-
injectedTokens: emitted !== null ? estimateTokens(emitted) : 0,
|
|
139
|
-
staticHash: outcome.staticHash ?? null,
|
|
140
|
-
recallHash: outcome.recallHash ?? null,
|
|
141
|
-
emittedHash: emitted !== null ? blockHash(emitted) : null,
|
|
142
|
-
elapsedMs: Math.max(0, Date.now() - startedMs),
|
|
143
|
-
candidates: rows,
|
|
144
|
-
};
|
|
38
|
+
return { payloadSession, envSession, prompt, hostTurnId, hookEvent, sessionState };
|
|
39
|
+
}
|
|
40
|
+
function rejectId(state, id, stage, reason, score, tokens) {
|
|
41
|
+
const held = state.candidates.get(id);
|
|
42
|
+
// First rejection wins; an id never offered is not a candidate of this call.
|
|
43
|
+
if (!held || held.reason !== null)
|
|
44
|
+
return;
|
|
45
|
+
state.candidates.set(id, { ...held, stage, reason, score, tokens });
|
|
46
|
+
}
|
|
47
|
+
function pickedRows(state) {
|
|
48
|
+
const staticReused = state.outcome.staticReused === true;
|
|
49
|
+
const rows = [];
|
|
50
|
+
for (const p of state.picked.values()) {
|
|
51
|
+
const held = state.candidates.get(p.entry.id);
|
|
52
|
+
rows.push({
|
|
53
|
+
memoryId: p.entry.id,
|
|
54
|
+
sourceStore: p.sourceStore,
|
|
55
|
+
pool: p.promptRecall ? 'prompt-recall' : held?.pool === 'pin' || p.entry.pinned ? 'pin' : 'recent',
|
|
56
|
+
stage: 'final',
|
|
57
|
+
outcome: staticReused && !p.promptRecall ? 'reused' : 'emitted',
|
|
58
|
+
reason: null,
|
|
59
|
+
rank: p.rank,
|
|
60
|
+
score: p.score,
|
|
61
|
+
tokens: p.tokens,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return rows;
|
|
65
|
+
}
|
|
66
|
+
/** Rejected candidates deepest first, capped at DELIVERY_REJECTED_ROW_CAP rows. */
|
|
67
|
+
function rejectedRows(state) {
|
|
68
|
+
const rejected = [];
|
|
69
|
+
let undecided = 0;
|
|
70
|
+
for (const c of state.candidates.values()) {
|
|
71
|
+
if (state.picked.has(c.id))
|
|
72
|
+
continue;
|
|
73
|
+
if (c.reason === null)
|
|
74
|
+
undecided += 1;
|
|
75
|
+
else
|
|
76
|
+
rejected.push(c);
|
|
77
|
+
}
|
|
78
|
+
rejected.sort(byDepthThenScore);
|
|
79
|
+
const rows = rejected.slice(0, DELIVERY_REJECTED_ROW_CAP).map((c) => ({
|
|
80
|
+
memoryId: c.id, sourceStore: c.sourceStore, pool: c.pool, stage: c.stage ?? 'load', outcome: 'rejected',
|
|
81
|
+
reason: c.reason, rank: null, score: c.score, tokens: c.tokens,
|
|
82
|
+
}));
|
|
83
|
+
const overflow = Math.max(0, rejected.length - DELIVERY_REJECTED_ROW_CAP);
|
|
84
|
+
return { rows, rejected: rejected.length, undecided, overflow };
|
|
85
|
+
}
|
|
86
|
+
function buildEvent(init, payload, state, ts, startedMs) {
|
|
87
|
+
const { facts, outcome } = state;
|
|
88
|
+
const picked = pickedRows(state);
|
|
89
|
+
const rejected = rejectedRows(state);
|
|
90
|
+
const rows = [...picked, ...rejected.rows];
|
|
91
|
+
const emitted = outcome.emittedText ?? null;
|
|
92
|
+
return {
|
|
93
|
+
ts,
|
|
94
|
+
tenantId: init.tenantId,
|
|
95
|
+
runtime: payload.hostTurnId !== null ? 'codex' : payload.hookEvent !== null ? 'claude-code' : 'unknown',
|
|
96
|
+
eventType: payload.hookEvent === 'UserPromptSubmit' ? 'prompt-submit' : 'pinned-manual',
|
|
97
|
+
surface: 'hook',
|
|
98
|
+
storeHash: init.storeHash,
|
|
99
|
+
writeStore: init.writeStore,
|
|
100
|
+
projectHash: facts !== null && facts.projectName !== '' ? blockHash(facts.projectName) : null,
|
|
101
|
+
sessionId: payload.payloadSession ?? payload.envSession,
|
|
102
|
+
sessionState: payload.sessionState,
|
|
103
|
+
hostTurnId: payload.hostTurnId,
|
|
104
|
+
promptHash: payload.prompt !== null ? blockHash(payload.prompt) : null,
|
|
105
|
+
promptLength: payload.prompt?.length ?? 0,
|
|
106
|
+
queryHash: null,
|
|
107
|
+
recallTraceId: null,
|
|
108
|
+
blockState: state.disabledSeen ? 'disabled' : outcome.state,
|
|
109
|
+
promptRecall: facts?.promptRecall === true,
|
|
110
|
+
consideredCount: new Set([...state.candidates.keys(), ...state.picked.keys()]).size,
|
|
111
|
+
filteredCount: state.filtered.size,
|
|
112
|
+
selectedCount: state.picked.size,
|
|
113
|
+
emittedCount: rows.filter((r) => r.outcome === 'emitted').length,
|
|
114
|
+
rejectedCount: rejected.rejected + rejected.undecided,
|
|
115
|
+
rejectedUnlisted: rejected.overflow + rejected.undecided,
|
|
116
|
+
sectionsShown: state.shown,
|
|
117
|
+
sectionsDropped: state.dropped,
|
|
118
|
+
budgetTokens: facts?.budgetTokens ?? 0,
|
|
119
|
+
selectedTokens: [...state.picked.values()].reduce((sum, p) => sum + p.tokens, 0),
|
|
120
|
+
injectedTokens: emitted !== null ? estimateTokens(emitted) : 0,
|
|
121
|
+
staticHash: outcome.staticHash ?? null,
|
|
122
|
+
recallHash: outcome.recallHash ?? null,
|
|
123
|
+
emittedHash: emitted !== null ? blockHash(emitted) : null,
|
|
124
|
+
elapsedMs: Math.max(0, Date.now() - startedMs),
|
|
125
|
+
candidates: rows,
|
|
145
126
|
};
|
|
127
|
+
}
|
|
128
|
+
/** The candidate-tracking half of the observer, each method run through `guard`. */
|
|
129
|
+
function candidateMethods(state, guard) {
|
|
146
130
|
return {
|
|
147
|
-
root: init.root,
|
|
148
|
-
facts: (f) => guard(() => { facts = { ...f }; }),
|
|
149
|
-
sections: (s, d) => guard(() => { shown = s; dropped = d; }),
|
|
150
|
-
watchAdmit: (admit) => (e) => {
|
|
151
|
-
const ok = admit(e);
|
|
152
|
-
if (!ok)
|
|
153
|
-
guard(() => { filtered.add(e.id); });
|
|
154
|
-
return ok;
|
|
155
|
-
},
|
|
156
131
|
qualityDropped: (e, isGlobal) => guard(() => {
|
|
157
|
-
filtered.add(e.id);
|
|
158
|
-
if (!candidates.has(e.id)) {
|
|
159
|
-
candidates.set(e.id, {
|
|
132
|
+
state.filtered.add(e.id);
|
|
133
|
+
if (!state.candidates.has(e.id)) {
|
|
134
|
+
state.candidates.set(e.id, {
|
|
160
135
|
id: e.id, sourceStore: storeOf(isGlobal), pool: 'recent', stage: null, reason: null, score: null, tokens: null,
|
|
161
136
|
});
|
|
162
137
|
}
|
|
163
|
-
rejectId(e.id, 'load', 'quality', null, null);
|
|
138
|
+
rejectId(state, e.id, 'load', 'quality', null, null);
|
|
164
139
|
}),
|
|
165
|
-
disabled: () => guard(() => { disabledSeen = true; }),
|
|
166
140
|
offer: (entries, isGlobal, pool) => guard(() => {
|
|
167
141
|
for (const e of entries) {
|
|
168
|
-
const held = candidates.get(e.id);
|
|
142
|
+
const held = state.candidates.get(e.id);
|
|
169
143
|
const wanted = pool ?? (e.pinned ? 'pin' : 'recent');
|
|
170
144
|
// A loaded recent row the prompt-recall gate then judges belongs to that pool.
|
|
171
145
|
const relabel = held !== undefined && held.reason === null && held.pool === 'recent' && wanted === 'prompt-recall';
|
|
172
146
|
if (held !== undefined && !relabel)
|
|
173
147
|
continue;
|
|
174
|
-
candidates.set(e.id, {
|
|
148
|
+
state.candidates.set(e.id, {
|
|
175
149
|
id: e.id, sourceStore: storeOf(isGlobal), pool: wanted, stage: null, reason: null, score: null, tokens: null,
|
|
176
150
|
});
|
|
177
151
|
}
|
|
178
152
|
}),
|
|
179
|
-
reject: (e, stage, reason, score, tokens) => guard(() => rejectId(e.id, stage, reason, score ?? null, tokens ?? null)),
|
|
153
|
+
reject: (e, stage, reason, score, tokens) => guard(() => rejectId(state, e.id, stage, reason, score ?? null, tokens ?? null)),
|
|
180
154
|
dropMissing: (before, after, stage, reason) => guard(() => {
|
|
181
155
|
const kept = new Set(after.map((e) => e.id));
|
|
182
156
|
for (const e of before)
|
|
183
157
|
if (!kept.has(e.id))
|
|
184
|
-
rejectId(e.id, stage, reason, null, null);
|
|
158
|
+
rejectId(state, e.id, stage, reason, null, null);
|
|
185
159
|
}),
|
|
186
160
|
gated: (prompt, items, gate, kept) => guard(() => {
|
|
187
161
|
const keptIds = new Set(kept.map((g) => g.item.id));
|
|
@@ -190,29 +164,74 @@ export function createDeliveryRecorder(init) {
|
|
|
190
164
|
continue;
|
|
191
165
|
const { score, shared } = scoreOverlap(prompt, c.tokens, gate.metric);
|
|
192
166
|
const cleared = score >= gate.threshold && shared >= gate.minShared;
|
|
193
|
-
rejectId(c.id, 'gate', cleared ? 'gate-max-items' : 'gate-below-threshold', score, null);
|
|
167
|
+
rejectId(state, c.id, 'gate', cleared ? 'gate-max-items' : 'gate-below-threshold', score, null);
|
|
194
168
|
}
|
|
195
169
|
}),
|
|
196
170
|
selected: (items) => guard(() => {
|
|
197
|
-
picked.clear();
|
|
171
|
+
state.picked.clear();
|
|
198
172
|
items.forEach((r, i) => {
|
|
199
|
-
picked.set(r.entry.id, {
|
|
173
|
+
state.picked.set(r.entry.id, {
|
|
200
174
|
entry: r.entry, rank: i + 1, score: r.score, tokens: r.tokens,
|
|
201
175
|
sourceStore: storeOf(r.isGlobal), promptRecall: r.promptRecall === true,
|
|
202
176
|
});
|
|
203
177
|
});
|
|
204
178
|
}),
|
|
205
|
-
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
/** A recorder for one call; every observer method is guarded, and a throw marks it broken instead of escaping. */
|
|
182
|
+
export function createDeliveryRecorder(init) {
|
|
183
|
+
const startedMs = Date.now();
|
|
184
|
+
const ts = evalNow().toISOString();
|
|
185
|
+
// Test-only fault injection, as HIPPO_FAKE_NOW is for time.
|
|
186
|
+
const fault = envTestDeliveryFault();
|
|
187
|
+
const payload = readPayload(init);
|
|
188
|
+
const state = {
|
|
189
|
+
candidates: new Map(), picked: new Map(), filtered: new Set(), facts: null, shown: 0, dropped: 0,
|
|
190
|
+
disabledSeen: false, outcome: { state: 'empty' }, broken: null, flushed: false,
|
|
191
|
+
};
|
|
192
|
+
const guard = (fn) => {
|
|
193
|
+
if (state.broken !== null)
|
|
194
|
+
return;
|
|
195
|
+
try {
|
|
196
|
+
if (fault === 'observe')
|
|
197
|
+
throw new Error('injected observe fault');
|
|
198
|
+
fn();
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
state.broken = error instanceof Error ? error.message : String(error);
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
const { qualityDropped, offer, reject, dropMissing, gated, selected } = candidateMethods(state, guard);
|
|
205
|
+
return {
|
|
206
|
+
root: init.root,
|
|
207
|
+
facts: (f) => guard(() => { state.facts = { ...f }; }),
|
|
208
|
+
sections: (s, d) => guard(() => { state.shown = s; state.dropped = d; }),
|
|
209
|
+
watchAdmit: (admit) => (e) => {
|
|
210
|
+
const ok = admit(e);
|
|
211
|
+
if (!ok)
|
|
212
|
+
guard(() => { state.filtered.add(e.id); });
|
|
213
|
+
return ok;
|
|
214
|
+
},
|
|
215
|
+
qualityDropped,
|
|
216
|
+
disabled: () => guard(() => { state.disabledSeen = true; }),
|
|
217
|
+
offer,
|
|
218
|
+
reject,
|
|
219
|
+
dropMissing,
|
|
220
|
+
gated,
|
|
221
|
+
selected,
|
|
222
|
+
delivered: (o) => guard(() => { state.outcome = { ...o }; }),
|
|
206
223
|
flush: (write) => {
|
|
207
|
-
if (flushed)
|
|
224
|
+
if (state.flushed)
|
|
208
225
|
return;
|
|
209
|
-
flushed = true;
|
|
210
|
-
if (broken !== null) {
|
|
226
|
+
state.flushed = true;
|
|
227
|
+
if (state.broken !== null) {
|
|
211
228
|
// Same pinned `[hippo] delivery ledger` hook stderr line as recall-trace.ts's write failure.
|
|
212
|
-
console.error(`[hippo] delivery ledger skipped: recorder failed: ${broken}`);
|
|
229
|
+
console.error(`[hippo] delivery ledger skipped: recorder failed: ${state.broken}`);
|
|
213
230
|
return;
|
|
214
231
|
}
|
|
215
|
-
|
|
232
|
+
if (fault === 'build')
|
|
233
|
+
throw new Error('injected build fault');
|
|
234
|
+
const input = buildEvent(init, payload, state, ts, startedMs);
|
|
216
235
|
if (fault === 'flush')
|
|
217
236
|
throw new Error('injected flush fault');
|
|
218
237
|
write(input);
|
package/dist/doctor.js
CHANGED
|
@@ -15,7 +15,7 @@ import { openHippoDbReadOnly, closeHippoDb, getSchemaVersion, getCurrentSchemaVe
|
|
|
15
15
|
import { REPLAY_AFTER_MS, TRANSCRIPT_FILL_WINDOW_MS } from './compaction-record.js';
|
|
16
16
|
import { isEmbeddingAvailable } from './local-embedding.js';
|
|
17
17
|
import { CODEX_TRUST_LINE, codexHomeDir, isCodexPresent, isJsonObject } from './hooks/shared.js';
|
|
18
|
-
import {
|
|
18
|
+
import { planProjectRepair } from './project-merge.js';
|
|
19
19
|
import { resolveTenantId } from './tenant.js';
|
|
20
20
|
/** Minimum Node.js version hippo supports (package.json engines). */
|
|
21
21
|
export const MIN_NODE = '22.16.0';
|
|
@@ -131,16 +131,21 @@ function compactionsCheck(db, now) {
|
|
|
131
131
|
: { id: 'compactions', status: 'warn', detail: `cannot read the compaction records: ${message}` };
|
|
132
132
|
}
|
|
133
133
|
}
|
|
134
|
-
/**
|
|
134
|
+
/** Old project tags in the global store, counted by the repair's own plan so a truly user-global merge never warns. */
|
|
135
135
|
function projectsCheck(globalRoot) {
|
|
136
136
|
let db = null;
|
|
137
137
|
try {
|
|
138
138
|
db = openHippoDbReadOnly(globalRoot);
|
|
139
|
-
const r =
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
const r = planProjectRepair(db, globalRoot, resolveTenantId({}));
|
|
140
|
+
const found = [
|
|
141
|
+
r.copies.length > 0 ? `${r.copies.length} imported notes copied under the wrong project` : '',
|
|
142
|
+
r.folds.length > 0 ? `old project names that now resolve to another project: ${r.folds.map((f) => `${f.from} -> ${f.into}`).join(', ')}` : '',
|
|
143
|
+
r.collisions.length > 0 ? `old project names shared by several projects, folded by hand only: ${r.collisions.map((c) => `${c.name} (${c.ids.join(', ')})`).join('; ')}` : '',
|
|
144
|
+
r.toProject.length + r.setAside.length > 0 ? `${r.toProject.length + r.setAside.length} merged memories tagged user-global` : '',
|
|
145
|
+
].filter((s) => s !== '');
|
|
146
|
+
return found.length === 0
|
|
147
|
+
? { id: 'projects', status: 'pass', detail: 'no duplicate or out-of-date project tags in the global store' }
|
|
148
|
+
: { id: 'projects', status: 'warn', detail: `global store: ${found.join('; ')}`, fix: 'hippo projects repair --global (dry run; add --apply to write)' };
|
|
144
149
|
}
|
|
145
150
|
catch (err) {
|
|
146
151
|
return { id: 'projects', status: 'info', detail: `project tags not checked (${err instanceof Error ? err.message : String(err)})` };
|
|
@@ -150,124 +155,138 @@ function projectsCheck(globalRoot) {
|
|
|
150
155
|
closeHippoDb(db);
|
|
151
156
|
}
|
|
152
157
|
}
|
|
158
|
+
function nodeCheck(node) {
|
|
159
|
+
return versionAtLeast(node, MIN_NODE)
|
|
160
|
+
? { id: 'node', status: 'pass', detail: `Node.js ${node}` }
|
|
161
|
+
: { id: 'node', status: 'fail', detail: `Node.js ${node} is older than ${MIN_NODE}`, fix: `Install Node.js ${MIN_NODE} or newer` };
|
|
162
|
+
}
|
|
163
|
+
/** The store the checks run against (project first, then global), and the check that says which. */
|
|
164
|
+
function storeCheck(local, globalRoot, hasGlobal) {
|
|
165
|
+
if (local !== null && isInitialized(local)) {
|
|
166
|
+
return { store: local, check: { id: 'store', status: 'pass', detail: `project store at ${local}${hasGlobal ? ` (global store at ${globalRoot} too)` : ''}` } };
|
|
167
|
+
}
|
|
168
|
+
if (local !== null) {
|
|
169
|
+
// The walk stops at the first .hippo it finds, so a bare one (no hippo.db) blocks a parent or global store too.
|
|
170
|
+
return { store: null, check: { id: 'store', status: 'fail', detail: `${local} has no hippo.db, so hippo commands run here stop at it`, fix: `run hippo init in ${path.dirname(local)}, or remove that .hippo folder` } };
|
|
171
|
+
}
|
|
172
|
+
if (hasGlobal) {
|
|
173
|
+
return { store: globalRoot, check: { id: 'store', status: 'warn', detail: `no project store here; using the global store at ${globalRoot}`, fix: 'hippo init (in the project root)' } };
|
|
174
|
+
}
|
|
175
|
+
return { store: null, check: { id: 'store', status: 'fail', detail: 'no hippo store found (project or global)', fix: 'hippo init (in the project root), or hippo init --global' } };
|
|
176
|
+
}
|
|
177
|
+
function schemaCheck(have, want) {
|
|
178
|
+
return have === want
|
|
179
|
+
? { id: 'schema', status: 'pass', detail: `database schema v${have}` }
|
|
180
|
+
: have > want
|
|
181
|
+
? { id: 'schema', status: 'fail', detail: `database schema v${have} is newer than this hippo (v${want})`, fix: 'npm install -g hippo-memory@latest' }
|
|
182
|
+
: { id: 'schema', status: 'info', detail: `database schema v${have}; hippo migrates it to v${want} on the next write` };
|
|
183
|
+
}
|
|
184
|
+
function memoriesCheck(db) {
|
|
185
|
+
const memories = countTableRows(db, 'memories');
|
|
186
|
+
const dormant = countTableRows(db, 'dormant_memories');
|
|
187
|
+
const memoryCheck = { id: 'memories', status: 'info', detail: `${memories ?? '?'} memories${dormant !== null ? `, ${dormant} dormant` : ''}` };
|
|
188
|
+
if (memories === 0) {
|
|
189
|
+
memoryCheck.status = 'warn';
|
|
190
|
+
memoryCheck.fix = 'hippo learn --git (seed lessons from git history)';
|
|
191
|
+
}
|
|
192
|
+
return memoryCheck;
|
|
193
|
+
}
|
|
194
|
+
function tokensCheck(db, since) {
|
|
195
|
+
try {
|
|
196
|
+
// SAFETY: COUNT/SUM aggregate row.
|
|
197
|
+
const row = db.prepare(`SELECT COUNT(CASE WHEN event = 'inject' THEN 1 END) AS n,
|
|
198
|
+
COALESCE(SUM(CASE WHEN event = 'inject' THEN tokens END), 0) AS t,
|
|
199
|
+
COALESCE(SUM(CASE WHEN event = 'reread' THEN tokens END), 0) AS r
|
|
200
|
+
FROM token_ledger WHERE ts >= ?`).get(since);
|
|
201
|
+
return {
|
|
202
|
+
id: 'tokens',
|
|
203
|
+
status: 'info',
|
|
204
|
+
detail: `${Number(row?.n ?? 0)} memory blocks sent to agents in 7 days, about ${Number(row?.t ?? 0)} tokens sent and ${Number(row?.r ?? 0)} re-read by later model calls (hippo tokens for detail)`,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
catch {
|
|
208
|
+
return { id: 'tokens', status: 'info', detail: 'no token ledger yet (created on the next write)' };
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/** Checks that read the store's database; a failed open becomes a failed schema check after whatever already passed. */
|
|
212
|
+
function databaseChecks(store, now) {
|
|
213
|
+
const checks = [];
|
|
214
|
+
let db = null;
|
|
215
|
+
try {
|
|
216
|
+
db = openHippoDbReadOnly(store);
|
|
217
|
+
const have = getSchemaVersion(db);
|
|
218
|
+
checks.push(schemaCheck(have, getCurrentSchemaVersion()));
|
|
219
|
+
checks.push(memoriesCheck(db));
|
|
220
|
+
const since = new Date(now.getTime() - 7 * 86_400_000).toISOString();
|
|
221
|
+
checks.push(tokensCheck(db, since));
|
|
222
|
+
checks.push(failuresCheck(db, since, have));
|
|
223
|
+
checks.push(sleepCheck(db, now));
|
|
224
|
+
checks.push(compactionsCheck(db, now));
|
|
225
|
+
}
|
|
226
|
+
catch (err) {
|
|
227
|
+
checks.push({
|
|
228
|
+
id: 'schema',
|
|
229
|
+
status: 'fail',
|
|
230
|
+
detail: `cannot open the database: ${err instanceof Error ? err.message : String(err)}`,
|
|
231
|
+
fix: err instanceof IncompatibleBinaryError ? 'npm install -g hippo-memory@latest' : 'check file permissions on the .hippo folder',
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
finally {
|
|
235
|
+
if (db !== null)
|
|
236
|
+
closeHippoDb(db);
|
|
237
|
+
}
|
|
238
|
+
return checks;
|
|
239
|
+
}
|
|
240
|
+
// Each hook and what it does, so a partial install says what is missing.
|
|
241
|
+
const CLAUDE_CODE_HOOKS = [
|
|
242
|
+
['hippo context --pinned-only', 'per-prompt memory'],
|
|
243
|
+
['hippo session-end', 'session-end capture and sleep'],
|
|
244
|
+
['hippo pre-compact', 'compaction snapshot and memories request'],
|
|
245
|
+
['hippo compact-resume', 'resume after compaction'],
|
|
246
|
+
['hippo post-compact', 'saving the memories a compaction lists'],
|
|
247
|
+
['hippo capture-error', 'failed-tool capture'],
|
|
248
|
+
];
|
|
249
|
+
function claudeCodeCheck(home) {
|
|
250
|
+
const claudeDir = path.join(home, '.claude');
|
|
251
|
+
if (!fs.existsSync(claudeDir)) {
|
|
252
|
+
return { id: 'claude-code', status: 'info', detail: 'Claude Code not found; other agents can use hippo over MCP (hippo mcp)' };
|
|
253
|
+
}
|
|
254
|
+
const settings = readJson(path.join(claudeDir, 'settings.json'));
|
|
255
|
+
const text = settings === null ? '' : JSON.stringify(settings);
|
|
256
|
+
const viaPlugin = /hippo-memory@/.test(text);
|
|
257
|
+
const missing = CLAUDE_CODE_HOOKS.filter(([marker]) => !text.includes(marker)).map(([, what]) => what);
|
|
258
|
+
if (viaPlugin) {
|
|
259
|
+
return { id: 'claude-code', status: 'pass', detail: 'Claude Code: hippo plugin enabled (all hooks, including compaction and failed-tool capture)' };
|
|
260
|
+
}
|
|
261
|
+
if (missing.length === 0) {
|
|
262
|
+
return { id: 'claude-code', status: 'pass', detail: 'Claude Code: all hippo hooks installed, including compaction and failed-tool capture' };
|
|
263
|
+
}
|
|
264
|
+
if (missing.length === CLAUDE_CODE_HOOKS.length) {
|
|
265
|
+
return { id: 'claude-code', status: 'warn', detail: 'Claude Code found, but no hippo hooks are installed', fix: 'hippo hook install claude-code' };
|
|
266
|
+
}
|
|
267
|
+
return { id: 'claude-code', status: 'warn', detail: `Claude Code: hippo hooks missing for ${missing.join(', ')}`, fix: 'hippo hook install claude-code (adds only what is missing)' };
|
|
268
|
+
}
|
|
153
269
|
/** Run every check. Never throws for a broken install; broken parts become failed checks. */
|
|
154
270
|
export function runDoctor(opts) {
|
|
155
271
|
const cwd = opts.cwd ?? process.cwd();
|
|
156
272
|
const home = opts.home ?? os.homedir();
|
|
157
273
|
const now = opts.now ?? new Date();
|
|
158
274
|
const checks = [];
|
|
159
|
-
|
|
160
|
-
checks.push(versionAtLeast(node, MIN_NODE)
|
|
161
|
-
? { id: 'node', status: 'pass', detail: `Node.js ${node}` }
|
|
162
|
-
: { id: 'node', status: 'fail', detail: `Node.js ${node} is older than ${MIN_NODE}`, fix: `Install Node.js ${MIN_NODE} or newer` });
|
|
275
|
+
checks.push(nodeCheck(opts.nodeVersion ?? process.versions.node));
|
|
163
276
|
const local = findHippoStoreDir(cwd);
|
|
164
277
|
const globalRoot = getGlobalRoot();
|
|
165
278
|
const hasGlobal = isInitialized(globalRoot);
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
checks.push(
|
|
170
|
-
}
|
|
171
|
-
else if (local !== null) {
|
|
172
|
-
// The walk stops at the first .hippo it finds, so a bare one (no hippo.db) blocks a parent or global store too.
|
|
173
|
-
checks.push({ id: 'store', status: 'fail', detail: `${local} has no hippo.db, so hippo commands run here stop at it`, fix: `run hippo init in ${path.dirname(local)}, or remove that .hippo folder` });
|
|
174
|
-
}
|
|
175
|
-
else if (hasGlobal) {
|
|
176
|
-
store = globalRoot;
|
|
177
|
-
checks.push({ id: 'store', status: 'warn', detail: `no project store here; using the global store at ${globalRoot}`, fix: 'hippo init (in the project root)' });
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
checks.push({ id: 'store', status: 'fail', detail: 'no hippo store found (project or global)', fix: 'hippo init (in the project root), or hippo init --global' });
|
|
181
|
-
}
|
|
182
|
-
if (store !== null) {
|
|
183
|
-
let db = null;
|
|
184
|
-
try {
|
|
185
|
-
db = openHippoDbReadOnly(store);
|
|
186
|
-
const have = getSchemaVersion(db);
|
|
187
|
-
const want = getCurrentSchemaVersion();
|
|
188
|
-
checks.push(have === want
|
|
189
|
-
? { id: 'schema', status: 'pass', detail: `database schema v${have}` }
|
|
190
|
-
: have > want
|
|
191
|
-
? { id: 'schema', status: 'fail', detail: `database schema v${have} is newer than this hippo (v${want})`, fix: 'npm install -g hippo-memory@latest' }
|
|
192
|
-
: { id: 'schema', status: 'info', detail: `database schema v${have}; hippo migrates it to v${want} on the next write` });
|
|
193
|
-
const memories = countTableRows(db, 'memories');
|
|
194
|
-
const dormant = countTableRows(db, 'dormant_memories');
|
|
195
|
-
const memoryCheck = { id: 'memories', status: 'info', detail: `${memories ?? '?'} memories${dormant !== null ? `, ${dormant} dormant` : ''}` };
|
|
196
|
-
if (memories === 0) {
|
|
197
|
-
memoryCheck.status = 'warn';
|
|
198
|
-
memoryCheck.fix = 'hippo learn --git (seed lessons from git history)';
|
|
199
|
-
}
|
|
200
|
-
checks.push(memoryCheck);
|
|
201
|
-
const since = new Date(now.getTime() - 7 * 86_400_000).toISOString();
|
|
202
|
-
try {
|
|
203
|
-
// SAFETY: COUNT/SUM aggregate row.
|
|
204
|
-
const row = db.prepare(`SELECT COUNT(CASE WHEN event = 'inject' THEN 1 END) AS n,
|
|
205
|
-
COALESCE(SUM(CASE WHEN event = 'inject' THEN tokens END), 0) AS t,
|
|
206
|
-
COALESCE(SUM(CASE WHEN event = 'reread' THEN tokens END), 0) AS r
|
|
207
|
-
FROM token_ledger WHERE ts >= ?`).get(since);
|
|
208
|
-
checks.push({
|
|
209
|
-
id: 'tokens',
|
|
210
|
-
status: 'info',
|
|
211
|
-
detail: `${Number(row?.n ?? 0)} memory blocks sent to agents in 7 days, about ${Number(row?.t ?? 0)} tokens sent and ${Number(row?.r ?? 0)} re-read by later model calls (hippo tokens for detail)`,
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
catch {
|
|
215
|
-
checks.push({ id: 'tokens', status: 'info', detail: 'no token ledger yet (created on the next write)' });
|
|
216
|
-
}
|
|
217
|
-
checks.push(failuresCheck(db, since, have));
|
|
218
|
-
checks.push(sleepCheck(db, now));
|
|
219
|
-
checks.push(compactionsCheck(db, now));
|
|
220
|
-
}
|
|
221
|
-
catch (err) {
|
|
222
|
-
checks.push({
|
|
223
|
-
id: 'schema',
|
|
224
|
-
status: 'fail',
|
|
225
|
-
detail: `cannot open the database: ${err instanceof Error ? err.message : String(err)}`,
|
|
226
|
-
fix: err instanceof IncompatibleBinaryError ? 'npm install -g hippo-memory@latest' : 'check file permissions on the .hippo folder',
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
finally {
|
|
230
|
-
if (db !== null)
|
|
231
|
-
closeHippoDb(db);
|
|
232
|
-
}
|
|
233
|
-
}
|
|
279
|
+
const { store, check } = storeCheck(local, globalRoot, hasGlobal);
|
|
280
|
+
checks.push(check);
|
|
281
|
+
if (store !== null)
|
|
282
|
+
checks.push(...databaseChecks(store, now));
|
|
234
283
|
const holdoutRateBp = store === null ? 0 : loadConfig(store).pilot.holdoutRateBp;
|
|
235
284
|
if (holdoutRateBp > 0) {
|
|
236
285
|
checks.push({ id: 'pilot', status: 'info', detail: `pilot holdout on: about ${holdoutRateBp / 100}% of sessions get no memories pushed by hippo (pilot.holdoutRateBp=${holdoutRateBp})` });
|
|
237
286
|
}
|
|
238
287
|
if (hasGlobal)
|
|
239
288
|
checks.push(projectsCheck(globalRoot));
|
|
240
|
-
|
|
241
|
-
if (fs.existsSync(claudeDir)) {
|
|
242
|
-
const settings = readJson(path.join(claudeDir, 'settings.json'));
|
|
243
|
-
const text = settings === null ? '' : JSON.stringify(settings);
|
|
244
|
-
const viaPlugin = /hippo-memory@/.test(text);
|
|
245
|
-
// Each hook and what it does, so a partial install says what is missing.
|
|
246
|
-
const hooks = [
|
|
247
|
-
['hippo context --pinned-only', 'per-prompt memory'],
|
|
248
|
-
['hippo session-end', 'session-end capture and sleep'],
|
|
249
|
-
['hippo pre-compact', 'compaction snapshot and memories request'],
|
|
250
|
-
['hippo compact-resume', 'resume after compaction'],
|
|
251
|
-
['hippo post-compact', 'saving the memories a compaction lists'],
|
|
252
|
-
['hippo capture-error', 'failed-tool capture'],
|
|
253
|
-
];
|
|
254
|
-
const missing = hooks.filter(([marker]) => !text.includes(marker)).map(([, what]) => what);
|
|
255
|
-
if (viaPlugin) {
|
|
256
|
-
checks.push({ id: 'claude-code', status: 'pass', detail: 'Claude Code: hippo plugin enabled (all hooks, including compaction and failed-tool capture)' });
|
|
257
|
-
}
|
|
258
|
-
else if (missing.length === 0) {
|
|
259
|
-
checks.push({ id: 'claude-code', status: 'pass', detail: 'Claude Code: all hippo hooks installed, including compaction and failed-tool capture' });
|
|
260
|
-
}
|
|
261
|
-
else if (missing.length === hooks.length) {
|
|
262
|
-
checks.push({ id: 'claude-code', status: 'warn', detail: 'Claude Code found, but no hippo hooks are installed', fix: 'hippo hook install claude-code' });
|
|
263
|
-
}
|
|
264
|
-
else {
|
|
265
|
-
checks.push({ id: 'claude-code', status: 'warn', detail: `Claude Code: hippo hooks missing for ${missing.join(', ')}`, fix: 'hippo hook install claude-code (adds only what is missing)' });
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
else {
|
|
269
|
-
checks.push({ id: 'claude-code', status: 'info', detail: 'Claude Code not found; other agents can use hippo over MCP (hippo mcp)' });
|
|
270
|
-
}
|
|
289
|
+
checks.push(claudeCodeCheck(home));
|
|
271
290
|
if (isCodexPresent(home))
|
|
272
291
|
checks.push(codexCheck(home));
|
|
273
292
|
checks.push({ id: 'embeddings', status: 'info', detail: isEmbeddingAvailable() ? 'local embeddings available (hybrid search)' : 'embeddings not installed; recall uses BM25 (optional: hippo embed --help)' });
|
package/dist/dormant.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { rejectionDigest } from './rejection.js';
|
|
2
|
+
import { escapeLike } from './escape.js';
|
|
2
3
|
const DEFAULT_LIST_LIMIT = 20;
|
|
3
4
|
/**
|
|
4
5
|
* Insert (or refresh) the dormant snapshot for `move.entry`. Does not touch
|
|
@@ -17,10 +18,6 @@ export function insertDormantRow(db, move) {
|
|
|
17
18
|
dormant_at = excluded.dormant_at
|
|
18
19
|
`).run(move.entry.tenantId, move.entry.id, move.entry.content, JSON.stringify(move.entry), move.reason, move.strength, move.dormantAt);
|
|
19
20
|
}
|
|
20
|
-
/** Escape LIKE metacharacters so a search term matches literally (ESCAPE '\'). */
|
|
21
|
-
function escapeLike(term) {
|
|
22
|
-
return term.replace(/[\\%_]/g, (ch) => `\\${ch}`);
|
|
23
|
-
}
|
|
24
21
|
/**
|
|
25
22
|
* Parse a stored snapshot back into a MemoryEntry, or null when the row no
|
|
26
23
|
* longer matches the snapshot it carries (edited by hand, or truncated).
|