opcode-pg-memory 2.2.8 → 2.3.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 (76) hide show
  1. package/dist/cli.js +232 -214
  2. package/dist/index.d.ts +1 -0
  3. package/dist/index.d.ts.map +1 -1
  4. package/dist/index.js +30 -21006
  5. package/dist/index.js.map +1 -0
  6. package/dist/mcp-server.js +319 -302
  7. package/dist/mcp-server.js.map +1 -0
  8. package/dist/src/cache/semantic-cache.js +399 -0
  9. package/dist/src/cache/semantic-cache.js.map +1 -0
  10. package/dist/src/cli.js +404 -0
  11. package/dist/src/cli.js.map +1 -0
  12. package/dist/src/config.d.ts +5 -0
  13. package/dist/src/config.d.ts.map +1 -1
  14. package/dist/src/config.js +89 -0
  15. package/dist/src/config.js.map +1 -0
  16. package/dist/src/db/init-db.js +545 -0
  17. package/dist/src/db/init-db.js.map +1 -0
  18. package/dist/src/hooks/message-part-updated.js +203 -0
  19. package/dist/src/hooks/message-part-updated.js.map +1 -0
  20. package/dist/src/hooks/message-updated.js +347 -0
  21. package/dist/src/hooks/message-updated.js.map +1 -0
  22. package/dist/src/hooks/session-compacting.js +179 -0
  23. package/dist/src/hooks/session-compacting.js.map +1 -0
  24. package/dist/src/hooks/session-completed.js +337 -0
  25. package/dist/src/hooks/session-completed.js.map +1 -0
  26. package/dist/src/hooks/session-created.js +206 -0
  27. package/dist/src/hooks/session-created.js.map +1 -0
  28. package/dist/src/hooks/tool-execute.js +267 -0
  29. package/dist/src/hooks/tool-execute.js.map +1 -0
  30. package/dist/src/index.d.ts +1 -0
  31. package/dist/src/index.d.ts.map +1 -1
  32. package/dist/src/index.js +642 -0
  33. package/dist/src/index.js.map +1 -0
  34. package/dist/src/mcp/hindsight-reflect-omo.js +318 -0
  35. package/dist/src/mcp/hindsight-reflect-omo.js.map +1 -0
  36. package/dist/src/mcp/hindsight-reflect.js +838 -0
  37. package/dist/src/mcp/hindsight-reflect.js.map +1 -0
  38. package/dist/src/mcp/recall-memory-omo.js +263 -0
  39. package/dist/src/mcp/recall-memory-omo.js.map +1 -0
  40. package/dist/src/mcp/recall-memory.d.ts +6 -0
  41. package/dist/src/mcp/recall-memory.d.ts.map +1 -1
  42. package/dist/src/mcp/recall-memory.js +900 -0
  43. package/dist/src/mcp/recall-memory.js.map +1 -0
  44. package/dist/src/omo/adapter.js +583 -0
  45. package/dist/src/omo/adapter.js.map +1 -0
  46. package/dist/src/omo/types.js +44 -0
  47. package/dist/src/omo/types.js.map +1 -0
  48. package/dist/src/services/db-polling.d.ts +33 -0
  49. package/dist/src/services/db-polling.d.ts.map +1 -0
  50. package/dist/src/services/db-polling.js +104 -0
  51. package/dist/src/services/db-polling.js.map +1 -0
  52. package/dist/src/services/event-synchronizer.d.ts +15 -0
  53. package/dist/src/services/event-synchronizer.d.ts.map +1 -0
  54. package/dist/src/services/event-synchronizer.js +119 -0
  55. package/dist/src/services/event-synchronizer.js.map +1 -0
  56. package/dist/src/services/keyword.js +29 -0
  57. package/dist/src/services/keyword.js.map +1 -0
  58. package/dist/src/services/logger.js +42 -0
  59. package/dist/src/services/logger.js.map +1 -0
  60. package/dist/src/services/opencode-schema-adapter.d.ts +100 -0
  61. package/dist/src/services/opencode-schema-adapter.d.ts.map +1 -0
  62. package/dist/src/services/opencode-schema-adapter.js +192 -0
  63. package/dist/src/services/opencode-schema-adapter.js.map +1 -0
  64. package/dist/src/services/privacy.js +23 -0
  65. package/dist/src/services/privacy.js.map +1 -0
  66. package/dist/src/topic/segment-manager.js +447 -0
  67. package/dist/src/topic/segment-manager.js.map +1 -0
  68. package/dist/src/types.d.ts +20 -2
  69. package/dist/src/types.d.ts.map +1 -1
  70. package/dist/src/types.js +8 -0
  71. package/dist/src/types.js.map +1 -0
  72. package/dist/src/utils/embedding.js +180 -0
  73. package/dist/src/utils/embedding.js.map +1 -0
  74. package/dist/src/utils/token-budget.js +152 -0
  75. package/dist/src/utils/token-budget.js.map +1 -0
  76. package/package.json +5 -4
