@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.
Files changed (153) hide show
  1. package/ARCHITECTURE.md +4 -3
  2. package/README.md +457 -174
  3. package/dist/background-indexer.d.ts +19 -4
  4. package/dist/background-indexer.d.ts.map +1 -1
  5. package/dist/background-indexer.js +329 -17
  6. package/dist/cache.d.ts +110 -0
  7. package/dist/cache.d.ts.map +1 -0
  8. package/dist/cache.js +495 -0
  9. package/dist/compaction-fence.d.ts +1 -1
  10. package/dist/compaction-fence.js +1 -1
  11. package/dist/compositor.d.ts +114 -27
  12. package/dist/compositor.d.ts.map +1 -1
  13. package/dist/compositor.js +1678 -229
  14. package/dist/content-type-classifier.d.ts +41 -0
  15. package/dist/content-type-classifier.d.ts.map +1 -0
  16. package/dist/content-type-classifier.js +181 -0
  17. package/dist/cross-agent.d.ts +5 -0
  18. package/dist/cross-agent.d.ts.map +1 -1
  19. package/dist/cross-agent.js +5 -0
  20. package/dist/db.d.ts +1 -1
  21. package/dist/db.d.ts.map +1 -1
  22. package/dist/db.js +6 -2
  23. package/dist/desired-state-store.d.ts +1 -1
  24. package/dist/desired-state-store.d.ts.map +1 -1
  25. package/dist/desired-state-store.js +15 -5
  26. package/dist/doc-chunk-store.d.ts +26 -1
  27. package/dist/doc-chunk-store.d.ts.map +1 -1
  28. package/dist/doc-chunk-store.js +114 -1
  29. package/dist/doc-chunker.d.ts +1 -1
  30. package/dist/doc-chunker.js +1 -1
  31. package/dist/dreaming-promoter.d.ts +86 -0
  32. package/dist/dreaming-promoter.d.ts.map +1 -0
  33. package/dist/dreaming-promoter.js +381 -0
  34. package/dist/episode-store.d.ts +2 -1
  35. package/dist/episode-store.d.ts.map +1 -1
  36. package/dist/episode-store.js +4 -4
  37. package/dist/fact-store.d.ts +19 -1
  38. package/dist/fact-store.d.ts.map +1 -1
  39. package/dist/fact-store.js +64 -3
  40. package/dist/fleet-store.d.ts +1 -1
  41. package/dist/fleet-store.js +1 -1
  42. package/dist/fos-mod.d.ts +178 -0
  43. package/dist/fos-mod.d.ts.map +1 -0
  44. package/dist/fos-mod.js +416 -0
  45. package/dist/hybrid-retrieval.d.ts +5 -1
  46. package/dist/hybrid-retrieval.d.ts.map +1 -1
  47. package/dist/hybrid-retrieval.js +7 -3
  48. package/dist/image-eviction.d.ts +49 -0
  49. package/dist/image-eviction.d.ts.map +1 -0
  50. package/dist/image-eviction.js +251 -0
  51. package/dist/index.d.ts +50 -11
  52. package/dist/index.d.ts.map +1 -1
  53. package/dist/index.js +73 -43
  54. package/dist/keystone-scorer.d.ts +51 -0
  55. package/dist/keystone-scorer.d.ts.map +1 -0
  56. package/dist/keystone-scorer.js +52 -0
  57. package/dist/knowledge-graph.d.ts +1 -1
  58. package/dist/knowledge-graph.js +1 -1
  59. package/dist/knowledge-lint.d.ts +29 -0
  60. package/dist/knowledge-lint.d.ts.map +1 -0
  61. package/dist/knowledge-lint.js +116 -0
  62. package/dist/knowledge-store.d.ts +1 -1
  63. package/dist/knowledge-store.d.ts.map +1 -1
  64. package/dist/knowledge-store.js +8 -2
  65. package/dist/library-schema.d.ts +3 -3
  66. package/dist/library-schema.d.ts.map +1 -1
  67. package/dist/library-schema.js +324 -3
  68. package/dist/message-store.d.ts +15 -2
  69. package/dist/message-store.d.ts.map +1 -1
  70. package/dist/message-store.js +51 -1
  71. package/dist/metrics-dashboard.d.ts +114 -0
  72. package/dist/metrics-dashboard.d.ts.map +1 -0
  73. package/dist/metrics-dashboard.js +260 -0
  74. package/dist/obsidian-exporter.d.ts +57 -0
  75. package/dist/obsidian-exporter.d.ts.map +1 -0
  76. package/dist/obsidian-exporter.js +274 -0
  77. package/dist/obsidian-watcher.d.ts +147 -0
  78. package/dist/obsidian-watcher.d.ts.map +1 -0
  79. package/dist/obsidian-watcher.js +403 -0
  80. package/dist/open-domain.d.ts +46 -0
  81. package/dist/open-domain.d.ts.map +1 -0
  82. package/dist/open-domain.js +125 -0
  83. package/dist/preference-store.d.ts +1 -1
  84. package/dist/preference-store.js +1 -1
  85. package/dist/preservation-gate.d.ts +1 -1
  86. package/dist/preservation-gate.js +1 -1
  87. package/dist/proactive-pass.d.ts +63 -0
  88. package/dist/proactive-pass.d.ts.map +1 -0
  89. package/dist/proactive-pass.js +239 -0
  90. package/dist/profiles.d.ts +44 -0
  91. package/dist/profiles.d.ts.map +1 -0
  92. package/dist/profiles.js +227 -0
  93. package/dist/provider-translator.d.ts +13 -3
  94. package/dist/provider-translator.d.ts.map +1 -1
  95. package/dist/provider-translator.js +63 -9
  96. package/dist/rate-limiter.d.ts +1 -1
  97. package/dist/rate-limiter.js +1 -1
  98. package/dist/repair-tool-pairs.d.ts +38 -0
  99. package/dist/repair-tool-pairs.d.ts.map +1 -0
  100. package/dist/repair-tool-pairs.js +138 -0
  101. package/dist/retrieval-policy.d.ts +51 -0
  102. package/dist/retrieval-policy.d.ts.map +1 -0
  103. package/dist/retrieval-policy.js +77 -0
  104. package/dist/schema.d.ts +2 -2
  105. package/dist/schema.d.ts.map +1 -1
  106. package/dist/schema.js +28 -2
  107. package/dist/secret-scanner.d.ts +1 -1
  108. package/dist/secret-scanner.js +1 -1
  109. package/dist/seed.d.ts +2 -2
  110. package/dist/seed.js +2 -2
  111. package/dist/session-flusher.d.ts +53 -0
  112. package/dist/session-flusher.d.ts.map +1 -0
  113. package/dist/session-flusher.js +69 -0
  114. package/dist/session-topic-map.d.ts +41 -0
  115. package/dist/session-topic-map.d.ts.map +1 -0
  116. package/dist/session-topic-map.js +77 -0
  117. package/dist/spawn-context.d.ts +54 -0
  118. package/dist/spawn-context.d.ts.map +1 -0
  119. package/dist/spawn-context.js +159 -0
  120. package/dist/system-store.d.ts +1 -1
  121. package/dist/system-store.js +1 -1
  122. package/dist/temporal-store.d.ts +80 -0
  123. package/dist/temporal-store.d.ts.map +1 -0
  124. package/dist/temporal-store.js +149 -0
  125. package/dist/topic-detector.d.ts +35 -0
  126. package/dist/topic-detector.d.ts.map +1 -0
  127. package/dist/topic-detector.js +249 -0
  128. package/dist/topic-store.d.ts +1 -1
  129. package/dist/topic-store.js +1 -1
  130. package/dist/topic-synthesizer.d.ts +51 -0
  131. package/dist/topic-synthesizer.d.ts.map +1 -0
  132. package/dist/topic-synthesizer.js +315 -0
  133. package/dist/trigger-registry.d.ts +63 -0
  134. package/dist/trigger-registry.d.ts.map +1 -0
  135. package/dist/trigger-registry.js +163 -0
  136. package/dist/types.d.ts +214 -10
  137. package/dist/types.d.ts.map +1 -1
  138. package/dist/types.js +1 -1
  139. package/dist/vector-store.d.ts +43 -5
  140. package/dist/vector-store.d.ts.map +1 -1
  141. package/dist/vector-store.js +189 -10
  142. package/dist/version.d.ts +34 -0
  143. package/dist/version.d.ts.map +1 -0
  144. package/dist/version.js +34 -0
  145. package/dist/wiki-page-emitter.d.ts +65 -0
  146. package/dist/wiki-page-emitter.d.ts.map +1 -0
  147. package/dist/wiki-page-emitter.js +258 -0
  148. package/dist/work-store.d.ts +1 -1
  149. package/dist/work-store.js +1 -1
  150. package/package.json +15 -5
  151. package/dist/redis.d.ts +0 -188
  152. package/dist/redis.d.ts.map +0 -1
  153. package/dist/redis.js +0 -534
