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.
Files changed (259) hide show
  1. package/README.md +6 -4
  2. package/dist/agent-memories/apply.js +5 -5
  3. package/dist/api/assemble.js +2 -1
  4. package/dist/api/audit.d.ts +7 -7
  5. package/dist/api/audit.js +19 -17
  6. package/dist/api/auth.d.ts +5 -6
  7. package/dist/api/auth.js +28 -33
  8. package/dist/api/conflicts.js +1 -1
  9. package/dist/api/context-auto.js +4 -2
  10. package/dist/api/context-select.js +8 -20
  11. package/dist/api/context.js +12 -10
  12. package/dist/api/drill-down.js +2 -1
  13. package/dist/api/forget.d.ts +2 -2
  14. package/dist/api/forget.js +6 -7
  15. package/dist/api/graph.js +2 -1
  16. package/dist/api/learn.d.ts +1 -1
  17. package/dist/api/learn.js +45 -44
  18. package/dist/api/memories.d.ts +4 -2
  19. package/dist/api/memories.js +16 -5
  20. package/dist/api/objects.js +2 -1
  21. package/dist/api/outcome.d.ts +5 -5
  22. package/dist/api/outcome.js +16 -15
  23. package/dist/api/predictions.js +2 -1
  24. package/dist/api/promote.d.ts +3 -3
  25. package/dist/api/promote.js +25 -27
  26. package/dist/api/prompt-hook.d.ts +3 -1
  27. package/dist/api/prompt-hook.js +1 -0
  28. package/dist/api/quarantine.js +2 -1
  29. package/dist/api/recall-core.js +8 -3
  30. package/dist/api/recall-pipeline.js +17 -17
  31. package/dist/api/recall-types.d.ts +3 -1
  32. package/dist/api/recall.js +32 -29
  33. package/dist/api/remember-local.d.ts +5 -2
  34. package/dist/api/remember-local.js +14 -6
  35. package/dist/api/remember.d.ts +5 -2
  36. package/dist/api/remember.js +16 -14
  37. package/dist/api/session-end.d.ts +24 -0
  38. package/dist/api/session-end.js +31 -1
  39. package/dist/api/sleep-run.js +5 -4
  40. package/dist/api/sleep.js +4 -3
  41. package/dist/api/tokens.js +1 -1
  42. package/dist/api/types.d.ts +1 -7
  43. package/dist/capture/command.js +1 -1
  44. package/dist/capture/compaction-spool.d.ts +3 -5
  45. package/dist/capture/compaction-spool.js +41 -46
  46. package/dist/capture/session-end-caller.js +1 -1
  47. package/dist/capture/session-worker.js +19 -6
  48. package/dist/cli/audit.js +10 -10
  49. package/dist/cli/auth.js +4 -4
  50. package/dist/cli/client.d.ts +1 -2
  51. package/dist/cli/client.js +13 -13
  52. package/dist/cli/context.js +1 -1
  53. package/dist/cli/curate.d.ts +1 -1
  54. package/dist/cli/curate.js +9 -9
  55. package/dist/cli/decisions.js +4 -5
  56. package/dist/cli/hook-runtime.d.ts +3 -1
  57. package/dist/cli/hook-runtime.js +2 -2
  58. package/dist/cli/init.d.ts +1 -1
  59. package/dist/cli/init.js +9 -9
  60. package/dist/cli/object-verbs.d.ts +2 -2
  61. package/dist/cli/object-verbs.js +4 -4
  62. package/dist/cli/predictions.js +2 -3
  63. package/dist/cli/quality-repair-once.d.ts +1 -1
  64. package/dist/cli/quality-repair-once.js +2 -2
  65. package/dist/cli/refine-llm.js +2 -1
  66. package/dist/cli/remember.js +5 -5
  67. package/dist/cli/serve.js +9 -4
  68. package/dist/cli/session-hooks.d.ts +0 -4
  69. package/dist/cli/session-hooks.js +57 -66
  70. package/dist/cli/setup.d.ts +1 -1
  71. package/dist/cli/setup.js +2 -2
  72. package/dist/cli/shared.d.ts +1 -1
  73. package/dist/cli/shared.js +2 -2
  74. package/dist/cli/sleep.js +2 -2
  75. package/dist/cli/transfer.d.ts +3 -3
  76. package/dist/cli/transfer.js +34 -15
  77. package/dist/cli/usage.d.ts +1 -1
  78. package/dist/cli/usage.js +1 -1
  79. package/dist/cli/verb-row.d.ts +2 -0
  80. package/dist/cli/verbs/health.js +2 -0
  81. package/dist/cli/verbs/memory.js +43 -51
  82. package/dist/cli/verbs/objects.js +6 -3
  83. package/dist/cli/verbs/sessions.js +4 -2
  84. package/dist/cli.js +1 -2
  85. package/dist/connectors/dlq.js +2 -1
  86. package/dist/connectors/github/deletion.js +2 -1
  87. package/dist/connectors/github/ingest.js +2 -1
  88. package/dist/connectors/github/tenant-routing.js +2 -1
  89. package/dist/connectors/github/webhook.js +4 -2
  90. package/dist/connectors/ingest.js +2 -1
  91. package/dist/connectors/slack/deletion.js +2 -1
  92. package/dist/connectors/slack/ingest.js +2 -1
  93. package/dist/connectors/slack/tenant-routing.js +2 -1
  94. package/dist/consolidate/conflicts.js +1 -1
  95. package/dist/consolidate/dag.js +14 -9
  96. package/dist/consolidate/dedupe.js +1 -1
  97. package/dist/consolidate/memory-value.js +1 -1
  98. package/dist/consolidate/merge.js +6 -6
  99. package/dist/consolidate/sleep.js +1 -1
  100. package/dist/consolidate/traces.js +2 -2
  101. package/dist/core/config.js +15 -16
  102. package/dist/core/markdown.js +2 -2
  103. package/dist/core/memory.d.ts +4 -4
  104. package/dist/core/memory.js +10 -5
  105. package/dist/core/physics.d.ts +3 -1
  106. package/dist/core/physics.js +17 -2
  107. package/dist/core/project-identity.js +2 -1
  108. package/dist/core/search-types.d.ts +1 -1
  109. package/dist/dashboard/dashboard-actions.d.ts +2 -2
  110. package/dist/dashboard/dashboard-actions.js +5 -5
  111. package/dist/dashboard/dashboard-queries.js +2 -7
  112. package/dist/dashboard/dashboard-snapshot.js +1 -3
  113. package/dist/dashboard/dashboard.js +16 -5
  114. package/dist/db/connect.d.ts +2 -1
  115. package/dist/db/connect.js +2 -1
  116. package/dist/db/migrate.js +14 -7
  117. package/dist/db/migrations/v11.js +1 -0
  118. package/dist/db/migrations/v14.js +1 -0
  119. package/dist/db/physics-state.d.ts +0 -4
  120. package/dist/db/physics-state.js +5 -31
  121. package/dist/db/request-stores.d.ts +1 -0
  122. package/dist/db/request-stores.js +2 -1
  123. package/dist/db/vector-store.js +2 -1
  124. package/dist/doctor.d.ts +3 -0
  125. package/dist/doctor.js +7 -7
  126. package/dist/embeddings/local.js +2 -2
  127. package/dist/embeddings/provider.js +1 -10
  128. package/dist/entry/store-kit.d.ts +37 -0
  129. package/dist/entry/store-kit.js +25 -0
  130. package/dist/entry/testing.d.ts +6 -0
  131. package/dist/entry/testing.js +7 -0
  132. package/dist/eval/judgment.js +1 -1
  133. package/dist/hooks/json-hooks.js +6 -2
  134. package/dist/importers/core.js +3 -3
  135. package/dist/importers/vault.d.ts +1 -1
  136. package/dist/importers/vault.js +18 -18
  137. package/dist/index.d.ts +2 -2
  138. package/dist/index.js +1 -1
  139. package/dist/learn/autolearn.d.ts +6 -2
  140. package/dist/learn/autolearn.js +8 -6
  141. package/dist/learn/extract.js +1 -1
  142. package/dist/learn/invalidation.d.ts +2 -1
  143. package/dist/learn/invalidation.js +10 -5
  144. package/dist/mcp/memory-tools.d.ts +1 -1
  145. package/dist/mcp/memory-tools.js +6 -9
  146. package/dist/mcp/request.js +20 -19
  147. package/dist/mcp/stdio.js +13 -8
  148. package/dist/rerankers/cross-encoder.js +10 -3
  149. package/dist/search/embed-memories.d.ts +5 -2
  150. package/dist/search/embed-memories.js +40 -28
  151. package/dist/search/engine.d.ts +8 -0
  152. package/dist/search/engine.js +10 -0
  153. package/dist/search/vector.js +5 -5
  154. package/dist/server/auth.d.ts +1 -1
  155. package/dist/server/auth.js +7 -8
  156. package/dist/server/boot.d.ts +2 -1
  157. package/dist/server/boot.js +28 -19
  158. package/dist/server/client-ip.d.ts +9 -4
  159. package/dist/server/client-ip.js +35 -17
  160. package/dist/server/route-table.js +5 -6
  161. package/dist/server/routes/memories.js +4 -2
  162. package/dist/server/sleep-offload.js +3 -3
  163. package/dist/server/tls.d.ts +7 -2
  164. package/dist/server/tls.js +17 -5
  165. package/dist/server/types.d.ts +7 -2
  166. package/dist/server.d.ts +4 -41
  167. package/dist/server.js +3 -30
  168. package/dist/sharing/global-store.d.ts +4 -5
  169. package/dist/sharing/global-store.js +6 -11
  170. package/dist/sharing/global-sync.js +1 -1
  171. package/dist/sharing/search-both.js +9 -2
  172. package/dist/sharing/share.js +2 -4
  173. package/dist/store/ambient.js +2 -1
  174. package/dist/store/candidates.js +18 -8
  175. package/dist/store/cards.js +5 -4
  176. package/dist/store/compactions-record.js +8 -7
  177. package/dist/store/conflicts.d.ts +7 -1
  178. package/dist/store/conflicts.js +6 -5
  179. package/dist/store/connectors/dlq.js +2 -1
  180. package/dist/store/connectors/event-log.d.ts +13 -0
  181. package/dist/store/connectors/event-log.js +24 -0
  182. package/dist/store/connectors/github.js +6 -8
  183. package/dist/store/connectors/slack.js +6 -10
  184. package/dist/store/dashboard-reads.js +1 -0
  185. package/dist/store/delete-and-batch.js +5 -4
  186. package/dist/store/delivery-recorder.d.ts +2 -2
  187. package/dist/store/delivery-recorder.js +1 -6
  188. package/dist/store/descendant-page.js +2 -1
  189. package/dist/store/dormant.js +7 -3
  190. package/dist/store/entry-reads.d.ts +25 -3
  191. package/dist/store/entry-reads.js +43 -12
  192. package/dist/store/entry-row.js +9 -2
  193. package/dist/store/entry-writes.d.ts +10 -7
  194. package/dist/store/entry-writes.js +23 -11
  195. package/dist/store/goals.js +3 -2
  196. package/dist/store/graph-reads.js +5 -4
  197. package/dist/store/graph-view-rows.js +2 -1
  198. package/dist/store/half-life.js +2 -2
  199. package/dist/store/handoffs.d.ts +9 -3
  200. package/dist/store/handoffs.js +4 -3
  201. package/dist/store/index.d.ts +2 -2
  202. package/dist/store/index.js +1 -1
  203. package/dist/store/mirrors.js +9 -4
  204. package/dist/store/open.d.ts +2 -1
  205. package/dist/store/open.js +2 -1
  206. package/dist/store/port.d.ts +59 -3
  207. package/dist/store/quality-repair.js +1 -1
  208. package/dist/store/recall-trace.js +2 -1
  209. package/dist/store/rejected-values.js +14 -9
  210. package/dist/store/rows.js +2 -2
  211. package/dist/store/rule-sql.d.ts +2 -0
  212. package/dist/store/rule-sql.js +12 -6
  213. package/dist/store/search-rows.d.ts +5 -5
  214. package/dist/store/search-rows.js +9 -12
  215. package/dist/store/set-aside.js +1 -1
  216. package/dist/store/sqlite/connector-writes-group.js +3 -1
  217. package/dist/store/sqlite/entry-writes-group.d.ts +3 -3
  218. package/dist/store/sqlite/entry-writes-group.js +1 -1
  219. package/dist/store/sqlite/held-connection.d.ts +2 -0
  220. package/dist/store/sqlite/held-connection.js +1 -1
  221. package/dist/store/sqlite/last-recall-group.d.ts +13 -0
  222. package/dist/store/sqlite/last-recall-group.js +57 -0
  223. package/dist/store/sqlite/local.d.ts +0 -17
  224. package/dist/store/sqlite/local.js +0 -31
  225. package/dist/store/sqlite/maintenance-groups.d.ts +6 -0
  226. package/dist/store/sqlite/maintenance-groups.js +60 -0
  227. package/dist/store/sqlite/objects-group.js +14 -6
  228. package/dist/store/sqlite/store.d.ts +1 -8
  229. package/dist/store/sqlite/store.js +13 -24
  230. package/dist/store/summaries.d.ts +7 -0
  231. package/dist/store/summaries.js +49 -7
  232. package/dist/store/token-ledger-rows.js +3 -2
  233. package/dist/store/vector-index.js +5 -2
  234. package/dist/util/anthropic-messages.d.ts +3 -1
  235. package/dist/util/anthropic-messages.js +4 -2
  236. package/dist/util/atomic-write.d.ts +5 -2
  237. package/dist/util/atomic-write.js +12 -8
  238. package/dist/util/env.d.ts +10 -1
  239. package/dist/util/env.js +10 -1
  240. package/dist/util/format.d.ts +2 -0
  241. package/dist/util/format.js +4 -0
  242. package/dist/util/request-scope.d.ts +6 -2
  243. package/dist/util/request-scope.js +7 -3
  244. package/dist/util/spawn-argv.d.ts +9 -0
  245. package/dist/util/spawn-argv.js +57 -0
  246. package/dist/util/sql.d.ts +3 -0
  247. package/dist/util/sql.js +6 -0
  248. package/dist/util/vector-codec.d.ts +4 -0
  249. package/dist/util/vector-codec.js +15 -0
  250. package/dist/util/version.d.ts +1 -1
  251. package/dist/util/version.js +1 -1
  252. package/extensions/openclaw-plugin/openclaw.plugin.json +1 -1
  253. package/extensions/openclaw-plugin/package.json +1 -1
  254. package/openclaw.plugin.json +1 -1
  255. package/package.json +3 -1
  256. package/dist/api/on-store.d.ts +0 -16
  257. package/dist/api/on-store.js +0 -38
  258. package/dist/cli/lenient-id.d.ts +0 -2
  259. package/dist/cli/lenient-id.js +0 -13
