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
package/dist/cli/transfer.js
CHANGED
|
@@ -27,12 +27,31 @@ import { CliExit } from './exit.js';
|
|
|
27
27
|
const STDERR_PREVIEW_CHARS = 80;
|
|
28
28
|
const MAX_ENTRIES_SHOWN = 10;
|
|
29
29
|
// Watch command
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
const WATCH_USAGE = 'Usage: hippo watch -- <command> [args...], or hippo watch --shell "<command line>"';
|
|
31
|
+
/** The argv to run and whether a shell parses it: `--shell` takes one command line, anything else is argv as given. */
|
|
32
|
+
function watchedCommand(args, flags) {
|
|
33
|
+
if (flags['shell'] !== undefined) {
|
|
34
|
+
const line = nonEmptyStringFlag(flags, 'shell');
|
|
35
|
+
if (line === undefined) {
|
|
36
|
+
printError(WATCH_USAGE);
|
|
37
|
+
throw new CliExit(1);
|
|
38
|
+
}
|
|
39
|
+
return { argv: [line], shell: true };
|
|
40
|
+
}
|
|
41
|
+
if (args.length === 0) {
|
|
42
|
+
printError(WATCH_USAGE);
|
|
33
43
|
throw new CliExit(1);
|
|
34
44
|
}
|
|
35
|
-
|
|
45
|
+
// A quoted 1.x command line would now run as one program name, so it is refused rather than guessed at.
|
|
46
|
+
if (args.length === 1 && /\s/.test(args[0])) {
|
|
47
|
+
printError(`hippo watch: "${args[0]}" is one argument with spaces. Use \`hippo watch -- cmd args\` or \`hippo watch --shell "<command line>"\`.`);
|
|
48
|
+
throw new CliExit(1);
|
|
49
|
+
}
|
|
50
|
+
return { argv: [...args], shell: false };
|
|
51
|
+
}
|
|
52
|
+
async function cmdWatch(argv, shell, hippoRoot, tenantId) {
|
|
53
|
+
const command = argv.join(' ');
|
|
54
|
+
const { exitCode, stderr } = await runWatched(argv, undefined, { shell });
|
|
36
55
|
if (exitCode === 0) {
|
|
37
56
|
// Success: no noise
|
|
38
57
|
return;
|
|
@@ -46,7 +65,7 @@ async function cmdWatch(command, hippoRoot, tenantId) {
|
|
|
46
65
|
// A rejection-guard refusal of a failed command's output must not crash the watcher:
|
|
47
66
|
// skip with the message below and still exit with the wrapped command's real exit code.
|
|
48
67
|
try {
|
|
49
|
-
api.rememberLocally(cliApiContext(hippoRoot, tenantId), {
|
|
68
|
+
await api.rememberLocally(cliApiContext(hippoRoot, tenantId), {
|
|
50
69
|
content: failure.content,
|
|
51
70
|
tags: failure.tags,
|
|
52
71
|
layer: failure.layer,
|
|
@@ -69,7 +88,7 @@ async function cmdWatch(command, hippoRoot, tenantId) {
|
|
|
69
88
|
throw new CliExit(exitCode);
|
|
70
89
|
}
|
|
71
90
|
// Learn command
|
|
72
|
-
export function handleLearn({ hippoRoot, tenantId, flags }) {
|
|
91
|
+
export async function handleLearn({ hippoRoot, tenantId, flags }) {
|
|
73
92
|
requireInit(hippoRoot);
|
|
74
93
|
if (!flags['git']) {
|
|
75
94
|
printError('Usage: hippo learn --git [--days <n>] [--repos <paths>]');
|
|
@@ -84,14 +103,14 @@ export function handleLearn({ hippoRoot, tenantId, flags }) {
|
|
|
84
103
|
let totalSkipped = 0;
|
|
85
104
|
for (const repo of repos) {
|
|
86
105
|
const label = path.basename(repo);
|
|
87
|
-
const { added, skipped } = learnFromRepo(hippoRoot, tenantId, repo, days, label);
|
|
106
|
+
const { added, skipped } = await learnFromRepo(hippoRoot, tenantId, repo, days, label);
|
|
88
107
|
totalAdded += added;
|
|
89
108
|
totalSkipped += skipped;
|
|
90
109
|
}
|
|
91
110
|
console.log(`Git learn complete: ${totalAdded} new lessons added, ${totalSkipped} duplicates skipped across ${repos.length} repos.`);
|
|
92
111
|
}
|
|
93
112
|
else {
|
|
94
|
-
const { added, skipped } = learnFromRepo(hippoRoot, tenantId, process.cwd(), days);
|
|
113
|
+
const { added, skipped } = await learnFromRepo(hippoRoot, tenantId, process.cwd(), days);
|
|
95
114
|
console.log(`Git learn complete: ${added} new lessons added, ${skipped} duplicates skipped.`);
|
|
96
115
|
}
|
|
97
116
|
}
|
|
@@ -118,7 +137,7 @@ function warnRedacted(count) {
|
|
|
118
137
|
if (count)
|
|
119
138
|
printError(`Warning: secret-shaped text was redacted from ${count} imported ${count === 1 ? 'entry' : 'entries'} before storing`);
|
|
120
139
|
}
|
|
121
|
-
export function handleImport({ hippoRoot, tenantId, args, flags }) {
|
|
140
|
+
export async function handleImport({ hippoRoot, tenantId, args, flags }) {
|
|
122
141
|
const useGlobal = boolFlag(flags, 'global');
|
|
123
142
|
const dryRun = boolFlag(flags, 'dry-run');
|
|
124
143
|
const extraTags = Array.isArray(flags['tag'])
|
|
@@ -144,7 +163,7 @@ export function handleImport({ hippoRoot, tenantId, args, flags }) {
|
|
|
144
163
|
// Vault import is a FOLDER importer that dispatches apart from the single-file slot below; it writes through tenant-scoped api calls,
|
|
145
164
|
// so the tenant is resolved and passed through. --global is unsupported (the connector raw-archive path is tenant-local).
|
|
146
165
|
if (flags['vault'])
|
|
147
|
-
return importVaultFolder(hippoRoot, tenantId, flags, importOptions, useGlobal, dryRun);
|
|
166
|
+
return await importVaultFolder(hippoRoot, tenantId, flags, importOptions, useGlobal, dryRun);
|
|
148
167
|
importFromFile(targetRoot, args, flags, { importOptions, useGlobal, dryRun, tenantId });
|
|
149
168
|
}
|
|
150
169
|
// Determine which importer to use based on flag
|
|
@@ -219,7 +238,7 @@ function importAgentMemories(hippoRoot, useGlobal, dryRun) {
|
|
|
219
238
|
for (const warning of report.warnings)
|
|
220
239
|
printError(`hippo: agent memories: ${warning}`);
|
|
221
240
|
}
|
|
222
|
-
function importVaultFolder(hippoRoot, tenantId, flags, importOptions, useGlobal, dryRun) {
|
|
241
|
+
async function importVaultFolder(hippoRoot, tenantId, flags, importOptions, useGlobal, dryRun) {
|
|
223
242
|
const folderPath = String(flags['vault']);
|
|
224
243
|
checkVaultArgs(folderPath, flags, useGlobal);
|
|
225
244
|
const vaultOptions = {
|
|
@@ -228,7 +247,7 @@ function importVaultFolder(hippoRoot, tenantId, flags, importOptions, useGlobal,
|
|
|
228
247
|
name: flags['name'] ? String(flags['name']) : undefined,
|
|
229
248
|
scope: flags['scope'] ? String(flags['scope']) : undefined,
|
|
230
249
|
};
|
|
231
|
-
const vaultResult = importVault(folderPath, vaultOptions);
|
|
250
|
+
const vaultResult = await importVault(folderPath, vaultOptions);
|
|
232
251
|
printVaultSummary(vaultResult, folderPath, cliApiContext(hippoRoot, tenantId), dryRun);
|
|
233
252
|
}
|
|
234
253
|
function checkVaultArgs(folderPath, flags, useGlobal) {
|
|
@@ -300,9 +319,9 @@ export function handleSync({ hippoRoot, flags }) {
|
|
|
300
319
|
console.log(`Synced ${count} global memories into local project.${includeCrossProject ? '' : ' (other-project rows skipped; use --cross-project to ' +
|
|
301
320
|
'include them)'}`);
|
|
302
321
|
}
|
|
303
|
-
export async function handleWatch({ hippoRoot, tenantId, args }) {
|
|
304
|
-
const
|
|
305
|
-
await cmdWatch(
|
|
322
|
+
export async function handleWatch({ hippoRoot, tenantId, args, flags }) {
|
|
323
|
+
const { argv, shell } = watchedCommand(args, flags);
|
|
324
|
+
await cmdWatch(argv, shell, hippoRoot, tenantId);
|
|
306
325
|
}
|
|
307
326
|
export async function handlePromote({ hippoRoot, tenantId, args }) {
|
|
308
327
|
const id = args[0];
|
package/dist/cli/usage.d.ts
CHANGED
|
@@ -2,5 +2,5 @@ export declare function printAuditPruneUsage(): void;
|
|
|
2
2
|
export declare function printSlackBackfillUsage(): void;
|
|
3
3
|
export declare function printSlackWorkspacesUsage(): void;
|
|
4
4
|
export declare const USAGE_HEADER = "\nHippo - Make your agent's memory work like a brain. Hippo is long-term memory for coding agents.\n\nUsage: hippo <command> [options]\n\nCommands:";
|
|
5
|
-
export declare const USAGE_EXAMPLES = "\n\nExamples:\n hippo init\n hippo remember \"FRED cache can silently drop series\" --tag error\n hippo recall \"data pipeline issues\" --budget 2000\n hippo context --auto --budget 1500\n hippo conflicts\n hippo reject mem_abc123 --reason \"leaked credential\"\n hippo reject --value \"never store my key again\" --reason \"secret\"\n hippo rejections\n hippo unreject a1b2c3d4e5f6\n hippo dormant \"staging hostname\"\n hippo dormant restore mem_abc123\n hippo tokens --days 7\n hippo session log --id sess_123 --task \"Ship feature\" --type progress --content \"Build is green, next step is docs\"\n hippo session latest --json\n hippo session resume\n hippo snapshot save --task \"Ship feature\" --summary \"Tests are green\" --next-step \"Open the PR\" --session sess_123\n hippo handoff create --summary \"PR is open, tests green\" --next \"Merge after review\" --session sess_123 --artifact src/foo.ts\n hippo handoff create --summary s --constraint a --constraint b --outcome partial --target-runtime codex --card-id c1 --tests pass\n hippo card create --title \"Add cards table\" --repo hippo --depends-on card_abc\n hippo card claim card_abc --runtime codex\n hippo card complete card_abc --outcome success\n hippo card heartbeat card_abc --run 7\n hippo card reclaim\n hippo embed --status\n hippo watch
|
|
5
|
+
export declare const USAGE_EXAMPLES = "\n\nExamples:\n hippo init\n hippo remember \"FRED cache can silently drop series\" --tag error\n hippo recall \"data pipeline issues\" --budget 2000\n hippo context --auto --budget 1500\n hippo conflicts\n hippo reject mem_abc123 --reason \"leaked credential\"\n hippo reject --value \"never store my key again\" --reason \"secret\"\n hippo rejections\n hippo unreject a1b2c3d4e5f6\n hippo dormant \"staging hostname\"\n hippo dormant restore mem_abc123\n hippo tokens --days 7\n hippo session log --id sess_123 --task \"Ship feature\" --type progress --content \"Build is green, next step is docs\"\n hippo session latest --json\n hippo session resume\n hippo snapshot save --task \"Ship feature\" --summary \"Tests are green\" --next-step \"Open the PR\" --session sess_123\n hippo handoff create --summary \"PR is open, tests green\" --next \"Merge after review\" --session sess_123 --artifact src/foo.ts\n hippo handoff create --summary s --constraint a --constraint b --outcome partial --target-runtime codex --card-id c1 --tests pass\n hippo card create --title \"Add cards table\" --repo hippo --depends-on card_abc\n hippo card claim card_abc --runtime codex\n hippo card complete card_abc --outcome success\n hippo card heartbeat card_abc --run 7\n hippo card reclaim\n hippo embed --status\n hippo watch -- npm run build\n hippo learn --git --days 30\n hippo promote mem_abc123\n hippo sync\n hippo setup\n hippo hook install claude-code\n hippo decide \"Use PostgreSQL for new services\" --context \"JSONB support\"\n hippo incident \"Prod outage: DB connection pool exhausted\" --context \"spike at 14:00\"\n hippo process new \"Release\" --step \"run tests\" --step \"bump version\" --step \"publish\"\n hippo policy new \"Data retention\" --text \"Delete logs after 90 days\" --from 2026-01-01\n hippo policy asof 2026-03-01 --name \"Data retention\"\n hippo skill new \"Run tests\" --instructions \"npm test before every commit\" --trigger \"before commit\"\n hippo skill export\n hippo brief new \"hippo\" --summary \"Agent-memory library; E2 first-class objects in progress\"\n hippo brief refresh \"hippo\"\n hippo note new \"Acme Corp\" --text \"Renewal call: wants SSO before Q3; champion is the VP Eng\"\n hippo note list --customer \"Acme Corp\" --status active\n hippo graph extract\n hippo invalidate \"REST API\" --dry-run\n hippo invalidate \"REST API\" --reason \"migrated to GraphQL\"\n hippo invalidate --id mem_a1b2c3d4e5f6 --reason \"superseded by new policy\"\n hippo invalidate --churn --dry-run\n hippo export memories.json\n hippo export --format markdown memories.md\n hippo sleep --dry-run\n hippo outcome --good\n hippo status\n";
|
|
6
6
|
//# sourceMappingURL=usage.d.ts.map
|
package/dist/cli/usage.js
CHANGED
package/dist/cli/verb-row.d.ts
CHANGED
|
@@ -18,6 +18,8 @@ export interface VerbRow {
|
|
|
18
18
|
readonly aliases?: readonly string[];
|
|
19
19
|
/** Runs in a request scope, opening each store once; set on api-backed verbs, as hook verbs open their own. */
|
|
20
20
|
readonly scoped?: true;
|
|
21
|
+
/** Routine verbs after which a previously installed codex wrapper is repaired (never first-installed). */
|
|
22
|
+
readonly repairsCodexWrapper?: true;
|
|
21
23
|
/** Deletes or hides memories: an unknown flag stops the run instead of being ignored. */
|
|
22
24
|
readonly destructive?: true;
|
|
23
25
|
/** Unset: --dry-run is honoured exactly when the verb declares it; `false` refuses it even so. */
|
package/dist/cli/verbs/health.js
CHANGED
|
@@ -4,6 +4,7 @@ import { DEFAULT_ASSEMBLE_BUDGET } from '../../api/assemble.js';
|
|
|
4
4
|
import { verb } from '../verb-row.js';
|
|
5
5
|
export const HEALTH_VERBS = {
|
|
6
6
|
status: verb(() => import('../status.js'), 'handleStatus', {
|
|
7
|
+
repairsCodexWrapper: true,
|
|
7
8
|
flags: {},
|
|
8
9
|
usage: [`
|
|
9
10
|
status Show memory health stats`],
|
|
@@ -98,6 +99,7 @@ export const HEALTH_VERBS = {
|
|
|
98
99
|
--json Output as JSON`],
|
|
99
100
|
}),
|
|
100
101
|
outcome: verb(() => import('../curate.js'), 'handleOutcome', {
|
|
102
|
+
repairsCodexWrapper: true,
|
|
101
103
|
scoped: true,
|
|
102
104
|
flags: { switches: ['bad', 'good'], values: ['id'] },
|
|
103
105
|
usage: [`
|
package/dist/cli/verbs/memory.js
CHANGED
|
@@ -5,6 +5,7 @@ import { DEFAULT_GRAPH_HOPS, DEFAULT_GRAPH_SEED_COUNT } from '../../graph/stream
|
|
|
5
5
|
import { verb } from '../verb-row.js';
|
|
6
6
|
export const MEMORY_VERBS = {
|
|
7
7
|
init: verb(() => import('../init.js'), 'handleInit', {
|
|
8
|
+
repairsCodexWrapper: true,
|
|
8
9
|
flags: { switches: ['global', 'no-hooks', 'no-learn', 'no-schedule'], values: ['scan'], numbers: ['days'] },
|
|
9
10
|
usage: [`
|
|
10
11
|
init Create .hippo/ structure in current directory
|
|
@@ -18,6 +19,7 @@ export const MEMORY_VERBS = {
|
|
|
18
19
|
coding agents' own memories (every init imports those)`],
|
|
19
20
|
}),
|
|
20
21
|
remember: verb(() => import('../remember.js'), 'handleRemember', {
|
|
22
|
+
repairsCodexWrapper: true,
|
|
21
23
|
flags: {
|
|
22
24
|
switches: ['error', 'extract', 'force', 'global', 'inferred', 'observed', 'pin', 'verified'],
|
|
23
25
|
values: ['artifact-ref', 'kind', 'layer', 'owner', 'scope'],
|
|
@@ -43,6 +45,7 @@ export const MEMORY_VERBS = {
|
|
|
43
45
|
--pin Pin the new memory (default: pinned if the old one was)`],
|
|
44
46
|
}),
|
|
45
47
|
recall: verb(() => import('../recall.js'), 'handleRecall', {
|
|
48
|
+
repairsCodexWrapper: true,
|
|
46
49
|
scoped: true,
|
|
47
50
|
flags: {
|
|
48
51
|
switches: ['classic', 'continuity', 'equal-sources', 'evc-adaptive', 'filter-conflicts', 'graph-stream',
|
|
@@ -61,47 +64,40 @@ export const MEMORY_VERBS = {
|
|
|
61
64
|
--as-of <iso-date> Return the memories that were current on that date
|
|
62
65
|
--multihop Search a second time with the speaker: and topic: tags the
|
|
63
66
|
first pass found (also on when config multihop.enabled is true)
|
|
64
|
-
--hops <n>
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
light up the same traversal once extracted.
|
|
67
|
+
--hops <n> Also surface memories reached by walking the entity and
|
|
68
|
+
relation graph <n> hops (0..3, default off) out from the
|
|
69
|
+
lexical matches. Graph hits are tagged [graph: Nhop <rel>].
|
|
70
|
+
Today the graph holds supersedes edges; cross-object
|
|
71
|
+
edges light up the same walk once extracted.
|
|
70
72
|
--max-neighbors <n> Per-hop fanout cap for --hops (1..200, default 25).
|
|
71
|
-
--graph-stream
|
|
72
|
-
in-pool results by
|
|
73
|
-
|
|
74
|
-
only.
|
|
75
|
-
|
|
73
|
+
--graph-stream Fuse a graph-proximity ranking into the result list,
|
|
74
|
+
re-ranking in-pool results by closeness to the strong
|
|
75
|
+
lexical matches. Implies rrf scoring (default is blend).
|
|
76
|
+
Local store only. Unlike --hops, it adds no memories
|
|
77
|
+
from outside the candidate pool.
|
|
76
78
|
--graph-hops <n> Hops for --graph-stream (1..3, default ${DEFAULT_GRAPH_HOPS}).
|
|
77
79
|
--graph-seeds <n> Lexical anchors for --graph-stream (default ${DEFAULT_GRAPH_SEED_COUNT}). The stream
|
|
78
|
-
re-ranks the
|
|
79
|
-
every candidate is
|
|
80
|
+
re-ranks the results below the anchors; on a pool with <= n
|
|
81
|
+
candidates every candidate is an anchor and the stream is inert.
|
|
80
82
|
--no-mmr Disable MMR diversity re-ranking
|
|
81
83
|
--mmr-lambda <f> MMR balance 0..1 (default: ${DEFAULT_MMR_LAMBDA}, 1.0 = pure relevance)
|
|
82
|
-
--evc-adaptive
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
--filter-conflicts
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
--rerank-utility OFC option-value re-ranker: combine relevance,
|
|
97
|
-
strength, and integration cost into a single utility
|
|
98
|
-
= score * (0.5 + 0.5 * strength) * (1 - cost_factor)
|
|
99
|
-
where cost_factor = min(0.3, tokens / 10000). Re-sorts
|
|
100
|
-
results by utility. Default off. RESEARCH.md §PFC.OFC.
|
|
84
|
+
--evc-adaptive When the top results overlap heavily (a conflict
|
|
85
|
+
cluster), widen the pool and re-rank by recency.
|
|
86
|
+
Default off.
|
|
87
|
+
--filter-conflicts Drop superseded entries and down-weight by 0.3x any
|
|
88
|
+
entry in an open conflict with another result.
|
|
89
|
+
Uses recorded supersession and conflicts only, never
|
|
90
|
+
lexical inference. Default off.
|
|
91
|
+
--value-aware Boost memories with positive cumulative outcomes and
|
|
92
|
+
demote those with negative ones during ranking:
|
|
93
|
+
multiplier clip(1 + 0.3*tanh(pos - neg), 0.7, 1.3).
|
|
94
|
+
Reads outcome_positive and outcome_negative. Default off.
|
|
95
|
+
--rerank-utility Re-sort results by utility = score * (0.5 + 0.5 * strength)
|
|
96
|
+
* (1 - cost_factor), where cost_factor =
|
|
97
|
+
min(0.3, tokens / 10000). Default off.
|
|
101
98
|
--reranker <name> Apply a reranker pass after retrieval
|
|
102
|
-
(cross-encoder|jev|clef-flash|clef|llm)
|
|
103
|
-
|
|
104
|
-
re-orders the top-K candidates. Default unset (no reranker).
|
|
99
|
+
(cross-encoder|jev|clef-flash|clef|llm) to re-order the
|
|
100
|
+
top-K candidates. Default unset (no reranker).
|
|
105
101
|
jev calls the hosted TypeSafe Jev API: it needs
|
|
106
102
|
TYPESAFE_API_KEY, sends the query and candidate
|
|
107
103
|
text to that API, costs about 0.0004 USD a recall,
|
|
@@ -110,30 +106,24 @@ export const MEMORY_VERBS = {
|
|
|
110
106
|
Cloudflare Workers AI (CLOUDFLARE_ACCOUNT_ID and
|
|
111
107
|
CLOUDFLARE_API_TOKEN) or to HIPPO_CLEF_ENDPOINT, and
|
|
112
108
|
keep the native order on any failure.
|
|
113
|
-
See docs/evals/2026-09-19-jev-reranker.md and
|
|
114
|
-
docs/plans/2026-05-10-f6-reranker-hardening.md.
|
|
115
109
|
--reranker-top-k <n> Cap candidates passed to the reranker (default 50;
|
|
116
110
|
40 for jev, clef-flash and clef).
|
|
117
|
-
--goal <tag>
|
|
118
|
-
|
|
119
|
-
re-sorted. Default off.
|
|
120
|
-
--session-id <id> Session identifier for
|
|
111
|
+
--goal <tag> Goal-conditioned recall: memories tagged with the
|
|
112
|
+
goal tag get a 1.5x score boost and results are
|
|
113
|
+
re-sorted. Default off.
|
|
114
|
+
--session-id <id> Session identifier for the goal-stack boost.
|
|
121
115
|
Defaults to $HIPPO_SESSION_ID. When set and the
|
|
122
116
|
(tenant, session) has active goals (see
|
|
123
117
|
'hippo goal push'), recall auto-boosts memories
|
|
124
118
|
whose tags match an active goal name. Boost stacks
|
|
125
119
|
on top of base BM25 score, capped at 3.0x.
|
|
126
120
|
--salience-threshold <n>
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
lexical gate destroyed LoCoMo 0.28 -> 0.02; this v2
|
|
134
|
-
is retrieval-side, opt-in only — see MEMORY.md
|
|
135
|
-
"Hippo salience gate destroys benchmark recall".)
|
|
136
|
-
--continuity Include continuity block (active task snapshot,
|
|
121
|
+
Down-weight memories retrieved fewer than n times:
|
|
122
|
+
score *= max(0.5, retrieval_count / n) for entries
|
|
123
|
+
with count < n; entries at or above n are unchanged.
|
|
124
|
+
Salience comes from use, not from lexical overlap.
|
|
125
|
+
Default off.
|
|
126
|
+
--continuity Include a continuity block (active task snapshot,
|
|
137
127
|
latest matching session handoff, last 5 session
|
|
138
128
|
events) above the memory list. Useful at agent
|
|
139
129
|
boot when you want both relevant memories AND
|
|
@@ -198,6 +188,7 @@ export const MEMORY_VERBS = {
|
|
|
198
188
|
--json Output full summary as JSON`],
|
|
199
189
|
}),
|
|
200
190
|
context: verb(() => import('../context.js'), 'handleContext', {
|
|
191
|
+
repairsCodexWrapper: true,
|
|
201
192
|
flags: {
|
|
202
193
|
switches: ['auto', 'cross-project', 'pinned-only'],
|
|
203
194
|
values: ['budget', 'format', 'framing', 'include-recent', 'runtime', 'scope'],
|
|
@@ -215,6 +206,7 @@ export const MEMORY_VERBS = {
|
|
|
215
206
|
--framing <mode> Framing: observe (default), suggest, assert`],
|
|
216
207
|
}),
|
|
217
208
|
sleep: verb(() => import('../sleep.js'), 'handleSleep', {
|
|
209
|
+
repairsCodexWrapper: true,
|
|
218
210
|
destructive: true,
|
|
219
211
|
scoped: true,
|
|
220
212
|
flags: { switches: ['dry-run', 'no-learn', 'no-share'], values: ['log-file'] },
|
|
@@ -188,16 +188,19 @@ export const OBJECT_VERBS = {
|
|
|
188
188
|
mcp Start MCP server (stdio transport)`],
|
|
189
189
|
}),
|
|
190
190
|
serve: verb(() => import('../serve.js'), 'handleServe', {
|
|
191
|
-
flags: { values: ['host', 'tls-cert', 'tls-key'], numbers: ['port'] },
|
|
191
|
+
flags: { switches: ['allow-cleartext'], values: ['host', 'tls-cert', 'tls-key'], numbers: ['port'] },
|
|
192
192
|
usage: [`
|
|
193
193
|
serve Start the HTTP API server for this store (Ctrl+C stops it)
|
|
194
194
|
--port <n> Port to serve on (default: $HIPPO_PORT or ${DEFAULT_SERVER_PORT})
|
|
195
195
|
--host <host> Address to bind (default: ${DEFAULT_SERVER_HOST})
|
|
196
196
|
--tls-cert <file> PEM certificate; serve HTTPS only (or $HIPPO_TLS_CERT)
|
|
197
197
|
--tls-key <file> PEM private key for it; give both (or $HIPPO_TLS_KEY)
|
|
198
|
+
--allow-cleartext Bind a non-loopback host without TLS, for a TLS proxy
|
|
199
|
+
in front (or $HIPPO_ALLOW_CLEARTEXT=1)
|
|
198
200
|
Every request needs an API key (hippo auth create).
|
|
199
201
|
$HIPPO_ALLOW_KEYLESS_LOCAL=1 lets requests from this machine
|
|
200
|
-
in without one
|
|
202
|
+
in without one, as a member ($HIPPO_KEYLESS_LOCAL_ROLE=admin
|
|
203
|
+
for host admin); $HIPPO_REQUIRE_AUTH=1 wins over it.`],
|
|
201
204
|
}),
|
|
202
205
|
goal: verb(() => import('../goals.js'), 'handleGoal', {
|
|
203
206
|
scoped: true,
|
|
@@ -206,7 +209,7 @@ export const OBJECT_VERBS = {
|
|
|
206
209
|
values: ['level', 'outcome', 'parent', 'policy', 'session-id', 'success', 'tenant-id'],
|
|
207
210
|
},
|
|
208
211
|
usage: [`
|
|
209
|
-
goal <sub>
|
|
212
|
+
goal <sub> Goal stack, scoped per session
|
|
210
213
|
goal push <name> Push a new active goal; prints the new goal id
|
|
211
214
|
--policy <type> schema-fit-biased | error-prioritized |
|
|
212
215
|
recency-first | hybrid
|
|
@@ -132,9 +132,10 @@ export const SESSION_VERBS = {
|
|
|
132
132
|
--status Show embedding coverage`],
|
|
133
133
|
}),
|
|
134
134
|
watch: verb(() => import('../transfer.js'), 'handleWatch', {
|
|
135
|
-
flags: {},
|
|
135
|
+
flags: { values: ['shell'] },
|
|
136
136
|
usage: [`
|
|
137
|
-
watch
|
|
137
|
+
watch -- <cmd> [args] Run command, auto-learn from failures (no shell)
|
|
138
|
+
--shell "<line>" Run one command line through the shell instead`],
|
|
138
139
|
}),
|
|
139
140
|
learn: verb(() => import('../transfer.js'), 'handleLearn', {
|
|
140
141
|
scoped: true,
|
|
@@ -203,6 +204,7 @@ export const SESSION_VERBS = {
|
|
|
203
204
|
--format <fmt> Output format: json (default) or markdown`],
|
|
204
205
|
}),
|
|
205
206
|
capture: verb(() => import('../session-hooks.js'), 'handleCapture', {
|
|
207
|
+
repairsCodexWrapper: true,
|
|
206
208
|
flags: { switches: ['dry-run', 'global', 'last-session', 'stdin'], values: ['file', 'log-file', 'transcript'] },
|
|
207
209
|
usage: [`
|
|
208
210
|
capture Extract memories from conversation text
|
package/dist/cli.js
CHANGED
|
@@ -107,9 +107,8 @@ export function parseArgs(argv) {
|
|
|
107
107
|
export function shouldAutoRepairCodexWrapper(currentCommand, flags) {
|
|
108
108
|
if (envSkipAutoIntegrations())
|
|
109
109
|
return false;
|
|
110
|
-
if (!
|
|
110
|
+
if (!COMMAND_INDEX.get(currentCommand)?.repairsCodexWrapper)
|
|
111
111
|
return false;
|
|
112
|
-
}
|
|
113
112
|
if (currentCommand === 'init' && flagIsTrue(flags, 'no-hooks'))
|
|
114
113
|
return false;
|
|
115
114
|
return true;
|
package/dist/connectors/dlq.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// The dead-letter queue every webhook connector parks into: one redaction, one unroutable sentinel, one set of defaults, one replay result.
|
|
2
2
|
import { DEFAULT_LIST_LIMIT } from '../util/limits.js';
|
|
3
3
|
import { DLQ_REDACTED_NOTE, redactPayload } from '../util/secret-detect.js';
|
|
4
|
-
import { requireGroup
|
|
4
|
+
import { requireGroup } from '../store/index.js';
|
|
5
|
+
import { storeFor } from '../store/sqlite/store.js';
|
|
5
6
|
import { errorMessage } from '../util/log.js';
|
|
6
7
|
/** The tenant stored on a row no tenant was resolved for; the column is NOT NULL. */
|
|
7
8
|
const UNROUTABLE_TENANT = '__unroutable__';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { requireGroup
|
|
1
|
+
import { requireGroup } from '../../store/index.js';
|
|
2
|
+
import { storeFor } from '../../store/sqlite/store.js';
|
|
2
3
|
/** Handle `issue_comment.deleted` and `pull_request_review_comment.deleted`: archive ALL active raw rows for the artifact_ref in one write.
|
|
3
4
|
* Tenant and kind='raw' filtering are load-bearing: without them a deletion could archive another tenant's row or a distilled row. */
|
|
4
5
|
export async function handleCommentDeleted(ctx, input) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
// GitHub event ingest, once per idempotency key. The key comes from the event's own content, never from the unsigned
|
|
2
2
|
// X-GitHub-Delivery header, so a replay cannot get past it by rotating the delivery id. The pre-check is only the fast path:
|
|
3
3
|
// the store logs the key in the memory's own transaction, which is what holds when two workers race.
|
|
4
|
-
import { requireGroup
|
|
4
|
+
import { requireGroup } from '../../store/index.js';
|
|
5
|
+
import { storeFor } from '../../store/sqlite/store.js';
|
|
5
6
|
import { rememberWithEventLog } from '../ingest.js';
|
|
6
7
|
import { computeIdempotencyKey } from './signature.js';
|
|
7
8
|
import { issueEventToRememberOpts, issueCommentEventToRememberOpts, pullRequestEventToRememberOpts, prReviewCommentEventToRememberOpts, } from './transform.js';
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { envGitHubAllowUnknownInstallationFallback, envTenant } from '../../util/env.js';
|
|
2
|
-
import { requireGroup
|
|
2
|
+
import { requireGroup } from '../../store/index.js';
|
|
3
|
+
import { storeFor } from '../../store/sqlite/store.js';
|
|
3
4
|
/** Resolve a GitHub envelope's tenant_id by installation id, then repository.full_name; the HIPPO_TENANT fallback applies only if both tables are empty.
|
|
4
5
|
* An unknown installation or repo on a multi-tenant deployment returns null (fail closed); GITHUB_ALLOW_UNKNOWN_INSTALLATION_FALLBACK=1 escapes. */
|
|
5
6
|
export async function resolveTenantForGitHub(hippoRoot, args, store) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { envGitHubWebhookSecret, envGitHubWebhookSecretPrevious } from '../../util/env.js';
|
|
2
|
+
import { DAY_MS } from '../../util/time.js';
|
|
2
3
|
import { verifyGitHubSignature } from './signature.js';
|
|
3
4
|
import { isGitHubWebhookEnvelope, isGitHubIssueEvent, isGitHubIssueCommentEvent, isGitHubPullRequestEvent, isGitHubPullRequestReviewCommentEvent, } from './types.js';
|
|
4
5
|
import { ingestEvent as ingestGitHubEvent } from './ingest.js';
|
|
@@ -58,9 +59,10 @@ export async function handleGitHubEventsWebhook(request, store) {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
// GitHub signs no timestamp, so a captured delivery verifies forever; this window is what refuses its replay.
|
|
61
|
-
const DELIVERY_REPLAY_TTL_MS =
|
|
62
|
+
const DELIVERY_REPLAY_TTL_MS = DAY_MS;
|
|
62
63
|
const DELIVERY_REPLAY_MAX_IDS = 10_000;
|
|
63
|
-
//
|
|
64
|
+
// A fast path only: the durable dedupe is the connector event log's content-derived idempotency key (ingest.ts, and the deletion key below),
|
|
65
|
+
// so a replay after a restart or an eviction is a stored 'duplicate' with no new rows.
|
|
64
66
|
const seenDeliveries = new Map();
|
|
65
67
|
/** Claims a delivery id for the window; false when it was claimed within the window already. The oldest claim goes first at the cap. */
|
|
66
68
|
function claimDelivery(key, now) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
// The write half every connector's ingest shares, after its own pre-check of the event log.
|
|
2
2
|
import { remember } from '../api/index.js';
|
|
3
|
-
import { requireGroup
|
|
3
|
+
import { requireGroup } from '../store/index.js';
|
|
4
|
+
import { storeFor } from '../store/sqlite/store.js';
|
|
4
5
|
import { RejectedValueError } from '../core/api-errors.js';
|
|
5
6
|
/** Stores `opts` once per event key; null `opts` (an empty body) and a tombstoned value are logged with no memory, so a redelivery acks instead of retrying. */
|
|
6
7
|
export async function rememberWithEventLog(ctx, event, opts) {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { archiveRaw } from '../../api/index.js';
|
|
2
|
-
import { requireGroup
|
|
2
|
+
import { requireGroup } from '../../store/index.js';
|
|
3
|
+
import { storeFor } from '../../store/sqlite/store.js';
|
|
3
4
|
/** Handles Slack `message_deleted`. The store logs the event in the archive's own transaction, so a crash cannot leave a retry
|
|
4
5
|
* answering `not_found` where `duplicate` is due. */
|
|
5
6
|
export async function handleMessageDeleted(ctx, input) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { requireGroup
|
|
1
|
+
import { requireGroup } from '../../store/index.js';
|
|
2
|
+
import { storeFor } from '../../store/sqlite/store.js';
|
|
2
3
|
import { rememberWithEventLog } from '../ingest.js';
|
|
3
4
|
import { messageToRememberOpts } from './transform.js';
|
|
4
5
|
/** Ingests a Slack message as a kind='raw' memory once per event id (Slack redelivers within a minute); the pre-check is only the fast path,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { envSlackAllowUnknownTeamFallback, envTenant } from '../../util/env.js';
|
|
2
|
-
import { requireGroup
|
|
2
|
+
import { requireGroup } from '../../store/index.js';
|
|
3
|
+
import { storeFor } from '../../store/sqlite/store.js';
|
|
3
4
|
/** Look up the tenant_id for a Slack team_id; the HIPPO_TENANT fallback applies only when slack_workspaces is empty (single-workspace install).
|
|
4
5
|
* An unknown team on a multi-workspace install returns null (fail closed); SLACK_ALLOW_UNKNOWN_TEAM_FALLBACK=1 is the emergency escape. */
|
|
5
6
|
export async function resolveTenantForSlackTeam(hippoRoot, teamId, store) {
|
|
@@ -65,7 +65,7 @@ function recalledTogether(a, b) {
|
|
|
65
65
|
// A personal row pairs only inside its own scope; any other pair shows its id to someone else.
|
|
66
66
|
if ((isPersonalScope(a.scope) || isPersonalScope(b.scope)) && a.scope !== b.scope)
|
|
67
67
|
return false;
|
|
68
|
-
if (a.
|
|
68
|
+
if (a.tenant_id !== b.tenant_id)
|
|
69
69
|
return false;
|
|
70
70
|
const [x, y] = [a.origin_project ?? null, b.origin_project ?? null];
|
|
71
71
|
return x === y || (x === '' && y !== null) || (y === '' && x !== null);
|
package/dist/consolidate/dag.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { createMemory, Layer } from '../core/memory.js';
|
|
2
2
|
import { writeEntry, writeEntriesTogether } from '../store/entry-writes.js';
|
|
3
|
-
import { loadAllDirtySummaries, loadChildrenOfSummary, applyRebuildResult, clearSummaryDirtyAfterBuild, } from '../store/summaries.js';
|
|
3
|
+
import { loadAllDirtySummaries, loadChildrenOfSummary, loadChildrenOfSummaries, childrenKey, applyRebuildResult, clearSummaryDirtyAfterBuild, } from '../store/summaries.js';
|
|
4
4
|
import { RejectedValueError } from '../core/api-errors.js';
|
|
5
5
|
import { generateDagSummary } from './dag-summary.js';
|
|
6
6
|
import { overlapPartners } from './overlap-index.js';
|
|
@@ -50,7 +50,7 @@ export function clusterFacts(facts) {
|
|
|
50
50
|
function partitionFactsByTenant(unparented) {
|
|
51
51
|
const unparentedByTenant = new Map();
|
|
52
52
|
for (const fact of unparented) {
|
|
53
|
-
const key = derivationPartitionKey(fact.
|
|
53
|
+
const key = derivationPartitionKey(fact.tenant_id, fact.scope, fact.origin_project);
|
|
54
54
|
const bucket = unparentedByTenant.get(key);
|
|
55
55
|
if (bucket)
|
|
56
56
|
bucket.push(fact);
|
|
@@ -103,7 +103,7 @@ async function summarizeCluster(hippoRoot, cluster, home, opts, result) {
|
|
|
103
103
|
result.factsLinked += writeEntriesTogether(hippoRoot, cluster.members.map((member) => ({ ...member, dag_parent_id: summaryEntry.id })));
|
|
104
104
|
// Member writes just marked this fresh summary dirty; clear it, or the same sleep cycle's
|
|
105
105
|
// rebuild pass would re-rebuild every new summary at twice the LLM cost.
|
|
106
|
-
clearSummaryDirtyAfterBuild(hippoRoot, summaryEntry.id, summaryEntry.
|
|
106
|
+
clearSummaryDirtyAfterBuild(hippoRoot, summaryEntry.id, summaryEntry.tenant_id, 'buildDag');
|
|
107
107
|
}
|
|
108
108
|
export async function buildDag(hippoRoot, facts, opts) {
|
|
109
109
|
const result = { candidateClusters: 0, summariesCreated: 0, factsLinked: 0, rejected: 0 };
|
|
@@ -111,7 +111,7 @@ export async function buildDag(hippoRoot, facts, opts) {
|
|
|
111
111
|
const unparented = facts.filter((f) => f.dag_level === 1 && !f.dag_parent_id && f.tags.includes('extracted'));
|
|
112
112
|
for (const [, tenantFacts] of partitionFactsByTenant(unparented)) {
|
|
113
113
|
const home = {
|
|
114
|
-
tenantId: tenantFacts[0].
|
|
114
|
+
tenantId: tenantFacts[0].tenant_id,
|
|
115
115
|
scope: derivationScope(tenantFacts[0].scope),
|
|
116
116
|
originProject: tenantFacts[0].origin_project,
|
|
117
117
|
baseHalfLifeDays,
|
|
@@ -126,8 +126,11 @@ export async function buildDag(hippoRoot, facts, opts) {
|
|
|
126
126
|
return result;
|
|
127
127
|
}
|
|
128
128
|
/** Regenerate one dirty summary from its children and tally the outcome; throws reach the caller's isolation. */
|
|
129
|
-
async function rebuildOneSummary(hippoRoot, summary, opts, result) {
|
|
130
|
-
|
|
129
|
+
async function rebuildOneSummary(hippoRoot, summary, opts, result, preloaded, queuedIds) {
|
|
130
|
+
// A queued child may have been rebuilt earlier in this drain, so its preloaded content is stale: reread then.
|
|
131
|
+
const children = preloaded.some((c) => queuedIds.has(c.id))
|
|
132
|
+
? loadChildrenOfSummary(hippoRoot, summary.id, summary.tenant_id)
|
|
133
|
+
: preloaded;
|
|
131
134
|
if (children.length === 0) {
|
|
132
135
|
clearZeroChildSummary(hippoRoot, summary, result);
|
|
133
136
|
return;
|
|
@@ -196,6 +199,8 @@ export async function rebuildDirtySummaries(hippoRoot, opts) {
|
|
|
196
199
|
failed: 0,
|
|
197
200
|
capped,
|
|
198
201
|
};
|
|
202
|
+
const childrenByParent = loadChildrenOfSummaries(hippoRoot, queue);
|
|
203
|
+
const queuedIds = new Set(queue.map((s) => s.id));
|
|
199
204
|
for (const [index, summary] of queue.entries()) {
|
|
200
205
|
// Yield the macrotask queue every 25 summaries: each iteration is synchronous SQLite, so a batch of up to 1000
|
|
201
206
|
// would starve timers and IPC (server keep-alives, Vitest's birpc heartbeat).
|
|
@@ -203,13 +208,13 @@ export async function rebuildDirtySummaries(hippoRoot, opts) {
|
|
|
203
208
|
await new Promise((resolve) => setImmediate(resolve));
|
|
204
209
|
}
|
|
205
210
|
try {
|
|
206
|
-
await rebuildOneSummary(hippoRoot, summary, opts, result);
|
|
211
|
+
await rebuildOneSummary(hippoRoot, summary, opts, result, childrenByParent.get(childrenKey(summary.tenant_id, summary.id)) ?? [], queuedIds);
|
|
207
212
|
}
|
|
208
213
|
catch (err) {
|
|
209
214
|
// One throw doesn't abort the queue; log enough to triage an exotic
|
|
210
215
|
// failure (SQLite I/O, prepare), since audit() already catches its own.
|
|
211
216
|
result.failed++;
|
|
212
|
-
log.error(`rebuildDirtySummaries: summary ${summary.id} (tenant ${summary.
|
|
217
|
+
log.error(`rebuildDirtySummaries: summary ${summary.id} (tenant ${summary.tenant_id}) failed: ${errorMessage(err)}`, errorFields(err));
|
|
213
218
|
}
|
|
214
219
|
}
|
|
215
220
|
return result;
|
|
@@ -275,7 +280,7 @@ export async function buildEntityProfiles(hippoRoot, l2Summaries, opts) {
|
|
|
275
280
|
const unparented = l2Summaries.filter((s) => s.dag_level === 2 && !s.dag_parent_id);
|
|
276
281
|
for (const [, tenantL2s] of partitionFactsByTenant(unparented)) {
|
|
277
282
|
const home = {
|
|
278
|
-
tenantId: tenantL2s[0].
|
|
283
|
+
tenantId: tenantL2s[0].tenant_id,
|
|
279
284
|
scope: derivationScope(tenantL2s[0].scope),
|
|
280
285
|
originProject: tenantL2s[0].origin_project,
|
|
281
286
|
baseHalfLifeDays,
|
|
@@ -34,7 +34,7 @@ function entriesByPartition(entries) {
|
|
|
34
34
|
const entriesByTenant = new Map();
|
|
35
35
|
for (const entry of entries) {
|
|
36
36
|
// EI2: also split by restricted scope, else a private copy can delete the readable one.
|
|
37
|
-
const key = derivationPartitionKey(entry.
|
|
37
|
+
const key = derivationPartitionKey(entry.tenant_id, entry.scope, entry.origin_project);
|
|
38
38
|
const bucket = entriesByTenant.get(key);
|
|
39
39
|
if (bucket)
|
|
40
40
|
bucket.push(entry);
|
|
@@ -114,7 +114,7 @@ export function rankNonPinnedByTenant(entries, now, weights = MEMORY_VALUE_WEIGH
|
|
|
114
114
|
continue; // pinned entries never compete for rescue (never condemned)
|
|
115
115
|
// Default an undefined tenantId as dag.ts:341 does: a raw/legacy row can carry one at runtime,
|
|
116
116
|
// and keying it "undefined" would split it into its own singleton tenant.
|
|
117
|
-
const tenantId = e.
|
|
117
|
+
const tenantId = e.tenant_id ?? DEFAULT_TENANT_ID;
|
|
118
118
|
const list = byTenant.get(tenantId);
|
|
119
119
|
if (list)
|
|
120
120
|
list.push(e);
|