@hybridaione/hybridclaw 0.2.2 → 0.2.6

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 (277) hide show
  1. package/.github/workflows/ci.yml +70 -0
  2. package/.husky/pre-commit +1 -0
  3. package/CHANGELOG.md +85 -0
  4. package/CONTRIBUTING.md +33 -0
  5. package/README.md +41 -16
  6. package/SECURITY.md +17 -0
  7. package/biome.json +35 -0
  8. package/config.example.json +71 -8
  9. package/container/package-lock.json +2 -2
  10. package/container/package.json +1 -1
  11. package/container/src/approval-policy.ts +1303 -0
  12. package/container/src/browser-tools.ts +431 -136
  13. package/container/src/extensions.ts +36 -12
  14. package/container/src/hybridai-client.ts +34 -13
  15. package/container/src/index.ts +451 -109
  16. package/container/src/ipc.ts +5 -3
  17. package/container/src/token-usage.ts +20 -10
  18. package/container/src/tools.ts +599 -225
  19. package/container/src/types.ts +32 -2
  20. package/container/src/web-fetch.ts +89 -32
  21. package/dist/agent.d.ts.map +1 -1
  22. package/dist/agent.js +10 -2
  23. package/dist/agent.js.map +1 -1
  24. package/dist/audit-cli.d.ts.map +1 -1
  25. package/dist/audit-cli.js +4 -2
  26. package/dist/audit-cli.js.map +1 -1
  27. package/dist/audit-events.d.ts.map +1 -1
  28. package/dist/audit-events.js +53 -3
  29. package/dist/audit-events.js.map +1 -1
  30. package/dist/audit-trail.d.ts.map +1 -1
  31. package/dist/audit-trail.js +17 -8
  32. package/dist/audit-trail.js.map +1 -1
  33. package/dist/channels/discord/attachments.d.ts.map +1 -1
  34. package/dist/channels/discord/attachments.js +14 -7
  35. package/dist/channels/discord/attachments.js.map +1 -1
  36. package/dist/channels/discord/debounce.d.ts +9 -0
  37. package/dist/channels/discord/debounce.d.ts.map +1 -0
  38. package/dist/channels/discord/debounce.js +20 -0
  39. package/dist/channels/discord/debounce.js.map +1 -0
  40. package/dist/channels/discord/delivery.d.ts +4 -1
  41. package/dist/channels/discord/delivery.d.ts.map +1 -1
  42. package/dist/channels/discord/delivery.js +19 -3
  43. package/dist/channels/discord/delivery.js.map +1 -1
  44. package/dist/channels/discord/human-delay.d.ts +16 -0
  45. package/dist/channels/discord/human-delay.d.ts.map +1 -0
  46. package/dist/channels/discord/human-delay.js +29 -0
  47. package/dist/channels/discord/human-delay.js.map +1 -0
  48. package/dist/channels/discord/inbound.d.ts +4 -0
  49. package/dist/channels/discord/inbound.d.ts.map +1 -1
  50. package/dist/channels/discord/inbound.js +45 -4
  51. package/dist/channels/discord/inbound.js.map +1 -1
  52. package/dist/channels/discord/mentions.d.ts.map +1 -1
  53. package/dist/channels/discord/mentions.js +16 -4
  54. package/dist/channels/discord/mentions.js.map +1 -1
  55. package/dist/channels/discord/presence.d.ts +33 -0
  56. package/dist/channels/discord/presence.d.ts.map +1 -0
  57. package/dist/channels/discord/presence.js +111 -0
  58. package/dist/channels/discord/presence.js.map +1 -0
  59. package/dist/channels/discord/rate-limiter.d.ts +14 -0
  60. package/dist/channels/discord/rate-limiter.d.ts.map +1 -0
  61. package/dist/channels/discord/rate-limiter.js +49 -0
  62. package/dist/channels/discord/rate-limiter.js.map +1 -0
  63. package/dist/channels/discord/reactions.d.ts +38 -0
  64. package/dist/channels/discord/reactions.d.ts.map +1 -0
  65. package/dist/channels/discord/reactions.js +151 -0
  66. package/dist/channels/discord/reactions.js.map +1 -0
  67. package/dist/channels/discord/runtime.d.ts +6 -3
  68. package/dist/channels/discord/runtime.d.ts.map +1 -1
  69. package/dist/channels/discord/runtime.js +621 -125
  70. package/dist/channels/discord/runtime.js.map +1 -1
  71. package/dist/channels/discord/stream.d.ts +4 -1
  72. package/dist/channels/discord/stream.d.ts.map +1 -1
  73. package/dist/channels/discord/stream.js +16 -8
  74. package/dist/channels/discord/stream.js.map +1 -1
  75. package/dist/channels/discord/tool-actions.d.ts.map +1 -1
  76. package/dist/channels/discord/tool-actions.js +24 -12
  77. package/dist/channels/discord/tool-actions.js.map +1 -1
  78. package/dist/channels/discord/typing.d.ts +15 -0
  79. package/dist/channels/discord/typing.d.ts.map +1 -0
  80. package/dist/channels/discord/typing.js +106 -0
  81. package/dist/channels/discord/typing.js.map +1 -0
  82. package/dist/chunk.d.ts.map +1 -1
  83. package/dist/chunk.js +4 -2
  84. package/dist/chunk.js.map +1 -1
  85. package/dist/cli.js +47 -22
  86. package/dist/cli.js.map +1 -1
  87. package/dist/config.d.ts +19 -0
  88. package/dist/config.d.ts.map +1 -1
  89. package/dist/config.js +103 -18
  90. package/dist/config.js.map +1 -1
  91. package/dist/container-runner.d.ts.map +1 -1
  92. package/dist/container-runner.js +58 -26
  93. package/dist/container-runner.js.map +1 -1
  94. package/dist/container-setup.d.ts.map +1 -1
  95. package/dist/container-setup.js +10 -9
  96. package/dist/container-setup.js.map +1 -1
  97. package/dist/conversation.d.ts +2 -2
  98. package/dist/conversation.d.ts.map +1 -1
  99. package/dist/conversation.js +1 -1
  100. package/dist/conversation.js.map +1 -1
  101. package/dist/db.d.ts +118 -2
  102. package/dist/db.d.ts.map +1 -1
  103. package/dist/db.js +1568 -50
  104. package/dist/db.js.map +1 -1
  105. package/dist/delegation-manager.d.ts.map +1 -1
  106. package/dist/delegation-manager.js +3 -2
  107. package/dist/delegation-manager.js.map +1 -1
  108. package/dist/gateway-client.d.ts +2 -2
  109. package/dist/gateway-client.d.ts.map +1 -1
  110. package/dist/gateway-client.js +10 -4
  111. package/dist/gateway-client.js.map +1 -1
  112. package/dist/gateway-service.d.ts +3 -3
  113. package/dist/gateway-service.d.ts.map +1 -1
  114. package/dist/gateway-service.js +563 -73
  115. package/dist/gateway-service.js.map +1 -1
  116. package/dist/gateway-types.d.ts +24 -0
  117. package/dist/gateway-types.d.ts.map +1 -1
  118. package/dist/gateway-types.js.map +1 -1
  119. package/dist/gateway.js +179 -24
  120. package/dist/gateway.js.map +1 -1
  121. package/dist/health.d.ts.map +1 -1
  122. package/dist/health.js +20 -10
  123. package/dist/health.js.map +1 -1
  124. package/dist/heartbeat.d.ts +4 -0
  125. package/dist/heartbeat.d.ts.map +1 -1
  126. package/dist/heartbeat.js +48 -20
  127. package/dist/heartbeat.js.map +1 -1
  128. package/dist/hybridai-bots.d.ts.map +1 -1
  129. package/dist/hybridai-bots.js +4 -2
  130. package/dist/hybridai-bots.js.map +1 -1
  131. package/dist/instruction-approval-audit.d.ts.map +1 -1
  132. package/dist/instruction-approval-audit.js.map +1 -1
  133. package/dist/instruction-integrity.d.ts.map +1 -1
  134. package/dist/instruction-integrity.js +8 -2
  135. package/dist/instruction-integrity.js.map +1 -1
  136. package/dist/ipc.d.ts.map +1 -1
  137. package/dist/ipc.js +6 -1
  138. package/dist/ipc.js.map +1 -1
  139. package/dist/logger.js.map +1 -1
  140. package/dist/memory-consolidation.d.ts +17 -0
  141. package/dist/memory-consolidation.d.ts.map +1 -0
  142. package/dist/memory-consolidation.js +25 -0
  143. package/dist/memory-consolidation.js.map +1 -0
  144. package/dist/memory-service.d.ts +200 -0
  145. package/dist/memory-service.d.ts.map +1 -0
  146. package/dist/memory-service.js +294 -0
  147. package/dist/memory-service.js.map +1 -0
  148. package/dist/mount-security.d.ts.map +1 -1
  149. package/dist/mount-security.js +31 -7
  150. package/dist/mount-security.js.map +1 -1
  151. package/dist/observability-ingest.d.ts.map +1 -1
  152. package/dist/observability-ingest.js +32 -11
  153. package/dist/observability-ingest.js.map +1 -1
  154. package/dist/onboarding.d.ts.map +1 -1
  155. package/dist/onboarding.js +32 -9
  156. package/dist/onboarding.js.map +1 -1
  157. package/dist/proactive-policy.d.ts.map +1 -1
  158. package/dist/proactive-policy.js +2 -1
  159. package/dist/proactive-policy.js.map +1 -1
  160. package/dist/prompt-hooks.d.ts.map +1 -1
  161. package/dist/prompt-hooks.js +9 -7
  162. package/dist/prompt-hooks.js.map +1 -1
  163. package/dist/runtime-config.d.ts +98 -1
  164. package/dist/runtime-config.d.ts.map +1 -1
  165. package/dist/runtime-config.js +477 -23
  166. package/dist/runtime-config.js.map +1 -1
  167. package/dist/scheduled-task-runner.d.ts +1 -0
  168. package/dist/scheduled-task-runner.d.ts.map +1 -1
  169. package/dist/scheduled-task-runner.js +29 -10
  170. package/dist/scheduled-task-runner.js.map +1 -1
  171. package/dist/scheduler.d.ts +43 -4
  172. package/dist/scheduler.d.ts.map +1 -1
  173. package/dist/scheduler.js +530 -56
  174. package/dist/scheduler.js.map +1 -1
  175. package/dist/session-export.d.ts +26 -0
  176. package/dist/session-export.d.ts.map +1 -0
  177. package/dist/session-export.js +149 -0
  178. package/dist/session-export.js.map +1 -0
  179. package/dist/session-maintenance.d.ts.map +1 -1
  180. package/dist/session-maintenance.js +75 -13
  181. package/dist/session-maintenance.js.map +1 -1
  182. package/dist/session-transcripts.d.ts.map +1 -1
  183. package/dist/session-transcripts.js.map +1 -1
  184. package/dist/side-effects.d.ts.map +1 -1
  185. package/dist/side-effects.js +14 -2
  186. package/dist/side-effects.js.map +1 -1
  187. package/dist/skills-guard.d.ts.map +1 -1
  188. package/dist/skills-guard.js +893 -130
  189. package/dist/skills-guard.js.map +1 -1
  190. package/dist/skills.d.ts +5 -0
  191. package/dist/skills.d.ts.map +1 -1
  192. package/dist/skills.js +29 -15
  193. package/dist/skills.js.map +1 -1
  194. package/dist/token-efficiency.d.ts.map +1 -1
  195. package/dist/token-efficiency.js.map +1 -1
  196. package/dist/tui.js +92 -11
  197. package/dist/tui.js.map +1 -1
  198. package/dist/types.d.ts +146 -0
  199. package/dist/types.d.ts.map +1 -1
  200. package/dist/types.js +24 -1
  201. package/dist/types.js.map +1 -1
  202. package/dist/update.d.ts.map +1 -1
  203. package/dist/update.js +42 -14
  204. package/dist/update.js.map +1 -1
  205. package/dist/workspace.d.ts.map +1 -1
  206. package/dist/workspace.js +49 -9
  207. package/dist/workspace.js.map +1 -1
  208. package/docs/chat.html +9 -3
  209. package/docs/index.html +37 -13
  210. package/package.json +8 -2
  211. package/src/agent.ts +16 -3
  212. package/src/audit-cli.ts +44 -16
  213. package/src/audit-events.ts +69 -5
  214. package/src/audit-trail.ts +41 -15
  215. package/src/channels/discord/attachments.ts +81 -27
  216. package/src/channels/discord/debounce.ts +25 -0
  217. package/src/channels/discord/delivery.ts +57 -13
  218. package/src/channels/discord/human-delay.ts +48 -0
  219. package/src/channels/discord/inbound.ts +66 -7
  220. package/src/channels/discord/mentions.ts +42 -18
  221. package/src/channels/discord/presence.ts +148 -0
  222. package/src/channels/discord/rate-limiter.ts +58 -0
  223. package/src/channels/discord/reactions.ts +211 -0
  224. package/src/channels/discord/runtime.ts +1048 -182
  225. package/src/channels/discord/stream.ts +73 -27
  226. package/src/channels/discord/tool-actions.ts +78 -37
  227. package/src/channels/discord/typing.ts +140 -0
  228. package/src/chunk.ts +12 -4
  229. package/src/cli.ts +141 -56
  230. package/src/config.ts +192 -34
  231. package/src/container-runner.ts +132 -42
  232. package/src/container-setup.ts +57 -22
  233. package/src/conversation.ts +9 -7
  234. package/src/db.ts +2217 -84
  235. package/src/delegation-manager.ts +6 -2
  236. package/src/gateway-client.ts +41 -17
  237. package/src/gateway-service.ts +1019 -201
  238. package/src/gateway-types.ts +33 -0
  239. package/src/gateway.ts +321 -48
  240. package/src/health.ts +66 -26
  241. package/src/heartbeat.ts +84 -22
  242. package/src/hybridai-bots.ts +14 -5
  243. package/src/instruction-approval-audit.ts +4 -1
  244. package/src/instruction-integrity.ts +30 -9
  245. package/src/ipc.ts +23 -5
  246. package/src/logger.ts +4 -1
  247. package/src/memory-consolidation.ts +41 -0
  248. package/src/memory-service.ts +606 -0
  249. package/src/mount-security.ts +58 -13
  250. package/src/observability-ingest.ts +134 -35
  251. package/src/onboarding.ts +126 -35
  252. package/src/proactive-policy.ts +3 -1
  253. package/src/prompt-hooks.ts +40 -17
  254. package/src/runtime-config.ts +1114 -99
  255. package/src/scheduled-task-runner.ts +63 -11
  256. package/src/scheduler.ts +683 -60
  257. package/src/session-export.ts +196 -0
  258. package/src/session-maintenance.ts +125 -22
  259. package/src/session-transcripts.ts +12 -3
  260. package/src/side-effects.ts +28 -5
  261. package/src/skills-guard.ts +1067 -219
  262. package/src/skills.ts +163 -65
  263. package/src/token-efficiency.ts +31 -9
  264. package/src/tui.ts +166 -25
  265. package/src/types.ts +195 -2
  266. package/src/update.ts +79 -23
  267. package/src/workspace.ts +63 -11
  268. package/tests/approval-policy.test.ts +224 -0
  269. package/tests/discord.basic.test.ts +82 -2
  270. package/tests/discord.human-presence.test.ts +85 -0
  271. package/tests/gateway-service.media-routing.test.ts +8 -2
  272. package/tests/memory-service.test.ts +1114 -0
  273. package/tests/token-efficiency.basic.test.ts +8 -2
  274. package/vitest.e2e.config.ts +3 -1
  275. package/vitest.integration.config.ts +3 -1
  276. package/vitest.live.config.ts +3 -1
  277. package/vitest.unit.config.ts +9 -0