@@ -0,0 +1,642 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ exports.hindsightReflect = exports.recallMemory = exports.OpenCodePGMemory = void 0;
18
+ const init_db_1 = require("./db/init-db");
19
+ const tool_execute_1 = require("./hooks/tool-execute");
20
+ const message_part_updated_1 = require("./hooks/message-part-updated");
21
+ const session_compacting_1 = require("./hooks/session-compacting");
22
+ const recall_memory_1 = require("./mcp/recall-memory");
23
+ const hindsight_reflect_1 = require("./mcp/hindsight-reflect");
24
+ const semantic_cache_1 = require("./cache/semantic-cache");
25
+ const logger_1 = require("./services/logger");
26
+ const db_polling_1 = require("./services/db-polling");
27
+ const event_synchronizer_1 = require("./services/event-synchronizer");
28
+ const logger = (0, logger_1.createLogger)('plugin');
29
+ const token_budget_1 = require("./utils/token-budget");
30
+ const keyword_1 = require("./services/keyword");
31
+ // ============================================================================
32
+ // Default Configuration
33
+ // ============================================================================
34
+ const DEFAULT_PLUGIN_CONFIG = {
35
+ database: {
36
+ host: process.env.PG_HOST || 'localhost',
37
+ port: parseInt(process.env.PG_PORT || '5432', 10),
38
+ database: process.env.PG_DATABASE || 'opencode_memory',
39
+ user: process.env.PG_USER || 'opencode',
40
+ password: process.env.PG_PASSWORD || '',
41
+ ssl: process.env.PG_SSL === 'true',
42
+ },
43
+ embedding: {
44
+ model: 'text-embedding-3-small',
45
+ dimensions: 1536,
46
+ batchSize: 100,
47
+ },
48
+ cache: {
49
+ initialThreshold: 0.92,
50
+ adjustmentStep: 0.02,
51
+ minThreshold: 0.85,
52
+ maxThreshold: 0.97,
53
+ enabled: true,
54
+ },
55
+ reflection: {
56
+ observationThreshold: 30,
57
+ segmentThreshold: 5,
58
+ modelSize: '7b',
59
+ offPeakHours: [1, 2, 3, 4, 5],
60
+ enabled: true,
61
+ },
62
+ tokenBudget: {
63
+ contextLimitRatio: 0.05,
64
+ minTokens: 500,
65
+ maxTokens: 4000,
66
+ },
67
+ retrieval: {
68
+ defaultStrategies: ['semantic', 'bm25', 'graph'],
69
+ rerankEnabled: true,
70
+ maxResults: 10,
71
+ weights: {
72
+ semantic: 0.5,
73
+ recency: 0.3,
74
+ importance: 0.2,
75
+ },
76
+ },
77
+ };
78
+ // ============================================================================
79
+ // Internal Plugin State (NOT exported)
80
+ // ============================================================================
81
+ class OpenCodePGMemoryPlugin {
82
+ pool = null;
83
+ config;
84
+ cacheManager = null;
85
+ initialized = false;
86
+ cleanupIntervals = [];
87
+ constructor(config = {}) {
88
+ this.config = this.mergeConfig(config);
89
+ }
90
+ mergeConfig(userConfig) {
91
+ return {
92
+ ...DEFAULT_PLUGIN_CONFIG,
93
+ ...userConfig,
94
+ database: { ...DEFAULT_PLUGIN_CONFIG.database, ...userConfig.database },
95
+ embedding: { ...DEFAULT_PLUGIN_CONFIG.embedding, ...userConfig.embedding },
96
+ cache: { ...DEFAULT_PLUGIN_CONFIG.cache, ...userConfig.cache },
97
+ reflection: { ...DEFAULT_PLUGIN_CONFIG.reflection, ...userConfig.reflection },
98
+ tokenBudget: { ...DEFAULT_PLUGIN_CONFIG.tokenBudget, ...userConfig.tokenBudget },
99
+ retrieval: { ...DEFAULT_PLUGIN_CONFIG.retrieval, ...userConfig.retrieval },
100
+ };
101
+ }
102
+ async initialize() {
103
+ if (this.initialized)
104
+ return;
105
+ logger.info('Initializing plugin...');
106
+ try {
107
+ this.pool = await (0, init_db_1.initializeDatabase)(this.config.database);
108
+ if (this.config.cache.enabled) {
109
+ this.cacheManager = (0, semantic_cache_1.createCacheManager)(this.pool, this.config.cache);
110
+ }
111
+ this.startCleanupTasks();
112
+ this.initialized = true;
113
+ logger.info('Plugin initialized successfully');
114
+ }
115
+ catch (error) {
116
+ logger.error('Plugin initialization failed:', error);
117
+ throw error;
118
+ }
119
+ }
120
+ async close() {
121
+ if (!this.initialized)
122
+ return;
123
+ logger.info('Closing plugin...');
124
+ for (const interval of this.cleanupIntervals) {
125
+ clearInterval(interval);
126
+ }
127
+ this.cleanupIntervals = [];
128
+ await (0, init_db_1.closeDatabase)();
129
+ this.pool = null;
130
+ this.cacheManager = null;
131
+ this.initialized = false;
132
+ logger.info('Plugin closed');
133
+ }
134
+ startCleanupTasks() {
135
+ // Clean up expired message part accumulators every 5 minutes
136
+ this.cleanupIntervals.push(setInterval(() => {
137
+ (0, message_part_updated_1.cleanupExpiredAccumulators)(300000);
138
+ }, 300000));
139
+ // Clean up expired cache entries daily
140
+ this.cleanupIntervals.push(setInterval(async () => {
141
+ if (this.cacheManager) {
142
+ await this.cacheManager.cleanupExpiredCache(30);
143
+ }
144
+ }, 24 * 60 * 60 * 1000));
145
+ }
146
+ getPool() {
147
+ if (!this.pool) {
148
+ throw new Error('Plugin not initialized. Call initialize() first.');
149
+ }
150
+ return this.pool;
151
+ }
152
+ getCacheManager() {
153
+ return this.cacheManager;
154
+ }
155
+ }
156
+ // ============================================================================
157
+ // Plugin Export - Official OpenCode Plugin API format
158
+ // ============================================================================
159
+ const OpenCodePGMemory = async (ctx) => {
160
+ // Read config from environment
161
+ const config = buildConfigFromEnv();
162
+ // Create internal state
163
+ const plugin = new OpenCodePGMemoryPlugin(config);
164
+ await plugin.initialize();
165
+ const pool = plugin.getPool();
166
+ const cacheManager = plugin.getCacheManager();
167
+ const logger = (0, logger_1.createLogger)('plugin');
168
+ const eventSync = new event_synchronizer_1.EventSynchronizer(pool, {
169
+ mode: (process.env.PG_MEMORY_SYNC_MODE || 'hybrid'),
170
+ pollingIntervalMs: parseInt(process.env.PG_MEMORY_POLL_INTERVAL || '5000'),
171
+ });
172
+ let dbPolling = null;
173
+ if (process.env.PG_MEMORY_DB_POLLING !== 'false') {
174
+ dbPolling = new db_polling_1.OpenCodeDBPollingSource(pool, eventSync, {
175
+ intervalMs: parseInt(process.env.PG_MEMORY_POLL_INTERVAL || '5000'),
176
+ backoffBaseMs: 1000,
177
+ backoffMaxMs: 60000,
178
+ });
179
+ dbPolling.start().catch(err => logger.warn('DB polling start failed', err));
180
+ }
181
+ // Cleanup on process exit
182
+ const cleanup = () => {
183
+ if (dbPolling)
184
+ dbPolling.stop();
185
+ plugin.close().catch((err) => logger.error('Cleanup error:', err));
186
+ };
187
+ process.on('exit', cleanup);
188
+ process.on('SIGINT', cleanup);
189
+ process.on('SIGTERM', cleanup);
190
+ // Track sessions that have received memory injection
191
+ const injectedSessions = new Set();
192
+ const injectedSystemPrompt = new Set();
193
+ // ==========================================================================
194
+ // Return hooks object
195
+ // ==========================================================================
196
+ return {
197
+ // -----------------------------------------------------------------------
198
+ // Unified event hook - receives ALL bus events
199
+ // -----------------------------------------------------------------------
200
+ event: async (input) => {
201
+ const { type, properties } = input.event;
202
+ try {
203
+ const sid = properties.sessionID || properties.session?.id;
204
+ if (!sid)
205
+ return;
206
+ await eventSync.handleEvent({
207
+ id: `${type}:${sid}:${Date.now()}`,
208
+ type: type,
209
+ sessionId: sid,
210
+ timestamp: Date.now(),
211
+ version: 1,
212
+ source: 'hook',
213
+ data: properties,
214
+ });
215
+ }
216
+ catch (error) {
217
+ console.error(`[PG Memory] Error handling event '${type}':`, error);
218
+ }
219
+ },
220
+ // -----------------------------------------------------------------------
221
+ // chat.message - inject relevant memories on first message
222
+ // -----------------------------------------------------------------------
223
+ 'chat.message': async (input, output) => {
224
+ try {
225
+ // Only inject on the first message of a session
226
+ if (!injectedSessions.has(input.sessionID)) {
227
+ injectedSessions.add(input.sessionID);
228
+ logger.info('First message in session', { sessionID: input.sessionID });
229
+ // Retrieve relevant facts for this session
230
+ const contextLimit = 128000; // default model context limit
231
+ const budget = (0, token_budget_1.calculateTokenBudget)(contextLimit, config.tokenBudget || {});
232
+ const facts = await retrieveFactsForInjection(input.sessionID, { maxTokens: typeof budget === 'number' ? budget : 2000 }, pool, { minConfidence: 0.5, minWeight: 0.3 });
233
+ if (facts.length > 0) {
234
+ // Format as context block
235
+ const contextBlock = formatMemoryContext(facts);
236
+ // Inject as synthetic part (visible to LLM, hidden in TUI)
237
+ if (output.parts && Array.isArray(output.parts)) {
238
+ output.parts.unshift({
239
+ id: `prt_pgmemory-context-${Date.now()}`,
240
+ sessionID: input.sessionID,
241
+ messageID: output.message?.id || input.messageID || '',
242
+ type: 'text',
243
+ text: contextBlock,
244
+ synthetic: true,
245
+ });
246
+ logger.info(`Injected ${facts.length} memories`, { sessionID: input.sessionID });
247
+ }
248
+ }
249
+ else {
250
+ logger.debug('No memories to inject');
251
+ }
252
+ }
253
+ // Check for memory keywords AFTER first-message injection
254
+ const userText = output.parts
255
+ .filter((p) => p.type === 'text' && !p.synthetic)
256
+ .map((p) => p.text || '')
257
+ .join(' ');
258
+ if ((0, keyword_1.detectMemoryKeyword)(userText)) {
259
+ logger.info('Memory keyword detected', { sessionID: input.sessionID });
260
+ output.parts.push({
261
+ id: `prt_pgmemory-nudge-${Date.now()}`,
262
+ sessionID: input.sessionID,
263
+ messageID: output.message?.id || input.messageID || '',
264
+ type: 'text',
265
+ text: keyword_1.MEMORY_NUDGE_MESSAGE,
266
+ synthetic: true,
267
+ });
268
+ }
269
+ }
270
+ catch (error) {
271
+ logger.error('Failed to inject memories in chat.message', error);
272
+ // Non-blocking: never crash the message flow
273
+ }
274
+ },
275
+ // -----------------------------------------------------------------------
276
+ // tool.execute.before - intercept before tool execution
277
+ // -----------------------------------------------------------------------
278
+ 'tool.execute.before': async (input, output) => {
279
+ try {
280
+ await (0, tool_execute_1.handleToolExecuteBefore)({
281
+ session: { id: input.sessionID },
282
+ tool: { name: input.tool, parameters: output.args || {} },
283
+ messageId: input.callID,
284
+ }, { parameters: output.args }, pool);
285
+ }
286
+ catch (error) {
287
+ logger.error('Error in tool.execute.before:', error);
288
+ }
289
+ },
290
+ // -----------------------------------------------------------------------
291
+ // tool.execute.after - intercept after tool execution
292
+ // -----------------------------------------------------------------------
293
+ 'tool.execute.after': async (input, output) => {
294
+ try {
295
+ const result = {
296
+ success: !(output.output && output.output.toLowerCase().includes('error')),
297
+ data: output.output,
298
+ error: output.output && output.output.toLowerCase().includes('error') ? output.output : undefined,
299
+ };
300
+ await (0, tool_execute_1.handleToolExecuteAfter)({
301
+ session: { id: input.sessionID },
302
+ tool: { name: input.tool, parameters: input.args || {} },
303
+ result,
304
+ messageId: input.callID,
305
+ executionTimeMs: output.metadata?.executionTimeMs || 0,
306
+ }, {}, pool);
307
+ }
308
+ catch (error) {
309
+ logger.error('Error in tool.execute.after:', error);
310
+ }
311
+ },
312
+ // -----------------------------------------------------------------------
313
+ // experimental.chat.system.transform - inject MCP tool usage instructions
314
+ // -----------------------------------------------------------------------
315
+ 'experimental.chat.system.transform': async (input, output) => {
316
+ try {
317
+ const sessionId = input.sessionID || 'default';
318
+ if (injectedSystemPrompt.has(sessionId))
319
+ return;
320
+ injectedSystemPrompt.add(sessionId);
321
+ output.system = output.system || [];
322
+ output.system.push(`## PG Memory Tools Available
323
+
324
+ You have access to long-term memory via the pg-memory plugin. These tools help you reuse knowledge across sessions:
325
+
326
+ ### recall_memory — search historical memories
327
+ Call this BEFORE starting any new task. It retrieves relevant entities, observations, and reflections from past sessions.
328
+ - Example: recall_memory({ query: "database connection pool tuning" })
329
+ - Best practice: always pass your current task goal as the query
330
+
331
+ ### hindsight_reflect — reflect on session
332
+ Call this AFTER completing significant work to extract reusable patterns.
333
+ - Example: hindsight_reflect({ trigger_type: "manual" })
334
+ - Reflexions are automatically available in future sessions
335
+
336
+ ### When to use
337
+ - Before diving into a new problem → recall_memory(query=<your goal>)
338
+ - After completing a major task → hindsight_reflect()
339
+ - When you need historical context about a specific topic → recall_memory(topic_segment_id=<id>)
340
+ `);
341
+ }
342
+ catch (error) {
343
+ logger.error('Error in system.transform:', error);
344
+ }
345
+ },
346
+ // -----------------------------------------------------------------------
347
+ // experimental.session.compacting - session compaction hook
348
+ // -----------------------------------------------------------------------
349
+ 'experimental.session.compacting': async (input, output) => {
350
+ try {
351
+ // Show compaction notification
352
+ if (ctx.client?.tui?.showToast) {
353
+ ctx.client.tui.showToast({
354
+ body: {
355
+ title: 'PG Memory Compaction',
356
+ message: 'Compacting with pg-memory context...',
357
+ variant: 'warning',
358
+ duration: 3000,
359
+ },
360
+ }).catch(() => { });
361
+ }
362
+ await (0, session_compacting_1.handleSessionCompacting)({
363
+ session: { id: input.sessionID },
364
+ messagesToCompact: output.context || [],
365
+ compactionStrategy: 'prune',
366
+ }, { preserveMessageIds: [] }, pool);
367
+ // Show success toast
368
+ if (ctx.client?.tui?.showToast) {
369
+ setTimeout(() => {
370
+ ctx.client.tui.showToast({
371
+ body: {
372
+ title: 'Compaction Complete',
373
+ message: 'PG Memory preserved high-value observations',
374
+ variant: 'success',
375
+ duration: 2000,
376
+ },
377
+ }).catch(() => { });
378
+ }, 500);
379
+ }
380
+ }
381
+ catch (error) {
382
+ logger.error('Error in experimental.session.compacting:', error);
383
+ }
384
+ },
385
+ // -----------------------------------------------------------------------
386
+ // Custom MCP Tools
387
+ // -----------------------------------------------------------------------
388
+ tool: {
389
+ recall_memory: {
390
+ description: '从长期记忆中检索相关事实、实体、观察和反思,支持多策略并行检索(语义+BM25+图遍历)。使用多维评分函数:Relevance = 0.5*SemSim + 0.3/(1+RecencyDays) + 0.2*Importance',
391
+ args: {
392
+ query: {
393
+ type: 'string',
394
+ description: '检索查询文本',
395
+ },
396
+ session_id: {
397
+ type: 'string',
398
+ description: '当前会话ID,用于上下文过滤',
399
+ },
400
+ retrieval_strategies: {
401
+ type: 'array',
402
+ items: { type: 'string', enum: ['semantic', 'bm25', 'graph', 'keyword'] },
403
+ default: ['semantic', 'bm25', 'graph'],
404
+ description: '检索策略组合',
405
+ },
406
+ max_results: {
407
+ type: 'number',
408
+ minimum: 1,
409
+ maximum: 50,
410
+ default: 10,
411
+ description: '返回结果数量上限',
412
+ },
413
+ filters: {
414
+ type: 'object',
415
+ properties: {
416
+ entity_types: {
417
+ type: 'array',
418
+ items: { type: 'string' },
419
+ description: '实体类型过滤',
420
+ },
421
+ tier_levels: {
422
+ type: 'array',
423
+ items: { enum: ['permanent', 'project', 'session'] },
424
+ description: '层级过滤',
425
+ },
426
+ min_confidence: {
427
+ type: 'number',
428
+ minimum: 0,
429
+ maximum: 1,
430
+ default: 0.5,
431
+ description: '最低置信度',
432
+ },
433
+ time_range_days: {
434
+ type: 'number',
435
+ description: '时间范围(天)',
436
+ },
437
+ },
438
+ },
439
+ rerank: {
440
+ type: 'boolean',
441
+ default: true,
442
+ description: '是否使用交叉编码器重排序',
443
+ },
444
+ },
445
+ execute: async (args, _context) => {
446
+ return (0, recall_memory_1.recallMemory)(args, pool, {
447
+ maxResults: plugin.config.retrieval.maxResults,
448
+ rerankEnabled: plugin.config.retrieval.rerankEnabled,
449
+ weights: plugin.config.retrieval.weights,
450
+ });
451
+ },
452
+ },
453
+ hindsight_reflect: {
454
+ description: '对会话观察进行反思,归纳经验模式,生成可复用的反思记录。每30-50条经验触发一次,使用7B蒸馏模型在低峰期执行。',
455
+ args: {
456
+ session_id: {
457
+ type: 'string',
458
+ description: '要反思的会话ID',
459
+ },
460
+ trigger_type: {
461
+ type: 'string',
462
+ enum: ['threshold', 'scheduled', 'manual'],
463
+ default: 'threshold',
464
+ description: '触发类型',
465
+ },
466
+ observation_threshold: {
467
+ type: 'number',
468
+ minimum: 10,
469
+ maximum: 100,
470
+ default: 30,
471
+ description: '触发反思的观察数量阈值',
472
+ },
473
+ model_size: {
474
+ type: 'string',
475
+ enum: ['7b', '14b', 'full'],
476
+ default: '7b',
477
+ description: '使用的模型规模',
478
+ },
479
+ },
480
+ execute: async (args, _context) => {
481
+ return (0, hindsight_reflect_1.hindsightReflect)(args, pool, {
482
+ observationThreshold: plugin.config.reflection.observationThreshold,
483
+ modelSize: plugin.config.reflection.modelSize,
484
+ offPeakHours: plugin.config.reflection.offPeakHours,
485
+ });
486
+ },
487
+ },
488
+ },
489
+ };
490
+ };
491
+ exports.OpenCodePGMemory = OpenCodePGMemory;
492
+ // ============================================================================
493
+ // Helpers
494
+ // ============================================================================
495
+ /**
496
+ * Build plugin config from environment variables, with sensible defaults.
497
+ */
498
+ function buildConfigFromEnv() {
499
+ return {
500
+ database: {
501
+ host: process.env.PG_HOST || 'localhost',
502
+ port: parseInt(process.env.PG_PORT || '5432', 10),
503
+ database: process.env.PG_DATABASE || 'opencode_memory',
504
+ user: process.env.PG_USER || 'opencode',
505
+ password: process.env.PG_PASSWORD || '',
506
+ ssl: process.env.PG_SSL === 'true',
507
+ },
508
+ embedding: {
509
+ model: process.env.EMBEDDING_MODEL || 'text-embedding-3-small',
510
+ dimensions: parseInt(process.env.EMBEDDING_DIMENSIONS || '1536', 10),
511
+ batchSize: parseInt(process.env.EMBEDDING_BATCH_SIZE || '100', 10),
512
+ },
513
+ cache: {
514
+ enabled: process.env.CACHE_ENABLED !== 'false',
515
+ initialThreshold: parseFloat(process.env.CACHE_INITIAL_THRESHOLD || '0.92'),
516
+ adjustmentStep: parseFloat(process.env.CACHE_ADJUSTMENT_STEP || '0.02'),
517
+ minThreshold: parseFloat(process.env.CACHE_MIN_THRESHOLD || '0.85'),
518
+ maxThreshold: parseFloat(process.env.CACHE_MAX_THRESHOLD || '0.97'),
519
+ },
520
+ reflection: {
521
+ enabled: process.env.REFLECTION_ENABLED !== 'false',
522
+ observationThreshold: parseInt(process.env.REFLECTION_OBSERVATION_THRESHOLD || '30', 10),
523
+ segmentThreshold: parseInt(process.env.REFLECTION_SEGMENT_THRESHOLD || '5', 10),
524
+ modelSize: process.env.REFLECTION_MODEL_SIZE || '7b',
525
+ offPeakHours: (process.env.REFLECTION_OFF_PEAK_HOURS || '1,2,3,4,5')
526
+ .split(',')
527
+ .map((n) => parseInt(n.trim(), 10))
528
+ .filter((n) => !isNaN(n)),
529
+ },
530
+ tokenBudget: {
531
+ contextLimitRatio: parseFloat(process.env.TOKEN_CONTEXT_LIMIT_RATIO || '0.05'),
532
+ minTokens: parseInt(process.env.TOKEN_MIN_TOKENS || '500', 10),
533
+ maxTokens: parseInt(process.env.TOKEN_MAX_TOKENS || '4000', 10),
534
+ },
535
+ retrieval: {
536
+ defaultStrategies: (process.env.RETRIEVAL_STRATEGIES || 'semantic,bm25,graph')
537
+ .split(',')
538
+ .map((s) => s.trim()),
539
+ rerankEnabled: process.env.RETRIEVAL_RERANK !== 'false',
540
+ maxResults: parseInt(process.env.RETRIEVAL_MAX_RESULTS || '10', 10),
541
+ weights: {
542
+ semantic: parseFloat(process.env.RETRIEVAL_WEIGHT_SEMANTIC || '0.5'),
543
+ recency: parseFloat(process.env.RETRIEVAL_WEIGHT_RECENCY || '0.3'),
544
+ importance: parseFloat(process.env.RETRIEVAL_WEIGHT_IMPORTANCE || '0.2'),
545
+ },
546
+ },
547
+ };
548
+ }
549
+ /**
550
+ * Normalize session data from various event formats into a consistent shape
551
+ * compatible with the existing handler functions.
552
+ */
553
+ function normalizeSessionData(data) {
554
+ return {
555
+ id: data.id || data.external_id || '',
556
+ projectId: data.projectId || data.project_id || undefined,
557
+ model: {
558
+ id: data.model?.id || data.modelID || data.model_id || 'unknown',
559
+ contextLimit: data.model?.contextLimit || data.contextLimit || data.model_context_limit || 128000,
560
+ name: data.model?.name || data.modelName || data.model_name || 'unknown',
561
+ },
562
+ messages: data.messages || [],
563
+ };
564
+ }
565
+ // ============================================================================
566
+ // Chat Message Helpers
567
+ // ============================================================================
568
+ /**
569
+ * Format memory facts into a context block for LLM consumption.
570
+ */
571
+ function formatMemoryContext(facts) {
572
+ if (facts.length === 0)
573
+ return '';
574
+ const lines = ['[PG MEMORY]', 'Relevant context from previous sessions:', ''];
575
+ for (const f of facts) {
576
+ const typeLabel = f.type === 'reflection' ? 'REFLECTION'
577
+ : f.type === 'observation' ? 'OBSERVATION'
578
+ : f.type === 'entity' ? 'ENTITY'
579
+ : f.type.toUpperCase();
580
+ const tierLabel = f.tier ? ` [${f.tier}]` : '';
581
+ const score = f.relevanceScore ? ` (${(f.relevanceScore * 100).toFixed(0)}%)` : '';
582
+ lines.push(`- [${typeLabel}${tierLabel}] ${f.content.substring(0, 200)}${score}`);
583
+ }
584
+ lines.push('', 'Tip: Use recall_memory(query="...") to search more specific memories, or /pg-memory-reflect to summarize this session.');
585
+ return lines.join('\n');
586
+ }
587
+ /**
588
+ * Simplified inline memory retrieval for chat.message injection.
589
+ * Queries entities with high weight directly from PG.
590
+ */
591
+ async function retrieveFactsForInjection(sessionId, budget, pool, config) {
592
+ const facts = [];
593
+ const maxTokens = budget.maxTokens || 2000;
594
+ let usedTokens = 0;
595
+ try {
596
+ const { rows } = await pool.query(`
597
+ SELECT name, type, tier, weight, description, confidence,
598
+ EXTRACT(EPOCH FROM (NOW() - last_seen_at)) / 86400.0 AS days_ago
599
+ FROM entities
600
+ WHERE weight >= $1 AND confidence >= $2
601
+ ORDER BY tier = 'permanent' DESC, tier = 'project' DESC, weight DESC
602
+ LIMIT 20
603
+ `, [config?.minWeight || 0.5, config?.minConfidence || 0.5]);
604
+ for (const row of rows) {
605
+ const content = row.description
606
+ ? `${row.name}: ${row.description.substring(0, 150)}`
607
+ : `${row.name} (${row.type})`;
608
+ const tokens = Math.ceil(content.length / 4);
609
+ if (usedTokens + tokens > maxTokens)
610
+ break;
611
+ usedTokens += tokens;
612
+ const recency = Math.max(0, 1 - (row.days_ago || 0) / 90);
613
+ facts.push({
614
+ type: 'entity',
615
+ content,
616
+ tier: row.tier,
617
+ tokens,
618
+ relevanceScore: (row.weight / 10) * 0.6 + recency * 0.4,
619
+ metadata: { entityName: row.name, entityType: row.type, tier: row.tier },
620
+ });
621
+ }
622
+ }
623
+ catch (err) {
624
+ // Non-blocking fallback - return empty
625
+ }
626
+ return facts;
627
+ }
628
+ // ============================================================================
629
+ // Re-exports (types and utilities for external consumers)
630
+ // NOTE: Selective exports to avoid ambiguity with ./types
631
+ // ============================================================================
632
+ __exportStar(require("./types"), exports);
633
+ __exportStar(require("./db/init-db"), exports);
634
+ __exportStar(require("./utils/token-budget"), exports);
635
+ __exportStar(require("./cache/semantic-cache"), exports);
636
+ var recall_memory_2 = require("./mcp/recall-memory");
637
+ Object.defineProperty(exports, "recallMemory", { enumerable: true, get: function () { return recall_memory_2.recallMemory; } });
638
+ var hindsight_reflect_2 = require("./mcp/hindsight-reflect");
639
+ Object.defineProperty(exports, "hindsightReflect", { enumerable: true, get: function () { return hindsight_reflect_2.hindsightReflect; } });
640
+ // Default export for OpenCode plugin loader
641
+ exports.default = exports.OpenCodePGMemory;
642
+ //# sourceMappingURL=index.js.map