hippo-memory 1.63.2 → 1.64.1
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 +11 -5
- package/dist/agent-memories/claude-code.js +3 -3
- package/dist/ambient-store.d.ts +2 -0
- package/dist/ambient-store.js +4 -3
- package/dist/api/assemble.d.ts +2 -0
- package/dist/api/assemble.js +11 -7
- package/dist/api/auth.d.ts +15 -11
- package/dist/api/auth.js +116 -22
- package/dist/api/context-select.d.ts +5 -3
- package/dist/api/context-select.js +6 -11
- package/dist/api/context-types.d.ts +2 -1
- package/dist/api/context.d.ts +1 -1
- package/dist/api/context.js +79 -55
- package/dist/api/dormant.d.ts +5 -5
- package/dist/api/dormant.js +13 -9
- package/dist/api/drill-down.d.ts +2 -0
- package/dist/api/drill-down.js +12 -7
- package/dist/api/forget.d.ts +1 -1
- package/dist/api/forget.js +8 -4
- package/dist/api/learn.js +5 -3
- package/dist/api/outcome.d.ts +1 -1
- package/dist/api/outcome.js +2 -1
- package/dist/api/promote.js +8 -5
- package/dist/api/recall-types.d.ts +4 -2
- package/dist/api/recall.js +51 -36
- package/dist/api/remember.d.ts +7 -0
- package/dist/api/remember.js +26 -2
- package/dist/api/sleep-run.d.ts +28 -0
- package/dist/api/sleep-run.js +267 -0
- package/dist/api/sleep.d.ts +5 -45
- package/dist/api/sleep.js +5 -252
- package/dist/api/types.d.ts +5 -0
- package/dist/api/types.js +4 -0
- package/dist/audit-prune.js +1 -1
- package/dist/audit.d.ts +1 -3
- package/dist/audit.js +7 -103
- package/dist/auth.d.ts +48 -4
- package/dist/auth.js +111 -42
- package/dist/autolearn.js +2 -2
- package/dist/capture/caller-session.d.ts +16 -0
- package/dist/capture/caller-session.js +58 -0
- package/dist/capture/command.d.ts +19 -1
- package/dist/capture/command.js +28 -21
- package/dist/capture/compact-resume-caller.d.ts +11 -0
- package/dist/capture/compact-resume-caller.js +35 -0
- package/dist/capture/compact.d.ts +0 -14
- package/dist/capture/compact.js +7 -160
- package/dist/capture/compaction-items-caller.d.ts +16 -0
- package/dist/capture/compaction-items-caller.js +32 -0
- package/dist/capture/extract.d.ts +2 -14
- package/dist/capture/extract.js +135 -346
- package/dist/capture/failure-caller.d.ts +21 -0
- package/dist/capture/failure-caller.js +74 -0
- package/dist/capture/failure-reading.d.ts +44 -0
- package/dist/capture/failure-reading.js +78 -0
- package/dist/capture/pre-compact-caller.d.ts +16 -0
- package/dist/capture/pre-compact-caller.js +39 -0
- package/dist/capture/session-end-caller.d.ts +21 -0
- package/dist/capture/session-end-caller.js +30 -0
- package/dist/capture/session-texts.d.ts +14 -0
- package/dist/capture/session-texts.js +42 -0
- package/dist/capture/transcript.d.ts +6 -1
- package/dist/capture/transcript.js +20 -16
- package/dist/capture/working-state.d.ts +21 -0
- package/dist/capture/working-state.js +154 -0
- package/dist/capture-error.d.ts +8 -22
- package/dist/capture-error.js +9 -61
- package/dist/cli/audit.js +25 -0
- package/dist/cli/auth.js +4 -3
- package/dist/cli/context.js +13 -158
- package/dist/cli/continuity.js +2 -1
- package/dist/cli/init.js +5 -3
- package/dist/cli/last-sleep.d.ts +6 -0
- package/dist/cli/last-sleep.js +68 -0
- package/dist/cli/maintenance.d.ts +2 -1
- package/dist/cli/maintenance.js +17 -11
- package/dist/cli/quality-repair-once.d.ts +3 -0
- package/dist/cli/quality-repair-once.js +36 -0
- package/dist/cli/recall.js +2 -1
- package/dist/cli/remember.d.ts +2 -0
- package/dist/cli/remember.js +4 -1
- package/dist/cli/session-hooks.d.ts +0 -3
- package/dist/cli/session-hooks.js +12 -49
- package/dist/cli/setup.js +10 -2
- package/dist/cli/shared.d.ts +7 -14
- package/dist/cli/shared.js +19 -78
- package/dist/cli/sleep.js +6 -3
- package/dist/cli/usage.js +18 -9
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +3 -2
- package/dist/client.js +1 -1
- package/dist/compaction-record.d.ts +19 -1
- package/dist/compaction-record.js +61 -132
- package/dist/compaction-spool.d.ts +45 -0
- package/dist/compaction-spool.js +454 -0
- package/dist/config.d.ts +8 -0
- package/dist/config.js +49 -0
- package/dist/connectors/github/deletion.d.ts +1 -1
- package/dist/connectors/github/deletion.js +5 -17
- package/dist/consolidate/conflicts.js +4 -0
- package/dist/consolidate/flush-units.d.ts +9 -0
- package/dist/consolidate/flush-units.js +63 -0
- package/dist/consolidate/llm-passes.js +5 -4
- package/dist/consolidate/merge.js +5 -1
- package/dist/consolidate/run.d.ts +2 -1
- package/dist/consolidate/sleep.d.ts +2 -0
- package/dist/consolidate/sleep.js +82 -15
- package/dist/context-render.d.ts +14 -0
- package/dist/context-render.js +28 -0
- package/dist/dag.js +6 -0
- package/dist/dashboard-actions.js +3 -2
- package/dist/dashboard.js +4 -3
- package/dist/db/backup.d.ts +6 -0
- package/dist/db/backup.js +29 -0
- package/dist/db/busy.d.ts +9 -0
- package/dist/db/busy.js +35 -0
- package/dist/db/continuity.d.ts +1 -0
- package/dist/db/continuity.js +31 -4
- package/dist/db/meta.d.ts +2 -0
- package/dist/db/meta.js +7 -0
- package/dist/db/migrate.js +2 -3
- package/dist/db/migrations/index.d.ts +1 -1
- package/dist/db/migrations/index.js +5 -1
- package/dist/db/migrations/v39.js +2 -7
- package/dist/db/migrations/v53.d.ts +3 -0
- package/dist/db/migrations/v53.js +16 -0
- package/dist/db/migrations/v54.d.ts +3 -0
- package/dist/db/migrations/v54.js +25 -0
- package/dist/db/open.d.ts +11 -0
- package/dist/db/open.js +27 -0
- package/dist/db/tables.d.ts +2 -0
- package/dist/db/tables.js +6 -2
- package/dist/db.d.ts +2 -2
- package/dist/db.js +2 -2
- package/dist/doctor.d.ts +1 -1
- package/dist/doctor.js +31 -15
- package/dist/dormant.d.ts +16 -20
- package/dist/dormant.js +24 -30
- package/dist/embedding-provider.js +4 -3
- package/dist/entry/json-hooks.d.ts +3 -0
- package/dist/entry/json-hooks.js +4 -0
- package/dist/entry/project-identity.d.ts +2 -0
- package/dist/entry/project-identity.js +3 -0
- package/dist/entry/session-text.d.ts +7 -0
- package/dist/entry/session-text.js +8 -0
- package/dist/extract.js +6 -0
- package/dist/failure-log.d.ts +10 -2
- package/dist/failure-log.js +19 -2
- package/dist/gated-write.js +1 -1
- package/dist/graph/delta.d.ts +44 -0
- package/dist/graph/delta.js +109 -0
- package/dist/graph/types.d.ts +5 -0
- package/dist/graph/write.d.ts +20 -20
- package/dist/graph/write.js +103 -36
- package/dist/graph-extract.d.ts +43 -9
- package/dist/graph-extract.js +140 -157
- package/dist/graph-recall.d.ts +1 -0
- package/dist/graph-recall.js +2 -2
- package/dist/graph-view.d.ts +1 -0
- package/dist/graph-view.js +25 -2
- package/dist/half-life-migration.js +5 -1
- package/dist/handoff-evidence.d.ts +5 -0
- package/dist/handoff-evidence.js +30 -0
- package/dist/home-path.d.ts +5 -0
- package/dist/home-path.js +24 -0
- package/dist/hooks/json-hooks.d.ts +13 -2
- package/dist/hooks/json-hooks.js +246 -117
- package/dist/hooks/opencode.js +1 -1
- package/dist/hooks/shared.d.ts +2 -0
- package/dist/hooks/shared.js +6 -1
- package/dist/http-util.d.ts +16 -7
- package/dist/http-util.js +65 -20
- package/dist/importers/vault.js +2 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/json.d.ts +2 -0
- package/dist/json.js +7 -0
- package/dist/ledger-db.d.ts +10 -0
- package/dist/ledger-db.js +42 -0
- package/dist/local-embedding.d.ts +2 -0
- package/dist/local-embedding.js +20 -4
- package/dist/mcp/admin-tools.d.ts +2 -2
- package/dist/mcp/admin-tools.js +47 -5
- package/dist/mcp/memory-tools.js +7 -2
- package/dist/mcp/protocol.d.ts +6 -0
- package/dist/mcp/protocol.js +4 -2
- package/dist/mcp/recall-tools.js +19 -6
- package/dist/mcp/request.js +4 -0
- package/dist/mcp/session-state.d.ts +4 -0
- package/dist/mcp/session-state.js +15 -6
- package/dist/mcp/shared-gate.d.ts +4 -0
- package/dist/mcp/shared-gate.js +22 -0
- package/dist/mcp/tools.js +1 -0
- package/dist/memory-quality.d.ts +35 -0
- package/dist/memory-quality.js +191 -0
- package/dist/memory.d.ts +4 -4
- package/dist/physics-state.js +1 -1
- package/dist/pilot-arm.d.ts +14 -3
- package/dist/pilot-arm.js +32 -10
- package/dist/postinstall.js +2 -2
- package/dist/project-briefs.js +4 -4
- package/dist/project-identity.d.ts +14 -10
- package/dist/project-identity.js +29 -11
- package/dist/project-merge.d.ts +0 -2
- package/dist/project-merge.js +9 -13
- package/dist/prompt-hook.d.ts +65 -0
- package/dist/prompt-hook.js +213 -0
- package/dist/prompt-recall.js +1 -1
- package/dist/quality-repair.d.ts +27 -0
- package/dist/quality-repair.js +220 -0
- package/dist/rate-limit.d.ts +1 -0
- package/dist/rate-limit.js +5 -3
- package/dist/raw-archive.d.ts +1 -1
- package/dist/raw-archive.js +6 -16
- package/dist/recall-scope.d.ts +28 -5
- package/dist/recall-scope.js +52 -7
- package/dist/recall-trace.js +1 -1
- package/dist/reject-flow.d.ts +2 -0
- package/dist/reject-flow.js +20 -6
- package/dist/secret-detect.js +7 -8
- package/dist/server/auth.d.ts +8 -6
- package/dist/server/auth.js +85 -38
- package/dist/server/client-ip.d.ts +2 -0
- package/dist/server/client-ip.js +16 -1
- package/dist/server/mcp-http.d.ts +9 -3
- package/dist/server/mcp-http.js +56 -13
- package/dist/server/request.d.ts +1 -1
- package/dist/server/request.js +5 -5
- package/dist/server/routes/admin.js +12 -6
- package/dist/server/routes/memories.js +9 -1
- package/dist/server/routes/recall.js +14 -2
- package/dist/server/types.d.ts +42 -5
- package/dist/server/validation.d.ts +5 -2
- package/dist/server/validation.js +19 -2
- package/dist/server.d.ts +21 -4
- package/dist/server.js +207 -49
- package/dist/session-digest.d.ts +0 -1
- package/dist/session-digest.js +3 -12
- package/dist/session-owners.d.ts +7 -0
- package/dist/session-owners.js +54 -0
- package/dist/share-scrub.d.ts +3 -0
- package/dist/share-scrub.js +8 -0
- package/dist/shared.d.ts +1 -0
- package/dist/shared.js +26 -18
- package/dist/store/candidates.d.ts +14 -3
- package/dist/store/candidates.js +48 -26
- package/dist/store/conflicts.d.ts +15 -0
- package/dist/store/conflicts.js +68 -44
- package/dist/store/delete-and-batch.d.ts +25 -1
- package/dist/store/delete-and-batch.js +121 -69
- package/dist/store/entry-reads.d.ts +5 -6
- package/dist/store/entry-reads.js +24 -12
- package/dist/store/entry-row.d.ts +7 -10
- package/dist/store/entry-row.js +40 -18
- package/dist/store/entry-writes.d.ts +3 -3
- package/dist/store/entry-writes.js +9 -23
- package/dist/store/handoffs.d.ts +9 -6
- package/dist/store/handoffs.js +72 -59
- package/dist/store/index-and-stats.js +2 -2
- package/dist/store/open.d.ts +3 -1
- package/dist/store/open.js +3 -3
- package/dist/store/search-rows.d.ts +8 -5
- package/dist/store/search-rows.js +15 -18
- package/dist/store/sessions.d.ts +25 -6
- package/dist/store/sessions.js +61 -19
- package/dist/store/summaries.d.ts +1 -1
- package/dist/store/summaries.js +3 -18
- package/dist/store/tenant-lookup.d.ts +7 -3
- package/dist/store/tenant-lookup.js +9 -9
- package/dist/store-port.d.ts +13 -0
- package/dist/store-port.js +20 -0
- package/dist/version.d.ts +5 -1
- package/dist/version.js +5 -1
- package/dist/working-memory.js +1 -1
- package/dist/write-budget.d.ts +15 -0
- package/dist/write-budget.js +15 -0
- 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 +8 -4
package/dist/db/continuity.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { META_TABLE_DDL } from './meta.js';
|
|
2
|
+
import { tableExists } from './tables.js';
|
|
2
3
|
// Shared by migration v48 and the stamped-store self-heal below.
|
|
3
4
|
export const MEMORY_QUARANTINE_DDL = `
|
|
4
5
|
CREATE TABLE IF NOT EXISTS memory_quarantine (
|
|
@@ -27,7 +28,9 @@ const TASK_SNAPSHOTS_DDL = `
|
|
|
27
28
|
updated_at TEXT NOT NULL,
|
|
28
29
|
session_id TEXT,
|
|
29
30
|
tenant_id TEXT NOT NULL DEFAULT 'default',
|
|
30
|
-
scope TEXT
|
|
31
|
+
scope TEXT,
|
|
32
|
+
owner_subject TEXT,
|
|
33
|
+
origin_project TEXT
|
|
31
34
|
)
|
|
32
35
|
`;
|
|
33
36
|
const SESSION_EVENTS_DDL = `
|
|
@@ -60,7 +63,19 @@ const SESSION_HANDOFFS_DDL = `
|
|
|
60
63
|
evidence_json TEXT,
|
|
61
64
|
outcome TEXT,
|
|
62
65
|
target_runtime TEXT,
|
|
63
|
-
card_id TEXT
|
|
66
|
+
card_id TEXT,
|
|
67
|
+
owner_subject TEXT,
|
|
68
|
+
origin_project TEXT
|
|
69
|
+
)
|
|
70
|
+
`;
|
|
71
|
+
// Shared by migration v54 and the self-heal: a session id belongs to the first owner that binds it.
|
|
72
|
+
export const SESSION_OWNERS_DDL = `
|
|
73
|
+
CREATE TABLE IF NOT EXISTS session_owners (
|
|
74
|
+
tenant_id TEXT NOT NULL,
|
|
75
|
+
session_id TEXT NOT NULL,
|
|
76
|
+
owner_subject TEXT NOT NULL,
|
|
77
|
+
created_at TEXT NOT NULL,
|
|
78
|
+
PRIMARY KEY (tenant_id, session_id)
|
|
64
79
|
)
|
|
65
80
|
`;
|
|
66
81
|
const CARDS_DDL = `
|
|
@@ -128,6 +143,7 @@ const CONTINUITY_TABLE_DDL = [
|
|
|
128
143
|
CARD_RUNS_DDL,
|
|
129
144
|
CARD_COMMENTS_DDL,
|
|
130
145
|
MEMORY_QUARANTINE_DDL,
|
|
146
|
+
SESSION_OWNERS_DDL,
|
|
131
147
|
];
|
|
132
148
|
const CONTINUITY_INDEX_DDL = [
|
|
133
149
|
`CREATE INDEX IF NOT EXISTS idx_task_snapshots_status_updated ON task_snapshots(status, updated_at DESC, id DESC)`,
|
|
@@ -145,19 +161,30 @@ const CONTINUITY_INDEX_DDL = [
|
|
|
145
161
|
`CREATE INDEX IF NOT EXISTS idx_card_runs_tenant_card ON card_runs(tenant_id, card, started DESC)`,
|
|
146
162
|
`CREATE INDEX IF NOT EXISTS idx_card_comments_tenant_card ON card_comments(tenant_id, card_id, created_at DESC)`,
|
|
147
163
|
`CREATE INDEX IF NOT EXISTS idx_session_handoffs_tenant_card ON session_handoffs(tenant_id, card_id, created_at DESC)`,
|
|
164
|
+
`CREATE INDEX IF NOT EXISTS idx_task_snapshots_owner ON task_snapshots(tenant_id, owner_subject, origin_project, status, updated_at DESC)`,
|
|
165
|
+
`CREATE INDEX IF NOT EXISTS idx_session_handoffs_owner ON session_handoffs(tenant_id, owner_subject, origin_project, created_at DESC)`,
|
|
148
166
|
];
|
|
167
|
+
// A client retry carries the same request id, so a repeat finds the earlier row instead of writing again.
|
|
168
|
+
const REQUEST_INDEX_DDL = {
|
|
169
|
+
compactions: `CREATE UNIQUE INDEX IF NOT EXISTS idx_compactions_request ON compactions(tenant_id, request_id) WHERE request_id IS NOT NULL`,
|
|
170
|
+
failure_log: `CREATE UNIQUE INDEX IF NOT EXISTS idx_failure_log_request ON failure_log(tenant_id, request_id) WHERE request_id IS NOT NULL`,
|
|
171
|
+
};
|
|
149
172
|
// Lives with the other re-asserted DDL so the required-object list below derives from one place.
|
|
150
173
|
export const MEMORIES_FTS_DDL = `CREATE VIRTUAL TABLE IF NOT EXISTS memories_fts USING fts5(id UNINDEXED, content, tags)`;
|
|
151
|
-
const CREATED_NAME = /CREATE\s+(?:VIRTUAL\s+)?(?:TABLE|INDEX)\s+IF\s+NOT\s+EXISTS\s+(\w+)/gi;
|
|
174
|
+
const CREATED_NAME = /CREATE\s+(?:VIRTUAL\s+|UNIQUE\s+)?(?:TABLE|INDEX)\s+IF\s+NOT\s+EXISTS\s+(\w+)/gi;
|
|
152
175
|
/** Names of every table and index a `CREATE ... IF NOT EXISTS` in `ddl` makes. */
|
|
153
176
|
export function createdObjectNames(ddl) {
|
|
154
177
|
return [...ddl.matchAll(CREATED_NAME)].map((m) => m[1]);
|
|
155
178
|
}
|
|
156
179
|
/** Every object runMigrations re-asserts on a stamped store; the open fast path requires all of them. */
|
|
157
|
-
export const REQUIRED_SCHEMA_OBJECTS = Object.freeze([META_TABLE_DDL, ...CONTINUITY_TABLE_DDL, ...CONTINUITY_INDEX_DDL, MEMORIES_FTS_DDL].flatMap(createdObjectNames));
|
|
180
|
+
export const REQUIRED_SCHEMA_OBJECTS = Object.freeze([META_TABLE_DDL, ...CONTINUITY_TABLE_DDL, ...CONTINUITY_INDEX_DDL, ...Object.values(REQUEST_INDEX_DDL), MEMORIES_FTS_DDL].flatMap(createdObjectNames));
|
|
158
181
|
// After the loop: tenant_id (v16) and scope (v23) do not exist yet on a genuine old store.
|
|
159
182
|
export function ensureContinuityIndexes(db) {
|
|
160
183
|
for (const ddl of CONTINUITY_INDEX_DDL)
|
|
161
184
|
db.exec(ddl);
|
|
185
|
+
// A store that lost one of these tables still opens, so its hooks can log the failed step and run the rest.
|
|
186
|
+
for (const [table, ddl] of Object.entries(REQUEST_INDEX_DDL))
|
|
187
|
+
if (tableExists(db, table))
|
|
188
|
+
db.exec(ddl);
|
|
162
189
|
}
|
|
163
190
|
//# sourceMappingURL=continuity.js.map
|
package/dist/db/meta.d.ts
CHANGED
|
@@ -5,5 +5,7 @@ export declare function getSchemaVersion(db: DatabaseSyncLike): number;
|
|
|
5
5
|
export declare function setSchemaVersion(db: DatabaseSyncLike, version: number): void;
|
|
6
6
|
export declare function getMeta(db: DatabaseSyncLike, key: string, fallback?: string): string;
|
|
7
7
|
export declare function setMeta(db: DatabaseSyncLike, key: string, value: string): void;
|
|
8
|
+
/** Raise min_compatible_binary to `version`; never lowers it, since an older floor would let back in a binary a newer write already shut out. */
|
|
9
|
+
export declare function raiseMinBinary(db: DatabaseSyncLike, version: string): void;
|
|
8
10
|
export declare function isFtsAvailable(db: DatabaseSyncLike): boolean;
|
|
9
11
|
//# sourceMappingURL=meta.d.ts.map
|
package/dist/db/meta.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { compareSemver } from '../version.js';
|
|
1
2
|
import { tableExists } from './tables.js';
|
|
2
3
|
export const META_TABLE_DDL = `
|
|
3
4
|
CREATE TABLE IF NOT EXISTS meta (
|
|
@@ -30,6 +31,12 @@ export function getMeta(db, key, fallback = '') {
|
|
|
30
31
|
export function setMeta(db, key, value) {
|
|
31
32
|
db.prepare(`INSERT INTO meta(key, value) VALUES(?, ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value`).run(key, value);
|
|
32
33
|
}
|
|
34
|
+
/** Raise min_compatible_binary to `version`; never lowers it, since an older floor would let back in a binary a newer write already shut out. */
|
|
35
|
+
export function raiseMinBinary(db, version) {
|
|
36
|
+
const existing = getMeta(db, 'min_compatible_binary');
|
|
37
|
+
if (!existing || compareSemver(version, existing) > 0)
|
|
38
|
+
setMeta(db, 'min_compatible_binary', version);
|
|
39
|
+
}
|
|
33
40
|
export function isFtsAvailable(db) {
|
|
34
41
|
return getMeta(db, 'fts5_available', '0') === '1';
|
|
35
42
|
}
|
package/dist/db/migrate.js
CHANGED
|
@@ -179,13 +179,12 @@ export function repairFtsDrift(db) {
|
|
|
179
179
|
const counts = ftsRowCounts(db);
|
|
180
180
|
if (counts === null || counts.memories === counts.fts)
|
|
181
181
|
return false;
|
|
182
|
+
// NOT IN reads the UNINDEXED id column once, not once per memory; one NULL id would make NOT IN match nothing.
|
|
182
183
|
db.exec(`
|
|
183
184
|
INSERT INTO memories_fts(id, content, tags)
|
|
184
185
|
SELECT m.id, m.content, m.tags_json
|
|
185
186
|
FROM memories m
|
|
186
|
-
WHERE NOT
|
|
187
|
-
SELECT 1 FROM memories_fts f WHERE f.id = m.id
|
|
188
|
-
)
|
|
187
|
+
WHERE m.id NOT IN (SELECT id FROM memories_fts WHERE id IS NOT NULL)
|
|
189
188
|
`);
|
|
190
189
|
db.exec(`
|
|
191
190
|
DELETE FROM memories_fts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Migration } from './types.js';
|
|
2
|
-
export declare const CURRENT_SCHEMA_VERSION =
|
|
2
|
+
export declare const CURRENT_SCHEMA_VERSION = 54;
|
|
3
3
|
/** Every schema migration in version order; runMigrations applies each one above the stored version. */
|
|
4
4
|
export declare const MIGRATIONS: Migration[];
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -50,7 +50,9 @@ import { v49 } from './v49.js';
|
|
|
50
50
|
import { v50 } from './v50.js';
|
|
51
51
|
import { v51 } from './v51.js';
|
|
52
52
|
import { v52 } from './v52.js';
|
|
53
|
-
|
|
53
|
+
import { v53 } from './v53.js';
|
|
54
|
+
import { v54 } from './v54.js';
|
|
55
|
+
export const CURRENT_SCHEMA_VERSION = 54;
|
|
54
56
|
/** Every schema migration in version order; runMigrations applies each one above the stored version. */
|
|
55
57
|
export const MIGRATIONS = [
|
|
56
58
|
v01,
|
|
@@ -105,5 +107,7 @@ export const MIGRATIONS = [
|
|
|
105
107
|
v50,
|
|
106
108
|
v51,
|
|
107
109
|
v52,
|
|
110
|
+
v53,
|
|
111
|
+
v54,
|
|
108
112
|
];
|
|
109
113
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as os from 'os';
|
|
2
2
|
import * as path from 'path';
|
|
3
|
-
import { compareSemver } from '../../version.js';
|
|
4
3
|
import { deriveOriginProject, originFromSource, isGlobalStoreRoot } from '../../project-identity.js';
|
|
4
|
+
import { raiseMinBinary } from '../meta.js';
|
|
5
5
|
import { tableHasColumn } from '../tables.js';
|
|
6
6
|
export const v39 = {
|
|
7
7
|
version: 39,
|
|
@@ -48,12 +48,7 @@ export const v39 = {
|
|
|
48
48
|
// this DB would ignore origin_project and the secret veto and resume
|
|
49
49
|
// injecting cross-project rows. 1.24.0 is the first version with the
|
|
50
50
|
// isolation behavior. Forward-only - never lower an existing minimum.
|
|
51
|
-
|
|
52
|
-
// named in the SELECT above.
|
|
53
|
-
const existingMin = db.prepare(`SELECT value FROM meta WHERE key = 'min_compatible_binary'`).get()?.value;
|
|
54
|
-
if (!existingMin || compareSemver('1.24.0', existingMin) > 0) {
|
|
55
|
-
db.prepare(`INSERT INTO meta(key, value) VALUES('min_compatible_binary', ?) ON CONFLICT(key) DO UPDATE SET value=excluded.value`).run('1.24.0');
|
|
56
|
-
}
|
|
51
|
+
raiseMinBinary(db, '1.24.0');
|
|
57
52
|
},
|
|
58
53
|
};
|
|
59
54
|
//# sourceMappingURL=v39.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { tableHasColumn, tableExists } from '../tables.js';
|
|
2
|
+
export const v53 = {
|
|
3
|
+
version: 53,
|
|
4
|
+
up: (db) => {
|
|
5
|
+
// Self-service keys record who minted them and when they stop working; both stay null for keys minted before this.
|
|
6
|
+
// No floor raise here: a store with no expiring key is safe for older binaries, so the first expiring key raises it.
|
|
7
|
+
if (tableExists(db, 'api_keys')) {
|
|
8
|
+
if (!tableHasColumn(db, 'api_keys', 'owner_subject'))
|
|
9
|
+
db.exec(`ALTER TABLE api_keys ADD COLUMN owner_subject TEXT`);
|
|
10
|
+
if (!tableHasColumn(db, 'api_keys', 'expires_at'))
|
|
11
|
+
db.exec(`ALTER TABLE api_keys ADD COLUMN expires_at TEXT`);
|
|
12
|
+
db.exec(`CREATE INDEX IF NOT EXISTS idx_api_keys_live_owner ON api_keys(tenant_id, owner_subject) WHERE revoked_at IS NULL`);
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
//# sourceMappingURL=v53.js.map
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { SESSION_OWNERS_DDL } from '../continuity.js';
|
|
2
|
+
import { tableHasColumn, tableExists } from '../tables.js';
|
|
3
|
+
const OWNER_COLUMNS = [
|
|
4
|
+
['task_snapshots', 'owner_subject'],
|
|
5
|
+
['task_snapshots', 'origin_project'],
|
|
6
|
+
['session_handoffs', 'owner_subject'],
|
|
7
|
+
['session_handoffs', 'origin_project'],
|
|
8
|
+
['failure_log', 'owner_subject'],
|
|
9
|
+
['failure_log', 'origin_project'],
|
|
10
|
+
['compactions', 'request_id'],
|
|
11
|
+
['failure_log', 'request_id'],
|
|
12
|
+
];
|
|
13
|
+
export const v54 = {
|
|
14
|
+
version: 54,
|
|
15
|
+
up: (db) => {
|
|
16
|
+
// Rows from before this stay NULL: no backfill, so they never match an owner's key.
|
|
17
|
+
// No floor raise here: the first session bind or owner snapshot raises it, so an unused store stays open to older binaries.
|
|
18
|
+
for (const [t, c] of OWNER_COLUMNS) {
|
|
19
|
+
if (tableExists(db, t) && !tableHasColumn(db, t, c))
|
|
20
|
+
db.exec(`ALTER TABLE ${t} ADD COLUMN ${c} TEXT`);
|
|
21
|
+
}
|
|
22
|
+
db.exec(SESSION_OWNERS_DDL);
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
//# sourceMappingURL=v54.js.map
|
package/dist/db/open.d.ts
CHANGED
|
@@ -11,8 +11,19 @@ export declare function withSharedStoreHandles<T>(fn: () => T | Promise<T>, opts
|
|
|
11
11
|
}): Promise<T>;
|
|
12
12
|
/** Lock wait inside an HTTP request: SQLite waits synchronously, so a long wait would stall every other request on the event loop. */
|
|
13
13
|
export declare const SERVER_DB_WAIT_MS = 250;
|
|
14
|
+
/** Lock wait for each of sleep's short write transactions, even inside a server request: a run stops only on a writer that holds the lock longer. */
|
|
15
|
+
export declare const SLEEP_DB_WAIT_MS = 5000;
|
|
14
16
|
/** Runs `fn` so that every store opened inside it, across awaits, waits at most `busyWaitMs` for a lock unless the open passes its own. */
|
|
15
17
|
export declare function withBusyWait<T>(busyWaitMs: number, fn: () => T): T;
|
|
18
|
+
/** The lock wait an open here would get without its own `busyWaitMs`, in openHippoDb's order; undefined outside both scopes. */
|
|
19
|
+
export declare function scopedBusyWait(): number | undefined;
|
|
20
|
+
/** Thrown by a hippo.db open inside a request served from another store: the code path is not ported to the store port yet. */
|
|
21
|
+
export declare class SqliteBlockedError extends Error {
|
|
22
|
+
readonly storeKind: string;
|
|
23
|
+
constructor(storeKind: string);
|
|
24
|
+
}
|
|
25
|
+
/** Runs `fn` so that every hippo.db open inside it, across awaits, throws; otherwise a missed port would create and write a hippo.db nobody reads. */
|
|
26
|
+
export declare function withSqliteBlocked<T>(storeKind: string, fn: () => T): T;
|
|
16
27
|
/** `busyWaitMs` shortens every lock wait of this open, for a hook that must finish inside its own timeout. */
|
|
17
28
|
export declare function openHippoDb(hippoRoot: string, opts?: {
|
|
18
29
|
busyWaitMs?: number;
|
package/dist/db/open.js
CHANGED
|
@@ -54,13 +54,39 @@ export async function withSharedStoreHandles(fn, opts) {
|
|
|
54
54
|
}
|
|
55
55
|
/** Lock wait inside an HTTP request: SQLite waits synchronously, so a long wait would stall every other request on the event loop. */
|
|
56
56
|
export const SERVER_DB_WAIT_MS = 250;
|
|
57
|
+
/** Lock wait for each of sleep's short write transactions, even inside a server request: a run stops only on a writer that holds the lock longer. */
|
|
58
|
+
export const SLEEP_DB_WAIT_MS = 5000;
|
|
57
59
|
const scopedBusyWaitMs = new AsyncLocalStorage();
|
|
58
60
|
/** Runs `fn` so that every store opened inside it, across awaits, waits at most `busyWaitMs` for a lock unless the open passes its own. */
|
|
59
61
|
export function withBusyWait(busyWaitMs, fn) {
|
|
60
62
|
return scopedBusyWaitMs.run(busyWaitMs, fn);
|
|
61
63
|
}
|
|
64
|
+
/** The lock wait an open here would get without its own `busyWaitMs`, in openHippoDb's order; undefined outside both scopes. */
|
|
65
|
+
export function scopedBusyWait() {
|
|
66
|
+
return shareBusyWaitMs ?? scopedBusyWaitMs.getStore();
|
|
67
|
+
}
|
|
68
|
+
/** Thrown by a hippo.db open inside a request served from another store: the code path is not ported to the store port yet. */
|
|
69
|
+
export class SqliteBlockedError extends Error {
|
|
70
|
+
storeKind;
|
|
71
|
+
constructor(storeKind) {
|
|
72
|
+
super(`hippo.db is not opened while the '${storeKind}' store serves this request; this code path is not ported to the store yet`);
|
|
73
|
+
this.storeKind = storeKind;
|
|
74
|
+
this.name = 'SqliteBlockedError';
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const sqliteBlockedBy = new AsyncLocalStorage();
|
|
78
|
+
/** Runs `fn` so that every hippo.db open inside it, across awaits, throws; otherwise a missed port would create and write a hippo.db nobody reads. */
|
|
79
|
+
export function withSqliteBlocked(storeKind, fn) {
|
|
80
|
+
return sqliteBlockedBy.run(storeKind, fn);
|
|
81
|
+
}
|
|
82
|
+
function assertSqliteAllowed() {
|
|
83
|
+
const storeKind = sqliteBlockedBy.getStore();
|
|
84
|
+
if (storeKind !== undefined)
|
|
85
|
+
throw new SqliteBlockedError(storeKind);
|
|
86
|
+
}
|
|
62
87
|
/** `busyWaitMs` shortens every lock wait of this open, for a hook that must finish inside its own timeout. */
|
|
63
88
|
export function openHippoDb(hippoRoot, opts) {
|
|
89
|
+
assertSqliteAllowed();
|
|
64
90
|
if (shareDepth === 0)
|
|
65
91
|
return openOwnHippoDb(hippoRoot, { busyWaitMs: opts?.busyWaitMs ?? scopedBusyWaitMs.getStore() });
|
|
66
92
|
const busyWaitMs = opts?.busyWaitMs ?? shareBusyWaitMs ?? scopedBusyWaitMs.getStore();
|
|
@@ -121,6 +147,7 @@ function openOwnHippoDb(hippoRoot, opts) {
|
|
|
121
147
|
}
|
|
122
148
|
/** Open an existing store without changing it: no mkdir, WAL switch, migration or mirror cleanup. Throws when hippo.db is missing. */
|
|
123
149
|
export function openHippoDbReadOnly(hippoRoot) {
|
|
150
|
+
assertSqliteAllowed();
|
|
124
151
|
const db = new DatabaseSync(getHippoDbPath(hippoRoot), { readOnly: true });
|
|
125
152
|
try {
|
|
126
153
|
db.exec('PRAGMA busy_timeout = 5000');
|
package/dist/db/tables.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { DatabaseSyncLike } from './sqlite.js';
|
|
2
|
+
/** Column names of one table; empty when the table is missing. */
|
|
3
|
+
export declare function tableColumns(db: DatabaseSyncLike, tableName: string): Set<string>;
|
|
2
4
|
export declare function tableHasColumn(db: DatabaseSyncLike, tableName: string, columnName: string): boolean;
|
|
3
5
|
export declare function tableExists(db: DatabaseSyncLike, tableName: string): boolean;
|
|
4
6
|
/** Row count of one table; null when the table is missing or unreadable, which callers show as unknown. */
|
package/dist/db/tables.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
|
|
1
|
+
/** Column names of one table; empty when the table is missing. */
|
|
2
|
+
export function tableColumns(db, tableName) {
|
|
2
3
|
if (!/^[a-z_]+$/i.test(tableName))
|
|
3
4
|
throw new Error(`Invalid table name: ${tableName}`);
|
|
4
5
|
// SAFETY: rows' shape matches PRAGMA table_info's documented `name` column.
|
|
5
6
|
const rows = db.prepare(`PRAGMA table_info(${tableName})`).all();
|
|
6
|
-
return rows.
|
|
7
|
+
return new Set(rows.flatMap((row) => (row.name === undefined ? [] : [row.name])));
|
|
8
|
+
}
|
|
9
|
+
export function tableHasColumn(db, tableName, columnName) {
|
|
10
|
+
return tableColumns(db, tableName).has(columnName);
|
|
7
11
|
}
|
|
8
12
|
export function tableExists(db, tableName) {
|
|
9
13
|
if (!/^[a-z_]+$/i.test(tableName))
|
package/dist/db.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export type { DatabaseSyncLike } from './db/sqlite.js';
|
|
2
|
-
export { isSqliteBusy, STORE_BUSY_MESSAGE, execWithBusyRetry } from './db/busy.js';
|
|
2
|
+
export { isSqliteBusy, isStoreBusy, StoreBusyError, STORE_BUSY_MESSAGE, execWithBusyRetry, withWriteScope } from './db/busy.js';
|
|
3
3
|
export { getSchemaVersion, getMeta, setMeta, isFtsAvailable } from './db/meta.js';
|
|
4
4
|
export { countTableRows, pruneConsolidationRuns } from './db/tables.js';
|
|
5
5
|
export { getCurrentSchemaVersion, IncompatibleBinaryError, ftsRowCounts, repairFtsDrift } from './db/migrate.js';
|
|
6
|
-
export { getHippoDbPath, HOOK_DB_WAIT_MS, noteStoreBusy, withSharedStoreHandles, SERVER_DB_WAIT_MS, withBusyWait, openHippoDb, openHippoDbReadOnly, closeHippoDb, } from './db/open.js';
|
|
6
|
+
export { getHippoDbPath, HOOK_DB_WAIT_MS, noteStoreBusy, withSharedStoreHandles, SERVER_DB_WAIT_MS, SLEEP_DB_WAIT_MS, withBusyWait, scopedBusyWait, SqliteBlockedError, withSqliteBlocked, openHippoDb, openHippoDbReadOnly, closeHippoDb, } from './db/open.js';
|
|
7
7
|
//# sourceMappingURL=db.d.ts.map
|
package/dist/db.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
export { isSqliteBusy, STORE_BUSY_MESSAGE, execWithBusyRetry } from './db/busy.js';
|
|
1
|
+
export { isSqliteBusy, isStoreBusy, StoreBusyError, STORE_BUSY_MESSAGE, execWithBusyRetry, withWriteScope } from './db/busy.js';
|
|
2
2
|
export { getSchemaVersion, getMeta, setMeta, isFtsAvailable } from './db/meta.js';
|
|
3
3
|
export { countTableRows, pruneConsolidationRuns } from './db/tables.js';
|
|
4
4
|
export { getCurrentSchemaVersion, IncompatibleBinaryError, ftsRowCounts, repairFtsDrift } from './db/migrate.js';
|
|
5
|
-
export { getHippoDbPath, HOOK_DB_WAIT_MS, noteStoreBusy, withSharedStoreHandles, SERVER_DB_WAIT_MS, withBusyWait, openHippoDb, openHippoDbReadOnly, closeHippoDb, } from './db/open.js';
|
|
5
|
+
export { getHippoDbPath, HOOK_DB_WAIT_MS, noteStoreBusy, withSharedStoreHandles, SERVER_DB_WAIT_MS, SLEEP_DB_WAIT_MS, withBusyWait, scopedBusyWait, SqliteBlockedError, withSqliteBlocked, openHippoDb, openHippoDbReadOnly, closeHippoDb, } from './db/open.js';
|
|
6
6
|
//# sourceMappingURL=db.js.map
|
package/dist/doctor.d.ts
CHANGED
|
@@ -19,7 +19,7 @@ export interface DoctorReport {
|
|
|
19
19
|
/** Inputs for {@link runDoctor}; defaults come from the process. */
|
|
20
20
|
export interface DoctorOpts {
|
|
21
21
|
cwd?: string;
|
|
22
|
-
/** Home directory used to find agent configuration (~/.claude, ~/.codex). */
|
|
22
|
+
/** Home directory used to find agent configuration (~/.claude unless CLAUDE_CONFIG_DIR is set, ~/.codex unless CODEX_HOME is). */
|
|
23
23
|
home?: string;
|
|
24
24
|
version: string;
|
|
25
25
|
nodeVersion?: string;
|
package/dist/doctor.js
CHANGED
|
@@ -13,10 +13,12 @@ import { isInitialized } from './store/open.js';
|
|
|
13
13
|
import { loadConfig } from './config.js';
|
|
14
14
|
import { openHippoDbReadOnly, closeHippoDb, getSchemaVersion, getCurrentSchemaVersion, countTableRows, ftsRowCounts, IncompatibleBinaryError } from './db.js';
|
|
15
15
|
import { REPLAY_AFTER_MS, TRANSCRIPT_FILL_WINDOW_MS } from './compaction-record.js';
|
|
16
|
+
import { SPOOL_DIR, spoolCounts } from './compaction-spool.js';
|
|
16
17
|
import { isEmbeddingAvailable } from './local-embedding.js';
|
|
17
|
-
import { CODEX_TRUST_LINE, codexHomeDir, isCodexPresent, isJsonObject } from './hooks/shared.js';
|
|
18
|
+
import { CODEX_TRUST_LINE, claudeConfigDir, codexHomeDir, isCodexPresent, isJsonObject } from './hooks/shared.js';
|
|
18
19
|
import { planProjectRepair } from './project-merge.js';
|
|
19
20
|
import { resolveTenantId } from './tenant.js';
|
|
21
|
+
import { readJsonFile } from './json.js';
|
|
20
22
|
/** Minimum Node.js version hippo supports (package.json engines). */
|
|
21
23
|
export const MIN_NODE = '22.16.0';
|
|
22
24
|
function versionAtLeast(actual, min) {
|
|
@@ -32,8 +34,7 @@ function versionAtLeast(actual, min) {
|
|
|
32
34
|
}
|
|
33
35
|
function readJson(file) {
|
|
34
36
|
try {
|
|
35
|
-
|
|
36
|
-
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
37
|
+
return readJsonFile(file);
|
|
37
38
|
}
|
|
38
39
|
catch {
|
|
39
40
|
// A missing or corrupt file is the finding doctor reports, so null is the answer.
|
|
@@ -101,8 +102,17 @@ function sleepCheck(db, now) {
|
|
|
101
102
|
return { id: 'sleep', status: 'info', detail: `sleep history unavailable (${message})` };
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
|
-
/**
|
|
105
|
-
function
|
|
105
|
+
/** Spool counts in their own try, so a spool that cannot be read never hides the database counts. */
|
|
106
|
+
function readSpool(store, now) {
|
|
107
|
+
try {
|
|
108
|
+
return { counts: spoolCounts(store, now, REPLAY_AFTER_MS), unread: '' };
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
return { counts: { waiting: 0, stale: 0, bad: 0 }, unread: `; spool not read: ${err instanceof Error ? err.message : String(err)}` };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/** Compaction records the PostCompact hook left unfinished and spool files a replay has not finished, which `hippo sleep` replays. */
|
|
115
|
+
function compactionsCheck(db, store, now) {
|
|
106
116
|
const stuckBefore = new Date(now.getTime() - REPLAY_AFTER_MS).toISOString();
|
|
107
117
|
const transcriptFloor = new Date(now.getTime() - TRANSCRIPT_FILL_WINDOW_MS).toISOString();
|
|
108
118
|
try {
|
|
@@ -115,14 +125,20 @@ function compactionsCheck(db, now) {
|
|
|
115
125
|
const summarised = Number(row?.summarised ?? 0);
|
|
116
126
|
const started = Number(row?.started ?? 0);
|
|
117
127
|
const stuck = summarised + started;
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
status: '
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
128
|
+
const { counts: spool, unread } = readSpool(store, now);
|
|
129
|
+
const spooled = spool.waiting + spool.stale + spool.bad > 0;
|
|
130
|
+
const recorded = `${total} compaction${total === 1 ? '' : 's'} recorded, none stuck`;
|
|
131
|
+
if (stuck === 0 && !spooled && unread === '')
|
|
132
|
+
return { id: 'compactions', status: 'pass', detail: recorded };
|
|
133
|
+
const fix = [
|
|
134
|
+
stuck + spool.waiting + spool.stale > 0 ? 'hippo sleep (replays them)' : '',
|
|
135
|
+
spool.bad > 0 ? `open the .bad files in ${path.join(store, SPOOL_DIR)}, save what you still need with hippo remember, then delete them` : '',
|
|
136
|
+
].filter((s) => s !== '');
|
|
137
|
+
const unfinished = stuck === 0
|
|
138
|
+
? `${recorded} in the store`
|
|
139
|
+
: `${stuck} compaction${stuck === 1 ? '' : 's'} unfinished after 10 minutes (${summarised} with a summary whose memories are not saved yet, ${started} with no summary yet)`;
|
|
140
|
+
const note = spooled ? `; spool: ${spool.waiting} waiting, ${spool.stale} left by a replay that stopped, ${spool.bad} .bad` : '';
|
|
141
|
+
return { id: 'compactions', status: 'warn', detail: `${unfinished}${note}${unread}`, fix: fix.join('; ') };
|
|
126
142
|
}
|
|
127
143
|
catch (err) {
|
|
128
144
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -241,7 +257,7 @@ function databaseChecks(store, now) {
|
|
|
241
257
|
checks.push(tokensCheck(db, since));
|
|
242
258
|
checks.push(failuresCheck(db, since, have));
|
|
243
259
|
checks.push(sleepCheck(db, now));
|
|
244
|
-
checks.push(compactionsCheck(db, now));
|
|
260
|
+
checks.push(compactionsCheck(db, store, now));
|
|
245
261
|
}
|
|
246
262
|
catch (err) {
|
|
247
263
|
checks.push({
|
|
@@ -267,7 +283,7 @@ const CLAUDE_CODE_HOOKS = [
|
|
|
267
283
|
['hippo capture-error', 'failed-tool capture'],
|
|
268
284
|
];
|
|
269
285
|
function claudeCodeCheck(home) {
|
|
270
|
-
const claudeDir =
|
|
286
|
+
const claudeDir = claudeConfigDir(home);
|
|
271
287
|
if (!fs.existsSync(claudeDir)) {
|
|
272
288
|
return { id: 'claude-code', status: 'info', detail: 'Claude Code not found; other agents can use hippo over MCP (hippo mcp)' };
|
|
273
289
|
}
|
package/dist/dormant.d.ts
CHANGED
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import type { DatabaseSyncLike } from './db.js';
|
|
21
21
|
import type { MemoryEntry } from './memory.js';
|
|
22
|
-
|
|
23
|
-
|
|
22
|
+
import type { SqlFragment } from './recall-scope.js';
|
|
23
|
+
/** Why a memory went dormant: sleep's decay pass, an imported agent memory whose note was deleted, `hippo projects repair` splitting a two-project merge, or `hippo audit repair` setting aside an automatic memory with a certain defect. */
|
|
24
|
+
export type DormantReason = 'decay' | 'source-deleted' | 'project-repair' | 'quality-repair';
|
|
24
25
|
/** One memory that sleep is moving out of active memory into the dormant store. */
|
|
25
26
|
export interface DormantMove {
|
|
26
27
|
/** The memory as it stood when it faded; restored verbatim apart from its recall clock. */
|
|
@@ -39,7 +40,7 @@ export interface DormantMemory {
|
|
|
39
40
|
tags: string[];
|
|
40
41
|
/** Live strength when it went dormant. */
|
|
41
42
|
strength: number;
|
|
42
|
-
/** Why it went dormant
|
|
43
|
+
/** Why it went dormant: one of the {@link DormantReason} values. */
|
|
43
44
|
reason: string;
|
|
44
45
|
/** ISO time it went dormant. */
|
|
45
46
|
dormantAt: string;
|
|
@@ -57,8 +58,8 @@ export interface ListDormantOpts {
|
|
|
57
58
|
* memory is never in both places or in neither.
|
|
58
59
|
*/
|
|
59
60
|
export declare function insertDormantRow(db: DatabaseSyncLike, move: DormantMove): void;
|
|
60
|
-
/** A tenant's dormant memories, newest first, optionally filtered by search terms. */
|
|
61
|
-
export declare function listDormantRows(db: DatabaseSyncLike, tenantId: string, opts?: ListDormantOpts): DormantMemory[];
|
|
61
|
+
/** A tenant's dormant memories whose scope `admit` passes, newest first, optionally filtered by search terms. */
|
|
62
|
+
export declare function listDormantRows(db: DatabaseSyncLike, tenantId: string, opts?: ListDormantOpts, admit?: SqlFragment): DormantMemory[];
|
|
62
63
|
/** A dormant memory's stored snapshot plus when and why it went dormant. */
|
|
63
64
|
export interface DormantSnapshot {
|
|
64
65
|
entry: MemoryEntry;
|
|
@@ -77,21 +78,16 @@ export declare function listDormantSnapshots(db: DatabaseSyncLike, tenantId: str
|
|
|
77
78
|
export declare function dormantSnapshotsBySourcePrefix(db: DatabaseSyncLike, tenantId: string, prefix: string): DormantSnapshot[];
|
|
78
79
|
/** Put `entry` in place of a tenant's dormant memory `id`, keeping when and why that one went dormant. */
|
|
79
80
|
export declare function replaceDormantEntry(db: DatabaseSyncLike, tenantId: string, id: string, entry: MemoryEntry): void;
|
|
80
|
-
/** Whether a tenant has a dormant memory with this id (snapshot readable or not). */
|
|
81
|
-
export declare function hasDormantRow(db: DatabaseSyncLike, tenantId: string, id: string): boolean;
|
|
81
|
+
/** Whether a tenant has a dormant memory with this id whose scope `admit` passes (snapshot readable or not). */
|
|
82
|
+
export declare function hasDormantRow(db: DatabaseSyncLike, tenantId: string, id: string, admit?: SqlFragment): boolean;
|
|
82
83
|
/** Delete a tenant's dormant memory. Returns false when there was none. */
|
|
83
84
|
export declare function deleteDormantRow(db: DatabaseSyncLike, tenantId: string, id: string): boolean;
|
|
84
|
-
/**
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
export declare function
|
|
92
|
-
/**
|
|
93
|
-
* Delete every dormant memory (all tenants) that went dormant before
|
|
94
|
-
* `cutoffIso`: the `dormant.retentionDays` window. Returns how many went.
|
|
95
|
-
*/
|
|
96
|
-
export declare function purgeExpiredDormant(db: DatabaseSyncLike, cutoffIso: string): number;
|
|
85
|
+
/** Deletes the tenant's dormant copies of a rejected digest whose snapshot scope `inReach` admits, so the value cannot linger; returns their ids. */
|
|
86
|
+
export declare function purgeDormantByDigest(db: DatabaseSyncLike, tenantId: string, digest: string, inReach: (scope: string | null) => boolean): string[];
|
|
87
|
+
export interface DormantKey {
|
|
88
|
+
readonly tenantId: string;
|
|
89
|
+
readonly id: string;
|
|
90
|
+
}
|
|
91
|
+
export declare function expiredDormantKeys(db: DatabaseSyncLike, cutoffIso: string): DormantKey[];
|
|
92
|
+
export declare function deleteExpiredDormantRow(db: DatabaseSyncLike, key: DormantKey, cutoffIso: string): number;
|
|
97
93
|
//# sourceMappingURL=dormant.d.ts.map
|
package/dist/dormant.js
CHANGED
|
@@ -49,8 +49,11 @@ function rowToDormantMemory(row) {
|
|
|
49
49
|
dormantAt: row.dormant_at,
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
-
|
|
53
|
-
|
|
52
|
+
// The snapshot's scope as a `scope` column, so recall-scope's SQL fragments read it as they read memories.scope.
|
|
53
|
+
const DORMANT_WITH_SCOPE = `(SELECT *, CASE WHEN json_valid(entry_json) THEN json_extract(entry_json, '$.scope') END AS scope FROM dormant_memories)`;
|
|
54
|
+
const EVERY_SCOPE = { sql: '1', params: [] };
|
|
55
|
+
/** A tenant's dormant memories whose scope `admit` passes, newest first, optionally filtered by search terms. */
|
|
56
|
+
export function listDormantRows(db, tenantId, opts = {}, admit = EVERY_SCOPE) {
|
|
54
57
|
const terms = (opts.query ?? '').trim().split(/\s+/).filter((t) => t.length > 0);
|
|
55
58
|
const limit = opts.limit !== undefined && Number.isFinite(opts.limit) && opts.limit >= 1
|
|
56
59
|
? Math.floor(opts.limit)
|
|
@@ -58,10 +61,10 @@ export function listDormantRows(db, tenantId, opts = {}) {
|
|
|
58
61
|
const termClauses = terms.map(() => ` AND content LIKE ? ESCAPE '\\'`).join('');
|
|
59
62
|
// SAFETY: rows' shape matches the seven columns named in the SELECT.
|
|
60
63
|
const rows = db.prepare(`SELECT tenant_id, id, content, entry_json, reason, strength, dormant_at
|
|
61
|
-
FROM
|
|
62
|
-
WHERE tenant_id =
|
|
64
|
+
FROM ${DORMANT_WITH_SCOPE}
|
|
65
|
+
WHERE tenant_id = ? AND ${admit.sql}${termClauses}
|
|
63
66
|
ORDER BY dormant_at DESC, id ASC
|
|
64
|
-
LIMIT ?`).all(tenantId, ...terms.map((t) => `%${escapeLike(t)}%`), limit);
|
|
67
|
+
LIMIT ?`).all(tenantId, ...admit.params, ...terms.map((t) => `%${escapeLike(t)}%`), limit);
|
|
65
68
|
return rows.map(rowToDormantMemory);
|
|
66
69
|
}
|
|
67
70
|
/**
|
|
@@ -99,45 +102,36 @@ export function replaceDormantEntry(db, tenantId, id, entry) {
|
|
|
99
102
|
db.prepare(`UPDATE dormant_memories SET id = ?, content = ?, entry_json = ? WHERE tenant_id = ? AND id = ?`)
|
|
100
103
|
.run(entry.id, entry.content, JSON.stringify(entry), tenantId, id);
|
|
101
104
|
}
|
|
102
|
-
/** Whether a tenant has a dormant memory with this id (snapshot readable or not). */
|
|
103
|
-
export function hasDormantRow(db, tenantId, id) {
|
|
104
|
-
return db.prepare(`SELECT 1 FROM
|
|
105
|
+
/** Whether a tenant has a dormant memory with this id whose scope `admit` passes (snapshot readable or not). */
|
|
106
|
+
export function hasDormantRow(db, tenantId, id, admit = EVERY_SCOPE) {
|
|
107
|
+
return db.prepare(`SELECT 1 FROM ${DORMANT_WITH_SCOPE} WHERE tenant_id = ? AND id = ? AND ${admit.sql}`).get(tenantId, id, ...admit.params) !== undefined;
|
|
105
108
|
}
|
|
106
109
|
/** Delete a tenant's dormant memory. Returns false when there was none. */
|
|
107
110
|
export function deleteDormantRow(db, tenantId, id) {
|
|
108
111
|
const result = db.prepare(`DELETE FROM dormant_memories WHERE tenant_id = ? AND id = ?`).run(tenantId, id);
|
|
109
112
|
return Number(result.changes ?? 0) > 0;
|
|
110
113
|
}
|
|
111
|
-
/**
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
export function purgeDormantByDigest(db, tenantId, digest) {
|
|
117
|
-
// SAFETY: rows' shape matches the two columns named in the SELECT.
|
|
118
|
-
const rows = db.prepare(`SELECT id, content FROM dormant_memories WHERE tenant_id = ?`)
|
|
119
|
-
.all(tenantId);
|
|
114
|
+
/** Deletes the tenant's dormant copies of a rejected digest whose snapshot scope `inReach` admits, so the value cannot linger; returns their ids. */
|
|
115
|
+
export function purgeDormantByDigest(db, tenantId, digest, inReach) {
|
|
116
|
+
// SAFETY: rows' shape matches the three columns named in the SELECT.
|
|
117
|
+
const rows = db.prepare(`SELECT id, content, CASE WHEN json_valid(entry_json) THEN json_extract(entry_json, '$.scope') END AS scope
|
|
118
|
+
FROM dormant_memories WHERE tenant_id = ?`).all(tenantId);
|
|
120
119
|
const removed = [];
|
|
121
120
|
for (const row of rows) {
|
|
122
|
-
if (rejectionDigest(row.content) !== digest)
|
|
121
|
+
if (rejectionDigest(row.content) !== digest || !inReach(row.scope))
|
|
123
122
|
continue;
|
|
124
123
|
deleteDormantRow(db, tenantId, row.id);
|
|
125
124
|
removed.push(row.id);
|
|
126
125
|
}
|
|
127
126
|
return removed;
|
|
128
127
|
}
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
// SAFETY:
|
|
132
|
-
|
|
133
|
-
return Number(row.n);
|
|
128
|
+
export function expiredDormantKeys(db, cutoffIso) {
|
|
129
|
+
const sql = `SELECT tenant_id AS tenantId, id FROM dormant_memories WHERE dormant_at < ?`;
|
|
130
|
+
// SAFETY: rows' shape matches the two columns named in the SELECT.
|
|
131
|
+
return db.prepare(sql).all(cutoffIso);
|
|
134
132
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
*/
|
|
139
|
-
export function purgeExpiredDormant(db, cutoffIso) {
|
|
140
|
-
const result = db.prepare(`DELETE FROM dormant_memories WHERE dormant_at < ?`).run(cutoffIso);
|
|
141
|
-
return Number(result.changes ?? 0);
|
|
133
|
+
export function deleteExpiredDormantRow(db, key, cutoffIso) {
|
|
134
|
+
const sql = `DELETE FROM dormant_memories WHERE tenant_id = ? AND id = ? AND dormant_at < ?`;
|
|
135
|
+
return Number(db.prepare(sql).run(key.tenantId, key.id, cutoffIso).changes ?? 0);
|
|
142
136
|
}
|
|
143
137
|
//# sourceMappingURL=dormant.js.map
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
* egress-blocked in the build sandbox).
|
|
28
28
|
*/
|
|
29
29
|
import { envByName } from './env.js';
|
|
30
|
-
import { getEmbedding, isEmbeddingAvailable, resolveEmbeddingModel, DEFAULT_EMBEDDING_MODEL, } from './local-embedding.js';
|
|
30
|
+
import { getEmbedding, isEmbeddingAvailable, requireLocalPipeline, resolveEmbeddingModel, DEFAULT_EMBEDDING_MODEL, } from './local-embedding.js';
|
|
31
31
|
import { loadConfig } from './config.js';
|
|
32
32
|
import { redactSecretsStrict } from './secret-detect.js';
|
|
33
33
|
import { fetchWithRetry } from './http-retry.js';
|
|
@@ -57,8 +57,9 @@ class LocalEmbeddingProvider {
|
|
|
57
57
|
return this.enabled && isEmbeddingAvailable();
|
|
58
58
|
}
|
|
59
59
|
async embed(texts, role) {
|
|
60
|
-
//
|
|
61
|
-
|
|
60
|
+
// A model that cannot load fails the call, as an API outage does; items then run one at a time on the shared pipeline.
|
|
61
|
+
if (texts.length > 0)
|
|
62
|
+
await requireLocalPipeline(this.model);
|
|
62
63
|
const out = [];
|
|
63
64
|
for (const text of texts) {
|
|
64
65
|
out.push(await getEmbedding(text, this.model, role));
|