hippo-memory 1.62.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 +9 -0
- package/dist/agent-memories/apply.d.ts +2 -0
- package/dist/agent-memories/apply.js +23 -1
- package/dist/agent-memories/claude-code.d.ts +2 -1
- package/dist/agent-memories/claude-code.js +4 -4
- package/dist/agent-memories/report.d.ts +1 -0
- package/dist/agent-memories/report.js +2 -2
- package/dist/agent-memories/sync.js +37 -23
- 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 +7 -12
- 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 +4 -3
- package/dist/api/context-select.js +9 -11
- package/dist/api/context-types.d.ts +12 -17
- package/dist/api/context.d.ts +3 -2
- package/dist/api/context.js +20 -20
- package/dist/api/dormant.js +1 -1
- package/dist/api/drill-down.d.ts +4 -4
- package/dist/api/drill-down.js +22 -10
- 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/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 +7 -18
- package/dist/api/remember.d.ts +2 -2
- package/dist/api/sleep.d.ts +7 -35
- package/dist/api/sleep.js +3 -2
- 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 +1 -1
- package/dist/capture/compact.js +9 -13
- 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/curate.d.ts +1 -1
- package/dist/cli/curate.js +8 -24
- package/dist/cli/dag.js +5 -9
- package/dist/cli/decisions.js +10 -21
- package/dist/cli/explain.js +2 -1
- package/dist/cli/goals.js +1 -1
- package/dist/cli/init.js +1 -1
- package/dist/cli/playbooks.js +4 -9
- package/dist/cli/projects.js +3 -1
- package/dist/cli/recall.js +2 -1
- package/dist/cli/remember.js +6 -18
- package/dist/cli/session-hooks.js +21 -40
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.js +2 -6
- 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 +7 -12
- package/dist/cli/transfer.js +8 -13
- 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 -0
- 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 +6 -9
- package/dist/dag.d.ts +10 -21
- package/dist/dag.js +23 -73
- 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 +6 -6
- package/dist/dedupe.js +86 -61
- package/dist/delivery-recorder.js +154 -135
- package/dist/doctor.js +119 -104
- 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 +5 -6
- 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 +1 -1
- 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 +12 -14
- 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 +3 -3
- package/dist/project-briefs.d.ts +2 -3
- package/dist/project-briefs.js +9 -13
- package/dist/project-identity.d.ts +22 -9
- package/dist/project-identity.js +47 -12
- package/dist/project-merge.d.ts +17 -2
- package/dist/project-merge.js +109 -27
- 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 +17 -17
- package/dist/shared.js +93 -97
- package/dist/skills.d.ts +3 -3
- package/dist/skills.js +8 -8
- 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 +31 -59
- 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
package/dist/decisions.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Decision first-class object.
|
|
3
3
|
*
|
|
4
4
|
* `hippo decide` used to write only a tagged memory (tags ['decision'], source
|
|
5
5
|
* 'decision') with a 90-day half-life, so an in-force decision decayed out of
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* Tenant scoping: every helper requires tenantId. BEFORE INSERT/UPDATE triggers
|
|
17
17
|
* enforce decisions.tenant_id == the referenced memory's tenant_id, and a
|
|
18
18
|
* superseded_by same-tenant trigger makes cross-tenant supersession
|
|
19
|
-
* unrepresentable. Mirrors the
|
|
19
|
+
* unrepresentable. Mirrors the predictions pattern (src/predictions.ts).
|
|
20
20
|
*
|
|
21
21
|
* Dual-write atomicity: `saveDecision` writes the memory + decisions row (and,
|
|
22
22
|
* when superseding, the old row's UPDATE) inside writeEntry's SAVEPOINT
|
|
@@ -80,7 +80,7 @@ function buildDecisionMemory(hippoRoot, tenantId, opts) {
|
|
|
80
80
|
// supersedesDecisionId (e.g. superseding id 1 on an empty store, where the
|
|
81
81
|
// INSERT below would itself become id 1), making the row supersede itself.
|
|
82
82
|
// Validating first means the new row is never a candidate for its own
|
|
83
|
-
// supersede UPDATE.
|
|
83
|
+
// supersede UPDATE.
|
|
84
84
|
function preflightDecisionSupersede(db, tenantId, supersedesId) {
|
|
85
85
|
// SAFETY: row shape matches the single `status` column named in the SELECT below.
|
|
86
86
|
const pred = db.prepare(`SELECT status FROM decisions WHERE id = ? AND tenant_id = ?`).get(supersedesId, tenantId);
|
|
@@ -111,7 +111,7 @@ function supersedeDecisionRow(db, tenantId, actor, supersedesId, decisionId, now
|
|
|
111
111
|
WHERE id = ? AND tenant_id = ? AND status = 'active' AND id != ?
|
|
112
112
|
`).run(decisionId, now, supersedesId, tenantId, decisionId);
|
|
113
113
|
if (sup.changes === 0) {
|
|
114
|
-
throw new
|
|
114
|
+
throw new ConflictError(`saveDecision: decision ${supersedesId} could not be superseded (no longer active or self-reference).`);
|
|
115
115
|
}
|
|
116
116
|
appendAuditEvent(db, {
|
|
117
117
|
tenantId,
|
|
@@ -179,7 +179,7 @@ export function saveDecision(hippoRoot, tenantId, opts, actor = 'cli') {
|
|
|
179
179
|
},
|
|
180
180
|
// Post-commit hook: mark the tenant's graph dirty AFTER the DB row commits
|
|
181
181
|
// but BEFORE the markdown mirrors are written, so a mirror-write failure can
|
|
182
|
-
// never leave a committed save unflagged
|
|
182
|
+
// never leave a committed save unflagged. markGraphDirty is fail-soft.
|
|
183
183
|
afterCommit: () => markGraphDirty(hippoRoot, tenantId, mem.id),
|
|
184
184
|
});
|
|
185
185
|
if (!savedRow) {
|
|
@@ -232,7 +232,7 @@ export function closeDecision(hippoRoot, tenantId, id, actor = 'cli') {
|
|
|
232
232
|
// Closing removes the object from the graph. Remove its rows DIRECTLY (deterministic),
|
|
233
233
|
// not only via an enqueued rebuild whose queue item is lost if the mirror is later
|
|
234
234
|
// forgotten (the queue row cascade-deletes with the memory), which would leave the closed
|
|
235
|
-
// object stale and could block that forget
|
|
235
|
+
// object stale and could block that forget. Still enqueue when a mirror exists
|
|
236
236
|
// so a concurrent rebuild re-derives consistently (harmless if it also runs).
|
|
237
237
|
removeGraphEntitiesForObject(hippoRoot, tenantId, 'decision', closed.id);
|
|
238
238
|
if (closed.memoryId) {
|
package/dist/dedupe.js
CHANGED
|
@@ -22,7 +22,9 @@
|
|
|
22
22
|
*/
|
|
23
23
|
import { textOverlap } from './tokenize.js';
|
|
24
24
|
import { loadAllEntries } from './store/entry-reads.js';
|
|
25
|
-
import {
|
|
25
|
+
import { deleteEntryOn, memoriesBackingObjects } from './store/delete-and-batch.js';
|
|
26
|
+
import { openStore } from './store/open.js';
|
|
27
|
+
import { closeHippoDb } from './db.js';
|
|
26
28
|
import { compareEntryIdentity } from './compare.js';
|
|
27
29
|
import { canAutoDelete } from './memory.js';
|
|
28
30
|
import { derivationPartitionKey } from './recall-scope.js';
|
|
@@ -56,6 +58,71 @@ export function strengthBucket(strength) {
|
|
|
56
58
|
const s = strength ?? 0;
|
|
57
59
|
return Number.isFinite(s) ? Math.round(s / STRENGTH_TIE_EPSILON) : 0;
|
|
58
60
|
}
|
|
61
|
+
// finiteCount mirrors strengthBucket's non-finite hardening on the
|
|
62
|
+
// retrieval leg: a NaN retrieval_count would make the comparator return
|
|
63
|
+
// NaN and break the total order the same way a NaN bucket would.
|
|
64
|
+
// Unreachable via the schema (non-nullable INTEGER column), so this is
|
|
65
|
+
// symmetry, not a live bug.
|
|
66
|
+
const finiteCount = (n) => Number.isFinite(n ?? 0) ? (n ?? 0) : 0;
|
|
67
|
+
/** The v1.26.3 survivor total order: strength bucket desc, then retrieval_count desc, then compareEntryIdentity. */
|
|
68
|
+
function survivorOrder(a, b) {
|
|
69
|
+
const bucketDiff = strengthBucket(b.strength) - strengthBucket(a.strength);
|
|
70
|
+
if (bucketDiff !== 0)
|
|
71
|
+
return bucketDiff;
|
|
72
|
+
const retrievalDiff = finiteCount(b.retrieval_count) - finiteCount(a.retrieval_count);
|
|
73
|
+
if (retrievalDiff !== 0)
|
|
74
|
+
return retrievalDiff;
|
|
75
|
+
return compareEntryIdentity(a, b);
|
|
76
|
+
}
|
|
77
|
+
// Tenant partition (mirrors consolidate.ts mergeCandidatesByTenant and
|
|
78
|
+
// dag.ts unparentedByTenant): group by tenantId BEFORE the sort so a
|
|
79
|
+
// duplicate pair can never form across tenants. Map preserves insertion
|
|
80
|
+
// order, so a single-tenant store (every row 'default') gets exactly one
|
|
81
|
+
// group and the sort plus pair loop below run byte-identical to the
|
|
82
|
+
// pre-fix global pass.
|
|
83
|
+
function entriesByPartition(entries) {
|
|
84
|
+
const entriesByTenant = new Map();
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
// EI2: also split by restricted scope, else a private copy can delete the readable one.
|
|
87
|
+
const key = derivationPartitionKey(entry.tenantId, entry.scope, entry.origin_project);
|
|
88
|
+
const bucket = entriesByTenant.get(key);
|
|
89
|
+
if (bucket)
|
|
90
|
+
bucket.push(entry);
|
|
91
|
+
else
|
|
92
|
+
entriesByTenant.set(key, [entry]);
|
|
93
|
+
}
|
|
94
|
+
return entriesByTenant;
|
|
95
|
+
}
|
|
96
|
+
function dedupPair(kept, dropped) {
|
|
97
|
+
return {
|
|
98
|
+
kept: kept.id,
|
|
99
|
+
keptContent: kept.content,
|
|
100
|
+
keptLayer: kept.layer,
|
|
101
|
+
keptStrength: kept.strength ?? 0,
|
|
102
|
+
removed: dropped.id,
|
|
103
|
+
removedContent: dropped.content,
|
|
104
|
+
removedLayer: dropped.layer,
|
|
105
|
+
removedStrength: dropped.strength ?? 0,
|
|
106
|
+
similarity: textOverlap(kept.content, dropped.content),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/** Pairs within one partition, already in survivor order, grouped by survivor; marks each loser in `removed`. */
|
|
110
|
+
function partitionPairs(tenantEntries, removed, backing) {
|
|
111
|
+
const groups = new Map();
|
|
112
|
+
for (const entry of tenantEntries) {
|
|
113
|
+
const key = duplicateKey(entry.content);
|
|
114
|
+
const group = groups.get(key);
|
|
115
|
+
if (!group) {
|
|
116
|
+
groups.set(key, { survivor: entry, pairs: [] });
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
if (!canAutoDelete(entry) || backing.has(entry.id))
|
|
120
|
+
continue;
|
|
121
|
+
removed.add(entry.id);
|
|
122
|
+
group.pairs.push(dedupPair(group.survivor, entry));
|
|
123
|
+
}
|
|
124
|
+
return [...groups.values()].flatMap((group) => group.pairs);
|
|
125
|
+
}
|
|
59
126
|
/**
|
|
60
127
|
* Scan the store for duplicates and remove the weaker copy: same text apart
|
|
61
128
|
* from spacing, since a near-duplicate can differ in a value (port, version,
|
|
@@ -70,28 +137,7 @@ export function deduplicateStore(hippoRoot, options = {}) {
|
|
|
70
137
|
// Only current distilled rows compete: raw rows are append-only (the delete
|
|
71
138
|
// trigger would abort sleep mid-loop) and superseded rows are history, as in consolidate.ts.
|
|
72
139
|
const entries = loadAllEntries(hippoRoot).filter((e) => e.kind === 'distilled' && !e.superseded_by);
|
|
73
|
-
|
|
74
|
-
// dag.ts unparentedByTenant): group by tenantId BEFORE the sort so a
|
|
75
|
-
// duplicate pair can never form across tenants. Map preserves insertion
|
|
76
|
-
// order, so a single-tenant store (every row 'default') gets exactly one
|
|
77
|
-
// group and the sort plus pair loop below run byte-identical to the
|
|
78
|
-
// pre-fix global pass.
|
|
79
|
-
const entriesByTenant = new Map();
|
|
80
|
-
for (const entry of entries) {
|
|
81
|
-
// EI2: also split by restricted scope, else a private copy can delete the readable one.
|
|
82
|
-
const key = derivationPartitionKey(entry.tenantId, entry.scope, entry.origin_project);
|
|
83
|
-
const bucket = entriesByTenant.get(key);
|
|
84
|
-
if (bucket)
|
|
85
|
-
bucket.push(entry);
|
|
86
|
-
else
|
|
87
|
-
entriesByTenant.set(key, [entry]);
|
|
88
|
-
}
|
|
89
|
-
// finiteCount mirrors strengthBucket's non-finite hardening on the
|
|
90
|
-
// retrieval leg: a NaN retrieval_count would make the comparator return
|
|
91
|
-
// NaN and break the total order the same way a NaN bucket would.
|
|
92
|
-
// Unreachable via the schema (non-nullable INTEGER column), so this is
|
|
93
|
-
// symmetry, not a live bug.
|
|
94
|
-
const finiteCount = (n) => Number.isFinite(n ?? 0) ? (n ?? 0) : 0;
|
|
140
|
+
const entriesByTenant = entriesByPartition(entries);
|
|
95
141
|
// Shared across tenant groups: safe because memory ids are globally
|
|
96
142
|
// unique (crypto.randomUUID at creation), so an id in `removed` can never
|
|
97
143
|
// collide with another tenant's row, and deleteEntry below deletes by
|
|
@@ -100,45 +146,24 @@ export function deduplicateStore(hippoRoot, options = {}) {
|
|
|
100
146
|
const pairs = [];
|
|
101
147
|
const backing = memoriesBackingObjects(hippoRoot);
|
|
102
148
|
for (const tenantEntries of entriesByTenant.values()) {
|
|
103
|
-
// The
|
|
104
|
-
//
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
tenantEntries.sort((a, b) => {
|
|
108
|
-
const bucketDiff = strengthBucket(b.strength) - strengthBucket(a.strength);
|
|
109
|
-
if (bucketDiff !== 0)
|
|
110
|
-
return bucketDiff;
|
|
111
|
-
const retrievalDiff = finiteCount(b.retrieval_count) - finiteCount(a.retrieval_count);
|
|
112
|
-
if (retrievalDiff !== 0)
|
|
113
|
-
return retrievalDiff;
|
|
114
|
-
return compareEntryIdentity(a, b);
|
|
115
|
-
});
|
|
116
|
-
const texts = tenantEntries.map((e) => duplicateKey(e.content));
|
|
117
|
-
for (let i = 0; i < tenantEntries.length; i++) {
|
|
118
|
-
if (removed.has(tenantEntries[i].id))
|
|
119
|
-
continue;
|
|
120
|
-
for (let j = i + 1; j < tenantEntries.length; j++) {
|
|
121
|
-
if (removed.has(tenantEntries[j].id) || !canAutoDelete(tenantEntries[j]) || backing.has(tenantEntries[j].id))
|
|
122
|
-
continue;
|
|
123
|
-
if (texts[j] !== texts[i])
|
|
124
|
-
continue;
|
|
125
|
-
const similarity = textOverlap(tenantEntries[i].content, tenantEntries[j].content);
|
|
126
|
-
removed.add(tenantEntries[j].id);
|
|
127
|
-
pairs.push({
|
|
128
|
-
kept: tenantEntries[i].id,
|
|
129
|
-
keptContent: tenantEntries[i].content,
|
|
130
|
-
keptLayer: tenantEntries[i].layer,
|
|
131
|
-
keptStrength: tenantEntries[i].strength ?? 0,
|
|
132
|
-
removed: tenantEntries[j].id,
|
|
133
|
-
removedContent: tenantEntries[j].content,
|
|
134
|
-
removedLayer: tenantEntries[j].layer,
|
|
135
|
-
removedStrength: tenantEntries[j].strength ?? 0,
|
|
136
|
-
similarity,
|
|
137
|
-
});
|
|
138
|
-
}
|
|
139
|
-
}
|
|
149
|
+
// The survivor total order (see the file-level docstring), scoped per
|
|
150
|
+
// tenant group: the total order holds within a tenant only, matching the partition above.
|
|
151
|
+
tenantEntries.sort(survivorOrder);
|
|
152
|
+
pairs.push(...partitionPairs(tenantEntries, removed, backing));
|
|
140
153
|
}
|
|
141
|
-
const done = dryRun ? pairs :
|
|
154
|
+
const done = dryRun ? pairs : deletePairs(hippoRoot, pairs, options.actor);
|
|
142
155
|
return { removed: done.length, pairs: done };
|
|
143
156
|
}
|
|
157
|
+
/** The pairs whose loser was deleted; one store handle, one transaction per delete. */
|
|
158
|
+
function deletePairs(hippoRoot, pairs, actor) {
|
|
159
|
+
if (pairs.length === 0)
|
|
160
|
+
return [];
|
|
161
|
+
const db = openStore(hippoRoot);
|
|
162
|
+
try {
|
|
163
|
+
return pairs.filter((p) => deleteEntryOn(db, hippoRoot, p.removed, { actor, reason: `dedup: duplicate of ${p.kept}`, automatic: true }));
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
closeHippoDb(db);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
144
169
|
//# sourceMappingURL=dedupe.js.map
|
|
@@ -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);
|