@@ -0,0 +1,606 @@
1
+ import {
2
+ addKnowledgeEntity as dbAddKnowledgeEntity,
3
+ addKnowledgeRelation as dbAddKnowledgeRelation,
4
+ appendCanonicalMessages as dbAppendCanonicalMessages,
5
+ clearSessionHistory as dbClearSessionHistory,
6
+ deleteMemoryValue as dbDeleteMemoryValue,
7
+ deleteMessagesBeforeId as dbDeleteMessagesBeforeId,
8
+ forgetSemanticMemory as dbForgetSemanticMemory,
9
+ getCanonicalContext as dbGetCanonicalContext,
10
+ getCompactionCandidateMessages as dbGetCompactionCandidateMessages,
11
+ getConversationHistory as dbGetConversationHistory,
12
+ getMemoryValue as dbGetMemoryValue,
13
+ getOrCreateSession as dbGetOrCreateSession,
14
+ getRecentMessages as dbGetRecentMessages,
15
+ getSessionById as dbGetSessionById,
16
+ listMemoryValues as dbListMemoryValues,
17
+ markSessionMemoryFlush as dbMarkSessionMemoryFlush,
18
+ queryKnowledgeGraph as dbQueryKnowledgeGraph,
19
+ recallSemanticMemories as dbRecallSemanticMemories,
20
+ setMemoryValue as dbSetMemoryValue,
21
+ storeMessage as dbStoreMessage,
22
+ storeSemanticMemory as dbStoreSemanticMemory,
23
+ updateSessionSummary as dbUpdateSessionSummary,
24
+ decaySemanticMemories,
25
+ type SemanticRecallFilter,
26
+ } from './db.js';
27
+ import {
28
+ type MemoryConsolidationConfig,
29
+ MemoryConsolidationEngine,
30
+ type MemoryConsolidationReport,
31
+ } from './memory-consolidation.js';
32
+ import type {
33
+ CanonicalSession,
34
+ CanonicalSessionContext,
35
+ KnowledgeEntityTypeValue,
36
+ KnowledgeGraphMatch,
37
+ KnowledgeGraphPattern,
38
+ KnowledgeRelationTypeValue,
39
+ SemanticMemoryEntry,
40
+ Session,
41
+ StoredMessage,
42
+ StructuredMemoryEntry,
43
+ } from './types.js';
44
+
45
+ export interface CompactionCandidate {
46
+ cutoffId: number;
47
+ olderMessages: StoredMessage[];
48
+ }
49
+
50
+ export interface MemoryBackend {
51
+ getOrCreateSession: (
52
+ sessionId: string,
53
+ guildId: string | null,
54
+ channelId: string,
55
+ ) => Session;
56
+ getSessionById: (sessionId: string) => Session | undefined;
57
+ getConversationHistory: (
58
+ sessionId: string,
59
+ limit?: number,
60
+ ) => StoredMessage[];
61
+ getRecentMessages: (sessionId: string, limit?: number) => StoredMessage[];
62
+ get: (sessionId: string, key: string) => unknown | null;
63
+ set: (sessionId: string, key: string, value: unknown) => void;
64
+ delete: (sessionId: string, key: string) => boolean;
65
+ list: (sessionId: string, prefix?: string) => StructuredMemoryEntry[];
66
+ appendCanonicalMessages: (params: {
67
+ agentId: string;
68
+ userId: string;
69
+ newMessages: Array<{
70
+ role: string;
71
+ content: string;
72
+ sessionId: string;
73
+ channelId?: string | null;
74
+ createdAt?: string | null;
75
+ }>;
76
+ windowSize?: number;
77
+ compactionThreshold?: number;
78
+ }) => CanonicalSession;
79
+ getCanonicalContext: (params: {
80
+ agentId: string;
81
+ userId: string;
82
+ windowSize?: number;
83
+ excludeSessionId?: string | null;
84
+ }) => CanonicalSessionContext;
85
+ addKnowledgeEntity: (params: {
86
+ id?: string | null;
87
+ name: string;
88
+ entityType: KnowledgeEntityTypeValue | string;
89
+ properties?: Record<string, unknown> | null;
90
+ }) => string;
91
+ addKnowledgeRelation: (params: {
92
+ source: string;
93
+ relation: KnowledgeRelationTypeValue | string;
94
+ target: string;
95
+ properties?: Record<string, unknown> | null;
96
+ confidence?: number;
97
+ }) => string;
98
+ queryKnowledgeGraph: (
99
+ pattern?: KnowledgeGraphPattern,
100
+ ) => KnowledgeGraphMatch[];
101
+ getCompactionCandidateMessages: (
102
+ sessionId: string,
103
+ keepRecent: number,
104
+ ) => CompactionCandidate | null;
105
+ storeMessage: (
106
+ sessionId: string,
107
+ userId: string,
108
+ username: string | null,
109
+ role: string,
110
+ content: string,
111
+ ) => number;
112
+ storeSemanticMemory: (params: {
113
+ sessionId: string;
114
+ role: string;
115
+ source?: string | null;
116
+ scope?: string | null;
117
+ metadata?: Record<string, unknown> | string | null;
118
+ content: string;
119
+ confidence?: number;
120
+ embedding?: number[] | null;
121
+ sourceMessageId?: number | null;
122
+ }) => number;
123
+ recallSemanticMemories: (params: {
124
+ sessionId: string;
125
+ query: string;
126
+ limit?: number;
127
+ minConfidence?: number;
128
+ queryEmbedding?: number[] | null;
129
+ filter?: SemanticRecallFilter;
130
+ }) => SemanticMemoryEntry[];
131
+ forgetSemanticMemory: (id: number) => boolean;
132
+ decaySemanticMemories: (params?: {
133
+ decayRate?: number;
134
+ staleAfterDays?: number;
135
+ minConfidence?: number;
136
+ }) => number;
137
+ clearSessionHistory: (sessionId: string) => number;
138
+ deleteMessagesBeforeId: (sessionId: string, cutoffId: number) => number;
139
+ updateSessionSummary: (sessionId: string, summary: string) => void;
140
+ markSessionMemoryFlush: (sessionId: string) => void;
141
+ }
142
+
143
+ export interface MemoryServiceConfig {
144
+ semanticRecallLimit: number;
145
+ semanticMinConfidence: number;
146
+ semanticMaxContentChars: number;
147
+ semanticDecayRate: number;
148
+ semanticDecayStaleAfterDays: number;
149
+ semanticDecayMinConfidence: number;
150
+ summaryDecayRate: number;
151
+ summaryMinConfidence: number;
152
+ summaryDiscardThreshold: number;
153
+ embeddingDimensions: number;
154
+ }
155
+
156
+ export interface EmbeddingProvider {
157
+ embed(text: string): number[] | null;
158
+ }
159
+
160
+ export interface StoreTurnParams {
161
+ sessionId: string;
162
+ user: {
163
+ userId: string;
164
+ username: string | null;
165
+ content: string;
166
+ };
167
+ assistant: {
168
+ userId?: string;
169
+ username?: string | null;
170
+ content: string;
171
+ };
172
+ }
173
+
174
+ export interface BuildMemoryPromptParams {
175
+ session: Session;
176
+ query: string;
177
+ semanticLimit?: number;
178
+ }
179
+
180
+ export interface BuildMemoryPromptResult {
181
+ promptSummary: string | null;
182
+ summaryConfidence: number | null;
183
+ semanticMemories: SemanticMemoryEntry[];
184
+ }
185
+
186
+ export interface RecallSemanticMemoriesParams {
187
+ sessionId: string;
188
+ query: string;
189
+ limit?: number;
190
+ minConfidence?: number;
191
+ filter?: SemanticRecallFilter;
192
+ }
193
+
194
+ const DEFAULT_CONFIG: MemoryServiceConfig = {
195
+ semanticRecallLimit: 5,
196
+ semanticMinConfidence: 0.2,
197
+ semanticMaxContentChars: 1_200,
198
+ semanticDecayRate: 0.1,
199
+ semanticDecayStaleAfterDays: 7,
200
+ semanticDecayMinConfidence: 0.1,
201
+ summaryDecayRate: 0.04,
202
+ summaryMinConfidence: 0.1,
203
+ summaryDiscardThreshold: 0.22,
204
+ embeddingDimensions: 128,
205
+ };
206
+
207
+ const DEFAULT_BACKEND: MemoryBackend = {
208
+ getOrCreateSession: dbGetOrCreateSession,
209
+ getSessionById: dbGetSessionById,
210
+ getConversationHistory: dbGetConversationHistory,
211
+ getRecentMessages: dbGetRecentMessages,
212
+ get: dbGetMemoryValue,
213
+ set: dbSetMemoryValue,
214
+ delete: dbDeleteMemoryValue,
215
+ list: dbListMemoryValues,
216
+ appendCanonicalMessages: dbAppendCanonicalMessages,
217
+ getCanonicalContext: dbGetCanonicalContext,
218
+ addKnowledgeEntity: dbAddKnowledgeEntity,
219
+ addKnowledgeRelation: dbAddKnowledgeRelation,
220
+ queryKnowledgeGraph: dbQueryKnowledgeGraph,
221
+ getCompactionCandidateMessages: dbGetCompactionCandidateMessages,
222
+ storeMessage: dbStoreMessage,
223
+ storeSemanticMemory: dbStoreSemanticMemory,
224
+ recallSemanticMemories: dbRecallSemanticMemories,
225
+ forgetSemanticMemory: dbForgetSemanticMemory,
226
+ decaySemanticMemories,
227
+ clearSessionHistory: dbClearSessionHistory,
228
+ deleteMessagesBeforeId: dbDeleteMessagesBeforeId,
229
+ updateSessionSummary: dbUpdateSessionSummary,
230
+ markSessionMemoryFlush: dbMarkSessionMemoryFlush,
231
+ };
232
+
233
+ function parseTimestamp(raw: string | null | undefined): number | null {
234
+ const value = (raw || '').trim();
235
+ if (!value) return null;
236
+ if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(value)) {
237
+ const parsed = Date.parse(`${value.replace(' ', 'T')}Z`);
238
+ return Number.isNaN(parsed) ? null : parsed;
239
+ }
240
+ const parsed = Date.parse(value);
241
+ return Number.isNaN(parsed) ? null : parsed;
242
+ }
243
+
244
+ export function computeDecayedConfidence(params: {
245
+ updatedAt: string | null | undefined;
246
+ decayRate: number;
247
+ minConfidence: number;
248
+ nowMs?: number;
249
+ }): number {
250
+ const updatedMs = parseTimestamp(params.updatedAt);
251
+ if (updatedMs == null) return 1;
252
+
253
+ const nowMs =
254
+ typeof params.nowMs === 'number' && Number.isFinite(params.nowMs)
255
+ ? params.nowMs
256
+ : Date.now();
257
+ const ageDays = Math.max(0, (nowMs - updatedMs) / 86_400_000);
258
+ const decayRate = Math.max(0, Math.min(0.95, params.decayRate));
259
+ const minConfidence = Math.max(0, Math.min(0.95, params.minConfidence));
260
+ const decayed = (1 - decayRate) ** ageDays;
261
+ return Math.max(minConfidence, Math.min(1, decayed));
262
+ }
263
+
264
+ function truncateInline(content: string, maxChars: number): string {
265
+ const compact = content.replace(/\s+/g, ' ').trim();
266
+ if (compact.length <= maxChars) return compact;
267
+ return `${compact.slice(0, maxChars)}...`;
268
+ }
269
+
270
+ class HashedTokenEmbeddingProvider implements EmbeddingProvider {
271
+ private readonly dimensions: number;
272
+
273
+ constructor(dimensions: number) {
274
+ this.dimensions = Math.max(16, Math.min(1024, Math.floor(dimensions)));
275
+ }
276
+
277
+ embed(text: string): number[] | null {
278
+ const normalized = text
279
+ .toLowerCase()
280
+ .replace(/[^a-z0-9_\s-]/g, ' ')
281
+ .replace(/\s+/g, ' ')
282
+ .trim();
283
+ if (!normalized) return null;
284
+
285
+ const tokens = normalized
286
+ .split(' ')
287
+ .map((token) => token.trim())
288
+ .filter((token) => token.length > 1)
289
+ .slice(0, 256);
290
+ if (tokens.length === 0) return null;
291
+
292
+ const vector = new Float32Array(this.dimensions);
293
+ for (const token of tokens) {
294
+ const hash = this.hashToken(token);
295
+ const index = hash % this.dimensions;
296
+ const sign = (hash & 1) === 0 ? 1 : -1;
297
+ vector[index] += sign * Math.min(4, token.length);
298
+ }
299
+
300
+ let norm = 0;
301
+ for (let i = 0; i < vector.length; i += 1) {
302
+ norm += vector[i] * vector[i];
303
+ }
304
+ if (norm <= Number.EPSILON) return null;
305
+ const scale = 1 / Math.sqrt(norm);
306
+ return Array.from(vector, (value) => value * scale);
307
+ }
308
+
309
+ private hashToken(token: string): number {
310
+ let hash = 2166136261;
311
+ for (let i = 0; i < token.length; i += 1) {
312
+ hash ^= token.charCodeAt(i);
313
+ hash = Math.imul(hash, 16777619);
314
+ }
315
+ return hash >>> 0;
316
+ }
317
+ }
318
+
319
+ export class MemoryService {
320
+ private readonly backend: MemoryBackend;
321
+ private readonly config: MemoryServiceConfig;
322
+ private readonly embeddingProvider: EmbeddingProvider;
323
+ private readonly consolidationEngine: MemoryConsolidationEngine;
324
+
325
+ constructor(
326
+ backend: MemoryBackend = DEFAULT_BACKEND,
327
+ config?: Partial<MemoryServiceConfig>,
328
+ embeddingProvider?: EmbeddingProvider,
329
+ ) {
330
+ this.backend = backend;
331
+ this.config = { ...DEFAULT_CONFIG, ...(config || {}) };
332
+ this.embeddingProvider =
333
+ embeddingProvider ||
334
+ new HashedTokenEmbeddingProvider(this.config.embeddingDimensions);
335
+ this.consolidationEngine = new MemoryConsolidationEngine(this.backend, {
336
+ decayRate: this.config.semanticDecayRate,
337
+ staleAfterDays: this.config.semanticDecayStaleAfterDays,
338
+ minConfidence: this.config.semanticDecayMinConfidence,
339
+ });
340
+ }
341
+
342
+ getOrCreateSession(
343
+ sessionId: string,
344
+ guildId: string | null,
345
+ channelId: string,
346
+ ): Session {
347
+ return this.backend.getOrCreateSession(sessionId, guildId, channelId);
348
+ }
349
+
350
+ getSessionById(sessionId: string): Session | undefined {
351
+ return this.backend.getSessionById(sessionId);
352
+ }
353
+
354
+ getConversationHistory(sessionId: string, limit = 50): StoredMessage[] {
355
+ return this.backend.getConversationHistory(sessionId, limit);
356
+ }
357
+
358
+ getRecentMessages(sessionId: string, limit?: number): StoredMessage[] {
359
+ return this.backend.getRecentMessages(sessionId, limit);
360
+ }
361
+
362
+ get(sessionId: string, key: string): unknown | null {
363
+ return this.backend.get(sessionId, key);
364
+ }
365
+
366
+ set(sessionId: string, key: string, value: unknown): void {
367
+ this.backend.set(sessionId, key, value);
368
+ }
369
+
370
+ delete(sessionId: string, key: string): boolean {
371
+ return this.backend.delete(sessionId, key);
372
+ }
373
+
374
+ list(sessionId: string, prefix?: string): StructuredMemoryEntry[] {
375
+ return this.backend.list(sessionId, prefix);
376
+ }
377
+
378
+ appendCanonicalMessages(params: {
379
+ agentId: string;
380
+ userId: string;
381
+ newMessages: Array<{
382
+ role: string;
383
+ content: string;
384
+ sessionId: string;
385
+ channelId?: string | null;
386
+ createdAt?: string | null;
387
+ }>;
388
+ windowSize?: number;
389
+ compactionThreshold?: number;
390
+ }): CanonicalSession {
391
+ return this.backend.appendCanonicalMessages(params);
392
+ }
393
+
394
+ getCanonicalContext(params: {
395
+ agentId: string;
396
+ userId: string;
397
+ windowSize?: number;
398
+ excludeSessionId?: string | null;
399
+ }): CanonicalSessionContext {
400
+ return this.backend.getCanonicalContext(params);
401
+ }
402
+
403
+ addKnowledgeEntity(params: {
404
+ id?: string | null;
405
+ name: string;
406
+ entityType: KnowledgeEntityTypeValue | string;
407
+ properties?: Record<string, unknown> | null;
408
+ }): string {
409
+ return this.backend.addKnowledgeEntity(params);
410
+ }
411
+
412
+ addKnowledgeRelation(params: {
413
+ source: string;
414
+ relation: KnowledgeRelationTypeValue | string;
415
+ target: string;
416
+ properties?: Record<string, unknown> | null;
417
+ confidence?: number;
418
+ }): string {
419
+ return this.backend.addKnowledgeRelation(params);
420
+ }
421
+
422
+ queryKnowledgeGraph(pattern?: KnowledgeGraphPattern): KnowledgeGraphMatch[] {
423
+ return this.backend.queryKnowledgeGraph(pattern);
424
+ }
425
+
426
+ consolidateMemories(
427
+ overrides?: Partial<MemoryConsolidationConfig>,
428
+ ): MemoryConsolidationReport {
429
+ return this.consolidationEngine.consolidate(overrides);
430
+ }
431
+
432
+ recallSemanticMemories(
433
+ params: RecallSemanticMemoriesParams,
434
+ ): SemanticMemoryEntry[] {
435
+ const limit = Math.max(
436
+ 1,
437
+ Math.min(Math.floor(params.limit || this.config.semanticRecallLimit), 50),
438
+ );
439
+ const rawMinConfidence =
440
+ typeof params.minConfidence === 'number' &&
441
+ Number.isFinite(params.minConfidence)
442
+ ? params.minConfidence
443
+ : this.config.semanticMinConfidence;
444
+ const minConfidence = Math.max(0, Math.min(1, rawMinConfidence));
445
+ const query = params.query.trim();
446
+
447
+ return this.backend.recallSemanticMemories({
448
+ sessionId: params.sessionId,
449
+ query,
450
+ limit,
451
+ minConfidence,
452
+ queryEmbedding: this.embeddingProvider.embed(query),
453
+ filter: params.filter,
454
+ });
455
+ }
456
+
457
+ getCompactionCandidateMessages(
458
+ sessionId: string,
459
+ keepRecent: number,
460
+ ): CompactionCandidate | null {
461
+ return this.backend.getCompactionCandidateMessages(sessionId, keepRecent);
462
+ }
463
+
464
+ clearSessionHistory(sessionId: string): number {
465
+ return this.backend.clearSessionHistory(sessionId);
466
+ }
467
+
468
+ deleteMessagesBeforeId(sessionId: string, cutoffId: number): number {
469
+ return this.backend.deleteMessagesBeforeId(sessionId, cutoffId);
470
+ }
471
+
472
+ updateSessionSummary(sessionId: string, summary: string): void {
473
+ this.backend.updateSessionSummary(sessionId, summary);
474
+ }
475
+
476
+ markSessionMemoryFlush(sessionId: string): void {
477
+ this.backend.markSessionMemoryFlush(sessionId);
478
+ }
479
+
480
+ forgetSemanticMemory(id: number): boolean {
481
+ return this.backend.forgetSemanticMemory(id);
482
+ }
483
+
484
+ storeMessage(params: {
485
+ sessionId: string;
486
+ userId: string;
487
+ username: string | null;
488
+ role: string;
489
+ content: string;
490
+ }): number {
491
+ return this.backend.storeMessage(
492
+ params.sessionId,
493
+ params.userId,
494
+ params.username,
495
+ params.role,
496
+ params.content,
497
+ );
498
+ }
499
+
500
+ storeTurn(params: StoreTurnParams): void {
501
+ this.storeMessage({
502
+ sessionId: params.sessionId,
503
+ userId: params.user.userId,
504
+ username: params.user.username,
505
+ role: 'user',
506
+ content: params.user.content,
507
+ });
508
+ const assistantMessageId = this.storeMessage({
509
+ sessionId: params.sessionId,
510
+ userId: params.assistant.userId || 'assistant',
511
+ username: params.assistant.username || null,
512
+ role: 'assistant',
513
+ content: params.assistant.content,
514
+ });
515
+
516
+ const interactionText = this.normalizeSemanticContent(
517
+ `User asked: ${params.user.content.trim()}\nI responded: ${params.assistant.content.trim()}`,
518
+ );
519
+ if (!interactionText) return;
520
+
521
+ this.backend.storeSemanticMemory({
522
+ sessionId: params.sessionId,
523
+ role: 'assistant',
524
+ source: 'conversation',
525
+ scope: 'episodic',
526
+ metadata: {},
527
+ content: interactionText,
528
+ confidence: 1,
529
+ embedding: this.embeddingProvider.embed(interactionText),
530
+ sourceMessageId: assistantMessageId,
531
+ });
532
+ }
533
+
534
+ buildPromptMemoryContext(
535
+ params: BuildMemoryPromptParams,
536
+ ): BuildMemoryPromptResult {
537
+ const summaryText = (params.session.session_summary || '').trim();
538
+ const summaryConfidence = summaryText
539
+ ? computeDecayedConfidence({
540
+ updatedAt: params.session.summary_updated_at,
541
+ decayRate: this.config.summaryDecayRate,
542
+ minConfidence: this.config.summaryMinConfidence,
543
+ })
544
+ : null;
545
+
546
+ const includeSummary =
547
+ summaryText &&
548
+ (summaryConfidence == null ||
549
+ summaryConfidence >= this.config.summaryDiscardThreshold);
550
+
551
+ const semanticLimit = Math.max(
552
+ 1,
553
+ Math.min(
554
+ Math.floor(params.semanticLimit || this.config.semanticRecallLimit),
555
+ 12,
556
+ ),
557
+ );
558
+ const semanticMemories = this.recallSemanticMemories({
559
+ sessionId: params.session.id,
560
+ query: params.query,
561
+ limit: semanticLimit,
562
+ minConfidence: this.config.semanticMinConfidence,
563
+ });
564
+
565
+ const sections: string[] = [];
566
+ if (includeSummary) {
567
+ if (summaryConfidence != null && summaryConfidence < 0.999) {
568
+ sections.push(
569
+ `Summary confidence: ${Math.round(summaryConfidence * 100)}% (decayed by age).`,
570
+ );
571
+ }
572
+ sections.push(summaryText);
573
+ }
574
+
575
+ if (semanticMemories.length > 0) {
576
+ const lines = semanticMemories.map((memory) => {
577
+ const confidence = Math.round(
578
+ Math.max(0, Math.min(1, memory.confidence)) * 100,
579
+ );
580
+ return `- (${confidence}%) ${truncateInline(memory.content, 220)}`;
581
+ });
582
+ sections.push(
583
+ [
584
+ '### Relevant Memory Recall',
585
+ 'Topic-matched context from older turns (vector cosine search):',
586
+ ...lines,
587
+ ].join('\n'),
588
+ );
589
+ }
590
+
591
+ const promptSummary = sections.join('\n\n').trim();
592
+ return {
593
+ promptSummary: promptSummary || null,
594
+ summaryConfidence,
595
+ semanticMemories,
596
+ };
597
+ }
598
+
599
+ private normalizeSemanticContent(content: string): string {
600
+ const compact = content.replace(/\s+/g, ' ').trim();
601
+ if (compact.length <= this.config.semanticMaxContentChars) return compact;
602
+ return compact.slice(0, this.config.semanticMaxContentChars);
603
+ }
604
+ }
605
+
606
+ export const memoryService = new MemoryService();