hippo-memory 1.63.1 → 1.64.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 +13 -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/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/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/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/graph-extract.js
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Populates the graph from the already-structured consolidated first-class object tables -
|
|
5
5
|
* NO NLP, no precision gate for entities + supersedes. The graph is a pure derived
|
|
6
|
-
* function of the current object state, so `extractGraph` is an idempotent REBUILD:
|
|
7
|
-
*
|
|
6
|
+
* function of the current object state, so `extractGraph` is an idempotent REBUILD: derive
|
|
7
|
+
* entities + `supersedes` relations from decisions /
|
|
8
8
|
* policies / customer_notes / project_briefs (the four object types whose kind maps to the
|
|
9
|
-
* `entity_type` enum)
|
|
10
|
-
*
|
|
9
|
+
* `entity_type` enum), then write only their difference from the stored rows. All writes go
|
|
10
|
+
* through the src/graph/write.ts consolidated-source guard; this module issues no raw SQL.
|
|
11
11
|
*
|
|
12
12
|
* Pass 3 adds the
|
|
13
13
|
* first CROSS-OBJECT relations: a deterministic NAME-MATCH heuristic that emits a
|
|
@@ -19,8 +19,11 @@
|
|
|
19
19
|
* skill/incident/process entities (not in the entity_type enum - needs a migration); the
|
|
20
20
|
* `hippo sleep` enqueue-hook.
|
|
21
21
|
*/
|
|
22
|
-
import {
|
|
22
|
+
import { applyGraphOps, runGraphRebuildTransaction } from './graph/write.js';
|
|
23
23
|
import { MAX_ENTITY_NAME_LEN } from './graph/types.js';
|
|
24
|
+
import { graphDelta, readGraphDelta } from './graph/delta.js';
|
|
25
|
+
import { WRITE_BUDGET } from './write-budget.js';
|
|
26
|
+
import { SLEEP_DB_WAIT_MS } from './db.js';
|
|
24
27
|
import { loadDecisions } from './decisions.js';
|
|
25
28
|
import { loadPolicies } from './policies.js';
|
|
26
29
|
import { loadCustomerNotes } from './customer-notes.js';
|
|
@@ -64,7 +67,7 @@ function sourceObjectOf(entityType, e2Id) {
|
|
|
64
67
|
throw new Error(`graph-extract: entityType '${entityType}' has no source_object_type mapping`);
|
|
65
68
|
return { type, id: e2Id };
|
|
66
69
|
}
|
|
67
|
-
/** Unordered entity-
|
|
70
|
+
/** Unordered entity-key pair, so a relation between a,b is found in either direction. */
|
|
68
71
|
function pairKey(a, b) {
|
|
69
72
|
return a < b ? `${a}:${b}` : `${b}:${a}`;
|
|
70
73
|
}
|
|
@@ -88,7 +91,7 @@ textOf) {
|
|
|
88
91
|
hitCap = true;
|
|
89
92
|
for (const r of loaded) {
|
|
90
93
|
// SAFETY: loadFn is always one of loadDecisions/loadPolicies/loadCustomerNotes/
|
|
91
|
-
// loadProjectBriefs (wired
|
|
94
|
+
// loadProjectBriefs (wired in GRAPH_SOURCES); every one of their row
|
|
92
95
|
// types declares id: number, memoryId: string | null, supersededBy: number | null.
|
|
93
96
|
// `any` here is the deliberate type-erasure boundary (see eslint-disable above)
|
|
94
97
|
// that lets one loop handle all four object row shapes.
|
|
@@ -102,144 +105,97 @@ textOf) {
|
|
|
102
105
|
});
|
|
103
106
|
}
|
|
104
107
|
}
|
|
105
|
-
return { rows, hitCap };
|
|
108
|
+
return { entityType, rows, hitCap };
|
|
106
109
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// and a throw mid-rebuild rolls back the clear (no bricked graph). No second
|
|
131
|
-
// connection is opened inside.
|
|
132
|
-
return runGraphRebuildTransaction(hippoRoot, tenantId, (txDb) => rebuildGraphRows(txDb, hippoRoot, tenantId, loaded));
|
|
110
|
+
const GRAPH_SOURCES = [
|
|
111
|
+
{ entityType: 'decision', loadFn: loadDecisions, nameOf: (r) => r.decisionText, textOf: (r) => [r.decisionText, r.context].filter(Boolean).join(' ') },
|
|
112
|
+
{ entityType: 'policy', loadFn: loadPolicies, nameOf: (r) => r.policyName, textOf: (r) => [r.policyName, r.policyText].filter(Boolean).join(' ') },
|
|
113
|
+
{ entityType: 'customer', loadFn: loadCustomerNotes, nameOf: (r) => r.customer, textOf: (r) => [r.customer, r.note].filter(Boolean).join(' ') },
|
|
114
|
+
{ entityType: 'project', loadFn: loadProjectBriefs, nameOf: (r) => r.repo, textOf: (r) => [r.repo, r.summary].filter(Boolean).join(' ') },
|
|
115
|
+
];
|
|
116
|
+
/** Every source type's rows, each read on its own connection; callers load before any write lock is taken. */
|
|
117
|
+
export function loadGraphSources(hippoRoot, tenantId) {
|
|
118
|
+
assertTenantId('loadGraphSources', tenantId);
|
|
119
|
+
return GRAPH_SOURCES.map((src) => loadType(hippoRoot, tenantId, src.entityType, src.loadFn, src.nameOf, src.textOf));
|
|
120
|
+
}
|
|
121
|
+
/** The tenant's graph as a pure function of its loaded objects; the write side diffs it against what is stored. */
|
|
122
|
+
export function deriveGraph(loaded) {
|
|
123
|
+
const pass = deriveEntities(loaded);
|
|
124
|
+
const supersedes = deriveSupersedes(pass);
|
|
125
|
+
const references = deriveReferences(pass.created, supersedes.supersededPairs, pass.truncated);
|
|
126
|
+
return {
|
|
127
|
+
entities: pass.created.map((e) => ({ entityType: e.entityType, sourceObject: e.sourceObject, name: e.name, memoryId: e.memoryId })),
|
|
128
|
+
relations: [...supersedes.relations, ...references],
|
|
129
|
+
byType: pass.byType,
|
|
130
|
+
truncated: pass.truncated,
|
|
131
|
+
references: references.length,
|
|
132
|
+
};
|
|
133
133
|
}
|
|
134
|
-
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
const pass = { byType: {}, truncated: [], allRows: [], entityIdByKey: new Map(), memoryIdByKey: new Map(), created: [] };
|
|
134
|
+
const naturalKeyOf = (e) => ({ entityType: e.entityType, sourceObject: e.sourceObject });
|
|
135
|
+
// Pass 1: every active or superseded object row becomes an entity anchored to its object, so it survives a
|
|
136
|
+
// forgotten mirror; the mirror memory rides along only while it exists.
|
|
137
|
+
function deriveEntities(loaded) {
|
|
138
|
+
const pass = { byType: {}, truncated: [], allRows: [], byKey: new Map(), created: [] };
|
|
140
139
|
for (const { entityType, rows, hitCap } of loaded) {
|
|
141
140
|
if (hitCap)
|
|
142
141
|
pass.truncated.push(entityType);
|
|
143
142
|
pass.byType[entityType] = 0;
|
|
144
143
|
for (const row of rows) {
|
|
145
144
|
pass.allRows.push(row);
|
|
146
|
-
//
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (name.length === 0)
|
|
145
|
+
// Names are uncapped at source; trimming on both sides of the cap gives the exact name insertEntity stores.
|
|
146
|
+
const name = (row.name ?? '').trim().slice(0, MAX_ENTITY_NAME_LEN).trim();
|
|
147
|
+
const key = keyOf(row.entityType, row.e2Id);
|
|
148
|
+
// A row seen twice was superseded between the two status loads; its first sighting stands.
|
|
149
|
+
if (name.length === 0 || pass.byKey.has(key))
|
|
151
150
|
continue;
|
|
152
|
-
const
|
|
153
|
-
const entity = insertEntity(hippoRoot, tenantId, {
|
|
151
|
+
const entity = {
|
|
154
152
|
entityType: row.entityType,
|
|
153
|
+
sourceObject: sourceObjectOf(row.entityType, row.e2Id),
|
|
155
154
|
name,
|
|
156
155
|
memoryId: row.memoryId,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
pass.
|
|
162
|
-
pass.created.push(
|
|
156
|
+
key,
|
|
157
|
+
searchText: row.searchText ?? '',
|
|
158
|
+
superseded: row.supersededBy !== null,
|
|
159
|
+
};
|
|
160
|
+
pass.byKey.set(key, entity);
|
|
161
|
+
pass.created.push(entity);
|
|
163
162
|
pass.byType[entityType] += 1;
|
|
164
163
|
}
|
|
165
164
|
}
|
|
166
165
|
return pass;
|
|
167
166
|
}
|
|
168
|
-
// "Y supersedes X"
|
|
169
|
-
//
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
let relations = 0;
|
|
174
|
-
// Entity-id pairs already related by supersedes (unordered). Pass 3 skips a references
|
|
175
|
-
// edge for such a pair: a version-extends-its-predecessor's-name containment (e.g.
|
|
176
|
-
// "Adopt X (managed)" contains "Adopt X") is a name artifact, not a cross-reference,
|
|
177
|
-
// and supersedes already captures their relationship.
|
|
167
|
+
// Pass 2: "Y supersedes X" when both were derived (Y may be closed and absent). The edge is anchored to the
|
|
168
|
+
// successor's object and carries its mirror memory only while that lives.
|
|
169
|
+
function deriveSupersedes(pass) {
|
|
170
|
+
const relations = [];
|
|
171
|
+
// Pass 3 skips these pairs: "Adopt X (managed)" containing "Adopt X" is a version, not a cross-reference.
|
|
178
172
|
const supersededPairs = new Set();
|
|
179
173
|
for (const row of pass.allRows) {
|
|
180
174
|
if (row.supersededBy === null)
|
|
181
175
|
continue;
|
|
182
|
-
const
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
const toId = pass.entityIdByKey.get(xKey); // superseded X
|
|
186
|
-
if (fromId === undefined || toId === undefined)
|
|
176
|
+
const successor = pass.byKey.get(keyOf(row.entityType, row.supersededBy));
|
|
177
|
+
const superseded = pass.byKey.get(keyOf(row.entityType, row.e2Id));
|
|
178
|
+
if (!successor || !superseded)
|
|
187
179
|
continue;
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
toEntityId: toId,
|
|
180
|
+
relations.push({
|
|
181
|
+
from: naturalKeyOf(successor),
|
|
182
|
+
to: naturalKeyOf(superseded),
|
|
192
183
|
relType: 'supersedes',
|
|
193
|
-
memoryId:
|
|
194
|
-
sourceObject:
|
|
195
|
-
}
|
|
196
|
-
supersededPairs.add(pairKey(
|
|
197
|
-
relations += 1;
|
|
184
|
+
memoryId: successor.memoryId,
|
|
185
|
+
sourceObject: successor.sourceObject,
|
|
186
|
+
});
|
|
187
|
+
supersededPairs.add(pairKey(successor.key, superseded.key));
|
|
198
188
|
}
|
|
199
189
|
return { relations, supersededPairs };
|
|
200
190
|
}
|
|
201
|
-
/**
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
* re-derives entities + `supersedes` + `references` from the preloaded rows. All
|
|
205
|
-
* DB access here is on `txDb` (or in-memory) — no other connection is opened.
|
|
206
|
-
*/
|
|
207
|
-
function rebuildGraphRows(txDb, hippoRoot, tenantId, loaded) {
|
|
208
|
-
// Rebuild from scratch: the graph is derived, so clear then re-derive.
|
|
209
|
-
clearGraph(hippoRoot, tenantId, txDb);
|
|
210
|
-
const pass = insertEntityRows(txDb, hippoRoot, tenantId, loaded);
|
|
211
|
-
const { byType, truncated, created } = pass;
|
|
212
|
-
const supersedes = insertSupersedesRelations(txDb, hippoRoot, tenantId, pass);
|
|
213
|
-
let relations = supersedes.relations;
|
|
214
|
-
// Pass 3: cross-object `references` edges via conservative name matching. A source's
|
|
215
|
-
// text containing a target entity's name -> "source references target". Sources +
|
|
216
|
-
// targets are CREATED entities only, each anchored to its source object (so the
|
|
217
|
-
// edge survives a forgotten source mirror).
|
|
218
|
-
const references = extractReferences(hippoRoot, tenantId, created, supersedes.supersededPairs, truncated, txDb);
|
|
219
|
-
relations += references;
|
|
220
|
-
const entities = created.length;
|
|
221
|
-
return { entities, relations, references, byType, truncated };
|
|
222
|
-
}
|
|
223
|
-
/**
|
|
224
|
-
* Pass 3. Build a target-name index from the created entities (names within the length
|
|
225
|
-
* bounds, ambiguous names dropped), scan each created entity's text once with one
|
|
226
|
-
* combined word-boundary regex, and emit `references` edges (self-skipped, deduped,
|
|
227
|
-
* per-source capped). Each edge is anchored to the source entity's object (memoryId
|
|
228
|
-
* passed through only when the mirror lives). Returns the number of references edges written.
|
|
229
|
-
*/
|
|
230
|
-
function extractReferences(hippoRoot, tenantId, created, supersededPairs, truncated, txDb) {
|
|
231
|
-
// Build the target index: normalised name -> single entity id. A name is a target only
|
|
232
|
-
// if its length is in bounds; a name shared by >1 entity is AMBIGUOUS and dropped.
|
|
233
|
-
const nameToId = new Map();
|
|
191
|
+
/** Normalised name to its single target; a name two entities share is ambiguous and dropped. */
|
|
192
|
+
function referenceTargets(created) {
|
|
193
|
+
const byName = new Map();
|
|
234
194
|
const ambiguous = new Set();
|
|
235
195
|
for (const e of created) {
|
|
236
|
-
// References are among ACTIVE entities only: a superseded (outdated) row is not a
|
|
237
|
-
// current cross-reference target.
|
|
238
196
|
if (e.superseded)
|
|
239
197
|
continue;
|
|
240
|
-
// Decisions are
|
|
241
|
-
// not by name-mention. Excluding them as targets also prevents a decision's own name
|
|
242
|
-
// (== its searchText) from whole-string self-matching and shadowing embedded targets.
|
|
198
|
+
// Decisions are sources only: their name is their prose, which would self-match and shadow embedded targets.
|
|
243
199
|
if (e.entityType === 'decision')
|
|
244
200
|
continue;
|
|
245
201
|
const norm = e.name.trim().toLowerCase();
|
|
@@ -247,59 +203,86 @@ function extractReferences(hippoRoot, tenantId, created, supersededPairs, trunca
|
|
|
247
203
|
continue;
|
|
248
204
|
if (ambiguous.has(norm))
|
|
249
205
|
continue;
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
continue;
|
|
206
|
+
const seen = byName.get(norm);
|
|
207
|
+
if (seen === undefined)
|
|
208
|
+
byName.set(norm, e);
|
|
209
|
+
else if (seen.key !== e.key) {
|
|
210
|
+
byName.delete(norm);
|
|
211
|
+
ambiguous.add(norm);
|
|
257
212
|
}
|
|
258
|
-
nameToId.set(norm, e.entityId);
|
|
259
213
|
}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
214
|
+
return byName;
|
|
215
|
+
}
|
|
216
|
+
// Pass 3: a `references` edge when one object's text names another entity, matched on word boundaries,
|
|
217
|
+
// self-skipped, deduped and capped per source; the edge is anchored to the source object.
|
|
218
|
+
function deriveReferences(created, supersededPairs, truncated) {
|
|
219
|
+
const byName = referenceTargets(created);
|
|
220
|
+
if (byName.size === 0)
|
|
221
|
+
return [];
|
|
222
|
+
if (byName.size > MAX_TARGET_NAMES)
|
|
265
223
|
truncated.push('references-targets');
|
|
266
|
-
//
|
|
267
|
-
|
|
268
|
-
// (`postgres pro` wins over `postgres`). Deterministic, so truncation is stable.
|
|
269
|
-
const targetNames = [...nameToId.keys()]
|
|
224
|
+
// Longest first, then alphabetical: regex alternation is leftmost-first, so `postgres pro` beats `postgres`.
|
|
225
|
+
const targetNames = [...byName.keys()]
|
|
270
226
|
.sort((a, b) => b.length - a.length || (a < b ? -1 : a > b ? 1 : 0))
|
|
271
227
|
.slice(0, MAX_TARGET_NAMES);
|
|
272
|
-
const
|
|
273
|
-
const
|
|
274
|
-
let references = 0;
|
|
228
|
+
const re = new RegExp(`\\b(?:${targetNames.map(escapeRegex).join('|')})\\b`, 'gi');
|
|
229
|
+
const relations = [];
|
|
275
230
|
for (const src of created) {
|
|
276
|
-
if (src.superseded)
|
|
277
|
-
continue; // superseded sources hold only stale references
|
|
278
|
-
if (!src.searchText)
|
|
231
|
+
if (src.superseded || !src.searchText)
|
|
279
232
|
continue;
|
|
280
|
-
const targets = new
|
|
233
|
+
const targets = new Map();
|
|
281
234
|
for (const m of src.searchText.matchAll(re)) {
|
|
282
|
-
const
|
|
283
|
-
if (
|
|
284
|
-
continue;
|
|
285
|
-
if (supersededPairs.has(pairKey(src.
|
|
286
|
-
continue;
|
|
287
|
-
targets.
|
|
235
|
+
const target = byName.get(m[0].toLowerCase());
|
|
236
|
+
if (target === undefined || target.key === src.key)
|
|
237
|
+
continue;
|
|
238
|
+
if (supersededPairs.has(pairKey(src.key, target.key)))
|
|
239
|
+
continue;
|
|
240
|
+
targets.set(target.key, target);
|
|
288
241
|
if (targets.size >= MAX_REFERENCES_PER_OBJECT)
|
|
289
|
-
break;
|
|
242
|
+
break;
|
|
290
243
|
}
|
|
291
|
-
for (const
|
|
292
|
-
|
|
293
|
-
fromEntityId: src.entityId,
|
|
294
|
-
toEntityId: targetId,
|
|
295
|
-
relType: 'references',
|
|
296
|
-
// Anchored to the source object; its mirror memory is passed only when it lives.
|
|
297
|
-
memoryId: src.memoryId,
|
|
298
|
-
sourceObject: src.sourceObject,
|
|
299
|
-
}, txDb);
|
|
300
|
-
references += 1;
|
|
244
|
+
for (const target of targets.values()) {
|
|
245
|
+
relations.push({ from: naturalKeyOf(src), to: naturalKeyOf(target), relType: 'references', memoryId: src.memoryId, sourceObject: src.sourceObject });
|
|
301
246
|
}
|
|
302
247
|
}
|
|
303
|
-
return
|
|
248
|
+
return relations;
|
|
249
|
+
}
|
|
250
|
+
function resultOf(derived, skipped) {
|
|
251
|
+
const { entities, relations, references, byType, truncated } = derived;
|
|
252
|
+
const result = { entities: entities.length, relations: relations.length, references, byType, truncated };
|
|
253
|
+
if (skipped > 0)
|
|
254
|
+
result.skipped = skipped;
|
|
255
|
+
return result;
|
|
256
|
+
}
|
|
257
|
+
/** Idempotent rebuild of the tenant's graph from its consolidated objects, in one transaction; safe to re-run.
|
|
258
|
+
* Returns the derived counts and which types hit the per-type cap. */
|
|
259
|
+
export function extractGraph(hippoRoot, tenantId) {
|
|
260
|
+
assertTenantId('extractGraph', tenantId);
|
|
261
|
+
// Loaded first: opening a second connection under the rebuild's BEGIN IMMEDIATE dead-locks ('database is locked').
|
|
262
|
+
const derived = deriveGraph(loadGraphSources(hippoRoot, tenantId));
|
|
263
|
+
// Diffed and applied under one lock, so two concurrent rebuilds serialize instead of both inserting a missing row.
|
|
264
|
+
const skipped = runGraphRebuildTransaction(hippoRoot, tenantId, (txDb) => applyGraphOps(txDb, hippoRoot, tenantId, graphDelta(txDb, tenantId, derived), 0, { holdMs: Infinity }).skipped);
|
|
265
|
+
return resultOf(derived, skipped);
|
|
266
|
+
}
|
|
267
|
+
/** extractGraph for sleep: diffs outside the lock, then applies in transactions of about `budget.holdMs` with a pause
|
|
268
|
+
* between them. A run stopped between chunks leaves a mixed graph that the next run's diff finishes. */
|
|
269
|
+
export async function extractGraphChunked(hippoRoot, tenantId, budget = WRITE_BUDGET) {
|
|
270
|
+
assertTenantId('extractGraphChunked', tenantId);
|
|
271
|
+
const derived = deriveGraph(loadGraphSources(hippoRoot, tenantId));
|
|
272
|
+
const ops = readGraphDelta(hippoRoot, tenantId, derived);
|
|
273
|
+
let next = 0;
|
|
274
|
+
let skipped = 0;
|
|
275
|
+
let committedAt = 0;
|
|
276
|
+
while (next < ops.length) {
|
|
277
|
+
if (next > 0)
|
|
278
|
+
await budget.pause(committedAt);
|
|
279
|
+
const from = next;
|
|
280
|
+
// Sleep's wait, not a server request's 250 ms: each chunk is a fresh BEGIN IMMEDIATE a hook's write could otherwise fail.
|
|
281
|
+
const chunk = runGraphRebuildTransaction(hippoRoot, tenantId, (txDb) => applyGraphOps(txDb, hippoRoot, tenantId, ops, from, { holdMs: budget.holdMs, clock: budget.clock }), { busyWaitMs: SLEEP_DB_WAIT_MS });
|
|
282
|
+
committedAt = budget.clock();
|
|
283
|
+
next = chunk.next;
|
|
284
|
+
skipped += chunk.skipped;
|
|
285
|
+
}
|
|
286
|
+
return resultOf(derived, skipped);
|
|
304
287
|
}
|
|
305
288
|
//# sourceMappingURL=graph-extract.js.map
|
package/dist/graph-recall.d.ts
CHANGED
package/dist/graph-recall.js
CHANGED
|
@@ -137,8 +137,8 @@ function passesRecallFilters(mem, via, successorValidFrom, opts) {
|
|
|
137
137
|
return false; // default recall drops superseded
|
|
138
138
|
}
|
|
139
139
|
return recallScope.additive
|
|
140
|
-
? passesCliRecallScopeFilter(mem.scope ?? null, recallScope.requested)
|
|
141
|
-
: passesScopeFilterForRecall(mem.scope ?? null, recallScope.requested);
|
|
140
|
+
? passesCliRecallScopeFilter(mem.scope ?? null, recallScope.requested) || passesScopeFilterForRecall(mem.scope ?? null, undefined, recallScope.ownScope)
|
|
141
|
+
: passesScopeFilterForRecall(mem.scope ?? null, recallScope.requested, recallScope.ownScope);
|
|
142
142
|
}
|
|
143
143
|
/** Traverse one store's graph from its seeds into `hitsByOrigin`. Pure reads; mutates `seenMemoryIds`
|
|
144
144
|
* and `seenContent` so a memory, or a share/promote copy of it, surfaces at most once across stores. */
|
package/dist/graph-view.d.ts
CHANGED
|
@@ -36,6 +36,7 @@ export declare const DEFAULT_VIEW_LIMIT = 500;
|
|
|
36
36
|
export declare function buildGraphModel(hippoRoot: string, tenantId: string, opts?: {
|
|
37
37
|
entity?: string;
|
|
38
38
|
limit?: number;
|
|
39
|
+
canRead?: (scope: string | null) => boolean;
|
|
39
40
|
}): GraphModel;
|
|
40
41
|
/**
|
|
41
42
|
* Deterministic Fruchterman-Reingold-style force layout. Seeded circular init +
|
package/dist/graph-view.js
CHANGED
|
@@ -10,6 +10,25 @@
|
|
|
10
10
|
import { loadEntities, loadEntitiesByName, loadEntitiesByIds, loadRelations, loadNeighborRelations, loadRelationsAmong, withGraphReadSnapshot, } from './graph/read.js';
|
|
11
11
|
/** Default bound for the CLI viewer / show so an unbounded set is never laid out. */
|
|
12
12
|
export const DEFAULT_VIEW_LIMIT = 500;
|
|
13
|
+
// graph/read.ts's IN-list chunk, so one statement stays far under SQLite's bound-parameter limit.
|
|
14
|
+
const MEMORY_ID_CHUNK = 400;
|
|
15
|
+
/** The items whose source memory `canRead` admits: a NULL memoryId has no scope to hide, and a missing memory fails closed. */
|
|
16
|
+
function readableOnly(db, tenantId, items, canRead) {
|
|
17
|
+
if (!canRead)
|
|
18
|
+
return items;
|
|
19
|
+
const ids = [...new Set(items.flatMap((i) => (i.memoryId === null ? [] : [i.memoryId])))];
|
|
20
|
+
const readable = new Set();
|
|
21
|
+
for (let i = 0; i < ids.length; i += MEMORY_ID_CHUNK) {
|
|
22
|
+
const slice = ids.slice(i, i + MEMORY_ID_CHUNK);
|
|
23
|
+
// SAFETY: rows' shape matches the id and scope columns named in the SELECT.
|
|
24
|
+
const rows = db.prepare(`SELECT id, scope FROM memories WHERE tenant_id = ? AND id IN (${slice.map(() => '?').join(',')})`)
|
|
25
|
+
.all(tenantId, ...slice);
|
|
26
|
+
for (const r of rows)
|
|
27
|
+
if (canRead(r.scope))
|
|
28
|
+
readable.add(r.id);
|
|
29
|
+
}
|
|
30
|
+
return items.filter((i) => i.memoryId === null || readable.has(i.memoryId));
|
|
31
|
+
}
|
|
13
32
|
/**
|
|
14
33
|
* Build the view-model from the graph (reads only). With `opts.entity`, returns a
|
|
15
34
|
* focus subgraph: every entity whose name === entity, plus their 1-hop neighbours,
|
|
@@ -32,7 +51,9 @@ export function buildGraphModel(hippoRoot, tenantId, opts = {}) {
|
|
|
32
51
|
// cap the focus matches. (3) Discover 1-hop neighbours and cap the UNION to
|
|
33
52
|
// `limit` nodes. (4) Load ALL edges AMONG the union so neighbour-to-neighbour
|
|
34
53
|
// edges that don't touch the focus are included too.
|
|
35
|
-
const
|
|
54
|
+
const named = loadEntitiesByName(hippoRoot, tenantId, opts.entity, { limit }, db);
|
|
55
|
+
// A hidden focus must not answer through its neighbours that the name exists.
|
|
56
|
+
const focus = readableOnly(db, tenantId, named, opts.canRead);
|
|
36
57
|
if (focus.length === 0)
|
|
37
58
|
return { nodes: [], edges: [], truncated: false };
|
|
38
59
|
const focusIds = focus.map((e) => e.id);
|
|
@@ -54,7 +75,7 @@ export function buildGraphModel(hippoRoot, tenantId, opts = {}) {
|
|
|
54
75
|
// edge in favour of out-of-union rows.
|
|
55
76
|
relations = loadRelationsAmong(hippoRoot, tenantId, unionIds, { limit }, db);
|
|
56
77
|
truncated =
|
|
57
|
-
|
|
78
|
+
named.length >= limit ||
|
|
58
79
|
hop.length >= limit || // neighbour scan capped -> a 1-hop neighbour may be omitted
|
|
59
80
|
neighboursCapped || // node cap filled before all neighbours were consumed
|
|
60
81
|
union.size > unionIds.length ||
|
|
@@ -65,6 +86,8 @@ export function buildGraphModel(hippoRoot, tenantId, opts = {}) {
|
|
|
65
86
|
relations = loadRelations(hippoRoot, tenantId, { limit }, db);
|
|
66
87
|
truncated = nodeEntities.length >= limit || relations.length >= limit;
|
|
67
88
|
}
|
|
89
|
+
nodeEntities = readableOnly(db, tenantId, nodeEntities, opts.canRead);
|
|
90
|
+
relations = readableOnly(db, tenantId, relations, opts.canRead);
|
|
68
91
|
const nodeIds = new Set(nodeEntities.map((e) => e.id));
|
|
69
92
|
const nodes = nodeEntities.map((e) => ({
|
|
70
93
|
id: e.id,
|
|
@@ -169,7 +169,11 @@ function writePlan(db, plan, old, move) {
|
|
|
169
169
|
const byTenant = new Map();
|
|
170
170
|
for (const e of plan) {
|
|
171
171
|
update.run(e.half_life_days, e.id);
|
|
172
|
-
|
|
172
|
+
// One record per tenant, filled in place: copying it per row made the write grow with the square of the store.
|
|
173
|
+
let record = byTenant.get(e.tenantId);
|
|
174
|
+
if (!record)
|
|
175
|
+
byTenant.set(e.tenantId, (record = {}));
|
|
176
|
+
record[e.id] = old.get(e.id);
|
|
173
177
|
}
|
|
174
178
|
for (const [tenantId, oldHalfLives] of byTenant) {
|
|
175
179
|
appendAuditEvent(db, { tenantId, actor: move.actor, op: 'half_life_migrate', metadata: { from: move.from, to: move.to, ids: Object.keys(oldHalfLives), oldHalfLives } });
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// A handoff's git evidence, read with no store, so a hook with no store can send it with the session's end.
|
|
2
|
+
import { execFileSync } from 'child_process';
|
|
3
|
+
// Best-effort git state; a missing git, non-repo cwd, or a call past timeoutMs (each call, default 2 s)
|
|
4
|
+
// yields null fields rather than throw, so a hook with a hard deadline can cap a slow repo.
|
|
5
|
+
export function collectHandoffEvidence(cwd, testStatus, options = {}) {
|
|
6
|
+
const timeoutMs = options.timeoutMs ?? 2000;
|
|
7
|
+
let gitRef = null;
|
|
8
|
+
try {
|
|
9
|
+
gitRef = execFileSync('git', ['rev-parse', 'HEAD'], {
|
|
10
|
+
cwd, encoding: 'utf8', timeout: timeoutMs, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true,
|
|
11
|
+
}).trim() || null;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// No git, not a repo, or timed out: evidence is optional, so the field stays null.
|
|
15
|
+
gitRef = null;
|
|
16
|
+
}
|
|
17
|
+
let dirtyTree = null;
|
|
18
|
+
try {
|
|
19
|
+
const status = execFileSync('git', ['status', '--porcelain'], {
|
|
20
|
+
cwd, encoding: 'utf8', timeout: timeoutMs, stdio: ['ignore', 'pipe', 'ignore'], windowsHide: true,
|
|
21
|
+
});
|
|
22
|
+
dirtyTree = status.trim().length > 0;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
// Same as gitRef: unknown tree state is reported as null, never as an error.
|
|
26
|
+
dirtyTree = null;
|
|
27
|
+
}
|
|
28
|
+
return { gitRef, dirtyTree, testStatus };
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=handoff-evidence.js.map
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
/** One pattern per path shape that carries a user name: Windows, Git Bash and WSL mounts, extended-length, UNC home shares, Linux, macOS and 8.3 short names. */
|
|
2
|
+
export declare const USER_SEGMENT: readonly RegExp[];
|
|
3
|
+
/** `text` with each home-directory segment replaced by `[home]`. */
|
|
4
|
+
export declare function maskHomePaths(text: string): string;
|
|
5
|
+
//# sourceMappingURL=home-path.d.ts.map
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Home-directory paths name their user, so text bound for a shared store tests or masks them here; no imports keep it light.
|
|
2
|
+
// VS Code writes file links URL-encoded, so a separator may be %5C or %2F.
|
|
3
|
+
const WIN_SEP = String.raw `(?:[\\/]|%5C|%2F)`;
|
|
4
|
+
const WIN_CHAR = String.raw `(?:(?!%5C|%2F)[^\\/:*?"<>|\s])`;
|
|
5
|
+
// A Windows name never holds a quote, so JSON stops it; one with spaces runs to a separator, quote, closing mark or line end, leaving the mark, as stopping at its first word leaks the surname.
|
|
6
|
+
const WIN_USER = String.raw `(?:${WIN_CHAR}+(?: ${WIN_CHAR}+){0,3}(?=${WIN_SEP}|["'\`)\]>,;]|\.(?=\s|$)|(?<!\.)(?:[^\S ]|$))(?<![\`)\],;'])|${WIN_CHAR}+)`;
|
|
7
|
+
/** One pattern per path shape that carries a user name: Windows, Git Bash and WSL mounts, extended-length, UNC home shares, Linux, macOS and 8.3 short names. */
|
|
8
|
+
export const USER_SEGMENT = [
|
|
9
|
+
new RegExp(String.raw `[A-Za-z](?::|%3A)${WIN_SEP}+(?:Users|Documents and Settings)${WIN_SEP}+${WIN_USER}`, 'i'),
|
|
10
|
+
new RegExp(String.raw `(?<![\w.])(?:\/mnt)?\/[A-Za-z]\/Users\/${WIN_USER}`, 'i'),
|
|
11
|
+
/\\\\\?\\/,
|
|
12
|
+
new RegExp(String.raw `\\\\[^\\/\s]+\\+(?:homes?|users|profiles?)\$?\\+${WIN_USER}`, 'i'),
|
|
13
|
+
/(?<![\w.~-])(?:\/var)?\/home\/[^/\s]+/,
|
|
14
|
+
/(?<![\w.~-])(?:\/var)?\/root(?![\w.-])/,
|
|
15
|
+
/(?<![\w.~-])\/Users\/[^/\s]+/,
|
|
16
|
+
/[\\/][A-Z0-9_$]{1,6}~\d{1,6}(?:\.[A-Z0-9]{1,3})?(?![\w~])|\b[A-Z0-9_$]{1,6}~\d{1,6}(?:\.[A-Z0-9]{1,3})?[\\/]/,
|
|
17
|
+
];
|
|
18
|
+
// Global copies, so the shared test patterns keep their lastIndex at 0.
|
|
19
|
+
const USER_SEGMENT_ALL = USER_SEGMENT.map((re) => new RegExp(re.source, `${re.flags}g`));
|
|
20
|
+
/** `text` with each home-directory segment replaced by `[home]`. */
|
|
21
|
+
export function maskHomePaths(text) {
|
|
22
|
+
return USER_SEGMENT_ALL.reduce((out, re) => out.replace(re, '[home]'), text);
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=home-path.js.map
|
|
@@ -10,8 +10,9 @@
|
|
|
10
10
|
* sequence, writing both outputs to the log file. The parent returns in
|
|
11
11
|
* <100ms so the TUI teardown can't kill the child before it finishes.
|
|
12
12
|
* - SessionStart: `hippo last-sleep --path <path>` - prints the log
|
|
13
|
-
* written by the previous session's detached worker
|
|
14
|
-
*
|
|
13
|
+
* written by the previous session's detached worker on stderr and
|
|
14
|
+
* clears it. Stdout carries only a `systemMessage` problems line,
|
|
15
|
+
* because Claude Code shows that to the user, not the model.
|
|
15
16
|
* Earlier Claude Code forms are detected and migrated automatically:
|
|
16
17
|
* - < 0.20.2: `Stop` hook firing `hippo sleep` on every assistant turn.
|
|
17
18
|
* - < 0.21.0: bare `hippo sleep` in SessionEnd, no `--log-file`.
|
|
@@ -33,11 +34,14 @@
|
|
|
33
34
|
* `hooks` block from opencode.json.
|
|
34
35
|
*/
|
|
35
36
|
import { type JsonHookTarget } from './shared.js';
|
|
37
|
+
import { type JsonValue } from '../json.js';
|
|
38
|
+
/** A target's hook settings file, the log its SessionEnd hook writes, and the tool's display name. */
|
|
36
39
|
export interface JsonHookPaths {
|
|
37
40
|
settings: string;
|
|
38
41
|
logFile: string;
|
|
39
42
|
display: string;
|
|
40
43
|
}
|
|
44
|
+
/** What installJsonHooks wrote, migrated or left alone in one target's settings file. */
|
|
41
45
|
export interface InstallResult {
|
|
42
46
|
target: JsonHookTarget;
|
|
43
47
|
settingsPath: string;
|
|
@@ -57,7 +61,14 @@ export interface InstallResult {
|
|
|
57
61
|
/** The file exists but is not JSON hippo can merge into, so it was left untouched. */
|
|
58
62
|
invalidJson: boolean;
|
|
59
63
|
}
|
|
64
|
+
/** The paths for `target` under the current home directory; reads and writes nothing. */
|
|
60
65
|
export declare function resolveJsonHookPaths(target: JsonHookTarget): JsonHookPaths;
|
|
66
|
+
/** Adds hippo's hooks to `target`'s settings file and migrates older hippo entries; a file that is not JSON stays untouched. */
|
|
61
67
|
export declare function installJsonHooks(target: JsonHookTarget): InstallResult;
|
|
68
|
+
/** Swaps the file in with one rename, so a crash mid-write cannot leave a truncated settings.json that Claude Code cannot parse. */
|
|
69
|
+
export declare function writeSettingsFile(file: string, settings: JsonValue): void;
|
|
70
|
+
/** Removes hippo's own hook handlers from `target`'s settings file and keeps every other handler; true when the file changed. */
|
|
62
71
|
export declare function uninstallJsonHooks(target: JsonHookTarget): boolean;
|
|
72
|
+
/** Whether uninstall can edit settings.json; `invalidJson` marks a file that exists but is not JSON hippo can edit, which `uninstallJsonHooks` reports only as false. */
|
|
73
|
+
export declare function checkUninstallable(target: JsonHookTarget): Pick<InstallResult, 'settingsPath' | 'invalidJson'>;
|
|
63
74
|
//# sourceMappingURL=json-hooks.d.ts.map
|