@psiclawops/hypermem 0.1.0 → 0.5.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/ARCHITECTURE.md +4 -3
- package/README.md +457 -174
- package/dist/background-indexer.d.ts +19 -4
- package/dist/background-indexer.d.ts.map +1 -1
- package/dist/background-indexer.js +329 -17
- package/dist/cache.d.ts +110 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +495 -0
- package/dist/compaction-fence.d.ts +1 -1
- package/dist/compaction-fence.js +1 -1
- package/dist/compositor.d.ts +114 -27
- package/dist/compositor.d.ts.map +1 -1
- package/dist/compositor.js +1678 -229
- package/dist/content-type-classifier.d.ts +41 -0
- package/dist/content-type-classifier.d.ts.map +1 -0
- package/dist/content-type-classifier.js +181 -0
- package/dist/cross-agent.d.ts +5 -0
- package/dist/cross-agent.d.ts.map +1 -1
- package/dist/cross-agent.js +5 -0
- package/dist/db.d.ts +1 -1
- package/dist/db.d.ts.map +1 -1
- package/dist/db.js +6 -2
- package/dist/desired-state-store.d.ts +1 -1
- package/dist/desired-state-store.d.ts.map +1 -1
- package/dist/desired-state-store.js +15 -5
- package/dist/doc-chunk-store.d.ts +26 -1
- package/dist/doc-chunk-store.d.ts.map +1 -1
- package/dist/doc-chunk-store.js +114 -1
- package/dist/doc-chunker.d.ts +1 -1
- package/dist/doc-chunker.js +1 -1
- package/dist/dreaming-promoter.d.ts +86 -0
- package/dist/dreaming-promoter.d.ts.map +1 -0
- package/dist/dreaming-promoter.js +381 -0
- package/dist/episode-store.d.ts +2 -1
- package/dist/episode-store.d.ts.map +1 -1
- package/dist/episode-store.js +4 -4
- package/dist/fact-store.d.ts +19 -1
- package/dist/fact-store.d.ts.map +1 -1
- package/dist/fact-store.js +64 -3
- package/dist/fleet-store.d.ts +1 -1
- package/dist/fleet-store.js +1 -1
- package/dist/fos-mod.d.ts +178 -0
- package/dist/fos-mod.d.ts.map +1 -0
- package/dist/fos-mod.js +416 -0
- package/dist/hybrid-retrieval.d.ts +5 -1
- package/dist/hybrid-retrieval.d.ts.map +1 -1
- package/dist/hybrid-retrieval.js +7 -3
- package/dist/image-eviction.d.ts +49 -0
- package/dist/image-eviction.d.ts.map +1 -0
- package/dist/image-eviction.js +251 -0
- package/dist/index.d.ts +50 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +73 -43
- package/dist/keystone-scorer.d.ts +51 -0
- package/dist/keystone-scorer.d.ts.map +1 -0
- package/dist/keystone-scorer.js +52 -0
- package/dist/knowledge-graph.d.ts +1 -1
- package/dist/knowledge-graph.js +1 -1
- package/dist/knowledge-lint.d.ts +29 -0
- package/dist/knowledge-lint.d.ts.map +1 -0
- package/dist/knowledge-lint.js +116 -0
- package/dist/knowledge-store.d.ts +1 -1
- package/dist/knowledge-store.d.ts.map +1 -1
- package/dist/knowledge-store.js +8 -2
- package/dist/library-schema.d.ts +3 -3
- package/dist/library-schema.d.ts.map +1 -1
- package/dist/library-schema.js +324 -3
- package/dist/message-store.d.ts +15 -2
- package/dist/message-store.d.ts.map +1 -1
- package/dist/message-store.js +51 -1
- package/dist/metrics-dashboard.d.ts +114 -0
- package/dist/metrics-dashboard.d.ts.map +1 -0
- package/dist/metrics-dashboard.js +260 -0
- package/dist/obsidian-exporter.d.ts +57 -0
- package/dist/obsidian-exporter.d.ts.map +1 -0
- package/dist/obsidian-exporter.js +274 -0
- package/dist/obsidian-watcher.d.ts +147 -0
- package/dist/obsidian-watcher.d.ts.map +1 -0
- package/dist/obsidian-watcher.js +403 -0
- package/dist/open-domain.d.ts +46 -0
- package/dist/open-domain.d.ts.map +1 -0
- package/dist/open-domain.js +125 -0
- package/dist/preference-store.d.ts +1 -1
- package/dist/preference-store.js +1 -1
- package/dist/preservation-gate.d.ts +1 -1
- package/dist/preservation-gate.js +1 -1
- package/dist/proactive-pass.d.ts +63 -0
- package/dist/proactive-pass.d.ts.map +1 -0
- package/dist/proactive-pass.js +239 -0
- package/dist/profiles.d.ts +44 -0
- package/dist/profiles.d.ts.map +1 -0
- package/dist/profiles.js +227 -0
- package/dist/provider-translator.d.ts +13 -3
- package/dist/provider-translator.d.ts.map +1 -1
- package/dist/provider-translator.js +63 -9
- package/dist/rate-limiter.d.ts +1 -1
- package/dist/rate-limiter.js +1 -1
- package/dist/repair-tool-pairs.d.ts +38 -0
- package/dist/repair-tool-pairs.d.ts.map +1 -0
- package/dist/repair-tool-pairs.js +138 -0
- package/dist/retrieval-policy.d.ts +51 -0
- package/dist/retrieval-policy.d.ts.map +1 -0
- package/dist/retrieval-policy.js +77 -0
- package/dist/schema.d.ts +2 -2
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +28 -2
- package/dist/secret-scanner.d.ts +1 -1
- package/dist/secret-scanner.js +1 -1
- package/dist/seed.d.ts +2 -2
- package/dist/seed.js +2 -2
- package/dist/session-flusher.d.ts +53 -0
- package/dist/session-flusher.d.ts.map +1 -0
- package/dist/session-flusher.js +69 -0
- package/dist/session-topic-map.d.ts +41 -0
- package/dist/session-topic-map.d.ts.map +1 -0
- package/dist/session-topic-map.js +77 -0
- package/dist/spawn-context.d.ts +54 -0
- package/dist/spawn-context.d.ts.map +1 -0
- package/dist/spawn-context.js +159 -0
- package/dist/system-store.d.ts +1 -1
- package/dist/system-store.js +1 -1
- package/dist/temporal-store.d.ts +80 -0
- package/dist/temporal-store.d.ts.map +1 -0
- package/dist/temporal-store.js +149 -0
- package/dist/topic-detector.d.ts +35 -0
- package/dist/topic-detector.d.ts.map +1 -0
- package/dist/topic-detector.js +249 -0
- package/dist/topic-store.d.ts +1 -1
- package/dist/topic-store.js +1 -1
- package/dist/topic-synthesizer.d.ts +51 -0
- package/dist/topic-synthesizer.d.ts.map +1 -0
- package/dist/topic-synthesizer.js +315 -0
- package/dist/trigger-registry.d.ts +63 -0
- package/dist/trigger-registry.d.ts.map +1 -0
- package/dist/trigger-registry.js +163 -0
- package/dist/types.d.ts +214 -10
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +1 -1
- package/dist/vector-store.d.ts +43 -5
- package/dist/vector-store.d.ts.map +1 -1
- package/dist/vector-store.js +189 -10
- package/dist/version.d.ts +34 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +34 -0
- package/dist/wiki-page-emitter.d.ts +65 -0
- package/dist/wiki-page-emitter.d.ts.map +1 -0
- package/dist/wiki-page-emitter.js +258 -0
- package/dist/work-store.d.ts +1 -1
- package/dist/work-store.js +1 -1
- package/package.json +15 -5
- package/dist/redis.d.ts +0 -188
- package/dist/redis.d.ts.map +0 -1
- package/dist/redis.js +0 -534
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* hypermem — Agent-Centric Memory & Context Composition Engine
|
|
3
3
|
*
|
|
4
4
|
* @module @psiclawops/hypermem
|
|
5
5
|
*
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
* L3: vectors.db — per-agent semantic search index (reconstructable)
|
|
10
10
|
* L4: library.db — fleet-wide structured knowledge (crown jewel)
|
|
11
11
|
*/
|
|
12
|
+
export { ENGINE_VERSION, MIN_NODE_VERSION, MIN_REDIS_VERSION, SQLITE_VEC_VERSION, MAIN_SCHEMA_VERSION, LIBRARY_SCHEMA_VERSION_EXPORT, HYPERMEM_COMPAT_VERSION, SCHEMA_COMPAT } from './version.js';
|
|
12
13
|
export { DatabaseManager } from './db.js';
|
|
13
14
|
export { MessageStore } from './message-store.js';
|
|
14
15
|
export { FactStore } from './fact-store.js';
|
|
@@ -20,13 +21,15 @@ export { FleetStore } from './fleet-store.js';
|
|
|
20
21
|
export { SystemStore } from './system-store.js';
|
|
21
22
|
export { WorkStore } from './work-store.js';
|
|
22
23
|
export { DesiredStateStore } from './desired-state-store.js';
|
|
24
|
+
export { evictStaleContent, DEFAULT_EVICTION_CONFIG } from './image-eviction.js';
|
|
23
25
|
export { KnowledgeGraph } from './knowledge-graph.js';
|
|
24
26
|
export { RateLimiter, createRateLimitedEmbedder } from './rate-limiter.js';
|
|
25
|
-
export {
|
|
26
|
-
export { Compositor } from './compositor.js';
|
|
27
|
+
export { CacheLayer } from './cache.js';
|
|
28
|
+
export { Compositor, applyToolGradientToWindow, canPersistReshapedHistory } from './compositor.js';
|
|
29
|
+
export { TRIGGER_REGISTRY, TRIGGER_REGISTRY_VERSION, TRIGGER_REGISTRY_HASH, DEFAULT_TRIGGERS, matchTriggers, } from './trigger-registry.js';
|
|
27
30
|
export { ensureCompactionFenceSchema, updateCompactionFence, getCompactionFence, getCompactionEligibility, getCompactableMessages, } from './compaction-fence.js';
|
|
28
31
|
export { verifyPreservation, verifyPreservationFromVectors, } from './preservation-gate.js';
|
|
29
|
-
export { toProviderFormat, fromProviderFormat, userMessageToNeutral, toolResultsToNeutral, normalizeToolCallId, generateToolCallId, detectProvider, } from './provider-translator.js';
|
|
32
|
+
export { toProviderFormat, fromProviderFormat, userMessageToNeutral, toolResultsToNeutral, normalizeToolCallId, generateToolCallId, detectProvider, repairToolCallPairs, } from './provider-translator.js';
|
|
30
33
|
export { migrate, SCHEMA_VERSION } from './schema.js';
|
|
31
34
|
export { migrateLibrary, LIBRARY_SCHEMA_VERSION } from './library-schema.js';
|
|
32
35
|
export { VectorStore, generateEmbeddings } from './vector-store.js';
|
|
@@ -34,8 +37,18 @@ export { hybridSearch, buildFtsQuery } from './hybrid-retrieval.js';
|
|
|
34
37
|
export { DocChunkStore } from './doc-chunk-store.js';
|
|
35
38
|
export { WorkspaceSeeder, seedWorkspace } from './seed.js';
|
|
36
39
|
export { chunkMarkdown, chunkFile, inferCollection, hashContent, ACA_COLLECTIONS } from './doc-chunker.js';
|
|
37
|
-
export { crossAgentQuery, canAccess, visibilityFilter, defaultOrgRegistry, buildOrgRegistryFromDb, } from './cross-agent.js';
|
|
40
|
+
export { crossAgentQuery, canAccess, visibilityFilter, defaultOrgRegistry, buildOrgRegistryFromDb, loadOrgRegistryFromDb, } from './cross-agent.js';
|
|
38
41
|
export { BackgroundIndexer, createIndexer } from './background-indexer.js';
|
|
42
|
+
export { runDreamingPromoter, runDreamingPassForFleet, resolveAgentWorkspacePath, DEFAULT_DREAMER_CONFIG, } from './dreaming-promoter.js';
|
|
43
|
+
export { TopicSynthesizer } from './topic-synthesizer.js';
|
|
44
|
+
export { WikiPageEmitter } from './wiki-page-emitter.js';
|
|
45
|
+
export { lintKnowledge } from './knowledge-lint.js';
|
|
46
|
+
export { buildSpawnContext } from './spawn-context.js';
|
|
47
|
+
export { runNoiseSweep, runToolDecay } from './proactive-pass.js';
|
|
48
|
+
export { classifyContentType, signalWeight, isSignalBearing, SIGNAL_WEIGHT } from './content-type-classifier.js';
|
|
49
|
+
export { detectTopicShift, stripMessageMetadata } from './topic-detector.js';
|
|
50
|
+
export { SessionTopicMap } from './session-topic-map.js';
|
|
51
|
+
export { getActiveFOS, matchMOD, renderFOS, renderMOD, recordOutputMetrics, } from './fos-mod.js';
|
|
39
52
|
import { DatabaseManager } from './db.js';
|
|
40
53
|
import { MessageStore } from './message-store.js';
|
|
41
54
|
import { FactStore } from './fact-store.js';
|
|
@@ -48,10 +61,11 @@ import { SystemStore } from './system-store.js';
|
|
|
48
61
|
import { WorkStore } from './work-store.js';
|
|
49
62
|
import { KnowledgeGraph } from './knowledge-graph.js';
|
|
50
63
|
import { DesiredStateStore } from './desired-state-store.js';
|
|
51
|
-
import {
|
|
64
|
+
import { CacheLayer } from './cache.js';
|
|
52
65
|
import { Compositor } from './compositor.js';
|
|
53
66
|
import { VectorStore } from './vector-store.js';
|
|
54
67
|
import { userMessageToNeutral, fromProviderFormat } from './provider-translator.js';
|
|
68
|
+
import { stripMessageMetadata } from './topic-detector.js';
|
|
55
69
|
import { DocChunkStore } from './doc-chunk-store.js';
|
|
56
70
|
import { WorkspaceSeeder } from './seed.js';
|
|
57
71
|
import { crossAgentQuery, buildOrgRegistryFromDb } from './cross-agent.js';
|
|
@@ -60,13 +74,10 @@ import os from 'node:os';
|
|
|
60
74
|
const DEFAULT_CONFIG = {
|
|
61
75
|
enabled: true,
|
|
62
76
|
dataDir: path.join(process.env.HOME || os.homedir(), '.openclaw', 'hypermem'),
|
|
63
|
-
|
|
64
|
-
host: 'localhost',
|
|
65
|
-
port: 6379,
|
|
77
|
+
cache: {
|
|
66
78
|
keyPrefix: 'hm:',
|
|
67
79
|
sessionTTL: 14400, // 4 hours — system/identity/meta slots
|
|
68
|
-
historyTTL:
|
|
69
|
-
flushInterval: 1000,
|
|
80
|
+
historyTTL: 604800, // 7 days — extended for ClawCanvas display
|
|
70
81
|
},
|
|
71
82
|
compositor: {
|
|
72
83
|
// TUNE-010 (2026-04-02): Raised from 65000 → 90000.
|
|
@@ -75,9 +86,9 @@ const DEFAULT_CONFIG = {
|
|
|
75
86
|
// re-run composition, so 90k is safe — leaves ~30k headroom for in-flight
|
|
76
87
|
// tool results on a 120k window. Budget is better spent on context quality.
|
|
77
88
|
defaultTokenBudget: 90000,
|
|
78
|
-
maxHistoryMessages:
|
|
79
|
-
maxFacts:
|
|
80
|
-
maxCrossSessionContext:
|
|
89
|
+
maxHistoryMessages: 1000,
|
|
90
|
+
maxFacts: 28,
|
|
91
|
+
maxCrossSessionContext: 6000,
|
|
81
92
|
maxRecentToolPairs: 3,
|
|
82
93
|
maxProseToolPairs: 10,
|
|
83
94
|
warmHistoryBudgetFraction: 0.4,
|
|
@@ -90,6 +101,8 @@ const DEFAULT_CONFIG = {
|
|
|
90
101
|
factDecayRate: 0.01,
|
|
91
102
|
episodeSignificanceThreshold: 0.5,
|
|
92
103
|
periodicInterval: 300000,
|
|
104
|
+
batchSize: 128,
|
|
105
|
+
maxMessagesPerTick: 500,
|
|
93
106
|
},
|
|
94
107
|
embedding: {
|
|
95
108
|
ollamaUrl: 'http://localhost:11434',
|
|
@@ -100,24 +113,24 @@ const DEFAULT_CONFIG = {
|
|
|
100
113
|
},
|
|
101
114
|
};
|
|
102
115
|
/**
|
|
103
|
-
*
|
|
116
|
+
* hypermem — the main API facade.
|
|
104
117
|
*
|
|
105
118
|
* Usage:
|
|
106
|
-
* const hm = await
|
|
119
|
+
* const hm = await hypermem.create({ dataDir: '~/.openclaw/hypermem' });
|
|
107
120
|
* await hm.record('forge', 'agent:forge:webchat:main', userMsg);
|
|
108
121
|
* const result = await hm.compose({ agentId: 'forge', sessionKey: '...', ... });
|
|
109
122
|
*/
|
|
110
123
|
export class HyperMem {
|
|
111
124
|
dbManager;
|
|
112
|
-
|
|
125
|
+
cache;
|
|
113
126
|
compositor;
|
|
114
127
|
config;
|
|
115
128
|
constructor(config) {
|
|
116
129
|
this.config = config;
|
|
117
130
|
this.dbManager = new DatabaseManager({ dataDir: config.dataDir });
|
|
118
|
-
this.
|
|
131
|
+
this.cache = new CacheLayer(config.cache);
|
|
119
132
|
this.compositor = new Compositor({
|
|
120
|
-
|
|
133
|
+
cache: this.cache,
|
|
121
134
|
vectorStore: null, // Set after create() when vector DB is available
|
|
122
135
|
libraryDb: null, // Set after create() when library DB is available
|
|
123
136
|
}, config.compositor);
|
|
@@ -130,13 +143,13 @@ export class HyperMem {
|
|
|
130
143
|
return this.compositor.vectorStore;
|
|
131
144
|
}
|
|
132
145
|
/**
|
|
133
|
-
* Create and initialize a
|
|
146
|
+
* Create and initialize a hypermem instance.
|
|
134
147
|
*/
|
|
135
148
|
static async create(config) {
|
|
136
149
|
const merged = {
|
|
137
150
|
...DEFAULT_CONFIG,
|
|
138
151
|
...config,
|
|
139
|
-
|
|
152
|
+
cache: { ...DEFAULT_CONFIG.cache, ...config?.cache },
|
|
140
153
|
compositor: { ...DEFAULT_CONFIG.compositor, ...config?.compositor },
|
|
141
154
|
indexer: { ...DEFAULT_CONFIG.indexer, ...config?.indexer },
|
|
142
155
|
embedding: {
|
|
@@ -145,12 +158,12 @@ export class HyperMem {
|
|
|
145
158
|
},
|
|
146
159
|
};
|
|
147
160
|
const hm = new HyperMem(merged);
|
|
148
|
-
const
|
|
149
|
-
if (
|
|
150
|
-
console.log('[hypermem]
|
|
161
|
+
const cacheOk = await hm.cache.connect();
|
|
162
|
+
if (cacheOk) {
|
|
163
|
+
console.log('[hypermem] Cache connected');
|
|
151
164
|
}
|
|
152
165
|
else {
|
|
153
|
-
console.warn('[hypermem]
|
|
166
|
+
console.warn('[hypermem] Cache unavailable — running in SQLite-only mode');
|
|
154
167
|
}
|
|
155
168
|
// ── Vector store init ─────────────────────────────────────
|
|
156
169
|
// Attempt to wire up sqlite-vec + nomic-embed-text for semantic recall.
|
|
@@ -164,7 +177,10 @@ export class HyperMem {
|
|
|
164
177
|
const vs = new VectorStore(vectorDb, merged.embedding, hm.dbManager.getLibraryDb());
|
|
165
178
|
vs.ensureTables();
|
|
166
179
|
hm.compositor.setVectorStore(vs);
|
|
167
|
-
|
|
180
|
+
const embeddingDesc = merged.embedding.provider === 'openai'
|
|
181
|
+
? `${merged.embedding.openaiBaseUrl?.includes('openrouter') ? 'openrouter' : 'openai'}/${merged.embedding.model ?? 'text-embedding-3-small'}`
|
|
182
|
+
: `ollama/${merged.embedding.model ?? 'nomic-embed-text'}`;
|
|
183
|
+
console.log(`[hypermem] Vector store initialized (sqlite-vec + ${embeddingDesc})`);
|
|
168
184
|
}
|
|
169
185
|
else {
|
|
170
186
|
console.warn('[hypermem] sqlite-vec unavailable — semantic recall in FTS5-only mode');
|
|
@@ -189,13 +205,13 @@ export class HyperMem {
|
|
|
189
205
|
provider: opts?.provider,
|
|
190
206
|
model: opts?.model,
|
|
191
207
|
});
|
|
192
|
-
const neutral = userMessageToNeutral(content);
|
|
208
|
+
const neutral = userMessageToNeutral(stripMessageMetadata(content));
|
|
193
209
|
const stored = store.recordMessage(conversation.id, agentId, neutral, {
|
|
194
210
|
tokenCount: opts?.tokenCount,
|
|
195
211
|
isHeartbeat: opts?.isHeartbeat,
|
|
196
212
|
});
|
|
197
|
-
await this.
|
|
198
|
-
await this.
|
|
213
|
+
await this.cache.pushHistory(agentId, sessionKey, [stored], this.config.compositor.maxHistoryMessages);
|
|
214
|
+
await this.cache.touchSession(agentId, sessionKey);
|
|
199
215
|
return stored;
|
|
200
216
|
}
|
|
201
217
|
/**
|
|
@@ -211,8 +227,8 @@ export class HyperMem {
|
|
|
211
227
|
const stored = store.recordMessage(conversation.id, agentId, message, {
|
|
212
228
|
tokenCount: opts?.tokenCount,
|
|
213
229
|
});
|
|
214
|
-
await this.
|
|
215
|
-
await this.
|
|
230
|
+
await this.cache.pushHistory(agentId, sessionKey, [stored], this.config.compositor.maxHistoryMessages);
|
|
231
|
+
await this.cache.touchSession(agentId, sessionKey);
|
|
216
232
|
return stored;
|
|
217
233
|
}
|
|
218
234
|
/**
|
|
@@ -238,6 +254,13 @@ export class HyperMem {
|
|
|
238
254
|
const libraryDb = this.dbManager.getLibraryDb();
|
|
239
255
|
await this.compositor.warmSession(agentId, sessionKey, db, { ...opts, libraryDb });
|
|
240
256
|
}
|
|
257
|
+
/**
|
|
258
|
+
* Recompute the Redis hot history view from SQLite and re-apply tool gradient.
|
|
259
|
+
*/
|
|
260
|
+
async refreshRedisGradient(agentId, sessionKey, tokenBudget) {
|
|
261
|
+
const db = this.dbManager.getMessageDb(agentId);
|
|
262
|
+
await this.compositor.refreshRedisGradient(agentId, sessionKey, db, tokenBudget);
|
|
263
|
+
}
|
|
241
264
|
/**
|
|
242
265
|
* Full-text search across all messages for an agent.
|
|
243
266
|
*/
|
|
@@ -363,7 +386,7 @@ export class HyperMem {
|
|
|
363
386
|
const store = new FleetStore(db);
|
|
364
387
|
const result = store.upsertAgent(id, data);
|
|
365
388
|
// Invalidate cache — fire and forget
|
|
366
|
-
this.
|
|
389
|
+
this.cache.invalidateFleetAgent(id).catch(() => { });
|
|
367
390
|
return result;
|
|
368
391
|
}
|
|
369
392
|
/**
|
|
@@ -371,14 +394,14 @@ export class HyperMem {
|
|
|
371
394
|
*/
|
|
372
395
|
async getFleetAgentCached(id) {
|
|
373
396
|
// Try cache first
|
|
374
|
-
const cached = await this.
|
|
397
|
+
const cached = await this.cache.getCachedFleetAgent(id);
|
|
375
398
|
if (cached)
|
|
376
399
|
return cached;
|
|
377
400
|
// Fall back to SQLite
|
|
378
401
|
const agent = this.getFleetAgent(id);
|
|
379
402
|
if (agent) {
|
|
380
403
|
// Warm cache — fire and forget
|
|
381
|
-
this.
|
|
404
|
+
this.cache.cacheFleetAgent(id, agent).catch(() => { });
|
|
382
405
|
}
|
|
383
406
|
return agent;
|
|
384
407
|
}
|
|
@@ -531,7 +554,7 @@ export class HyperMem {
|
|
|
531
554
|
const store = new DesiredStateStore(db);
|
|
532
555
|
const result = store.setDesired(agentId, configKey, desiredValue, opts);
|
|
533
556
|
// Invalidate cache — desired state change affects fleet view
|
|
534
|
-
this.
|
|
557
|
+
this.cache.invalidateFleetAgent(agentId).catch(() => { });
|
|
535
558
|
return result;
|
|
536
559
|
}
|
|
537
560
|
/**
|
|
@@ -541,7 +564,7 @@ export class HyperMem {
|
|
|
541
564
|
const db = this.dbManager.getLibraryDb();
|
|
542
565
|
const store = new DesiredStateStore(db);
|
|
543
566
|
const result = store.reportActual(agentId, configKey, actualValue);
|
|
544
|
-
this.
|
|
567
|
+
this.cache.invalidateFleetAgent(agentId).catch(() => { });
|
|
545
568
|
return result;
|
|
546
569
|
}
|
|
547
570
|
/**
|
|
@@ -551,7 +574,7 @@ export class HyperMem {
|
|
|
551
574
|
const db = this.dbManager.getLibraryDb();
|
|
552
575
|
const store = new DesiredStateStore(db);
|
|
553
576
|
const result = store.reportActualBulk(agentId, actuals);
|
|
554
|
-
this.
|
|
577
|
+
this.cache.invalidateFleetAgent(agentId).catch(() => { });
|
|
555
578
|
return result;
|
|
556
579
|
}
|
|
557
580
|
/**
|
|
@@ -722,7 +745,7 @@ export class HyperMem {
|
|
|
722
745
|
*/
|
|
723
746
|
async getSessionCursor(agentId, sessionKey) {
|
|
724
747
|
// Try Redis first (hot path)
|
|
725
|
-
const redisCursor = await this.
|
|
748
|
+
const redisCursor = await this.cache.getCursor(agentId, sessionKey);
|
|
726
749
|
if (redisCursor)
|
|
727
750
|
return redisCursor;
|
|
728
751
|
// Fallback to SQLite
|
|
@@ -746,7 +769,7 @@ export class HyperMem {
|
|
|
746
769
|
};
|
|
747
770
|
// Re-warm Redis so subsequent reads are fast
|
|
748
771
|
try {
|
|
749
|
-
await this.
|
|
772
|
+
await this.cache.setCursor(agentId, sessionKey, cursor);
|
|
750
773
|
}
|
|
751
774
|
catch {
|
|
752
775
|
// Best-effort re-warm
|
|
@@ -976,7 +999,7 @@ export class HyperMem {
|
|
|
976
999
|
* - Fleet summary (counts, drift status)
|
|
977
1000
|
*/
|
|
978
1001
|
async hydrateFleetCache() {
|
|
979
|
-
if (!this.
|
|
1002
|
+
if (!this.cache.isConnected)
|
|
980
1003
|
return { agents: 0, summary: false };
|
|
981
1004
|
const db = this.dbManager.getLibraryDb();
|
|
982
1005
|
const fleetStore = new FleetStore(db);
|
|
@@ -1000,7 +1023,7 @@ export class HyperMem {
|
|
|
1000
1023
|
})),
|
|
1001
1024
|
desiredConfig,
|
|
1002
1025
|
};
|
|
1003
|
-
await this.
|
|
1026
|
+
await this.cache.cacheFleetAgent(agent.id, composite);
|
|
1004
1027
|
hydrated++;
|
|
1005
1028
|
}
|
|
1006
1029
|
catch (err) {
|
|
@@ -1022,7 +1045,7 @@ export class HyperMem {
|
|
|
1022
1045
|
drift: driftSummary,
|
|
1023
1046
|
hydratedAt: new Date().toISOString(),
|
|
1024
1047
|
};
|
|
1025
|
-
await this.
|
|
1048
|
+
await this.cache.cacheFleetSummary(summary);
|
|
1026
1049
|
}
|
|
1027
1050
|
catch {
|
|
1028
1051
|
return { agents: hydrated, summary: false };
|
|
@@ -1034,9 +1057,16 @@ export class HyperMem {
|
|
|
1034
1057
|
* Clean shutdown.
|
|
1035
1058
|
*/
|
|
1036
1059
|
async close() {
|
|
1037
|
-
await this.
|
|
1060
|
+
await this.cache.disconnect();
|
|
1038
1061
|
this.dbManager.close();
|
|
1039
1062
|
}
|
|
1040
1063
|
}
|
|
1041
1064
|
export default HyperMem;
|
|
1065
|
+
export { SessionFlusher, flushSession } from './session-flusher.js';
|
|
1066
|
+
export { importVault, watchVault, parseObsidianNote, parseFrontmatter, extractWikilinks, extractTags, cleanObsidianMarkdown } from './obsidian-watcher.js';
|
|
1067
|
+
export { exportToVault } from './obsidian-exporter.js';
|
|
1068
|
+
export { collectMetrics, formatMetricsSummary } from './metrics-dashboard.js';
|
|
1069
|
+
export { getProfile, mergeProfile, PROFILES, lightProfile, standardProfile, fullProfile, extendedProfile, minimalProfile, richProfile } from './profiles.js';
|
|
1070
|
+
export { renderStarterFOS, resolveOutputTier } from './fos-mod.js';
|
|
1071
|
+
export { repairToolPairs } from './repair-tool-pairs.js';
|
|
1042
1072
|
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hypermem Keystone Scorer
|
|
3
|
+
*
|
|
4
|
+
* Scores message candidates for inclusion in the Keystone History Slot (P2.1).
|
|
5
|
+
* A "keystone" is an older, high-signal message that provides critical context
|
|
6
|
+
* for the current conversation — decisions, specs, discoveries that happened
|
|
7
|
+
* before the recent history window.
|
|
8
|
+
*
|
|
9
|
+
* Scoring formula (weights sum to 1.0):
|
|
10
|
+
* - episodeSignificance × 0.5 (was this message linked to a significant episode?)
|
|
11
|
+
* - ftsRelevance × 0.3 (is it semantically relevant to the current prompt?)
|
|
12
|
+
* - recencyFactor × 0.2 (how recent is it, relative to maxAgeHours?)
|
|
13
|
+
*
|
|
14
|
+
* Content-type bonus: messages classified as 'decision' or 'spec' get +0.1,
|
|
15
|
+
* capped at 1.0. These are the highest-value signals for context recall.
|
|
16
|
+
*/
|
|
17
|
+
export interface KeystoneCandidate {
|
|
18
|
+
messageId: number;
|
|
19
|
+
messageIndex: number;
|
|
20
|
+
role: string;
|
|
21
|
+
content: string;
|
|
22
|
+
timestamp: string;
|
|
23
|
+
/** Significance from the episodes table (NULL if no episode was linked). */
|
|
24
|
+
episodeSignificance: number | null;
|
|
25
|
+
/** FTS5 BM25 relevance rank, already normalized to [0, 1]. */
|
|
26
|
+
ftsRank: number;
|
|
27
|
+
/** Age in hours from now. */
|
|
28
|
+
ageHours: number;
|
|
29
|
+
}
|
|
30
|
+
export interface ScoredKeystone extends KeystoneCandidate {
|
|
31
|
+
score: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Score a single keystone candidate.
|
|
35
|
+
*
|
|
36
|
+
* @param candidate - The message candidate with its signals
|
|
37
|
+
* @param maxAgeHours - The age ceiling for the recency factor (messages older
|
|
38
|
+
* than this get recencyFactor = 0, but are not excluded — they can still score
|
|
39
|
+
* via significance + ftsRelevance).
|
|
40
|
+
* @returns Score in [0.0, 1.0]
|
|
41
|
+
*/
|
|
42
|
+
export declare function scoreKeystone(candidate: KeystoneCandidate, maxAgeHours: number): number;
|
|
43
|
+
/**
|
|
44
|
+
* Score an array of candidates and sort by score descending.
|
|
45
|
+
*
|
|
46
|
+
* @param candidates - Raw candidates from the DB query
|
|
47
|
+
* @param maxAgeHours - Age ceiling for recency scoring (e.g. 720 = 30 days)
|
|
48
|
+
* @returns Candidates sorted by score DESC with score field attached
|
|
49
|
+
*/
|
|
50
|
+
export declare function rankKeystones(candidates: KeystoneCandidate[], maxAgeHours: number): ScoredKeystone[];
|
|
51
|
+
//# sourceMappingURL=keystone-scorer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"keystone-scorer.d.ts","sourceRoot":"","sources":["../src/keystone-scorer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAMH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,8DAA8D;IAC9D,OAAO,EAAE,MAAM,CAAC;IAChB,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,cAAe,SAAQ,iBAAiB;IACvD,KAAK,EAAE,MAAM,CAAC;CACf;AAID;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAC3B,SAAS,EAAE,iBAAiB,EAC5B,WAAW,EAAE,MAAM,GAClB,MAAM,CAcR;AAED;;;;;;GAMG;AACH,wBAAgB,aAAa,CAC3B,UAAU,EAAE,iBAAiB,EAAE,EAC/B,WAAW,EAAE,MAAM,GAClB,cAAc,EAAE,CAIlB"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hypermem Keystone Scorer
|
|
3
|
+
*
|
|
4
|
+
* Scores message candidates for inclusion in the Keystone History Slot (P2.1).
|
|
5
|
+
* A "keystone" is an older, high-signal message that provides critical context
|
|
6
|
+
* for the current conversation — decisions, specs, discoveries that happened
|
|
7
|
+
* before the recent history window.
|
|
8
|
+
*
|
|
9
|
+
* Scoring formula (weights sum to 1.0):
|
|
10
|
+
* - episodeSignificance × 0.5 (was this message linked to a significant episode?)
|
|
11
|
+
* - ftsRelevance × 0.3 (is it semantically relevant to the current prompt?)
|
|
12
|
+
* - recencyFactor × 0.2 (how recent is it, relative to maxAgeHours?)
|
|
13
|
+
*
|
|
14
|
+
* Content-type bonus: messages classified as 'decision' or 'spec' get +0.1,
|
|
15
|
+
* capped at 1.0. These are the highest-value signals for context recall.
|
|
16
|
+
*/
|
|
17
|
+
import { classifyContentType } from './content-type-classifier.js';
|
|
18
|
+
// ─── Scorer ──────────────────────────────────────────────────────
|
|
19
|
+
/**
|
|
20
|
+
* Score a single keystone candidate.
|
|
21
|
+
*
|
|
22
|
+
* @param candidate - The message candidate with its signals
|
|
23
|
+
* @param maxAgeHours - The age ceiling for the recency factor (messages older
|
|
24
|
+
* than this get recencyFactor = 0, but are not excluded — they can still score
|
|
25
|
+
* via significance + ftsRelevance).
|
|
26
|
+
* @returns Score in [0.0, 1.0]
|
|
27
|
+
*/
|
|
28
|
+
export function scoreKeystone(candidate, maxAgeHours) {
|
|
29
|
+
const significance = candidate.episodeSignificance ?? 0.3;
|
|
30
|
+
const ftsRelevance = Math.min(1.0, Math.max(0, candidate.ftsRank));
|
|
31
|
+
const recencyFactor = Math.max(0, 1.0 - (candidate.ageHours / maxAgeHours));
|
|
32
|
+
let score = (significance * 0.5) + (ftsRelevance * 0.3) + (recencyFactor * 0.2);
|
|
33
|
+
// Content-type bonus: decisions and specs get +0.1 (capped at 1.0)
|
|
34
|
+
const contentType = classifyContentType(candidate.content);
|
|
35
|
+
if (contentType.type === 'decision' || contentType.type === 'spec') {
|
|
36
|
+
score = Math.min(1.0, score + 0.1);
|
|
37
|
+
}
|
|
38
|
+
return score;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Score an array of candidates and sort by score descending.
|
|
42
|
+
*
|
|
43
|
+
* @param candidates - Raw candidates from the DB query
|
|
44
|
+
* @param maxAgeHours - Age ceiling for recency scoring (e.g. 720 = 30 days)
|
|
45
|
+
* @returns Candidates sorted by score DESC with score field attached
|
|
46
|
+
*/
|
|
47
|
+
export function rankKeystones(candidates, maxAgeHours) {
|
|
48
|
+
return candidates
|
|
49
|
+
.map(c => ({ ...c, score: scoreKeystone(c, maxAgeHours) }))
|
|
50
|
+
.sort((a, b) => b.score - a.score);
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=keystone-scorer.js.map
|
package/dist/knowledge-graph.js
CHANGED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Lint
|
|
3
|
+
*
|
|
4
|
+
* Health checks for the knowledge table:
|
|
5
|
+
* 1. Stale syntheses — decay confidence on old topic-synthesis entries
|
|
6
|
+
* 2. Orphan topics — topics with too few messages, stale > 48h
|
|
7
|
+
* 3. Coverage gaps — topics with many messages but no synthesis
|
|
8
|
+
*/
|
|
9
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
10
|
+
export interface LintResult {
|
|
11
|
+
staleDecayed: number;
|
|
12
|
+
orphansFound: number;
|
|
13
|
+
coverageGaps: string[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Run lint checks on the knowledge table.
|
|
17
|
+
*
|
|
18
|
+
* 1. Stale syntheses: topic-synthesis entries where the source topic's
|
|
19
|
+
* updated_at is older than LINT_STALE_DAYS and there are no new messages.
|
|
20
|
+
* Marks these with confidence = 0.3.
|
|
21
|
+
*
|
|
22
|
+
* 2. Orphan topics: topics with message_count < 3 and updated_at older than 48h.
|
|
23
|
+
* Logged but not synthesized.
|
|
24
|
+
*
|
|
25
|
+
* 3. Coverage gaps: topics with message_count >= 20 but no corresponding
|
|
26
|
+
* knowledge entry (domain='topic-synthesis', key=topic.name).
|
|
27
|
+
*/
|
|
28
|
+
export declare function lintKnowledge(libraryDb: DatabaseSync): LintResult;
|
|
29
|
+
//# sourceMappingURL=knowledge-lint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"knowledge-lint.d.ts","sourceRoot":"","sources":["../src/knowledge-lint.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,MAAM,WAAW,UAAU;IACzB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAID;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,SAAS,EAAE,YAAY,GAAG,UAAU,CAuGjE"}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Knowledge Lint
|
|
3
|
+
*
|
|
4
|
+
* Health checks for the knowledge table:
|
|
5
|
+
* 1. Stale syntheses — decay confidence on old topic-synthesis entries
|
|
6
|
+
* 2. Orphan topics — topics with too few messages, stale > 48h
|
|
7
|
+
* 3. Coverage gaps — topics with many messages but no synthesis
|
|
8
|
+
*/
|
|
9
|
+
import { LINT_STALE_DAYS } from './topic-synthesizer.js';
|
|
10
|
+
// ─── lintKnowledge ──────────────────────────────────────────────
|
|
11
|
+
/**
|
|
12
|
+
* Run lint checks on the knowledge table.
|
|
13
|
+
*
|
|
14
|
+
* 1. Stale syntheses: topic-synthesis entries where the source topic's
|
|
15
|
+
* updated_at is older than LINT_STALE_DAYS and there are no new messages.
|
|
16
|
+
* Marks these with confidence = 0.3.
|
|
17
|
+
*
|
|
18
|
+
* 2. Orphan topics: topics with message_count < 3 and updated_at older than 48h.
|
|
19
|
+
* Logged but not synthesized.
|
|
20
|
+
*
|
|
21
|
+
* 3. Coverage gaps: topics with message_count >= 20 but no corresponding
|
|
22
|
+
* knowledge entry (domain='topic-synthesis', key=topic.name).
|
|
23
|
+
*/
|
|
24
|
+
export function lintKnowledge(libraryDb) {
|
|
25
|
+
const result = {
|
|
26
|
+
staleDecayed: 0,
|
|
27
|
+
orphansFound: 0,
|
|
28
|
+
coverageGaps: [],
|
|
29
|
+
};
|
|
30
|
+
// ── 1. Stale syntheses ─────────────────────────────────────────
|
|
31
|
+
// Find topic-synthesis knowledge entries whose source topic hasn't been
|
|
32
|
+
// updated in LINT_STALE_DAYS days.
|
|
33
|
+
try {
|
|
34
|
+
const staleSyntheses = libraryDb.prepare(`
|
|
35
|
+
SELECT k.id, k.source_ref, k.agent_id, k.key
|
|
36
|
+
FROM knowledge k
|
|
37
|
+
WHERE k.domain = 'topic-synthesis'
|
|
38
|
+
AND k.superseded_by IS NULL
|
|
39
|
+
AND k.updated_at < datetime('now', '-${LINT_STALE_DAYS} days')
|
|
40
|
+
`).all();
|
|
41
|
+
for (const entry of staleSyntheses) {
|
|
42
|
+
// Extract topic id from source_ref: "topic:<id>" or "topic:<id>:mc:<count>"
|
|
43
|
+
if (!entry.source_ref)
|
|
44
|
+
continue;
|
|
45
|
+
const match = entry.source_ref.match(/^topic:(\d+)/);
|
|
46
|
+
if (!match)
|
|
47
|
+
continue;
|
|
48
|
+
const topicId = parseInt(match[1], 10);
|
|
49
|
+
// Check if topic exists and is stale (no recent updates)
|
|
50
|
+
let topicRow;
|
|
51
|
+
try {
|
|
52
|
+
topicRow = libraryDb.prepare('SELECT updated_at, message_count FROM topics WHERE id = ?').get(topicId);
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (!topicRow)
|
|
58
|
+
continue;
|
|
59
|
+
// Check if topic is stale (updated_at older than LINT_STALE_DAYS)
|
|
60
|
+
const topicAge = libraryDb.prepare(`
|
|
61
|
+
SELECT CASE WHEN datetime(?) < datetime('now', '-${LINT_STALE_DAYS} days') THEN 1 ELSE 0 END AS is_stale
|
|
62
|
+
`).get(topicRow.updated_at);
|
|
63
|
+
if (topicAge.is_stale) {
|
|
64
|
+
// Decay confidence to 0.3
|
|
65
|
+
libraryDb.prepare('UPDATE knowledge SET confidence = 0.3, updated_at = datetime(\'now\') WHERE id = ?').run(entry.id);
|
|
66
|
+
result.staleDecayed++;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
// Non-fatal — continue with other checks
|
|
72
|
+
}
|
|
73
|
+
// ── 2. Orphan topics ───────────────────────────────────────────
|
|
74
|
+
// Topics with message_count < 3 and updated_at > 48h ago
|
|
75
|
+
try {
|
|
76
|
+
const orphans = libraryDb.prepare(`
|
|
77
|
+
SELECT id, name, message_count, updated_at FROM topics
|
|
78
|
+
WHERE message_count < 3
|
|
79
|
+
AND updated_at < datetime('now', '-48 hours')
|
|
80
|
+
`).all();
|
|
81
|
+
result.orphansFound = orphans.length;
|
|
82
|
+
if (orphans.length > 0) {
|
|
83
|
+
console.log(`[lint] ${orphans.length} orphan topic(s) found (< 3 messages, stale > 48h): ${orphans.map(o => o.name).join(', ')}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
// Non-fatal
|
|
88
|
+
}
|
|
89
|
+
// ── 3. Coverage gaps ───────────────────────────────────────────
|
|
90
|
+
// Topics with >= 20 messages but no synthesis in knowledge table
|
|
91
|
+
try {
|
|
92
|
+
const bigTopics = libraryDb.prepare(`
|
|
93
|
+
SELECT t.name, t.agent_id
|
|
94
|
+
FROM topics t
|
|
95
|
+
WHERE t.message_count >= 20
|
|
96
|
+
`).all();
|
|
97
|
+
for (const topic of bigTopics) {
|
|
98
|
+
const synthesis = libraryDb.prepare(`
|
|
99
|
+
SELECT id FROM knowledge
|
|
100
|
+
WHERE agent_id = ?
|
|
101
|
+
AND domain = 'topic-synthesis'
|
|
102
|
+
AND key = ?
|
|
103
|
+
AND superseded_by IS NULL
|
|
104
|
+
LIMIT 1
|
|
105
|
+
`).get(topic.agent_id, topic.name);
|
|
106
|
+
if (!synthesis) {
|
|
107
|
+
result.coverageGaps.push(topic.name);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch {
|
|
112
|
+
// Non-fatal
|
|
113
|
+
}
|
|
114
|
+
return result;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=knowledge-lint.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"knowledge-store.d.ts","sourceRoot":"","sources":["../src/knowledge-store.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"knowledge-store.d.ts","sourceRoot":"","sources":["../src/knowledge-store.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAwB5C,MAAM,MAAM,QAAQ,GAAG,UAAU,GAAG,aAAa,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC;AAE5F,qBAAa,cAAc;IACb,OAAO,CAAC,QAAQ,CAAC,EAAE;gBAAF,EAAE,EAAE,YAAY;IAE7C;;;;;;;;;;;;OAYG;IACH,MAAM,CACJ,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,GAAG,EAAE,MAAM,EACX,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;QACL,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,GACA,SAAS;IA4EZ;;OAEG;IACH,SAAS,CACP,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;QACL,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,GACA,SAAS,EAAE;IAyBd;;OAEG;IACH,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,GAAG,IAAI;IAUnE;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,SAAS,EAAE;IAUrE;;OAEG;IACH,MAAM,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,GAAE,MAAW,GAAG,SAAS,EAAE;IAavE;;OAEG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE;IAUrC;;OAEG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI;IAO/D;;OAEG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM;IAOjC;;;OAGG;IACH,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;CAwDlF"}
|
package/dist/knowledge-store.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* hypermem Knowledge Store
|
|
3
3
|
*
|
|
4
4
|
* Long-term structured knowledge — replaces MEMORY.md.
|
|
5
5
|
* Lives in the central library DB.
|
|
6
6
|
* Knowledge entries are keyed (domain + key), versioned via superseded_by,
|
|
7
7
|
* and linked to each other via knowledge_links.
|
|
8
8
|
*/
|
|
9
|
+
import { isSafeForSharedVisibility, requiresScan } from './secret-scanner.js';
|
|
9
10
|
function nowIso() {
|
|
10
11
|
return new Date().toISOString();
|
|
11
12
|
}
|
|
@@ -47,7 +48,12 @@ export class KnowledgeStore {
|
|
|
47
48
|
const now = nowIso();
|
|
48
49
|
const sourceType = opts?.sourceType || 'manual';
|
|
49
50
|
const confidence = opts?.confidence ?? 1.0;
|
|
50
|
-
|
|
51
|
+
// Secret gate: if requested visibility is shared, verify content is clean.
|
|
52
|
+
// Downgrade to 'private' rather than reject — matches episode-store pattern.
|
|
53
|
+
let visibility = opts?.visibility ?? 'private';
|
|
54
|
+
if (requiresScan(visibility) && !isSafeForSharedVisibility(content)) {
|
|
55
|
+
visibility = 'private';
|
|
56
|
+
}
|
|
51
57
|
// Find current active entry (not superseded, not expired)
|
|
52
58
|
const existing = this.db.prepare(`
|
|
53
59
|
SELECT * FROM knowledge
|