@psiclawops/hypermem 0.5.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 (160) hide show
  1. package/dist/background-indexer.d.ts +132 -0
  2. package/dist/background-indexer.d.ts.map +1 -0
  3. package/dist/background-indexer.js +1044 -0
  4. package/dist/cache.d.ts +110 -0
  5. package/dist/cache.d.ts.map +1 -0
  6. package/dist/cache.js +495 -0
  7. package/dist/compaction-fence.d.ts +89 -0
  8. package/dist/compaction-fence.d.ts.map +1 -0
  9. package/dist/compaction-fence.js +153 -0
  10. package/dist/compositor.d.ts +226 -0
  11. package/dist/compositor.d.ts.map +1 -0
  12. package/dist/compositor.js +2558 -0
  13. package/dist/content-type-classifier.d.ts +41 -0
  14. package/dist/content-type-classifier.d.ts.map +1 -0
  15. package/dist/content-type-classifier.js +181 -0
  16. package/dist/cross-agent.d.ts +62 -0
  17. package/dist/cross-agent.d.ts.map +1 -0
  18. package/dist/cross-agent.js +259 -0
  19. package/dist/db.d.ts +131 -0
  20. package/dist/db.d.ts.map +1 -0
  21. package/dist/db.js +402 -0
  22. package/dist/desired-state-store.d.ts +100 -0
  23. package/dist/desired-state-store.d.ts.map +1 -0
  24. package/dist/desired-state-store.js +222 -0
  25. package/dist/doc-chunk-store.d.ts +140 -0
  26. package/dist/doc-chunk-store.d.ts.map +1 -0
  27. package/dist/doc-chunk-store.js +391 -0
  28. package/dist/doc-chunker.d.ts +99 -0
  29. package/dist/doc-chunker.d.ts.map +1 -0
  30. package/dist/doc-chunker.js +324 -0
  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 +49 -0
  35. package/dist/episode-store.d.ts.map +1 -0
  36. package/dist/episode-store.js +135 -0
  37. package/dist/fact-store.d.ts +75 -0
  38. package/dist/fact-store.d.ts.map +1 -0
  39. package/dist/fact-store.js +236 -0
  40. package/dist/fleet-store.d.ts +144 -0
  41. package/dist/fleet-store.d.ts.map +1 -0
  42. package/dist/fleet-store.js +276 -0
  43. package/dist/fos-mod.d.ts +178 -0
  44. package/dist/fos-mod.d.ts.map +1 -0
  45. package/dist/fos-mod.js +416 -0
  46. package/dist/hybrid-retrieval.d.ts +64 -0
  47. package/dist/hybrid-retrieval.d.ts.map +1 -0
  48. package/dist/hybrid-retrieval.js +344 -0
  49. package/dist/image-eviction.d.ts +49 -0
  50. package/dist/image-eviction.d.ts.map +1 -0
  51. package/dist/image-eviction.js +251 -0
  52. package/dist/index.d.ts +650 -0
  53. package/dist/index.d.ts.map +1 -0
  54. package/dist/index.js +1072 -0
  55. package/dist/keystone-scorer.d.ts +51 -0
  56. package/dist/keystone-scorer.d.ts.map +1 -0
  57. package/dist/keystone-scorer.js +52 -0
  58. package/dist/knowledge-graph.d.ts +110 -0
  59. package/dist/knowledge-graph.d.ts.map +1 -0
  60. package/dist/knowledge-graph.js +305 -0
  61. package/dist/knowledge-lint.d.ts +29 -0
  62. package/dist/knowledge-lint.d.ts.map +1 -0
  63. package/dist/knowledge-lint.js +116 -0
  64. package/dist/knowledge-store.d.ts +72 -0
  65. package/dist/knowledge-store.d.ts.map +1 -0
  66. package/dist/knowledge-store.js +247 -0
  67. package/dist/library-schema.d.ts +22 -0
  68. package/dist/library-schema.d.ts.map +1 -0
  69. package/dist/library-schema.js +1038 -0
  70. package/dist/message-store.d.ts +89 -0
  71. package/dist/message-store.d.ts.map +1 -0
  72. package/dist/message-store.js +323 -0
  73. package/dist/metrics-dashboard.d.ts +114 -0
  74. package/dist/metrics-dashboard.d.ts.map +1 -0
  75. package/dist/metrics-dashboard.js +260 -0
  76. package/dist/obsidian-exporter.d.ts +57 -0
  77. package/dist/obsidian-exporter.d.ts.map +1 -0
  78. package/dist/obsidian-exporter.js +274 -0
  79. package/dist/obsidian-watcher.d.ts +147 -0
  80. package/dist/obsidian-watcher.d.ts.map +1 -0
  81. package/dist/obsidian-watcher.js +403 -0
  82. package/dist/open-domain.d.ts +46 -0
  83. package/dist/open-domain.d.ts.map +1 -0
  84. package/dist/open-domain.js +125 -0
  85. package/dist/preference-store.d.ts +54 -0
  86. package/dist/preference-store.d.ts.map +1 -0
  87. package/dist/preference-store.js +109 -0
  88. package/dist/preservation-gate.d.ts +82 -0
  89. package/dist/preservation-gate.d.ts.map +1 -0
  90. package/dist/preservation-gate.js +150 -0
  91. package/dist/proactive-pass.d.ts +63 -0
  92. package/dist/proactive-pass.d.ts.map +1 -0
  93. package/dist/proactive-pass.js +239 -0
  94. package/dist/profiles.d.ts +44 -0
  95. package/dist/profiles.d.ts.map +1 -0
  96. package/dist/profiles.js +227 -0
  97. package/dist/provider-translator.d.ts +50 -0
  98. package/dist/provider-translator.d.ts.map +1 -0
  99. package/dist/provider-translator.js +403 -0
  100. package/dist/rate-limiter.d.ts +76 -0
  101. package/dist/rate-limiter.d.ts.map +1 -0
  102. package/dist/rate-limiter.js +179 -0
  103. package/dist/repair-tool-pairs.d.ts +38 -0
  104. package/dist/repair-tool-pairs.d.ts.map +1 -0
  105. package/dist/repair-tool-pairs.js +138 -0
  106. package/dist/retrieval-policy.d.ts +51 -0
  107. package/dist/retrieval-policy.d.ts.map +1 -0
  108. package/dist/retrieval-policy.js +77 -0
  109. package/dist/schema.d.ts +15 -0
  110. package/dist/schema.d.ts.map +1 -0
  111. package/dist/schema.js +229 -0
  112. package/dist/secret-scanner.d.ts +51 -0
  113. package/dist/secret-scanner.d.ts.map +1 -0
  114. package/dist/secret-scanner.js +248 -0
  115. package/dist/seed.d.ts +108 -0
  116. package/dist/seed.d.ts.map +1 -0
  117. package/dist/seed.js +177 -0
  118. package/dist/session-flusher.d.ts +53 -0
  119. package/dist/session-flusher.d.ts.map +1 -0
  120. package/dist/session-flusher.js +69 -0
  121. package/dist/session-topic-map.d.ts +41 -0
  122. package/dist/session-topic-map.d.ts.map +1 -0
  123. package/dist/session-topic-map.js +77 -0
  124. package/dist/spawn-context.d.ts +54 -0
  125. package/dist/spawn-context.d.ts.map +1 -0
  126. package/dist/spawn-context.js +159 -0
  127. package/dist/system-store.d.ts +73 -0
  128. package/dist/system-store.d.ts.map +1 -0
  129. package/dist/system-store.js +182 -0
  130. package/dist/temporal-store.d.ts +80 -0
  131. package/dist/temporal-store.d.ts.map +1 -0
  132. package/dist/temporal-store.js +149 -0
  133. package/dist/topic-detector.d.ts +35 -0
  134. package/dist/topic-detector.d.ts.map +1 -0
  135. package/dist/topic-detector.js +249 -0
  136. package/dist/topic-store.d.ts +45 -0
  137. package/dist/topic-store.d.ts.map +1 -0
  138. package/dist/topic-store.js +136 -0
  139. package/dist/topic-synthesizer.d.ts +51 -0
  140. package/dist/topic-synthesizer.d.ts.map +1 -0
  141. package/dist/topic-synthesizer.js +315 -0
  142. package/dist/trigger-registry.d.ts +63 -0
  143. package/dist/trigger-registry.d.ts.map +1 -0
  144. package/dist/trigger-registry.js +163 -0
  145. package/dist/types.d.ts +533 -0
  146. package/dist/types.d.ts.map +1 -0
  147. package/dist/types.js +9 -0
  148. package/dist/vector-store.d.ts +170 -0
  149. package/dist/vector-store.d.ts.map +1 -0
  150. package/dist/vector-store.js +677 -0
  151. package/dist/version.d.ts +34 -0
  152. package/dist/version.d.ts.map +1 -0
  153. package/dist/version.js +34 -0
  154. package/dist/wiki-page-emitter.d.ts +65 -0
  155. package/dist/wiki-page-emitter.d.ts.map +1 -0
  156. package/dist/wiki-page-emitter.js +258 -0
  157. package/dist/work-store.d.ts +112 -0
  158. package/dist/work-store.d.ts.map +1 -0
  159. package/dist/work-store.js +273 -0
  160. package/package.json +1 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repair-tool-pairs.d.ts","sourceRoot":"","sources":["../src/repair-tool-pairs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAE1C;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,UAAU,EAAE,CAmHpE"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * repair-tool-pairs.ts
