hippo-memory 1.52.8 → 1.53.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 (122) hide show
  1. package/README.md +185 -101
  2. package/dist/agent-memories/apply.d.ts +47 -0
  3. package/dist/agent-memories/apply.js +253 -0
  4. package/dist/agent-memories/claude-code.d.ts +11 -0
  5. package/dist/agent-memories/claude-code.js +113 -0
  6. package/dist/agent-memories/codex.d.ts +3 -0
  7. package/dist/agent-memories/codex.js +47 -0
  8. package/dist/agent-memories/copilot.d.ts +3 -0
  9. package/dist/agent-memories/copilot.js +125 -0
  10. package/dist/agent-memories/files.d.ts +37 -0
  11. package/dist/agent-memories/files.js +77 -0
  12. package/dist/agent-memories/folder-store.d.ts +17 -0
  13. package/dist/agent-memories/folder-store.js +44 -0
  14. package/dist/agent-memories/gemini.d.ts +3 -0
  15. package/dist/agent-memories/gemini.js +103 -0
  16. package/dist/agent-memories/git.d.ts +8 -0
  17. package/dist/agent-memories/git.js +11 -0
  18. package/dist/agent-memories/keys.d.ts +9 -0
  19. package/dist/agent-memories/keys.js +20 -0
  20. package/dist/agent-memories/legacy.d.ts +17 -0
  21. package/dist/agent-memories/legacy.js +45 -0
  22. package/dist/agent-memories/markdown.d.ts +13 -0
  23. package/dist/agent-memories/markdown.js +123 -0
  24. package/dist/agent-memories/openclaw.d.ts +3 -0
  25. package/dist/agent-memories/openclaw.js +42 -0
  26. package/dist/agent-memories/plan.d.ts +78 -0
  27. package/dist/agent-memories/plan.js +123 -0
  28. package/dist/agent-memories/qwen-code.d.ts +5 -0
  29. package/dist/agent-memories/qwen-code.js +50 -0
  30. package/dist/agent-memories/report.d.ts +52 -0
  31. package/dist/agent-memories/report.js +88 -0
  32. package/dist/agent-memories/source.d.ts +16 -0
  33. package/dist/agent-memories/source.js +32 -0
  34. package/dist/agent-memories/sync.d.ts +33 -0
  35. package/dist/agent-memories/sync.js +336 -0
  36. package/dist/agent-memories/tools.d.ts +33 -0
  37. package/dist/agent-memories/tools.js +19 -0
  38. package/dist/agent-memories/types.d.ts +42 -0
  39. package/dist/agent-memories/types.js +2 -0
  40. package/dist/api.d.ts +53 -20
  41. package/dist/api.js +141 -97
  42. package/dist/audit.d.ts +2 -1
  43. package/dist/audit.js +68 -2
  44. package/dist/capture.d.ts +48 -22
  45. package/dist/capture.js +186 -161
  46. package/dist/cli.d.ts +0 -2
  47. package/dist/cli.js +750 -797
  48. package/dist/codex-patch.d.ts +12 -0
  49. package/dist/codex-patch.js +71 -0
  50. package/dist/compaction-items.d.ts +18 -0
  51. package/dist/compaction-items.js +60 -0
  52. package/dist/compaction-record.d.ts +94 -0
  53. package/dist/compaction-record.js +546 -0
  54. package/dist/config.d.ts +4 -1
  55. package/dist/config.js +13 -4
  56. package/dist/connectors/slack/types.d.ts +0 -1
  57. package/dist/consolidate.js +87 -34
  58. package/dist/context-render.d.ts +36 -0
  59. package/dist/context-render.js +154 -0
  60. package/dist/dag.js +3 -2
  61. package/dist/db.d.ts +5 -1
  62. package/dist/db.js +46 -14
  63. package/dist/dedupe.d.ts +6 -6
  64. package/dist/dedupe.js +10 -9
  65. package/dist/doctor.d.ts +1 -1
  66. package/dist/doctor.js +69 -4
  67. package/dist/dormant.d.ts +9 -3
  68. package/dist/dormant.js +26 -2
  69. package/dist/embedding-provider.d.ts +2 -1
  70. package/dist/embedding-provider.js +2 -1
  71. package/dist/embeddings.js +23 -3
  72. package/dist/extract.js +5 -1
  73. package/dist/forward-claim-detector.d.ts +1 -1
  74. package/dist/forward-claim-detector.js +1 -1
  75. package/dist/gated-write.d.ts +9 -0
  76. package/dist/gated-write.js +24 -0
  77. package/dist/graph-recall.d.ts +3 -1
  78. package/dist/graph-recall.js +5 -3
  79. package/dist/hooks.d.ts +18 -2
  80. package/dist/hooks.js +128 -32
  81. package/dist/importers.js +5 -12
  82. package/dist/judgment.d.ts +30 -0
  83. package/dist/judgment.js +122 -0
  84. package/dist/mcp/server.js +171 -210
  85. package/dist/memory.d.ts +19 -2
  86. package/dist/memory.js +35 -3
  87. package/dist/merged-row.d.ts +6 -0
  88. package/dist/merged-row.js +35 -0
  89. package/dist/multihop.d.ts +2 -1
  90. package/dist/multihop.js +7 -4
  91. package/dist/physics-state.d.ts +0 -4
  92. package/dist/physics-state.js +0 -6
  93. package/dist/predictions.d.ts +2 -17
  94. package/dist/predictions.js +2 -15
  95. package/dist/reject-flow.d.ts +7 -5
  96. package/dist/reject-flow.js +41 -12
  97. package/dist/salience.js +12 -5
  98. package/dist/same-text.d.ts +17 -0
  99. package/dist/same-text.js +38 -0
  100. package/dist/scheduler.d.ts +4 -0
  101. package/dist/scheduler.js +8 -0
  102. package/dist/search.d.ts +7 -0
  103. package/dist/search.js +16 -32
  104. package/dist/secret-detect.d.ts +2 -0
  105. package/dist/secret-detect.js +6 -0
  106. package/dist/server-detect.js +9 -33
  107. package/dist/server.js +6 -62
  108. package/dist/session-digest.d.ts +79 -0
  109. package/dist/session-digest.js +528 -0
  110. package/dist/shared.d.ts +10 -2
  111. package/dist/shared.js +44 -36
  112. package/dist/store.d.ts +9 -2
  113. package/dist/store.js +25 -2
  114. package/dist/token-ledger.d.ts +46 -8
  115. package/dist/token-ledger.js +140 -21
  116. package/dist/version.d.ts +1 -1
  117. package/dist/version.js +1 -1
  118. package/extensions/openclaw-plugin/README.md +4 -4
  119. package/extensions/openclaw-plugin/openclaw.plugin.json +2 -2
  120. package/extensions/openclaw-plugin/package.json +1 -1
  121. package/openclaw.plugin.json +2 -2
  122. package/package.json +2 -2