@@ -0,0 +1,125 @@
1
+ /**
2
+ * open-domain.ts — Open-domain query detection and FTS5 retrieval
3
+ *
4
+ * LoCoMo benchmark open-domain questions are broad, exploratory, and have no
5
+ * topical anchor. They span the full conversation history and require content
6
+ * that may have been filtered out by the quality gate (isQualityFact). The
7
+ * fix: detect open-domain queries and run a separate FTS5 search against raw
8
+ * messages_fts, bypassing the quality filter entirely.
9
+ *
10
+ * Detection heuristics (conservative — false positives add noise):
11
+ * - Short query with no named entities (no TitleCase tokens)
12
+ * - Broad interrogative patterns (what did, how did, tell me about, etc.)
13
+ * - No temporal signals (those go to the temporal retrieval path)
14
+ * - No specific identifiers (URLs, IDs, ticket numbers, version strings)
15
+ *
16
+ * Retrieval: MessageStore.searchMessages() against messages_fts — covers all
17
+ * raw message history regardless of quality gate.
18
+ */
19
+ // ── Open-domain signal patterns ───────────────────────────────────────────
20
+ const BROAD_INTERROGATIVE = /\b(what did|what does|what has|what was|what were|what is|how did|how does|how has|tell me about|describe|explain|summarize|overview|recap|what do you know about|what have|who is|who was|who did)\b/i;
21
+ const SPECIFIC_ANCHOR = /\b([A-Z][a-z]{2,}(?:\s+[A-Z][a-z]{2,})+|v\d+\.\d+|#\d{2,}|https?:\/\/|[A-Z]{2,}-\d+)\b/;
22
+ const TEMPORAL_SIGNALS = /\b(before|after|when|last\s+\w+|yesterday|today|recently|between|since|until|ago|this\s+week|this\s+month|in\s+(january|february|march|april|may|june|july|august|september|october|november|december))\b/i;
23
+ /**
24
+ * Returns true if the query looks like an open-domain question:
25
+ * broad, exploratory, no specific anchors, no temporal signals.
26
+ */
27
+ export function isOpenDomainQuery(query) {
28
+ if (!query || query.trim().length < 8)
29
+ return false;
30
+ // Has temporal signals → temporal path handles it
31
+ if (TEMPORAL_SIGNALS.test(query))
32
+ return false;
33
+ // Has specific named entity / version / ticket anchor → not open-domain
34
+ if (SPECIFIC_ANCHOR.test(query))
35
+ return false;
36
+ // Must match a broad interrogative pattern
37
+ if (!BROAD_INTERROGATIVE.test(query))
38
+ return false;
39
+ // Sanity: query should not be too long (long queries are usually specific)
40
+ const wordCount = query.trim().split(/\s+/).length;
41
+ if (wordCount > 20)
42
+ return false;
43
+ return true;
44
+ }
45
+ // ── FTS5 query builder ────────────────────────────────────────────────────
46
+ /**
47
+ * Build a FTS5 MATCH query from a broad question.
48
+ * Strips stop words, question words, and punctuation.
49
+ * Returns up to 6 prefix-matched terms joined with OR.
50
+ */
51
+ export function buildOpenDomainFtsQuery(query) {
52
+ const STOP_WORDS = new Set([
53
+ 'what', 'did', 'does', 'has', 'was', 'were', 'is', 'are', 'how',
54
+ 'tell', 'me', 'about', 'describe', 'explain', 'summarize', 'overview',
55
+ 'recap', 'who', 'do', 'you', 'know', 'have', 'the', 'a', 'an', 'of',
56
+ 'in', 'on', 'at', 'to', 'for', 'and', 'or', 'but', 'with', 'from',
57
+ ]);
58
+ const terms = query
59
+ .toLowerCase()
60
+ .replace(/[^a-z0-9\s]/g, ' ')
61
+ .split(/\s+/)
62
+ .filter(w => w.length >= 3 && !STOP_WORDS.has(w))
63
+ .slice(0, 6)
64
+ .map(w => `${w}*`);
65
+ if (terms.length === 0)
66
+ return null;
67
+ return terms.join(' OR ');
68
+ }
69
+ /**
70
+ * Search raw message history via FTS5 for open-domain queries.
71
+ * Returns up to `limit` matching messages, deduplicated against existing context.
72
+ *
73
+ * @param db — agent messages DB (contains messages_fts)
74
+ * @param query — the user's query
75
+ * @param existingContent — already-assembled context (for dedup)
76
+ * @param limit — max results (default 10)
77
+ */
78
+ export function searchOpenDomain(db, query, existingContent, limit = 10) {
79
+ const ftsQuery = buildOpenDomainFtsQuery(query);
80
+ if (!ftsQuery)
81
+ return [];
82
+ try {
83
+ const rows = db.prepare(`
84
+ WITH fts_matches AS (
85
+ SELECT rowid, rank
86
+ FROM messages_fts
87
+ WHERE messages_fts MATCH ?
88
+ ORDER BY rank
89
+ LIMIT ?
90
+ )
91
+ SELECT
92
+ m.role,
93
+ m.text_content AS content,
94
+ m.created_at AS createdAt
95
+ FROM messages m
96
+ JOIN fts_matches ON m.id = fts_matches.rowid
97
+ WHERE m.text_content IS NOT NULL
98
+ AND m.text_content != ''
99
+ AND m.is_heartbeat = 0
100
+ ORDER BY fts_matches.rank
101
+ `).all(ftsQuery, limit * 2);
102
+ // Deduplicate against existing context and filter short content
103
+ const seen = new Set();
104
+ const results = [];
105
+ for (const row of rows) {
106
+ if (!row.content || row.content.trim().length < 20)
107
+ continue;
108
+ const fingerprint = row.content.slice(0, 80);
109
+ if (seen.has(fingerprint))
110
+ continue;
111
+ if (existingContent.includes(fingerprint))
112
+ continue;
113
+ seen.add(fingerprint);
114
+ results.push(row);
115
+ if (results.length >= limit)
116
+ break;
117
+ }
118
+ return results;
119
+ }
120
+ catch {
121
+ // FTS query may fail on special characters — degrade silently
122
+ return [];
123
+ }
124
+ }
125
+ //# sourceMappingURL=open-domain.js.map
@@ -1,5 +1,5 @@
1
1
  /**
2
- * HyperMem Preference Store
2
+ * hypermem Preference Store
3
3
  *
4
4
  * Behavioral patterns observed about people, systems, and workflows.
5
5
  * Lives in the central library DB.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * HyperMem Preference Store
2
+ * hypermem Preference Store
3
3
  *
4
4
  * Behavioral patterns observed about people, systems, and workflows.
5
5
  * Lives in the central library DB.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * HyperMem Preservation Gate
2
+ * hypermem Preservation Gate
3
3
  *
4
4
  * Verifies that a proposed compaction summary preserves the semantic
5
5
  * content of its source messages by measuring geometric fidelity in
@@ -1,5 +1,5 @@
1
1
  /**
2
- * HyperMem Preservation Gate
2
+ * hypermem Preservation Gate
3
3
  *
4
4
  * Verifies that a proposed compaction summary preserves the semantic
5
5
  * content of its source messages by measuring geometric fidelity in
@@ -0,0 +1,63 @@
1
+ /**
2
+ * hypermem Proactive Passes
3
+ *
4
+ * Background maintenance passes that run between indexer ticks to keep
5
+ * message storage lean. Two passes:
6
+ *
7
+ * 1. Noise Sweep — deletes low/zero-signal messages outside the recent
8
+ * window (heartbeats, acks, empty strings, control tokens).
9
+ *
10
+ * 2. Tool Decay — truncates oversized tool_results outside the recent
11
+ * window in-place, preserving JSON structure but collapsing large
12
+ * content blobs into a byte-count placeholder.
13
+ *
14
+ * Both passes are:
15
+ * - Synchronous (DatabaseSync, no async)
16
+ * - Wrapped in transactions (atomic)
17
+ * - Best-effort: catch all errors, log, and return a zero-change result
18
+ *
19
+ * Ported and adapted from ClawText proactive-pass.ts.
20
+ * hypermem schema differences vs ClawText:
21
+ * - No content_type column — we classify on the fly via classifyContentType()
22
+ * - No external payload store — we truncate content inline in tool_results JSON
23
+ * - No ClawText-specific dependencies (payload-store, tool-tracker, etc.)
24
+ */
25
+ import type { DatabaseSync } from 'node:sqlite';
26
+ export interface NoiseSweepResult {
27
+ messagesDeleted: number;
28
+ passType: 'noise_sweep';
29
+ }
30
+ export interface ToolDecayResult {
31
+ messagesUpdated: number;
32
+ bytesFreed: number;
33
+ passType: 'tool_decay';
34
+ }
35
+ /**
36
+ * Delete noise and heartbeat messages outside the recent window.
37
+ *
38
+ * "Outside the recent window" means message_index < maxIndex - recentWindowSize.
39
+ * Messages inside the window are never deleted, even if they are noise —
40
+ * the model may still reference them in the current turn.
41
+ *
42
+ * Deletions are wrapped in a single transaction. The FTS5 trigger handles
43
+ * index cleanup automatically (msg_fts_ad fires on DELETE).
44
+ */
45
+ export declare function runNoiseSweep(db: DatabaseSync, conversationId: number, recentWindowSize?: number): NoiseSweepResult;
46
+ /**
47
+ * Truncate oversized tool_results outside the recent window.
48
+ *
49
+ * Strategy:
50
+ * 1. Find messages whose tool_results JSON string is > 2000 chars total,
51
+ * outside the recent window.
52
+ * 2. Parse the JSON array.
53
+ * 3. For each result entry where the `content` field exceeds 500 chars,
54
+ * replace `content` with `[tool result truncated — N bytes]`.
55
+ * 4. Re-serialize and write back.
56
+ *
57
+ * The JSON structure is preserved (array of result objects). Only the
58
+ * oversized `content` values are collapsed.
59
+ *
60
+ * Mutations are committed in a single transaction.
61
+ */
62
+ export declare function runToolDecay(db: DatabaseSync, conversationId: number, recentWindowSize?: number): ToolDecayResult;
63
+ //# sourceMappingURL=proactive-pass.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"proactive-pass.d.ts","sourceRoot":"","sources":["../src/proactive-pass.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAKhD,MAAM,WAAW,gBAAgB;IAC/B,eAAe,EAAE,MAAM,CAAC;IACxB,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,YAAY,CAAC;CACxB;AA6CD;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAC3B,EAAE,EAAE,YAAY,EAChB,cAAc,EAAE,MAAM,EACtB,gBAAgB,GAAE,MAAW,GAC5B,gBAAgB,CA2ElB;AAID;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,YAAY,CAC1B,EAAE,EAAE,YAAY,EAChB,cAAc,EAAE,MAAM,EACtB,gBAAgB,GAAE,MAAW,GAC5B,eAAe,CAiGjB"}
@@ -0,0 +1,239 @@
1
+ /**
2
+ * hypermem Proactive Passes
3
+ *
4
+ * Background maintenance passes that run between indexer ticks to keep
5
+ * message storage lean. Two passes:
6
+ *
7
+ * 1. Noise Sweep — deletes low/zero-signal messages outside the recent
8
+ * window (heartbeats, acks, empty strings, control tokens).
9
+ *
10
+ * 2. Tool Decay — truncates oversized tool_results outside the recent
11
+ * window in-place, preserving JSON structure but collapsing large
12
+ * content blobs into a byte-count placeholder.
13
+ *
14
+ * Both passes are:
15
+ * - Synchronous (DatabaseSync, no async)
16
+ * - Wrapped in transactions (atomic)
17
+ * - Best-effort: catch all errors, log, and return a zero-change result
18
+ *
19
+ * Ported and adapted from ClawText proactive-pass.ts.
20
+ * hypermem schema differences vs ClawText:
21
+ * - No content_type column — we classify on the fly via classifyContentType()
22
+ * - No external payload store — we truncate content inline in tool_results JSON
23
+ * - No ClawText-specific dependencies (payload-store, tool-tracker, etc.)
24
+ */
25
+ import { classifyContentType } from './content-type-classifier.js';
26
+ // ─── Internal helpers ────────────────────────────────────────────
27
+ /**
28
+ * Resolve the safe window to a finite positive integer.
29
+ * Mirrors the ClawText resolveSafeWindow() guard.
30
+ */
31
+ function resolveSafeWindow(recentWindowSize) {
32
+ if (Number.isFinite(recentWindowSize) && recentWindowSize > 0) {
33
+ return Math.floor(recentWindowSize);
34
+ }
35
+ return 20;
36
+ }
37
+ /**
38
+ * Get the maximum message_index for a conversation.
39
+ * Returns -1 if no messages exist.
40
+ */
41
+ function getMaxMessageIndex(db, conversationId) {
42
+ const row = db
43
+ .prepare('SELECT COALESCE(MAX(message_index), -1) AS max_index FROM messages WHERE conversation_id = ?')
44
+ .get(conversationId);
45
+ return typeof row.max_index === 'number' ? row.max_index : -1;
46
+ }
47
+ /**
48
+ * Decide if a message is noise based on content + is_heartbeat flag.
49
+ *
50
+ * A message is noise when:
51
+ * - is_heartbeat = 1 (explicit heartbeat marker), OR
52
+ * - text_content is NULL or empty (≤3 chars after trimming), OR
53
+ * - classifyContentType() returns 'noise' or 'ack'
54
+ *
55
+ * We call the classifier rather than duplicating its patterns here.
56
+ */
57
+ function isNoiseMessage(textContent, isHeartbeat) {
58
+ if (isHeartbeat === 1)
59
+ return true;
60
+ if (textContent === null || textContent.trim().length <= 3)
61
+ return true;
62
+ const { type } = classifyContentType(textContent);
63
+ return type === 'noise' || type === 'ack';
64
+ }
65
+ // ─── Noise Sweep ─────────────────────────────────────────────────
66
+ /**
67
+ * Delete noise and heartbeat messages outside the recent window.
68
+ *
69
+ * "Outside the recent window" means message_index < maxIndex - recentWindowSize.
70
+ * Messages inside the window are never deleted, even if they are noise —
71
+ * the model may still reference them in the current turn.
72
+ *
73
+ * Deletions are wrapped in a single transaction. The FTS5 trigger handles
74
+ * index cleanup automatically (msg_fts_ad fires on DELETE).
75
+ */
76
+ export function runNoiseSweep(db, conversationId, recentWindowSize = 20) {
77
+ const ZERO = { messagesDeleted: 0, passType: 'noise_sweep' };
78
+ try {
79
+ const safeWindow = resolveSafeWindow(recentWindowSize);
80
+ const maxIndex = getMaxMessageIndex(db, conversationId);
81
+ if (maxIndex < 0)
82
+ return ZERO;
83
+ // Messages with message_index strictly below this value are eligible.
84
+ const cutoff = maxIndex - safeWindow;
85
+ if (cutoff <= 0)
86
+ return ZERO; // Not enough history yet
87
+ // Fetch all candidate messages outside the recent window.
88
+ // Exclude messages whose content lives entirely in tool_results — those
89
+ // are tool result rows handled by runToolDecay(), not noise sweep.
90
+ // We deliberately avoid a content-based WHERE clause for the classifier
91
+ // because SQLite can't use the index for JS classification logic;
92
+ // it's cheaper to fetch a small batch and classify in JS.
93
+ const candidates = db
94
+ .prepare(`
95
+ SELECT id, text_content, is_heartbeat
96
+ FROM messages
97
+ WHERE conversation_id = ?
98
+ AND message_index < ?
99
+ AND (tool_results IS NULL OR tool_results = '')
100
+ `)
101
+ .all(conversationId, cutoff);
102
+ if (candidates.length === 0)
103
+ return ZERO;
104
+ // Filter to noise messages
105
+ const toDelete = candidates.filter(row => isNoiseMessage(row.text_content, row.is_heartbeat));
106
+ if (toDelete.length === 0)
107
+ return ZERO;
108
+ const ids = toDelete.map(r => r.id);
109
+ // Delete in a transaction; use chunked IN clauses to avoid
110
+ // SQLite's SQLITE_LIMIT_VARIABLE_NUMBER (default 999).
111
+ let totalDeleted = 0;
112
+ const CHUNK = 500;
113
+ db.prepare('BEGIN').run();
114
+ try {
115
+ for (let i = 0; i < ids.length; i += CHUNK) {
116
+ const chunk = ids.slice(i, i + CHUNK);
117
+ const placeholders = chunk.map(() => '?').join(', ');
118
+ const result = db
119
+ .prepare(`DELETE FROM messages WHERE id IN (${placeholders})`)
120
+ .run(...chunk);
121
+ totalDeleted += typeof result.changes === 'number' ? result.changes : chunk.length;
122
+ }
123
+ db.prepare('COMMIT').run();
124
+ }
125
+ catch (innerErr) {
126
+ db.prepare('ROLLBACK').run();
127
+ throw innerErr;
128
+ }
129
+ return { messagesDeleted: totalDeleted, passType: 'noise_sweep' };
130
+ }
131
+ catch (err) {
132
+ console.warn(`[proactive-pass] Noise sweep failed for conversation ${conversationId}: ${err instanceof Error ? err.message : String(err)}`);
133
+ return ZERO;
134
+ }
135
+ }
136
+ // ─── Tool Decay ──────────────────────────────────────────────────
137
+ /**
138
+ * Truncate oversized tool_results outside the recent window.
139
+ *
140
+ * Strategy:
141
+ * 1. Find messages whose tool_results JSON string is > 2000 chars total,
142
+ * outside the recent window.
143
+ * 2. Parse the JSON array.
144
+ * 3. For each result entry where the `content` field exceeds 500 chars,
145
+ * replace `content` with `[tool result truncated — N bytes]`.
146
+ * 4. Re-serialize and write back.
147
+ *
148
+ * The JSON structure is preserved (array of result objects). Only the
149
+ * oversized `content` values are collapsed.
150
+ *
151
+ * Mutations are committed in a single transaction.
152
+ */
153
+ export function runToolDecay(db, conversationId, recentWindowSize = 40) {
154
+ const ZERO = { messagesUpdated: 0, bytesFreed: 0, passType: 'tool_decay' };
155
+ try {
156
+ const safeWindow = resolveSafeWindow(recentWindowSize);
157
+ const maxIndex = getMaxMessageIndex(db, conversationId);
158
+ if (maxIndex < 0)
159
+ return ZERO;
160
+ const cutoff = maxIndex - safeWindow;
161
+ if (cutoff <= 0)
162
+ return ZERO;
163
+ // Fetch messages with large tool_results outside the recent window.
164
+ const candidates = db
165
+ .prepare(`
166
+ SELECT id, tool_results
167
+ FROM messages
168
+ WHERE conversation_id = ?
169
+ AND message_index < ?
170
+ AND tool_results IS NOT NULL
171
+ AND length(tool_results) > 2000
172
+ `)
173
+ .all(conversationId, cutoff);
174
+ if (candidates.length === 0)
175
+ return ZERO;
176
+ // Build the update list by processing each candidate.
177
+ const updates = [];
178
+ for (const row of candidates) {
179
+ let parsed;
180
+ try {
181
+ parsed = JSON.parse(row.tool_results);
182
+ }
183
+ catch {
184
+ // Corrupt JSON — skip this row
185
+ continue;
186
+ }
187
+ if (!Array.isArray(parsed))
188
+ continue;
189
+ let changed = false;
190
+ const newResults = parsed.map(entry => {
191
+ const content = entry.content;
192
+ if (typeof content === 'string' && content.length > 500) {
193
+ const originalBytes = Buffer.byteLength(content, 'utf8');
194
+ changed = true;
195
+ return {
196
+ ...entry,
197
+ content: `[tool result truncated — ${originalBytes} bytes]`,
198
+ };
199
+ }
200
+ return entry;
201
+ });
202
+ if (!changed)
203
+ continue;
204
+ const newJson = JSON.stringify(newResults);
205
+ const savedBytes = row.tool_results.length - newJson.length;
206
+ if (savedBytes > 0) {
207
+ updates.push({ id: row.id, newJson, savedBytes });
208
+ }
209
+ }
210
+ if (updates.length === 0)
211
+ return ZERO;
212
+ let totalUpdated = 0;
213
+ let totalBytesFreed = 0;
214
+ db.prepare('BEGIN').run();
215
+ try {
216
+ const stmt = db.prepare('UPDATE messages SET tool_results = ? WHERE id = ?');
217
+ for (const { id, newJson, savedBytes } of updates) {
218
+ stmt.run(newJson, id);
219
+ totalUpdated++;
220
+ totalBytesFreed += savedBytes;
221
+ }
222
+ db.prepare('COMMIT').run();
223
+ }
224
+ catch (innerErr) {
225
+ db.prepare('ROLLBACK').run();
226
+ throw innerErr;
227
+ }
228
+ return {
229
+ messagesUpdated: totalUpdated,
230
+ bytesFreed: totalBytesFreed,
231
+ passType: 'tool_decay',
232
+ };
233
+ }
234
+ catch (err) {
235
+ console.warn(`[proactive-pass] Tool decay failed for conversation ${conversationId}: ${err instanceof Error ? err.message : String(err)}`);
236
+ return ZERO;
237
+ }
238
+ }
239
+ //# sourceMappingURL=proactive-pass.js.map
@@ -0,0 +1,44 @@
1
+ /**
2
+ * hypermem configuration profiles
3
+ *
4
+ * Pre-built configs for common deployment patterns. Pass to createHyperMem()
5
+ * directly or use as a base for custom configs via mergeProfile().
6
+ *
7
+ * Profiles:
8
+ * minimal — 64k context, single agent, low resource usage
9
+ * standard — 128k context, fleet default, balanced
10
+ * rich — 200k+ context, multi-agent, full feature set
11
+ */
12
+ import type { HyperMemConfig } from './types.js';
13
+ export declare const lightProfile: HyperMemConfig;
14
+ export declare const standardProfile: HyperMemConfig;
15
+ export declare const fullProfile: HyperMemConfig;
16
+ export type ProfileName = 'light' | 'standard' | 'full';
17
+ export declare const minimalProfile: HyperMemConfig;
18
+ export declare const extendedProfile: HyperMemConfig;
19
+ export declare const richProfile: HyperMemConfig;
20
+ export declare const PROFILES: Record<ProfileName, HyperMemConfig>;
21
+ /**
22
+ * Load a named profile.
23
+ *
24
+ * @example
25
+ * const config = getProfile('light');
26
+ * const hm = createHyperMem(config);
27
+ */
28
+ export declare function getProfile(name: ProfileName | 'extended'): HyperMemConfig;
29
+ /**
30
+ * Merge a partial config on top of a named profile.
31
+ * Deep-merges compositor and indexer; top-level fields are replaced.
32
+ *
33
+ * @example
34
+ * const config = mergeProfile('light', {
35
+ * cache: { keyPrefix: 'myapp:' },
36
+ * compositor: { outputProfile: 'standard' }, // upgrade tier
37
+ * });
38
+ */
39
+ export declare function mergeProfile(name: ProfileName | 'extended', overrides: DeepPartial<HyperMemConfig>): HyperMemConfig;
40
+ type DeepPartial<T> = {
41
+ [K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
42
+ };
43
+ export {};
44
+ //# sourceMappingURL=profiles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profiles.d.ts","sourceRoot":"","sources":["../src/profiles.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAyE,MAAM,YAAY,CAAC;AAiExH,eAAO,MAAM,YAAY,EAAE,cAa1B,CAAC;AA6CF,eAAO,MAAM,eAAe,EAAE,cAO7B,CAAC;AA8CF,eAAO,MAAM,WAAW,EAAE,cAWzB,CAAC;AAMF,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,UAAU,GAAG,MAAM,CAAC;AAGxD,eAAO,MAAM,cAAc,gBAAe,CAAC;AAC3C,eAAO,MAAM,eAAe,gBAAc,CAAC;AAC3C,eAAO,MAAM,WAAW,gBAAc,CAAC;AAEvC,eAAO,MAAM,QAAQ,EAAE,MAAM,CAAC,WAAW,EAAE,cAAc,CAIxD,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,WAAW,GAAG,UAAU,GAAG,cAAc,CAIzE;AAED;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,WAAW,GAAG,UAAU,EAC9B,SAAS,EAAE,WAAW,CAAC,cAAc,CAAC,GACrC,cAAc,CAUhB;AAMD,KAAK,WAAW,CAAC,CAAC,IAAI;KACnB,CAAC,IAAI,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,SAAS,MAAM,GAAG,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;CAChE,CAAC"}