hippo-memory 1.72.0 → 2.0.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 +6 -4
- package/dist/agent-memories/apply.js +5 -5
- package/dist/api/assemble.js +2 -1
- package/dist/api/audit.d.ts +7 -7
- package/dist/api/audit.js +19 -17
- package/dist/api/auth.d.ts +5 -6
- package/dist/api/auth.js +28 -33
- package/dist/api/conflicts.js +1 -1
- package/dist/api/context-auto.js +4 -2
- package/dist/api/context-select.js +8 -20
- package/dist/api/context.js +12 -10
- package/dist/api/drill-down.js +2 -1
- package/dist/api/forget.d.ts +2 -2
- package/dist/api/forget.js +6 -7
- package/dist/api/graph.js +2 -1
- package/dist/api/learn.d.ts +1 -1
- package/dist/api/learn.js +45 -44
- package/dist/api/memories.d.ts +4 -2
- package/dist/api/memories.js +16 -5
- package/dist/api/objects.js +2 -1
- package/dist/api/outcome.d.ts +5 -5
- package/dist/api/outcome.js +16 -15
- package/dist/api/predictions.js +2 -1
- package/dist/api/promote.d.ts +3 -3
- package/dist/api/promote.js +25 -27
- package/dist/api/prompt-hook.d.ts +3 -1
- package/dist/api/prompt-hook.js +1 -0
- package/dist/api/quarantine.js +2 -1
- package/dist/api/recall-core.js +8 -3
- package/dist/api/recall-pipeline.js +17 -17
- package/dist/api/recall-types.d.ts +3 -1
- package/dist/api/recall.js +32 -29
- package/dist/api/remember-local.d.ts +5 -2
- package/dist/api/remember-local.js +14 -6
- package/dist/api/remember.d.ts +5 -2
- package/dist/api/remember.js +16 -14
- package/dist/api/session-end.d.ts +24 -0
- package/dist/api/session-end.js +31 -1
- package/dist/api/sleep-run.js +5 -4
- package/dist/api/sleep.js +4 -3
- package/dist/api/tokens.js +1 -1
- package/dist/api/types.d.ts +1 -7
- package/dist/capture/command.js +1 -1
- package/dist/capture/compaction-spool.d.ts +3 -5
- package/dist/capture/compaction-spool.js +41 -46
- package/dist/capture/session-end-caller.js +1 -1
- package/dist/capture/session-worker.js +19 -6
- package/dist/cli/audit.js +10 -10
- package/dist/cli/auth.js +4 -4
- package/dist/cli/client.d.ts +1 -2
- package/dist/cli/client.js +13 -13
- package/dist/cli/context.js +1 -1
- package/dist/cli/curate.d.ts +1 -1
- package/dist/cli/curate.js +9 -9
- package/dist/cli/decisions.js +4 -5
- package/dist/cli/hook-runtime.d.ts +3 -1
- package/dist/cli/hook-runtime.js +2 -2
- package/dist/cli/init.d.ts +1 -1
- package/dist/cli/init.js +9 -9
- package/dist/cli/object-verbs.d.ts +2 -2
- package/dist/cli/object-verbs.js +4 -4
- package/dist/cli/predictions.js +2 -3
- package/dist/cli/quality-repair-once.d.ts +1 -1
- package/dist/cli/quality-repair-once.js +2 -2
- package/dist/cli/refine-llm.js +2 -1
- package/dist/cli/remember.js +5 -5
- package/dist/cli/serve.js +9 -4
- package/dist/cli/session-hooks.d.ts +0 -4
- package/dist/cli/session-hooks.js +57 -66
- package/dist/cli/setup.d.ts +1 -1
- package/dist/cli/setup.js +2 -2
- package/dist/cli/shared.d.ts +1 -1
- package/dist/cli/shared.js +2 -2
- package/dist/cli/sleep.js +2 -2
- package/dist/cli/transfer.d.ts +3 -3
- package/dist/cli/transfer.js +34 -15
- package/dist/cli/usage.d.ts +1 -1
- package/dist/cli/usage.js +1 -1
- package/dist/cli/verb-row.d.ts +2 -0
- package/dist/cli/verbs/health.js +2 -0
- package/dist/cli/verbs/memory.js +43 -51
- package/dist/cli/verbs/objects.js +6 -3
- package/dist/cli/verbs/sessions.js +4 -2
- package/dist/cli.js +1 -2
- package/dist/connectors/dlq.js +2 -1
- package/dist/connectors/github/deletion.js +2 -1
- package/dist/connectors/github/ingest.js +2 -1
- package/dist/connectors/github/tenant-routing.js +2 -1
- package/dist/connectors/github/webhook.js +4 -2
- package/dist/connectors/ingest.js +2 -1
- package/dist/connectors/slack/deletion.js +2 -1
- package/dist/connectors/slack/ingest.js +2 -1
- package/dist/connectors/slack/tenant-routing.js +2 -1
- package/dist/consolidate/conflicts.js +1 -1
- package/dist/consolidate/dag.js +14 -9
- package/dist/consolidate/dedupe.js +1 -1
- package/dist/consolidate/memory-value.js +1 -1
- package/dist/consolidate/merge.js +6 -6
- package/dist/consolidate/sleep.js +1 -1
- package/dist/consolidate/traces.js +2 -2
- package/dist/core/config.js +15 -16
- package/dist/core/markdown.js +2 -2
- package/dist/core/memory.d.ts +4 -4
- package/dist/core/memory.js +10 -5
- package/dist/core/physics.d.ts +3 -1
- package/dist/core/physics.js +17 -2
- package/dist/core/project-identity.js +2 -1
- package/dist/core/search-types.d.ts +1 -1
- package/dist/dashboard/dashboard-actions.d.ts +2 -2
- package/dist/dashboard/dashboard-actions.js +5 -5
- package/dist/dashboard/dashboard-queries.js +2 -7
- package/dist/dashboard/dashboard-snapshot.js +1 -3
- package/dist/dashboard/dashboard.js +16 -5
- package/dist/db/connect.d.ts +2 -1
- package/dist/db/connect.js +2 -1
- package/dist/db/migrate.js +14 -7
- package/dist/db/migrations/v11.js +1 -0
- package/dist/db/migrations/v14.js +1 -0
- package/dist/db/physics-state.d.ts +0 -4
- package/dist/db/physics-state.js +5 -31
- package/dist/db/request-stores.d.ts +1 -0
- package/dist/db/request-stores.js +2 -1
- package/dist/db/vector-store.js +2 -1
- package/dist/doctor.d.ts +3 -0
- package/dist/doctor.js +7 -7
- package/dist/embeddings/local.js +2 -2
- package/dist/embeddings/provider.js +1 -10
- package/dist/entry/store-kit.d.ts +37 -0
- package/dist/entry/store-kit.js +25 -0
- package/dist/entry/testing.d.ts +6 -0
- package/dist/entry/testing.js +7 -0
- package/dist/eval/judgment.js +1 -1
- package/dist/hooks/json-hooks.js +6 -2
- package/dist/importers/core.js +3 -3
- package/dist/importers/vault.d.ts +1 -1
- package/dist/importers/vault.js +18 -18
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/learn/autolearn.d.ts +6 -2
- package/dist/learn/autolearn.js +8 -6
- package/dist/learn/extract.js +1 -1
- package/dist/learn/invalidation.d.ts +2 -1
- package/dist/learn/invalidation.js +10 -5
- package/dist/mcp/memory-tools.d.ts +1 -1
- package/dist/mcp/memory-tools.js +6 -9
- package/dist/mcp/request.js +20 -19
- package/dist/mcp/stdio.js +13 -8
- package/dist/rerankers/cross-encoder.js +10 -3
- package/dist/search/embed-memories.d.ts +5 -2
- package/dist/search/embed-memories.js +40 -28
- package/dist/search/engine.d.ts +8 -0
- package/dist/search/engine.js +10 -0
- package/dist/search/vector.js +5 -5
- package/dist/server/auth.d.ts +1 -1
- package/dist/server/auth.js +7 -8
- package/dist/server/boot.d.ts +2 -1
- package/dist/server/boot.js +28 -19
- package/dist/server/client-ip.d.ts +9 -4
- package/dist/server/client-ip.js +35 -17
- package/dist/server/route-table.js +5 -6
- package/dist/server/routes/memories.js +4 -2
- package/dist/server/sleep-offload.js +3 -3
- package/dist/server/tls.d.ts +7 -2
- package/dist/server/tls.js +17 -5
- package/dist/server/types.d.ts +7 -2
- package/dist/server.d.ts +4 -41
- package/dist/server.js +3 -30
- package/dist/sharing/global-store.d.ts +4 -5
- package/dist/sharing/global-store.js +6 -11
- package/dist/sharing/global-sync.js +1 -1
- package/dist/sharing/search-both.js +9 -2
- package/dist/sharing/share.js +2 -4
- package/dist/store/ambient.js +2 -1
- package/dist/store/candidates.js +18 -8
- package/dist/store/cards.js +5 -4
- package/dist/store/compactions-record.js +8 -7
- package/dist/store/conflicts.d.ts +7 -1
- package/dist/store/conflicts.js +6 -5
- package/dist/store/connectors/dlq.js +2 -1
- package/dist/store/connectors/event-log.d.ts +13 -0
- package/dist/store/connectors/event-log.js +24 -0
- package/dist/store/connectors/github.js +6 -8
- package/dist/store/connectors/slack.js +6 -10
- package/dist/store/dashboard-reads.js +1 -0
- package/dist/store/delete-and-batch.js +5 -4
- package/dist/store/delivery-recorder.d.ts +2 -2
- package/dist/store/delivery-recorder.js +1 -6
- package/dist/store/descendant-page.js +2 -1
- package/dist/store/dormant.js +7 -3
- package/dist/store/entry-reads.d.ts +25 -3
- package/dist/store/entry-reads.js +43 -12
- package/dist/store/entry-row.js +9 -2
- package/dist/store/entry-writes.d.ts +10 -7
- package/dist/store/entry-writes.js +23 -11
- package/dist/store/goals.js +3 -2
- package/dist/store/graph-reads.js +5 -4
- package/dist/store/graph-view-rows.js +2 -1
- package/dist/store/half-life.js +2 -2
- package/dist/store/handoffs.d.ts +9 -3
- package/dist/store/handoffs.js +4 -3
- package/dist/store/index.d.ts +2 -2
- package/dist/store/index.js +1 -1
- package/dist/store/mirrors.js +9 -4
- package/dist/store/open.d.ts +2 -1
- package/dist/store/open.js +2 -1
- package/dist/store/port.d.ts +59 -3
- package/dist/store/quality-repair.js +1 -1
- package/dist/store/recall-trace.js +2 -1
- package/dist/store/rejected-values.js +14 -9
- package/dist/store/rows.js +2 -2
- package/dist/store/rule-sql.d.ts +2 -0
- package/dist/store/rule-sql.js +12 -6
- package/dist/store/search-rows.d.ts +5 -5
- package/dist/store/search-rows.js +9 -12
- package/dist/store/set-aside.js +1 -1
- package/dist/store/sqlite/connector-writes-group.js +3 -1
- package/dist/store/sqlite/entry-writes-group.d.ts +3 -3
- package/dist/store/sqlite/entry-writes-group.js +1 -1
- package/dist/store/sqlite/held-connection.d.ts +2 -0
- package/dist/store/sqlite/held-connection.js +1 -1
- package/dist/store/sqlite/last-recall-group.d.ts +13 -0
- package/dist/store/sqlite/last-recall-group.js +57 -0
- package/dist/store/sqlite/local.d.ts +0 -17
- package/dist/store/sqlite/local.js +0 -31
- package/dist/store/sqlite/maintenance-groups.d.ts +6 -0
- package/dist/store/sqlite/maintenance-groups.js +60 -0
- package/dist/store/sqlite/objects-group.js +14 -6
- package/dist/store/sqlite/store.d.ts +1 -8
- package/dist/store/sqlite/store.js +13 -24
- package/dist/store/summaries.d.ts +7 -0
- package/dist/store/summaries.js +49 -7
- package/dist/store/token-ledger-rows.js +3 -2
- package/dist/store/vector-index.js +5 -2
- package/dist/util/anthropic-messages.d.ts +3 -1
- package/dist/util/anthropic-messages.js +4 -2
- package/dist/util/atomic-write.d.ts +5 -2
- package/dist/util/atomic-write.js +12 -8
- package/dist/util/env.d.ts +10 -1
- package/dist/util/env.js +10 -1
- package/dist/util/format.d.ts +2 -0
- package/dist/util/format.js +4 -0
- package/dist/util/request-scope.d.ts +6 -2
- package/dist/util/request-scope.js +7 -3
- package/dist/util/spawn-argv.d.ts +9 -0
- package/dist/util/spawn-argv.js +57 -0
- package/dist/util/sql.d.ts +3 -0
- package/dist/util/sql.js +6 -0
- package/dist/util/vector-codec.d.ts +4 -0
- package/dist/util/vector-codec.js +15 -0
- package/dist/util/version.d.ts +1 -1
- package/dist/util/version.js +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 +3 -1
- package/dist/api/on-store.d.ts +0 -16
- package/dist/api/on-store.js +0 -38
- package/dist/cli/lenient-id.d.ts +0 -2
- package/dist/cli/lenient-id.js +0 -13
|
@@ -23,7 +23,7 @@ export function retireHeldTexts(run) {
|
|
|
23
23
|
const rejectedIn = (tenantId) => (text) => run.tombstones.find(tenantId, rejectionDigest(text)) !== null;
|
|
24
24
|
for (let i = survivors.length - 1; i >= 0; i--) {
|
|
25
25
|
const row = survivors[i];
|
|
26
|
-
const successor = run.retirable(row) ? successorAfterRetirement(row, byId, rejectedIn(row.
|
|
26
|
+
const successor = run.retirable(row) ? successorAfterRetirement(row, byId, rejectedIn(row.tenant_id)) : undefined;
|
|
27
27
|
if (successor === undefined)
|
|
28
28
|
continue;
|
|
29
29
|
run.result.details.push(` ✂️ ${row.id} held a retired text${successor ? `, ${successor.id} holds the rest` : ''}`);
|
|
@@ -51,7 +51,7 @@ function partitionMergeCandidates(survivors) {
|
|
|
51
51
|
// Map keeps insertion order, so a single-tenant store gets one partition in the original order.
|
|
52
52
|
const mergeCandidatesByTenant = new Map();
|
|
53
53
|
for (const entry of mergeCandidates) {
|
|
54
|
-
const key = derivationPartitionKey(entry.
|
|
54
|
+
const key = derivationPartitionKey(entry.tenant_id, entry.scope, entry.origin_project);
|
|
55
55
|
const bucket = mergeCandidatesByTenant.get(key);
|
|
56
56
|
if (bucket)
|
|
57
57
|
bucket.push(entry);
|
|
@@ -70,7 +70,7 @@ export function mergePass(run) {
|
|
|
70
70
|
let mergesSkippedRejected = 0;
|
|
71
71
|
for (const [, tenantCandidates] of mergeCandidatesByTenant) {
|
|
72
72
|
const partition = {
|
|
73
|
-
tenantId: tenantCandidates[0].
|
|
73
|
+
tenantId: tenantCandidates[0].tenant_id,
|
|
74
74
|
scope: derivationScope(tenantCandidates[0].scope),
|
|
75
75
|
origin: tenantCandidates[0].origin_project,
|
|
76
76
|
};
|
|
@@ -156,8 +156,8 @@ function mergeRejected(run, semantic, cluster, related, used) {
|
|
|
156
156
|
const { tombstones } = run;
|
|
157
157
|
const newDigest = rejectionDigest(semantic.content);
|
|
158
158
|
const oldDigest = rejectionDigest(legacyMergeContents(related)); // tombstones from older releases hold this format's digest
|
|
159
|
-
const newHit = tombstones.find(semantic.
|
|
160
|
-
const tombstone = newHit ?? tombstones.find(semantic.
|
|
159
|
+
const newHit = tombstones.find(semantic.tenant_id, newDigest);
|
|
160
|
+
const tombstone = newHit ?? tombstones.find(semantic.tenant_id, oldDigest);
|
|
161
161
|
const mergeDigest = newHit ? newDigest : oldDigest;
|
|
162
162
|
if (!tombstone)
|
|
163
163
|
return false;
|
|
@@ -167,7 +167,7 @@ function mergeRejected(run, semantic, cluster, related, used) {
|
|
|
167
167
|
used.add(e.id);
|
|
168
168
|
try {
|
|
169
169
|
tombstones.audit({
|
|
170
|
-
tenantId: semantic.
|
|
170
|
+
tenantId: semantic.tenant_id,
|
|
171
171
|
actor: 'sleep',
|
|
172
172
|
op: 'reject_refusal',
|
|
173
173
|
metadata: {
|
|
@@ -156,7 +156,7 @@ function auditRescues(run, { rescuedEntries, rankById }) {
|
|
|
156
156
|
const rescueEvent = (entry) => {
|
|
157
157
|
const rank = rankById.get(entry.id);
|
|
158
158
|
return {
|
|
159
|
-
tenantId: entry.
|
|
159
|
+
tenantId: entry.tenant_id,
|
|
160
160
|
actor: 'sleep',
|
|
161
161
|
op: 'mv_rescue',
|
|
162
162
|
targetId: entry.id,
|
|
@@ -103,12 +103,12 @@ function sessionEnding(events) {
|
|
|
103
103
|
// sleep; check the tombstone under the entry's own stamped tenantId, never a guessed one.
|
|
104
104
|
function traceRejected(run, trace, sessionId) {
|
|
105
105
|
const traceDigest = rejectionDigest(trace.content);
|
|
106
|
-
const tombstone = run.tombstones.find(trace.
|
|
106
|
+
const tombstone = run.tombstones.find(trace.tenant_id, traceDigest);
|
|
107
107
|
if (!tombstone)
|
|
108
108
|
return false;
|
|
109
109
|
try {
|
|
110
110
|
run.tombstones.audit({
|
|
111
|
-
tenantId: trace.
|
|
111
|
+
tenantId: trace.tenant_id,
|
|
112
112
|
actor: 'sleep',
|
|
113
113
|
op: 'reject_refusal',
|
|
114
114
|
metadata: {
|
package/dist/core/config.js
CHANGED
|
@@ -252,17 +252,23 @@ function fileStamp(file) {
|
|
|
252
252
|
return null;
|
|
253
253
|
}
|
|
254
254
|
}
|
|
255
|
-
|
|
255
|
+
/** A config.json that exists must load: running on defaults would silently drop the user's settings, such as `sharedStore`. */
|
|
256
|
+
function readConfigFile(configPath) {
|
|
257
|
+
let raw;
|
|
256
258
|
try {
|
|
257
|
-
|
|
258
|
-
return configOverDefaults(raw);
|
|
259
|
+
raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
259
260
|
}
|
|
260
261
|
catch (err) {
|
|
261
|
-
|
|
262
|
-
log.warn(`failed to parse ${configPath}: ${errorMessage(err)}`);
|
|
263
|
-
}
|
|
264
|
-
return { ...DEFAULT_CONFIG };
|
|
262
|
+
throw new Error(`${configPath} could not be loaded: ${errorMessage(err)}. Fix the file or delete it to use the defaults.`, { cause: err });
|
|
265
263
|
}
|
|
264
|
+
if (!isJsonObject(raw)) {
|
|
265
|
+
throw new Error(`${configPath} must hold a JSON object. Fix the file or delete it to use the defaults.`);
|
|
266
|
+
}
|
|
267
|
+
// SAFETY: an object is all the shape check proves; configOverDefaults validates each key it reads.
|
|
268
|
+
return raw;
|
|
269
|
+
}
|
|
270
|
+
function parseConfigFile(configPath) {
|
|
271
|
+
return configOverDefaults(readConfigFile(configPath));
|
|
266
272
|
}
|
|
267
273
|
function configOverDefaults(raw) {
|
|
268
274
|
const basis = raw.decayBasis;
|
|
@@ -334,15 +340,8 @@ export function isSharedStore(hippoRoot) {
|
|
|
334
340
|
const stamp = fileStamp(configPath);
|
|
335
341
|
if (stamp === null || notSharedStamps.get(configPath) === stamp)
|
|
336
342
|
return false;
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
raw = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
340
|
-
}
|
|
341
|
-
catch (err) {
|
|
342
|
-
log.warn(`failed to read ${configPath}: ${errorMessage(err)} - sharedStore read as false.`);
|
|
343
|
-
return false;
|
|
344
|
-
}
|
|
345
|
-
if (raw?.sharedStore !== true) {
|
|
343
|
+
const raw = readConfigFile(configPath);
|
|
344
|
+
if (raw.sharedStore !== true) {
|
|
346
345
|
if (notSharedStamps.size >= PARSED_CONFIGS_MAX)
|
|
347
346
|
notSharedStamps.clear();
|
|
348
347
|
notSharedStamps.set(configPath, stamp);
|
package/dist/core/markdown.js
CHANGED
|
@@ -42,7 +42,7 @@ export function serializeEntry(entry) {
|
|
|
42
42
|
const frontmatter = entryFrontmatter(entry);
|
|
43
43
|
// Emit tenant_id only when not 'default' to keep diffs clean for the dominant
|
|
44
44
|
// single-tenant case (mirrors the plan's task 7 guidance).
|
|
45
|
-
const tenantId = entry.
|
|
45
|
+
const tenantId = entry.tenant_id ?? DEFAULT_TENANT_ID;
|
|
46
46
|
if (tenantId !== DEFAULT_TENANT_ID) {
|
|
47
47
|
frontmatter['tenant_id'] = tenantId;
|
|
48
48
|
}
|
|
@@ -98,7 +98,7 @@ export function deserializeEntry(raw) {
|
|
|
98
98
|
scope: data['scope'] === null || data['scope'] === undefined ? null : String(data['scope']),
|
|
99
99
|
owner: data['owner'] === null || data['owner'] === undefined ? null : String(data['owner']),
|
|
100
100
|
artifact_ref: data['artifact_ref'] === null || data['artifact_ref'] === undefined ? null : String(data['artifact_ref']),
|
|
101
|
-
|
|
101
|
+
tenant_id: data['tenant_id'] === null || data['tenant_id'] === undefined ? DEFAULT_TENANT_ID : String(data['tenant_id']),
|
|
102
102
|
origin_project: !('origin_project' in data) ? undefined : data['origin_project'] === null ? null : String(data['origin_project']),
|
|
103
103
|
};
|
|
104
104
|
}
|
package/dist/core/memory.d.ts
CHANGED
|
@@ -44,8 +44,8 @@ export interface MemoryEntry {
|
|
|
44
44
|
descendant_count?: number;
|
|
45
45
|
earliest_at?: string | null;
|
|
46
46
|
latest_at?: string | null;
|
|
47
|
-
/**
|
|
48
|
-
summary_dirty?:
|
|
47
|
+
/** True when a child was invalidated, superseded, forgotten or archived since the last rebuild; sleep clears it. Always false below level 2. */
|
|
48
|
+
summary_dirty?: boolean;
|
|
49
49
|
/** ISO 8601 time of the last successful rebuild, or null if never rebuilt. */
|
|
50
50
|
last_rebuilt_at?: string | null;
|
|
51
51
|
/** Count of successful rebuilds: 0 at the first buildDag write, bumped by each rebuild. */
|
|
@@ -56,7 +56,7 @@ export interface MemoryEntry {
|
|
|
56
56
|
scope: string | null;
|
|
57
57
|
owner: string | null;
|
|
58
58
|
artifact_ref: string | null;
|
|
59
|
-
|
|
59
|
+
tenant_id: string;
|
|
60
60
|
/** Owning project, lowercased; '' = user-global (injectable everywhere), null = none known (legacy or unnamed write), denied by ambient context.
|
|
61
61
|
* Stamped at write time by stampOriginProject (src/store/entry-row.ts); undefined only before the first write. */
|
|
62
62
|
origin_project?: string | null;
|
|
@@ -114,7 +114,7 @@ export interface ConfidenceFacets {
|
|
|
114
114
|
tier: ConfidenceLevel;
|
|
115
115
|
agedOut: boolean;
|
|
116
116
|
}
|
|
117
|
-
export declare function confidenceFacets(entry:
|
|
117
|
+
export declare function confidenceFacets(entry: ConfidenceInputs, now?: Date): ConfidenceFacets;
|
|
118
118
|
export declare function facetsOf(entry: ConfidenceInputs, now: Date): ConfidenceFacets;
|
|
119
119
|
export declare function confidenceLabel(entry: MemoryEntry, now?: Date): {
|
|
120
120
|
text: string;
|
package/dist/core/memory.js
CHANGED
|
@@ -261,7 +261,7 @@ export function createMemory(content, options = {}) {
|
|
|
261
261
|
scope: options.scope ?? null,
|
|
262
262
|
owner: options.owner ?? null,
|
|
263
263
|
artifact_ref: options.artifact_ref ?? null,
|
|
264
|
-
|
|
264
|
+
tenant_id: options.tenantId ?? DEFAULT_TENANT_ID,
|
|
265
265
|
};
|
|
266
266
|
entry.strength = calculateStrength(entry);
|
|
267
267
|
return entry;
|
|
@@ -310,8 +310,14 @@ export function schemaFitSourceOf(entries) {
|
|
|
310
310
|
}
|
|
311
311
|
return { rows: entries.length, tagCounts, contents: entries.map((e) => e.content) };
|
|
312
312
|
}
|
|
313
|
+
// Tokens of this length or shorter are filler for schema fit.
|
|
314
|
+
const FILLER_TOKEN_MAX_LENGTH = 3;
|
|
315
|
+
// Matching half the tags at average weight scores about 0.5, so the raw overlap ratio is doubled.
|
|
316
|
+
const TAG_OVERLAP_SCALE = 2;
|
|
317
|
+
// Floor on the matches that saturate the content score, so a tiny store cannot saturate it on one hit.
|
|
318
|
+
const CONTENT_ENOUGH_MIN_MATCHES = 5;
|
|
313
319
|
function significantTokens(text) {
|
|
314
|
-
return new Set(text.toLowerCase().replace(/[^\w\s]/g, ' ').split(/\s+/).filter((t) => t.length >
|
|
320
|
+
return new Set(text.toLowerCase().replace(/[^\w\s]/g, ' ').split(/\s+/).filter((t) => t.length > FILLER_TOKEN_MAX_LENGTH));
|
|
315
321
|
}
|
|
316
322
|
/** computeSchemaFit against `source`, for a caller that holds counts and texts but no rows. */
|
|
317
323
|
export function schemaFitFrom(content, tags, source) {
|
|
@@ -331,13 +337,12 @@ export function schemaFitFrom(content, tags, source) {
|
|
|
331
337
|
weightedOverlap += Math.log(N / freq) + 1;
|
|
332
338
|
totalWeight += maxIdf;
|
|
333
339
|
}
|
|
334
|
-
|
|
335
|
-
const tagScore = totalWeight > 0 ? Math.min(1, (weightedOverlap / totalWeight) * 2) : 0;
|
|
340
|
+
const tagScore = totalWeight > 0 ? Math.min(1, (weightedOverlap / totalWeight) * TAG_OVERLAP_SCALE) : 0;
|
|
336
341
|
const newTokens = significantTokens(content);
|
|
337
342
|
if (newTokens.size === 0)
|
|
338
343
|
return Math.min(1, Math.max(0, tagScore));
|
|
339
344
|
// The content score is capped at 1, which this many matching memories reach.
|
|
340
|
-
const enough = Math.max(
|
|
345
|
+
const enough = Math.max(CONTENT_ENOUGH_MIN_MATCHES, N * CONTENT_ENOUGH_FRACTION);
|
|
341
346
|
let contentMatches = 0;
|
|
342
347
|
for (const text of source.contents) {
|
|
343
348
|
const entryTokens = significantTokens(text);
|
package/dist/core/physics.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** Memory-as-Physics engine: pure math (forces, Velocity Verlet integration, physics-based scoring, cluster amplification), no I/O.
|
|
2
2
|
* Memories are particles on the unit hypersphere in embedding space (384-dim); query gravity, attraction, conflict repulsion and drag act on them,
|
|
3
3
|
* and nearby high-scoring memories amplify each other. */
|
|
4
|
-
import { type EmotionalValence } from './memory.js';
|
|
4
|
+
import { type EmotionalValence, type MemoryEntry } from './memory.js';
|
|
5
5
|
import type { PhysicsConfig } from './physics-config.js';
|
|
6
6
|
export interface PhysicsParticle {
|
|
7
7
|
memoryId: string;
|
|
@@ -71,4 +71,6 @@ export declare function physicsScore(particles: PhysicsParticle[], queryEmbeddin
|
|
|
71
71
|
tieKeyOf?: (memoryId: string) => string): ScoredPhysicsResult[];
|
|
72
72
|
/** Nudges a particle toward (good outcome) or away from (bad outcome) the query embedding; new memories respond more (temperature). Mutates in place. */
|
|
73
73
|
export declare function applyOutcomeFeedback(particle: PhysicsParticle, queryEmbedding: number[], good: boolean, feedbackAlpha: number): void;
|
|
74
|
+
/** Initialize physics state for a memory from its embedding vector; returns the new particle without persisting (the caller must save). */
|
|
75
|
+
export declare function initializeParticle(entry: MemoryEntry, embedding: number[], now?: Date): PhysicsParticle;
|
|
74
76
|
//# sourceMappingURL=physics.d.ts.map
|
package/dist/core/physics.js
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Memories are particles on the unit hypersphere in embedding space (384-dim); query gravity, attraction, conflict repulsion and drag act on them,
|
|
3
3
|
* and nearby high-scoring memories amplify each other. */
|
|
4
4
|
import { cosineOf } from './cosine.js';
|
|
5
|
-
import { isRecallBoostAblated } from './ablation.js';
|
|
6
|
-
import { FALLBACK_HALF_LIFE_DAYS } from './memory.js';
|
|
5
|
+
import { evalNow, isRecallBoostAblated, strengthAblation } from './ablation.js';
|
|
6
|
+
import { calculateStrength, FALLBACK_HALF_LIFE_DAYS } from './memory.js';
|
|
7
|
+
import { DAY_MS } from '../util/time.js';
|
|
7
8
|
import { comparePhysicsResultsBy } from './compare.js';
|
|
8
9
|
export function vecDot(a, b) {
|
|
9
10
|
let sum = 0;
|
|
@@ -301,4 +302,18 @@ export function applyOutcomeFeedback(particle, queryEmbedding, good, feedbackAlp
|
|
|
301
302
|
const nudge = vecScale(direction, sign * feedbackAlpha * particle.temperature);
|
|
302
303
|
particle.position = vecNormalize(vecAdd(particle.position, nudge));
|
|
303
304
|
}
|
|
305
|
+
/** Initialize physics state for a memory from its embedding vector; returns the new particle without persisting (the caller must save). */
|
|
306
|
+
export function initializeParticle(entry, embedding, now = evalNow()) {
|
|
307
|
+
const strength = calculateStrength(entry, now, { ablation: strengthAblation() });
|
|
308
|
+
const ageDays = (now.getTime() - new Date(entry.created).getTime()) / DAY_MS;
|
|
309
|
+
return {
|
|
310
|
+
memoryId: entry.id,
|
|
311
|
+
position: [...embedding], // copy: position will diverge from original embedding
|
|
312
|
+
velocity: vecZero(embedding.length),
|
|
313
|
+
mass: computeMass(strength, entry.retrieval_count),
|
|
314
|
+
charge: computeCharge(entry.emotional_valence),
|
|
315
|
+
temperature: computeTemperature(ageDays, 1.0),
|
|
316
|
+
lastSimulation: now.toISOString(),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
304
319
|
//# sourceMappingURL=physics.js.map
|
|
@@ -8,6 +8,7 @@ import { MAX_ID_LEN } from '../util/limits.js';
|
|
|
8
8
|
import { errorMessage, log } from '../util/log.js';
|
|
9
9
|
import { originRemoteId, projectFileId } from './project-remote.js';
|
|
10
10
|
import { realpathOrResolve } from '../util/real-path.js';
|
|
11
|
+
import { placeholders } from '../util/sql.js';
|
|
11
12
|
const MAX_WALK_DEPTH = 64;
|
|
12
13
|
const identityCache = new Map();
|
|
13
14
|
let remoteSwitch = null;
|
|
@@ -196,7 +197,7 @@ export function assertCallerProject(project) {
|
|
|
196
197
|
}
|
|
197
198
|
/** `origin_project IN (?, ...)` with one placeholder per name; an empty list matches nothing. */
|
|
198
199
|
export function originInSql(names, column = 'origin_project') {
|
|
199
|
-
return names.length === 0 ? '0' : `${column} IN (${names.
|
|
200
|
+
return names.length === 0 ? '0' : `${column} IN (${placeholders(names.length)})`;
|
|
200
201
|
}
|
|
201
202
|
/** Classifies a memory's origin_project against the active project. An empty current id (home or markerless cwd) puts everything in scope;
|
|
202
203
|
* NULL/undefined origin (legacy row, or a shared-store write naming none) counts as cross-project: deny by default for a security partition. */
|
|
@@ -63,7 +63,7 @@ export interface ScoreBreakdown {
|
|
|
63
63
|
scopeBoost: number;
|
|
64
64
|
/** Extra multiplier applied post-hybrid (e.g. 1.2x for local hits in a local+global merge); 1.0 otherwise. */
|
|
65
65
|
sourceBump: number;
|
|
66
|
-
/** Immediate outcome nudge, 1 + 0.15*tanh(pos - neg) clipped to [0.85, 1.15]; separate from the slow strength path. */
|
|
66
|
+
/** Immediate outcome nudge, 1 + 0.15*tanh((pos - neg) / 2) clipped to [0.85, 1.15]; separate from the slow strength path. */
|
|
67
67
|
outcomeBoost: number;
|
|
68
68
|
/** CHURN_STALE_RANK_MULTIPLIER if tagged 'churn-stale', else 1.0. */
|
|
69
69
|
churnStaleMultiplier: number;
|
|
@@ -12,9 +12,9 @@ export interface ActionResult {
|
|
|
12
12
|
/** Sets `pinned`; the audit row says `remember` with actor `dashboard`, the same op the old star toggle wrote. */
|
|
13
13
|
export declare function pinMemory(hippoRoot: string, tenantId: string, rawId: string, body: ActionBody): ActionResult;
|
|
14
14
|
/** Marks a memory wrong, the same signal as `hippo outcome --bad`. */
|
|
15
|
-
export declare function markWrong(hippoRoot: string, tenantId: string, rawId: string): ActionResult
|
|
15
|
+
export declare function markWrong(hippoRoot: string, tenantId: string, rawId: string): Promise<ActionResult>;
|
|
16
16
|
/** Deletes a memory like `hippo forget`; a raw receipt is append-only and answers 409 with the archive hint. */
|
|
17
|
-
export declare function forgetMemory(hippoRoot: string, tenantId: string, rawId: string): ActionResult
|
|
17
|
+
export declare function forgetMemory(hippoRoot: string, tenantId: string, rawId: string): Promise<ActionResult>;
|
|
18
18
|
/** Keeps one side of an open conflict and weakens the other; every refusal maps to its own status. */
|
|
19
19
|
export declare function resolveOpenConflict(hippoRoot: string, tenantId: string, conflictId: number, body: ActionBody): ActionResult;
|
|
20
20
|
//# sourceMappingURL=dashboard-actions.d.ts.map
|
|
@@ -29,25 +29,25 @@ export function pinMemory(hippoRoot, tenantId, rawId, body) {
|
|
|
29
29
|
return { status: 200, body: null, changed: true, entry: next };
|
|
30
30
|
}
|
|
31
31
|
/** Marks a memory wrong, the same signal as `hippo outcome --bad`. */
|
|
32
|
-
export function markWrong(hippoRoot, tenantId, rawId) {
|
|
32
|
+
export async function markWrong(hippoRoot, tenantId, rawId) {
|
|
33
33
|
const id = parseMemoryId(rawId);
|
|
34
34
|
if (liveEntry(hippoRoot, tenantId, id) === null)
|
|
35
35
|
return notFound();
|
|
36
36
|
const ctx = { hippoRoot, tenantId, actor: api.adminActor(ACTOR) };
|
|
37
37
|
// outcome() answers `applied: 0` for a missing or other-tenant id instead of throwing.
|
|
38
|
-
if (api.outcome(ctx, [id], false).applied === 0)
|
|
38
|
+
if ((await api.outcome(ctx, [id], false)).applied === 0)
|
|
39
39
|
return notFound();
|
|
40
40
|
const entry = readEntry(hippoRoot, id, tenantId);
|
|
41
41
|
return entry === null ? notFound() : { status: 200, body: null, changed: true, entry };
|
|
42
42
|
}
|
|
43
43
|
/** Deletes a memory like `hippo forget`; a raw receipt is append-only and answers 409 with the archive hint. */
|
|
44
|
-
export function forgetMemory(hippoRoot, tenantId, rawId) {
|
|
44
|
+
export async function forgetMemory(hippoRoot, tenantId, rawId) {
|
|
45
45
|
const id = parseMemoryId(rawId);
|
|
46
46
|
if (liveEntry(hippoRoot, tenantId, id) === null)
|
|
47
47
|
return notFound();
|
|
48
48
|
const ctx = { hippoRoot, tenantId, actor: api.adminActor(ACTOR) };
|
|
49
49
|
try {
|
|
50
|
-
const done = api.forget(ctx, id);
|
|
50
|
+
const done = await api.forget(ctx, id);
|
|
51
51
|
return { status: 200, body: done, changed: true };
|
|
52
52
|
}
|
|
53
53
|
catch (err) {
|
|
@@ -79,7 +79,7 @@ export function resolveOpenConflict(hippoRoot, tenantId, conflictId, body) {
|
|
|
79
79
|
if (liveEntry(hippoRoot, tenantId, conflict.memory_a_id) === null || liveEntry(hippoRoot, tenantId, conflict.memory_b_id) === null) {
|
|
80
80
|
return notFound();
|
|
81
81
|
}
|
|
82
|
-
const resolved = resolveConflict(hippoRoot, conflictId, keep,
|
|
82
|
+
const resolved = resolveConflict(hippoRoot, { conflictId, keepId: keep, tenantId }, { rejectedBy: ACTOR });
|
|
83
83
|
// Both guards passed above, so null here is a race with another resolver.
|
|
84
84
|
if (resolved === null)
|
|
85
85
|
return already();
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { calculateStrength } from '../core/memory.js';
|
|
3
3
|
import { loadOpenConflictsOf } from '../store/conflicts.js';
|
|
4
4
|
import { BANDS, DAY_MS, LAYERS, isLiveMemory, memoryFacts, projectIdentity, } from './dashboard-snapshot.js';
|
|
5
|
+
import { pct, round1 } from '../util/format.js';
|
|
5
6
|
const SERIES_DAYS = 90;
|
|
6
7
|
/** The server words each delta for this range; the client re-slices the series for 7d and 90d. */
|
|
7
8
|
const DELTA_DAYS = 30;
|
|
@@ -9,9 +10,6 @@ const POINTS_MAX = 4_000;
|
|
|
9
10
|
const GRID_COLS = 64;
|
|
10
11
|
const GRID_ROWS = 32;
|
|
11
12
|
const NAME_MATCHES = 3;
|
|
12
|
-
function round1(value) {
|
|
13
|
-
return Math.round(value * 10) / 10;
|
|
14
|
-
}
|
|
15
13
|
function round3(value) {
|
|
16
14
|
return Math.round(value * 1000) / 1000;
|
|
17
15
|
}
|
|
@@ -32,9 +30,6 @@ function cumulativeSeries(times, nowMs) {
|
|
|
32
30
|
function addedWithin(series) {
|
|
33
31
|
return series[SERIES_DAYS] - series[SERIES_DAYS - DELTA_DAYS];
|
|
34
32
|
}
|
|
35
|
-
function pct(share) {
|
|
36
|
-
return `${(share * 100).toFixed(1)}%`;
|
|
37
|
-
}
|
|
38
33
|
function plural(n, one, many) {
|
|
39
34
|
return `${n} ${n === 1 ? one : many}`;
|
|
40
35
|
}
|
|
@@ -342,7 +337,7 @@ export function buildMemoryDetail(hippoRoot, tenantId, entry, options) {
|
|
|
342
337
|
projectKey: project.key,
|
|
343
338
|
projectName: project.name,
|
|
344
339
|
scope: entry.scope,
|
|
345
|
-
tenant: entry.
|
|
340
|
+
tenant: entry.tenant_id,
|
|
346
341
|
kind: entry.kind,
|
|
347
342
|
embedded,
|
|
348
343
|
conflicts: openConflictsOf(hippoRoot, tenantId, entry, nowMs),
|
|
@@ -5,6 +5,7 @@ import { isQuarantineScope } from '../trust/quarantine.js';
|
|
|
5
5
|
import { listMemoryConflicts } from '../store/conflicts.js';
|
|
6
6
|
import { DashboardConnection, loadDashboardRows } from '../store/dashboard-reads.js';
|
|
7
7
|
import { DAY_MS } from '../util/time.js';
|
|
8
|
+
import { round1 } from '../util/format.js';
|
|
8
9
|
export { DAY_MS };
|
|
9
10
|
export const LAYERS = ['buffer', 'episodic', 'semantic', 'trace'];
|
|
10
11
|
// Wire order of `Band`, and the order the mockup sorts bands in.
|
|
@@ -99,9 +100,6 @@ function cutHead(content) {
|
|
|
99
100
|
end -= 1;
|
|
100
101
|
return { head: content.slice(0, end), truncated: true };
|
|
101
102
|
}
|
|
102
|
-
function round1(value) {
|
|
103
|
-
return Math.round(value * 10) / 10;
|
|
104
|
-
}
|
|
105
103
|
function newDraft(identity) {
|
|
106
104
|
return {
|
|
107
105
|
summary: {
|
|
@@ -179,7 +179,7 @@ async function handlePost(ctx, segments) {
|
|
|
179
179
|
}
|
|
180
180
|
const body = await readActionBody(req);
|
|
181
181
|
const tenantId = resolveTenantId({});
|
|
182
|
-
const result = action(hippoRoot, tenantId, body);
|
|
182
|
+
const result = await action(hippoRoot, tenantId, body);
|
|
183
183
|
if (result.changed)
|
|
184
184
|
snapshots.invalidate();
|
|
185
185
|
if (result.entry) {
|
|
@@ -300,7 +300,7 @@ function answerDashboardFailure(req, res, err) {
|
|
|
300
300
|
const clientFault = err instanceof ParamError || err instanceof URIError || err instanceof BodyTimeoutError;
|
|
301
301
|
// A cut-short response is logged even for a client fault; a bare 400 is not.
|
|
302
302
|
if (res.headersSent || !clientFault) {
|
|
303
|
-
log.error('dashboard request failed', { ...errorFields(err), error: errorMessage(err), path: req.url });
|
|
303
|
+
log.error('dashboard request failed', { ...errorFields(err), error: errorMessage(err), path: (req.url ?? '/').split('?')[0] });
|
|
304
304
|
}
|
|
305
305
|
if (res.headersSent) {
|
|
306
306
|
res.end();
|
|
@@ -318,9 +318,20 @@ function answerDashboardFailure(req, res, err) {
|
|
|
318
318
|
return jsonResponse(res, { error: 'Malformed URL path' }, 400);
|
|
319
319
|
jsonResponse(res, { error: 'Internal error' }, 500);
|
|
320
320
|
}
|
|
321
|
-
|
|
321
|
+
const DASHBOARD_URL_FILE = 'dashboard.url';
|
|
322
|
+
function printDashboardBanner(hippoRoot, boundPort, token, distUiDir) {
|
|
322
323
|
// The banner is the `hippo dashboard` command's printed result, so it stays on stdout.
|
|
323
|
-
|
|
324
|
+
const url = `http://localhost:${boundPort}/?token=${token}`;
|
|
325
|
+
if (process.stdout.isTTY) {
|
|
326
|
+
console.log(`Hippo Dashboard running at ${url}`);
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
// Captured stdout lands in logs, so the token goes to an owner-only file, created fresh so its mode holds.
|
|
330
|
+
const file = path.join(hippoRoot, DASHBOARD_URL_FILE);
|
|
331
|
+
fs.rmSync(file, { force: true });
|
|
332
|
+
fs.writeFileSync(file, `${url}\n`, { flag: 'wx', mode: 0o600 });
|
|
333
|
+
console.log(`Hippo Dashboard running at http://localhost:${boundPort}/ (open the URL with its token from ${file})`);
|
|
334
|
+
}
|
|
324
335
|
if (distUiDir !== null)
|
|
325
336
|
console.log(`Serving React UI from ${distUiDir}`);
|
|
326
337
|
console.log('Press Ctrl+C to stop.');
|
|
@@ -406,7 +417,7 @@ export function serveDashboard(hippoRoot, port = 3333, token = randomBytes(32).t
|
|
|
406
417
|
server.listen(port, '127.0.0.1', () => {
|
|
407
418
|
// SAFETY: listen() was given a TCP port, so address() is AddressInfo, never a pipe name.
|
|
408
419
|
const boundPort = server.address().port;
|
|
409
|
-
printDashboardBanner(boundPort, token, hasDistUi ? distUiDir : null);
|
|
420
|
+
printDashboardBanner(hippoRoot, boundPort, token, hasDistUi ? distUiDir : null);
|
|
410
421
|
});
|
|
411
422
|
return server;
|
|
412
423
|
}
|
package/dist/db/connect.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ export interface OpenedDb {
|
|
|
8
8
|
readonly db: DatabaseSyncLike;
|
|
9
9
|
readonly facts: OpenFacts | null;
|
|
10
10
|
}
|
|
11
|
-
/** A new connection with the store's pragmas and migrations applied and the mirror cleanup run when it is due; the caller owns and closes it.
|
|
11
|
+
/** A new connection with the store's pragmas and migrations applied and the mirror cleanup run when it is due; the caller owns and closes it.
|
|
12
|
+
* Outside any request scope (CLI start-up, timers) this is the only handle there is; openHippoDb reuses the scope's when one exists. */
|
|
12
13
|
export declare function connectWithFacts(hippoRoot: string, busyWaitMs?: number): OpenedDb;
|
|
13
14
|
//# sourceMappingURL=connect.d.ts.map
|
package/dist/db/connect.js
CHANGED
|
@@ -51,7 +51,8 @@ function archiveTop(db) {
|
|
|
51
51
|
const row = db.prepare('SELECT MAX(id) AS top FROM raw_archive').get();
|
|
52
52
|
return Number(row?.top ?? 0);
|
|
53
53
|
}
|
|
54
|
-
/** A new connection with the store's pragmas and migrations applied and the mirror cleanup run when it is due; the caller owns and closes it.
|
|
54
|
+
/** A new connection with the store's pragmas and migrations applied and the mirror cleanup run when it is due; the caller owns and closes it.
|
|
55
|
+
* Outside any request scope (CLI start-up, timers) this is the only handle there is; openHippoDb reuses the scope's when one exists. */
|
|
55
56
|
export function connectWithFacts(hippoRoot, busyWaitMs = DEFAULT_BUSY_WAIT_MS) {
|
|
56
57
|
createStoreFilesOwnerOnly(hippoRoot);
|
|
57
58
|
const db = new DatabaseSync(getHippoDbPath(hippoRoot));
|
package/dist/db/migrate.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { PACKAGE_VERSION, compareSemver } from '../util/version.js';
|
|
2
|
-
import { errorMessage, log } from '../util/log.js';
|
|
2
|
+
import { errorFields, errorMessage, log } from '../util/log.js';
|
|
3
3
|
import { importLegacyEmbeddingIndex } from './vector-store.js';
|
|
4
4
|
import { execWithBusyRetry, withWriteScope } from './busy.js';
|
|
5
5
|
import { tableExists } from './tables.js';
|
|
6
6
|
import { ensureMetaTable, getMeta, getSchemaVersion, setMeta, setSchemaVersion } from './meta.js';
|
|
7
7
|
import { MEMORIES_FTS_DDL, REQUIRED_SCHEMA_OBJECTS, ensureContinuityIndexes, ensureContinuityTables } from './continuity.js';
|
|
8
8
|
import { CURRENT_SCHEMA_VERSION, MIGRATIONS } from './migrations/index.js';
|
|
9
|
+
import { placeholders } from '../util/sql.js';
|
|
9
10
|
export function getCurrentSchemaVersion() {
|
|
10
11
|
return CURRENT_SCHEMA_VERSION;
|
|
11
12
|
}
|
|
@@ -130,7 +131,7 @@ function ensureMetaDefaults(db) {
|
|
|
130
131
|
const keys = META_DEFAULTS.map(([key]) => key);
|
|
131
132
|
// SAFETY: the statement selects only the `key` column of `meta`, which is TEXT NOT NULL, so each row is { key: string }.
|
|
132
133
|
const present = new Set(db
|
|
133
|
-
.prepare(`SELECT key FROM meta WHERE key IN (${keys.
|
|
134
|
+
.prepare(`SELECT key FROM meta WHERE key IN (${placeholders(keys.length)})`)
|
|
134
135
|
.all(...keys)
|
|
135
136
|
.map((r) => r.key));
|
|
136
137
|
if (present.size === META_DEFAULTS.length)
|
|
@@ -142,18 +143,24 @@ function ensureMetaDefaults(db) {
|
|
|
142
143
|
}
|
|
143
144
|
}
|
|
144
145
|
function ensureOptionalFts(db) {
|
|
145
|
-
let available = false;
|
|
146
146
|
try {
|
|
147
147
|
db.exec(MEMORIES_FTS_DDL);
|
|
148
148
|
repairFtsDrift(db);
|
|
149
|
-
available = true;
|
|
150
149
|
}
|
|
151
150
|
catch (err) {
|
|
151
|
+
// A busy or locked open says nothing about fts5, so only a missing module may flip the flag.
|
|
152
|
+
if (!/no such module/i.test(errorMessage(err)) || !/fts5/i.test(errorMessage(err))) {
|
|
153
|
+
log.warn(`full-text index not set up this open (${errorMessage(err)}); the next open retries`, errorFields(err));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
152
156
|
log.warn(`full-text index unavailable (${errorMessage(err)}); search falls back to slower LIKE matching`);
|
|
157
|
+
writeFtsFlag(db, '0');
|
|
158
|
+
return;
|
|
153
159
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
160
|
+
writeFtsFlag(db, '1');
|
|
161
|
+
}
|
|
162
|
+
// Read-first: only write when the flag actually changed, so a healthy store's open takes no write lock.
|
|
163
|
+
function writeFtsFlag(db, flag) {
|
|
157
164
|
if (getMeta(db, 'fts5_available') !== flag)
|
|
158
165
|
setMeta(db, 'fts5_available', flag);
|
|
159
166
|
}
|
|
@@ -9,6 +9,7 @@ export const v11 = {
|
|
|
9
9
|
if (!tableHasColumn(db, 'memories', 'superseded_by')) {
|
|
10
10
|
db.exec(`ALTER TABLE memories ADD COLUMN superseded_by TEXT`);
|
|
11
11
|
}
|
|
12
|
+
// SHORTCUT: overlaps idx_memories_layer_created (v01); dropping it is held until a schema migration is approved.
|
|
12
13
|
db.exec(`CREATE INDEX IF NOT EXISTS idx_memories_current ON memories(layer, created) WHERE superseded_by IS NULL`);
|
|
13
14
|
},
|
|
14
15
|
};
|
|
@@ -48,6 +48,7 @@ export const v14 = {
|
|
|
48
48
|
if (!tableHasColumn(db, 'memories', 'owner')) {
|
|
49
49
|
db.exec(`ALTER TABLE memories ADD COLUMN owner TEXT`);
|
|
50
50
|
}
|
|
51
|
+
// SHORTCUT: artifact_ref has no index, so connector deletes and vault lookups scan the tenant; the index is held until a schema migration is approved.
|
|
51
52
|
if (!tableHasColumn(db, 'memories', 'artifact_ref')) {
|
|
52
53
|
db.exec(`ALTER TABLE memories ADD COLUMN artifact_ref TEXT`);
|
|
53
54
|
}
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
import type { DatabaseSyncLike } from './index.js';
|
|
3
3
|
import type { MemoryEntry } from '../core/memory.js';
|
|
4
4
|
import type { PhysicsParticle } from '../core/physics.js';
|
|
5
|
-
export declare function float32ToBuffer(arr: number[]): Buffer;
|
|
6
|
-
export declare function bufferToFloat32(buf: Buffer | Uint8Array): number[];
|
|
7
5
|
/** Create the memory_physics table (migration v8). */
|
|
8
6
|
export declare function createPhysicsTable(db: DatabaseSyncLike): void;
|
|
9
7
|
/**
|
|
@@ -14,8 +12,6 @@ export declare function loadPhysicsState(db: DatabaseSyncLike, memoryIds?: strin
|
|
|
14
12
|
* Save physics state for multiple particles (batch upsert).
|
|
15
13
|
*/
|
|
16
14
|
export declare function savePhysicsState(db: DatabaseSyncLike, particles: PhysicsParticle[]): void;
|
|
17
|
-
/** Initialize physics state for a memory from its embedding vector; returns the new particle without persisting (the caller must save). */
|
|
18
|
-
export declare function initializeParticle(entry: MemoryEntry, embedding: number[], now?: Date): PhysicsParticle;
|
|
19
15
|
/** Drops every particle, then seeds one per entry that has an embedding; `entries` may be read lazily, as each batch is written before the next is read. */
|
|
20
16
|
export declare function resetAllPhysicsState(db: DatabaseSyncLike, entries: Iterable<MemoryEntry>, embeddingIndex: Record<string, number[]>, now?: Date): number;
|
|
21
17
|
/** Refresh mass, charge and temperature for existing particles from current memory attributes (called during consolidation). */
|
package/dist/db/physics-state.js
CHANGED
|
@@ -1,23 +1,11 @@
|
|
|
1
1
|
/** Physics state persistence: particle state (position, velocity, mass, charge, temperature) in SQLite, with BLOB columns for 384-dim vectors. */
|
|
2
2
|
import { evalNow, strengthAblation } from '../core/ablation.js';
|
|
3
3
|
import { withWriteScope } from './busy.js';
|
|
4
|
-
import { computeMass, computeCharge, computeTemperature,
|
|
4
|
+
import { computeMass, computeCharge, computeTemperature, initializeParticle } from '../core/physics.js';
|
|
5
5
|
import { calculateStrength } from '../core/memory.js';
|
|
6
6
|
import { DAY_MS } from '../util/time.js';
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const f32 = new Float32Array(arr);
|
|
10
|
-
return Buffer.from(f32.buffer);
|
|
11
|
-
}
|
|
12
|
-
export function bufferToFloat32(buf) {
|
|
13
|
-
if (buf.byteLength === 0 || buf.byteLength % 4 !== 0)
|
|
14
|
-
return [];
|
|
15
|
-
// Ensure we have a properly aligned copy (SQLite may return Uint8Array, not Buffer)
|
|
16
|
-
const bytes = new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
17
|
-
const aligned = new ArrayBuffer(bytes.length);
|
|
18
|
-
new Uint8Array(aligned).set(bytes);
|
|
19
|
-
return Array.from(new Float32Array(aligned));
|
|
20
|
-
}
|
|
7
|
+
import { placeholders } from '../util/sql.js';
|
|
8
|
+
import { bufferToFloat32, float32ToBuffer } from '../util/vector-codec.js';
|
|
21
9
|
// Schema migration
|
|
22
10
|
/** Create the memory_physics table (migration v8). */
|
|
23
11
|
export function createPhysicsTable(db) {
|
|
@@ -48,10 +36,10 @@ export function loadPhysicsState(db, memoryIds) {
|
|
|
48
36
|
// Chunked so a whole-store candidate list stays under SQLite's bound-parameter limit.
|
|
49
37
|
for (let i = 0; i < memoryIds.length; i += PHYSICS_ID_CHUNK) {
|
|
50
38
|
const chunk = memoryIds.slice(i, i + PHYSICS_ID_CHUNK);
|
|
51
|
-
const
|
|
39
|
+
const marks = placeholders(chunk.length);
|
|
52
40
|
// SAFETY: the SELECT projects exactly the PhysicsRow columns, in order.
|
|
53
41
|
rows = rows.concat(db.prepare(`SELECT memory_id, position_blob, velocity_blob, mass, charge, temperature, last_simulation
|
|
54
|
-
FROM memory_physics WHERE memory_id IN (${
|
|
42
|
+
FROM memory_physics WHERE memory_id IN (${marks})`).all(...chunk));
|
|
55
43
|
}
|
|
56
44
|
}
|
|
57
45
|
else {
|
|
@@ -97,20 +85,6 @@ export function savePhysicsState(db, particles) {
|
|
|
97
85
|
}
|
|
98
86
|
});
|
|
99
87
|
}
|
|
100
|
-
/** Initialize physics state for a memory from its embedding vector; returns the new particle without persisting (the caller must save). */
|
|
101
|
-
export function initializeParticle(entry, embedding, now = evalNow()) {
|
|
102
|
-
const strength = calculateStrength(entry, now, { ablation: strengthAblation() });
|
|
103
|
-
const ageDays = (now.getTime() - new Date(entry.created).getTime()) / DAY_MS;
|
|
104
|
-
return {
|
|
105
|
-
memoryId: entry.id,
|
|
106
|
-
position: [...embedding], // copy — position will diverge from original embedding
|
|
107
|
-
velocity: vecZero(embedding.length),
|
|
108
|
-
mass: computeMass(strength, entry.retrieval_count),
|
|
109
|
-
charge: computeCharge(entry.emotional_valence),
|
|
110
|
-
temperature: computeTemperature(ageDays, 1.0),
|
|
111
|
-
lastSimulation: now.toISOString(),
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
88
|
const RESET_BATCH = 64;
|
|
115
89
|
/** Drops every particle, then seeds one per entry that has an embedding; `entries` may be read lazily, as each batch is written before the next is read. */
|
|
116
90
|
export function resetAllPhysicsState(db, entries, embeddingIndex, now = evalNow()) {
|
|
@@ -15,6 +15,7 @@ export declare class RequestStores implements RequestScope {
|
|
|
15
15
|
readonly busyWaitMs: number | undefined;
|
|
16
16
|
readonly requestId: string | undefined;
|
|
17
17
|
readonly deadline: import("../util/request-scope.js").RequestDeadline | undefined;
|
|
18
|
+
readonly signal: AbortSignal | undefined;
|
|
18
19
|
readonly setupDoneFor: string | undefined;
|
|
19
20
|
constructor(opts?: RequestStoresOptions);
|
|
20
21
|
get closed(): boolean;
|