@@ -0,0 +1,336 @@
1
+ // Runs the adapters and routes each container to its store: the project pass, the user pass and their call sites (plan designs 2, 8, 11).
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+ import { errorMessage } from '../capture.js';
6
+ import { loadConfig } from '../config.js';
7
+ import { closeHippoDb, isSqliteBusy, openHippoDb } from '../db.js';
8
+ import { deriveOriginProject, isGlobalStoreRoot, resolveGlobalRootDir } from '../project-identity.js';
9
+ import { duplicateKey, heldTextKeys } from '../same-text.js';
10
+ import { initStore, isInitialized, removeEntryMirrors, selectLiveEntriesBySourcePrefix, updateStats, writeEntryMirrors } from '../store.js';
11
+ import { resolveTenantId } from '../tenant.js';
12
+ import { setAsideRow, syncContainer } from './apply.js';
13
+ import { claudeCodeAdapter, claudeTranscriptListing } from './claude-code.js';
14
+ import { codexAdapter } from './codex.js';
15
+ import { copilotAdapter } from './copilot.js';
16
+ import { geminiAdapter } from './gemini.js';
17
+ import { legacyWork } from './legacy.js';
18
+ import { openclawAdapter } from './openclaw.js';
19
+ import { qwenCodeAdapter } from './qwen-code.js';
20
+ import { addTally, emptyReport, mergeReports, toolReport } from './report.js';
21
+ import { containerId, containerPrefix, splitSource } from './source.js';
22
+ import { AGENT_MEMORY_SOURCE_PREFIX, AGENT_MEMORY_TOOLS, isToolId, toolSourcePrefix } from './tools.js';
23
+ export const ADAPTERS = [claudeCodeAdapter, codexAdapter, geminiAdapter, copilotAdapter, openclawAdapter, qwenCodeAdapter];
24
+ /** Overrides config because `hippo init` creates the store in the same command that imports (plan design 11). */
25
+ export const TOOLS_ENV = 'HIPPO_AGENT_MEMORY_TOOLS';
26
+ export function currentMachine() {
27
+ return { home: os.homedir(), env: process.env, platform: process.platform };
28
+ }
29
+ /** init, sleep, `import --agents` and the daily runner: a local store gets its project pass then the user pass; the global store the user pass only. */
30
+ export function importForStore(hippoRoot, opts) {
31
+ if (isGlobalStoreRoot(hippoRoot))
32
+ return importUserMemories(hippoRoot, opts);
33
+ const report = importProjectMemories(hippoRoot, opts);
34
+ mergeReports(report, importUserMemories(hippoRoot, opts));
35
+ return report;
36
+ }
37
+ /** A local store's project pass with legacy adoption; `init --scan` runs it per repository and the user pass once. */
38
+ export function importProjectMemories(hippoRoot, opts) {
39
+ const ctx = context(opts.machine, { projectRoot: path.dirname(hippoRoot) });
40
+ return runPass({
41
+ scope: 'project', target: hippoRoot, invoking: hippoRoot, list: (a) => a.list(ctx, 'project'), legacy: true, originProject: undefined, handover: true,
42
+ }, opts);
43
+ }
44
+ /** Every tool's user-level memory into the global store, created on demand, with no origin. */
45
+ export function importUserMemories(invokingRoot, opts) {
46
+ const ctx = context(opts.machine, {});
47
+ return runPass({
48
+ scope: 'user', target: resolveGlobalRootDir(), invoking: invokingRoot, list: (a) => a.list(ctx, 'user'), legacy: false, originProject: '', handover: false,
49
+ }, opts);
50
+ }
51
+ /** Session end in a folder with no store of its own: the session's project into the global store with its origin, then the user pass. */
52
+ export function importAtSessionEnd(cwd, transcriptPath, opts) {
53
+ const globalRoot = resolveGlobalRootDir();
54
+ const ctx = context(opts.machine, { projectRoot: cwd, transcriptPath });
55
+ const report = runPass({
56
+ scope: 'project', target: globalRoot, invoking: globalRoot, list: (a) => a.list(ctx, 'project'), legacy: false, originProject: deriveOriginProject(cwd), handover: false,
57
+ }, opts);
58
+ mergeReports(report, importUserMemories(globalRoot, opts));
59
+ return report;
60
+ }
61
+ /** Post-compact: the transcript folder's notes only, with no git call, no legacy adoption and no user pass, as the hook has 10 seconds. */
62
+ export function importAtCompaction(hippoRoot, transcriptPath, originProject, opts) {
63
+ const ctx = context(opts.machine, {});
64
+ return runPass({
65
+ scope: 'project', target: hippoRoot, invoking: hippoRoot, legacy: false, originProject, handover: false,
66
+ list: (a) => (a.tool === 'claude-code' ? claudeTranscriptListing(ctx, transcriptPath) : null),
67
+ }, opts);
68
+ }
69
+ function context(machine, extra) {
70
+ return { home: machine.home, env: machine.env, platform: machine.platform, ...extra };
71
+ }
72
+ /** The variable when set (empty or `none` is off); else the tools both the invoking and the target store's config allow. */
73
+ export function allowedTools(env, invoking, target, warnings) {
74
+ const fromEnv = env[TOOLS_ENV];
75
+ if (fromEnv !== undefined) {
76
+ const listed = fromEnv.trim().toLowerCase() === 'none' ? [] : fromEnv.split(',').map((t) => t.trim()).filter((t) => t !== '');
77
+ return knownTools(listed, TOOLS_ENV, warnings);
78
+ }
79
+ const mine = configuredTools(invoking, warnings);
80
+ const theirs = configuredTools(target, warnings);
81
+ return new Set(AGENT_MEMORY_TOOLS.map((t) => t.id).filter((id) => (mine === null || mine.has(id)) && (theirs === null || theirs.has(id))));
82
+ }
83
+ function configuredTools(root, warnings) {
84
+ const tools = loadConfig(root).agentMemories.tools;
85
+ return tools === null ? null : knownTools(tools, 'config agentMemories.tools', warnings);
86
+ }
87
+ function knownTools(ids, where, warnings) {
88
+ const out = new Set();
89
+ for (const id of ids) {
90
+ if (isToolId(id))
91
+ out.add(id);
92
+ else
93
+ warnings.push(`${where}: unknown agent memory tool "${id}" ignored (known: ${AGENT_MEMORY_TOOLS.map((t) => t.id).join(', ')})`);
94
+ }
95
+ return out;
96
+ }
97
+ function runPass(pass, opts) {
98
+ const report = emptyReport();
99
+ const allowed = allowedTools(opts.machine.env, pass.invoking, pass.target, report.warnings);
100
+ const listings = ADAPTERS.filter((a) => allowed.has(a.tool)).flatMap((a) => listSafely(a, pass, report));
101
+ const hasItems = listings.some((l) => l.containers.some((c) => c.items.length > 0));
102
+ let store = null;
103
+ let synced = [];
104
+ try {
105
+ store = openTarget(pass.target, hasItems, opts);
106
+ if (store !== null)
107
+ synced = syncStore(pass, listings, store, opts, report);
108
+ }
109
+ catch (err) {
110
+ report.warnings.push(`agent memories not synced into ${pass.target}: ${isSqliteBusy(err) ? 'the store was busy' : errorMessage(err)}`);
111
+ }
112
+ finally {
113
+ store?.close();
114
+ }
115
+ if (pass.handover && !opts.dryRun)
116
+ handOver(synced, path.dirname(pass.target), opts, report);
117
+ return report;
118
+ }
119
+ function listSafely(adapter, pass, report) {
120
+ const tool = toolReport(report, adapter.tool);
121
+ let listing;
122
+ try {
123
+ listing = pass.list(adapter);
124
+ }
125
+ catch (err) {
126
+ report.warnings.push(`${tool.label}: ${errorMessage(err)}`);
127
+ return [];
128
+ }
129
+ if (listing === null)
130
+ return [];
131
+ if (!tool.homes.includes(listing.home))
132
+ tool.homes.push(listing.home);
133
+ report.warnings.push(...listing.warnings.map((w) => `${tool.label}: ${w}`));
134
+ for (const c of listing.containers) {
135
+ tool.containers.push({ scope: c.scope, path: c.path, store: pass.target, items: c.items.length, readable: c.readable });
136
+ report.warnings.push(...c.warnings.map((w) => `${tool.label}: ${w}`));
137
+ }
138
+ return [listing];
139
+ }
140
+ /** A store that does not exist yet is created only when there is something to put in it; a dry run plans against an empty stand-in. */
141
+ function openTarget(target, hasItems, opts) {
142
+ const global = isGlobalStoreRoot(target);
143
+ if (!isInitialized(target)) {
144
+ if (opts.dryRun)
145
+ return emptyStandIn(global);
146
+ if (!hasItems)
147
+ return null;
148
+ initStore(target);
149
+ }
150
+ const db = openHippoDb(target, { busyWaitMs: opts.busyWaitMs });
151
+ return { db, root: target, global, close: () => closeHippoDb(db) };
152
+ }
153
+ function emptyStandIn(global) {
154
+ const root = fs.mkdtempSync(path.join(os.tmpdir(), 'hippo-agent-memories-'));
155
+ const db = openHippoDb(root);
156
+ return {
157
+ db, root, global,
158
+ close: () => {
159
+ closeHippoDb(db);
160
+ fs.rmSync(root, { recursive: true, force: true });
161
+ },
162
+ };
163
+ }
164
+ function syncStore(pass, listings, store, opts, report) {
165
+ const tenantId = resolveTenantId({});
166
+ const legacy = pass.legacy ? legacyWork(store.db, tenantId, listings) : null;
167
+ const session = {
168
+ db: store.db,
169
+ hippoRoot: store.root,
170
+ tenantId,
171
+ baseHalfLifeDays: loadConfig(store.root).defaultHalfLifeDays,
172
+ originProject: pass.originProject,
173
+ isDuplicate: duplicateCheck(store, tenantId, pass.originProject, legacy?.adopted ?? new Set()),
174
+ dryRun: opts.dryRun === true,
175
+ };
176
+ // A project's rows in the global store are parted by origin: a worktree and its main checkout share a Claude folder.
177
+ const partition = store.global && pass.scope === 'project' ? pass.originProject ?? '' : null;
178
+ const synced = [];
179
+ let remembered = 0;
180
+ for (const listing of listings) {
181
+ const tool = toolOf(listing.tool);
182
+ const out = toolReport(report, tool.id);
183
+ for (const container of listing.containers) {
184
+ if (!container.readable) {
185
+ out.tally.unreadable++;
186
+ continue;
187
+ }
188
+ const work = containerWork(tool, container, opts.machine.platform, legacy, partition ?? '');
189
+ const outcome = syncOne(session, work, out, report);
190
+ if (outcome === null)
191
+ continue;
192
+ synced.push(work);
193
+ remembered += outcome.tally.imported + outcome.tally.replaced;
194
+ if (!session.dryRun)
195
+ afterCommit(store.root, outcome, report);
196
+ }
197
+ if (session.dryRun)
198
+ out.unlisted += unlistedRows(store.db, tenantId, tool, pass.scope, listing, opts.machine.platform, partition);
199
+ }
200
+ if (remembered > 0 && !session.dryRun)
201
+ bumpRemembered(store.root, remembered, report);
202
+ return synced;
203
+ }
204
+ function containerWork(tool, container, platform, legacy, origin) {
205
+ const none = new Map();
206
+ const own = tool.id === 'claude-code' ? legacy?.byContainer.get(container.path) : undefined;
207
+ return {
208
+ tool,
209
+ container,
210
+ prefix: containerPrefix(tool.id, containerId(container.path, container.scope, platform, origin)),
211
+ adopt: own?.adopt ?? none,
212
+ replace: own?.replace ?? none,
213
+ };
214
+ }
215
+ /** Null when the container was skipped: a busy store waits for the next sync, any other failure is a warning and the sync goes on. */
216
+ function syncOne(session, work, out, report) {
217
+ try {
218
+ const outcome = syncContainer(session, work);
219
+ addTally(out.tally, outcome.tally);
220
+ return outcome;
221
+ }
222
+ catch (err) {
223
+ if (isSqliteBusy(err)) {
224
+ out.tally.busy++;
225
+ report.warnings.push(`${out.label}: ${work.container.path} skipped, the store was busy`);
226
+ }
227
+ else {
228
+ report.warnings.push(`${out.label}: ${work.container.path} not synced: ${errorMessage(err)}`);
229
+ }
230
+ return null;
231
+ }
232
+ }
233
+ function afterCommit(root, outcome, report) {
234
+ for (const entry of outcome.mirror)
235
+ writeEntryMirrors(root, entry);
236
+ for (const id of outcome.purge) {
237
+ try {
238
+ removeEntryMirrors(root, id);
239
+ }
240
+ catch (err) {
241
+ // rebuildIndex would bring the row back live, and the next sync sets it aside again.
242
+ report.warnings.push(`mirror of ${id} not removed: ${errorMessage(err)}`);
243
+ }
244
+ }
245
+ }
246
+ function bumpRemembered(root, remembered, report) {
247
+ try {
248
+ updateStats(root, { remembered });
249
+ }
250
+ catch (err) {
251
+ report.warnings.push(`remembered counter not updated: ${errorMessage(err)}`);
252
+ }
253
+ }
254
+ /** Design 6: only text stored by another path counts, and in the global store only rows visible where the new row goes. */
255
+ function duplicateCheck(store, tenantId, origin, adopted) {
256
+ let keys = null;
257
+ return (text) => {
258
+ keys ??= otherPathKeys(store, tenantId, origin ?? '', adopted);
259
+ return keys.has(duplicateKey(text));
260
+ };
261
+ }
262
+ function otherPathKeys(store, tenantId, origin, adopted) {
263
+ const visible = store.global ? ` AND (origin_project = '' OR origin_project = ?)` : '';
264
+ const params = store.global ? [tenantId, AGENT_MEMORY_SOURCE_PREFIX, origin] : [tenantId, AGENT_MEMORY_SOURCE_PREFIX];
265
+ // SAFETY: the SELECT names the three columns of the row type.
266
+ const rows = store.db.prepare(`SELECT id, content, source FROM memories
267
+ WHERE tenant_id = ? AND superseded_by IS NULL AND substr(source, 1, ${AGENT_MEMORY_SOURCE_PREFIX.length}) != ?${visible}`).all(...params);
268
+ return new Set(rows.filter((r) => !adopted.has(r.id)).flatMap(heldTextKeys));
269
+ }
270
+ /** Dry run only: kept rows of this scope in containers this run did not list (a moved project's old folder); `partition` limits it to one origin. */
271
+ function unlistedRows(db, tenantId, tool, scope, listing, platform, partition) {
272
+ const listed = listing.containers.map((c) => containerPrefix(tool.id, containerId(c.path, c.scope, platform, partition ?? '')));
273
+ return selectLiveEntriesBySourcePrefix(db, tenantId, `${toolSourcePrefix(tool.id)}${scope === 'project' ? 'p' : 'u'}-`)
274
+ .filter((row) => row.tags.includes(tool.tag) && !listed.some((p) => row.source.startsWith(p)))
275
+ .filter((row) => partition === null || (row.origin_project ?? '') === partition).length;
276
+ }
277
+ /** Design 2's handover: rows the store-less hook path left in the global store, under this project's origin, for containers its store now syncs. */
278
+ function handOver(synced, projectRoot, opts, report) {
279
+ const globalRoot = resolveGlobalRootDir();
280
+ if (synced.length === 0 || !isInitialized(globalRoot))
281
+ return;
282
+ let db;
283
+ try {
284
+ db = openHippoDb(globalRoot, { busyWaitMs: opts.busyWaitMs });
285
+ const tenantId = resolveTenantId({});
286
+ // A folder with no git and no marker wrote as '' before its store existed, and as its own name after.
287
+ const origins = [...new Set([deriveOriginProject(projectRoot), ''])];
288
+ for (const work of synced)
289
+ handOverContainer(db, globalRoot, tenantId, work, origins, opts.machine.platform, report);
290
+ }
291
+ catch (err) {
292
+ report.warnings.push(`global copies not handed over: ${isSqliteBusy(err) ? 'the global store was busy' : errorMessage(err)}`);
293
+ }
294
+ finally {
295
+ if (db)
296
+ closeHippoDb(db);
297
+ }
298
+ }
299
+ function handOverContainer(db, root, tenantId, work, origins, platform, report) {
300
+ // A note the local pass could not read has no local row yet, so its global copy stays until it does.
301
+ const unread = new Set(work.container.skipped);
302
+ const mirror = [];
303
+ const purge = [];
304
+ db.exec('BEGIN IMMEDIATE');
305
+ try {
306
+ for (const origin of origins) {
307
+ const prefix = containerPrefix(work.tool.id, containerId(work.container.path, work.container.scope, platform, origin));
308
+ for (const row of selectLiveEntriesBySourcePrefix(db, tenantId, prefix)) {
309
+ if (!row.tags.includes(work.tool.tag) || unread.has(splitSource(row.source, prefix).key))
310
+ continue;
311
+ const result = setAsideRow(db, work.tool.tag, row, 'handover');
312
+ if (result.kind === 'untagged')
313
+ mirror.push(result.entry);
314
+ else
315
+ purge.push(result.id);
316
+ }
317
+ }
318
+ db.exec('COMMIT');
319
+ }
320
+ catch (err) {
321
+ try {
322
+ db.exec('ROLLBACK');
323
+ }
324
+ catch { /* already rolled back; keep the original error */ }
325
+ throw err;
326
+ }
327
+ toolReport(report, work.tool.id).tally.handedOver += mirror.length + purge.length;
328
+ afterCommit(root, { mirror, purge }, report);
329
+ }
330
+ function toolOf(id) {
331
+ const tool = AGENT_MEMORY_TOOLS.find((t) => t.id === id);
332
+ if (tool === undefined)
333
+ throw new Error(`agent memory sync: no tool ${id}`);
334
+ return tool;
335
+ }
336
+ //# sourceMappingURL=sync.js.map
@@ -0,0 +1,33 @@
1
+ export declare const AGENT_MEMORY_TOOLS: readonly [{
2
+ readonly id: "claude-code";
3
+ readonly tag: "claude-code-memory";
4
+ readonly label: "Claude Code";
5
+ }, {
6
+ readonly id: "codex";
7
+ readonly tag: "codex-memory";
8
+ readonly label: "Codex";
9
+ }, {
10
+ readonly id: "gemini";
11
+ readonly tag: "gemini-memory";
12
+ readonly label: "Gemini CLI";
13
+ }, {
14
+ readonly id: "copilot";
15
+ readonly tag: "copilot-memory";
16
+ readonly label: "Copilot";
17
+ }, {
18
+ readonly id: "openclaw";
19
+ readonly tag: "openclaw-memory";
20
+ readonly label: "OpenClaw";
21
+ }, {
22
+ readonly id: "qwen-code";
23
+ readonly tag: "qwen-code-memory";
24
+ readonly label: "Qwen Code";
25
+ }];
26
+ export type AgentMemoryTool = (typeof AGENT_MEMORY_TOOLS)[number];
27
+ export type ToolId = AgentMemoryTool['id'];
28
+ export declare const AGENT_MEMORY_SOURCE_PREFIX = "agent-memory:";
29
+ /** Every imported row of a tool has a source starting with this. */
30
+ export declare function toolSourcePrefix(id: ToolId): string;
31
+ export declare const AGENT_MEMORY_TAGS: readonly string[];
32
+ export declare function isToolId(value: string): value is ToolId;
33
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1,19 @@
1
+ // The agents whose own memories hippo imports. A leaf module: the keep rule, merge and share lists are built from it.
2
+ export const AGENT_MEMORY_TOOLS = [
3
+ { id: 'claude-code', tag: 'claude-code-memory', label: 'Claude Code' },
4
+ { id: 'codex', tag: 'codex-memory', label: 'Codex' },
5
+ { id: 'gemini', tag: 'gemini-memory', label: 'Gemini CLI' },
6
+ { id: 'copilot', tag: 'copilot-memory', label: 'Copilot' },
7
+ { id: 'openclaw', tag: 'openclaw-memory', label: 'OpenClaw' },
8
+ { id: 'qwen-code', tag: 'qwen-code-memory', label: 'Qwen Code' },
9
+ ];
10
+ export const AGENT_MEMORY_SOURCE_PREFIX = 'agent-memory:';
11
+ /** Every imported row of a tool has a source starting with this. */
12
+ export function toolSourcePrefix(id) {
13
+ return `${AGENT_MEMORY_SOURCE_PREFIX}${id}:`;
14
+ }
15
+ export const AGENT_MEMORY_TAGS = AGENT_MEMORY_TOOLS.map((t) => t.tag);
16
+ export function isToolId(value) {
17
+ return AGENT_MEMORY_TOOLS.some((t) => t.id === value);
18
+ }
19
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1,42 @@
1
+ import type { ToolId } from './tools.js';
2
+ export type Scope = 'project' | 'user';
3
+ /** All an adapter may know about the machine; it reads nothing else from the process, so tests can fake it all. */
4
+ export interface AdapterContext {
5
+ readonly home: string;
6
+ readonly env: Readonly<Record<string, string | undefined>>;
7
+ readonly platform: NodeJS.Platform;
8
+ readonly projectRoot?: string;
9
+ /** Claude Code's transcript at a hook: its folder's `memory/` holds that session's own notes. */
10
+ readonly transcriptPath?: string;
11
+ }
12
+ export interface MemoryItem {
13
+ /** A path inside the container with '/' separators, or `<heading slug>/<text hash>` in a single-file store. */
14
+ readonly key: string;
15
+ readonly text: string;
16
+ readonly updatedAt: number;
17
+ }
18
+ /** A folder, or one file's memory section, that exists on disk. */
19
+ export interface Container {
20
+ readonly scope: Scope;
21
+ readonly path: string;
22
+ /** False when it exists but could not be read or failed its shape check, so nothing in it is set aside. */
23
+ readonly readable: boolean;
24
+ readonly items: readonly MemoryItem[];
25
+ /** Keys of items on disk that were not read (too big, not text, a failed read); their rows are left alone. */
26
+ readonly skipped: readonly string[];
27
+ readonly warnings: readonly string[];
28
+ /** Keys made from text, so an edit is matched by heading rather than by key. */
29
+ readonly textKeyed: boolean;
30
+ }
31
+ export interface Listing {
32
+ readonly tool: ToolId;
33
+ readonly home: string;
34
+ readonly containers: readonly Container[];
35
+ /** Problems finding containers, such as a malformed index file; an unlisted container is left alone. */
36
+ readonly warnings: readonly string[];
37
+ }
38
+ export interface Adapter {
39
+ readonly tool: ToolId;
40
+ list(ctx: AdapterContext, scope: Scope): Listing;
41
+ }
42
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
package/dist/api.d.ts CHANGED
@@ -274,6 +274,9 @@ export interface RecallOpts {
274
274
  * callers leave this unset and get the trace.
275
275
  */
276
276
  suppressRecallTrace?: boolean;
277
+ /** Set only by the MCP recall tool, which ranks with its own scorer and drops copies from its own final list: this call
278
+ * then keeps a memory that a merged row in the same result holds word for word. Other callers leave it unset. */
279
+ keepHeldCopies?: boolean;
277
280
  }