@@ -1,7 +1,7 @@
1
1
  // One store handle per store for each request (HTTP call, MCP tool call, scoped CLI command), opened lazily and closed with it.
2
2
  import * as path from 'node:path';
3
3
  import { connectWithFacts, getHippoDbPath } from './connect.js';
4
- import { currentDeadline, currentRequestId, requestScopes } from '../util/request-scope.js';
4
+ import { currentDeadline, currentRequestId, currentSignal, requestScopes } from '../util/request-scope.js';
5
5
  // Handles a scope owns, so closeHippoDb leaves them open for the rest of the request.
6
6
  const scopedHandles = new WeakSet();
7
7
  // process.exit skips every finally, so one exit listener closes the handles of scopes still open.
@@ -24,6 +24,7 @@ export class RequestStores {
24
24
  // Taken from the scope this one opens inside, so the request's log lines keep their id.
25
25
  requestId = currentRequestId();
26
26
  deadline = currentDeadline();
27
+ signal = currentSignal();
27
28
  setupDoneFor;
28
29
  #failFastWhenBusy;
29
30
  #handles = new Map();
@@ -3,6 +3,7 @@ import * as fs from 'fs';
3
3
  import * as path from 'path';
4
4
  import { withWriteScope } from './busy.js';
5
5
  import { errorCode, errorFields, log } from '../util/log.js';
6
+ import { placeholders } from '../util/sql.js';
6
7
  /** Legacy whole-file index; imported once into `memory_vectors`, then kept beside the store as a renamed backup. */
7
8
  const LEGACY_EMBEDDINGS_FILE = 'embeddings.json';
8
9
  export const EMBEDDING_MODEL_META_KEY = 'embedding_model';
@@ -66,7 +67,7 @@ function eachStoredVector(db, ids, take) {
66
67
  const unique = [...new Set(ids)];
67
68
  for (let i = 0; i < unique.length; i += ID_CHUNK) {
68
69
  const chunk = unique.slice(i, i + ID_CHUNK);
69
- read(db.prepare(`SELECT memory_id, vector FROM memory_vectors WHERE memory_id IN (${chunk.map(() => '?').join(', ')})`).all(...chunk));
70
+ read(db.prepare(`SELECT memory_id, vector FROM memory_vectors WHERE memory_id IN (${placeholders(chunk.length)})`).all(...chunk));
70
71
  }
71
72
  }
72
73
  /** Vectors for `ids`, or every stored vector when `ids` is omitted. */
package/dist/doctor.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { type SpoolFs } from './capture/compaction-spool.js';
1
2
  /** Outcome of one check. `fail` makes `hippo doctor` exit 1. */
2
3
  export type DoctorStatus = 'pass' | 'warn' | 'fail' | 'info';
3
4
  /** One check in a {@link DoctorReport}. */
@@ -24,6 +25,8 @@ export interface DoctorOpts {
24
25
  version: string;
25
26
  nodeVersion?: string;
26
27
  now?: Date;
28
+ /** Stands in for the fs the spool check reads; a test makes one call fail. */
29
+ spoolFs?: SpoolFs;
27
30
  }
28
31
  /** Minimum Node.js version hippo supports (package.json engines). */
29
32
  export declare const MIN_NODE = "22.16.0";
package/dist/doctor.js CHANGED
@@ -105,16 +105,16 @@ function sleepCheck(lastSleep, now) {
105
105
  : { id: 'sleep', status: 'pass', detail: `last sleep ${days === 0 ? 'today' : `${days} day${days === 1 ? '' : 's'} ago`}` };
106
106
  }
107
107
  /** Spool counts in their own try, so a spool that cannot be read never hides the database counts. */
108
- function readSpool(store, now) {
108
+ function readSpool(store, now, spoolFs) {
109
109
  try {
110
- return { counts: spoolCounts(store, now, REPLAY_AFTER_MS), unread: '' };
110
+ return { counts: spoolCounts(store, now, REPLAY_AFTER_MS, spoolFs), unread: '' };
111
111
  }
112
112
  catch (err) {
113
113
  return { counts: { waiting: 0, stale: 0, bad: 0 }, unread: `; spool not read: ${errorMessage(err)}` };
114
114
  }
115
115
  }
116
116
  /** Compaction records the PostCompact hook left unfinished and spool files a replay has not finished, which `hippo sleep` replays. */
117
- function compactionsCheck(compactions, store, now) {
117
+ function compactionsCheck(compactions, store, now, spoolFs) {
118
118
  if (!compactions.ok) {
119
119
  return compactions.message.includes('no such table')
120
120
  ? { id: 'compactions', status: 'info', detail: 'no compaction records yet (hippo creates them on the next write)' }
@@ -122,7 +122,7 @@ function compactionsCheck(compactions, store, now) {
122
122
  }
123
123
  const { total, summarised, started } = compactions.value;
124
124
  const stuck = summarised + started;
125
- const { counts: spool, unread } = readSpool(store, now);
125
+ const { counts: spool, unread } = readSpool(store, now, spoolFs);
126
126
  const spooled = spool.waiting + spool.stale + spool.bad > 0;
127
127
  const recorded = `${total} compaction${total === 1 ? '' : 's'} recorded, none stuck`;
128
128
  if (stuck === 0 && !spooled && unread === '')
@@ -260,7 +260,7 @@ function tokensCheck(tokens) {
260
260
  };
261
261
  }
262
262
  /** Checks that read the store's database; a store that cannot be opened becomes one failed schema check. */
263
- function databaseChecks(store, now) {
263
+ function databaseChecks(store, now, spoolFs) {
264
264
  let health;
265
265
  try {
266
266
  health = readStoreHealth(store, {
@@ -284,7 +284,7 @@ function databaseChecks(store, now) {
284
284
  tokensCheck(health.tokens),
285
285
  failuresCheck(health.failures, health.schemaVersion),
286
286
  sleepCheck(health.lastSleep, now),
287
- compactionsCheck(health.compactions, store, now),
287
+ compactionsCheck(health.compactions, store, now, spoolFs),
288
288
  ];
289
289
  }
290
290
  // Each hook and what it does, so a partial install says what is missing.
@@ -334,7 +334,7 @@ export function runDoctor(opts) {
334
334
  const { store, check } = storeCheck(local, globalRoot, hasGlobal);
335
335
  checks.push(check);
336
336
  if (store !== null)
337
- checks.push(...databaseChecks(store, now));
337
+ checks.push(...databaseChecks(store, now, opts.spoolFs));
338
338
  const holdoutRateBp = store === null ? 0 : loadConfig(store).pilot.holdoutRateBp;
339
339
  if (holdoutRateBp > 0) {
340
340
  checks.push({
@@ -44,8 +44,8 @@ export function createLocalEmbedder(transformers = INSTALLED_TRANSFORMERS) {
44
44
  return Array.from(output.data);
45
45
  }
46
46
  catch (err) {
47
- // The caller sees `[]` and names the memory; a failed load already warned once for its backoff window.
48
- log.debug(`local embedding failed: ${errorMessage(err)}`);
47
+ // The caller sees `[]` and names the memory; the first failure per model is told, the repeats go to debug.
48
+ log.warnThenDebug(`local-embed:${model}`, `local embedding with ${model} failed: ${errorMessage(err)}`, errorFields(err));
49
49
  return [];
50
50
  }
51
51
  },
@@ -229,15 +229,6 @@ class ApiEmbeddingProvider {
229
229
  }
230
230
  }
231
231
  }
232
- function readEmbeddingsConfig(hippoRoot) {
233
- try {
234
- return loadConfig(hippoRoot).embeddings;
235
- }
236
- catch {
237
- // An unreadable config falls back to provider defaults; loadConfig warns on a bad parse.
238
- return {};
239
- }
240
- }
241
232
  /** Validate a user-supplied API base URL: HTTPS only (or explicit localhost). */
242
233
  function validateBaseUrl(url, fallback) {
243
234
  const candidate = url?.trim();
@@ -260,7 +251,7 @@ function validateBaseUrl(url, fallback) {
260
251
  /** Build the active embedding provider from config (or an explicit override). A missing key never throws here (it surfaces via `isAvailable()`);
261
252
  * only an invalid config (e.g. insecure apiBaseUrl) throws, deliberately loud, so hot-path callers wrap it. */
262
253
  export function resolveEmbeddingProvider(hippoRoot, opts = {}) {
263
- const cfg = readEmbeddingsConfig(hippoRoot);
254
+ const cfg = loadConfig(hippoRoot).embeddings;
264
255
  const requested = opts.provider ?? cfg.provider ?? 'local';
265
256
  // An explicit embeddings.enabled=false hard-disables embedding for BOTH local
266
257
  // and API providers — for API this prevents unwanted paid off-box calls.
@@ -0,0 +1,37 @@
1
+ export { hasGroup, sqliteStore, type AmbientCandidateRequest, type ContextReads, type EntryRemoval, type EntryTarget, type EntryWrite, type EntryWrites, type HippoStore, type KeyAudit, type KeyListQuery, type KeyMint, type KeyRevoke, type KeyWrites, type LastRecall, type LastRecallWrites, type OutcomeWrite, type RawArchive, type RecallSearchArgs, type RecallWrites, type SelfKeyMint, type StoreGroup, type StoreGroups, type SupersedeWrite, type VectorReads, type VectorBackfillQuery, type VectorRowWrite, type VectorWrite, type VectorWriteResult, type VectorWrites, } from '../store/index.js';
2
+ export { entryAfterOutcome } from '../core/memory.js';
3
+ export { rejectionDigest } from '../store/rejection.js';
4
+ export { RejectedValueError } from '../core/api-errors.js';
5
+ export { ownScopeTouches } from '../core/recall-scope.js';
6
+ export type { HippoDbContext } from '../api/types.js';
7
+ export type { ApiKeyListItem, ApiKeyListRow, ApiKeyRecord, ListApiKeysOpts, NewApiKey } from '../store/auth.js';
8
+ export type { KeysetPosition } from '../util/keyset.js';
9
+ export type { AppendAuditOpts, AuditEvent, ListAuditAfterOpts } from '../store/audit.js';
10
+ export type { ContinuityBlock } from '../api/recall-types.js';
11
+ export type { ActiveGoals, GetActiveGoalsOpts, Goal, GoalRecallLogRow, RetrievalPolicy } from '../store/goals.js';
12
+ export type { MemoryEntry } from '../core/memory.js';
13
+ export type { ClassResolution, PlanningFallacyEvidence } from '../store/planning-fallacy-evidence.js';
14
+ export type { PredictionBaserate } from '../store/predictions.js';
15
+ export type { RecallTraceInput } from '../store/recall-trace.js';
16
+ export type { StrengthenOptions } from '../store/entry-writes.js';
17
+ export type { OriginFilter, RecallScopeFilter, VectorCandidateSpec } from '../store/search-rows.js';
18
+ export type { ContinuityKey } from '../store/sessions.js';
19
+ export type { SessionEvent, TaskSnapshot } from '../store/rows.js';
20
+ export type { SessionHandoff } from '../core/handoff.js';
21
+ export type { AmbientLoadResult, AmbientRecallRequest, ContextCandidateFilter, RecentOrigins } from '../store/candidates.js';
22
+ export type { AmbientStoreFilter } from '../store/ambient.js';
23
+ export type { AmbientTallies } from '../core/ambient.js';
24
+ export type { TokenUse } from '../store/token-ledger.js';
25
+ export type { EmbeddingIndexState } from '../store/vector-index.js';
26
+ export type { PhysicsParticle } from '../core/physics.js';
27
+ export { StoreBusyError } from '../store/port.js';
28
+ export { decodeVector, EMBEDDING_MODEL_META_KEY, encodeVector, rankVectorRows, type VectorMatch, type VectorRow } from '../db/vector-store.js';
29
+ export { bufferToFloat32, float32ToBuffer } from '../util/vector-codec.js';
30
+ export { replacesIndex } from '../store/vector-index.js';
31
+ export { passesScopeFilterForRecall, RECALL_DEFAULT_DENY_SCOPES } from '../core/recall-scope.js';
32
+ export { SECRET_TAGS } from '../util/secret-detect.js';
33
+ export { tallyAmbientEntries } from '../core/ambient.js';
34
+ export { ftsTermParts, rarestFtsQuery } from '../core/prompt-recall.js';
35
+ export { OTHER_STORE_MARKER } from '../db/index.js';
36
+ export { isSharedStore } from '../core/config.js';
37
+ //# sourceMappingURL=store-kit.d.ts.map
@@ -0,0 +1,25 @@
1
+ // The hippo-memory/store-kit subpath: the contract for a second HippoStore (port types, row types, hippo.db's rule helpers),
2
+ // so a store built on it applies hippo.db's rules with the same code.
3
+ // An add-on serves from another database by passing serve() its own HippoStore.
4
+ export { hasGroup, sqliteStore, } from '../store/index.js';
5
+ // An add-on store's entry writes apply an outcome, guard tombstones and check reach exactly as hippo.db does.
6
+ export { entryAfterOutcome } from '../core/memory.js';
7
+ export { rejectionDigest } from '../store/rejection.js';
8
+ export { RejectedValueError } from '../core/api-errors.js';
9
+ export { ownScopeTouches } from '../core/recall-scope.js';
10
+ export { StoreBusyError } from '../store/port.js';
11
+ // An add-on store encodes, decodes and ranks vectors and particles with hippo.db's own code, and drops the index by its rule,
12
+ // so both stores keep the same bytes and return the same ids in the same order.
13
+ export { decodeVector, EMBEDDING_MODEL_META_KEY, encodeVector, rankVectorRows } from '../db/vector-store.js';
14
+ export { bufferToFloat32, float32ToBuffer } from '../util/vector-codec.js';
15
+ export { replacesIndex } from '../store/vector-index.js';
16
+ // An add-on's ContextReads applies hippo.db's scope, secret, tally and rarest-term rules with the same code.
17
+ export { passesScopeFilterForRecall, RECALL_DEFAULT_DENY_SCOPES } from '../core/recall-scope.js';
18
+ export { SECRET_TAGS } from '../util/secret-detect.js';
19
+ export { tallyAmbientEntries } from '../core/ambient.js';
20
+ export { ftsTermParts, rarestFtsQuery } from '../core/prompt-recall.js';
21
+ // store copy --db writes the marker into the other store's folder.
22
+ export { OTHER_STORE_MARKER } from '../db/index.js';
23
+ // An add-on that serves a team store checks the flag before it starts.
24
+ export { isSharedStore } from '../core/config.js';
25
+ //# sourceMappingURL=store-kit.js.map
@@ -0,0 +1,6 @@
1
+ export { _resetAblationCacheForTests } from '../core/ablation.js';
2
+ export { _resetSharedStoreCacheForTests } from '../core/config.js';
3
+ export { _resetLossAversionRatioCacheForTests } from '../core/memory.js';
4
+ export { _resetSessionRecallHistoryMcpForTests } from '../mcp/session-state.js';
5
+ export { __resetSessionRecallHistoryHttp } from '../server/routes/recall.js';
6
+ //# sourceMappingURL=testing.d.ts.map
@@ -0,0 +1,7 @@
1
+ // Test seams for add-ons and hippo's own suite. Not for production code; no semver guarantee beyond the major version.
2
+ export { _resetAblationCacheForTests } from '../core/ablation.js';
3
+ export { _resetSharedStoreCacheForTests } from '../core/config.js';
4
+ export { _resetLossAversionRatioCacheForTests } from '../core/memory.js';
5
+ export { _resetSessionRecallHistoryMcpForTests } from '../mcp/session-state.js';
6
+ export { __resetSessionRecallHistoryHttp } from '../server/routes/recall.js';
7
+ //# sourceMappingURL=testing.js.map
@@ -99,7 +99,7 @@ async function judge(content, opts) {
99
99
  data = await readCappedJson(res, JUDGE_MAX_REPLY_BYTES);
100
100
  }
101
101
  catch (err) {
102
- log.debug(`judge: unreadable response: ${errorMessage(err)}`);
102
+ log.warnThenDebug('judge-unreadable', `judge: unreadable response: ${errorMessage(err)}`, errorFields(err));
103
103
  return null;
104
104
  }
105
105
  const durable = answerNamed(data, 'durable').noul;
@@ -7,6 +7,7 @@ import { claudeConfigDir, codexHomeDir, homeDir } from '../util/agent-homes.js';
7
7
  import { isJsonString, readJsonFile, isJsonObjectLiteral } from '../util/json.js';
8
8
  import { escapeRegex } from '../util/escape.js';
9
9
  import { writeFileAtomic } from '../util/atomic-write.js';
10
+ import { errorMessage, log } from '../util/log.js';
10
11
  /** The paths for `target` under the current home directory; reads and writes nothing. */
11
12
  export function resolveJsonHookPaths(target) {
12
13
  const home = homeDir();
@@ -366,8 +367,11 @@ export function uninstallJsonHooks(target) {
366
367
  try {
367
368
  settings = readJsonFile(settingsPath);
368
369
  }
369
- catch {
370
- // Never rewrite a settings file we cannot parse; report nothing uninstalled.
370
+ catch (err) {
371
+ // Never rewrite a settings file we cannot parse; a read failure (permissions, a directory) is the caller's to see.
372
+ if (!(err instanceof SyntaxError))
373
+ throw new Error(`could not read ${settingsPath}: ${errorMessage(err)}`, { cause: err });
374
+ log.warn(`${settingsPath} is not valid JSON, so no hippo hooks were removed from it: ${err.message}`);
371
375
  return false;
372
376
  }
373
377
  if (!isJsonObjectLiteral(settings) || !isJsonObjectLiteral(settings.hooks))
@@ -3,7 +3,7 @@
3
3
  import { DEFAULT_TENANT_ID } from '../util/env.js';
4
4
  import { createMemory, Layer } from '../core/memory.js';
5
5
  import { writeEntry } from '../store/entry-writes.js';
6
- import { loadAllEntries } from '../store/entry-reads.js';
6
+ import { loadEntryTextKeys } from '../store/entry-reads.js';
7
7
  import { duplicateKey, storedTextKeys } from '../util/same-text.js';
8
8
  import { getGlobalRoot, initGlobal } from '../sharing/global-store.js';
9
9
  import { withRequestStoresSync } from '../db/request-stores.js';
@@ -58,7 +58,7 @@ function openImportTarget(options) {
58
58
  if (options.global) {
59
59
  initGlobal();
60
60
  }
61
- const keys = storedTextKeys(loadAllEntries(targetRoot, options.global ? undefined : options.tenantId));
61
+ const keys = storedTextKeys(loadEntryTextKeys(targetRoot, options.global ? undefined : options.tenantId));
62
62
  return { targetRoot, keys };
63
63
  }
64
64
  function skipReason(chunk, keys) {
@@ -95,7 +95,7 @@ function createImportEntry(chunk, source, allTags, options, baseHalfLifeDays) {
95
95
  /** Writes the entry, or on a dry run only probes the guard; false when a rejected value refuses it. */
96
96
  function writeOrProbeImport(targetRoot, entry, options) {
97
97
  if (options.dryRun)
98
- return !rejectionGuardRefuses(targetRoot, entry.tenantId ?? DEFAULT_TENANT_ID, entry.id, entry.content);
98
+ return !rejectionGuardRefuses(targetRoot, entry.tenant_id ?? DEFAULT_TENANT_ID, entry.id, entry.content);
99
99
  // A rejection refuses one chunk, not the whole import, so siblings still land.
100
100
  try {
101
101
  writeEntry(targetRoot, entry);
@@ -1,5 +1,5 @@
1
1
  import { type ImportResult, type ImportOptions } from './core.js';
2
2
  /** Import a markdown vault FOLDER as `kind='raw'` memories.
3
3
  * NOT re-entrant: idempotency rests on the in-memory `existing` Map loaded once, so two concurrent runs over one vault can double-insert. */
4
- export declare function importVault(folderPath: string, options: ImportOptions): ImportResult;
4
+ export declare function importVault(folderPath: string, options: ImportOptions): Promise<ImportResult>;
5
5
  //# sourceMappingURL=vault.d.ts.map
@@ -8,7 +8,7 @@ import { remember, archiveRaw, isPrivateScope } from '../api/index.js';
8
8
  import { assertClientScope } from '../core/recall-scope.js';
9
9
  import { RejectedValueError } from '../core/api-errors.js';
10
10
  import { rejectionGuardRefuses } from '../store/rejected-values.js';
11
- import { withRequestStoresSync } from '../db/request-stores.js';
11
+ import { runWithRequestStores } from '../db/request-stores.js';
12
12
  import { loadConfig } from '../core/config.js';
13
13
  import { vetSecrets } from '../util/secret-detect.js';
14
14
  import { errorMessage, log } from '../util/log.js';
@@ -18,7 +18,7 @@ import { isJsonString } from '../util/json.js';
18
18
  import { escapeLike } from '../util/escape.js';
19
19
  /** Import a markdown vault FOLDER as `kind='raw'` memories.
20
20
  * NOT re-entrant: idempotency rests on the in-memory `existing` Map loaded once, so two concurrent runs over one vault can double-insert. */
21
- export function importVault(folderPath, options) {
21
+ export async function importVault(folderPath, options) {
22
22
  const hippoRoot = options.hippoRoot;
23
23
  const identity = vaultIdentityOrThrow(options);
24
24
  if (options.global) {
@@ -34,7 +34,7 @@ export function importVault(folderPath, options) {
34
34
  }
35
35
  return syncVaultFolder(folderPath, options, identity);
36
36
  }
37
- function syncVaultFolder(folderPath, options, { vaultName, scope }) {
37
+ async function syncVaultFolder(folderPath, options, { vaultName, scope }) {
38
38
  const hippoRoot = options.hippoRoot;
39
39
  const tenantId = options.tenantId ?? DEFAULT_TENANT_ID;
40
40
  const extraTags = options.extraTags ?? [];
@@ -54,15 +54,15 @@ function syncVaultFolder(folderPath, options, { vaultName, scope }) {
54
54
  const run = {
55
55
  ctx, folderPath, vaultName, scope, extraTags, dryRun, existing, seen, baseHalfLifeDays, tally,
56
56
  };
57
- const importNotes = () => {
57
+ const importNotes = async () => {
58
58
  for (const relpath of relpaths)
59
- importVaultNote(run, relpath);
59
+ await importVaultNote(run, relpath);
60
60
  };
61
61
  // A dry run probes the rejection guard once per changed note; one scope lets every probe share a handle.
62
62
  if (dryRun)
63
- withRequestStoresSync(importNotes);
63
+ await runWithRequestStores(importNotes);
64
64
  else
65
- importNotes();
65
+ await importNotes();
66
66
  // Deletion-sync: an artifactRef in the Map but not seen this run vanished from the source, so archive its raw row.
67
67
  // Per-file archiveRaw, no outer SAVEPOINT: there is no cross-file idempotency row to commit atomically.
68
68
  for (const [artifactRef, rows] of existing) {
@@ -71,7 +71,7 @@ function syncVaultFolder(folderPath, options, { vaultName, scope }) {
71
71
  for (const row of rows) {
72
72
  tally.archived++; // count even in dryRun so the preview reflects destructive deletes
73
73
  if (!dryRun)
74
- archiveRaw(ctx, row.id, `source_deleted:${artifactRef}`);
74
+ await archiveRaw(ctx, row.id, `source_deleted:${artifactRef}`);
75
75
  }
76
76
  }
77
77
  const { total, imported, skipped, rejected, archived, redacted, entries } = tally;
@@ -120,7 +120,7 @@ function loadVaultRows(hippoRoot, tenantId, vaultName) {
120
120
  }
121
121
  return existing;
122
122
  }
123
- function importVaultNote(run, relpath) {
123
+ async function importVaultNote(run, relpath) {
124
124
  const { tally } = run;
125
125
  tally.total++;
126
126
  const artifactRef = `vault:${run.vaultName}:${relpath}`;
@@ -137,7 +137,7 @@ function importVaultNote(run, relpath) {
137
137
  // Empty or frontmatter-only note: nothing storable, and the content was deleted at source, so archive prior rows (the old body must not stay searchable)
138
138
  // and skip the write. Safe because remember() is skipped, so there is no archive-then-throw hazard; the note stays in `seen` so deletion-sync skips it.
139
139
  if (body.trim().length < 3) {
140
- archivePriors(run, priors, `emptied:${artifactRef}`);
140
+ await archivePriors(run, priors, `emptied:${artifactRef}`);
141
141
  tally.skipped++;
142
142
  return;
143
143
  }
@@ -147,13 +147,13 @@ function importVaultNote(run, relpath) {
147
147
  tally.skipped++;
148
148
  return;
149
149
  }
150
- writeChangedNote(run, artifactRef, priors, body, tags);
150
+ await writeChangedNote(run, artifactRef, priors, body, tags);
151
151
  }
152
- function writeChangedNote(run, artifactRef, priors, body, tags) {
152
+ async function writeChangedNote(run, artifactRef, priors, body, tags) {
153
153
  const { ctx, tally } = run;
154
154
  // Changed file: archive EVERY old raw row for this ref (>1 only after a concurrent double-insert), then append the new one; NEVER supersede.
155
155
  // archiveRaw commits before remember() runs, so no double-live row; a crash between them self-heals on the next import.
156
- archivePriors(run, priors, `changed:${artifactRef}`);
156
+ await archivePriors(run, priors, `changed:${artifactRef}`);
157
157
  // remember() owns the write; the `echo` from createMemory only fills the ImportResult, with its id reconciled to the row that landed.
158
158
  const content = vetSecrets(body, tags, true).content;
159
159
  const echo = createMemory(content, {
@@ -165,7 +165,7 @@ function writeChangedNote(run, artifactRef, priors, body, tags) {
165
165
  tenantId: ctx.tenantId,
166
166
  baseHalfLifeDays: run.baseHalfLifeDays,
167
167
  });
168
- if (!storeVaultNote(run, echo, content, tags, artifactRef)) {
168
+ if (!(await storeVaultNote(run, echo, content, tags, artifactRef))) {
169
169
  tally.rejected++;
170
170
  return;
171
171
  }
@@ -185,11 +185,11 @@ function readVaultNote(run, relpath) {
185
185
  return null;
186
186
  }
187
187
  }
188
- function archivePriors(run, priors, reason) {
188
+ async function archivePriors(run, priors, reason) {
189
189
  for (const p of priors) {
190
190
  run.tally.archived++; // count the would-be archive even in dryRun (true preview)
191
191
  if (!run.dryRun)
192
- archiveRaw(run.ctx, p.id, reason);
192
+ await archiveRaw(run.ctx, p.id, reason);
193
193
  }
194
194
  }
195
195
  function vaultNoteTags(run, hashTag, fm, body) {
@@ -221,14 +221,14 @@ function vaultEnvelopeUnchanged(priors, tags, scope) {
221
221
  }));
222
222
  }
223
223
  /** Returns false when the rejection guard refuses the note; any other error propagates. */
224
- function storeVaultNote(run, echo, content, tags, artifactRef) {
224
+ async function storeVaultNote(run, echo, content, tags, artifactRef) {
225
225
  // A dry run writes nothing; it asks the guard remember() uses, so its `rejected` count matches what a real run would refuse.
226
226
  if (run.dryRun)
227
227
  return !rejectionGuardRefuses(run.ctx.hippoRoot, run.ctx.tenantId, echo.id, echo.content);
228
228
  try {
229
229
  // A rejected note must not stop the scan or the deletion sync. Its priors are archived by now, so a rerun meets
230
230
  // the same refusal and counts it again.
231
- const result = remember(run.ctx, {
231
+ const result = await remember(run.ctx, {
232
232
  content,
233
233
  kind: 'raw',
234
234
  artifactRef,
package/dist/index.d.ts CHANGED
@@ -20,7 +20,7 @@ export { initStore } from './store/open.js';
20
20
  export { writeEntry } from './store/entry-writes.js';
21
21
  export { loadAllEntries, readEntry } from './store/entry-reads.js';
22
22
  export { deleteEntry } from './store/delete-and-batch.js';
23
- export { loadSearchEntries, loadRecallSearchEntries } from './store/search-rows.js';
23
+ export { loadSearchEntries, loadRecallEntries, type RecallSearchEntriesOptions } from './store/search-rows.js';
24
24
  export { loadIndex, rebuildIndex, loadSessionDecayContext, SessionDecayContext } from './store/index-and-stats.js';
25
25
  export { saveActiveTaskSnapshot, loadActiveTaskSnapshot, loadFreshActiveTaskSnapshot, SNAPSHOT_AMBIENT_MAX_AGE_MS, closeTaskSnapshotsForSession, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, type ContinuityKey, } from './store/sessions.js';
26
26
  export { listMemoryConflicts, replaceDetectedConflicts, resolveConflict } from './store/conflicts.js';
@@ -36,7 +36,7 @@ export { isEmbeddingAvailable, getEmbedding } from './embeddings/local.js';
36
36
  export { cosineSimilarity } from './store/embeddings/index.js';
37
37
  export { embedMemory, embedAll } from './search/embed-memories.js';
38
38
  export { loadEmbeddingIndex, saveEmbeddingIndex } from './store/vector-index.js';
39
- export { captureError, extractLessons, partitionLessons, deduplicateLesson, runWatched, fetchGitLog, } from './learn/autolearn.js';
39
+ export { captureError, extractLessons, partitionLessons, deduplicateLesson, runWatched, type RunWatchedOptions, fetchGitLog, } from './learn/autolearn.js';
40
40
  export { getGlobalRoot, initGlobal, promoteToGlobal } from './sharing/global-store.js';
41
41
  export { searchBoth, searchBothHybrid, HybridSearchOptions } from './sharing/search-both.js';
42
42
  export { syncGlobalToLocal } from './sharing/global-sync.js';
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ export { initStore } from './store/open.js';
21
21
  export { writeEntry } from './store/entry-writes.js';
22
22
  export { loadAllEntries, readEntry } from './store/entry-reads.js';
23
23
  export { deleteEntry } from './store/delete-and-batch.js';
24
- export { loadSearchEntries, loadRecallSearchEntries } from './store/search-rows.js';
24
+ export { loadSearchEntries, loadRecallEntries } from './store/search-rows.js';
25
25
  export { loadIndex, rebuildIndex, loadSessionDecayContext } from './store/index-and-stats.js';
26
26
  export { saveActiveTaskSnapshot, loadActiveTaskSnapshot, loadFreshActiveTaskSnapshot, SNAPSHOT_AMBIENT_MAX_AGE_MS, closeTaskSnapshotsForSession, clearActiveTaskSnapshot, appendSessionEvent, listSessionEvents, } from './store/sessions.js';
27
27
  export { listMemoryConflicts, replaceDetectedConflicts, resolveConflict } from './store/conflicts.js';
@@ -23,9 +23,13 @@ export declare class RunWatchedTimeoutError extends Error {
23
23
  readonly stderr: string;
24
24
  constructor(deadlineMs: number, stderr: string);
25
25
  }
26
- /** Run a command in a shell, streaming stderr live; never pass text the caller did not write.
26
+ export interface RunWatchedOptions {
27
+ /** Join argv with spaces and run it as one shell command line; only for text the caller wrote itself. */
28
+ shell?: boolean;
29
+ }
30
+ /** Run argv[0] with the rest as its arguments and no shell, so `;`, `&` and `$()` reach it literally; stderr streams live.
27
31
  * Returns: { exitCode, stderr } with only the last 64 KiB of stderr; rejects with RunWatchedTimeoutError past `deadlineMs`. */
28
- export declare function runWatched(command: string, deadlineMs?: number): Promise<{
32
+ export declare function runWatched(argv: readonly string[], deadlineMs?: number, opts?: RunWatchedOptions): Promise<{
29
33
  exitCode: number;
30
34
  stderr: string;
31
35
  }>;
@@ -1,6 +1,7 @@
1
1
  /** Auto-learn from errors and git history.
2
2
  * Agents learn from failures without explicit hippo remember calls. */
3
3
  import { execFileSync, spawn } from 'child_process';
4
+ import { spawnArgv } from '../util/spawn-argv.js';
4
5
  import { createMemory, Layer, DEFAULT_HALF_LIFE_DAYS } from '../core/memory.js';
5
6
  import { loadAllEntries } from '../store/entry-reads.js';
6
7
  import { textOverlap } from '../util/tokenize.js';
@@ -112,20 +113,21 @@ export class RunWatchedTimeoutError extends Error {
112
113
  this.name = 'RunWatchedTimeoutError';
113
114
  }
114
115
  }
115
- /** Run a command in a shell, streaming stderr live; never pass text the caller did not write.
116
+ /** Run argv[0] with the rest as its arguments and no shell, so `;`, `&` and `$()` reach it literally; stderr streams live.
116
117
  * Returns: { exitCode, stderr } with only the last 64 KiB of stderr; rejects with RunWatchedTimeoutError past `deadlineMs`. */
117
- export function runWatched(command, deadlineMs = RUN_WATCHED_DEADLINE_MS) {
118
+ export function runWatched(argv, deadlineMs = RUN_WATCHED_DEADLINE_MS, opts = {}) {
118
119
  return new Promise((resolve, reject) => {
119
- // Use shell: true so the command string is handled by the shell as-is
120
- const child = spawn(command, { shell: true, stdio: ['inherit', 'inherit', 'pipe'], windowsHide: true });
120
+ const options = { stdio: ['inherit', 'inherit', 'pipe'], windowsHide: true };
121
+ const child = opts.shell ? spawn(argv.join(' '), { ...options, windowsHide: true, shell: true }) : spawnArgv(argv, options);
121
122
  let tail = Buffer.alloc(0);
122
- child.stderr.on('data', (chunk) => {
123
+ child.stderr?.on('data', (chunk) => {
123
124
  tail = Buffer.concat([tail, chunk]);
124
125
  if (tail.length > STDERR_TAIL_BYTES)
125
126
  tail = tail.subarray(tail.length - STDERR_TAIL_BYTES);
126
127
  process.stderr.write(chunk);
127
128
  });
128
- // SHORTCUT: kills the shell only; on Windows a grandchild can outlive it. A process-tree kill is the upgrade path.
129
+ // SHORTCUT: kills the direct child only (the shell or cmd.exe when there is one); on Windows a grandchild can outlive it.
130
+ // A process-tree kill is the upgrade path.
129
131
  const timer = setTimeout(() => {
130
132
  child.kill('SIGKILL');
131
133
  reject(new RunWatchedTimeoutError(deadlineMs, tail.toString('utf8')));
@@ -118,7 +118,7 @@ function buildExtractedEntry(fact, source, inheritedTags, baseHalfLifeDays) {
118
118
  scope: source.scope,
119
119
  // Without it createMemory stamps 'default', and extracted facts leave
120
120
  // the tenant of the episodic memory they were extracted from.
121
- tenantId: source.tenantId,
121
+ tenantId: source.tenant_id,
122
122
  baseHalfLifeDays,
123
123
  }), origin_project: source.origin_project };
124
124
  }
@@ -1,3 +1,4 @@
1
+ import { type HippoStore } from '../store/port.js';
1
2
  import { type MemoryEntry } from '../core/memory.js';
2
3
  export interface InvalidationTarget {
3
4
  from: string;
@@ -28,7 +29,7 @@ export declare function extractInvalidationTarget(message: string): Invalidation
28
29
  export declare function invalidateMatching(hippoRoot: string, target: InvalidationTarget, tenantId?: string, options?: InvalidationOptions): InvalidationResult;
29
30
  /** invalidateMatching for a batch: matches against `live`, the caller's list of the tenant's rows, and keeps it current.
30
31
  * Each match is read again before it is weakened, so a row another writer changed since the list was read is never written back stale. */
31
- export declare function invalidateMatchingAmong(hippoRoot: string, live: MemoryEntry[], target: InvalidationTarget, tenantId?: string): InvalidationResult;
32
+ export declare function invalidateMatchingAmong(store: HippoStore, live: MemoryEntry[], target: InvalidationTarget, tenantId?: string): Promise<InvalidationResult>;
32
33
  export interface ChurnRefs {
33
34
  paths: string[];
34
35
  symbols: string[];
@@ -1,6 +1,7 @@
1
1
  import { addTagToEntries, writeEntriesTogether } from '../store/entry-writes.js';
2
2
  import { confirmedOutcomeTimes } from '../store/audit.js';
3
- import { loadAllEntries, loadEntriesByIds, loadEntriesMentioning, readEntry } from '../store/entry-reads.js';
3
+ import { loadAllEntries, loadEntriesMentioning, readEntry } from '../store/entry-reads.js';
4
+ import { requireGroup } from '../store/port.js';
4
5
  import { chunked } from '../util/chunked.js';
5
6
  import { CHURN_STALE_TAG } from '../core/memory.js';
6
7
  import { GitReadError, gitLsFilesAtHead, fetchChurnWindowLog, gitGrepPresence, resolveCommitBefore, packageScriptsAt, } from './churn-git.js';
@@ -57,13 +58,17 @@ export function invalidateMatching(hippoRoot, target, tenantId, options) {
57
58
  }
58
59
  /** invalidateMatching for a batch: matches against `live`, the caller's list of the tenant's rows, and keeps it current.
59
60
  * Each match is read again before it is weakened, so a row another writer changed since the list was read is never written back stale. */
60
- export function invalidateMatchingAmong(hippoRoot, live, target, tenantId) {
61
+ export async function invalidateMatchingAmong(store, live, target, tenantId) {
61
62
  const seen = weakenMatches(live, target, { dryRun: true }).result;
62
63
  const ids = [...seen.targets, ...seen.skippedPinned];
63
- // loadEntriesByIds reads at most 500 ids a call.
64
- const fresh = new Map(chunked(ids, 500).flatMap((chunk) => loadEntriesByIds(hippoRoot, chunk, tenantId)).map((entry) => [entry.id, entry]));
64
+ const fresh = new Map();
65
+ // The port reads at most 500 ids a call.
66
+ for (const chunk of chunked(ids, 500))
67
+ for (const entry of await store.entriesByIds(chunk, tenantId))
68
+ fresh.set(entry.id, entry);
65
69
  const { result, weakened } = weakenMatches(ids.flatMap((id) => fresh.get(id) ?? []), target);
66
- writeEntriesTogether(hippoRoot, weakened);
70
+ if (weakened.length > 0)
71
+ await requireGroup(store, 'lessonWrites').writeTogether(weakened);
67
72
  const byId = new Map(weakened.map((entry) => [entry.id, entry]));
68
73
  for (let i = 0; i < live.length; i++)
69
74
  live[i] = byId.get(live[i].id) ?? live[i];
@@ -1,5 +1,5 @@
1
1
  import { type ToolCall } from './protocol.js';
2
2
  export declare function runRememberTool({ args, ctx, hippoRoot, config, tenantId }: ToolCall): Promise<string>;
3
3
  export declare function runOutcomeTool({ args, ctx, hippoRoot, tenantId }: ToolCall): Promise<string>;
4
- export declare function runLearnTool({ args, ctx, hippoRoot, tenantId }: ToolCall): string;
4
+ export declare function runLearnTool({ args, ctx, hippoRoot, tenantId }: ToolCall): Promise<string>;
5
5
  //# sourceMappingURL=memory-tools.d.ts.map
@@ -1,5 +1,5 @@
1
1
  // Write-side tool handlers: hippo_remember, hippo_outcome and hippo_learn.
2
- import { storeFor } from '../store/index.js';
2
+ import { hasGroup } from '../store/index.js';
3
3
  import { startAutoSleepIfDue } from '../api/auto-sleep.js';
4
4
  import { remember as apiRemember, outcome as apiOutcome, learn as apiLearn, MCP_LEARN } from '../api/index.js';
5
5
  import { mcpActor } from './protocol.js';
@@ -14,7 +14,6 @@ export async function runRememberTool({ args, ctx, hippoRoot, config, tenantId }
14
14
  if (args.tag)
15
15
  tags.push(String(args.tag));
16
16
  // Route through api/index.ts so audit_log captures the caller identity like CLI/REST: the auth-resolved ctx.actor under HTTP-MCP, 'mcp' for stdio.
17
- // The entry is re-read to surface the half-life used in the MCP response.
18
17
  const apiCtx = {
19
18
  hippoRoot,
20
19
  tenantId,
@@ -27,14 +26,12 @@ export async function runRememberTool({ args, ctx, hippoRoot, config, tenantId }
27
26
  personal: args.personal === true,
28
27
  project: ctx?.project,
29
28
  });
30
- const [entry] = await storeFor(apiCtx).entriesByIds([result.id], tenantId);
31
29
  // Auto-sleep runs on hippo.db, so another store skips it.
32
- const allowed = (ctx?.store === undefined || ctx.store.kind === 'sqlite') && ctx?.autoSleep !== false;
30
+ const allowed = (ctx?.store === undefined || hasGroup(ctx.store, 'localFiles')) && ctx?.autoSleep !== false;
33
31
  startAutoSleepIfDue(hippoRoot, tenantId, config.autoSleep, allowed, ctx?.autoSleepRunner);
34
- const halfLife = entry?.half_life_days ?? config.defaultHalfLifeDays;
35
- const tagStr = entry?.tags.join(', ') || tags.join(', ') || 'none';
32
+ const tagStr = result.tags.join(', ') || 'none';
36
33
  const warnings = (result.warnings ?? []).map((w) => `\nWarning: ${w}`).join('');
37
- return `Remembered [${result.id}] (half-life: ${halfLife}d, tags: ${tagStr})${warnings}`;
34
+ return `Remembered [${result.id}] (half-life: ${result.halfLifeDays}d, tags: ${tagStr})${warnings}`;
38
35
  }
39
36
  export async function runOutcomeTool({ args, ctx, hippoRoot, tenantId }) {
40
37
  const good = Boolean(args.good);
@@ -53,10 +50,10 @@ export async function runOutcomeTool({ args, ctx, hippoRoot, tenantId }) {
53
50
  const { applied } = await apiOutcome(apiCtx, ids, good);
54
51
  return `Applied ${good ? 'positive' : 'negative'} outcome to ${applied} memories`;
55
52
  }
56
- export function runLearnTool({ args, ctx, hippoRoot, tenantId }) {
53
+ export async function runLearnTool({ args, ctx, hippoRoot, tenantId }) {
57
54
  const days = Number(args.days) || 7;
58
55
  // process.cwd() is the server's repo, not a remote caller's, so api learn refuses all but stdio and a host admin (mcpActor fails closed).
59
- const result = apiLearn({ hippoRoot, tenantId, actor: mcpActor(ctx), store: ctx?.store }, { repoPath: process.cwd(), days, profile: MCP_LEARN });
56
+ const result = await apiLearn({ hippoRoot, tenantId, actor: mcpActor(ctx), store: ctx?.store }, { repoPath: process.cwd(), days, profile: MCP_LEARN });
60
57
  if (result.status === 'not-a-repo')
61
58
  return 'No git history found.';
62
59
  if (result.status === 'no-commits')