3
+ *
4
+ * Strips orphaned tool result entries from a pi-agent message array.
5
+ *
6
+ * Background: HyperMem compaction and in-memory trim passes can remove assistant
7
+ * messages that contain tool_use/toolCall blocks without removing the corresponding
8
+ * tool result messages that follow them. Anthropic and Gemini reject these orphaned
9
+ * tool results with a 400 error.
10
+ *
11
+ * This module provides a pure repair function that can be applied at any output
12
+ * boundary to sanitise the message list before it reaches the provider.
13
+ *
14
+ * Supported formats:
15
+ * - pi-agent: role:'toolResult' messages with toolCallId field
16
+ * - Anthropic native: user messages with content blocks of type:'tool_result' and tool_use_id
17
+ *
18
+ * Returns a new array. Does not mutate the input.
19
+ */
20
+ /**
21
+ * Repair orphaned tool pairs in a pi-agent / OpenClaw message array.
22
+ *
23
+ * Orphan types handled:
24
+ * 1. role:'toolResult' message whose toolCallId has no matching toolCall/tool_use
25
+ * block in any assistant message in the array.
26
+ * 2. User message whose content contains only type:'tool_result' blocks where all
27
+ * of those blocks reference a tool_use_id that does not appear in any assistant
28
+ * message in the array. (Anthropic-native format.)
29
+ *
30
+ * Also strips orphaned assistant messages that contain ONLY tool_use/toolCall blocks
31
+ * where none of those calls has a corresponding tool result anywhere in the array.
32
+ *
33
+ * Returns a new array (does not mutate input).
34
+ */
35
+ export function repairToolPairs(messages) {
36
+ if (!Array.isArray(messages) || messages.length === 0)
37
+ return messages;
38
+ // ── Pass 1: Collect all valid tool call IDs from assistant messages ────────
39
+ const validCallIds = new Set();
40
+ for (const msg of messages) {
41
+ if (msg.role !== 'assistant')
42
+ continue;
43
+ // NeutralMessage format: msg.toolCalls[]
44
+ if (Array.isArray(msg.toolCalls)) {
45
+ for (const tc of msg.toolCalls) {
46
+ if (typeof tc.id === 'string' && tc.id)
47
+ validCallIds.add(tc.id);
48
+ }
49
+ }
50
+ // Content array format: type:'toolCall' or type:'tool_use' blocks
51
+ if (Array.isArray(msg.content)) {
52
+ for (const block of msg.content) {
53
+ if ((block.type === 'toolCall' || block.type === 'tool_use') &&
54
+ typeof block.id === 'string' &&
55
+ block.id) {
56
+ validCallIds.add(block.id);
57
+ }
58
+ }
59
+ }
60
+ }
61
+ // ── Pass 2: Collect all result IDs that have a valid call ─────────────────
62
+ const validResultIds = new Set();
63
+ for (const msg of messages) {
64
+ // pi-agent ToolResultMessage
65
+ if (msg.role === 'toolResult') {
66
+ const id = typeof msg.toolCallId === 'string' ? msg.toolCallId :
67
+ typeof msg.tool_call_id === 'string' ? msg.tool_call_id : '';
68
+ if (id && validCallIds.has(id))
69
+ validResultIds.add(id);
70
+ }
71
+ // Anthropic-native tool_result blocks inside user messages
72
+ if (msg.role === 'user' && Array.isArray(msg.content)) {
73
+ for (const block of msg.content) {
74
+ if (block.type === 'tool_result' && typeof block.tool_use_id === 'string' && block.tool_use_id) {
75
+ if (validCallIds.has(block.tool_use_id))
76
+ validResultIds.add(block.tool_use_id);
77
+ }
78
+ }
79
+ }
80
+ }
81
+ // ── Pass 3: Filter out orphaned messages / blocks ─────────────────────────
82
+ const result = [];
83
+ for (const msg of messages) {
84
+ // ── pi-agent ToolResultMessage ─────────────────────────────────────────
85
+ if (msg.role === 'toolResult') {
86
+ const id = typeof msg.toolCallId === 'string' ? msg.toolCallId :
87
+ typeof msg.tool_call_id === 'string' ? msg.tool_call_id : '';
88
+ if (!id || !validCallIds.has(id)) {
89
+ // Orphaned — drop
90
+ continue;
91
+ }
92
+ result.push(msg);
93
+ continue;
94
+ }
95
+ // ── Anthropic-native: user message with tool_result content blocks ─────
96
+ if (msg.role === 'user' && Array.isArray(msg.content)) {
97
+ const content = msg.content;
98
+ const hasToolResultBlocks = content.some(b => b.type === 'tool_result');
99
+ if (hasToolResultBlocks) {
100
+ const filteredContent = content.filter(block => {
101
+ if (block.type !== 'tool_result')
102
+ return true; // keep non-tool_result blocks
103
+ const toolUseId = typeof block.tool_use_id === 'string' ? block.tool_use_id : '';
104
+ return toolUseId && validCallIds.has(toolUseId);
105
+ });
106
+ // If the message became empty after stripping all orphaned tool_result blocks, skip it
107
+ if (filteredContent.length === 0)
108
+ continue;
109
+ result.push({ ...msg, content: filteredContent });
110
+ continue;
111
+ }
112
+ }
113
+ // ── Assistant message with ONLY unmatched tool_use/toolCall blocks ─────
114
+ if (msg.role === 'assistant' && Array.isArray(msg.content)) {
115
+ const content = msg.content;
116
+ const toolCallBlocks = content.filter(b => b.type === 'toolCall' || b.type === 'tool_use');
117
+ const nonToolCallBlocks = content.filter(b => b.type !== 'toolCall' && b.type !== 'tool_use');
118
+ // Only strip if the assistant message is purely tool-calls (no text)
119
+ if (toolCallBlocks.length > 0 && nonToolCallBlocks.length === 0) {
120
+ const hasAnyResult = toolCallBlocks.some(b => {
121
+ const id = typeof b.id === 'string' ? b.id : '';
122
+ return id && validResultIds.has(id);
123
+ });
124
+ if (!hasAnyResult) {
125
+ // Pure tool-call block with no paired results — drop
126
+ continue;
127
+ }
128
+ }
129
+ }
130
+ result.push(msg);
131
+ }
132
+ const dropped = messages.length - result.length;
133
+ if (dropped > 0) {
134
+ console.log(`[hypermem] repairToolPairs: dropped ${dropped} orphaned message(s) (${messages.length} → ${result.length})`);
135
+ }
136
+ return result;
137
+ }
138
+ //# sourceMappingURL=repair-tool-pairs.js.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * hypermem Retrieval Policy
3
+ *
4
+ * Single enforced policy layer for scope-based access control during retrieval.
5
+ * Called by the compositor to filter items before they are injected into context.
6
+ *
7
+ * Scope rules:
8
+ * 'agent' (default / null / undefined): allowed if agentId matches OR is null/undefined (global)
9
+ * 'session': allowed if both agentId AND sessionKey match
10
+ * 'user': allowed if agentId matches
11
+ * 'global': always allowed
12
+ * any other value: denied with reason 'ambiguous_scope'
13
+ */
14
+ export type RetrievalScope = 'agent' | 'session' | 'user' | 'global';
15
+ export interface RetrievalContext {
16
+ agentId: string;
17
+ sessionKey: string;
18
+ }
19
+ export interface ScopeCheckResult {
20
+ allowed: boolean;
21
+ /** One of: 'allowed' | 'scope_filtered' | 'ambiguous_scope' */
22
+ reason: string;
23
+ }
24
+ /**
25
+ * Check whether a single item is accessible in the given retrieval context.
26
+ *
27
+ * @param itemScope The scope stored on the item (null/undefined → defaults to 'agent')
28
+ * @param itemAgentId The agentId stored on the item (null/undefined → global)
29
+ * @param itemSessionKey The sessionKey stored on the item (null/undefined → any)
30
+ * @param ctx The requester's retrieval context
31
+ */
32
+ export declare function checkScope(itemScope: string | null | undefined, itemAgentId: string | null | undefined, itemSessionKey: string | null | undefined, ctx: RetrievalContext): ScopeCheckResult;
33
+ /**
34
+ * Filter an array of items by scope, returning allowed items and a filtered count.
35
+ *
36
+ * Items are expected to have optional `agentId`, `sessionKey`, and `scope` fields.
37
+ * Null/undefined fields are treated as "unset" (permissive for their slot).
38
+ *
39
+ * @param items Array of items to filter
40
+ * @param ctx The requester's retrieval context
41
+ * @returns { allowed: T[], filteredCount: number }
42
+ */
43
+ export declare function filterByScope<T extends {
44
+ agentId?: string | null;
45
+ sessionKey?: string | null;
46
+ scope?: string | null;
47
+ }>(items: T[], ctx: RetrievalContext): {
48
+ allowed: T[];
49
+ filteredCount: number;
50
+ };
51
+ //# sourceMappingURL=retrieval-policy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"retrieval-policy.d.ts","sourceRoot":"","sources":["../src/retrieval-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,MAAM,cAAc,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,GAAG,QAAQ,CAAC;AAErE,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,+DAA+D;IAC/D,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CACxB,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACpC,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACtC,cAAc,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACzC,GAAG,EAAE,gBAAgB,GACpB,gBAAgB,CAqClB;AAED;;;;;;;;;GASG;AACH,wBAAgB,aAAa,CAAC,CAAC,SAAS;IACtC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,UAAU,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,EACC,KAAK,EAAE,CAAC,EAAE,EACV,GAAG,EAAE,gBAAgB,GACpB;IAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAAC,aAAa,EAAE,MAAM,CAAA;CAAE,CAczC"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * hypermem Retrieval Policy
3
+ *
4
+ * Single enforced policy layer for scope-based access control during retrieval.
5
+ * Called by the compositor to filter items before they are injected into context.
6
+ *
7
+ * Scope rules:
8
+ * 'agent' (default / null / undefined): allowed if agentId matches OR is null/undefined (global)
9
+ * 'session': allowed if both agentId AND sessionKey match
10
+ * 'user': allowed if agentId matches
11
+ * 'global': always allowed
12
+ * any other value: denied with reason 'ambiguous_scope'
13
+ */
14
+ /**
15
+ * Check whether a single item is accessible in the given retrieval context.
16
+ *
17
+ * @param itemScope The scope stored on the item (null/undefined → defaults to 'agent')
18
+ * @param itemAgentId The agentId stored on the item (null/undefined → global)
19
+ * @param itemSessionKey The sessionKey stored on the item (null/undefined → any)
20
+ * @param ctx The requester's retrieval context
21
+ */
22
+ export function checkScope(itemScope, itemAgentId, itemSessionKey, ctx) {
23
+ const scope = itemScope ?? 'agent';
24
+ switch (scope) {
25
+ case 'agent':
26
+ // Global agent facts (null/undefined agentId) are readable by all agents.
27
+ // Agent-specific facts are only readable by the owning agent.
28
+ if (itemAgentId == null || itemAgentId === ctx.agentId) {
29
+ return { allowed: true, reason: 'allowed' };
30
+ }
31
+ return { allowed: false, reason: 'scope_filtered' };
32
+ case 'session':
33
+ // Session-scoped: both agentId AND sessionKey must match.
34
+ if ((itemAgentId == null || itemAgentId === ctx.agentId) &&
35
+ (itemSessionKey == null || itemSessionKey === ctx.sessionKey)) {
36
+ return { allowed: true, reason: 'allowed' };
37
+ }
38
+ return { allowed: false, reason: 'scope_filtered' };
39
+ case 'user':
40
+ // User-scoped: agentId must match (user prefs are keyed by agent).
41
+ if (itemAgentId == null || itemAgentId === ctx.agentId) {
42
+ return { allowed: true, reason: 'allowed' };
43
+ }
44
+ return { allowed: false, reason: 'scope_filtered' };
45
+ case 'global':
46
+ // Global: always accessible.
47
+ return { allowed: true, reason: 'allowed' };
48
+ default:
49
+ // Unknown scope — deny with ambiguous_scope.
50
+ return { allowed: false, reason: 'ambiguous_scope' };
51
+ }
52
+ }
53
+ /**
54
+ * Filter an array of items by scope, returning allowed items and a filtered count.
55
+ *
56
+ * Items are expected to have optional `agentId`, `sessionKey`, and `scope` fields.
57
+ * Null/undefined fields are treated as "unset" (permissive for their slot).
58
+ *
59
+ * @param items Array of items to filter
60
+ * @param ctx The requester's retrieval context
61
+ * @returns { allowed: T[], filteredCount: number }
62
+ */
63
+ export function filterByScope(items, ctx) {
64
+ const allowed = [];
65
+ let filteredCount = 0;
66
+ for (const item of items) {
67
+ const result = checkScope(item.scope, item.agentId, item.sessionKey, ctx);
68
+ if (result.allowed) {
69
+ allowed.push(item);
70
+ }
71
+ else {
72
+ filteredCount++;
73
+ }
74
+ }
75
+ return { allowed, filteredCount };
76
+ }
77
+ //# sourceMappingURL=retrieval-policy.js.map
@@ -0,0 +1,15 @@
1
+ /**
2
+ * hypermem Agent Message Schema
3
+ *
4
+ * Per-agent database: ~/.openclaw/hypermem/agents/{agentId}/messages.db
5
+ * Write-heavy, temporal, rotatable.
6
+ * Contains ONLY conversation data — structured knowledge lives in library.db.
7
+ */
8
+ import type { DatabaseSync } from 'node:sqlite';
9
+ export declare const LATEST_SCHEMA_VERSION = 6;
10
+ /**
11
+ * Run migrations on an agent message database.
12
+ */
13
+ export declare function migrate(db: DatabaseSync): void;
14
+ export { LATEST_SCHEMA_VERSION as SCHEMA_VERSION };
15
+ //# sourceMappingURL=schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD,eAAO,MAAM,qBAAqB,IAAI,CAAC;AAgJvC;;GAEG;AACH,wBAAgB,OAAO,CAAC,EAAE,EAAE,YAAY,GAAG,IAAI,CA4F9C;AAED,OAAO,EAAE,qBAAqB,IAAI,cAAc,EAAE,CAAC"}
package/dist/schema.js ADDED
@@ -0,0 +1,229 @@
1
+ /**
2
+ * hypermem Agent Message Schema
3
+ *
4
+ * Per-agent database: ~/.openclaw/hypermem/agents/{agentId}/messages.db
5
+ * Write-heavy, temporal, rotatable.
6
+ * Contains ONLY conversation data — structured knowledge lives in library.db.
7
+ */
8
+ export const LATEST_SCHEMA_VERSION = 6;
9
+ function nowIso() {
10
+ return new Date().toISOString();
11
+ }
12
+ /**
13
+ * V1–V3: Legacy schema (monolithic agent DB with facts/knowledge/episodes).
14
+ * Kept for migration detection — if we open an old DB, we know what version it is.
15
+ */
16
+ /**
17
+ * V4: Messages-only schema.
18
+ * Facts, knowledge, episodes, topics moved to library.db.
19
+ * Agent DB now contains only conversations, messages, summaries, and agent metadata.
20
+ */
21
+ function applyV4MessagesOnly(db) {
22
+ // -- Agent metadata (kept here for self-identification) --
23
+ db.exec(`
24
+ CREATE TABLE IF NOT EXISTS agent_meta (
25
+ id TEXT PRIMARY KEY,
26
+ display_name TEXT,
27
+ tier TEXT,
28
+ org TEXT,
29
+ config TEXT,
30
+ created_at TEXT NOT NULL,
31
+ updated_at TEXT NOT NULL
32
+ )
33
+ `);
34
+ // -- Conversations (sessions) --
35
+ db.exec(`
36
+ CREATE TABLE IF NOT EXISTS conversations (
37
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
38
+ session_key TEXT NOT NULL UNIQUE,
39
+ session_id TEXT,
40
+ agent_id TEXT NOT NULL,
41
+ channel_type TEXT NOT NULL,
42
+ channel_id TEXT,
43
+ provider TEXT,
44
+ model TEXT,
45
+ status TEXT DEFAULT 'active',
46
+ message_count INTEGER DEFAULT 0,
47
+ token_count_in INTEGER DEFAULT 0,
48
+ token_count_out INTEGER DEFAULT 0,
49
+ created_at TEXT NOT NULL,
50
+ updated_at TEXT NOT NULL,
51
+ ended_at TEXT
52
+ )
53
+ `);
54
+ db.exec('CREATE INDEX IF NOT EXISTS idx_conv_agent ON conversations(agent_id, status, updated_at DESC)');
55
+ db.exec('CREATE INDEX IF NOT EXISTS idx_conv_channel ON conversations(agent_id, channel_type, channel_id)');
56
+ // -- Messages (provider-neutral, structured) --
57
+ db.exec(`
58
+ CREATE TABLE IF NOT EXISTS messages (
59
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
60
+ conversation_id INTEGER NOT NULL REFERENCES conversations(id),
61
+ agent_id TEXT NOT NULL,
62
+ role TEXT NOT NULL,
63
+ text_content TEXT,
64
+ tool_calls TEXT,
65
+ tool_results TEXT,
66
+ metadata TEXT,
67
+ token_count INTEGER,
68
+ message_index INTEGER NOT NULL,
69
+ is_heartbeat INTEGER DEFAULT 0,
70
+ created_at TEXT NOT NULL
71
+ )
72
+ `);
73
+ db.exec('CREATE INDEX IF NOT EXISTS idx_msg_conv ON messages(conversation_id, message_index)');
74
+ db.exec('CREATE INDEX IF NOT EXISTS idx_msg_agent_time ON messages(agent_id, created_at DESC)');
75
+ // -- FTS5 for message full-text search --
76
+ db.exec(`
77
+ CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
78
+ text_content,
79
+ content='messages',
80
+ content_rowid='id'
81
+ )
82
+ `);
83
+ db.exec(`
84
+ CREATE TRIGGER IF NOT EXISTS msg_fts_ai AFTER INSERT ON messages BEGIN
85
+ INSERT INTO messages_fts(rowid, text_content) VALUES (new.id, new.text_content);
86
+ END
87
+ `);
88
+ db.exec(`
89
+ CREATE TRIGGER IF NOT EXISTS msg_fts_ad AFTER DELETE ON messages BEGIN
90
+ INSERT INTO messages_fts(messages_fts, rowid, text_content) VALUES('delete', old.id, old.text_content);
91
+ END
92
+ `);
93
+ db.exec(`
94
+ CREATE TRIGGER IF NOT EXISTS msg_fts_au AFTER UPDATE ON messages BEGIN
95
+ INSERT INTO messages_fts(messages_fts, rowid, text_content) VALUES('delete', old.id, old.text_content);
96
+ INSERT INTO messages_fts(rowid, text_content) VALUES (new.id, new.text_content);
97
+ END
98
+ `);
99
+ // -- Summaries (hierarchical compaction) --
100
+ db.exec(`
101
+ CREATE TABLE IF NOT EXISTS summaries (
102
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
103
+ conversation_id INTEGER NOT NULL REFERENCES conversations(id),
104
+ agent_id TEXT NOT NULL,
105
+ depth INTEGER NOT NULL DEFAULT 0,
106
+ content TEXT NOT NULL,
107
+ token_count INTEGER,
108
+ created_at TEXT NOT NULL,
109
+ updated_at TEXT NOT NULL
110
+ )
111
+ `);
112
+ db.exec('CREATE INDEX IF NOT EXISTS idx_summaries_conv ON summaries(conversation_id, depth)');
113
+ db.exec(`
114
+ CREATE TABLE IF NOT EXISTS summary_messages (
115
+ summary_id INTEGER NOT NULL REFERENCES summaries(id),
116
+ message_id INTEGER NOT NULL REFERENCES messages(id),
117
+ PRIMARY KEY (summary_id, message_id)
118
+ )
119
+ `);
120
+ db.exec(`
121
+ CREATE TABLE IF NOT EXISTS summary_parents (
122
+ parent_summary_id INTEGER NOT NULL REFERENCES summaries(id),
123
+ child_summary_id INTEGER NOT NULL REFERENCES summaries(id),
124
+ PRIMARY KEY (parent_summary_id, child_summary_id)
125
+ )
126
+ `);
127
+ // -- Index events (for tracking what's been vectorized) --
128
+ db.exec(`
129
+ CREATE TABLE IF NOT EXISTS index_events (
130
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
131
+ agent_id TEXT NOT NULL,
132
+ event_type TEXT NOT NULL,
133
+ target_table TEXT,
134
+ target_id INTEGER,
135
+ details TEXT,
136
+ created_at TEXT NOT NULL
137
+ )
138
+ `);
139
+ db.exec('CREATE INDEX IF NOT EXISTS idx_index_events ON index_events(agent_id, created_at DESC)');
140
+ }
141
+ /**
142
+ * Run migrations on an agent message database.
143
+ */
144
+ export function migrate(db) {
145
+ db.exec(`
146
+ CREATE TABLE IF NOT EXISTS schema_version (
147
+ version INTEGER PRIMARY KEY,
148
+ applied_at TEXT NOT NULL
149
+ )
150
+ `);
151
+ const row = db
152
+ .prepare('SELECT MAX(version) AS version FROM schema_version')
153
+ .get();
154
+ const currentVersion = typeof row?.version === 'number' ? row.version : 0;
155
+ if (currentVersion > LATEST_SCHEMA_VERSION) {
156
+ console.warn(`[hypermem] Database schema version (${currentVersion}) is newer than this engine (${LATEST_SCHEMA_VERSION}).`);
157
+ return;
158
+ }
159
+ // For fresh DBs (version 0), jump straight to v4 (messages-only).
160
+ // For existing DBs (v1–v3), we skip the old schema — those tables will be
161
+ // left in place but unused. Data migration to library is a separate step.
162
+ if (currentVersion < 4) {
163
+ if (currentVersion === 0) {
164
+ // Fresh DB — create messages-only schema directly
165
+ applyV4MessagesOnly(db);
166
+ }
167
+ else {
168
+ // Existing DB with old schema (v1–v3).
169
+ // The old tables (facts, knowledge, episodes, topics, agents) remain
170
+ // but are no longer written to. New tables get created alongside.
171
+ applyV4MessagesOnly(db);
172
+ // Note: old tables like 'facts', 'knowledge', etc. are left in place
173
+ // for data migration. A separate migration tool will move them to library.db.
174
+ }
175
+ db.prepare('INSERT INTO schema_version (version, applied_at) VALUES (?, ?)')
176
+ .run(4, nowIso());
177
+ }
178
+ // v4 → v5: add cursor columns to conversations table for dual-write durability
179
+ if (currentVersion < 5) {
180
+ // ALTER TABLE ADD COLUMN is safe on existing rows — all default to NULL
181
+ const cols = db.prepare('PRAGMA table_info(conversations)').all()
182
+ .map(r => r.name);
183
+ if (!cols.includes('cursor_last_sent_id')) {
184
+ db.exec('ALTER TABLE conversations ADD COLUMN cursor_last_sent_id INTEGER');
185
+ }
186
+ if (!cols.includes('cursor_last_sent_index')) {
187
+ db.exec('ALTER TABLE conversations ADD COLUMN cursor_last_sent_index INTEGER');
188
+ }
189
+ if (!cols.includes('cursor_last_sent_at')) {
190
+ db.exec('ALTER TABLE conversations ADD COLUMN cursor_last_sent_at TEXT');
191
+ }
192
+ if (!cols.includes('cursor_window_size')) {
193
+ db.exec('ALTER TABLE conversations ADD COLUMN cursor_window_size INTEGER');
194
+ }
195
+ if (!cols.includes('cursor_token_count')) {
196
+ db.exec('ALTER TABLE conversations ADD COLUMN cursor_token_count INTEGER');
197
+ }
198
+ db.prepare('INSERT OR IGNORE INTO schema_version (version, applied_at) VALUES (?, ?)')
199
+ .run(5, nowIso());
200
+ }
201
+ // v5 → v6: add topic_id to messages, add per-session topics table
202
+ if (currentVersion < 6) {
203
+ // Add topic_id column to messages (nullable TEXT)
204
+ const msgCols = db.prepare('PRAGMA table_info(messages)').all()
205
+ .map(r => r.name);
206
+ if (!msgCols.includes('topic_id')) {
207
+ db.exec('ALTER TABLE messages ADD COLUMN topic_id TEXT');
208
+ }
209
+ // Add index on messages.topic_id
210
+ db.exec('CREATE INDEX IF NOT EXISTS idx_messages_topic_id ON messages(topic_id)');
211
+ // Add per-session topics table (in messages.db for session-scoped topic tracking)
212
+ db.exec(`
213
+ CREATE TABLE IF NOT EXISTS topics (
214
+ id TEXT PRIMARY KEY,
215
+ session_key TEXT NOT NULL,
216
+ name TEXT NOT NULL,
217
+ created_at INTEGER NOT NULL,
218
+ last_active_at INTEGER NOT NULL,
219
+ message_count INTEGER DEFAULT 0,
220
+ metadata TEXT
221
+ )
222
+ `);
223
+ db.exec('CREATE INDEX IF NOT EXISTS idx_topics_session_key ON topics(session_key)');
224
+ db.prepare('INSERT OR IGNORE INTO schema_version (version, applied_at) VALUES (?, ?)')
225
+ .run(6, nowIso());
226
+ }
227
+ }
228
+ export { LATEST_SCHEMA_VERSION as SCHEMA_VERSION };
229
+ //# sourceMappingURL=schema.js.map
@@ -0,0 +1,51 @@
1
+ /**
2
+ * hypermem Secret Scanner
3
+ *
4
+ * Lightweight regex-based gate to prevent secrets from leaking into
5
+ * shared memory (visibility >= 'org'). Runs before any write that
6
+ * promotes content to org/council/fleet visibility.
7
+ *
8
+ * Design principles:
9
+ * - Fast: regex-only, no LLM dependency
10
+ * - Conservative: false positives are okay; false negatives are not
11
+ * - Auditable: each hit includes the rule name and matched region (redacted)
12
+ * - Not a full DLP system: blocks the obvious leaks; does not guarantee clean content
13
+ *
14
+ * Patterns sourced from:
15
+ * - gitleaks ruleset (https://github.com/gitleaks/gitleaks)
16
+ * - trufflehog common patterns
17
+ * - Manual additions for OpenClaw-specific secrets
18
+ */
19
+ export interface ScanResult {
20
+ clean: boolean;
21
+ hits: ScanHit[];
22
+ }
23
+ export interface ScanHit {
24
+ rule: string;
25
+ description: string;
26
+ /** Redacted match — only first/last 3 chars of the matched value */
27
+ redactedMatch: string;
28
+ /** Character offset of the match start */
29
+ offset: number;
30
+ }
31
+ /**
32
+ * Scan content for secrets before promoting to shared visibility.
33
+ *
34
+ * Returns { clean: true } when no secrets found.
35
+ * Returns { clean: false, hits } when secrets are detected.
36
+ *
37
+ * Callers must NOT write content with visibility >= 'org' when clean is false.
38
+ */
39
+ export declare function scanForSecrets(content: string): ScanResult;
40
+ /**
41
+ * Returns true when content is safe to promote to shared visibility (>= 'org').
42
+ * Convenience wrapper around scanForSecrets.
43
+ */
44
+ export declare function isSafeForSharedVisibility(content: string): boolean;
45
+ /**
46
+ * Check whether a visibility level requires secret scanning.
47
+ * Scanning is required for 'org', 'council', and 'fleet'.
48
+ * 'private' content stays with the owner — no cross-agent risk.
49
+ */
50
+ export declare function requiresScan(visibility: string): boolean;
51
+ //# sourceMappingURL=secret-scanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret-scanner.d.ts","sourceRoot":"","sources":["../src/secret-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,OAAO,CAAC;IACf,IAAI,EAAE,OAAO,EAAE,CAAC;CACjB;AAED,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,oEAAoE;IACpE,aAAa,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,MAAM,EAAE,MAAM,CAAC;CAChB;AAmMD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,CA+B1D;AAED;;;GAGG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAElE;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAExD"}