278
281
  export interface ContinuityBlock {
279
282
  activeSnapshot: TaskSnapshot | null;
@@ -373,7 +376,7 @@ export interface RecallResult {
373
376
  * the calling agent sees its track record at the moment of forecasting
374
377
  * (Lovallo-Kahneman 2003 inside-vs-outside view).
375
378
  *
376
- * Populated by `api.recall` itself via `computePlanningFallacyHint`.
379
+ * Populated by `api.recall` itself via `computePlanningFallacyOutput`.
377
380
  * Pipeline-invariant: the value depends only on (queryText, tenantId,
378
381
  * predictions table state) — all three are identical regardless of
379
382
  * which downstream search pipeline produces the memory list, so MCP
@@ -553,6 +556,11 @@ export interface AssembleOpts {
553
556
  * is set on the result so the caller knows to widen.
554
557
  */
555
558
  rowCap?: number;
559
+ cost?: AssembleCost;
560
+ }
561
+ export interface AssembleCost {
562
+ item: (it: AssembledContextItem) => number;
563
+ fixed: (widest: number) => number;
556
564
  }
557
565
  export interface AssembledContextItem {
558
566
  id: string;
@@ -623,7 +631,7 @@ export interface DrillDownOpts {
623
631
  /**
624
632
  * Optional token budget. When set, children are appended in chronological
625
633
  * order (created ASC) until adding the next child would exceed the budget.
626
- * Token cost = ceil(content.length / 4) per child.
634
+ * Token cost = the child's printed line under `cost`, else ceil(content.length / 4).
627
635
  *
628
636
  * For depth > 1, the budget is GLOBAL cumulative (NOT per-level).
629
637
  */
@@ -636,22 +644,29 @@ export interface DrillDownOpts {
636
644
  * construction).
637
645
  */
638
646
  depth?: number;
647
+ cost?: DrillDownCost;
648
+ }
649
+ export interface DrillDownSummary {
650
+ id: string;
651
+ content: string;
652
+ descendantCount: number;
653
+ earliestAt: string | null;
654
+ latestAt: string | null;
655
+ }
656
+ export interface DrillDownChild {
657
+ id: string;
658
+ content: string;
659
+ layer: string;
660
+ dagLevel: number;
661
+ created: string;
662
+ }
663
+ export interface DrillDownCost {
664
+ child: (c: DrillDownChild) => number;
665
+ fixed: (summary: DrillDownSummary, widest: number) => number;
639
666
  }
640
667
  export interface DrillDownResult {
641
- summary: {
642
- id: string;
643
- content: string;
644
- descendantCount: number;
645
- earliestAt: string | null;
646
- latestAt: string | null;
647
- };
648
- children: Array<{
649
- id: string;
650
- content: string;
651
- layer: string;
652
- dagLevel: number;
653
- created: string;
654
- }>;
668
+ summary: DrillDownSummary;
669
+ children: DrillDownChild[];
655
670
  totalChildren: number;
656
671
  truncated: boolean;
657
672
  }
@@ -955,10 +970,28 @@ export interface ContextOpts {
955
970
  currentSessionId?: string | null;
956
971
  /** Z1: raw hook-payload prompt; only the pinned-only branch reads it, gated on `pinnedInject.promptRecall`. */
957
972
  prompt?: string;
973
+ /** What the budget pays for, from the caller that renders the block. Absent = the memory text alone. */
974
+ cost?: ContextCost;
975
+ }
976
+ /** Budget prices in the text a caller prints, so the budget bounds what reaches the model. */
977
+ export interface ContextCost {
978
+ /** Tokens of one entry as printed. */
979
+ entry: (item: Pick<ContextResultEntry, 'entry' | 'isGlobal' | 'promptRecall' | 'origin' | 'category'>) => number;
980
+ /** Tokens of the headers and footer the block can print at this budget, reserved before any entry. */
981
+ fixed: (budget: number, can: {
982
+ cross: boolean;
983
+ promptRecall: boolean;
984
+ ambient: boolean;
985
+ }) => number;
986
+ /** Tokens of the sections printed ahead of the memories, each as printed. */
987
+ snapshot: (s: TaskSnapshot) => number;
988
+ handoff: (h: SessionHandoff) => number;
989
+ trail: (events: SessionEvent[]) => number;
958
990
  }
959
991
  export interface ContextResultEntry {
960
992
  entry: MemoryEntry;
961
993
  score: number;
994
+ /** What this entry cost the budget: its printed line under `ContextOpts.cost`, else its memory text. */
962
995
  tokens: number;
963
996
  isGlobal?: boolean;
964
997
  isFreshTail?: boolean;
@@ -1005,7 +1038,7 @@ export declare function getContext(ctx: Context, opts?: ContextOpts): Promise<Co
1005
1038
  * (consolidate + dedup + audit + share + ambient) and return structured counts.
1006
1039
  *
1007
1040
  * Extracted from `cmdSleepCore` Phase 2-6 in Episode A. NOT covered by api.sleep:
1008
- * the cli-only auto-learn phase (Phase 1: learnFromRepo + learnFromMemoryMd),
1041
+ * the cli-only auto-learn phase (Phase 1: learnFromRepo + the agent memory import),
1009
1042
  * which is intrinsically host-bound (uses `process.cwd()` / `os.homedir()`).
1010
1043
  * Auto-learn stays in cli.ts cmdSleepCore as a pre-api block.
1011
1044
  *
@@ -1037,8 +1070,8 @@ export declare function recordTokens(ctx: Context, surface: TokenSurface, use: {
1037
1070
  }): void;
1038
1071
  /**
1039
1072
  * Token ledger totals for the tenant over the last `days` days (default 30):
1040
- * tokens sent per surface, blocks skipped as unchanged and the tokens that
1041
- * saved, and mean tokens per session.
1073
+ * tokens sent, skipped as unchanged and re-read by later model calls, per
1074
+ * surface, with session counts and mean tokens per session.
1042
1075
  */
1043
1076
  export declare function tokenSummary(ctx: Context, opts?: {
1044
1077
  days?: number;
@@ -1162,7 +1195,7 @@ export interface SleepResult {
1162
1195
  * api.sleep itself will need to scope dedup / audit / delete by ctx.tenantId.
1163
1196
  *
1164
1197
  * Dedup and audit deletes each log a `forget` row with the ctx actor and a
1165
- * `metadata.reason`. Pinned and raw rows are never auto-deleted (canAutoDelete).
1198
+ * `metadata.reason`. Pinned, raw and kept compaction-memory rows are never auto-deleted (canAutoDelete).
1166
1199
  * dryRun previews consolidate, dedup and audit, then returns before share/ambient.
1167
1200
  */
1168
1201
  /**